diff --git a/.github/workflows/compile-common-image-lilygo-2CAN.yml b/.github/workflows/compile-common-image-lilygo-2CAN.yml
deleted file mode 100644
index 220699280..000000000
--- a/.github/workflows/compile-common-image-lilygo-2CAN.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: ๐ Compile Common Image for Lilygo 2CAN
-
-on:
- # The workflow is run when a commit is pushed or for a
- # Pull Request.
- - push
- - pull_request
-
-# This is the list of jobs that will be run concurrently.
-jobs:
- # This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
- skip-duplicate-actions:
- runs-on: ubuntu-latest
- # Map a step output to a job output
- outputs:
- should_skip: ${{ steps.skip_check.outputs.should_skip }}
- steps:
- - id: skip_check
- uses: fkirc/skip-duplicate-actions@v5
- with:
- # All of these options are optional, so you can remove them if you are happy with the defaults
- concurrent_skipping: 'never'
- skip_after_successful_duplicate: 'true'
- do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
-
- build-common-image:
- # This is the platform GitHub will use to run our workflow.
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v5
- name: Checkout code
-
- - uses: actions/cache@v4
- with:
- path: |
- ~/.cache/pip
- ~/.platformio/.cache
- key: ${{ runner.os }}-pio
-
- - uses: actions/setup-python@v6
- with:
- python-version: '3.11'
- - name: Install PlatformIO Core
- run: pip install --upgrade platformio
-
- - name: Build image for Lilygo
- run: pio run -e lilygo_2CAN_330
-
- - name: Upload artifact
- uses: actions/upload-artifact@v4
- with:
- name: battery-emulator-lilygo.bin
- path: .pio/build/lilygo_2CAN_330/firmware.bin
diff --git a/.github/workflows/compile-common-image-lilygo-TCAN.yml b/.github/workflows/compile-common-image-lilygo-TCAN.yml
deleted file mode 100644
index d96c28718..000000000
--- a/.github/workflows/compile-common-image-lilygo-TCAN.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: ๐ Compile Common Image for Lilygo T-CAN
-
-on:
- # The workflow is run when a commit is pushed or for a
- # Pull Request.
- - push
- - pull_request
-
-# This is the list of jobs that will be run concurrently.
-jobs:
- # This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
- skip-duplicate-actions:
- runs-on: ubuntu-latest
- # Map a step output to a job output
- outputs:
- should_skip: ${{ steps.skip_check.outputs.should_skip }}
- steps:
- - id: skip_check
- uses: fkirc/skip-duplicate-actions@v5
- with:
- # All of these options are optional, so you can remove them if you are happy with the defaults
- concurrent_skipping: 'never'
- skip_after_successful_duplicate: 'true'
- do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
-
- build-common-image:
- # This is the platform GitHub will use to run our workflow.
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v5
- name: Checkout code
-
- - uses: actions/cache@v4
- with:
- path: |
- ~/.cache/pip
- ~/.platformio/.cache
- key: ${{ runner.os }}-pio
-
- - uses: actions/setup-python@v6
- with:
- python-version: '3.11'
- - name: Install PlatformIO Core
- run: pip install --upgrade platformio
-
- - name: Build image for Lilygo
- run: pio run -e lilygo_330
-
- - name: Upload artifact
- uses: actions/upload-artifact@v4
- with:
- name: battery-emulator-lilygo.bin
- path: .pio/build/lilygo_330/firmware.bin
diff --git a/.github/workflows/compile-common-image-stark.yml b/.github/workflows/compile-common-image-stark.yml
deleted file mode 100644
index 06a08a07e..000000000
--- a/.github/workflows/compile-common-image-stark.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: ๐ Compile Common Image for Stark CMR
-
-on:
- # The workflow is run when a commit is pushed or for a
- # Pull Request.
- - push
- - pull_request
-
-# This is the list of jobs that will be run concurrently.
-jobs:
- # This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
- skip-duplicate-actions:
- runs-on: ubuntu-latest
- # Map a step output to a job output
- outputs:
- should_skip: ${{ steps.skip_check.outputs.should_skip }}
- steps:
- - id: skip_check
- uses: fkirc/skip-duplicate-actions@v5
- with:
- # All of these options are optional, so you can remove them if you are happy with the defaults
- concurrent_skipping: 'never'
- skip_after_successful_duplicate: 'true'
- do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
-
- build-common-image:
- # This is the platform GitHub will use to run our workflow.
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v5
- name: Checkout code
-
- - uses: actions/cache@v4
- with:
- path: |
- ~/.cache/pip
- ~/.platformio/.cache
- key: ${{ runner.os }}-pio
-
- - uses: actions/setup-python@v6
- with:
- python-version: '3.11'
- - name: Install PlatformIO Core
- run: pip install --upgrade platformio
-
- - name: Build image for Stark CMR
- run: pio run -e stark_330
-
- - name: Upload artifact
- uses: actions/upload-artifact@v4
- with:
- name: battery-emulator-stark.bin
- path: .pio/build/stark_330/firmware.bin
diff --git a/.github/workflows/compile-common-image.yml b/.github/workflows/compile-common-image.yml
new file mode 100644
index 000000000..0b649c38b
--- /dev/null
+++ b/.github/workflows/compile-common-image.yml
@@ -0,0 +1,77 @@
+name: ๐ Compile Common Images
+
+on:
+ # The workflow is run when a commit is pushed or for a
+ # Pull Request.
+ - push
+ - pull_request
+
+# This is the list of jobs that will be run concurrently.
+jobs:
+ # This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
+ skip-duplicate-actions:
+ runs-on: ubuntu-latest
+ # Map a step output to a job output
+ outputs:
+ should_skip: ${{ steps.skip_check.outputs.should_skip }}
+ steps:
+ - id: skip_check
+ uses: fkirc/skip-duplicate-actions@v5
+ with:
+ concurrent_skipping: 'never'
+ skip_after_successful_duplicate: 'true'
+ do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
+
+ build:
+ needs: skip-duplicate-actions
+ if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ include:
+ - board_name: "LilygoT-CAN485"
+ pio_env: "lilygo_330"
+ artifact_name: "battery-emulator-lilygo-t-can485"
+ - board_name: "Stark CMR"
+ pio_env: "stark_330"
+ artifact_name: "battery-emulator-stark"
+ - board_name: "LilygoT-2CAN"
+ pio_env: "lilygo_2CAN_330"
+ artifact_name: "battery-emulator-lilygo-t-2can"
+ - board_name: "ESP32 DevKit V1"
+ pio_env: "esp32devkit_330"
+ artifact_name: "battery-emulator-esp32devkit"
+ - board_name: "BECom"
+ pio_env: "BECom_330"
+ artifact_name: "battery-emulator-becom"
+ - board_name: "Compiler warning check"
+ pio_env: "compiler_warning_check"
+ artifact_name: "battery-dummy-check"
+
+ steps:
+ - uses: actions/checkout@v6
+ name: Checkout code
+
+ - uses: actions/cache@v5
+ with:
+ path: |
+ ~/.cache/pip
+ ~/.platformio/.cache
+ key: ${{ runner.os }}-pio
+
+ - uses: actions/setup-python@v6
+ with:
+ python-version: '3.11'
+
+ - name: Install PlatformIO Core
+ run: pip install --upgrade platformio
+
+ - name: Build image for ${{ matrix.board_name }}
+ run: pio run -e ${{ matrix.pio_env }}
+
+ - name: Upload artifact for ${{ matrix.board_name }}
+ uses: actions/upload-artifact@v6
+ with:
+ name: ${{ matrix.artifact_name }}.bin
+ path: .pio/build/${{ matrix.pio_env }}/firmware.bin
diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml
index 74772519a..e297d6a4c 100644
--- a/.github/workflows/release-assets.yml
+++ b/.github/workflows/release-assets.yml
@@ -25,7 +25,7 @@ jobs:
fi
- name: Checkout code at tag
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: refs/tags/${{ steps.vars.outputs.tag }}
@@ -34,7 +34,7 @@ jobs:
mkdir output
echo "Built for tag ${{ steps.vars.outputs.tag }}" > output/info.txt
- - uses: actions/cache@v4
+ - uses: actions/cache@v5
with:
path: |
~/.cache/pip
@@ -45,7 +45,7 @@ jobs:
with:
python-version: '3.11'
- name: Install PlatformIO Core
- run: pip install --upgrade platformio
+ run: pip install --upgrade platformio esptool
- name: ๐ Build ota image for Lilygo T-CAN
run: |
@@ -54,7 +54,7 @@ jobs:
- name: ๐ Build factory image for Lilygo T-CAN
run: |
- esptool --chip esp32 merge-bin -o .pio/build/lilygo_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 4MB 0x1000 .pio/build/lilygo_330/bootloader.bin 0x8000 .pio/build/lilygo_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_330/firmware.bin
+ esptool --chip esp32 merge-bin -o .pio/build/lilygo_330/factory.bin --flash-mode dio --flash-freq 80m --flash-size 4MB 0x1000 .pio/build/lilygo_330/bootloader.bin 0x8000 .pio/build/lilygo_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_330/firmware.bin
mv .pio/build/lilygo_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-CAN485.factory.bin
- name: ๐ Build ota image for Lilygo 2-CAN
@@ -64,7 +64,7 @@ jobs:
- name: ๐ Build factory image for Lilygo 2-CAN
run: |
- esptool --chip esp32s3 merge-bin -o .pio/build/lilygo_2CAN_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 16MB 0x0000 .pio/build/lilygo_2CAN_330/bootloader.bin 0x8000 .pio/build/lilygo_2CAN_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_2CAN_330/firmware.bin
+ esptool --chip esp32s3 merge-bin -o .pio/build/lilygo_2CAN_330/factory.bin --flash-mode dio --flash-freq 80m --flash-size 16MB 0x0000 .pio/build/lilygo_2CAN_330/bootloader.bin 0x8000 .pio/build/lilygo_2CAN_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_2CAN_330/firmware.bin
mv .pio/build/lilygo_2CAN_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-2CAN.factory.bin
- name: ๐ Build ota image for Stark
@@ -74,7 +74,7 @@ jobs:
- name: ๐ Build factory image for Stark
run: |
- esptool --chip esp32 merge-bin -o .pio/build/stark_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 4MB 0x1000 .pio/build/stark_330/bootloader.bin 0x8000 .pio/build/stark_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/stark_330/firmware.bin
+ esptool --chip esp32 merge-bin -o .pio/build/stark_330/factory.bin --flash-mode dio --flash-freq 80m --flash-size 8MB 0x1000 .pio/build/stark_330/bootloader.bin 0x8000 .pio/build/stark_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/stark_330/firmware.bin
mv .pio/build/stark_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_StarkCMR.factory.bin
- name: ๐ Deploy to Web Installer repo
@@ -87,7 +87,7 @@ jobs:
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
mkdir -p images/${{ steps.vars.outputs.tag }}
- cp ../output/*.factory.bin images/${{ steps.vars.outputs.tag }}/
+ cp ../output/*.factory.bin ../output/*.ota.bin images/${{ steps.vars.outputs.tag }}/
git add images
git commit -m "Deploy from GitHub Actions"
git push origin main
diff --git a/.github/workflows/run-pre-commit.yml b/.github/workflows/run-pre-commit.yml
index 869545bae..1c6f697b4 100644
--- a/.github/workflows/run-pre-commit.yml
+++ b/.github/workflows/run-pre-commit.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Run pre-commit
uses: pre-commit/action@v3.0.1
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 081538cc8..f73a1dc1b 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -9,7 +9,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Configure and build with CMake
run: |
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3bbaee3e0..78546410b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,7 +10,7 @@ ci:
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v21.1.2
+ rev: v21.1.8
hooks:
- id: clang-format
args: [-Werror] # change formatting warnings to errors, hook includes -i (Inplace edit) by default
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index be0f22c51..763bc2059 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -108,18 +108,27 @@ sudo make
```
## Downloading a pull request build to test locally ๐
-If you want to help test a new feature that is only available in an open pull request, you can download the precompiled binaries from the build system. To do this,start by clicking on the "**Checks**" tab
+If you want to help test a new feature that is only available in an open pull request, you can download the precompiled binaries from the build system.
-
+First, login to Github. If you don't have an account already, create one.
-Then select which hardware you need the binaries for. Currently we build for these hardwares:
+Start by clicking on the "**Checks**" tab
+
+
+
+Then click the down arrow next to the "Compile Common Images" selection, and then click on the hardware you need the binaries for. Currently we build for these hardwares:
- LilyGo T-CAN485
- Stark CMR
- LilyGo T-2CAN
+- ESP32 Devkit
+- BECom
+
+
+
+After selecting the hardware you need, click the "**Upload Artifact**", and there will be a download link. Download the file!
-
+
-After selecting the hardware you need, click the "**Upload Artifact**", and there will be a download link. Download the file, and [OTA Update](https://github.com/dalathegreat/Battery-Emulator/wiki/OTA-Update) your device with this file!
+After downloading the .zip file, extract the .bin file from it. Then [OTA Update](https://github.com/dalathegreat/Battery-Emulator/wiki/OTA-Update) your device with this .bin file!
-
diff --git a/Software/Software.cpp b/Software/Software.cpp
index 2e2f75cbe..3497e8a33 100644
--- a/Software/Software.cpp
+++ b/Software/Software.cpp
@@ -16,6 +16,8 @@
#include "src/communication/precharge_control/precharge_control.h"
#include "src/communication/rs485/comm_rs485.h"
#include "src/datalayer/datalayer.h"
+#include "src/devboard/display/display.h"
+#include "src/devboard/espnow/espnow.h"
#include "src/devboard/mqtt/mqtt.h"
#include "src/devboard/sdcard/sdcard.h"
#include "src/devboard/utils/events.h"
@@ -25,16 +27,18 @@
#include "src/devboard/utils/timer.h"
#include "src/devboard/utils/types.h"
#include "src/devboard/utils/value_mapping.h"
+#include "src/devboard/utils/watchdog.h"
#include "src/devboard/webserver/webserver.h"
#include "src/devboard/wifi/wifi.h"
#include "src/inverter/INVERTERS.h"
-#if !defined(HW_LILYGO) && !defined(HW_LILYGO2CAN) && !defined(HW_STARK) && !defined(HW_3LB) && !defined(HW_DEVKIT)
+#if !defined(HW_LILYGO) && !defined(HW_LILYGO2CAN) && !defined(HW_STARK) && !defined(HW_3LB) && !defined(HW_BECOM) && \
+ !defined(HW_DEVKIT)
#error You must select a target hardware!
#endif
// The current software version, shown on webserver
-const char* version_number = "9.2.dev";
+const char* version_number = "10.2.dev_esp32s3";
// Interval timers
volatile unsigned long currentMillis = 0;
@@ -49,14 +53,10 @@ TaskHandle_t main_loop_task;
TaskHandle_t connectivity_loop_task;
TaskHandle_t logging_loop_task;
TaskHandle_t mqtt_loop_task;
+Watchdog mqtt_loop_watchdog;
Logging logging;
-std::string mqtt_user; //TODO, move?
-std::string mqtt_password; //TODO, move?
-std::string http_username; //TODO, move?
-std::string http_password; //TODO, move?
-
static std::list transmitters;
void register_transmitter(Transmitter* transmitter) {
transmitters.push_back(transmitter);
@@ -67,7 +67,7 @@ void register_transmitter(Transmitter* transmitter) {
void init_serial() {
// Init Serial monitor
Serial.begin(115200);
-#if HW_LILYGO2CAN
+#if (HW_LILYGO2CAN || HW_BECOM)
// Wait up to 100ms for Serial to be available. On the ESP32S3 Serial is
// provided by the USB controller, so will only work if the board is connected
// to a computer.
@@ -92,25 +92,47 @@ void connectivity_loop(void*) {
init_mDNS();
}
+ init_display();
+
+ if (espnow_enabled) {
+ init_espnow();
+ }
+
while (true) {
START_TIME_MEASUREMENT(wifi);
wifi_monitor();
+ update_display();
+
+ if (espnow_enabled) {
+ update_espnow();
+ }
+
ota_monitor();
END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us);
+ mqtt_loop_watchdog.panic_if_exceeded_ms(60000, "MQTT task watchdog reset triggered!");
+
esp_task_wdt_reset(); // Reset watchdog
delay(1);
}
}
void logging_loop(void*) {
+ bool sd_initialized = false;
init_logging_buffers();
- init_sdcard();
+ sd_initialized = init_sdcard();
- while (true) {
+ // If the SD failed to init (delete the buffers and disable SD logging)
+ if (!sd_initialized) {
+ deinit_logging_buffers();
+ datalayer.system.info.SD_logging_active = false;
+ datalayer.system.info.CAN_SD_logging_active = false;
+ }
+
+ while (sd_initialized) {
if (datalayer.system.info.SD_logging_active) {
write_log_to_sdcard();
}
@@ -119,25 +141,68 @@ void logging_loop(void*) {
write_can_frame_to_sdcard();
}
}
+ // Delete the logging task only if SD failed to initialize to prevent panic.
+ vTaskDelete(NULL);
}
-void check_interconnect_available() {
- if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) {
- return; // Both voltage values need to be available to start check
+void check_interconnect_available(uint8_t batteryNumber) {
+
+ if (batteryNumber == 2) {
+ if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) {
+ return; // Both voltage values need to be available to start check
+ }
+ uint16_t voltage_diff_battery2_towards_main =
+ abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV);
+ uint8_t secondsOutOfVoltageSyncBattery2 = 0;
+
+ if (voltage_diff_battery2_towards_main <= 15) { // If we are within 1.5V between the batteries
+ clear_event(EVENT_VOLTAGE_DIFFERENCE);
+ secondsOutOfVoltageSyncBattery2 = 0;
+ if (datalayer.battery.status.bms_status == FAULT) {
+ // If main battery is in fault state, disengage the second battery
+ datalayer.system.status.battery2_allowed_contactor_closing = false;
+ } else { // If main battery is OK, allow second battery to join
+ datalayer.system.status.battery2_allowed_contactor_closing = true;
+ }
+ } else { //Voltage between the two packs is too large
+ set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff_battery2_towards_main / 10));
+
+ //If we start to drift out of sync between the two packs for more than 10 seconds, open contactors
+ if (secondsOutOfVoltageSyncBattery2 < 10) {
+ secondsOutOfVoltageSyncBattery2++;
+ } else {
+ datalayer.system.status.battery2_allowed_contactor_closing = false;
+ }
+ }
}
- uint16_t voltage_diff = abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV);
+ if (batteryNumber == 3) {
+ if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery3.status.voltage_dV == 0) {
+ return; // Both voltage values need to be available to start check
+ }
+ uint16_t voltage_diff_battery3_towards_main =
+ abs(datalayer.battery.status.voltage_dV - datalayer.battery3.status.voltage_dV);
+ uint8_t secondsOutOfVoltageSyncBattery3 = 0;
+
+ if (voltage_diff_battery3_towards_main <= 15) { // If we are within 1.5V between the batteries
+ clear_event(EVENT_VOLTAGE_DIFFERENCE);
+ secondsOutOfVoltageSyncBattery3 = 0;
+ if (datalayer.battery.status.bms_status == FAULT) {
+ // If main battery is in fault state, disengage the second battery
+ datalayer.system.status.battery3_allowed_contactor_closing = false;
+ } else { // If main battery is OK, allow second battery to join
+ datalayer.system.status.battery3_allowed_contactor_closing = true;
+ }
+ } else { //Voltage between the two packs is too large
+ set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff_battery3_towards_main / 10));
- if (voltage_diff <= 30) { // If we are within 3.0V between the batteries
- clear_event(EVENT_VOLTAGE_DIFFERENCE);
- if (datalayer.battery.status.bms_status == FAULT) {
- // If main battery is in fault state, disengage the second battery
- datalayer.system.status.battery2_allowed_contactor_closing = false;
- } else { // If main battery is OK, allow second battery to join
- datalayer.system.status.battery2_allowed_contactor_closing = true;
+ //If we start to drift out of sync between the two packs for more than 10 seconds, open contactors
+ if (secondsOutOfVoltageSyncBattery3 < 10) {
+ secondsOutOfVoltageSyncBattery3++;
+ } else {
+ datalayer.system.status.battery3_allowed_contactor_closing = false;
+ }
}
- } else { //Voltage between the two packs is too large
- set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10));
}
}
@@ -152,6 +217,9 @@ void update_calculated_values(unsigned long currentMillis) {
datalayer.system.info.CPU_temperature = temp.temp;
}
+ /*Update free heap*/
+ datalayer.system.info.CPU_free_heap = ESP.getFreeHeap();
+
/* Check is remote set limits have timed out */
if (currentMillis > datalayer.battery.settings.remote_set_timestamp + datalayer.battery.settings.remote_set_timeout) {
datalayer.battery.settings.remote_settings_limit_charge = false;
@@ -199,6 +267,18 @@ void update_calculated_values(unsigned long currentMillis) {
}
}
+ /* Calculate sum of all currents from all batteries*/
+ if (battery3) {
+ datalayer.battery.status.reported_current_dA =
+ (datalayer.battery.status.current_dA + datalayer.battery2.status.current_dA +
+ datalayer.battery3.status.current_dA);
+ } else if (battery2) {
+ datalayer.battery.status.reported_current_dA =
+ (datalayer.battery.status.current_dA + datalayer.battery2.status.current_dA);
+ } else { // Only one battery in use
+ datalayer.battery.status.reported_current_dA = datalayer.battery.status.current_dA;
+ }
+
/* Calculate active power based on voltage and current*/
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
@@ -237,6 +317,11 @@ void update_calculated_values(unsigned long currentMillis) {
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
}
+ if (battery3) {
+ /* Calculate active power based on voltage and current for battery 2*/
+ datalayer.battery3.status.active_power_W =
+ (datalayer.battery3.status.current_dA * (datalayer.battery3.status.voltage_dV / 100));
+ }
if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
@@ -311,27 +396,15 @@ void update_calculated_values(unsigned long currentMillis) {
}
}
- if (battery2) {
- // Perform extra SOC sanity checks on double battery setups
- if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average
- datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
- datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
- }
- if (datalayer.battery2.status.real_soc <
- 100) { //If this battery is under 1.00%, use this as SOC instead of average
+ //Check each extra battery, and if they are at the extremes, report the SOC from these batteries instead
+ if (battery2 && datalayer.system.status.battery2_allowed_contactor_closing) { //Battery2 is in the mix
+ if ((datalayer.battery2.status.real_soc < 100) || (datalayer.battery2.status.real_soc > 9900)) {
datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
- datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
- }
-
- if (datalayer.battery.status.real_soc >
- 9900) { //If this battery is over 99.00%, use this as SOC instead of average
- datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
- datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
- if (datalayer.battery2.status.real_soc >
- 9900) { //If this battery is over 99.00%, use this as SOC instead of average
- datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
- datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
+ }
+ if (battery3 && datalayer.system.status.battery3_allowed_contactor_closing) { //Battery3 is in the mix
+ if ((datalayer.battery3.status.real_soc < 100) || (datalayer.battery3.status.real_soc > 9900)) {
+ datalayer.battery.status.reported_soc = datalayer.battery3.status.real_soc;
}
}
}
@@ -412,18 +485,12 @@ void core_loop(void*) {
START_TIME_MEASUREMENT(all);
START_TIME_MEASUREMENT(comm);
- monitor_equipment_stop_button();
-
// Input, Runs as fast as possible
receive_can(); // Receive CAN messages
receive_rs485(); // Process serial2 RS485 interface
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
- START_TIME_MEASUREMENT(ota);
- ElegantOTA.loop();
- END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
-
// Process
currentMillis = millis();
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
@@ -434,15 +501,20 @@ void core_loop(void*) {
previousMillis10ms = currentMillis;
if (datalayer.system.info.performance_measurement_active) {
START_TIME_MEASUREMENT(10ms);
- }
- led_exe();
- handle_contactors(); // Take care of startup precharge/contactor closing
- if (precharge_control_enabled) {
- handle_precharge_control(currentMillis); //Drive the hia4v1 via PWM
- }
-
- if (datalayer.system.info.performance_measurement_active) {
+ monitor_equipment_stop_button();
+ led_exe();
+ handle_contactors(); // Take care of startup precharge/contactor closing
+ if (precharge_control_enabled) {
+ handle_precharge_control(currentMillis); //Drive the hia4v1 via PWM
+ }
END_TIME_MEASUREMENT_MAX(10ms, datalayer.system.status.time_10ms_us);
+ } else { //Run 10ms tasks without timing it
+ monitor_equipment_stop_button();
+ led_exe();
+ handle_contactors(); // Take care of startup precharge/contactor closing
+ if (precharge_control_enabled) {
+ handle_precharge_control(currentMillis); //Drive the hia4v1 via PWM
+ }
}
}
@@ -460,7 +532,11 @@ void core_loop(void*) {
if (battery2) {
battery2->update_values();
- check_interconnect_available();
+ check_interconnect_available(2);
+ }
+ if (battery3) {
+ battery3->update_values();
+ check_interconnect_available(3);
}
update_calculated_values(currentMillis);
update_machineryprotection(); // Check safeties
@@ -476,15 +552,19 @@ void core_loop(void*) {
}
if (datalayer.system.info.performance_measurement_active) {
START_TIME_MEASUREMENT(cantx);
- }
- // Let all transmitter objects send their messages
- for (auto& transmitter : transmitters) {
- transmitter->transmit(currentMillis);
+ for (auto& transmitter : transmitters) {
+ transmitter->transmit(currentMillis);
+ }
+
+ END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
+ } else {
+ for (auto& transmitter : transmitters) {
+ transmitter->transmit(currentMillis);
+ }
}
if (datalayer.system.info.performance_measurement_active) {
- END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us);
if (datalayer.system.status.core_task_10s_max_us > datalayer.system.status.core_task_max_us) {
// Update worst case total time
@@ -494,13 +574,11 @@ void core_loop(void*) {
datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us;
datalayer.system.status.time_snap_values_us = datalayer.system.status.time_values_us;
datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_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_ota_us = 0;
datalayer.system.status.time_comm_us = 0;
datalayer.system.status.time_10ms_us = 0;
datalayer.system.status.time_values_us = 0;
@@ -516,14 +594,13 @@ void core_loop(void*) {
}
void mqtt_loop(void*) {
- esp_task_wdt_add(NULL); // Register this task with WDT
-
while (true) {
+ mqtt_loop_watchdog.update();
+
START_TIME_MEASUREMENT(mqtt);
mqtt_client_loop();
END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us);
- esp_task_wdt_reset(); // Reset watchdog
- delay(1);
+ delay(100);
}
}
@@ -559,7 +636,7 @@ void setup() {
setup_charger();
setup_inverter();
setup_battery();
- setup_can_shunt();
+ setup_shunt();
// Init CAN only after any CAN receivers have had a chance to register.
init_CAN();
@@ -574,16 +651,7 @@ void setup() {
check_reset_reason();
// Initialize Task Watchdog for subscribed tasks
-#ifdef CONFIG_IDF_TARGET_ESP32S3
-// ESP32-S3 uses older watchdog API
-#ifdef CONFIG_ESP_TASK_WDT
- // ESP-IDF will have already initialized it, so deinit and reinit with our settings.
- esp_task_wdt_deinit();
-#endif
- // 5s timeout, panic on timeout
- esp_task_wdt_init(INTERVAL_5_S / 1000, true);
-#else
- // ESP32 uses newer watchdog API with config struct
+ // ESP-IDF 5.x uses newer watchdog API with config struct (both ESP32 and ESP32-S3)
esp_task_wdt_config_t wdt_config = {// 5s should be enough for the connectivity tasks (which are all contending
// for the same core) to yield to each other and reset their watchdogs.
.timeout_ms = INTERVAL_5_S,
@@ -600,7 +668,6 @@ void setup() {
#else
// Otherwise initialize it for the first time.
esp_task_wdt_init(&wdt_config);
-#endif
#endif
// Start tasks
diff --git a/Software/USER_SETTINGS_ESP32S3.h b/Software/USER_SETTINGS_ESP32S3.h
index 50fadac36..69b1fb255 100644
--- a/Software/USER_SETTINGS_ESP32S3.h
+++ b/Software/USER_SETTINGS_ESP32S3.h
@@ -21,18 +21,18 @@ MODIFY THESE PIN DEFINITIONS TO MATCH YOUR HARDWARE SETUP
#define ESP32S3_CAN_RX_PIN 18
// CAN Add-on via MCP2515 (SPI)
-#define ESP32S3_MCP2515_SCK 12
-#define ESP32S3_MCP2515_MOSI 11
-#define ESP32S3_MCP2515_MISO 13
-#define ESP32S3_MCP2515_CS 10
-#define ESP32S3_MCP2515_INT 14
+#define ESP32S3_MCP2515_SCK 4
+#define ESP32S3_MCP2515_MOSI 6
+#define ESP32S3_MCP2515_MISO 5
+#define ESP32S3_MCP2515_CS 36
+#define ESP32S3_MCP2515_INT 37
// CANFD Add-on via MCP2517/2518 (SPI)
-#define ESP32S3_MCP2517_SCK 36
-#define ESP32S3_MCP2517_SDI 35
-#define ESP32S3_MCP2517_SDO 37
-#define ESP32S3_MCP2517_CS 38
-#define ESP32S3_MCP2517_INT 39
+#define ESP32S3_MCP2517_SCK 12
+#define ESP32S3_MCP2517_SDI 11
+#define ESP32S3_MCP2517_SDO 13
+#define ESP32S3_MCP2517_CS 10
+#define ESP32S3_MCP2517_INT 14
// Contactor Control
#define ESP32S3_POSITIVE_CONTACTOR_PIN 5
diff --git a/Software/src/battery/BATTERIES.cpp b/Software/src/battery/BATTERIES.cpp
index 957928fa4..4c909c69f 100644
--- a/Software/src/battery/BATTERIES.cpp
+++ b/Software/src/battery/BATTERIES.cpp
@@ -1,10 +1,13 @@
#include "BATTERIES.h"
#include "../datalayer/datalayer_extended.h"
+#include "../devboard/hal/hal.h"
+#include "../devboard/utils/logging.h"
#include "CanBattery.h"
#include "RS485Battery.h"
Battery* battery = nullptr;
Battery* battery2 = nullptr;
+Battery* battery3 = nullptr;
std::vector supported_battery_types() {
std::vector types;
@@ -26,6 +29,8 @@ const char* name_for_chemistry(battery_chemistry_enum chem) {
return "NCA";
case battery_chemistry_enum::NMC:
return "NMC";
+ case battery_chemistry_enum::ZEBRA:
+ return "Molten Salt";
default:
return nullptr;
}
@@ -55,10 +60,16 @@ const char* name_for_battery_type(BatteryType type) {
return ChademoBattery::Name;
case BatteryType::CmfaEv:
return CmfaEvBattery::Name;
+ case BatteryType::CmpSmartCar:
+ return CmpSmartCarBattery::Name;
+ case BatteryType::FordMachE:
+ return FordMachEBattery::Name;
case BatteryType::Foxess:
return FoxessBattery::Name;
case BatteryType::GeelyGeometryC:
return GeelyGeometryCBattery::Name;
+ case BatteryType::GrowattHvArk:
+ return GrowattHvArkBattery::Name;
case BatteryType::HyundaiIoniq28:
return HyundaiIoniq28Battery::Name;
case BatteryType::OrionBms:
@@ -81,8 +92,10 @@ const char* name_for_battery_type(BatteryType type) {
return KiaHyundaiHybridBattery::Name;
case BatteryType::Meb:
return MebBattery::Name;
+#ifndef SMALL_FLASH_DEVICE
case BatteryType::Mg5:
return Mg5Battery::Name;
+#endif
case BatteryType::MgHsPhev:
return MgHsPHEVBattery::Name;
case BatteryType::NissanLeaf:
@@ -117,8 +130,14 @@ const char* name_for_battery_type(BatteryType type) {
return TeslaModel3YBattery::Name;
case BatteryType::TeslaModelSX:
return TeslaModelSXBattery::Name;
+ case BatteryType::TeslaLegacy:
+ return TeslaLegacyBattery::Name;
case BatteryType::TestFake:
return TestFakeBattery::Name;
+ case BatteryType::ThinkCity:
+ return ThinkBattery::Name;
+ case BatteryType::GeelySea:
+ return GeelySeaBattery::Name;
case BatteryType::VolvoSpa:
return VolvoSpaBattery::Name;
case BatteryType::VolvoSpaHybrid:
@@ -132,8 +151,9 @@ const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum:
battery_chemistry_enum user_selected_battery_chemistry = battery_chemistry_default;
-BatteryType user_selected_battery_type = BatteryType::NissanLeaf;
+BatteryType user_selected_battery_type = BatteryType::None;
bool user_selected_second_battery = false;
+bool user_selected_triple_battery = false;
Battery* create_battery(BatteryType type) {
switch (type) {
@@ -155,10 +175,16 @@ Battery* create_battery(BatteryType type) {
return new ChademoBattery();
case BatteryType::CmfaEv:
return new CmfaEvBattery();
+ case BatteryType::CmpSmartCar:
+ return new CmpSmartCarBattery();
+ case BatteryType::FordMachE:
+ return new FordMachEBattery();
case BatteryType::Foxess:
return new FoxessBattery();
case BatteryType::GeelyGeometryC:
return new GeelyGeometryCBattery();
+ case BatteryType::GrowattHvArk:
+ return new GrowattHvArkBattery();
case BatteryType::HyundaiIoniq28:
return new HyundaiIoniq28Battery();
case BatteryType::OrionBms:
@@ -181,8 +207,10 @@ Battery* create_battery(BatteryType type) {
return new KiaHyundaiHybridBattery();
case BatteryType::Meb:
return new MebBattery();
+#ifndef SMALL_FLASH_DEVICE
case BatteryType::Mg5:
return new Mg5Battery();
+#endif
case BatteryType::MgHsPhev:
return new MgHsPHEVBattery();
case BatteryType::NissanLeaf:
@@ -214,11 +242,17 @@ Battery* create_battery(BatteryType type) {
case BatteryType::SimpBms:
return new SimpBmsBattery();
case BatteryType::TeslaModel3Y:
- return new TeslaModel3YBattery(user_selected_battery_chemistry);
+ return new TeslaModel3YBattery();
case BatteryType::TeslaModelSX:
return new TeslaModelSXBattery();
+ case BatteryType::TeslaLegacy:
+ return new TeslaLegacyBattery();
case BatteryType::TestFake:
return new TestFakeBattery();
+ case BatteryType::ThinkCity:
+ return new ThinkBattery();
+ case BatteryType::GeelySea:
+ return new GeelySeaBattery();
case BatteryType::VolvoSpa:
return new VolvoSpaBattery();
case BatteryType::VolvoSpaHybrid:
@@ -234,6 +268,11 @@ void setup_battery() {
return;
}
+ // Set the chemistry to the user selected value, the battery can override.
+ datalayer.battery.info.chemistry = user_selected_battery_chemistry;
+ datalayer.battery2.info.chemistry = user_selected_battery_chemistry;
+ datalayer.battery3.info.chemistry = user_selected_battery_chemistry;
+
battery = create_battery(user_selected_battery_type);
if (battery) {
@@ -242,6 +281,9 @@ void setup_battery() {
if (user_selected_second_battery && !battery2) {
switch (user_selected_battery_type) {
+ case BatteryType::BydAtto3:
+ battery2 = new BydAttoBattery(&datalayer.battery2, nullptr, can_config.battery_double);
+ break;
case BatteryType::NissanLeaf:
battery2 = new NissanLeafBattery(&datalayer.battery2, nullptr, can_config.battery_double);
break;
@@ -252,14 +294,23 @@ void setup_battery() {
case BatteryType::CmfaEv:
battery2 = new CmfaEvBattery(&datalayer.battery2, nullptr, can_config.battery_double);
break;
+ case BatteryType::CmpSmartCar:
+ battery2 = new CmpSmartCarBattery(&datalayer.battery2, nullptr, can_config.battery_double);
+ break;
case BatteryType::KiaHyundai64:
battery2 = new KiaHyundai64Battery(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2,
&datalayer.system.status.battery2_allowed_contactor_closing,
can_config.battery_double);
break;
+ case BatteryType::Pylon:
+ battery2 = new PylonBattery(&datalayer.battery2, nullptr, can_config.battery_double);
+ break;
case BatteryType::SantaFePhev:
battery2 = new SantaFePhevBattery(&datalayer.battery2, can_config.battery_double);
break;
+ case BatteryType::RelionBattery:
+ battery2 = new RelionBattery(&datalayer.battery2, can_config.battery_double);
+ break;
case BatteryType::RenaultZoe1:
battery2 = new RenaultZoeGen1Battery(&datalayer.battery2, nullptr, can_config.battery_double);
break;
@@ -278,6 +329,24 @@ void setup_battery() {
battery2->setup();
}
}
+
+ if (user_selected_triple_battery && !battery3) {
+ switch (user_selected_battery_type) {
+ case BatteryType::NissanLeaf:
+ battery3 = new NissanLeafBattery(&datalayer.battery3, nullptr, can_config.battery_triple);
+ break;
+ case BatteryType::RelionBattery:
+ battery3 = new RelionBattery(&datalayer.battery3, can_config.battery_triple);
+ break;
+ default:
+ DEBUG_PRINTF("User tried enabling triple battery on non-supported integration!\n");
+ break;
+ }
+
+ if (battery3) {
+ battery3->setup();
+ }
+ }
}
/* User-selected Nissan LEAF settings */
@@ -291,6 +360,7 @@ uint16_t user_selected_tesla_GTW_chassisType = 2;
uint16_t user_selected_tesla_GTW_packEnergy = 1;
/* User-selected EGMP+others settings */
bool user_selected_use_estimated_SOC = false;
+uint16_t user_selected_pylon_baudrate = 500;
// Use 0V for user selected cell/pack voltage defaults (On boot will be replaced with saved values from NVM)
uint16_t user_selected_max_pack_voltage_dV = 0;
diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h
index cb24124e4..82fffa495 100644
--- a/Software/src/battery/BATTERIES.h
+++ b/Software/src/battery/BATTERIES.h
@@ -4,12 +4,13 @@
class Battery;
-// Currently initialized objects for primary and secondary battery.
+// Currently initialized objects for primary/secondary/tertiary battery.
// Null value indicates that battery is not configured/initialized
extern Battery* battery;
extern Battery* battery2;
+extern Battery* battery3;
-void setup_can_shunt();
+void setup_shunt();
#include "BMW-I3-BATTERY.h"
#include "BMW-IX-BATTERY.h"
@@ -21,10 +22,14 @@ void setup_can_shunt();
#include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h"
#include "CMFA-EV-BATTERY.h"
+#include "CMP-SMART-CAR-BATTERY.h"
#include "DALY-BMS.h"
#include "ECMP-BATTERY.h"
+#include "FORD-MACH-E-BATTERY.h"
#include "FOXESS-BATTERY.h"
#include "GEELY-GEOMETRY-C-BATTERY.h"
+#include "GEELY-SEA-BATTERY.h"
+#include "GROWATT-HV-ARK-BATTERY.h"
#include "HYUNDAI-IONIQ-28-BATTERY.h"
#include "IMIEV-CZERO-ION-BATTERY.h"
#include "JAGUAR-IPACE-BATTERY.h"
@@ -51,7 +56,9 @@ void setup_can_shunt();
#include "SIMPBMS-BATTERY.h"
#include "SONO-BATTERY.h"
#include "TESLA-BATTERY.h"
+#include "TESLA-LEGACY-BATTERY.h"
#include "TEST-FAKE-BATTERY.h"
+#include "THINK-BATTERY.h"
#include "VOLVO-SPA-BATTERY.h"
#include "VOLVO-SPA-HYBRID-BATTERY.h"
@@ -70,5 +77,6 @@ extern bool user_selected_tesla_GTW_rightHandDrive;
extern uint16_t user_selected_tesla_GTW_mapRegion;
extern uint16_t user_selected_tesla_GTW_chassisType;
extern uint16_t user_selected_tesla_GTW_packEnergy;
+extern uint16_t user_selected_pylon_baudrate;
#endif
diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp
index 5ae9d5100..f0186f6cf 100644
--- a/Software/src/battery/BMW-I3-BATTERY.cpp
+++ b/Software/src/battery/BMW-I3-BATTERY.cpp
@@ -3,32 +3,15 @@
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
/* Do not change code below unless you are sure what you are doing */
-const unsigned char crc8_table[256] =
- { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
- 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0,
- 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0,
- 0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23,
- 0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40,
- 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B,
- 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B,
- 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8,
- 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D,
- 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC,
- 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B,
- 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C,
- 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB,
- 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47,
- 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
- 0xE3, 0xFE, 0xD9, 0xC4};
-
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
- crc = crc8_table[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
+ crc = crc8_table_SAE_J1850_ZER0[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
}
return crc;
}
@@ -41,13 +24,34 @@ uint8_t BmwI3Battery::increment_alive_counter(uint8_t counter) {
return counter;
}
+void BmwI3Battery::initiate_offline_balancing() {
+ UserRequestBalancing = REQUESTED;
+ UserRequestBalancingMillis = millis();
+}
+
+void BmwI3Battery::end_offline_balancing() {
+ UserRequestBalancing = NONE;
+ UserRequestBalancingMillis = 0;
+ cmdState = SOC;
+ battery_info_available = false;
+ datalayer_battery->status.bms_status = ACTIVE;
+}
+
void BmwI3Battery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
- if (datalayer.system.settings.equipment_stop_active == true) {
+ if (datalayer.system.info.equipment_stop_active == true || UserRequestBalancing == STARTING ||
+ UserRequestBalancing == EXECUTING) {
digitalWrite(wakeup_pin, LOW); // Turn off wakeup pin
} else if (millis() > INTERVAL_1_S) {
digitalWrite(wakeup_pin, HIGH); // Wake up the battery
}
+ // When balancing mode has stopped CAN, keep the alive counter refreshed
+ // so the safety check (EVENT_CAN_BATTERY_MISSING) does not trigger
+ if (UserRequestBalancing == EXECUTING) {
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.bms_status = STANDBY;
+ }
+
if (!battery_awake) {
return;
}
@@ -64,10 +68,15 @@ void BmwI3Battery::update_values() { //This function maps all the values fetche
datalayer_battery->status.soh_pptt = battery_soh * 100;
- datalayer_battery->status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
+ if (UserRequestBalancing == NONE) {
+ datalayer_battery->status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
- datalayer_battery->status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
+ datalayer_battery->status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
+ } else {
+ datalayer_battery->status.max_discharge_power_W = 0;
+ datalayer_battery->status.max_charge_power_W = 0;
+ }
datalayer_battery->status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
datalayer_battery->status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
@@ -108,7 +117,8 @@ void BmwI3Battery::update_values() { //This function maps all the values fetche
void BmwI3Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
- battery_awake = true;
+ // Set to true unless balancing is going on and battery is supposed to go to sleep
+ battery_awake = UserRequestBalancing != EXECUTING;
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)
@@ -145,7 +155,8 @@ void BmwI3Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh
break;
case 0x2BD: //BMS [100ms] Status diagnosis high voltage - 1
- battery_awake = true;
+ // Set to true unless balancing is going on and battery is supposed to go to sleep
+ battery_awake = UserRequestBalancing != EXECUTING;
if (!skipCRCCheck) {
if (calculateCRC(rx_frame, rx_frame.DLC, 0x15) != rx_frame.data.u8[0]) {
// If calculated CRC does not match transmitted CRC, increase CANerror counter
@@ -172,7 +183,8 @@ void BmwI3Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2);
break;
case 0x2FF: //BMS [100ms] Status Heating High-Voltage Battery
- battery_awake = true;
+ // Set to true unless balancing is going on and battery is supposed to go to sleep
+ battery_awake = UserRequestBalancing != EXECUTING;
battery_actual_value_power_heating = (rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4);
break;
case 0x363: //BMS [1s] Identification High-Voltage Battery
@@ -283,6 +295,8 @@ void BmwI3Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_soc_hvmin = (message_data[4] << 8 | message_data[5]);
}
break;
+ default:
+ break;
}
}
break;
@@ -343,6 +357,38 @@ void BmwI3Battery::transmit_can(unsigned long currentMillis) {
alive_counter_200ms = increment_alive_counter(alive_counter_200ms);
transmit_can_frame(&BMW_19B);
+
+ if (UserRequestBalancing != NONE && battery_info_available) {
+ switch (detectedBattery) {
+ case BATTERY_60AH:
+ transmit_can_frame(&BMW_3E9);
+ break;
+ case BATTERY_94AH:
+ BMW_3E9.data.u8[0] = 0x0B;
+ BMW_3E9.data.u8[1] = 0x81;
+ transmit_can_frame(&BMW_3E9);
+ break;
+ case BATTERY_120AH:
+ BMW_3E9.data.u8[0] = 0xD8;
+ BMW_3E9.data.u8[1] = 0xA4;
+ transmit_can_frame(&BMW_3E9);
+ break;
+ }
+
+ cmdState = OFF;
+ if (UserRequestBalancing == REQUESTED && currentMillis - UserRequestBalancingMillis > 20000) {
+ UserRequestBalancing = STARTING;
+ }
+ if (UserRequestBalancing == STARTING && currentMillis - UserRequestBalancingMillis > 30000) {
+ battery_awake = false;
+ UserRequestBalancing = EXECUTING;
+ set_event(EVENT_BALANCING_START, 0);
+ }
+ if (UserRequestBalancing == EXECUTING && battery_awake) {
+ set_event(EVENT_BALANCING_START, 1);
+ battery_awake = false;
+ }
+ }
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
@@ -417,6 +463,11 @@ void BmwI3Battery::transmit_can(unsigned long currentMillis) {
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
+ if (UserRequestDTCreset) {
+ cmdState = CLEAR_DTC;
+ UserRequestDTCreset = false;
+ }
+
next_data = 0;
switch (cmdState) {
case SOC:
@@ -448,6 +499,16 @@ void BmwI3Battery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&BMW_6F1_SOC);
cmdState = SOC;
break;
+ case CLEAR_DTC:
+ transmit_can_frame(&BMW_6F1_CLEAR_DTC);
+ cmdState = SOC; //jump back to normal polling
+ break;
+ case OFF:
+ break;
+ default:
+ //Should never end up here
+ cmdState = SOC;
+ break;
}
}
// Send 5000ms CAN Message
diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h
index adb42908c..5abfca6dd 100644
--- a/Software/src/battery/BMW-I3-BATTERY.h
+++ b/Software/src/battery/BMW-I3-BATTERY.h
@@ -35,6 +35,13 @@ class BmwI3Battery : public CanBattery {
virtual void transmit_can(unsigned long currentMillis);
static constexpr const char* Name = "BMW i3";
+ bool supports_offline_balancing() { return true; }
+ virtual void initiate_offline_balancing();
+ virtual void end_offline_balancing();
+
+ bool supports_reset_DTC() { return true; }
+ void reset_DTC() { UserRequestDTCreset = true; }
+
// SOC% raw battery value. Might not always reach 100%
uint16_t SOC_raw() { return (battery_HVBatt_SOC * 10); }
// SOC% instrumentation cluster value. Will always reach 100%
@@ -61,6 +68,8 @@ class BmwI3Battery : public CanBattery {
uint8_t ST_isolation() { return battery_status_warning_isolation; }
// Status cold shutoff valve, 0 OK, 1 Short circuit to GND, 2 Short circuit to 12V, 3 Line break, 6 Driver error, 12 Stuck, 13 Stuck, 15 Invalid Signal
uint8_t ST_cold_shutoff_valve() { return battery_status_cold_shutoff_valve; }
+ // Status balancing
+ uint8_t ST_balancing_status() { return UserRequestBalancing; }
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
@@ -68,6 +77,11 @@ class BmwI3Battery : public CanBattery {
BmwI3HtmlRenderer renderer;
private:
+ bool UserRequestDTCreset = false;
+ enum BalancingState { NONE, REQUESTED, STARTING, EXECUTING };
+ BalancingState UserRequestBalancing = NONE;
+ unsigned long UserRequestBalancingMillis = 0;
+
const int MAX_CELL_VOLTAGE_60AH = 4110; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_60AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_94AH = 4140; // Battery is put into emergency stop if one cell goes over this value
@@ -109,7 +123,7 @@ class BmwI3Battery : public CanBattery {
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
BatterySize detectedBattery = BATTERY_60AH;
- enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
+ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST, CLEAR_DTC, OFF };
CmdState cmdState = SOC;
@@ -202,6 +216,11 @@ class BmwI3Battery : public CanBattery {
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
+ CAN_frame BMW_3E9 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x3E9,
+ .data = {0x08, 0x52, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BMW_3EC = {.FD = false,
.ext_ID = false,
.DLC = 8,
@@ -279,6 +298,11 @@ class BmwI3Battery : public CanBattery {
.DLC = 4,
.ID = 0x6F1,
.data = {0x07, 0x30, 0x00, 0x02}};
+ static constexpr CAN_frame BMW_6F1_CLEAR_DTC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 6,
+ .ID = 0x6F1,
+ .data = {0xDF, 0x04, 0x14, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
.ext_ID = false,
.DLC = 7,
diff --git a/Software/src/battery/BMW-I3-HTML.cpp b/Software/src/battery/BMW-I3-HTML.cpp
index a0d8931ce..fcd3b3726 100644
--- a/Software/src/battery/BMW-I3-HTML.cpp
+++ b/Software/src/battery/BMW-I3-HTML.cpp
@@ -93,6 +93,24 @@ String BmwI3HtmlRenderer::get_status_html() {
"Invalid Signal"};
content +=
"Cold shutoff valve: " + String(safeArrayAccess(valveText, 16, batt.ST_cold_shutoff_valve())) + " ";
+ static const char* balancingText[16] = {"Not requested",
+ "Requested",
+ "Starting",
+ "Executing",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "10",
+ "11",
+ "12",
+ "13",
+ "14",
+ "15"};
+ content +=
+ "Balancing status: " + String(safeArrayAccess(balancingText, 16, batt.ST_balancing_status())) + " ";
return content;
}
diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp
index 32397ddf1..3a4305a67 100644
--- a/Software/src/battery/BMW-IX-BATTERY.cpp
+++ b/Software/src/battery/BMW-IX-BATTERY.cpp
@@ -1,5 +1,6 @@
#include "BMW-IX-BATTERY.h"
#include "../communication/can/comm_can.h"
+#include "../communication/contactorcontrol/comm_contactorcontrol.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
@@ -18,7 +19,7 @@ bool BmwIXBattery::isStale(int16_t currentValue, uint16_t& lastValue, unsigned l
}
// Check if the value has stayed the same for the specified staleness period
- return (currentTime - lastChangeTime >= STALE_PERIOD);
+ return (currentTime - lastChangeTime >= STALE_PERIOD_CONFIG);
}
uint8_t BmwIXBattery::increment_uds_req_id_counter(uint8_t index) {
@@ -37,6 +38,384 @@ uint8_t BmwIXBattery::increment_alive_counter(uint8_t counter) {
return counter;
}
+// UDS Multi-Frame Reception Helper Functions
+void BmwIXBattery::startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) {
+ gUDSContext.UDS_inProgress = true;
+ gUDSContext.UDS_expectedLength = totalLength;
+ gUDSContext.UDS_bytesReceived = 0;
+ gUDSContext.UDS_sequenceNumber = 1; // Next expected sequence is 1
+ gUDSContext.UDS_moduleID = moduleID;
+ memset(gUDSContext.UDS_buffer, 0, sizeof(gUDSContext.UDS_buffer));
+ gUDSContext.UDS_lastFrameMillis = millis(); // Track timeout
+}
+
+bool BmwIXBattery::storeUDSPayload(const uint8_t* payload, uint8_t length) {
+ if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) {
+ logging.println("UDS buffer overflow prevented");
+ gUDSContext.UDS_inProgress = false;
+ return false;
+ }
+
+ memcpy(&gUDSContext.UDS_buffer[gUDSContext.UDS_bytesReceived], payload, length);
+ gUDSContext.UDS_bytesReceived += length;
+ gUDSContext.UDS_lastFrameMillis = millis();
+
+ // If we've reached or exceeded the expected length, mark complete
+ if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) {
+ gUDSContext.UDS_inProgress = false;
+ }
+ return true;
+}
+
+bool BmwIXBattery::isUDSMessageComplete() {
+ return (!gUDSContext.UDS_inProgress && gUDSContext.UDS_bytesReceived > 0);
+}
+CAN_frame BmwIXBattery::generate_433_datetime_message() {
+ CAN_frame frame_433;
+ frame_433.ID = 0x433;
+ frame_433.DLC = 8;
+ frame_433.ext_ID = false;
+ frame_433.FD = true;
+ // Hardcoded reference start time: 2025-02-21 17:00:00
+ const uint16_t startYear = 2025;
+ const uint8_t startMonth = 2;
+ const uint8_t startDay = 21;
+ const uint8_t startHour = 17;
+ const uint8_t startMinute = 0;
+ const uint8_t startSecond = 0;
+
+ // Calculate elapsed time since boot in seconds
+ unsigned long elapsedSeconds = millis() / 1000;
+
+ // Add elapsed seconds to reference time
+ uint32_t totalSeconds = startSecond + elapsedSeconds;
+ uint8_t second = totalSeconds % 60;
+ uint32_t totalMinutes = startMinute + (totalSeconds / 60);
+ uint8_t minute = totalMinutes % 60;
+ uint32_t totalHours = startHour + (totalMinutes / 60);
+ uint8_t hour = totalHours % 24;
+ uint32_t totalDays = startDay + (totalHours / 24);
+
+ // Simple month/year calculation (not accounting for varying month lengths)
+ // For production, you'd want a proper datetime library
+ uint8_t month = startMonth;
+ uint16_t year = startYear;
+
+ // Rough day overflow handling (assumes 30 days per month for simplicity)
+ while (totalDays > 30) {
+ totalDays -= 30;
+ month++;
+ if (month > 12) {
+ month = 1;
+ year++;
+ }
+ }
+ uint8_t day = totalDays;
+
+ // Byte 1: Hour (0-23)
+ frame_433.data.u8[0] = hour;
+
+ // Byte 2: Minute (0-59)
+ frame_433.data.u8[1] = minute;
+
+ // Byte 3: Second (0-59)
+ frame_433.data.u8[2] = second;
+
+ // Byte 4: Day of month (1-31)
+ frame_433.data.u8[3] = day;
+
+ // Byte 5: Month (upper nibble) + Year low digit (lower nibble)
+ // Month in upper 4 bits, year last digit in lower 4 bits
+ uint8_t yearLowDigit = year % 10;
+ frame_433.data.u8[4] = (month << 4) | yearLowDigit;
+
+ // Byte 6 + 7: Full year as 16-bit little-endian
+ frame_433.data.u8[5] = year & 0xFF; // Low byte
+ frame_433.data.u8[6] = (year >> 8) & 0xFF; // High byte
+
+ // Byte 8: Terminator/checksum (constant 0xF5 based on samples)
+ frame_433.data.u8[7] = 0xF5;
+
+ return frame_433;
+}
+CAN_frame BmwIXBattery::generate_442_time_counter_message() {
+ CAN_frame frame_442;
+ frame_442.ID = 0x442;
+ frame_442.DLC = 6;
+ frame_442.ext_ID = false;
+ frame_442.FD = true;
+
+ // Calculate elapsed time in seconds (counter increments at 1 Hz)
+ // millis() returns milliseconds, so divide by 1000 to get seconds
+ uint32_t timeCounter = millis() / 1000;
+
+ // Bytes 1-4: Time counter in little-endian format (seconds since boot)
+ frame_442.data.u8[0] = timeCounter & 0xFF; // LSB
+ frame_442.data.u8[1] = (timeCounter >> 8) & 0xFF;
+ frame_442.data.u8[2] = (timeCounter >> 16) & 0xFF;
+ frame_442.data.u8[3] = (timeCounter >> 24) & 0xFF; // MSB
+
+ // Bytes 5-6: Constant signature
+ frame_442.data.u8[4] = 0xE0;
+ frame_442.data.u8[5] = 0x23;
+
+ return frame_442;
+}
+void BmwIXBattery::parseDTCResponse() {
+ // Check for negative response
+ if (gUDSContext.UDS_buffer[0] == 0x7F) {
+ logging.print("DTC request rejected by battery. Reason code: 0x");
+ logging.print(gUDSContext.UDS_buffer[2], HEX);
+ logging.println();
+ datalayer_extended.bmwix.dtc_read_failed = true;
+ datalayer_extended.bmwix.dtc_read_in_progress = false;
+ return;
+ }
+
+ if (gUDSContext.UDS_buffer[0] != 0x59 || gUDSContext.UDS_buffer[1] != 0x02) {
+ logging.println("Invalid DTC response header");
+ datalayer_extended.bmwix.dtc_read_failed = true;
+ datalayer_extended.bmwix.dtc_read_in_progress = false;
+ return;
+ }
+
+ int dtcStartIndex = 3; // Skip 59 02 FF
+ int availableBytes = gUDSContext.UDS_bytesReceived - dtcStartIndex;
+ int maxDtcCount = availableBytes / 4;
+
+ if (maxDtcCount > MAX_DTC_COUNT) {
+ maxDtcCount = MAX_DTC_COUNT;
+ logging.println("DTC count exceeds buffer, truncating");
+ }
+
+ int validDtcCount = 0; // Track actual valid DTCs
+
+ logging.print("Parsing DTCs (max ");
+ logging.print(maxDtcCount);
+ logging.println("):");
+
+ for (int i = 0; i < maxDtcCount; i++) {
+ int offset = dtcStartIndex + (i * 4);
+
+ // Bounds check
+ if (offset + 3 > gUDSContext.UDS_bytesReceived) {
+ logging.println("DTC parsing: offset exceeds buffer, stopping");
+ break;
+ }
+
+ // Combine 3 bytes into single uint32
+ uint32_t dtcCode = ((uint32_t)gUDSContext.UDS_buffer[offset] << 16) |
+ ((uint32_t)gUDSContext.UDS_buffer[offset + 1] << 8) |
+ (uint32_t)gUDSContext.UDS_buffer[offset + 2];
+
+ uint8_t dtcStatus = gUDSContext.UDS_buffer[offset + 3];
+
+ // Skip invalid DTCs (0x000000 or status 0x00)
+ if (dtcCode == 0x000000 || dtcStatus == 0x00) {
+ logging.print(" Skipping invalid DTC at offset ");
+ logging.println(offset);
+ continue; // Don't store this one
+ }
+
+ // Store valid DTC
+ datalayer_extended.bmwix.dtc_codes[validDtcCount] = dtcCode;
+ datalayer_extended.bmwix.dtc_status[validDtcCount] = dtcStatus;
+
+ // Log each DTC for debugging
+ logging.print(" DTC #");
+ logging.print(validDtcCount + 1);
+ logging.print(": 0x");
+ if (dtcCode < 0x100000)
+ logging.print("0");
+ if (dtcCode < 0x10000)
+ logging.print("0");
+ if (dtcCode < 0x1000)
+ logging.print("0");
+ if (dtcCode < 0x100)
+ logging.print("0");
+ if (dtcCode < 0x10)
+ logging.print("0");
+ logging.print(dtcCode, HEX);
+ logging.print(" Status: 0x");
+ if (dtcStatus < 0x10)
+ logging.print("0");
+ logging.print(dtcStatus, HEX);
+ logging.println();
+
+ validDtcCount++; // Increment only for valid DTCs
+ }
+
+ datalayer_extended.bmwix.dtc_count = validDtcCount; // Store actual count
+
+ logging.print("Total valid DTCs: ");
+ logging.println(validDtcCount);
+
+ datalayer_extended.bmwix.dtc_last_read_millis = millis();
+ datalayer_extended.bmwix.dtc_read_failed = false;
+ datalayer_extended.bmwix.dtc_read_in_progress = false;
+}
+
+void BmwIXBattery::handleISOTPFrame(CAN_frame& rx_frame) {
+ uint8_t pciByte = rx_frame.data.u8[1]; // e.g., 0x10, 0x21, etc.
+ uint8_t pciType = pciByte >> 4; // top nibble => 0=SF,1=FF,2=CF,3=FC
+
+ // Only process multi-frame ISO-TP messages (FF and CF)
+ // Single-frame messages are handled directly in case 0x607
+ if (pciType != 0x1 && pciType != 0x2) {
+ return; // Not a multi-frame message we care about
+ }
+
+ switch (pciType) {
+ case 0x1: {
+ // First Frame (FF)
+ uint8_t pciLower = pciByte & 0x0F;
+ uint16_t totalLength = ((uint16_t)pciLower << 8) | rx_frame.data.u8[2];
+
+ uint8_t serviceResponse = rx_frame.data.u8[3]; // Service response byte (0x59, 0x62, etc.)
+ uint8_t moduleID;
+
+ // Determine which byte to use for module ID based on service response
+ if (serviceResponse == 0x59) {
+ // Standard UDS DTC response (0x19 -> 0x59)
+ // Use sub-function byte as module ID
+ moduleID = rx_frame.data.u8[4]; // 0x02 for reportDTCByStatusMask
+ } else {
+ // BMW proprietary responses (0x22 -> 0x62)
+ // Use parameter byte as module ID
+ moduleID = rx_frame.data.u8[5]; // 0x54, 0x53, etc.
+ }
+
+ // logging.print("FF arrived! moduleID=0x");
+ // logging.print(moduleID, HEX);
+ // logging.print(", totalLength=");
+ // logging.println(totalLength);
+
+ // Start the multi-frame reception
+ startUDSMultiFrameReception(totalLength, moduleID);
+ gUDSContext.receivedInBatch = 0; // Reset batch count
+
+ // Store the FF payload (starts at data[3] for extended addressing)
+ const uint8_t* ffPayload = &rx_frame.data.u8[3];
+ uint8_t ffPayloadSize = rx_frame.DLC - 3;
+ storeUDSPayload(ffPayload, ffPayloadSize);
+
+ // Request continuation
+ transmit_can_frame(&BMWiX_6F4_CONTINUE_DATA);
+ break;
+ }
+
+ case 0x2: {
+ // Consecutive Frame (CF)
+ if (!gUDSContext.UDS_inProgress) {
+ logging.println("Unexpected CF - not in progress");
+ return; // Unexpected CF, ignore
+ }
+
+ //uint8_t seq = pciByte & 0x0F;
+
+ // logging.print("CF seq=0x");
+ // logging.print(seq, HEX);
+ // logging.print(" for moduleID=0x");
+ // logging.println(gUDSContext.UDS_moduleID, HEX);
+
+ // Store CF payload (starts at byte 2)
+ storeUDSPayload(&rx_frame.data.u8[2], rx_frame.DLC - 2);
+
+ // Increment batch counter
+ gUDSContext.receivedInBatch++;
+
+ // logging.print("After CF, UDS_bytesReceived=");
+ // logging.println(gUDSContext.UDS_bytesReceived);
+
+ // Check if batch is complete (iX uses 2 frames per batch based on 0x30 0x00 0x02)
+ if (gUDSContext.receivedInBatch >= 2) {
+ //logging.println("Batch complete - requesting continue frame...");
+ transmit_can_frame(&BMWiX_6F4_CONTINUE_DATA);
+ gUDSContext.receivedInBatch = 0;
+ }
+ break;
+ }
+ }
+}
+
+void BmwIXBattery::processCompletedUDSResponse() {
+ uint8_t* buf = gUDSContext.UDS_buffer;
+ uint16_t len = gUDSContext.UDS_bytesReceived;
+
+ // Route based on moduleID (set during First Frame reception)
+ if (gUDSContext.UDS_moduleID == 0x02) {
+ // DTC Response (0x19 0x02 -> 0x59 0x02)
+ logging.println("=== DTC Response Received ===");
+ logging.print("Total bytes: ");
+ logging.println(len);
+ parseDTCResponse();
+
+ } else if (gUDSContext.UDS_moduleID == 0x54) {
+ // Cell Voltages (0x22 0xE5 0x54 -> 0x62 0xE5 0x54)
+ // logging.print("Parsing cell voltages - Total bytes: ");
+ // logging.println(len);
+ int voltage_index = 0;
+ for (int i = 3; i < len - 1; i += 2) {
+ if (voltage_index >= 108)
+ break;
+ uint16_t voltage = (buf[i] << 8) | buf[i + 1];
+ if (voltage < 10000) {
+ datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
+ }
+ voltage_index++;
+ }
+ // logging.print("Parsed ");
+ // logging.print(voltage_index);
+ // logging.println(" cell voltages");
+
+ } else if (gUDSContext.UDS_moduleID == 0xCE) {
+ // SOC Response (0x22 0xE5 0xCE -> 0x62 0xE5 0xCE)
+ if (len >= 9) {
+ avg_soc_state = (buf[3] << 8 | buf[4]);
+ min_soc_state = (buf[5] << 8 | buf[6]);
+ max_soc_state = (buf[7] << 8 | buf[8]);
+ logging.println("SOC data updated");
+ }
+
+ } else if (gUDSContext.UDS_moduleID == 0xCA) {
+ // Balancing Data (0x22 0xE4 0xCA -> 0x62 0xE4 0xCA)
+ if (len >= 4) {
+ balancing_status = buf[3];
+ logging.println("Balancing status updated");
+ }
+
+ // REMOVE THIS BLOCK - Isolation data comes as single CAN FD frame, not ISO-TP
+ // } else if (gUDSContext.UDS_moduleID == 0x60) {
+ // // Safety Isolation (0x22 0xA8 0x60 -> 0x62 0xA8 0x60)
+ // if (len >= 43) {
+ // iso_safety_positive = (buf[31] << 24) | (buf[32] << 16) | (buf[33] << 8) | buf[34];
+ // iso_safety_negative = (buf[35] << 24) | (buf[36] << 16) | (buf[37] << 8) | buf[38];
+ // iso_safety_parallel = (buf[39] << 24) | (buf[40] << 16) | (buf[41] << 8) | buf[42];
+ // logging.println("ISO safety data updated");
+ // }
+
+ } else if (gUDSContext.UDS_moduleID == 0xC0) {
+ // Uptime (0x22 0xE4 0xC0 -> 0x62 0xE4 0xC0)
+ if (len >= 11) {
+ sme_uptime = (buf[7] << 24) | (buf[8] << 16) | (buf[9] << 8) | buf[10];
+ logging.println("SME uptime updated");
+ }
+
+ } else if (gUDSContext.UDS_moduleID == 0x45) {
+ // SOH Data (0x22 0xE5 0x45 -> 0x62 0xE5 0x45)
+ if (len >= 11) {
+ min_soh_state = (buf[5] << 8 | buf[6]);
+ avg_soh_state = (buf[7] << 8 | buf[8]);
+ max_soh_state = (buf[9] << 8 | buf[10]);
+ logging.println("SOH data updated");
+ }
+ }
+
+ // Reset buffer after processing
+ gUDSContext.UDS_bytesReceived = 0;
+}
+
+/*
static uint8_t increment_C0_counter(uint8_t counter) {
counter++;
// Reset to 0xF0 if it exceeds 0xFE
@@ -45,7 +424,7 @@ static uint8_t increment_C0_counter(uint8_t counter) {
}
return counter;
}
-
+*/
void BmwIXBattery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = avg_soc_state;
@@ -63,22 +442,41 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
datalayer.battery.status.max_discharge_power_W =
datalayer.battery.status.override_discharge_power_W; //TODO: Estimated from UI
- // Estimated charge power is set in Settings page. Ramp power on top
- if (datalayer.battery.status.real_soc > 9900) {
- datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
- } else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
- // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
- datalayer.battery.status.max_charge_power_W =
- datalayer.battery.status.override_charge_power_W *
- (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
- } else { // No limits, max charging power allowed
- datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
- }
-
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
datalayer.battery.status.temperature_max_dC = max_battery_temperature;
+ // Calculate charge power limit based on multiple factors, taking the lowest value
+
+ // Factor 1: SOC-based limiting (linear ramp from RAMPDOWN_SOC to 100%)
+ int max_charge_power_soc = datalayer.battery.status.override_charge_power_W;
+ if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
+ // When real SOC is above RAMPDOWN_SOC, ramp the value linearly down to 0W at 100%
+ max_charge_power_soc = datalayer.battery.status.override_charge_power_W *
+ (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
+ // Ensure we never go negative (in case SOC exceeds 100%)
+ if (max_charge_power_soc < 0) {
+ max_charge_power_soc = 0;
+ }
+ }
+
+ // Factor 2: Temperature-based limiting (ramp from 0W at -10ยฐC to RAMPDOWN_TEMP_POWER_W at 5ยฐC)
+ int max_charge_power_temp = datalayer.battery.status.override_charge_power_W;
+
+ if (datalayer.battery.status.temperature_min_dC <= RAMPDOWN_TEMP_MIN_dC) {
+ // Below -10ยฐC: no charging allowed
+ max_charge_power_temp = 0;
+ } else if (datalayer.battery.status.temperature_min_dC < RAMPDOWN_TEMP_MAX_dC) {
+ // Between -10ยฐC and 5ยฐC: linear ramp from 0W to RAMPDOWN_TEMP_POWER_W
+ float ramp_percentage = (float)(datalayer.battery.status.temperature_min_dC - RAMPDOWN_TEMP_MIN_dC) /
+ (float)(RAMPDOWN_TEMP_MAX_dC - RAMPDOWN_TEMP_MIN_dC);
+ max_charge_power_temp = RAMPDOWN_TEMP_POWER_W * ramp_percentage;
+ }
+ // Above 5ยฐC: no temperature limitation
+
+ // Take the lowest of all limiting factors
+ datalayer.battery.status.max_charge_power_W = min(max_charge_power_soc, max_charge_power_temp);
+
//Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze.
bool isMinCellVoltageStale =
isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged);
@@ -98,26 +496,86 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
set_event(EVENT_12V_LOW, terminal30_12v_voltage);
}
+ // detect number of cells
if ((datalayer.battery.status.cell_voltages_mV[77] > 1000) &&
(datalayer.battery.status.cell_voltages_mV[78] < 1000)) {
//If we detect cellvoltage on cell78, but nothing on 79, we can confirm we are on SE12
- detected_number_of_cells = 78; //We are on 78S SE12 battery from BMW IX1
- } //Sidenote, detection of 96S and 108S batteries happen inside the cellvoltage reading blocks
+ detected_number_of_cells = 78; //We are on 78S SE12 battery from BMW iX1
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else if ((datalayer.battery.status.cell_voltages_mV[89] > 1000) &&
+ (datalayer.battery.status.cell_voltages_mV[90] < 1000)) {
+ detected_number_of_cells = 90;
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else if ((datalayer.battery.status.cell_voltages_mV[93] > 1000) &&
+ (datalayer.battery.status.cell_voltages_mV[94] < 1000)) {
+ detected_number_of_cells = 94;
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else if ((datalayer.battery.status.cell_voltages_mV[95] > 1000) &&
+ (datalayer.battery.status.cell_voltages_mV[96] < 1000)) {
+ detected_number_of_cells = 96;
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else if ((datalayer.battery.status.cell_voltages_mV[99] > 1000) &&
+ (datalayer.battery.status.cell_voltages_mV[100] < 1000)) {
+ detected_number_of_cells = 100;
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else if ((datalayer.battery.status.cell_voltages_mV[101] > 1000) &&
+ (datalayer.battery.status.cell_voltages_mV[102] < 1000)) {
+ detected_number_of_cells = 102;
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else if (datalayer.battery.status.cell_voltages_mV[107] > 1000) {
+ // voltage index cannot be larger than 107, therefore we only perform a check if we detect cellvoltage on cell107
+ detected_number_of_cells = 108;
+ logging.printf("Detected %dS battery\n", (int)detected_number_of_cells);
+ } else {
+ logging.println("Number of cells not recognized");
+ }
datalayer.battery.info.number_of_cells = detected_number_of_cells;
if (detected_number_of_cells == 78) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_78S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_78S_DV;
- }
- if (detected_number_of_cells == 96) {
+ } else if (detected_number_of_cells == 90) {
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV;
+ } else if (detected_number_of_cells == 94) {
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94S_DV;
+ } else if (detected_number_of_cells == 96) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV;
- }
- if (detected_number_of_cells == 108) {
+ } else if (detected_number_of_cells == 100) {
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_100S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_100S_DV;
+ } else if (detected_number_of_cells == 102) {
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_102S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_102S_DV;
+ } else if (detected_number_of_cells == 108) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_108S_DV;
}
+
+ // Map BMW IX balancing status to standard balancing status enum
+ // 0 = No balancing mode active (Ready)
+ // 1/2/3 = Various balancing modes active (Active)
+ // 4 = No balancing mode active, qualifier invalid (Error)
+ // default = Unknown
+ switch (balancing_status) {
+ case 0:
+ datalayer.battery.status.balancing_status = BALANCING_STATUS_READY;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ datalayer.battery.status.balancing_status = BALANCING_STATUS_ACTIVE;
+ break;
+ case 4:
+ datalayer.battery.status.balancing_status = BALANCING_STATUS_ERROR;
+ break;
+ default:
+ datalayer.battery.status.balancing_status = BALANCING_STATUS_UNKNOWN;
+ break;
+ }
}
void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
@@ -165,126 +623,72 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x587:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
+ case 0x7AB:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x8F:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0xD0D087:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
case 0x607: //SME responds to UDS requests on 0x607
- if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 &&
- rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) {
- //First of multi frame data - Parse the first frame
- if (rx_frame.DLC = 64 && rx_frame.data.u8[5] == 0x54) { //Individual Cell Voltages - First Frame
- int start_index = 6; //Data starts here
- int voltage_index = 0; //Start cell ID
- int num_voltages = 29; // number of voltage readings to get
- for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
- uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
- if (voltage < 10000) { //Check reading is plausible - otherwise ignore
- datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
- }
- voltage_index++;
- }
- }
-
- //Frame has continued data - so request it
- transmit_can_frame(&BMWiX_6F4_CONTINUE_DATA);
- }
-
- if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
- rx_frame.data.u8[1] == 0x21) { //Individual Cell Voltages - 1st Continue frame
- int start_index = 2; //Data starts here
- int voltage_index = 29; //Start cell ID
- int num_voltages = 31; // number of voltage readings to get
- for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
- uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
- if (voltage < 10000) { //Check reading is plausible - otherwise ignore
- datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
- }
- voltage_index++;
- }
- }
-
- if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
- rx_frame.data.u8[1] == 0x22) { //Individual Cell Voltages - 2nd Continue frame
- int start_index = 2; //Data starts here
- int voltage_index = 60; //Start cell ID
- int num_voltages = 31; // number of voltage readings to get
- for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
- uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
- if (voltage < 10000) { //Check reading is plausible - otherwise ignore
- datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
- }
- voltage_index++;
- }
- }
-
- if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
- rx_frame.data.u8[1] == 0x23) { //Individual Cell Voltages - 3rd Continue frame
- int start_index = 2; //Data starts here
- int voltage_index = 91; //Start cell ID
- int num_voltages;
- if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13] == 0xFF) { //97th cell is blank - assume 96S Battery
- num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S
- detected_number_of_cells = 96;
- } else { //We have data in 97th cell, assume 108S Battery
- num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S
- detected_number_of_cells = 108;
- }
+ // Removed immediate cell voltage parsing blocks - now handled by ISO-TP multi-frame handler below
- for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
- uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
- if (voltage < 10000) { //Check reading is plausible - otherwise ignore
- datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
- }
- voltage_index++;
- }
- }
- if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor)
+ if ((rx_frame.DLC == 7) && (rx_frame.data.u8[4] == 0x4D)) { //Main Battery Voltage (Pre Contactor)
battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10;
}
- if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor)
+ if ((rx_frame.DLC == 7) && (rx_frame.data.u8[4] == 0x4A)) { //Main Battery Voltage (After Contactor)
battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10;
}
- if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 &&
- rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge
+ if ((rx_frame.DLC == 12) && (rx_frame.data.u8[4] == 0xE5) &&
+ (rx_frame.data.u8[5] == 0x61)) { //Current amps 32bit signed MSB. dA . negative is discharge
battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[7] << 16) |
(rx_frame.data.u8[8] << 8) | rx_frame.data.u8[9])) *
0.1;
}
- if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data
+ if ((rx_frame.DLC == 64) && (rx_frame.data.u8[4] == 0xE4) && (rx_frame.data.u8[5] == 0xCA)) { //Balancing Data
balancing_status = (rx_frame.data.u8[6]); //4 = No symmetry mode active, invalid qualifier
}
- if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC%
+ if ((rx_frame.DLC >= 6) && (rx_frame.data.u8[2] == 0x62) && (rx_frame.data.u8[3] == 0x10) &&
+ (rx_frame.data.u8[4] == 0x0A)) {
+ energy_saving_mode_status = rx_frame.data.u8[5]; // Store the energy saving mode status byte
+ }
+ if ((rx_frame.DLC == 12) && (rx_frame.data.u8[4] == 0xE5) && (rx_frame.data.u8[5] == 0xCE)) { //Min/Avg/Max SOC%
min_soc_state = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
avg_soc_state = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
max_soc_state = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]);
}
- if (rx_frame.DLC =
- 12 && rx_frame.data.u8[4] == 0xE5 &&
- rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
+ if ((rx_frame.DLC == 12) && (rx_frame.data.u8[4] == 0xE5) &&
+ (rx_frame.data.u8[5] == 0xC7)) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
remaining_capacity = ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) * 10) - 50000;
max_capacity = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) * 10) - 50000;
}
- if (rx_frame.DLC = 20 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x45) { //SOH Max Min Mean Request
+ if ((rx_frame.DLC == 20) && (rx_frame.data.u8[4] == 0xE5) &&
+ (rx_frame.data.u8[5] == 0x45)) { //SOH Max Min Mean Request
min_soh_state = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]));
avg_soh_state = ((rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]));
max_soh_state = ((rx_frame.data.u8[12] << 8 | rx_frame.data.u8[13]));
}
- if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 &&
- rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit
+ if ((rx_frame.DLC == 12) && (rx_frame.data.u8[4] == 0xE5) &&
+ (rx_frame.data.u8[5] == 0x62)) { //Max allowed charge and discharge current - Signed 16bit
allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7])) / 10;
allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9])) / 10;
}
- if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 &&
- rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit
+ if ((rx_frame.DLC == 9) && (rx_frame.data.u8[4] == 0xE5) &&
+ (rx_frame.data.u8[5] == 0x4B)) { //Max allowed charge and discharge current - Signed 16bit
voltage_qualifier_status = (rx_frame.data.u8[8]); // Request HV Voltage Qualifier
}
- if (rx_frame.DLC =
- 48 && rx_frame.data.u8[4] == 0xA8 && rx_frame.data.u8[5] == 0x60) { // Safety Isolation Measurements
+ if ((rx_frame.DLC == 64) && (rx_frame.data.u8[4] == 0xA8) &&
+ (rx_frame.data.u8[5] == 0x60)) { // Safety Isolation Measurements
iso_safety_positive = (rx_frame.data.u8[34] << 24) | (rx_frame.data.u8[35] << 16) |
(rx_frame.data.u8[36] << 8) | rx_frame.data.u8[37]; //Assuming 32bit
iso_safety_negative = (rx_frame.data.u8[38] << 24) | (rx_frame.data.u8[39] << 16) |
@@ -293,20 +697,20 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
(rx_frame.data.u8[44] << 8) | rx_frame.data.u8[45]; //Assuming 32bit
}
- if (rx_frame.DLC =
- 48 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xC0) { // Uptime and Vehicle Time Status
+ if ((rx_frame.DLC == 48) && (rx_frame.data.u8[4] == 0xE4) &&
+ (rx_frame.data.u8[5] == 0xC0)) { // Uptime and Vehicle Time Status
sme_uptime = (rx_frame.data.u8[10] << 24) | (rx_frame.data.u8[11] << 16) | (rx_frame.data.u8[12] << 8) |
rx_frame.data.u8[13]; //Assuming 32bit
}
- if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xAC && rx_frame.data.u8[4] == 0x93) { // Pyro Status
+ if ((rx_frame.DLC == 8) && (rx_frame.data.u8[3] == 0xAC) && (rx_frame.data.u8[4] == 0x93)) { // Pyro Status
pyro_status_pss1 = (rx_frame.data.u8[5]);
pyro_status_pss4 = (rx_frame.data.u8[6]);
pyro_status_pss6 = (rx_frame.data.u8[7]);
}
- if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 &&
- rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid
+ if ((rx_frame.DLC == 12) && (rx_frame.data.u8[4] == 0xE5) &&
+ (rx_frame.data.u8[5] == 0x53)) { //Min and max cell voltage 10V = Qualifier Invalid
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter.
@@ -322,25 +726,27 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
}
- if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature
- min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10;
- avg_battery_temperature = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]) / 10;
- max_battery_temperature = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) / 10;
+ if ((rx_frame.DLC == 16) && (rx_frame.data.u8[4] == 0xDD) &&
+ (rx_frame.data.u8[5] == 0xC0)) { //Battery Temperature
+ min_battery_temperature = (int16_t)(rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10;
+ avg_battery_temperature = (int16_t)(rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]) / 10;
+ max_battery_temperature = (int16_t)(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) / 10;
}
- if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL
+ if ((rx_frame.DLC == 7) &&
+ (rx_frame.data.u8[4] == 0xA3)) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL
main_contactor_temperature = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]);
}
- if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply)
+ if ((rx_frame.DLC == 7) && (rx_frame.data.u8[4] == 0xA7)) { //Terminal 30 Voltage (12V SME supply)
terminal30_12v_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]);
}
- if (rx_frame.DLC = 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x04 &&
- rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xE5 &&
- rx_frame.data.u8[4] == 0x69) { //HVIL Status
+ if ((rx_frame.DLC == 6) && (rx_frame.data.u8[0] == 0xF4) && (rx_frame.data.u8[1] == 0x04) &&
+ (rx_frame.data.u8[2] == 0x62) && (rx_frame.data.u8[3] == 0xE5) &&
+ (rx_frame.data.u8[4] == 0x69)) { //HVIL Status
hvil_status = (rx_frame.data.u8[5]);
}
- if (rx_frame.DLC = 12 && rx_frame.data.u8[2] == 0x07 && rx_frame.data.u8[3] == 0x62 &&
- rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4C) { //Pack Voltage Limits
+ if ((rx_frame.DLC == 12) && (rx_frame.data.u8[2] == 0x07) && (rx_frame.data.u8[3] == 0x62) &&
+ (rx_frame.data.u8[4] == 0xE5) && (rx_frame.data.u8[5] == 0x4C)) { //Pack Voltage Limits
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) < 4700 &&
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) > 2600) { //Make sure values are plausible
battery_info_available = true;
@@ -349,7 +755,8 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
}
- if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C) { //Battery Serial Number
+ if ((rx_frame.DLC == 16) && (rx_frame.data.u8[3] == 0xF1) &&
+ (rx_frame.data.u8[4] == 0x8C)) { //Battery Serial Number
//Convert hex bytes to ASCII characters and combine them into a string
char numberString[11]; // 10 characters + null terminator
for (int i = 0; i < 10; i++) {
@@ -359,15 +766,34 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// Step 3: Convert the string to an unsigned long integer
battery_serial_number = strtoul(numberString, NULL, 10);
}
- break;
- case 0x7AB:
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x8F:
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- break;
- case 0xD0D087:
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+
+ // Handle single-frame DTC response (service 0x59)
+ if (rx_frame.data.u8[3] == 0x59 && rx_frame.data.u8[4] == 0x02) {
+ // Single-frame DTC response: F4 00 XX 59 02 FF [DTCs...]
+ // Copy to UDS buffer and parse
+ uint8_t sfLength = rx_frame.data.u8[2]; // Length byte
+ if (sfLength > 0 && sfLength <= (rx_frame.DLC - 3)) {
+ // Copy response data starting from byte 3 (service ID)
+ memcpy(gUDSContext.UDS_buffer, &rx_frame.data.u8[3], sfLength);
+ gUDSContext.UDS_bytesReceived = sfLength;
+ gUDSContext.UDS_moduleID = 0x02; // DTC response
+ gUDSContext.UDS_inProgress = false;
+
+ logging.println("=== Single-Frame DTC Response Received ===");
+ logging.print("Total bytes: ");
+ logging.println(gUDSContext.UDS_bytesReceived);
+
+ parseDTCResponse();
+ }
+ }
+
+ // Handle ISO-TP multi-frame messages
+ handleISOTPFrame(rx_frame);
+
+ // Check if complete UDS response is ready to process
+ if (isUDSMessageComplete()) {
+ processCompletedUDSResponse();
+ }
break;
default:
break;
@@ -375,6 +801,24 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
void BmwIXBattery::transmit_can(unsigned long currentMillis) {
+ // Perform startup BMS reset after 2 seconds, before allowing contactor close
+ if (!startup_reset_complete && (currentMillis > 2000)) {
+ logging.println("Performing startup BMS reset");
+ transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET);
+ startup_reset_complete = true;
+ // Allow contactors to close after reset
+ datalayer.system.status.battery_allows_contactor_closing = true;
+ }
+
+ // Timeout check for stuck UDS transfers
+ if (gUDSContext.UDS_inProgress) {
+ if (currentMillis - gUDSContext.UDS_lastFrameMillis > 2000) { // 2 second timeout
+ logging.println("UDS transfer timeout - aborting");
+ gUDSContext.UDS_inProgress = false;
+ gUDSContext.UDS_bytesReceived = 0;
+ }
+ }
+
// We can always send CAN as the iX BMS will wake up on vehicle comms
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
@@ -382,16 +826,19 @@ void BmwIXBattery::transmit_can(unsigned long currentMillis) {
// Detect edge
if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) {
// Rising edge detected
- logging.println("Rising edge detected. Resetting 10ms counter.");
+ logging.println("Rising edge. 10ms counter reset");
counter_10ms = 0; // reset counter
} else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) {
- // Dropping edge detected
- logging.println("Dropping edge detected. Resetting 10ms counter.");
+ // Falling edge detected
+ logging.println("Falling edge. 10ms counter reset");
counter_10ms = 0; // reset counter
}
ContactorCloseRequest.previous = ContactorCloseRequest.present;
- HandleBmwIxCloseContactorsRequest(counter_10ms);
- HandleBmwIxOpenContactorsRequest(counter_10ms);
+ // Only send CAN contactor commands if GPIO contactor control is disabled
+ if (!contactor_control_enabled) {
+ HandleBmwIxCloseContactorsRequest(counter_10ms);
+ HandleBmwIxOpenContactorsRequest(counter_10ms);
+ }
counter_10ms++;
// prevent counter overflow: 2^16-1 = 65535
@@ -404,19 +851,32 @@ void BmwIXBattery::transmit_can(unsigned long currentMillis) {
previousMillis100 = currentMillis;
HandleIncomingInverterRequest();
- //Loop through and send a different UDS request once the contactors are closed
- if (contactorCloseReq == true &&
- ContactorState.closed ==
- true) { // Do not send unless the contactors are requested to be closed and are closed, as sending these does not allow the contactors to close
- uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
- transmit_can_frame(
- UDS_REQUESTS100MS[uds_req_id_counter]); // FIXME: sending these does not allow the contactors to close
- } else { // FIXME: hotfix: If contactors are not requested to be closed, ensure the battery is reported as alive, even if no CAN messages are received
+ // Send UDS requests after startup reset completes
+ if (startup_reset_complete) {
+ if (contactor_control_enabled) {
+ // GPIO mode: always safe to send UDS (contactors controlled independently)
+ uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
+ transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter]);
+ } else {
+ // CAN mode: pause UDS during contactor closing to avoid interference
+ if (contactorCloseReq == true && ContactorState.closed == false) {
+ // Contactors are being closed - pause UDS
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ } else {
+ // Normal operation - send UDS requests
+ uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
+ transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter]);
+ }
+ }
+ } else {
+ // During startup (first 3 seconds), keep battery marked as alive
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
- // Keep contactors closed if needed
- BmwIxKeepContactorsClosed(counter_100ms);
+ // Keep contactors closed if needed (only when GPIO control is disabled)
+ if (!contactor_control_enabled) {
+ BmwIxKeepContactorsClosed(counter_100ms);
+ }
counter_100ms++;
if (counter_100ms == 140) {
counter_100ms = 0; // reset counter every 14 seconds
@@ -426,33 +886,78 @@ void BmwIXBattery::transmit_can(unsigned long currentMillis) {
//transmit_can_frame(&BMWiX_510);
}
// Send 200ms CAN Message
+ /*
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
- //Send SME Keep alive values 200ms
- //BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
- //transmit_can_frame(&BMWiX_C0);
+ Send SME Keep alive values 200ms
+ BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
+ transmit_can_frame(&BMWiX_C0);
}
+ */
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
-
+ CAN_frame BMWiX_433 = generate_433_datetime_message();
+ transmit_can_frame(&BMWiX_433);
+ CAN_frame BMWiX_442 = generate_442_time_counter_message();
+ transmit_can_frame(&BMWiX_442);
HandleIncomingUserRequest();
}
// Send 10000ms CAN Message
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
previousMillis10000 = currentMillis;
+
+ // Send slow UDS requests (like DTC reading) regardless of contactor state
+ uds_req_id_counter_slow++;
+ if (uds_req_id_counter_slow >= numUDSreqsSlow) {
+ uds_req_id_counter_slow = 0;
+ }
+ // Add logging to see which request is being sent
+ //logging.print("Sending slow UDS request #");
+ //logging.println(uds_req_id_counter_slow);
+ //transmit_can_frame(UDS_REQUESTS_SLOW[uds_req_id_counter_slow]); no messages needed on slow loop right now
+
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2);
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START);
}
+ // Handle user DTC read request
+ if (UserRequestDTCRead) {
+ logging.println("User req. DTC read");
+ transmit_can_frame(&BMWiX_6F4_REQUEST_READ_DTC);
+ UserRequestDTCRead = false;
+
+ // Set flags in datalayer for HTML renderer
+ datalayer_extended.bmwix.dtc_read_in_progress = true;
+ datalayer_extended.bmwix.dtc_read_failed = false;
+ }
+
+ // Handle user DTC reset request
+ if (UserRequestDTCreset) {
+ logging.println("User req. DTC reset");
+ transmit_can_frame(&BMWiX_6F4_REQUEST_CLEAR_DTC);
+ UserRequestDTCreset = false;
+ }
+
+ // Handle user BMS reset request
+ if (UserRequestBMSReset) {
+ logging.println("User req. BMS reset");
+ transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET);
+ UserRequestBMSReset = false;
+ }
+ // Handle user Energy Saving Mode reset request
+ if (UserRequestEnergySavingModeReset) {
+ logging.println("User req. Energy Saving Mode reset to normal");
+ transmit_can_frame(&BMWiX_6F4_SET_ENERGY_SAVING_MODE_NORMAL);
+ UserRequestEnergySavingModeReset = false;
+ }
}
void BmwIXBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
- //Reset Battery at bootup
- //transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET);
+ startup_reset_complete = false;
//Before we have started up and detected which battery is in use, use largest deviation possible to avoid errors
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV;
@@ -460,16 +965,16 @@ void BmwIXBattery::setup(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
- datalayer.system.status.battery_allows_contactor_closing = true;
+ datalayer.system.status.battery_allows_contactor_closing = false; // Don't allow contactors until reset is done
}
void BmwIXBattery::HandleIncomingUserRequest(void) {
// Debug user request to open or close the contactors
if (userRequestContactorClose) {
- logging.printf("User request: contactor close");
+ logging.printf("User req. contactor close");
}
if (userRequestContactorOpen) {
- logging.printf("User request: contactor open");
+ logging.printf("User req. contactor open");
}
if ((userRequestContactorClose == false) && (userRequestContactorOpen == false)) {
// do nothing
@@ -488,8 +993,7 @@ void BmwIXBattery::HandleIncomingUserRequest(void) {
userRequestContactorClose = false;
userRequestContactorOpen = false;
// print error, as both these flags shall not be true at the same time
- logging.println(
- "Error: user requested contactors to close and open at the same time. Contactors have been opened.");
+ logging.println("Error: user req. contactors to close and open at the same time. Contactors have been opened.");
}
}
@@ -498,18 +1002,17 @@ void BmwIXBattery::HandleIncomingInverterRequest(void) {
// Detect edge
if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) {
// Rising edge detected
- logging.println("Inverter requests to close contactors");
+ logging.println("Inverter req. to close contactors");
BmwIxCloseContactors();
} else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) {
// Falling edge detected
- logging.println("Inverter requests to open contactors");
+ logging.println("Inverter req. to open contactors");
BmwIxOpenContactors();
} // else: do nothing
// Update state
InverterContactorCloseRequest.previous = InverterContactorCloseRequest.present;
}
-
void BmwIXBattery::BmwIxCloseContactors(void) {
logging.println("Closing contactors");
contactorCloseReq = true;
@@ -522,6 +1025,10 @@ void BmwIXBattery::BmwIxOpenContactors(void) {
}
void BmwIXBattery::HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
+ // Block contactor close until startup reset is complete
+ if (!startup_reset_complete) {
+ return;
+ }
if (contactorCloseReq == true) { // Only when contactor close request is set to true
if (ContactorState.closed == false &&
ContactorState.open ==
@@ -541,35 +1048,36 @@ void BmwIXBattery::HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
if (counter_10ms == 0) {
// @0 ms
+ logging.println("Starting CAN-based contactor close sequence");
transmit_can_frame(&BMWiX_510);
- logging.println("Transmitted 0x510 - 1/6");
+ logging.println("Sent 0x510 - 1/6");
} else if (counter_10ms == 5) {
// @50 ms
transmit_can_frame(&BMWiX_276);
- logging.println("Transmitted 0x276 - 2/6");
+ logging.println("Sent 0x276 - 2/6");
} else if (counter_10ms == 10) {
// @100 ms
BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed
transmit_can_frame(&BMWiX_510);
- logging.println("Transmitted 0x510 - 3/6");
+ logging.println("Sent 0x510 - 3/6");
} else if (counter_10ms == 20) {
// @200 ms
BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed
BMWiX_510.data.u8[5] = 0x80; // needed to close contactors
transmit_can_frame(&BMWiX_510);
- logging.println("Transmitted 0x510 - 4/6");
+ logging.println("Sent 0x510 - 4/6");
} else if (counter_10ms == 30) {
// @300 ms
BMWiX_16E.data.u8[0] = 0x6A;
BMWiX_16E.data.u8[1] = 0xAD;
transmit_can_frame(&BMWiX_16E);
- logging.println("Transmitted 0x16E - 5/6");
+ logging.println("Sent 0x16E - 5/6");
} else if (counter_10ms == 50) {
// @500 ms
BMWiX_16E.data.u8[0] = 0x03;
BMWiX_16E.data.u8[1] = 0xA9;
transmit_can_frame(&BMWiX_16E);
- logging.println("Transmitted 0x16E - 6/6");
+ logging.println("Sent 0x16E - 6/6");
ContactorState.closed = true;
ContactorState.open = false;
}
@@ -656,6 +1164,9 @@ int BmwIXBattery::get_T30_Voltage() const {
int BmwIXBattery::get_balancing_status() const {
return balancing_status;
}
+int BmwIXBattery::get_energy_saving_mode_status() const {
+ return energy_saving_mode_status;
+}
int BmwIXBattery::get_hvil_status() const {
return hvil_status;
}
diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h
index 678d91f68..77a946157 100644
--- a/Software/src/battery/BMW-IX-BATTERY.h
+++ b/Software/src/battery/BMW-IX-BATTERY.h
@@ -4,6 +4,18 @@
#include "BMW-IX-HTML.h"
#include "CanBattery.h"
+// UDS Multi-Frame Reception Context
+struct UDS_CONTEXT {
+ uint8_t UDS_buffer[512]; // Buffer to store multi-frame UDS data
+ uint16_t UDS_bytesReceived; // Track number of bytes received
+ uint16_t UDS_expectedLength; // Total expected length from first frame
+ uint8_t UDS_sequenceNumber; // Expected sequence number for consecutive frames (for IX non-batch mode)
+ bool UDS_inProgress; // Flag indicating if we're in the middle of receiving
+ uint8_t UDS_moduleID; // Module ID responding (0xF4 for battery)
+ uint8_t receivedInBatch; // Number of CFs received in current batch (for PHEV batch mode)
+ unsigned long UDS_lastFrameMillis; // Timestamp of last frame for timeout detection
+};
+
class BmwIXBattery : public CanBattery {
public:
BmwIXBattery() : renderer(*this) {}
@@ -14,8 +26,15 @@ class BmwIXBattery : public CanBattery {
virtual void transmit_can(unsigned long currentMillis);
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
+ bool supports_read_DTC() { return true; }
+ void read_DTC() { UserRequestDTCRead = true; }
+ bool supports_reset_DTC() { return true; }
+ void reset_DTC() { UserRequestDTCreset = true; }
+ bool supports_reset_BMS() { return true; }
+ void reset_BMS() { UserRequestBMSReset = true; }
+ bool supports_energy_saving_mode_reset() { return true; }
+ void reset_energy_saving_mode() { UserRequestEnergySavingModeReset = true; }
bool supports_contactor_close() { return true; }
-
void request_open_contactors() { userRequestContactorOpen = true; }
void request_close_contactors() { userRequestContactorClose = true; }
@@ -27,6 +46,7 @@ class BmwIXBattery : public CanBattery {
unsigned long get_max_cell_voltage_data_age() const;
int get_T30_Voltage() const;
int get_balancing_status() const;
+ int get_energy_saving_mode_status() const;
int get_hvil_status() const;
unsigned long get_bms_uptime() const;
int get_allowable_charge_amps() const;
@@ -41,33 +61,48 @@ class BmwIXBattery : public CanBattery {
private:
bool userRequestContactorClose = false;
bool userRequestContactorOpen = false;
-
+ bool UserRequestDTCreset = false;
+ bool UserRequestBMSReset = false;
+ bool UserRequestDTCRead = false;
+ bool UserRequestEnergySavingModeReset = false;
+ bool startup_reset_complete = false; // Track if startup BMS reset is done
BmwIXHtmlRenderer renderer;
- static const int MAX_PACK_VOLTAGE_78S_DV = 3354; //SE12 battery, BMW iX1, 66.45kWh 286.3vNom
- static const int MIN_PACK_VOLTAGE_78S_DV = 2200;
- static const int MAX_PACK_VOLTAGE_96S_DV = 4128;
- static const int MIN_PACK_VOLTAGE_96S_DV = 2688;
- static const int MAX_PACK_VOLTAGE_108S_DV = 4650;
- static const int MIN_PACK_VOLTAGE_108S_DV = 3000;
+ static const int MAX_PACK_VOLTAGE_78S_DV = 3354; // 4.3V per cell | SE12 battery, BMW iX1, 66.45kWh 286.3Vnom
+ static const int MIN_PACK_VOLTAGE_78S_DV = 2184; // 2.8V per cell
+ static const int MAX_PACK_VOLTAGE_90S_DV = 3870; // 4.3V per cell | SE11 | SE 50
+ static const int MIN_PACK_VOLTAGE_90S_DV = 2520; // 2.8V per cell
+ static const int MAX_PACK_VOLTAGE_94S_DV = 4042; // 4.3V per cell | SE16
+ static const int MIN_PACK_VOLTAGE_94S_DV = 2632; // 2.8V per cell
+ static const int MAX_PACK_VOLTAGE_96S_DV = 4128; // 4.3V per cell | SE26
+ static const int MIN_PACK_VOLTAGE_96S_DV = 2688; // 2.8V per cell
+ static const int MAX_PACK_VOLTAGE_100S_DV = 4300; // 4.3V per cell | SE10
+ static const int MIN_PACK_VOLTAGE_100S_DV = 2800; // 2.8V per cell
+ static const int MAX_PACK_VOLTAGE_102S_DV = 4386; // 4.3V per cell | SE30
+ static const int MIN_PACK_VOLTAGE_102S_DV = 2856; // 2.8V per cell
+ static const int MAX_PACK_VOLTAGE_108S_DV = 4644; // 4.3V per cell | SE27
+ static const int MIN_PACK_VOLTAGE_108S_DV = 3024; // 2.8V per cell
static const int MAX_CELL_DEVIATION_MV = 250;
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
static const int RAMPDOWN_SOC =
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
+ static const int RAMPDOWN_TEMP_MIN_dC = -100; // (-10.0ยฐC) Temperature below which charging is not allowed
+ static const int RAMPDOWN_TEMP_MAX_dC = 50; // (5.0ยฐC) Temperature above which no temperature limitation applies
+ static const int RAMPDOWN_TEMP_POWER_W =
+ 10000; // (10000W) Maximum charge power at RAMPDOWN_TEMP_MAX_dC, ramping down to 0W at RAMPDOWN_TEMP_MIN_dC
static const int STALE_PERIOD_CONFIG =
900000; //Number of milliseconds before critical values are classed as stale/stuck 900000 = 900 seconds
unsigned long previousMillis10 = 0; // will store last time a 20ms CAN Message was send
- unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
- unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
- unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
- unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
- unsigned long previousMillis1000 = 0; // will store last time a 600ms CAN Message was send
- unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
+ unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was send
+ unsigned long previousMillis10000 = 0; // will store last time a 10s CAN Message was send
+ unsigned long min_cell_voltage_lastchanged = 0;
+ unsigned long max_cell_voltage_lastchanged = 0;
static const int ALIVE_MAX_VALUE = 14; // BMW CAN messages contain alive counter, goes from 0...14
+ static const int MAX_DTC_COUNT = 30; // Maximum number of DTCs to store/display
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
@@ -175,7 +210,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
0xC9,
0x3A, // 0x3A to close contactors, 0x33 to open contactors
0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output.
-
+ /*
static constexpr CAN_frame BMWiX_188 = {
.FD = true,
.ext_ID = false,
@@ -183,7 +218,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.ID = 0x188,
.data = {0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF}}; // CCU output - values while driving
- /*
+
static constexpr CAN_frame BMWiX_1EA = {
.FD = true,
.ext_ID = false,
@@ -191,7 +226,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.ID = 0x1EA,
//.data = {TODO:km_least_significant, TODO:, TODO:, TODO:, TODO:km_most_significant, 0xFF, TODO:, TODO:}
}; // KOMBI output - kilometerstand
-*/
+
static constexpr CAN_frame BMWiX_1FC = {
.FD = true,
.ext_ID = false,
@@ -199,7 +234,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.ID = 0x1FC,
.data = {0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xC0,
0x00}}; // FIXME:(add transmitter node) output - heat management engine control - values
- /*
+
static constexpr CAN_frame BMWiX_21D = {
.FD = true,
.ext_ID = false,
@@ -215,7 +250,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.ID = 0x276,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFD}}; // BDC output - vehicle condition. Used for contactor closing
-
+ /*
static constexpr CAN_frame BMWiX_2ED = {
.FD = true,
.ext_ID = false,
@@ -231,7 +266,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.DLC = 8,
.ID = 0x2F1,
.data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC output - values - varies at startup
- /*
+
static constexpr CAN_frame BMWiX_340 = {
.FD = true,
.ext_ID = false,
@@ -305,21 +340,7 @@ CAN_frame BMWiX_49C = {.FD = true,
.ID = 0x4F8,
// .data = {0xFF, 0xFD, 0xFF, 0xFF, 0xFF, TODO:, TODO:, 0xC8, 0x00, 0x00, 0xF0, 0x40, 0xFE, 0xFF, 0xFD, 0xFF, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // CCU output
-*/
- CAN_frame BMWiX_510 = {
- .FD = true,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x510,
- .data = {
- 0x40, 0x10,
- 0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
- 0x00, 0x00,
- 0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
- 0x01,
- 0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
-
- static constexpr CAN_frame BMWiX_6D = {
+ static constexpr CAN_frame BMWiX_6D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
@@ -327,8 +348,7 @@ CAN_frame BMWiX_49C = {.FD = true,
.data = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC output - [0] [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
-
- static constexpr CAN_frame BMWiX_C0 = {
+ static constexpr CAN_frame BMWiX_C0 = {
.FD = true,
.ext_ID = false,
.DLC = 2,
@@ -337,6 +357,19 @@ CAN_frame BMWiX_49C = {.FD = true,
0xF0,
0x00}}; // BDC output - Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 00 - MINIMUM ID TO KEEP SME AWAKE
//Vehicle CAN END
+*/
+ CAN_frame BMWiX_510 = {
+ .FD = true,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x510,
+ .data = {
+ 0x40, 0x10,
+ 0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
+ 0x00, 0x00,
+ 0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
+ 0x01,
+ 0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
//Request Data CAN START
static constexpr CAN_frame BMWiX_6F4 = {
@@ -345,6 +378,19 @@ CAN_frame BMWiX_49C = {.FD = true,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value
+ static constexpr CAN_frame BMWiX_6F4_REQUEST_ENERGY_SAVING_MODE_STATUS = {// UDS Request SME Energy saving mode status
+ .FD = true,
+ .ext_ID = false,
+ .DLC = 5,
+ .ID = 0x6F4,
+ .data = {0x07, 0x03, 0x22, 0x10, 0x0A}};
+ static constexpr CAN_frame BMWiX_6F4_SET_ENERGY_SAVING_MODE_NORMAL = {
+ // UDS Request Set energy saving mode to normal
+ .FD = true,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x6F4,
+ .data = {0x07, 0x05, 0x31, 0x01, 0x0F, 0x0C, 0x00, 0x00}};
static constexpr CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {
.FD = true,
.ext_ID = false,
@@ -431,6 +477,18 @@ CAN_frame BMWiX_49C = {.FD = true,
.data = {
0x07, 0x03, 0x22, 0xE5,
0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate
+ static constexpr CAN_frame BMWiX_6F4_START_DEFAULT_DIAG_SESSION =
+ {.FD = true, .ext_ID = false, .DLC = 8, .ID = 0x6F4, .data = {0x07, 0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00}};
+ static constexpr CAN_frame BMWiX_6F4_REQUEST_READ_DTC = {.FD = true,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x6F4,
+ .data = {0x07, 0x03, 0x19, 0x02, 0x0C, 0x00, 0x00, 0x00}};
+ static constexpr CAN_frame BMWiX_6F4_REQUEST_CLEAR_DTC = {.FD = true,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x6F4,
+ .data = {0x07, 0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00}};
static constexpr CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true,
.ext_ID = false,
.DLC = 5,
@@ -516,7 +574,7 @@ CAN_frame BMWiX_49C = {.FD = true,
//Request Data CAN End
//Setup UDS values to poll for
- static constexpr const CAN_frame* UDS_REQUESTS100MS[17] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
+ static constexpr const CAN_frame* UDS_REQUESTS100MS[18] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
&BMWiX_6F4_REQUEST_SOC,
&BMWiX_6F4_REQUEST_CAPACITY,
&BMWiX_6F4_REQUEST_MINMAXCELLV,
@@ -532,9 +590,15 @@ CAN_frame BMWiX_49C = {.FD = true,
&BMWiX_6F4_REQUEST_HVIL,
&BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS,
&BMWiX_6F4_REQUEST_BALANCINGSTATUS,
- &BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS};
- int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array
-
+ &BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS,
+ &BMWiX_6F4_REQUEST_ENERGY_SAVING_MODE_STATUS};
+ static const int numUDSreqs =
+ sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the fast array
+ static constexpr const CAN_frame* UDS_REQUESTS_SLOW[1] = {
+ &BMWiX_6F4_REQUEST_READ_DTC // Poll DTCs less frequently
+ };
+ static const int numUDSreqsSlow =
+ sizeof(UDS_REQUESTS_SLOW) / sizeof(UDS_REQUESTS_SLOW[0]); // Number of elements in the slow array
//iX Intermediate vars
bool battery_info_available = false;
uint32_t battery_serial_number = 0;
@@ -558,10 +622,7 @@ CAN_frame BMWiX_49C = {.FD = true,
int16_t main_contactor_temperature = 0;
uint16_t min_cell_voltage = 3700; //Startup with valid values - needs fixing in future
uint16_t max_cell_voltage = 3700; //Startup with valid values - needs fixing in future
- unsigned long min_cell_voltage_lastchanged = 0;
- unsigned long max_cell_voltage_lastchanged = 0;
- unsigned min_cell_voltage_lastreceived = 0;
- unsigned max_cell_voltage_lastreceived = 0;
+
uint32_t sme_uptime = 0; //Uses E4 C0
int16_t allowable_charge_amps = 0; //E5 62
int16_t allowable_discharge_amps = 0; //E5 62
@@ -571,8 +632,9 @@ CAN_frame BMWiX_49C = {.FD = true,
int16_t count_full_charges = 0; //TODO 42
int16_t count_charges = 0; //TODO 42
int16_t hvil_status = 0;
- int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
- int16_t balancing_status = 0; //4 = not active
+ int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
+ int16_t balancing_status = 0; //4 = not active
+ int16_t energy_saving_mode_status = 0;
uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
uint8_t contactor_status_precharge = 0; //TODO E5 BF
uint8_t contactor_status_negative = 0; //TODO E5 BF
@@ -581,12 +643,20 @@ CAN_frame BMWiX_49C = {.FD = true,
uint8_t pyro_status_pss4 = 0; //Using AC 93
uint8_t pyro_status_pss6 = 0; //Using AC 93
uint8_t uds_req_id_counter = 0;
+ uint8_t uds_req_id_counter_slow = 0;
uint8_t detected_number_of_cells = 0;
- const unsigned long STALE_PERIOD =
- STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
- //End iX Intermediate vars
- uint8_t current_cell_polled = 0;
+ // UDS Multi-Frame Reception Context
+ UDS_CONTEXT gUDSContext = {
+ {}, // UDS_buffer - zero-initialized array
+ 0, // UDS_bytesReceived
+ 0, // UDS_expectedLength
+ 0, // UDS_sequenceNumber
+ false, // UDS_inProgress
+ 0, // UDS_moduleID
+ 0, // receivedInBatch
+ 0 // UDS_lastFrameMillis
+ };
uint16_t counter_10ms = 0; // max 65535 --> 655.35 seconds
uint8_t counter_100ms = 0; // max 255 --> 25.5 seconds
@@ -595,6 +665,15 @@ CAN_frame BMWiX_49C = {.FD = true,
uint8_t increment_uds_req_id_counter(uint8_t index);
uint8_t increment_alive_counter(uint8_t counter);
+ // UDS Multi-Frame Helpers
+ void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID);
+ bool storeUDSPayload(const uint8_t* payload, uint8_t length);
+ bool isUDSMessageComplete();
+ void parseDTCResponse();
+ void handleISOTPFrame(CAN_frame& rx_frame);
+ void processCompletedUDSResponse();
+ CAN_frame generate_433_datetime_message();
+ CAN_frame generate_442_time_counter_message();
/**
* @brief Handle incoming user request to close or open contactors
*
diff --git a/Software/src/battery/BMW-IX-HTML.cpp b/Software/src/battery/BMW-IX-HTML.cpp
index a93d313a5..8afc97a85 100644
--- a/Software/src/battery/BMW-IX-HTML.cpp
+++ b/Software/src/battery/BMW-IX-HTML.cpp
@@ -1,44 +1,155 @@
#include "BMW-IX-HTML.h"
#include "BMW-IX-BATTERY.h"
+String BmwIXHtmlRenderer::getDTCDescription(uint32_t code) {
+ switch (code) {
+#ifndef SMALL_FLASH_DEVICE
+ case 0x020700:
+ return "SME: Transport mode active";
+ case 0x020708:
+ return "SME: Control unit is not encoded";
+ case 0x020709:
+ return "SME: Encoded data does not match vehicle";
+ case 0x020740:
+ return "SME, voltage supply: Undervoltage";
+ case 0x020780:
+ return "SME: Certificates (Type 1) not ready";
+ case 0x020785:
+ return "SME: Control unit not in field mode";
+ case 0x21F001:
+ return "High-voltage battery unit, coolant shutoff valve: Line disconnection";
+ case 0x21F00F:
+ return "High-voltage battery unit, coolant shutoff valve: Line disconnection";
+ case 0x21F121:
+ return "High-voltage battery unit, control unit: internal defective alarm function";
+ case 0x21F306:
+ return "High-voltage battery unit, control unit, idle time estimation: Estimation failed";
+ case 0x21F37E:
+ return "Impact detection: Crash detected due to ACSM signal";
+ case 0x21F38F:
+ return "SME: internal control unit errors (Group fault software)";
+ case 0x21F393:
+ return "High-voltage battery unit, multiple fault: Transport restricting fault memory active";
+ case 0x21F3F0:
+ return "High-voltage battery unit: Expansion of the operating range for the state of charge active";
+ case 0x21F436:
+ return "High-voltage battery unit, high-voltage safety battery terminal: High-voltage-minus not disconnected due "
+ "to unsuccessful actuation";
+ case 0x21F44F:
+ return "High-voltage battery unit, SME: Pyrofuse ignited or faulty actuation";
+ case 0x21F4B5:
+ return "High-voltage battery unit, voltage and current sensor: Power-down requested";
+ case 0x21F4ED:
+ return "High-voltage battery unit, voltage and current sensor: Line protection, threshold value exceeded";
+ case 0x3B001A:
+ return "High-voltage battery unit: Multiple faults, high-voltage system cannot be activated";
+ case 0xCAD450:
+ return "No message (- 0x125), SME receiver, CCU transmitter";
+ case 0xCAD454:
+ return "No message (- 0x16E), SME receiver, CCU transmitter";
+ case 0xCAD4B0:
+ return "No message (vehicle speed, 0x1A1), receiver SME, transmitter DSC";
+ case 0xCAD6D8:
+ return "No message (- 0x91), SME receiver, EME transmitter";
+
+ default:
+ return "";
+#else
+ default:
+ return "Current BE hardware does not support details. Please upgrade to large flash BE"; // Detailed DTC descriptions not available on 4MB low flash devices. The above text takes up a massive amount of flash!
+#endif
+ }
+}
+
String BmwIXHtmlRenderer::get_status_html() {
String content;
- content += "Battery Voltage after Contactor: " + String(batt.get_battery_voltage_after_contactor()) + " dV ";
+ // Power & Voltage Section
+ content +=
+ "โก Power & Voltage ";
+ content += "";
+ content +=
+ "
Battery Voltage (After Contactor): " + String(batt.get_battery_voltage_after_contactor()) + " dV ";
content += "Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV ";
content += "Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV ";
+ content += "T30 Terminal Voltage: " + String(batt.get_T30_Voltage()) + " mV ";
+ content += "Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W ";
+ content += "Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W ";
+ content += "BMS Allowed Charge Amps: " + String(batt.get_allowable_charge_amps()) + " A ";
+ content += "BMS Allowed Discharge Amps: " + String(batt.get_allowable_discharge_amps()) + " A ";
+ content += "
";
+
+ // Cell Information Section
+ content +=
+ "๐ Cell Information ";
+ content += "";
+ content += "
Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + " ";
content += "Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV ";
content += "Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV ";
content += "Min Cell Voltage Data Age: " + String(batt.get_min_cell_voltage_data_age()) + " ms ";
content += "Max Cell Voltage Data Age: " + String(batt.get_max_cell_voltage_data_age()) + " ms ";
- content += "Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W ";
- content += "Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W ";
- content += "T30 Terminal Voltage: " + String(batt.get_T30_Voltage()) + " mV ";
- content += "Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + " ";
+ content += "";
+
+ // Battery Status Section
+ content += "โ๏ธ Battery Status ";
+ content += "";
content += "
Balancing: ";
switch (batt.get_balancing_status()) {
case 0:
- content += "0 No balancing mode active ";
+ content += "No Balancing Mode Active";
break;
case 1:
- content += "1 Voltage-Controlled Balancing Mode";
+ content += "Voltage-Controlled Balancing Mode ";
break;
case 2:
- content += "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging";
+ content +=
+ "Time-Controlled Balancing Mode with Demand Calculation at End of "
+ "Charging ";
break;
case 3:
- content += "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage";
+ content +=
+ "Time-Controlled Balancing Mode with Demand Calculation at Resting "
+ "Voltage ";
break;
case 4:
- content += "4 No balancing mode active, qualifier invalid";
+ content += "No Balancing Mode Active (Qualifier Invalid)";
break;
default:
content += "Unknown";
}
+ // Energy Saving Mode Status
+ content += "Energy Saving Mode: ";
+ int energy_mode = batt.get_energy_saving_mode_status();
+ switch (energy_mode) {
+ case 0:
+ content += "No Operating Mode Set (Normal) ";
+ break;
+ case 1:
+ content += "Production Mode Active ";
+ break;
+ case 2:
+ content += "Transport Mode Active ";
+ break;
+ case 3:
+ content += "Flash Mode Active ";
+ break;
+ default:
+ if (energy_mode >= 4 && energy_mode <= 16) {
+ content += "Extended Operating Mode " + String(energy_mode) + " ";
+ } else {
+ content += "Unknown (" + String(energy_mode) + ")";
+ }
+ break;
+ }
+ content += "";
+
+ // Safety Systems Section
+ content += "๐ก๏ธ Safety Systems ";
+ content += "";
content += "
HVIL Status: ";
switch (batt.get_hvil_status()) {
case 0:
- content += "Error (Loop Open) ";
+ content += "โ Error (Loop Open) ";
break;
case 1:
content += "OK (Loop Closed)";
@@ -46,30 +157,22 @@ String BmwIXHtmlRenderer::get_status_html() {
default:
content += "Unknown";
}
- content += "BMS Uptime: " + String(batt.get_bms_uptime()) + " seconds ";
- content += "BMS Allowed Charge Amps: " + String(batt.get_allowable_charge_amps()) + " A ";
- content += "BMS Allowed Disharge Amps: " + String(batt.get_allowable_discharge_amps()) + " A ";
- content += " ";
- content += "HV Isolation (2147483647kOhm = maximum/invalid) ";
- content += "Isolation Positive: " + String(batt.get_iso_safety_positive()) + " kOhm ";
- content += "Isolation Negative: " + String(batt.get_iso_safety_negative()) + " kOhm ";
- content += "Isolation Parallel: " + String(batt.get_iso_safety_parallel()) + " kOhm ";
content += "Pyro Status PSS1: ";
switch (batt.get_pyro_status_pss1()) {
case 0:
- content += "0 Value Invalid ";
+ content += "Value Invalid";
break;
case 1:
- content += "1 Successfully Blown";
+ content += "โ Successfully Blown ";
break;
case 2:
- content += "2 Disconnected";
+ content += "Disconnected ";
break;
case 3:
- content += "3 Not Activated - Pyro Intact";
+ content += "Not Activated - Pyro Intact";
break;
case 4:
- content += "4 Unknown";
+ content += "Unknown";
break;
default:
content += "Unknown";
@@ -77,19 +180,19 @@ String BmwIXHtmlRenderer::get_status_html() {
content += "Pyro Status PSS4: ";
switch (batt.get_pyro_status_pss4()) {
case 0:
- content += "0 Value Invalid ";
+ content += "Value Invalid";
break;
case 1:
- content += "1 Successfully Blown";
+ content += "โ Successfully Blown ";
break;
case 2:
- content += "2 Disconnected";
+ content += "Disconnected ";
break;
case 3:
- content += "3 Not Activated - Pyro Intact";
+ content += "Not Activated - Pyro Intact";
break;
case 4:
- content += "4 Unknown";
+ content += "Unknown";
break;
default:
content += "Unknown";
@@ -97,23 +200,144 @@ String BmwIXHtmlRenderer::get_status_html() {
content += "Pyro Status PSS6: ";
switch (batt.get_pyro_status_pss6()) {
case 0:
- content += "0 Value Invalid ";
+ content += "Value Invalid";
break;
case 1:
- content += "1 Successfully Blown";
+ content += "โ Successfully Blown ";
break;
case 2:
- content += "2 Disconnected";
+ content += "Disconnected ";
break;
case 3:
- content += "3 Not Activated - Pyro Intact";
+ content += "Not Activated - Pyro Intact";
break;
case 4:
- content += "4 Unknown";
+ content += "Unknown";
break;
default:
content += "Unknown";
}
+ content += "";
+
+ // Isolation Monitoring Section
+ content +=
+ "๐ Isolation "
+ "Monitoring ";
+ content += "";
+ content +=
+ "
Isolation Positive: " + String(batt.get_iso_safety_positive()) + " kฮฉ (2147483647 = maximum/invalid) ";
+ content +=
+ "Isolation Negative: " + String(batt.get_iso_safety_negative()) + " kฮฉ (2147483647 = maximum/invalid) ";
+ content +=
+ "Isolation Parallel: " + String(batt.get_iso_safety_parallel()) + " kฮฉ (2147483647 = maximum/invalid) ";
+ content += "";
+
+ // Diagnostics Section
+ content += "๐ง Diagnostics ";
+ content += "";
+
+ // Convert uptime to days:hours:minutes:seconds format
+ unsigned long uptime_seconds = batt.get_bms_uptime();
+ unsigned long days = uptime_seconds / 86400;
+ unsigned long hours = (uptime_seconds % 86400) / 3600;
+ unsigned long minutes = (uptime_seconds % 3600) / 60;
+ unsigned long seconds = uptime_seconds % 60;
+
+ content += "
BMS Uptime: " + String(days) + "d " + String(hours) + "h " + String(minutes) + "m " +
+ String(seconds) + "s ";
+ content += "";
+
+ // Diagnostic Trouble Codes Section
+ content +=
+ "๐ง Diagnostic Trouble "
+ "Codes ";
+ content += "";
+
+ if (datalayer_extended.bmwix.dtc_last_read_millis == 0) {
+ // No DTC read has been performed yet
+ content +=
+ "
โน DTCs have not been read yet. Click 'Read DTC' to scan for fault codes.
";
+ } else if (datalayer_extended.bmwix.dtc_read_failed) {
+ content += "
โ Last DTC read failed or not supported
";
+ } else if (datalayer_extended.bmwix.dtc_count == 0) {
+ content += "
โ No DTCs present
";
+ } else {
+ content += "
DTC Count: " + String(datalayer_extended.bmwix.dtc_count) + "
";
+
+ // Convert last read time to days:hours:minutes:seconds format
+ unsigned long last_read_seconds = (millis() - datalayer_extended.bmwix.dtc_last_read_millis) / 1000;
+ unsigned long read_days = last_read_seconds / 86400;
+ unsigned long read_hours = (last_read_seconds % 86400) / 3600;
+ unsigned long read_minutes = (last_read_seconds % 3600) / 60;
+ unsigned long read_seconds = last_read_seconds % 60;
+
+ content += "
Last Read: ";
+ if (read_days > 0) {
+ content += String(read_days) + "d ";
+ }
+ if (read_hours > 0 || read_days > 0) {
+ content += String(read_hours) + "h ";
+ }
+ content += String(read_minutes) + "m " + String(read_seconds) + "s ago
";
+
+ content += "
";
+ content +=
+ "
";
+
+ content += "";
+ content += "";
+ content += "DTC Code ";
+ content += "Status ";
+ content += "Description ";
+ content += " ";
+ content += " ";
+
+ content += "";
+
+ for (int i = 0; i < datalayer_extended.bmwix.dtc_count; i++) {
+ uint32_t code = datalayer_extended.bmwix.dtc_codes[i];
+ uint8_t status = datalayer_extended.bmwix.dtc_status[i];
+
+ char dtcStr[12];
+ sprintf(dtcStr, "%06lX", code);
+
+ String statusStr = "Stored";
+ String statusColor = "#757575";
+
+ if (status & 0x08) {
+ statusStr = "Confirmed";
+ statusColor = "#ff6f00";
+ }
+
+ if (status & 0x01) {
+ statusStr = "Active";
+ statusColor = "#d32f2f";
+ }
+
+ String description = getDTCDescription(code);
+ if (description.length() == 0) {
+ description = "Unknown";
+ }
+
+ content += "";
+ content +=
+ "" +
+ String(dtcStr) + " ";
+ content += "" + statusStr + " ";
+ content += "" +
+ description + " ";
+ content += " ";
+ }
+
+ content += " ";
+ content += "
";
+ content += "
";
+ }
+
+ content += "
";
return content;
}
diff --git a/Software/src/battery/BMW-IX-HTML.h b/Software/src/battery/BMW-IX-HTML.h
index 44bf99d6a..b0101edaf 100644
--- a/Software/src/battery/BMW-IX-HTML.h
+++ b/Software/src/battery/BMW-IX-HTML.h
@@ -1,7 +1,9 @@
#ifndef _BMW_IX_HTML_H
#define _BMW_IX_HTML_H
+#include
#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
#include "../devboard/webserver/BatteryHtmlRenderer.h"
class BmwIXBattery;
@@ -13,6 +15,7 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer {
public:
BmwIXHtmlRenderer(BmwIXBattery& b) : batt(b) {}
+ String getDTCDescription(uint32_t code);
String get_status_html();
};
diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp
index 343ef1cde..7b6729cf8 100644
--- a/Software/src/battery/BMW-PHEV-BATTERY.cpp
+++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp
@@ -4,27 +4,10 @@
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
#include "../devboard/utils/logging.h"
-const unsigned char crc8_table[256] =
- { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
- 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0,
- 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0,
- 0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23,
- 0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40,
- 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B,
- 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B,
- 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8,
- 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D,
- 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC,
- 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B,
- 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C,
- 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB,
- 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47,
- 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
- 0xE3, 0xFE, 0xD9, 0xC4};
-
/*
INFO
@@ -122,13 +105,13 @@ bool BmwPhevBattery::isStale(int16_t currentValue, uint16_t& lastValue, unsigned
}
// Check if the value has stayed the same for the specified staleness period
- return (currentTime - lastChangeTime >= STALE_PERIOD);
+ return (currentTime - lastChangeTime >= STALE_PERIOD_CONFIG);
}
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
- crc = crc8_table[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
+ crc = crc8_table_SAE_J1850_ZER0[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
}
return crc;
}
@@ -191,14 +174,12 @@ void BmwPhevBattery::parseDTCResponse() {
logging.print(gUDSContext.UDS_buffer[2], HEX);
logging.println();
datalayer_extended.bmwphev.dtc_read_failed = true;
- datalayer_extended.bmwphev.dtc_read_in_progress = false;
return;
}
if (gUDSContext.UDS_buffer[0] != 0x59 || gUDSContext.UDS_buffer[1] != 0x02) {
logging.println("Invalid DTC response header");
datalayer_extended.bmwphev.dtc_read_failed = true;
- datalayer_extended.bmwphev.dtc_read_in_progress = false;
return;
}
@@ -240,8 +221,8 @@ void BmwPhevBattery::parseDTCResponse() {
}
// Store valid DTC
- datalayer_extended.bmwphev.dtc_codes[validDtcCount] = dtcCode;
- datalayer_extended.bmwphev.dtc_status[validDtcCount] = dtcStatus;
+ datalayer_extended.bmwix.dtc_codes[validDtcCount] = dtcCode;
+ datalayer_extended.bmwix.dtc_status[validDtcCount] = dtcStatus;
// Log each DTC for debugging
logging.print(" DTC #");
@@ -271,9 +252,8 @@ void BmwPhevBattery::parseDTCResponse() {
logging.print("Total valid DTCs: ");
logging.println(validDtcCount);
- datalayer_extended.bmwphev.dtc_last_read_millis = millis();
+ datalayer_extended.bmwix.dtc_last_read_millis = millis(); //Note we re-use ix struct to save memory
datalayer_extended.bmwphev.dtc_read_failed = false;
- datalayer_extended.bmwphev.dtc_read_in_progress = false;
}
void BmwPhevBattery::processCellVoltages() {
const int startByte = 3; // Start reading at byte 3
@@ -305,20 +285,20 @@ void BmwPhevBattery::wake_battery_via_canbus() {
static bool waiting_for_completion = false;
if (!waiting_for_completion) {
- logging.println("Setting Canbus to 100kbps...");
+ logging.println("Setting Canbus to 100kbps");
change_can_speed(CAN_Speed::CAN_SPEED_100KBPS);
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST);
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST);
- logging.println("Sent magic wakeup packet to SME at 100kbps...");
+ logging.println("Sent magic wakeup packet to SME at 100kbps");
wakeup_start_time = millis();
waiting_for_completion = true;
return;
}
if (millis() - wakeup_start_time >= 50) {
- logging.println("Resetting Canbus speed...");
+ logging.println("Resetting Canbus speed");
change_can_speed(CAN_Speed::CAN_SPEED_500KBPS);
- logging.println("CAN speed restored, ready for normal operation");
+ logging.println("CAN speed restored, ready for operation");
waiting_for_completion = false;
}
}
@@ -372,15 +352,11 @@ void BmwPhevBattery::update_values() { //This function maps all the values fetc
datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
- datalayer.battery.info.number_of_cells = detected_number_of_cells;
-
datalayer_extended.bmwphev.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged);
datalayer_extended.bmwphev.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged);
- datalayer_extended.bmwphev.T30_Voltage = terminal30_12v_voltage;
-
- datalayer_extended.bmwphev.hvil_status = hvil_status;
+ //datalayer_extended.bmwphev.hvil_status = hvil_status; //TODO, not implemented
datalayer_extended.bmwphev.allowable_charge_amps = allowable_charge_amps;
@@ -419,11 +395,6 @@ void BmwPhevBattery::update_values() { //This function maps all the values fetc
datalayer.battery.info.max_design_voltage_dV = max_design_voltage;
datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
}
- if (cell_limit_info_available) {
- // If we have cell limit data from battery - override the defaults to suit
- datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
- datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
- }
}
void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
@@ -437,7 +408,7 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
battery_request_open_contactors_fast = (rx_frame.data.u8[6] & 0x0C) >> 2;
battery_charging_condition_delta = (rx_frame.data.u8[6] & 0xF0) >> 4;
- battery_DC_link_voltage = rx_frame.data.u8[7];
+ //battery_DC_link_voltage = rx_frame.data.u8[7];
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
break;
@@ -483,7 +454,7 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
{
// UDS Multi Frame vars - Top nibble indicates Frame Type: SF (0), FF (1), CF (2), FC (3)
// Extended addressing => data[0] is ext address, data[1] is PCI
- uint8_t extAddr = rx_frame.data.u8[0]; // e.g., 0xF1
+ //uint8_t extAddr = rx_frame.data.u8[0]; // e.g., 0xF1
uint8_t pciByte = rx_frame.data.u8[1]; // e.g., 0x10, 0x21, etc.
uint8_t pciType = pciByte >> 4; // top nibble => 0=SF,1=FF,2=CF,3=FC
uint8_t pciLower = pciByte & 0x0F; // bottom nibble => length nibble or sequence
@@ -492,8 +463,8 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x0: {
// Single Frame reponse
// SF payload length is in pciLower
- uint8_t sfLength = pciLower;
- uint8_t moduleID = rx_frame.data.u8[5];
+ //uint8_t sfLength = pciLower;
+ //uint8_t moduleID = rx_frame.data.u8[5];
if (rx_frame.DLC == 8 && rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xDD &&
rx_frame.data.u8[4] == 0xC4) { // SOC%
@@ -525,7 +496,6 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
rx_frame.data.u8[3] == 0x03 &&
rx_frame.data.u8[4] == 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01
balancing_status = (rx_frame.data.u8[6]);
- //logging.println("Balancing Status received");
}
break;
@@ -704,10 +674,6 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
min_cell_voltage = new_min_voltage;
max_cell_voltage = new_max_voltage;
- // Always update "last received" timestamps
- min_cell_voltage_lastreceived = millis();
- max_cell_voltage_lastreceived = millis();
-
} else {
logging.println("Cell Min Max Invalid 65535 or 0...");
}
@@ -765,8 +731,8 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4;
battery_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6;
battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F);
- battery_temperature_HV = (rx_frame.data.u8[4] - 50);
- battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50);
+ //battery_temperature_HV = (rx_frame.data.u8[4] - 50);
+ //battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50);
if (rx_frame.data.u8[6] > 0 && rx_frame.data.u8[6] < 255) {
battery_temperature_min = (rx_frame.data.u8[6] - 50);
} else {
@@ -821,9 +787,6 @@ void BmwPhevBattery::transmit_can(unsigned long currentMillis) {
alive_counter_20ms = increment_alive_counter(alive_counter_20ms);
- BMW_13E_counter++;
- BMW_13E.data.u8[4] = BMW_13E_counter;
-
//if (datalayer.battery.status.bms_status == FAULT) { //ALLOW ANY TIME - TEST ONLY
//} //If battery is not in Fault mode, allow contactor to close by sending 10B
//else {
@@ -877,10 +840,10 @@ void BmwPhevBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&BMWPHEV_6F1_REQUEST_BALANCING_START); // Enable Balancing
}
} else {
- // Battery is asleep - try and wake it every 10 seconds
+ // Battery is asleep - try and wake it every 1 seconds
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
- logging.println("Battery asleep - sending wakeup packet...");
+ logging.println("Battery asleep, sending wakeup packet");
wake_battery_via_canbus();
}
}
@@ -889,6 +852,7 @@ void BmwPhevBattery::transmit_can(unsigned long currentMillis) {
void BmwPhevBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
+ datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h
index 539589c4b..ab72262b1 100644
--- a/Software/src/battery/BMW-PHEV-BATTERY.h
+++ b/Software/src/battery/BMW-PHEV-BATTERY.h
@@ -55,6 +55,8 @@ class BmwPhevBattery : public CanBattery {
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
+ unsigned long min_cell_voltage_lastchanged = 0;
+ unsigned long max_cell_voltage_lastchanged = 0;
static const int ALIVE_MAX_VALUE = 14; // BMW CAN messages contain alive counter, goes from 0...14
@@ -77,31 +79,17 @@ class BmwPhevBattery : public CanBattery {
UDS_RxContext gUDSContext;
//Vehicle CAN START
-
- CAN_frame BMWiX_0C0 = {
- .FD = false,
- .ext_ID = false,
- .DLC = 2,
- .ID = 0x0C0,
- .data = {
- 0xF0,
- 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 - MINIMUM ID TO KEEP SME AWAKE
-
- CAN_frame BMW_13E = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x13E,
- .data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
-
- uint8_t alive_counter_100ms = 0;
-
CAN_frame BMW_12F = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12F,
.data = {0x00, 0x20, 0x86, 0x1B, 0xF1, 0x35, 0x30, 0x02}}; // CRC, counter starts at 0x20, static data
-
+ CAN_frame BMW_10B = {.FD = false,
+ .ext_ID = false,
+ .DLC = 3,
+ .ID = 0x10B,
+ .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
//Vehicle CAN END
//Request Data CAN START
@@ -284,14 +272,7 @@ class BmwPhevBattery : public CanBattery {
.DLC = 8,
.ID = 0x6F1,
.data = {0x07, 0x04, 0x31, 0x02, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing stop request
-
- //Action Requests:
- CAN_frame BMW_10B = {.FD = false,
- .ext_ID = false,
- .DLC = 3,
- .ID = 0x10B,
- .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command?
-
+ /*
CAN_frame BMWPHEV_6F1_CELL_SOC = {.FD = false,
.ext_ID = false,
.DLC = 5,
@@ -302,10 +283,9 @@ class BmwPhevBattery : public CanBattery {
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
+ */
//Request Data CAN End
- bool battery_awake = false;
-
//Setup Fast UDS values to poll for
CAN_frame* UDS_REQUESTS_FAST[5] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY, &BMWPHEV_6F1_REQUEST_SOC,
&BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS, &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR,
@@ -328,41 +308,53 @@ class BmwPhevBattery : public CanBattery {
//PHEV intermediate vars
//#define UDS_LOG //Useful for logging multiframe handling
- uint16_t battery_max_charge_voltage = 0;
- int16_t battery_max_charge_amperage = 0;
- uint16_t battery_min_discharge_voltage = 0;
- int16_t battery_max_discharge_amperage = 0;
-
- uint8_t startup_counter_contactor = 0;
- uint8_t alive_counter_20ms = 0;
- uint8_t BMW_13E_counter = 0;
uint32_t battery_BEV_available_power_shortterm_charge = 0;
uint32_t battery_BEV_available_power_shortterm_discharge = 0;
uint32_t battery_BEV_available_power_longterm_charge = 0;
uint32_t battery_BEV_available_power_longterm_discharge = 0;
+ int32_t battery_current = 0;
+
+ uint16_t battery_max_charge_voltage = 0;
+ uint16_t battery_min_discharge_voltage = 0;
uint16_t battery_predicted_energy_charge_condition = 0;
uint16_t battery_predicted_energy_charging_target = 0;
-
uint16_t battery_prediction_voltage_shortterm_charge = 0;
uint16_t battery_prediction_voltage_shortterm_discharge = 0;
uint16_t battery_prediction_voltage_longterm_charge = 0;
uint16_t battery_prediction_voltage_longterm_discharge = 0;
+ uint16_t battery_prediction_duration_charging_minutes = 0;
+ uint16_t battery_energy_content_maximum_kWh = 0;
+ uint16_t battery_target_voltage_in_CV_mode = 0;
+ uint16_t battery_display_SOC = 0;
+ uint16_t battery_voltage = 3700; //Initialize as valid - should be fixed in future
+ uint16_t battery_voltage_after_contactor = 0;
+ uint16_t avg_soc_state = 5000;
+ uint16_t min_soh_state = 9999; // Uses E5 45, also available in 78 73
+ uint16_t max_design_voltage = MAX_PACK_VOLTAGE_DV;
+ uint16_t min_design_voltage = MIN_PACK_VOLTAGE_DV;
+ uint16_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT
+ uint16_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT
+ uint16_t iso_safety_trg_kohm = 0;
+ uint16_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT
+
+ int16_t battery_max_discharge_amperage = 0;
+ int16_t battery_max_charge_amperage = 0;
+ int16_t battery_temperature_max = 0;
+ int16_t battery_temperature_min = 0;
+ int16_t min_cell_voltage = 3700; //Initialize as valid - should be fixed in future
+ int16_t max_cell_voltage = 3700; //Initialize as valid - should be fixed in future
+ int16_t allowable_charge_amps = 0; //E5 62
+ int16_t allowable_discharge_amps = 0; //E5 62
uint8_t battery_status_service_disconnection_plug = 0;
uint8_t battery_status_measurement_isolation = 0;
uint8_t battery_request_abort_charging = 0;
- uint16_t battery_prediction_duration_charging_minutes = 0;
uint8_t battery_prediction_time_end_of_charging_minutes = 0;
- uint16_t battery_energy_content_maximum_kWh = 0;
-
- uint8_t battery_request_operating_mode = 0;
- uint16_t battery_target_voltage_in_CV_mode = 0;
uint8_t battery_request_charging_condition_minimum = 0;
uint8_t battery_request_charging_condition_maximum = 0;
- uint16_t battery_display_SOC = 0;
-
+ uint8_t battery_request_operating_mode = 0;
uint8_t battery_status_error_isolation_external_Bordnetz = 0;
uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
uint8_t battery_request_cooling = 0;
@@ -380,74 +372,20 @@ class BmwPhevBattery : public CanBattery {
uint8_t battery_request_open_contactors_instantly = 0;
uint8_t battery_request_open_contactors_fast = 0;
uint8_t battery_charging_condition_delta = 0;
- uint16_t battery_DC_link_voltage = 0;
- int16_t battery_temperature_HV = 0;
- int16_t battery_temperature_heat_exchanger = 0;
- int16_t battery_temperature_max = 0;
- int16_t battery_temperature_min = 0;
- bool pack_limit_info_available = false;
- bool cell_limit_info_available = false;
-
- //iX Intermediate vars
-
- uint32_t battery_serial_number = 0;
- int32_t battery_current = 0;
- int16_t battery_voltage = 3700; //Initialize as valid - should be fixed in future
- int16_t terminal30_12v_voltage = 0;
- int16_t battery_voltage_after_contactor = 0;
- int16_t min_soc_state = 5000;
- int16_t avg_soc_state = 5000;
- int16_t max_soc_state = 5000;
- int16_t min_soh_state = 9999; // Uses E5 45, also available in 78 73
- int16_t avg_soh_state = 9999; // Uses E5 45, also available in 78 73
- int16_t max_soh_state = 9999; // Uses E5 45, also available in 78 73
- uint16_t max_design_voltage = 0;
- uint16_t min_design_voltage = 0;
- int32_t remaining_capacity = 0;
- int32_t max_capacity = 0;
-
- int16_t main_contactor_temperature = 0;
- int16_t min_cell_voltage = 3700; //Initialize as valid - should be fixed in future
- int16_t max_cell_voltage = 3700; //Initialize as valid - should be fixed in future
- unsigned long min_cell_voltage_lastchanged = 0;
- unsigned long max_cell_voltage_lastchanged = 0;
- unsigned min_cell_voltage_lastreceived = 0;
- unsigned max_cell_voltage_lastreceived = 0;
- int16_t allowable_charge_amps = 0; //E5 62
- int16_t allowable_discharge_amps = 0; //E5 62
-
- int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT
- int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT
- int32_t iso_safety_trg_kohm = 0;
- int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS
- int32_t iso_safety_int_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_WERT
- int32_t iso_safety_trg_plausible = 0;
- int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT
- int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better)
-
- uint8_t paired_vin[17] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //17 Byte array for paired VIN
-
- int16_t count_full_charges = 0; //TODO 42
- int16_t count_charges = 0; //TODO 42
- int16_t hvil_status = 0;
- int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
- int16_t balancing_status = 0; //4 = not active
- uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
- uint8_t contactor_status_precharge = 0; //TODO E5 BF
- uint8_t contactor_status_negative = 0; //TODO E5 BF
- uint8_t contactor_status_positive = 0; //TODO E5 BF
+ uint8_t startup_counter_contactor = 0;
+ uint8_t alive_counter_20ms = 0;
+ uint8_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS
+ uint8_t iso_safety_int_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_WERT
+ uint8_t iso_safety_trg_plausible = 0;
+ uint8_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better)
+ uint8_t balancing_status = 0; //4 = not active
uint8_t uds_fast_req_id_counter = 0;
uint8_t uds_slow_req_id_counter = 0;
- uint8_t detected_number_of_cells = 96;
- const unsigned long STALE_PERIOD =
- STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
-
- uint8_t iX_0C0_counter = 0xF0; // Initialize to 0xF0
-
- //End iX Intermediate vars
-
- uint8_t current_cell_polled = 0;
+ uint8_t alive_counter_100ms = 0;
+ uint8_t paired_vin[17] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //17 Byte array for paired VIN
+ bool pack_limit_info_available = false;
+ bool battery_awake = false;
};
#endif
diff --git a/Software/src/battery/BMW-PHEV-HTML.h b/Software/src/battery/BMW-PHEV-HTML.h
index 4c07ce926..bfcd3f6cd 100644
--- a/Software/src/battery/BMW-PHEV-HTML.h
+++ b/Software/src/battery/BMW-PHEV-HTML.h
@@ -9,6 +9,7 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
public:
String getDTCDescription(uint32_t code) {
switch (code) {
+#ifndef SMALL_FLASH_DEVICE
// Contactor & Safety System
case 0x21F1F6:
return "High-voltage battery, switch contactors: Switch-off after a fault";
@@ -18,7 +19,6 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
return "High-voltage battery, contactor: Shutdown due to error";
case 0x21F190:
return "High-voltage battery: Control of switching contactor deactivated due to transport mode";
-
// Precharge System
case 0x21F156:
return "High-voltage battery, preloading: Safety box, precondition not fulfilled";
@@ -138,9 +138,12 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
return "High-voltage battery, cell modules: Temperature difference too large (fault threshold)";
case 0x21F22E:
return "High-voltage battery, cell modules: Check for faulty temperature sensors failed";
-
default:
return ""; // No description available
+#else
+ default:
+ return "Current BE hardware does not support details. Please upgrade to large flash BE"; // Detailed DTC descriptions not available on 4MB low flash devices. The above text takes up a massive amount of flash!
+#endif
}
}
String get_status_html() {
@@ -154,7 +157,6 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
content +=
"Battery Voltage (After Contactor): " + String(datalayer_extended.bmwphev.battery_voltage_after_contactor) +
" dV ";
- content += "T30 Terminal Voltage (Todo): " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV ";
content += "Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV ";
content += "Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV ";
content += "Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W ";
@@ -483,7 +485,7 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
} else {
content += "DTC Count: " + String(datalayer_extended.bmwphev.dtc_count) + "
";
content += "Last Read: " +
- String((millis() - datalayer_extended.bmwphev.dtc_last_read_millis) / 1000) + "s ago
";
+ String((millis() - datalayer_extended.bmwix.dtc_last_read_millis) / 1000) + "s ago ";
content += "";
content +=
@@ -502,11 +504,11 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
content += "
";
for (int i = 0; i < datalayer_extended.bmwphev.dtc_count; i++) {
- uint32_t code = datalayer_extended.bmwphev.dtc_codes[i];
- uint8_t status = datalayer_extended.bmwphev.dtc_status[i];
+ uint32_t code = datalayer_extended.bmwix.dtc_codes[i]; //Note we re-use datalayer for iX to save space
+ uint8_t status = datalayer_extended.bmwix.dtc_status[i]; //Note we re-use datalayer for iX to save space
char dtcStr[12];
- sprintf(dtcStr, "%06X", code);
+ sprintf(dtcStr, "%06lX", code);
String statusStr = "Stored";
String statusColor = "#757575";
diff --git a/Software/src/battery/BMW-SBOX.cpp b/Software/src/battery/BMW-SBOX.cpp
index ff90ce446..ac7973850 100644
--- a/Software/src/battery/BMW-SBOX.cpp
+++ b/Software/src/battery/BMW-SBOX.cpp
@@ -103,8 +103,7 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
SBOX_100.data.u8[0] = 0x55; // All open
if (datalayer.system.status.battery_allows_contactor_closing &&
- datalayer.system.status.inverter_allows_contactor_closing &&
- !datalayer.system.settings.equipment_stop_active &&
+ datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.info.equipment_stop_active &&
(datalayer.shunt.measured_voltage_mV > MINIMUM_INPUT_VOLTAGE * 1000)) {
contactorStatus = PRECHARGE;
}
@@ -112,8 +111,7 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
// In case the inverter requests contactors to open, set the state accordingly
if (contactorStatus == COMPLETED) {
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
- if (!datalayer.system.status.inverter_allows_contactor_closing ||
- datalayer.system.settings.equipment_stop_active) {
+ if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.info.equipment_stop_active) {
contactorStatus = DISCONNECTED;
}
}
@@ -171,6 +169,6 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
}
void BmwSbox::setup() {
- strncpy(datalayer.system.info.shunt_protocol, Name, 63);
- datalayer.system.info.shunt_protocol[63] = '\0';
+ strncpy(datalayer.system.info.shunt_protocol, Name, 31);
+ datalayer.system.info.shunt_protocol[31] = '\0';
}
diff --git a/Software/src/battery/BOLT-AMPERA-BATTERY.cpp b/Software/src/battery/BOLT-AMPERA-BATTERY.cpp
index fce8e1b86..2296e9545 100644
--- a/Software/src/battery/BOLT-AMPERA-BATTERY.cpp
+++ b/Software/src/battery/BOLT-AMPERA-BATTERY.cpp
@@ -70,7 +70,7 @@ static uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation fun
for (int i = 1; i < numEntries; ++i) {
if (cellVoltage >= voltage_lookup[i]) {
- double t = (cellVoltage - voltage_lookup[i]) / (voltage_lookup[i - 1] - voltage_lookup[i]);
+ float t = (cellVoltage - voltage_lookup[i]) / (voltage_lookup[i - 1] - voltage_lookup[i]);
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
}
}
@@ -151,198 +151,52 @@ void BoltAmperaBattery::update_values() { //This function maps all the values f
}
void BoltAmperaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ uint8_t cellbank_mux = 0;
+ uint8_t cellblock_index = 0;
switch (rx_frame.ID) {
case 0x200: //High voltage Battery Cell Voltage Matrix 1
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cellbank_mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //Goes from 0-7
- switch (cellbank_mux) {
- case 0:
- cellblock_voltage[0] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[1] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[2] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 1:
- cellblock_voltage[3] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[4] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[5] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 2:
- cellblock_voltage[6] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[7] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[8] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 3:
- cellblock_voltage[9] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[10] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[11] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 4:
- cellblock_voltage[12] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[13] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[14] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 5:
- cellblock_voltage[15] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[16] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[17] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 6:
- cellblock_voltage[18] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[19] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[20] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 7:
- cellblock_voltage[21] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[22] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[23] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- default:
- break;
- }
+ cellblock_index = cellbank_mux * 3;
+ cellblock_voltage[cellblock_index] =
+ (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 1] =
+ (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 2] =
+ (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25f;
break;
case 0x202: //High voltage Battery Cell Voltage Matrix 2
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cellbank_mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
- switch (cellbank_mux) {
- case 0:
- cellblock_voltage[24] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[25] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[26] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 1:
- cellblock_voltage[27] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[28] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[29] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 2:
- cellblock_voltage[30] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[31] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[32] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 3:
- cellblock_voltage[33] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[34] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[35] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 4:
- cellblock_voltage[36] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[37] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[38] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 5:
- cellblock_voltage[39] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[40] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[41] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 6:
- cellblock_voltage[42] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[43] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[44] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 7:
- cellblock_voltage[45] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[46] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[47] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- default:
- break;
- }
+ cellblock_index = 24 + (cellbank_mux * 3);
+ cellblock_voltage[cellblock_index] =
+ (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 1] =
+ (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 2] =
+ (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25f;
break;
case 0x204: //High voltage Battery Cell Voltage Matrix 3
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cellbank_mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
- switch (cellbank_mux) {
- case 0:
- cellblock_voltage[48] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[49] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[50] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 1:
- cellblock_voltage[51] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[52] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[53] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 2:
- cellblock_voltage[54] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[55] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[56] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 3:
- cellblock_voltage[57] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[58] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[59] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 4:
- cellblock_voltage[60] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[61] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[62] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 5:
- cellblock_voltage[63] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[64] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[65] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 6:
- cellblock_voltage[66] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[67] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[68] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 7:
- cellblock_voltage[69] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[70] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[71] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- default:
- break;
- }
+ cellblock_index = 48 + (cellbank_mux * 3);
+ cellblock_voltage[cellblock_index] =
+ (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 1] =
+ (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 2] =
+ (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25f;
break;
case 0x206: //High voltage Battery Cell Voltage Matrix 4
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cellbank_mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
- switch (cellbank_mux) {
- case 0:
- cellblock_voltage[72] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[73] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[74] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 1:
- cellblock_voltage[75] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[76] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[77] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 2:
- cellblock_voltage[78] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[79] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[80] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 3:
- cellblock_voltage[81] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[82] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[83] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 4:
- cellblock_voltage[84] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[85] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[86] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 5:
- cellblock_voltage[87] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[88] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[89] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 6:
- cellblock_voltage[90] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[91] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[92] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- case 7:
- cellblock_voltage[93] = (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[94] = (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25;
- cellblock_voltage[95] = (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25;
- break;
- default:
- break;
- }
+ cellblock_index = 72 + (cellbank_mux * 3);
+ cellblock_voltage[cellblock_index] =
+ (((rx_frame.data.u8[0] & 0x1F) << 7) | ((rx_frame.data.u8[1] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 1] =
+ (((rx_frame.data.u8[2] & 0x1F) << 7) | ((rx_frame.data.u8[3] & 0xFE) >> 1)) * 1.25f;
+ cellblock_voltage[cellblock_index + 2] =
+ (((rx_frame.data.u8[4]) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * 1.25f;
break;
case 0x208: //High voltage Battery Cell Voltage Matrix 5 (Empty on most packs)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -536,295 +390,18 @@ void BoltAmperaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case POLL_7E7_IGNITION_POWER_MODE:
battery_ignition_power_mode = rx_frame.data.u8[4];
break;
- case POLL_7E7_CELL_01:
- battery_cell_voltages[0] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_02:
- battery_cell_voltages[1] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_03:
- battery_cell_voltages[2] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_04:
- battery_cell_voltages[3] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_05:
- battery_cell_voltages[4] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_06:
- battery_cell_voltages[5] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_07:
- battery_cell_voltages[6] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_08:
- battery_cell_voltages[7] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_09:
- battery_cell_voltages[8] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_10:
- battery_cell_voltages[9] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_11:
- battery_cell_voltages[10] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_12:
- battery_cell_voltages[11] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_13:
- battery_cell_voltages[12] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_14:
- battery_cell_voltages[13] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_15:
- battery_cell_voltages[14] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_16:
- battery_cell_voltages[15] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_17:
- battery_cell_voltages[16] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_18:
- battery_cell_voltages[17] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_19:
- battery_cell_voltages[18] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_20:
- battery_cell_voltages[19] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_21:
- battery_cell_voltages[20] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_22:
- battery_cell_voltages[21] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_23:
- battery_cell_voltages[22] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_24:
- battery_cell_voltages[23] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_25:
- battery_cell_voltages[24] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_26:
- battery_cell_voltages[25] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_27:
- battery_cell_voltages[26] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_28:
- battery_cell_voltages[27] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_29:
- battery_cell_voltages[28] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_30:
- battery_cell_voltages[29] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_31:
- battery_cell_voltages[30] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_32:
- battery_cell_voltages[31] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_33:
- battery_cell_voltages[32] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_34:
- battery_cell_voltages[33] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_35:
- battery_cell_voltages[34] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_36:
- battery_cell_voltages[35] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_37:
- battery_cell_voltages[36] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_38:
- battery_cell_voltages[37] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_39:
- battery_cell_voltages[38] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_40:
- battery_cell_voltages[39] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_41:
- battery_cell_voltages[40] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_42:
- battery_cell_voltages[41] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_43:
- battery_cell_voltages[42] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_44:
- battery_cell_voltages[43] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_45:
- battery_cell_voltages[44] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_46:
- battery_cell_voltages[45] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_47:
- battery_cell_voltages[46] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_48:
- battery_cell_voltages[47] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_49:
- battery_cell_voltages[48] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_50:
- battery_cell_voltages[49] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_51:
- battery_cell_voltages[50] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_52:
- battery_cell_voltages[51] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_53:
- battery_cell_voltages[52] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_54:
- battery_cell_voltages[53] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_55:
- battery_cell_voltages[54] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_56:
- battery_cell_voltages[55] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_57:
- battery_cell_voltages[56] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_58:
- battery_cell_voltages[57] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_59:
- battery_cell_voltages[58] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_60:
- battery_cell_voltages[59] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_61:
- battery_cell_voltages[60] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_62:
- battery_cell_voltages[61] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_63:
- battery_cell_voltages[62] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_64:
- battery_cell_voltages[63] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_65:
- battery_cell_voltages[64] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_66:
- battery_cell_voltages[65] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_67:
- battery_cell_voltages[66] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_68:
- battery_cell_voltages[67] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_69:
- battery_cell_voltages[68] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_70:
- battery_cell_voltages[69] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_71:
- battery_cell_voltages[70] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_72:
- battery_cell_voltages[71] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_73:
- battery_cell_voltages[72] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_74:
- battery_cell_voltages[73] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_75:
- battery_cell_voltages[74] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_76:
- battery_cell_voltages[75] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_77:
- battery_cell_voltages[76] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_78:
- battery_cell_voltages[77] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_79:
- battery_cell_voltages[78] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_80:
- battery_cell_voltages[79] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_81:
- battery_cell_voltages[80] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_82:
- battery_cell_voltages[81] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_83:
- battery_cell_voltages[82] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_84:
- battery_cell_voltages[83] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_85:
- battery_cell_voltages[84] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_86:
- battery_cell_voltages[85] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_87:
- battery_cell_voltages[86] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_88:
- battery_cell_voltages[87] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_89:
- battery_cell_voltages[88] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_90:
- battery_cell_voltages[89] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_91:
- battery_cell_voltages[90] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_92:
- battery_cell_voltages[91] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_93:
- battery_cell_voltages[92] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_94:
- battery_cell_voltages[93] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_95:
- battery_cell_voltages[94] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
- case POLL_7E7_CELL_96:
- battery_cell_voltages[95] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
- break;
default:
+ // Handle cell voltages in two banks (as they are not contiguous)
+
+ if (reply_poll_7E7 >= POLL_7E7_CELL_01 && reply_poll_7E7 <= POLL_7E7_CELL_31) {
+ battery_cell_voltages[reply_poll_7E7 - POLL_7E7_CELL_01] =
+ ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
+ }
+
+ if (reply_poll_7E7 >= POLL_7E7_CELL_32 && reply_poll_7E7 <= POLL_7E7_CELL_96) {
+ battery_cell_voltages[reply_poll_7E7 - POLL_7E7_CELL_32 + 31] =
+ ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
+ }
break;
}
default:
diff --git a/Software/src/battery/BOLT-AMPERA-BATTERY.h b/Software/src/battery/BOLT-AMPERA-BATTERY.h
index 02d8bbdab..cc31149d4 100644
--- a/Software/src/battery/BOLT-AMPERA-BATTERY.h
+++ b/Software/src/battery/BOLT-AMPERA-BATTERY.h
@@ -253,7 +253,6 @@ class BoltAmperaBattery : public CanBattery {
int16_t temperature_6 = 0;
int16_t temperature_highest_C = 0;
int16_t temperature_lowest_C = 0;
- uint8_t cellbank_mux = 0;
uint8_t poll_index_7E4 = 0;
uint16_t currentpoll_7E4 = POLL_7E4_CAPACITY_EST_GEN1;
uint16_t reply_poll_7E4 = 0;
diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp
index 7e4137fca..6a3c0b89f 100644
--- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp
+++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp
@@ -113,7 +113,7 @@ uint16_t estimateSOCextended(uint16_t packVoltage) { // Linear interpolation fu
for (int i = 1; i < numPoints; ++i) {
if (packVoltage >= voltage_extended[i]) {
- double t = (packVoltage - voltage_extended[i]) / (voltage_extended[i - 1] - voltage_extended[i]);
+ float t = (packVoltage - voltage_extended[i]) / (voltage_extended[i - 1] - voltage_extended[i]);
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
}
}
@@ -130,7 +130,7 @@ uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation fu
for (int i = 1; i < numPoints; ++i) {
if (packVoltage >= voltage_standard[i]) {
- double t = (packVoltage - voltage_standard[i]) / (voltage_standard[i - 1] - voltage_standard[i]);
+ float t = (packVoltage - voltage_standard[i]) / (voltage_standard[i - 1] - voltage_standard[i]);
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
}
}
@@ -151,7 +151,9 @@ void BydAttoBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
if (BMS_voltage > 0) {
- datalayer_battery->status.voltage_dV = BMS_voltage * 10;
+ datalayer_battery->status.voltage_dV = BMS_voltage * 10; //Polled value
+ } else if (battery_voltage > 0) {
+ datalayer_battery->status.voltage_dV = battery_voltage * 10; //Value from periodic CAN data
}
if (battery_type == EXTENDED_RANGE) {
@@ -171,14 +173,16 @@ void BydAttoBattery::
datalayer_battery->status.real_soc = battery_estimated_SOC;
}
+ datalayer_battery->status.soh_pptt = BMS_SOH * 100;
+
datalayer_battery->status.current_dA = -BMS_current;
datalayer_battery->status.remaining_capacity_Wh = static_cast(
(static_cast(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
- if (SOC_method == ESTIMATED && battery_estimated_SOC * 0.1 < RAMPDOWN_SOC && RAMPDOWN_SOC > 0) {
+ if (SOC_method == ESTIMATED && battery_estimated_SOC * 0.1f < RAMPDOWN_SOC && RAMPDOWN_SOC > 0) {
// If using estimated SOC, ramp down max discharge power as SOC decreases.
- rampdown_power = RAMPDOWN_POWER_ALLOWED * ((battery_estimated_SOC * 0.1) / RAMPDOWN_SOC);
+ rampdown_power = RAMPDOWN_POWER_ALLOWED * ((battery_estimated_SOC * 0.1f) / RAMPDOWN_SOC);
if (rampdown_power < BMS_allowed_discharge_power * 100) { // Never allow more than BMS_allowed_discharge_power
datalayer_battery->status.max_discharge_power_W = rampdown_power;
@@ -195,6 +199,103 @@ void BydAttoBattery::
datalayer_battery->status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
+ // AC-like top-of-charge taper
+
+ // Tune thresholds here
+ const uint16_t V_TAPER_START_mV = 3420; // begin tapering here
+ const uint16_t V_TAPER_END_mV = 3500; // reach tail current by here (stay below hard clamp region)
+
+ const uint16_t D_TAPER_START_mV = 40; // begin tapering if delta exceeds this
+ const uint16_t D_TAPER_END_mV = 80; // reach tail current by here
+
+ const uint16_t TAIL_CURRENT_dA = 2; // 0.2A tail (deci-amps). You can set to 1 for 0.1A.
+
+ // Slew limits to make taper gradual
+ const uint16_t DOWN_RATE_dA_per_s = 2; // ramp down at 0.2A/s (change to 5 for 0.5A/s)
+ const uint16_t UP_RATE_dA_per_s = 1; // ramp up at 0.1A/s
+
+ const uint16_t cell_max_mV = datalayer_battery->status.cell_max_voltage_mV;
+ const uint16_t cell_min_mV = datalayer_battery->status.cell_min_voltage_mV;
+ const uint16_t delta_mV = (cell_max_mV > cell_min_mV) ? (cell_max_mV - cell_min_mV) : 0;
+
+ // Start from the user manual limit (deci-amps), but don't allow taper to go below tail current.
+ uint16_t user_cap_dA = datalayer_battery->settings.max_user_set_charge_dA;
+ if (user_cap_dA < TAIL_CURRENT_dA)
+ user_cap_dA = TAIL_CURRENT_dA;
+
+ // Compute taper progress 0..1 from voltage and delta; take whichever is "worse".
+ auto clamp01 = [](float x) -> float {
+ if (x < 0.0f)
+ return 0.0f;
+ if (x > 1.0f)
+ return 1.0f;
+ return x;
+ };
+
+ float vprog = 0.0f;
+ if (cell_max_mV > V_TAPER_START_mV) {
+ const uint16_t denom = (V_TAPER_END_mV > V_TAPER_START_mV) ? (V_TAPER_END_mV - V_TAPER_START_mV) : 1;
+ vprog = float(cell_max_mV - V_TAPER_START_mV) / float(denom);
+ }
+
+ float dprog = 0.0f;
+ if (delta_mV > D_TAPER_START_mV) {
+ const uint16_t denom = (D_TAPER_END_mV > D_TAPER_START_mV) ? (D_TAPER_END_mV - D_TAPER_START_mV) : 1;
+ dprog = float(delta_mV - D_TAPER_START_mV) / float(denom);
+ }
+
+ const float prog = clamp01((vprog > dprog) ? vprog : dprog);
+
+ // Desired current cap (deci-amps): linearly reduce from user_cap -> tail as prog goes 0 -> 1
+ uint16_t cap_target_dA = user_cap_dA;
+ if (prog > 0.0f) {
+ const float span = float(user_cap_dA - TAIL_CURRENT_dA);
+ cap_target_dA = uint16_t(float(TAIL_CURRENT_dA) + (1.0f - prog) * span);
+ if (cap_target_dA < TAIL_CURRENT_dA)
+ cap_target_dA = TAIL_CURRENT_dA;
+ }
+
+ // Slew-limit the cap so it changes smoothly over time
+ static uint16_t cap_slewed_dA = 0;
+ static uint32_t last_ms = 0;
+
+ const uint32_t now_ms = (uint32_t)millis64();
+ if (last_ms == 0) {
+ last_ms = now_ms;
+ cap_slewed_dA = user_cap_dA;
+ }
+
+ uint32_t dt_ms = now_ms - last_ms;
+ last_ms = now_ms;
+ if (dt_ms == 0)
+ dt_ms = 1;
+
+ uint32_t down_step = (uint32_t)DOWN_RATE_dA_per_s * dt_ms / 1000;
+ uint32_t up_step = (uint32_t)UP_RATE_dA_per_s * dt_ms / 1000;
+ if (down_step < 1)
+ down_step = 1;
+ if (up_step < 1)
+ up_step = 1;
+
+ if (cap_target_dA < cap_slewed_dA) {
+ const uint16_t diff = cap_slewed_dA - cap_target_dA;
+ const uint16_t step = (down_step >= diff) ? diff : (uint16_t)down_step;
+ cap_slewed_dA -= step;
+ } else if (cap_target_dA > cap_slewed_dA) {
+ const uint16_t diff = cap_target_dA - cap_slewed_dA;
+ const uint16_t step = (up_step >= diff) ? diff : (uint16_t)up_step;
+ cap_slewed_dA += step;
+ }
+
+ // Convert current cap (dA) -> power cap (W): P = I(dA) * V(dV) / 100
+ const uint32_t power_cap_W = (uint32_t(cap_slewed_dA) * uint32_t(datalayer_battery->status.voltage_dV)) / 100;
+
+ // Apply taper by capping the allowed charge power reported to the rest of BE/inverter logic.
+ if (datalayer_battery->status.max_charge_power_W > power_cap_W) {
+ datalayer_battery->status.max_charge_power_W = power_cap_W;
+ }
+ // End taper
+
datalayer_battery->status.total_discharged_battery_Wh = BMS_total_discharged_kwh * 1000;
datalayer_battery->status.total_charged_battery_Wh = BMS_total_charged_kwh * 1000;
@@ -393,6 +494,7 @@ void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x444:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
+ BMS_SOH = rx_frame.data.u8[4];
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
BMS_voltage_available = true;
break;
diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h
index a85c70cab..122fcce6a 100644
--- a/Software/src/battery/BYD-ATTO-3-BATTERY.h
+++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h
@@ -69,56 +69,33 @@ class BydAttoBattery : public CanBattery {
DATALAYER_INFO_BYDATTO3* datalayer_bydatto;
bool* allows_contactor_closing;
- static const int CELLCOUNT_EXTENDED = 126;
- static const int CELLCOUNT_STANDARD = 104;
- static const int MAX_PACK_VOLTAGE_EXTENDED_DV = 4410; //Extended range
- static const int MIN_PACK_VOLTAGE_EXTENDED_DV = 3800; //Extended range
- static const int MAX_PACK_VOLTAGE_STANDARD_DV = 3640; //Standard range
- static const int MIN_PACK_VOLTAGE_STANDARD_DV = 3136; //Standard range
- static const int MAX_CELL_DEVIATION_MV = 230;
- static const int MAX_CELL_VOLTAGE_MV = 3650; //Charging stops if one cell exceeds this value
- static const int MIN_CELL_VOLTAGE_MV = 2800; //Discharging stops if one cell goes below this value
-
- static const int POLL_FOR_BATTERY_SOC = 0x0005;
- static const uint8_t NOT_DETERMINED_YET = 0;
- static const uint8_t STANDARD_RANGE = 1;
- static const uint8_t EXTENDED_RANGE = 2;
-
- static const uint8_t NOT_RUNNING = 0xFF;
- static const uint8_t STARTED = 0;
- static const uint8_t RUNNING_STEP_1 = 1;
- static const uint8_t RUNNING_STEP_2 = 2;
-
- uint8_t battery_type = NOT_DETERMINED_YET;
- uint8_t stateMachineClearCrash = NOT_RUNNING;
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
- bool SOC_method = false;
- bool BMS_voltage_available = false;
- uint8_t counter_50ms = 0;
- uint8_t counter_100ms = 0;
- uint8_t frame6_counter = 0xB;
- uint8_t frame7_counter = 0x5;
+
+ uint32_t BMS_unknown0 = 0;
+ uint32_t BMS_unknown1 = 0;
+
+ static const uint16_t CELLCOUNT_EXTENDED = 126;
+ static const uint16_t CELLCOUNT_STANDARD = 104;
+ static const uint16_t MAX_PACK_VOLTAGE_EXTENDED_DV = 4599; //Extended range
+ static const uint16_t MIN_PACK_VOLTAGE_EXTENDED_DV = 3800; //Extended range
+ static const uint16_t MAX_PACK_VOLTAGE_STANDARD_DV = 3796; //Standard range
+ static const uint16_t MIN_PACK_VOLTAGE_STANDARD_DV = 3136; //Standard range
+ static const uint16_t MAX_CELL_DEVIATION_MV = 230;
+ static const uint16_t MAX_CELL_VOLTAGE_MV = 3650; //Charging stops if one cell exceeds this value
+ static const uint16_t MIN_CELL_VOLTAGE_MV = 2800; //Discharging stops if one cell goes below this value
+ static const uint16_t POLL_FOR_BATTERY_SOC = 0x0005;
+ uint16_t rampdown_power = 0;
+ uint16_t poll_state = POLL_FOR_BATTERY_SOC;
+ uint16_t pid_reply = 0;
uint16_t battery_voltage = 0;
- int16_t battery_temperature_ambient = 0;
- int16_t battery_daughterboard_temperatures[10];
- int16_t battery_lowest_temperature = 0;
- int16_t battery_highest_temperature = 0;
- int16_t battery_calc_min_temperature = 0;
- int16_t battery_calc_max_temperature = 0;
uint16_t battery_highprecision_SOC = 0;
uint16_t battery_estimated_SOC = 0;
uint16_t BMS_SOC = 0;
uint16_t BMS_voltage = 0;
- int16_t BMS_current = 0;
- int16_t BMS_lowest_cell_temperature = 0;
- int16_t BMS_highest_cell_temperature = 0;
- int16_t BMS_average_cell_temperature = 0;
uint16_t BMS_lowest_cell_voltage_mV = 3300;
uint16_t BMS_highest_cell_voltage_mV = 3300;
- uint32_t BMS_unknown0 = 0;
- uint32_t BMS_unknown1 = 0;
uint16_t BMS_allowed_charge_power = 0;
uint16_t BMS_charge_times = 0;
uint16_t BMS_allowed_discharge_power = 0;
@@ -127,16 +104,42 @@ class BydAttoBattery : public CanBattery {
uint16_t BMS_total_charged_kwh = 0;
uint16_t BMS_total_discharged_kwh = 0;
uint16_t BMS_times_full_power = 0;
+
+ int16_t battery_temperature_ambient = 0;
+ int16_t battery_lowest_temperature = 0;
+ int16_t battery_highest_temperature = 0;
+ int16_t battery_calc_min_temperature = 0;
+ int16_t battery_calc_max_temperature = 0;
+ int16_t BMS_current = 0;
+ int16_t BMS_lowest_cell_temperature = 0;
+ int16_t BMS_highest_cell_temperature = 0;
+ int16_t BMS_average_cell_temperature = 0;
+
+ static const uint8_t NOT_DETERMINED_YET = 0;
+ static const uint8_t STANDARD_RANGE = 1;
+ static const uint8_t EXTENDED_RANGE = 2;
+ static const uint8_t NOT_RUNNING = 0xFF;
+ static const uint8_t STARTED = 0;
+ static const uint8_t RUNNING_STEP_1 = 1;
+ static const uint8_t RUNNING_STEP_2 = 2;
+ uint8_t battery_type = NOT_DETERMINED_YET;
+ uint8_t stateMachineClearCrash = NOT_RUNNING;
+ uint8_t counter_50ms = 0;
+ uint8_t counter_100ms = 0;
+ uint8_t frame6_counter = 0xB;
+ uint8_t frame7_counter = 0x5;
+ uint8_t BMS_SOH = 99;
uint8_t BMS_unknown10 = 0;
uint8_t BMS_unknown11 = 0;
uint8_t BMS_unknown12 = 0;
uint8_t BMS_unknown13 = 0;
uint8_t battery_frame_index = 0;
- uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
- uint16_t rampdown_power = 0;
- uint16_t poll_state = POLL_FOR_BATTERY_SOC;
- uint16_t pid_reply = 0;
+ bool SOC_method = false;
+ bool BMS_voltage_available = false;
+
+ int16_t battery_daughterboard_temperatures[10];
+ uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
CAN_frame ATTO_3_12D = {.FD = false,
.ext_ID = false,
diff --git a/Software/src/battery/BYD-ATTO-3-HTML.h b/Software/src/battery/BYD-ATTO-3-HTML.h
index d2a43b873..85cc9f922 100644
--- a/Software/src/battery/BYD-ATTO-3-HTML.h
+++ b/Software/src/battery/BYD-ATTO-3-HTML.h
@@ -12,10 +12,10 @@ class BydAtto3HtmlRenderer : public BatteryHtmlRenderer {
String get_status_html() {
String content;
- float soc_estimated = static_cast(byd_datalayer->SOC_estimated) * 0.01;
- float soc_measured = static_cast(byd_datalayer->SOC_highprec) * 0.1;
- float BMS_maxChargePower = static_cast(byd_datalayer->chargePower) * 0.1;
- float BMS_maxDischargePower = static_cast(byd_datalayer->dischargePower) * 0.1;
+ float soc_estimated = static_cast(byd_datalayer->SOC_estimated) * 0.01f;
+ float soc_measured = static_cast(byd_datalayer->SOC_highprec) * 0.1f;
+ float BMS_maxChargePower = static_cast(byd_datalayer->chargePower) * 0.1f;
+ float BMS_maxDischargePower = static_cast(byd_datalayer->dischargePower) * 0.1f;
static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"};
content += "SOC method used: " + String(SOCmethod[byd_datalayer->SOC_method]) + " ";
diff --git a/Software/src/battery/Battery.cpp b/Software/src/battery/Battery.cpp
index 02280f522..01a80c55a 100644
--- a/Software/src/battery/Battery.cpp
+++ b/Software/src/battery/Battery.cpp
@@ -2,5 +2,5 @@
#include "../datalayer/datalayer.h"
float Battery::get_voltage() {
- return static_cast(datalayer.battery.status.voltage_dV) / 10.0;
+ return static_cast(datalayer.battery.status.voltage_dV) / 10.0f;
}
diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h
index 5c8ba204b..37519c0cd 100644
--- a/Software/src/battery/Battery.h
+++ b/Software/src/battery/Battery.h
@@ -49,6 +49,12 @@ enum class BatteryType {
RelionBattery = 41,
RivianBattery = 42,
BmwPhev = 43,
+ FordMachE = 44,
+ CmpSmartCar = 45,
+ ThinkCity = 47,
+ TeslaLegacy = 48,
+ GrowattHvArk = 49,
+ GeelySea = 50,
Highest
};
@@ -59,6 +65,7 @@ extern const char* name_for_comm_interface(comm_interface comm);
extern BatteryType user_selected_battery_type;
extern bool user_selected_second_battery;
+extern bool user_selected_triple_battery;
extern battery_chemistry_enum user_selected_battery_chemistry;
@@ -90,9 +97,11 @@ class Battery {
virtual bool supports_manual_balancing() { return false; }
virtual bool supports_real_BMS_status() { return false; }
virtual bool supports_toggle_SOC_method() { return false; }
+ virtual bool supports_energy_saving_mode_reset() { return false; }
virtual bool supports_factory_mode_method() { return false; }
virtual bool supports_chademo_restart() { return false; }
virtual bool supports_chademo_stop() { return false; }
+ virtual bool supports_offline_balancing() { return false; }
virtual void clear_isolation() {}
virtual void reset_BMS() {}
@@ -107,9 +116,12 @@ class Battery {
virtual void request_open_contactors() {}
virtual void request_close_contactors() {}
virtual void toggle_SOC_method() {}
+ virtual void reset_energy_saving_mode() {}
virtual void set_factory_mode() {}
virtual void chademo_restart() {}
virtual void chademo_stop() {}
+ virtual void initiate_offline_balancing() {}
+ virtual void end_offline_balancing() {}
virtual void set_fake_voltage(float v) {}
virtual float get_voltage();
diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp
index 8d5d089dd..1c365b98a 100644
--- a/Software/src/battery/CELLPOWER-BMS.cpp
+++ b/Software/src/battery/CELLPOWER-BMS.cpp
@@ -216,18 +216,18 @@ void CellPowerBms::handle_incoming_can_frame(CAN_frame rx_frame) {
}
void CellPowerBms::transmit_can(unsigned long currentMillis) {
-
- // Send 1s CAN Message
+ /*
+ // Send 1s CAN Message. NOTE; Not required to keep BMS happy
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;
-
- /*
+
transmit_can_frame(&CELLPOWER_18FF50E9);
transmit_can_frame(&CELLPOWER_18FF50E8);
transmit_can_frame(&CELLPOWER_18FF50E7);
transmit_can_frame(&CELLPOWER_18FF50E5);
- */
+
}
+ */
}
void CellPowerBms::setup(void) { // Performs one time setup at startup
diff --git a/Software/src/battery/CELLPOWER-BMS.h b/Software/src/battery/CELLPOWER-BMS.h
index 752518482..f2e752451 100644
--- a/Software/src/battery/CELLPOWER-BMS.h
+++ b/Software/src/battery/CELLPOWER-BMS.h
@@ -21,7 +21,7 @@ class CellPowerBms : public CanBattery {
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent
- //Actual content messages
+ /*Actual content messages
// Optional add-on charger module. Might not be needed to send these towards the BMS to keep it happy.
CAN_frame CELLPOWER_18FF50E9 = {.FD = false,
.ext_ID = true,
@@ -43,7 +43,7 @@ class CellPowerBms : public CanBattery {
.DLC = 5,
.ID = 0x18FF50E5,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
-
+ */
bool system_state_discharge = false;
bool system_state_charge = false;
bool system_state_cellbalancing = false;
diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp
index 40205fbe6..6d622bda4 100644
--- a/Software/src/battery/CHADEMO-BATTERY.cpp
+++ b/Software/src/battery/CHADEMO-BATTERY.cpp
@@ -47,12 +47,15 @@ void ChademoBattery::update_values() {
* CAN frames.
*/
+ /*
+
if (vehicle_can_received) {
uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
}
}
+*/
//Update extended datalayer for easier visualization of what's going on
datalayer_extended.chademo.CHADEMO_Status = CHADEMO_Status;
@@ -86,7 +89,7 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
- uint8_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
+ //uint8_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
vehicle_can_initialized = true;
diff --git a/Software/src/battery/CHADEMO-SHUNTS.cpp b/Software/src/battery/CHADEMO-SHUNTS.cpp
index b539f3153..10e6420da 100644
--- a/Software/src/battery/CHADEMO-SHUNTS.cpp
+++ b/Software/src/battery/CHADEMO-SHUNTS.cpp
@@ -28,27 +28,27 @@ 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;
+float AH; //Floating point with accumulated ampere-hours
+float KW;
+float KWH;
+
+float Voltage;
+float Voltage1;
+float Voltage2;
+float Voltage3;
+float VoltageHI;
+float Voltage1HI;
+float Voltage2HI;
+float Voltage3HI;
+float VoltageLO;
+float Voltage1LO;
+float Voltage2LO;
+float Voltage3LO;
+
+float Temperature;
bool firstframe;
-double milliamps;
+float milliamps;
long watt;
long As;
long lastAs;
diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp
index 7ea9e5502..516e584d7 100644
--- a/Software/src/battery/CMFA-EV-BATTERY.cpp
+++ b/Software/src/battery/CMFA-EV-BATTERY.cpp
@@ -34,7 +34,7 @@ void CmfaEvBattery::
datalayer_battery->status.current_dA = current * 10;
- datalayer_battery->status.voltage_dV = average_voltage_of_cells / 100;
+ datalayer_battery->status.voltage_dV = pack_voltage * 5;
datalayer_battery->info.total_capacity_Wh = 27000;
@@ -54,9 +54,6 @@ void CmfaEvBattery::
datalayer_battery->status.cell_max_voltage_mV = highest_cell_voltage_mv;
- //Map all cell voltages to the global array
- memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t));
-
if (lead_acid_voltage < 11000) { //11.000V
set_event(EVENT_12V_LOW, lead_acid_voltage);
}
@@ -80,6 +77,7 @@ void CmfaEvBattery::
datalayer_cmfa->cumulative_energy_when_charging = cumulative_energy_when_charging;
datalayer_cmfa->cumulative_energy_in_regen = cumulative_energy_in_regen;
datalayer_cmfa->soh_average = soh_average;
+ datalayer_cmfa->average_voltage_of_cells = average_voltage_of_cells;
}
}
@@ -89,6 +87,7 @@ void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500);
SOC_raw = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ pack_voltage = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]);
break;
case 0x3D6: //100ms, Same structure as old Zoe 0x424 message!
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -101,7 +100,6 @@ void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x3D7: //100ms
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F)));
break;
case 0x3D8: //100ms
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -213,227 +211,32 @@ void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cumulative_energy_in_regen = (uint64_t)((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) |
(rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7]));
break;
- case PID_POLL_CELL_1:
- cellvoltages_mv[0] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_2:
- cellvoltages_mv[1] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_3:
- cellvoltages_mv[2] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_4:
- cellvoltages_mv[3] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_5:
- cellvoltages_mv[4] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_6:
- cellvoltages_mv[5] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_7:
- cellvoltages_mv[6] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_8:
- cellvoltages_mv[7] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_9:
- cellvoltages_mv[8] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_10:
- cellvoltages_mv[9] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_11:
- cellvoltages_mv[10] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_12:
- cellvoltages_mv[11] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_13:
- cellvoltages_mv[12] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_14:
- cellvoltages_mv[13] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_15:
- cellvoltages_mv[14] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_16:
- cellvoltages_mv[15] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_17:
- cellvoltages_mv[16] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_18:
- cellvoltages_mv[17] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_19:
- cellvoltages_mv[18] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_20:
- cellvoltages_mv[19] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_21:
- cellvoltages_mv[20] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_22:
- cellvoltages_mv[21] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_23:
- cellvoltages_mv[22] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_24:
- cellvoltages_mv[23] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_25:
- cellvoltages_mv[24] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_26:
- cellvoltages_mv[25] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_27:
- cellvoltages_mv[26] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_28:
- cellvoltages_mv[27] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_29:
- cellvoltages_mv[28] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_30:
- cellvoltages_mv[29] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_31:
- cellvoltages_mv[30] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_32:
- cellvoltages_mv[31] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_33:
- cellvoltages_mv[32] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_34:
- cellvoltages_mv[33] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_35:
- cellvoltages_mv[34] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_36:
- cellvoltages_mv[35] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_37:
- cellvoltages_mv[36] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_38:
- cellvoltages_mv[37] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_39:
- cellvoltages_mv[38] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_40:
- cellvoltages_mv[39] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_41:
- cellvoltages_mv[40] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_42:
- cellvoltages_mv[41] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_43:
- cellvoltages_mv[42] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_44:
- cellvoltages_mv[43] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_45:
- cellvoltages_mv[44] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_46:
- cellvoltages_mv[45] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_47:
- cellvoltages_mv[46] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_48:
- cellvoltages_mv[47] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_49:
- cellvoltages_mv[48] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_50:
- cellvoltages_mv[49] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_51:
- cellvoltages_mv[50] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_52:
- cellvoltages_mv[51] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_53:
- cellvoltages_mv[52] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_54:
- cellvoltages_mv[53] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_55:
- cellvoltages_mv[54] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_56:
- cellvoltages_mv[55] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_57:
- cellvoltages_mv[56] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_58:
- cellvoltages_mv[57] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_59:
- cellvoltages_mv[58] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_60:
- cellvoltages_mv[59] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_61:
- cellvoltages_mv[60] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_62:
- cellvoltages_mv[61] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_63:
- cellvoltages_mv[62] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_64:
- cellvoltages_mv[63] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_65:
- cellvoltages_mv[64] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_66:
- cellvoltages_mv[65] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_67:
- cellvoltages_mv[66] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_68:
- cellvoltages_mv[67] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_69:
- cellvoltages_mv[68] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_70:
- cellvoltages_mv[69] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_71:
- cellvoltages_mv[70] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- case PID_POLL_CELL_72:
- cellvoltages_mv[71] = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- break;
- break;
- default:
+ case PID_POLL_DIDS_SUPPORTED_IN_RANGE_9041_9060:
+ break;
+ default: //Unknown pid_reply, or a cellvoltage
+ uint8_t cellnumber = 0;
+ if (pid_reply >= PID_POLL_CELL_1 && pid_reply <= PID_POLL_CELL_31) { //Cellvoltage PID reply
+ cellnumber = (pid_reply - PID_POLL_CELL_1);
+ } else if (pid_reply >= PID_POLL_CELL_32 && pid_reply <= PID_POLL_CELL_62) {
+ cellnumber = (pid_reply - PID_POLL_CELL_1) - 1;
+ } else if (pid_reply >= PID_POLL_CELL_63 && pid_reply <= PID_POLL_CELL_72) {
+ cellnumber = (pid_reply - PID_POLL_CELL_1) - 2;
+ } else { //Unknown pid_reply
+ break;
+ }
+
+ if (cellnumber < MAX_AMOUNT_CELLS) { //Prevent out of bounds array write
+ uint16_t cellvoltage_reading = (uint16_t)((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ if (cellvoltage_reading == 0) {
+ //Blown fuse/celltap. Force value to 10mV so user sees this in cellmonitor page. Also fire event
+ cellvoltage_reading = 10;
+ set_event(EVENT_BATTERY_FUSE, cellnumber);
+ }
+ datalayer_battery->status.cell_voltages_mV[cellnumber] = cellvoltage_reading * 0.976563;
+ }
+
break;
}
-
break;
default:
break;
@@ -939,7 +742,12 @@ void CmfaEvBattery::transmit_can(unsigned long currentMillis) {
break;
}
- transmit_can_frame(&CMFA_POLLING_FRAME);
+ if (UserRequestDTCclear) {
+ transmit_can_frame(&CMFA_CLEAR_DTC);
+ UserRequestDTCclear = false;
+ } else { //Normal PID polling
+ transmit_can_frame(&CMFA_POLLING_FRAME);
+ }
}
}
diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h
index 16a2e1005..1342d9c23 100644
--- a/Software/src/battery/CMFA-EV-BATTERY.h
+++ b/Software/src/battery/CMFA-EV-BATTERY.h
@@ -25,6 +25,9 @@ class CmfaEvBattery : public CanBattery {
datalayer_cmfa = &datalayer_extended.CMFAEV;
}
+ bool supports_reset_DTC() { return true; }
+ void reset_DTC() { UserRequestDTCclear = true; }
+
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
@@ -42,6 +45,8 @@ class CmfaEvBattery : public CanBattery {
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
+ bool UserRequestDTCclear = false;
+
uint16_t rescale_raw_SOC(uint32_t raw_SOC);
static const int MAX_PACK_VOLTAGE_DV = 3040; // 5000 = 500.0V
@@ -186,6 +191,11 @@ class CmfaEvBattery : public CanBattery {
.DLC = 8,
.ID = 0x79B,
.data = {0x03, 0x22, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame CMFA_CLEAR_DTC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x79B,
+ .data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}};
bool end_of_charge = false;
bool interlock_flag = false;
uint16_t soc_z = 0;
@@ -208,7 +218,6 @@ class CmfaEvBattery : public CanBattery {
uint64_t cumulative_energy_when_charging = 0;
uint64_t cumulative_energy_in_regen = 0;
uint16_t soh_average = 10000;
- uint16_t cellvoltages_mv[72];
uint32_t poll_pid = PID_POLL_SOH_AVERAGE;
uint16_t pid_reply = 0;
@@ -229,7 +238,7 @@ class CmfaEvBattery : public CanBattery {
uint32_t SOC_raw = 0;
uint16_t SOH = 99;
int16_t current = 0;
- uint16_t pack_voltage = 2700;
+ uint16_t pack_voltage = 500;
int16_t highest_cell_temperature = 0;
int16_t lowest_cell_temperature = 0;
uint32_t discharge_power_w = 0;
diff --git a/Software/src/battery/CMFA-EV-HTML.h b/Software/src/battery/CMFA-EV-HTML.h
index 22d924712..d30758140 100644
--- a/Software/src/battery/CMFA-EV-HTML.h
+++ b/Software/src/battery/CMFA-EV-HTML.h
@@ -16,6 +16,7 @@ class CmfaEvHtmlRenderer : public BatteryHtmlRenderer {
content += "12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "mV ";
content += "Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + " ";
content += "Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + " ";
+ content += "Sum of cellvoltages: " + String(datalayer_extended.CMFAEV.average_voltage_of_cells) + " ";
content += "Max regen power: " + String(datalayer_extended.CMFAEV.max_regen_power) + " ";
content += "Max discharge power: " + String(datalayer_extended.CMFAEV.max_discharge_power) + " ";
content += "Max charge power: " + String(datalayer_extended.CMFAEV.maximum_charge_power) + " ";
diff --git a/Software/src/battery/CMP-SMART-CAR-BATTERY-HTML.h b/Software/src/battery/CMP-SMART-CAR-BATTERY-HTML.h
new file mode 100644
index 000000000..8d0db7235
--- /dev/null
+++ b/Software/src/battery/CMP-SMART-CAR-BATTERY-HTML.h
@@ -0,0 +1,274 @@
+#ifndef _CMP_SMART_CAR_BATTERY_HTML_H
+#define _CMP_SMART_CAR_BATTERT_HTML_H
+
+#include
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
+#include "../devboard/webserver/BatteryHtmlRenderer.h"
+
+inline const char* getContactorState(int index) {
+ switch (index) {
+ case 0:
+ return "Open";
+ case 1:
+ return "Closed";
+ case 2:
+ return "STUCK Open!";
+ case 3:
+ return "STUCK Closed!";
+ default:
+ return "";
+ }
+}
+
+class CmpSmartCarHtmlRenderer : public BatteryHtmlRenderer {
+ public:
+ String get_status_html() {
+ String content;
+ content += "Balancing active: ";
+ if (datalayer_extended.stellantisCMPsmart.battery_balancing_active) {
+ content += "Yes ";
+ } else {
+ content += "No";
+ }
+ content += "Positive contactor: ";
+ content += getContactorState(datalayer_extended.stellantisCMPsmart.battery_positive_contactor_state);
+ content += " Negative contactor: ";
+ content += getContactorState(datalayer_extended.stellantisCMPsmart.battery_negative_contactor_state);
+ content += " Precharge contactor: ";
+ content += getContactorState(datalayer_extended.stellantisCMPsmart.battery_precharge_contactor_state);
+ content += " Wakeup reason: " + String(datalayer_extended.stellantisCMPsmart.hvbat_wakeup_state) + " ";
+ content += "Battery state: ";
+ if (datalayer_extended.stellantisCMPsmart.battery_state == 0) {
+ content += "Sleep";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 1) {
+ content += "Initialization";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 2) {
+ content += "Wait";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 3) {
+ content += "Ready";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 4) {
+ content += "Preheat";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 5) {
+ content += "Discharge";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 6) {
+ content += "Charge";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 7) {
+ content += "Fault";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 8) {
+ content += "Pre-shutdown";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 9) {
+ content += "Shutdown";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 10) {
+ content += "Cooling";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_state == 11) {
+ content += "HV battery precondition";
+ }
+ content += " ";
+
+ content += "Battery fault level: " + String(datalayer_extended.stellantisCMPsmart.battery_fault) + " ";
+
+ content += "Eplug status: ";
+ if (datalayer_extended.stellantisCMPsmart.eplug_status == 0) {
+ content += "Seated OK";
+ } else if (datalayer_extended.stellantisCMPsmart.eplug_status == 1) {
+ content += "Disconnected!";
+ } else if (datalayer_extended.stellantisCMPsmart.eplug_status == 2) {
+ content += "Open Status";
+ } else if (datalayer_extended.stellantisCMPsmart.eplug_status == 3) {
+ content += "Invalid";
+ }
+ content += " ";
+
+ content += "HVIL status: ";
+ if (datalayer_extended.stellantisCMPsmart.HVIL_status == 0) {
+ content += "Closed OK";
+ } else if (datalayer_extended.stellantisCMPsmart.HVIL_status == 1) {
+ content += "OPEN!!";
+ } else if (datalayer_extended.stellantisCMPsmart.HVIL_status == 2) {
+ content += "Error";
+ } else if (datalayer_extended.stellantisCMPsmart.HVIL_status == 3) {
+ content += "Invalid";
+ }
+ content += " ";
+
+ content += "EV Warning: ";
+ if (datalayer_extended.stellantisCMPsmart.ev_warning == 0) {
+ content += "OK No alarm";
+ } else if (datalayer_extended.stellantisCMPsmart.ev_warning == 1) {
+ content += "Blinking!!";
+ } else if (datalayer_extended.stellantisCMPsmart.ev_warning == 2) {
+ content += "ON!!";
+ } else if (datalayer_extended.stellantisCMPsmart.ev_warning == 3) {
+ content += "Invalid";
+ }
+ content += " ";
+
+ content += "Authorised for usage: ";
+ if (datalayer_extended.stellantisCMPsmart.power_auth) {
+ content += "NOT authorised ";
+ } else {
+ content += "Authorised OK";
+ }
+
+ content += "Charging status: ";
+ if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 0) {
+ content += "Not initiated";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 1) {
+ content += "In progress";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 2) {
+ content += "Completed";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 3) {
+ content += "Failure";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 3) {
+ content += "Stopped";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 3) {
+ content += "Forbidden";
+ } else if (datalayer_extended.stellantisCMPsmart.battery_charging_status == 3) {
+ content += "Prohibited, suggest preheat or precondition";
+ }
+ content += " ";
+
+ content += "Insulation status: ";
+ if (datalayer_extended.stellantisCMPsmart.insulation_fault == 0) {
+ content += "OK";
+ } else if (datalayer_extended.stellantisCMPsmart.insulation_fault == 1) {
+ content += "Symmetrical failure!!";
+ } else if (datalayer_extended.stellantisCMPsmart.insulation_fault == 2) {
+ content += "Asymmetric failure HV+!!";
+ } else if (datalayer_extended.stellantisCMPsmart.insulation_fault == 3) {
+ content += "Asymmetric failure HV-!!";
+ }
+ content += " ";
+
+ content += "Insulation circuit status: ";
+ if (datalayer_extended.stellantisCMPsmart.insulation_circuit_status == 0) {
+ content += "Inactive (Insulation function not enable)";
+ } else if (datalayer_extended.stellantisCMPsmart.insulation_circuit_status == 1) {
+ content += "Active (Insulation function enable)";
+ } else if (datalayer_extended.stellantisCMPsmart.insulation_circuit_status == 2) {
+ content += "FAULT!!";
+ } else if (datalayer_extended.stellantisCMPsmart.insulation_circuit_status == 3) {
+ content += "Insulation measurement in progress";
+ }
+ content += " ";
+
+ content += "Hardware fault status: ";
+ if (datalayer_extended.stellantisCMPsmart.hardware_fault_status == 0) {
+ content += "No Fault";
+ }
+ if (datalayer_extended.stellantisCMPsmart.hardware_fault_status & 0b001) {
+ content += "FAULT! Temperature sensor!";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.hardware_fault_status & 0b010) >> 1) {
+ content += "FAULT! Voltage sensing circuit!";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.hardware_fault_status & 0b100) >> 2) {
+ content += "FAULT! Current sensor!";
+ }
+ content += " ";
+
+ content += "L3 Fault: ";
+ if (datalayer_extended.stellantisCMPsmart.l3_fault == 0) {
+ content += "No Fault";
+ }
+ if (datalayer_extended.stellantisCMPsmart.l3_fault & 0b001) {
+ content += "Cell undervoltage";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.l3_fault & 0b010) >> 1) {
+ content += "Cell overvoltage";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.l3_fault & 0b100) >> 2) {
+ content += "Over temperature";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.l3_fault & 0b1000) >> 3) {
+ content += "Under temperature";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.l3_fault & 0b10000) >> 4) {
+ content += "Over discharge current";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.l3_fault & 0b100000) >> 5) {
+ content += "Pack undedr voltage";
+ }
+ content += " ";
+
+ content += "Plausibility error: ";
+ if (datalayer_extended.stellantisCMPsmart.plausibility_error == 0) {
+ content += "No error";
+ }
+ if (datalayer_extended.stellantisCMPsmart.plausibility_error & 0b001) {
+ content += "Module temperature plausibility error";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.plausibility_error & 0b010) >> 1) {
+ content += "Cell voltage plausibility error";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.plausibility_error & 0b100) >> 2) {
+ content += "Battery voltlage plausibility error";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.plausibility_error & 0b1000) >> 3) {
+ content += "HVBAT Current plausibility error";
+ }
+ content += " ";
+
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 > 0) ||
+ (datalayer_extended.stellantisCMPsmart.alert_frame4 > 0)) {
+ content += "ALERT!!! ";
+ }
+ if (datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b001) {
+ content += "Cell Undervoltage ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b010) >> 1) {
+ content += "Cell Overvoltage ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b100) >> 1) {
+ content += "High SOC ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b1000) >> 1) {
+ content += "Low SOC ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b10000) >> 1) {
+ content += "Overvoltage ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b100000) >> 1) {
+ content += "High temperature ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b01000000) >> 1) {
+ content += "Temperature Delta ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 & 0b10000000) >> 1) {
+ content += "Battery ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame4 & 0b10000) >> 1) {
+ content += "Contactor Opening ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame4 & 0b100000) >> 1) {
+ content += "Overcharge ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame4 & 0b01000000) >> 1) {
+ content += "Cell poor consistency ";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame4 & 0b10000000) >> 1) {
+ content += "SOC jump";
+ }
+ if ((datalayer_extended.stellantisCMPsmart.alert_frame3 > 0) ||
+ (datalayer_extended.stellantisCMPsmart.alert_frame4 > 0)) {
+ content += " ";
+ }
+
+ content += "RCD line active: ";
+ if (datalayer_extended.stellantisCMPsmart.rcd_line_active) {
+ content += "Yes ";
+ } else {
+ content += "No ";
+ }
+
+ content += "Active DTC Code: " + String(datalayer_extended.stellantisCMPsmart.active_DTC_code);
+ if (datalayer_extended.stellantisCMPsmart.active_DTC_code == 9) {
+ content += " Temperature sensor missing between pin 21-22";
+ }
+ content += " ";
+ return content;
+ }
+};
+
+#endif
diff --git a/Software/src/battery/CMP-SMART-CAR-BATTERY.cpp b/Software/src/battery/CMP-SMART-CAR-BATTERY.cpp
new file mode 100644
index 000000000..7fc1c7925
--- /dev/null
+++ b/Software/src/battery/CMP-SMART-CAR-BATTERY.cpp
@@ -0,0 +1,575 @@
+#include "CMP-SMART-CAR-BATTERY.h"
+#include
+#include "../communication/can/comm_can.h"
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h" //For More Battery Info page
+#include "../devboard/utils/common_functions.h"
+#include "../devboard/utils/events.h"
+
+void CmpSmartCarBattery::update_values() {
+ if (datalayer.system.info.equipment_stop_active == true) {
+ digitalWrite(esp32hal->WUP_PIN1(), LOW); // Turn off wakeup pin
+ } else if (millis() > INTERVAL_1_S) {
+ digitalWrite(esp32hal->WUP_PIN1(), HIGH); // Wake up the battery
+ }
+
+ datalayer_battery->status.real_soc = battery_soc * 10;
+
+ datalayer_battery->status.soh_pptt = SOH_estimated * 100;
+
+ datalayer_battery->status.voltage_dV = battery_voltage;
+
+ datalayer_battery->status.current_dA = battery_current_dA;
+
+ 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->info.total_capacity_Wh = total_energy_when_full_Wh;
+
+ datalayer_battery->status.remaining_capacity_Wh = static_cast(
+ (static_cast(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
+
+ datalayer_battery->status.max_charge_power_W = regen_charge_cont_power * 100;
+
+ datalayer_battery->status.max_discharge_power_W = discharge_cont_available_power * 100;
+
+ if ((battery_temperature_minimum != 0) && (battery_temperature_maximum != 0)) {
+ //Only update once both values are available
+ datalayer_battery->status.temperature_min_dC = battery_temperature_minimum * 10;
+ datalayer_battery->status.temperature_max_dC = battery_temperature_maximum * 10;
+ }
+
+ datalayer_battery->status.cell_min_voltage_mV = min_cell_voltage;
+
+ datalayer_battery->status.cell_max_voltage_mV = max_cell_voltage;
+
+ if (number_of_cells > 0) {
+ datalayer_battery->info.number_of_cells = number_of_cells;
+ }
+
+ datalayer_battery->status.total_discharged_battery_Wh = lifetime_kWh_discharged;
+
+ datalayer_battery->status.total_charged_battery_Wh = lifetime_kWh_charged;
+
+ if (thermal_runaway == 0x01) {
+ set_event(EVENT_THERMAL_RUNAWAY, 0);
+ }
+
+ datalayer_extended.stellantisCMPsmart.battery_negative_contactor_state = battery_negative_contactor_state;
+ datalayer_extended.stellantisCMPsmart.battery_precharge_contactor_state = battery_precharge_contactor_state;
+ datalayer_extended.stellantisCMPsmart.battery_positive_contactor_state = battery_positive_contactor_state;
+ datalayer_extended.stellantisCMPsmart.battery_balancing_active = battery_balancing_active;
+ datalayer_extended.stellantisCMPsmart.eplug_status = eplug_status;
+ datalayer_extended.stellantisCMPsmart.HVIL_status = HVIL_status;
+ datalayer_extended.stellantisCMPsmart.ev_warning = ev_warning;
+ datalayer_extended.stellantisCMPsmart.power_auth = power_auth;
+ datalayer_extended.stellantisCMPsmart.insulation_fault = insulation_fault;
+ datalayer_extended.stellantisCMPsmart.insulation_circuit_status = insulation_circuit_status;
+ datalayer_extended.stellantisCMPsmart.battery_state = battery_state;
+ datalayer_extended.stellantisCMPsmart.alert_frame3 = alert_frame3;
+ datalayer_extended.stellantisCMPsmart.alert_frame4 = alert_frame4;
+ datalayer_extended.stellantisCMPsmart.hardware_fault_status = hardware_fault_status;
+ datalayer_extended.stellantisCMPsmart.l3_fault = l3_fault;
+ datalayer_extended.stellantisCMPsmart.plausibility_error = plausibility_error;
+ datalayer_extended.stellantisCMPsmart.battery_charging_status = battery_charging_status;
+ datalayer_extended.stellantisCMPsmart.battery_fault = battery_fault;
+ datalayer_extended.stellantisCMPsmart.hvbat_wakeup_state = hvbat_wakeup_state;
+ datalayer_extended.stellantisCMPsmart.active_DTC_code = active_DTC_code;
+ datalayer_extended.stellantisCMPsmart.rcd_line_active = rcd_line_active;
+}
+
+bool checksum_OK(CAN_frame& rx_frame, uint8_t magic_byte) {
+ // Sum all data nibbles from bytes 0-6 (excluding last byte)
+ uint8_t sum = 0;
+
+ for (int i = 0; i < 7; i++) {
+ sum += (rx_frame.data.u8[i] >> 4);
+ sum += (rx_frame.data.u8[i] & 0x0F);
+ }
+
+ // Get counter from last byte low nibble
+ uint8_t counter = (rx_frame.data.u8[7] & 0x0F);
+
+ // Get checksum from last byte high nibble
+ uint8_t checksum = (rx_frame.data.u8[7] >> 4);
+
+ // Calculate expected checksum: (magic_byte - sum - counter) mod 16
+ int16_t expected = (magic_byte - (sum & 0x0F) - counter) % 16;
+ if (expected < 0) {
+ expected += 16; // Ensure positive modulo result
+ }
+
+ return (checksum == expected);
+}
+
+uint8_t calculate_checksum(CAN_frame& rx_frame, uint8_t magic_byte) {
+ // Sum all data nibbles from bytes 0-6 (excluding last byte)
+ uint8_t sum = 0;
+
+ for (int i = 0; i < 7; i++) {
+ sum += (rx_frame.data.u8[i] >> 4);
+ sum += (rx_frame.data.u8[i] & 0x0F);
+ }
+
+ // Get counter from last byte low nibble
+ uint8_t counter = (rx_frame.data.u8[7] & 0x0F);
+
+ // Calculate expected checksum: (magic_byte - sum - counter) mod 16
+ int16_t calculated_checksum = (magic_byte - (sum & 0x0F) - counter) % 16;
+ if (calculated_checksum < 0) {
+ calculated_checksum += 16; // Ensure positive modulo result
+ }
+
+ return calculated_checksum;
+}
+
+uint8_t calculate_checksum432(CAN_frame& rx_frame) {
+ // Sum all data nibbles from bytes 0-6 (excluding last byte)
+ uint8_t sum = 0;
+ uint8_t magic_byte = 6;
+
+ sum += (rx_frame.data.u8[0] >> 4);
+ sum += (rx_frame.data.u8[0] & 0x0F);
+ sum += (rx_frame.data.u8[1] >> 4);
+
+ for (int i = 3; i < 8; i++) {
+ sum += (rx_frame.data.u8[i] >> 4);
+ sum += (rx_frame.data.u8[i] & 0x0F);
+ }
+
+ // Get counter from last byte low nibble
+ uint8_t counter = (rx_frame.data.u8[2] & 0x0F);
+
+ // Calculate expected checksum: (magic_byte - sum - counter) mod 16
+ int16_t calculated_checksum = (magic_byte - (sum & 0x0F) - counter) % 16;
+ if (calculated_checksum < 0) {
+ calculated_checksum += 16; // Ensure positive modulo result
+ }
+
+ return calculated_checksum;
+}
+
+void CmpSmartCarBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ switch (rx_frame.ID) {
+ case 0x205: //10ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (!checksum_OK(rx_frame, 0x18)) {
+ datalayer_battery->status.CAN_error_counter++;
+ break; //Message checksum incorrect, abort reading data from it
+ }
+ battery_current_dA = -sign_extend_to_int16(((rx_frame.data.u8[0] << 7) | (rx_frame.data.u8[1] >> 1)), 15);
+ battery_soc = ((rx_frame.data.u8[2] & 0x1F) << 5) | (rx_frame.data.u8[3] >> 3);
+ battery_voltage =
+ ((((rx_frame.data.u8[3] & 0x07) << 10) | (rx_frame.data.u8[4] << 2) | (rx_frame.data.u8[5] >> 6)));
+ battery_state = ((rx_frame.data.u8[1] & 0x01) | (rx_frame.data.u8[2] >> 5));
+ battery_fault = ((rx_frame.data.u8[5] & 0x01) | (rx_frame.data.u8[6] >> 5));
+ battery_negative_contactor_state = ((rx_frame.data.u8[5] & 0x06) >> 1);
+ battery_positive_contactor_state = ((rx_frame.data.u8[5] & 0x18) >> 3);
+ battery_precharge_contactor_state = ((rx_frame.data.u8[5] & 0x20) >> 5);
+ battery_connect_status = (rx_frame.data.u8[6] & 0x03);
+ battery_charging_status = ((rx_frame.data.u8[6] & 0x1C) >> 5);
+ //counter_205 = (rx_frame.data.u8[7] & 0x0F);
+ break;
+ case 0x235: //10ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ discharge_available_10s_power = ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4)); //*0.1kW
+ discharge_available_10s_current =
+ (((rx_frame.data.u8[1] & 0x0F) << 9) | (rx_frame.data.u8[2] << 1) | (rx_frame.data.u8[3] >> 7)); //*0.1A
+ break;
+ case 0x275: //20ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (!checksum_OK(rx_frame, 0x01)) {
+ datalayer_battery->status.CAN_error_counter++;
+ break; //Message checksum incorrect, abort reading data from it
+ }
+ discharge_cont_available_power = ((rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] >> 5)); //*0.1kW
+ discharge_cont_available_current = (((rx_frame.data.u8[1] & 0x1F) << 8) | rx_frame.data.u8[2]); //*0.1A
+ discharge_available_30s_current = ((rx_frame.data.u8[3] << 5) | (rx_frame.data.u8[4] >> 3)); //*0.1A
+ discharge_available_30s_power = (((rx_frame.data.u8[4] & 0x07) << 8) | rx_frame.data.u8[5]); //*0.1kW
+ //counter_275 = (rx_frame.data.u8[7] & 0x0F);
+ break;
+ case 0x285: //20ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (!checksum_OK(rx_frame, 0x00)) {
+ datalayer_battery->status.CAN_error_counter++;
+ break; //Message checksum incorrect, abort reading data from it
+ }
+ regen_charge_cont_power = ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4)); //*0.1kW
+ regen_charge_30s_power = (((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]); //*0.1kW
+ regen_charge_30s_current = ((rx_frame.data.u8[3] << 5) | (rx_frame.data.u8[4] >> 3)); //*0.1A
+ regen_charge_cont_current = (((rx_frame.data.u8[4] & 0x07) << 8) | rx_frame.data.u8[5]); //*0.1A
+ //counter_285 = (rx_frame.data.u8[7] & 0x0F);
+ break;
+ case 0x2A5: //100ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ battery_quickcharge_connect_status = rx_frame.data.u8[0] >> 6;
+ regen_charge_10s_current = (((rx_frame.data.u8[0] & 0x3F) << 1) | (rx_frame.data.u8[1] >> 1)); //*0.1a
+ regen_charge_10s_power = ((rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[3] >> 4)); //*0.1kW
+ quick_charge_port_voltage = ((rx_frame.data.u8[4] << 4) | (rx_frame.data.u8[5] >> 4)); //*0.1kW
+ //qc_negative_contactor_status = rx_frame.data.u8[6] & 0x03;
+ //qc_positive_contactor_status = (rx_frame.data.u8[6] & 0x0C) >> 2;
+ //counter_2A5 = (rx_frame.data.u8[7] & 0x0F);
+ break;
+ case 0x325: //100ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (!checksum_OK(rx_frame, 0x05)) {
+ datalayer_battery->status.CAN_error_counter++;
+ break; //Message checksum incorrect, abort reading data from it
+ }
+ battery_balancing_active = rx_frame.data.u8[0] & 0x01;
+ eplug_status = (rx_frame.data.u8[0] & 0x06) >> 1; //0 connected, 1 disconnected, 2 open status, 3 invalid
+ ev_warning = (rx_frame.data.u8[0] & 0x18) >> 3;
+ power_auth = (rx_frame.data.u8[0] & 0x20) >> 5;
+ HVIL_status = (rx_frame.data.u8[0] & 0xC0) >> 6;
+ hardware_fault_status = (rx_frame.data.u8[1] & 0x1C) >> 2;
+ insulation_fault = (rx_frame.data.u8[1] & 0xE0) >> 5;
+ temperature = (((rx_frame.data.u8[1] & 0x03) << 7) | (rx_frame.data.u8[2] >> 1)); //*0.1kW
+ service_due = rx_frame.data.u8[3] & 0x03;
+ plausibility_error = (rx_frame.data.u8[3] & 0x3C) >> 2;
+ insulation_circuit_status = (rx_frame.data.u8[3] & 0xC0) >> 6;
+ l3_fault = rx_frame.data.u8[4] & 0x3F;
+ master_warning = (rx_frame.data.u8[4] & 0xC0) >> 6;
+ insulation_resistance_kOhm = ((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6]));
+ //counter_325 = (rx_frame.data.u8[7] & 0x0F);
+ break;
+ case 0x334: // Cellvoltages
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ mux = (rx_frame.data.u8[0] >> 3); // Mux goes from 0-25
+
+ if (mux < 25) { // Only process valid cell data frames (0-24) (100S, but protocol does support 108S)
+ uint8_t base_index = mux * 4;
+
+ datalayer_battery->status.cell_voltages_mV[base_index + 0] =
+ ((rx_frame.data.u8[1] << 4) | (rx_frame.data.u8[2] >> 4)) * 4;
+ datalayer_battery->status.cell_voltages_mV[base_index + 1] =
+ ((rx_frame.data.u8[3] & 0xFF) << 4) | (rx_frame.data.u8[4] >> 4);
+ datalayer_battery->status.cell_voltages_mV[base_index + 2] =
+ (((rx_frame.data.u8[4] & 0x0F) << 8) | (rx_frame.data.u8[5])) * 4;
+ datalayer_battery->status.cell_voltages_mV[base_index + 3] =
+ ((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[7];
+ }
+ break;
+ case 0x335: //100ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //checksum_335 //Init value 0xB
+ DC_bus_voltage = ((rx_frame.data.u8[0] << 5) | (rx_frame.data.u8[1] >> 3));
+ charge_max_voltage =
+ (((rx_frame.data.u8[1] & 0x07) << 10) | (rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] >> 6));
+ charge_cont_curr_max = (((rx_frame.data.u8[3] & 0x3F) << 7) | (rx_frame.data.u8[4] >> 1));
+ charge_cont_curr_req =
+ (((rx_frame.data.u8[4] & 0x01) << 12) | (rx_frame.data.u8[5] << 4) | (rx_frame.data.u8[6] >> 4));
+ //counter_335 = (rx_frame.data.u8[7] & 0x0F);
+ break;
+ case 0x385: //1000ms Wakeup state message
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ hvbat_wakeup_state = rx_frame.data.u8[0];
+ break;
+ case 0x3F4: //Event triggered
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ mux = (rx_frame.data.u8[0] >> 5);
+ //This message contains all individual temperature sensors. Not needed fofr us
+ break;
+ case 0x434: //1000ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ hours_spent_overvoltage = ((rx_frame.data.u8[0] << 5) | (rx_frame.data.u8[1] >> 3)) / 10;
+ hours_spent_overtemperature =
+ (((rx_frame.data.u8[1] & 0x07) << 10) | (rx_frame.data.u8[2] << 2) | (rx_frame.data.u8[3] >> 6)) / 10;
+ hours_spent_undertemperature = (((rx_frame.data.u8[3] & 0x3F) << 7) | (rx_frame.data.u8[4] >> 1)) / 10;
+ total_coloumb_counting_Ah =
+ (((rx_frame.data.u8[4] & 0x01) << 11) | (rx_frame.data.u8[5] << 3) | (rx_frame.data.u8[6] >> 5)) * 125;
+ total_coulomb_counting_kWh = (((rx_frame.data.u8[6] & 0x1F) << 7) | (rx_frame.data.u8[7] >> 1)) * 35;
+ break;
+ case 0x435: //500ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ tempval = rx_frame.data.u8[0] - 40;
+ if (tempval < 210) {
+ battery_temperature_maximum = rx_frame.data.u8[0] - 40;
+ }
+ temp = ((rx_frame.data.u8[2] << 6) | (rx_frame.data.u8[3] >> 2));
+ if ((temp > 2000) && (temp < 4500)) {
+ min_cell_voltage = temp;
+ }
+ min_cell_voltage_number = rx_frame.data.u8[4];
+ temp = ((rx_frame.data.u8[5] << 6) | (rx_frame.data.u8[6] >> 2));
+ if ((temp > 2000) && (temp < 4500)) {
+ max_cell_voltage = temp;
+ }
+ max_cell_voltage_number = rx_frame.data.u8[7];
+ break;
+ case 0x455: //100ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ nominal_voltage = ((rx_frame.data.u8[0] << 5) | (rx_frame.data.u8[1] >> 3));
+ charge_continue_power_limit =
+ (((rx_frame.data.u8[1] & 0x07) << 9) | (rx_frame.data.u8[2] << 1) | (rx_frame.data.u8[3] >> 7));
+ charge_energy_amount_requested = (((rx_frame.data.u8[3] & 0x7F) << 5) | (rx_frame.data.u8[4] >> 3));
+ bulk_SOC_DC_limit = rx_frame.data.u8[6] >> 1;
+ break;
+ case 0x485: //Non-periodic
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //heating_contactor_operating_count = ((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7]))
+ break;
+ case 0x494: //1000ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //total_coloumb_counting_Ah
+ lifetime_kWh_charged = (((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 35;
+ lifetime_kWh_discharged = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4)) * 35;
+ hours_spent_exceeding_charge_power = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]);
+ hours_spent_exceeding_discharge_power = (((rx_frame.data.u8[4] & 0x7F) << 5) | (rx_frame.data.u8[7] >> 3));
+ break;
+ case 0x535: //1000ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ SOC_actual = ((rx_frame.data.u8[0] << 2) | (rx_frame.data.u8[1] >> 6));
+ alert_low_battery_energy = rx_frame.data.u8[1] & 0x01;
+ battery_temperature_average = rx_frame.data.u8[4] - 40;
+ battery_minimum_voltage_reached_warning = rx_frame.data.u8[7] & 0x01;
+ break;
+ case 0x543: //1000ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ remaining_energy_Wh = ((rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] >> 5)) * 32;
+ total_energy_when_full_Wh = ((rx_frame.data.u8[2] << 3) | (rx_frame.data.u8[3] >> 5)) * 32;
+ SOH_internal_resistance = (((rx_frame.data.u8[3] & 0x1F) << 2) | (rx_frame.data.u8[4] >> 6));
+ SOH_estimated = (rx_frame.data.u8[5] >> 1);
+ break;
+ case 0x575:
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x583: //1000ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ max_temperature_probe_number = rx_frame.data.u8[0];
+ min_temperature_probe_number = rx_frame.data.u8[1];
+
+ alert_frame3 = rx_frame.data.u8[3];
+ alert_frame4 = rx_frame.data.u8[4];
+
+ tempval = rx_frame.data.u8[2] - 40;
+ if (tempval < 210) {
+ battery_temperature_minimum = rx_frame.data.u8[2] - 40;
+ }
+ if (rx_frame.data.u8[5] < 200) {
+ number_of_temperature_sensors = rx_frame.data.u8[5];
+ }
+ if (rx_frame.data.u8[6] < 200) {
+ number_of_cells = rx_frame.data.u8[6];
+ }
+ break;
+ case 0x595: //1000ms - Time to charge to 20/40/680/100 , not needed for integration
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x5B5: //100ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ heater_relay_status = rx_frame.data.u8[0] & 0x03;
+ preheating_status = (rx_frame.data.u8[0] & 0x0C) >> 3;
+ cooling_enabled = (rx_frame.data.u8[0] & 0x10) >> 4;
+ coolant_temperature_warning = (rx_frame.data.u8[0] & 0x60) >> 5;
+ coolant_alarm = (rx_frame.data.u8[0] & 0x80) >> 7;
+ coolant_temperature = rx_frame.data.u8[1] - 40;
+ thermal_control = (rx_frame.data.u8[2] & 0x70) >> 4;
+ thermal_runaway = (rx_frame.data.u8[6] & 0xC0) >> 6;
+ thermal_runaway_module_ID = (rx_frame.data.u8[6] & 0x1C) >> 2;
+ break;
+ case 0x615:
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x625:
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x665: //1000ms
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ main_contactor_cycle_count = ((rx_frame.data.u8[0] << 2) | (rx_frame.data.u8[1] >> 6)) * 200;
+ QC_contactor_cycle_count = (((rx_frame.data.u8[1] & 0x3F) << 4) | (rx_frame.data.u8[2] >> 4)) * 200;
+ break;
+ case 0x675:
+ //battery_remaining_capacity_Ah = //0.001 signal, 20 bit long, byte 5 bit 7 start pos
+ break;
+ case 0x694: // Poll reply
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x795:
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ rcd_line_active = (rx_frame.data.u8[0] & 0x40) >> 6;
+ break;
+ case 0x7B5:
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ active_DTC_code = rx_frame.data.u8[2];
+ break;
+ default:
+ break;
+ }
+}
+
+void CmpSmartCarBattery::transmit_can(unsigned long currentMillis) {
+
+ // Send periodic CAN Messages simulating the car still being attached
+ // Send 10ms messages
+ if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
+ previousMillis10 = currentMillis;
+
+ if (startup_increment < 250) {
+ startup_increment++;
+ }
+
+ /*
+ counter_10ms = (counter_10ms + 1) % 16; // counter_10ms repeats after 16 messages. 0-1..15-0
+
+ CMP_262.data.u8[0] =
+ 0x00; //0b0000 powertrain inactive, 0b0100 powertrain activation, 0b1000 powertrain active (Goes from E0, 00, 40 to 80 in logs). E0 all the time when CCS charging
+
+ CMP_208.data.u8[1] =
+ 0x00; //Goes from 00->20->3E in logs while starting to drive. TODO: Emulate? 00 while quickcharging
+ CMP_208.data.u8[5] = 0x23; // Powertrain status, off, TODO: 20, 21, 22, 23, 24?
+ CMP_208.data.u8[7] = counter_10ms;
+ CMP_208.data.u8[7] = (calculate_checksum(CMP_208, 0x05) << 4) | counter_10ms;
+
+ CMP_217.data.u8[4] = checksum217[counter_10ms];
+
+ CMP_241.data.u8[2] = 0x39; //Goes from 20-22-25-27-2A-2C-2F-31-34-36-39 on startup. TODO: Emulate?
+ //Highest bit in CMP_241[2] contains emergency shutdown request. 0b0 no emg stop, 0b1 emg stop requested.
+ CMP_241.data.u8[7] = counter_10ms;
+ CMP_241.data.u8[7] = (calculate_checksum(CMP_241, 0x08) << 4) | counter_10ms;
+
+ //transmit_can_frame(&CMP_208);
+ //transmit_can_frame(&CMP_217);
+ //transmit_can_frame(&CMP_241);
+ //transmit_can_frame(&CMP_262);
+ */
+ }
+
+ // Send 50ms messages
+ if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
+ previousMillis50 = currentMillis;
+ counter_50ms = (counter_50ms + 1) % 16; // counter_50ms repeats after 16 messages. 0-1..15-0
+
+ /*
+ Is this reallly needed? Or can we jump straight to wakeup?
+ if (startup_counter_432 < 255) {
+ startup_counter_432++;
+ }
+ if (startup_counter_432 < 15) {
+ CMP_432.data.u8[0] = 0x60; //Remote unlock, partial wakeup + No main wakeup
+ CMP_432.data.u8[4] = 0x04;
+ }
+ if (startup_counter_432 > 15) {
+ CMP_432.data.u8[0] = 0x70; //Remote unlock, partial wakeup + No main wakeup + Anti theft wake
+ }
+ if (startup_counter_432 > 200) {
+ CMP_432.data.u8[0] = 0x80; //Main wakeup
+ CMP_432.data.u8[4] = 0x00;
+ }
+ */
+ CMP_432.data.u8[0] = 0x80; //Main wakeup
+ CMP_432.data.u8[2] = counter_50ms;
+ //CMP_432.data.u8[3] = 0x wakeup charging remote, preconditioning wakeup, VCU stop/start charge, long park partial wake
+ CMP_432.data.u8[1] = 0x20; //1 no wish to start, 2 wish to start
+ CMP_432.data.u8[1] = 0x20 | calculate_checksum432(CMP_432);
+
+ //CMP_421.data.u8[3] = 0x20; //Post wakeup , goes from 0x00 when car on, to 0x80 to when turning off car
+
+ transmit_can_frame(&CMP_432); //Main wakeup
+ //transmit_can_frame(&CMP_421); //Post wakeup (Apparently not needed for contactor closing?)
+ }
+
+ // Send 60ms messages
+ if (currentMillis - previousMillis60 >= INTERVAL_60_MS) {
+ previousMillis60 = currentMillis;
+ counter_60ms = (counter_60ms + 1) % 16; // counter_60ms repeats after 16 messages. 0-1..15-0
+
+ if (startup_increment < 200) { //During startup we request open contactors
+ CMP_351.data.u8[0] = 0xA6;
+ CMP_351.data.u8[1] = 0x10;
+ CMP_351.data.u8[2] = 0x10;
+ } else { //Normal handling of 351 message according to we need to open/close contactors
+ if (datalayer_battery->status.bms_status == FAULT) {
+ //Open contactors
+ CMP_351.data.u8[0] = 0xA6;
+ CMP_351.data.u8[1] = 0x10;
+ CMP_351.data.u8[2] = 0x10;
+ } else { //Close contactors
+ CMP_351.data.u8[0] = 0x46;
+ CMP_351.data.u8[1] = 0x14;
+ CMP_351.data.u8[2] = 0x17;
+ }
+ }
+
+ CMP_351.data.u8[7] = counter_60ms;
+ CMP_351.data.u8[7] = (calculate_checksum(CMP_351, 0x06) << 4) | counter_60ms;
+
+ transmit_can_frame(&CMP_351); //Airbag
+ }
+
+ // Send 100ms messages
+ if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
+ previousMillis100 = currentMillis;
+ counter_100ms = (counter_100ms + 1) % 16; // counter_100ms repeats after 16 messages. 0-1..15-0
+
+ CMP_211.data.u8[2] = 0x00; //00 QC contactor OFF, 81, QC contactor ON
+
+ if (startup_increment < 200) { //During startup we request open contactors
+ CMP_211.data.u8[4] = 0x17; //Ready mode (unsure why this opens contactors)
+ } else { //Normal handling of close/open
+
+ if (datalayer_battery->status.bms_status == FAULT) {
+ //Open contactors
+ CMP_211.data.u8[4] = 0x17; //Ready mode (unsure why this opens contactors) (Bit 1 is insulation turn-off)
+ } else { //Close contactors
+ CMP_211.data.u8[4] = 0x06; //04 discharge (+bit1 insulation turn off), 4A charge
+ }
+ }
+
+ CMP_211.data.u8[7] = counter_100ms;
+ CMP_211.data.u8[7] = (calculate_checksum(CMP_211, 0x0B) << 4) | counter_100ms;
+
+ //CMP_4A2.data.u8[1] = 0x40; //00 plugged, 64 unplugged, 41vehiclerunning
+
+ transmit_can_frame(&CMP_211);
+ //transmit_can_frame(&CMP_231); // Battery Preconditioning (Apparently not needed for contactor closing?)
+ //transmit_can_frame(&CMP_422); // (Apparently not needed for contactor closing?)
+ //transmit_can_frame(&CMP_4A2); //Should we send plugged in, or unplugged? (Apparently not needed for contactor closing?)
+ }
+
+ // Send 1s messages
+ if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
+ previousMillis1000 = currentMillis;
+
+ /*
+ vehicle_time_counter = (vehicle_time_counter + 10);
+
+ CMP_552.data.u8[0] = ((vehicle_time_counter) & 0xFF000000) >> 24;
+ CMP_552.data.u8[1] = ((vehicle_time_counter) & 0x00FF0000) >> 16;
+ CMP_552.data.u8[2] = ((vehicle_time_counter) & 0x0000FF00) >> 8;
+ CMP_552.data.u8[3] = ((vehicle_time_counter) & 0x000000FF);
+
+ transmit_can_frame(&CMP_552);
+ //This message is odd. Non periodic, but increments 10 per cycle. Might be enough to send it once every second
+ */
+ if (datalayer_extended.stellantisCMPsmart.UserRequestDTCreset) {
+ transmit_can_frame(&CMP_CLEAR_ALL_DTC);
+ datalayer_extended.stellantisCMPsmart.UserRequestDTCreset = false;
+ }
+ }
+}
+
+void CmpSmartCarBattery::setup(void) { // Performs one time setup at startup
+ static bool pins_allocated = false;
+
+ // Only allocate pins once, double-battery uses same WUP pin
+ if (!pins_allocated) {
+ if (!esp32hal->alloc_pins(Name, esp32hal->WUP_PIN1())) {
+ return; // Pin allocation failed
+ }
+ pinMode(esp32hal->WUP_PIN1(), OUTPUT);
+ digitalWrite(esp32hal->WUP_PIN1(), LOW); // Set pin to low
+ pins_allocated = true;
+ }
+
+ strncpy(datalayer.system.info.battery_protocol, Name, 63);
+ datalayer.system.info.battery_protocol[63] = '\0';
+ datalayer_battery->info.number_of_cells = 100;
+ datalayer_battery->info.chemistry = battery_chemistry_enum::LFP;
+ datalayer_battery->info.total_capacity_Wh = 41400;
+ datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
+ datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
+ datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_100S_DV;
+ datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_100S_DV;
+ datalayer.system.status.battery_allows_contactor_closing = true;
+}
diff --git a/Software/src/battery/CMP-SMART-CAR-BATTERY.h b/Software/src/battery/CMP-SMART-CAR-BATTERY.h
new file mode 100644
index 000000000..ff835d9d9
--- /dev/null
+++ b/Software/src/battery/CMP-SMART-CAR-BATTERY.h
@@ -0,0 +1,238 @@
+#ifndef CMP_SMART_CAR_BATTERY_H
+#define CMP_SMART_CAR_BATTERY_H
+#include "../datalayer/datalayer_extended.h"
+#include "../devboard/hal/hal.h"
+#include "CMP-SMART-CAR-BATTERY-HTML.h"
+#include "CanBattery.h"
+
+class CmpSmartCarBattery : public CanBattery {
+ public:
+ // Use this constructor for the second battery.
+ CmpSmartCarBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_CMPSMART* extended, CAN_Interface targetCan)
+ : CanBattery(targetCan) {
+ datalayer_battery = datalayer_ptr;
+ datalayer_cmpsmart = extended;
+ }
+
+ // Use the default constructor to create the first or single battery.
+ CmpSmartCarBattery() {
+ datalayer_battery = &datalayer.battery;
+ datalayer_cmpsmart = &datalayer_extended.stellantisCMPsmart;
+ }
+
+ virtual void setup(void);
+ virtual void handle_incoming_can_frame(CAN_frame rx_frame);
+ virtual void update_values();
+ virtual void transmit_can(unsigned long currentMillis);
+ static constexpr const char* Name = "Stellantis CMP Smart Car Battery";
+
+ bool supports_charged_energy() { return true; }
+
+ bool supports_reset_DTC() { return true; }
+ void reset_DTC() { datalayer_extended.stellantisCMPsmart.UserRequestDTCreset = true; }
+
+ BatteryHtmlRenderer& get_status_renderer() { return renderer; }
+
+ private:
+ CmpSmartCarHtmlRenderer renderer;
+ DATALAYER_BATTERY_TYPE* datalayer_battery;
+ DATALAYER_INFO_CMPSMART* datalayer_cmpsmart;
+
+ static const int MAX_PACK_VOLTAGE_100S_DV = 3700;
+ static const int MIN_PACK_VOLTAGE_100S_DV = 2900;
+ static const int MAX_CELL_DEVIATION_MV = 100;
+ static const int MAX_CELL_VOLTAGE_MV = 3650;
+ static const int MIN_CELL_VOLTAGE_MV = 2800;
+
+ unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
+ unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
+ unsigned long previousMillis60 = 0; // will store last time a 60ms CAN Message was sent
+ unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
+ unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
+
+ uint8_t precalculated432[16] = {0x12, 0x11, 0x10, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B,
+ 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13};
+
+ CAN_frame CMP_211 = {.FD = false, //VCU contactor 100ms
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x211,
+ .data = {0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame CMP_351 = {.FD = false, //VCU 60ms Airbag
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x351,
+ .data = {0x46, 0x14, 0x17, 0x00, 0x00, 0x00, 0x00, 0x0F}};
+ CAN_frame CMP_432 = {.FD = false, //VCU 50ms
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x432,
+ .data = {0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x52}};
+
+ //Optional CAN messages to simulate more of the vehicle towards the battery (Not required?)
+ /*
+ uint8_t checksum217[16] = {0x50, 0x41, 0xB2, 0xA3, 0x14, 0x05, 0xF6, 0xE7,
+ 0x58, 0xC9, 0xBA, 0xAB, 0x1C, 0x8D, 0x7E, 0x6F};
+ CAN_frame CMP_208 = {.FD = false, //VCU 10ms
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x208,
+ .data = {0x00, 0x20, 0x00, 0x84, 0x40, 0x21, 0x00, 0x00}};
+
+ CAN_frame CMP_217 = {.FD = false, //VCU 10ms (Inverter motor speed)
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x217,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x00, 0x00}};
+ CAN_frame CMP_231 = {
+ .FD = false, //VCU preconditioning
+ .ext_ID = false,
+ .DLC = 8, //
+ .ID = 0x231, //0b00 : Not active0b01 : Heating function active0b10 : Cooling function active0b11 : Reserve
+ .data = {0x98, 0x59, 0x60, 0x00, 0xA3, 0x20, 0x00, 0x00}}; //Last byte, bit pos 1, has precond req
+ CAN_frame CMP_241 = {.FD = false, //VCU vehicle speed and emg stop 10ms
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x241,
+ .data = {0x00, 0x00, 0x39, 0x00, 0xC8, 0x00, 0x00, 0x00}};
+ CAN_frame CMP_262 = {.FD = false, //VCU 10ms
+ .ext_ID = false,
+ .DLC = 1,
+ .ID = 0x262,
+ .data = {0x00}};
+
+ CAN_frame CMP_421 = {.FD = false, //VCU 50ms
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x421,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame CMP_422 = {.FD = false, //100ms VCU, Configuration
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x422, //Fitting, Plant,check,storage,client,APV,showroom etc.
+ .data = {0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00}};
+
+ CAN_frame CMP_4A2 = {.FD = false, //OBC plug 100ms
+ .ext_ID = false,
+ .DLC = 2,
+ .ID = 0x4A2,
+ .data = {0x00, 0x41}}; //second byte, 00 plugged, 64 unplugged, 41vehiclerunning
+ CAN_frame CMP_552 = {.FD = false, //VCU mileage and time 1000ms
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x552,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE}};
+ */
+ CAN_frame CMP_POLL = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6B4, .data = {0x03, 0x22, 0xD8, 0x13}};
+ CAN_frame CMP_CLEAR_ALL_DTC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 5,
+ .ID = 0x6B4,
+ .data = {0x04, 0x14, 0xFF, 0xFF, 0xFF}};
+ uint32_t vehicle_time_counter = 0x088B390B; //Taken from log on 19thOctober2025
+ uint32_t main_contactor_cycle_count = 0;
+ uint32_t QC_contactor_cycle_count = 0;
+ uint32_t lifetime_kWh_charged = 0;
+ uint32_t lifetime_kWh_discharged = 0;
+ uint32_t remaining_energy_Wh = 0;
+ uint32_t total_energy_when_full_Wh = 41400;
+ uint32_t total_coloumb_counting_Ah = 0;
+ uint32_t total_coulomb_counting_kWh = 0;
+
+ uint16_t discharge_available_10s_power = 0;
+ uint16_t discharge_available_10s_current = 0;
+ uint16_t discharge_cont_available_power = 0;
+ uint16_t discharge_cont_available_current = 0;
+ uint16_t discharge_available_30s_current = 0;
+ uint16_t discharge_available_30s_power = 0;
+ uint16_t regen_charge_cont_power = 0;
+ uint16_t regen_charge_30s_power = 0;
+ uint16_t regen_charge_30s_current = 0;
+ uint16_t regen_charge_cont_current = 0;
+ uint16_t regen_charge_10s_current = 0;
+ uint16_t regen_charge_10s_power = 0;
+ uint16_t quick_charge_port_voltage = 0;
+ uint16_t insulation_resistance_kOhm = 0;
+ uint16_t DC_bus_voltage = 0;
+ uint16_t charge_max_voltage = 0;
+ uint16_t charge_cont_curr_max = 0;
+ uint16_t charge_cont_curr_req = 0;
+ uint16_t hours_spent_overvoltage = 0;
+ uint16_t hours_spent_overtemperature = 0;
+ uint16_t hours_spent_undertemperature = 0;
+ uint16_t battery_soc = 500;
+ uint16_t battery_voltage = 3300;
+ uint16_t temp = 0;
+ uint16_t min_cell_voltage = 3300;
+ uint16_t max_cell_voltage = 3300;
+ uint16_t nominal_voltage = 0;
+ uint16_t charge_continue_power_limit = 0;
+ uint16_t charge_energy_amount_requested = 0;
+ uint16_t hours_spent_exceeding_charge_power = 0;
+ uint16_t hours_spent_exceeding_discharge_power = 0;
+ uint16_t SOC_actual = 0;
+
+ int16_t battery_temperature_average = 0;
+ int16_t battery_temperature_maximum = 0;
+ int16_t coolant_temperature = 0;
+ int16_t battery_temperature_minimum = 0;
+ int16_t battery_current_dA = 0;
+
+ uint8_t tempval = 0;
+ uint8_t startup_increment = 0;
+ uint8_t active_DTC_code = 0;
+ uint8_t battery_quickcharge_connect_status = 0;
+ //uint8_t qc_negative_contactor_status = 0;
+ //uint8_t qc_positive_contactor_status = 0;
+ uint8_t eplug_status = 0;
+ uint8_t ev_warning = 0;
+ uint8_t battery_state = 0;
+ uint8_t battery_fault = 0;
+ uint8_t battery_negative_contactor_state = 0;
+ uint8_t battery_precharge_contactor_state = 0;
+ uint8_t battery_positive_contactor_state = 0;
+ uint8_t battery_connect_status = 0;
+ uint8_t battery_charging_status = 0;
+ uint8_t min_cell_voltage_number = 0;
+ uint8_t max_cell_voltage_number = 0;
+ uint8_t bulk_SOC_DC_limit = 0;
+ uint8_t mux = 0;
+ uint8_t startup_counter_432 = 0;
+ uint8_t counter_10ms = 0;
+ uint8_t counter_50ms = 0;
+ uint8_t counter_60ms = 0;
+ uint8_t counter_100ms = 0;
+ uint8_t SOH_internal_resistance = 0;
+ uint8_t SOH_estimated = 100;
+ uint8_t max_temperature_probe_number = 0;
+ uint8_t min_temperature_probe_number = 0;
+ uint8_t number_of_temperature_sensors = 0;
+ uint8_t number_of_cells = 0;
+ uint8_t coolant_temperature_warning = 0;
+ uint8_t heater_relay_status = 0;
+ uint8_t preheating_status = 0;
+ uint8_t thermal_control = 0;
+ uint8_t thermal_runaway = 0;
+ uint8_t thermal_runaway_module_ID = 0;
+ uint8_t HVIL_status = 0;
+ uint8_t hardware_fault_status = 0;
+ uint8_t insulation_fault = 0;
+ uint8_t temperature = 0;
+ uint8_t insulation_circuit_status = 0;
+ uint8_t plausibility_error = 0;
+ uint8_t service_due = 0;
+ uint8_t l3_fault = 0;
+ uint8_t master_warning = 0;
+ uint8_t hvbat_wakeup_state = 0;
+ uint8_t alert_frame3 = 0;
+ uint8_t alert_frame4 = 0;
+
+ bool rcd_line_active = false;
+ bool power_auth = false;
+ bool battery_balancing_active = false;
+ bool coolant_alarm = false;
+ bool cooling_enabled = false;
+ bool battery_minimum_voltage_reached_warning = false;
+ bool alert_low_battery_energy = false;
+};
+#endif
diff --git a/Software/src/battery/DALY-BMS.cpp b/Software/src/battery/DALY-BMS.cpp
index 74f927039..35d2ee7e3 100644
--- a/Software/src/battery/DALY-BMS.cpp
+++ b/Software/src/battery/DALY-BMS.cpp
@@ -16,7 +16,7 @@ static uint16_t voltage_dV = 0;
static uint32_t remaining_capacity_mAh = 0;
static uint16_t cellvoltages_mV[48] = {0};
static uint16_t cellvoltage_min_mV = 3700;
-static uint16_t cellvoltage_max_mV = 0;
+static uint16_t cellvoltage_max_mV = 3700;
static uint16_t cell_count = 0;
static uint16_t SOC = 0;
static bool has_fault = false;
@@ -31,6 +31,17 @@ void DalyBms::update_values() {
datalayer.battery.status.max_discharge_power_W =
(datalayer.battery.settings.max_user_set_discharge_dA * voltage_dV) / 100;
+ // limit power when reaching discharge voltage limit
+ uint32_t voltage_power_limit = 999999;
+ uint16_t min_voltage = datalayer.battery.info.min_design_voltage_dV;
+ if (datalayer.battery.settings.user_set_voltage_limits_active &&
+ datalayer.battery.settings.max_user_set_discharge_voltage_dV > min_voltage)
+ min_voltage = datalayer.battery.settings.max_user_set_discharge_voltage_dV;
+ if (voltage_dV - min_voltage < 20)
+ voltage_power_limit = (uint32_t)(voltage_dV - min_voltage) * POWER_PER_DV;
+ if (voltage_power_limit < datalayer.battery.status.max_discharge_power_W)
+ datalayer.battery.status.max_discharge_power_W = voltage_power_limit;
+
// limit power when SoC is low or high
uint32_t adaptive_power_limit = 999999;
if (SOC < 2000)
@@ -195,9 +206,10 @@ void DalyBms::receive() {
recv_len++;
- if (recv_len > 0 && recv_buff[0] != 0xA5 || recv_len > 1 && recv_buff[1] != 0x01 ||
- recv_len > 2 && (recv_buff[2] < 0x90 || recv_buff[2] > 0x98) || recv_len > 3 && recv_buff[3] != 8 ||
- recv_len > 12 && recv_buff[12] != calculate_checksum(recv_buff)) {
+ if (((recv_len > 0) && (recv_buff[0] != 0xA5)) || ((recv_len > 1) && (recv_buff[1] != 0x01)) ||
+ ((recv_len > 2) && ((recv_buff[2] < 0x90) || (recv_buff[2] > 0x98))) ||
+ ((recv_len > 3) && (recv_buff[3] != 8)) ||
+ ((recv_len > 12) && (recv_buff[12] != calculate_checksum(recv_buff)))) {
dump_buff("dropping partial rx: ", recv_buff, recv_len);
recv_len = 0;
}
diff --git a/Software/src/battery/DALY-BMS.h b/Software/src/battery/DALY-BMS.h
index 1bb7acb87..a0f2fc2ec 100644
--- a/Software/src/battery/DALY-BMS.h
+++ b/Software/src/battery/DALY-BMS.h
@@ -12,9 +12,10 @@ class DalyBms : public RS485Battery {
static constexpr const char* Name = "DALY RS485";
private:
- /* Tweak these according to your battery build */
+ /* You may tweak these according to your battery build. Values below are for Twizy Packs (14S2P NMC) */
static const int POWER_PER_PERCENT =
50; // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...)
+ static const int POWER_PER_DV = 50; // max power per dV when approaching discharge voltage limit
static const int POWER_PER_DEGREE_C = 60; // max power added/removed per degree above/below 0ยฐC
static const int POWER_AT_0_DEGREE_C = 800; // power at 0ยฐC
diff --git a/Software/src/battery/ECMP-BATTERY.cpp b/Software/src/battery/ECMP-BATTERY.cpp
index ebcd0c27e..e7af55655 100644
--- a/Software/src/battery/ECMP-BATTERY.cpp
+++ b/Software/src/battery/ECMP-BATTERY.cpp
@@ -34,11 +34,16 @@ void EcmpBattery::update_values() {
if (!MysteryVan) { //Normal eCMP platform
datalayer.battery.status.real_soc = battery_soc * 10;
- datalayer.battery.status.soh_pptt;
+ //datalayer.battery.status.soh_pptt; //TODO: Find SOH%
datalayer.battery.status.voltage_dV = battery_voltage * 10;
- datalayer.battery.status.current_dA = -(battery_current * 10);
+ // If High Precision Curent is avilable, use it
+ if (pid_current != NOT_SAMPLED_YET && datalayer.battery.status.bms_status != FAULT) {
+ datalayer.battery.status.current_dA = (int16_t)(pid_current / 100);
+ } else {
+ datalayer.battery.status.current_dA = -(battery_current * 10);
+ }
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
@@ -84,19 +89,12 @@ void EcmpBattery::update_values() {
10000);
}
- datalayer.battery.status.soh_pptt;
+ //datalayer.battery.status.soh_pptt; //TODO: Find SOH%
if (pid_pack_voltage != NOT_SAMPLED_YET) {
datalayer.battery.status.voltage_dV = pid_pack_voltage + 800;
}
- if (pid_current != NOT_SAMPLED_YET) {
- datalayer.battery.status.current_dA = (int16_t)(pid_current / 100);
-
- datalayer.battery.status.active_power_W =
- (uint16_t)((pid_current / 1000.0f) * (datalayer.battery.status.voltage_dV / 10.0f));
- }
-
if (pid_max_charge_10s != NOT_SAMPLED_YET) {
datalayer.battery.status.max_charge_power_W = pid_max_charge_10s;
}
@@ -105,7 +103,7 @@ void EcmpBattery::update_values() {
datalayer.battery.status.max_discharge_power_W = pid_max_discharge_10s;
}
- if ((pid_highest_temperature != NOT_SAMPLED_YET) && (pid_lowest_temperature != NOT_SAMPLED_YET)) {
+ if ((pid_highest_temperature != 127) && (pid_lowest_temperature != 127)) {
datalayer.battery.status.temperature_max_dC = pid_highest_temperature * 10;
datalayer.battery.status.temperature_min_dC = pid_lowest_temperature * 10;
}
@@ -171,7 +169,6 @@ void EcmpBattery::update_values() {
datalayer_extended.stellantisECMP.pid_sw_version_num = pid_sw_version_num;
datalayer_extended.stellantisECMP.pid_factory_mode_control = pid_factory_mode_control;
memcpy(datalayer_extended.stellantisECMP.pid_battery_serial, pid_battery_serial, sizeof(pid_battery_serial));
- uint8_t pid_battery_serial[13] = {0};
datalayer_extended.stellantisECMP.pid_aux_fuse_state = pid_aux_fuse_state;
datalayer_extended.stellantisECMP.pid_battery_state = pid_battery_state;
datalayer_extended.stellantisECMP.pid_precharge_short_circuit = pid_precharge_short_circuit;
@@ -260,7 +257,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
((rx_frame.data.u8[6] & 0xF0) >> 4); // (Wh, 0-200000)
HV_BATT_SOE_MAX = ((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]; // (Wh, 0-200000)
CHECKSUM_FRAME_3B4 = (rx_frame.data.u8[0] & 0xF0) >> 4;
- COUNTER_3B4 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_3B4 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x2F4: //MysteryVan 50/75kWh platform (Event triggered when charging)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -298,7 +295,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
((rx_frame.data.u8[6] & 0xC0) >> 6); // -1000000 - 0 W
MAX_ALLOW_CHRG_CURRENT = ((rx_frame.data.u8[6] & 0x3F) << 8) | rx_frame.data.u8[7];
CHECKSUM_FRAME_554 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xE
- COUNTER_554 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_554 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x373: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -317,7 +314,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
HV_BATT_REAL_VOLT_HD = ((rx_frame.data.u8[3] & 0x3F) << 8) | (rx_frame.data.u8[4]); //V 0-1000 * 0.1 scaling
HV_BATT_REAL_CURR_HD = (rx_frame.data.u8[1] << 8) | (rx_frame.data.u8[2]); //A -2000 - 2000 0.1 scaling
CHECKSUM_FRAME_373 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xD
- COUNTER_373 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_373 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x4F4: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -335,7 +332,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
(rx_frame.data.u8[2] & 0x30) >> 4; //00 : contactor open 01 : pre-load contactor 10 : contactor close
HV_BATT_DISCONT_WARNING_OPEN = (rx_frame.data.u8[7] & 0x08) >> 3;
CHECKSUM_FRAME_4F4 = (rx_frame.data.u8[0] & 0xF0) >> 4;
- COUNTER_4F4 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_4F4 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x414: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -345,7 +342,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
MAX_ALLOW_DISCHRG_POWER =
((rx_frame.data.u8[5] & 0x07) << 11) | (rx_frame.data.u8[6] << 3) | ((rx_frame.data.u8[7] & 0xE0) >> 5);
CHECKSUM_FRAME_414 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0x9
- COUNTER_414 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_414 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x353: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -354,7 +351,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
HV_BATT_COP_CURRENT =
(rx_frame.data.u8[3] << 5) | (rx_frame.data.u8[4] >> 3); //High resolution battery current (dA, -4000 - 4000)
CHECKSUM_FRAME_353 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xB
- COUNTER_353 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_353 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x474: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -370,7 +367,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
10 : charging fault
11 : charging finished*/
CHECKSUM_FRAME_474 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xF
- COUNTER_474 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_474 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x574: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -422,7 +419,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
//TBMU_CHRG_CONN_CONF //Fastcharger info, not needed for BE
//EVSE_GRID_FAULT //Fastcharger info, not needed for BE
CHECKSUM_FRAME_314 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0x8
- COUNTER_314 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_314 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x254: //MysteryVan 50/75kWh platform
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -447,7 +444,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
MAX_ALLOW_DISCHRG_CURRENT = ((rx_frame.data.u8[6] & 0x3F) << 5) | (rx_frame.data.u8[7] >> 3);
RC01_PERM_SYNTH_TBMU = (rx_frame.data.u8[7] & 0x04) >> 2; //TBMU Readiness Code synthesis
CHECKSUM_FRAME_4D4 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0x5
- COUNTER_4D4 = (rx_frame.data.u8[0] & 0x0F);
+ //COUNTER_4D4 = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x125: //Common eCMP
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -862,10 +859,11 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
pid_avg_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case PID_CURRENT:
- pid_current = -(((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) |
- rx_frame.data.u8[7]) -
- 76800) *
- 20;
+ pid_current = -((((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) |
+ rx_frame.data.u8[7]) -
+ 76800) *
+ 155) /
+ 10;
break;
case PID_INSULATION_NEG:
pid_insulation_res_neg = ((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) |
@@ -1122,7 +1120,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.data.u8[0]) {
case 0x10:
pid_current_time = (pid_current_time | rx_frame.data.u8[7]);
- break;
+ break; //Something is wrong here. TODO
case 0x21:
pid_current_time = (rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) |
(rx_frame.data.u8[1] << 8) | pid_current_time;
@@ -1216,6 +1214,9 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
DisableIsoMonitoringStatemachine = COMPLETED_STATE;
timeSpentDisableIsoMonitoring = COMPLETED_STATE;
}
+ } else if (datalayer_extended.stellantisECMP.UserRequestDTCreset) {
+ transmit_can_frame(&ECMP_CLEAR_DTC);
+ datalayer_extended.stellantisECMP.UserRequestDTCreset = false;
} else if (datalayer_extended.stellantisECMP.UserRequestContactorReset) {
if (ContactorResetStatemachine == 0) {
transmit_can_frame(&ECMP_DIAG_START);
@@ -1291,363 +1292,368 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
if (datalayer.battery.status.bms_status != FAULT) { //Stop PID polling if battery is in FAULT mode
- switch (poll_state) {
- case PID_WELD_CHECK:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_WELD_CHECK & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_WELD_CHECK & 0x00FF);
- poll_state = PID_CONT_REASON_OPEN;
- break;
- case PID_CONT_REASON_OPEN:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONT_REASON_OPEN & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONT_REASON_OPEN & 0x00FF);
- poll_state = PID_CONTACTOR_STATUS;
- break;
- case PID_CONTACTOR_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_STATUS & 0x00FF);
- poll_state = PID_NEG_CONT_CONTROL;
- break;
- case PID_NEG_CONT_CONTROL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_NEG_CONT_CONTROL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_NEG_CONT_CONTROL & 0x00FF);
- poll_state = PID_NEG_CONT_STATUS;
- break;
- case PID_NEG_CONT_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_NEG_CONT_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_NEG_CONT_STATUS & 0x00FF);
- poll_state = PID_POS_CONT_CONTROL;
- break;
- case PID_POS_CONT_CONTROL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_POS_CONT_CONTROL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_POS_CONT_CONTROL & 0x00FF);
- poll_state = PID_POS_CONT_STATUS;
- break;
- case PID_POS_CONT_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_POS_CONT_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_POS_CONT_STATUS & 0x00FF);
- poll_state = PID_CONTACTOR_NEGATIVE;
- break;
- case PID_CONTACTOR_NEGATIVE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_NEGATIVE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_NEGATIVE & 0x00FF);
- poll_state = PID_CONTACTOR_POSITIVE;
- break;
- case PID_CONTACTOR_POSITIVE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_POSITIVE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_POSITIVE & 0x00FF);
- poll_state = PID_PRECHARGE_RELAY_CONTROL;
- break;
- case PID_PRECHARGE_RELAY_CONTROL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_PRECHARGE_RELAY_CONTROL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_PRECHARGE_RELAY_CONTROL & 0x00FF);
- poll_state = PID_PRECHARGE_RELAY_STATUS;
- break;
- case PID_PRECHARGE_RELAY_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_PRECHARGE_RELAY_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_PRECHARGE_RELAY_STATUS & 0x00FF);
- poll_state = PID_RECHARGE_STATUS;
- break;
- case PID_RECHARGE_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_RECHARGE_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_RECHARGE_STATUS & 0x00FF);
- poll_state = PID_DELTA_TEMPERATURE;
- break;
- case PID_DELTA_TEMPERATURE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_DELTA_TEMPERATURE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_DELTA_TEMPERATURE & 0x00FF);
- poll_state = PID_COLDEST_MODULE;
- break;
- case PID_COLDEST_MODULE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_COLDEST_MODULE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_COLDEST_MODULE & 0x00FF);
- poll_state = PID_LOWEST_TEMPERATURE;
- break;
- case PID_LOWEST_TEMPERATURE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOWEST_TEMPERATURE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOWEST_TEMPERATURE & 0x00FF);
- poll_state = PID_AVERAGE_TEMPERATURE;
- break;
- case PID_AVERAGE_TEMPERATURE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_AVERAGE_TEMPERATURE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_AVERAGE_TEMPERATURE & 0x00FF);
- poll_state = PID_HIGHEST_TEMPERATURE;
- break;
- case PID_HIGHEST_TEMPERATURE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HIGHEST_TEMPERATURE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HIGHEST_TEMPERATURE & 0x00FF);
- poll_state = PID_HOTTEST_MODULE;
- break;
- case PID_HOTTEST_MODULE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HOTTEST_MODULE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HOTTEST_MODULE & 0x00FF);
- poll_state = PID_AVG_CELL_VOLTAGE;
- break;
- case PID_AVG_CELL_VOLTAGE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_AVG_CELL_VOLTAGE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_AVG_CELL_VOLTAGE & 0x00FF);
- poll_state = PID_CURRENT;
- break;
- case PID_CURRENT:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CURRENT & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CURRENT & 0x00FF);
- poll_state = PID_INSULATION_NEG;
- break;
- case PID_INSULATION_NEG:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_INSULATION_NEG & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_INSULATION_NEG & 0x00FF);
- poll_state = PID_INSULATION_POS;
- break;
- case PID_INSULATION_POS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_INSULATION_POS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_INSULATION_POS & 0x00FF);
- poll_state = PID_MAX_CURRENT_10S;
- break;
- case PID_MAX_CURRENT_10S:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_CURRENT_10S & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_CURRENT_10S & 0x00FF);
- poll_state = PID_MAX_DISCHARGE_10S;
- break;
- case PID_MAX_DISCHARGE_10S:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_DISCHARGE_10S & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_DISCHARGE_10S & 0x00FF);
- poll_state = PID_MAX_DISCHARGE_30S;
- break;
- case PID_MAX_DISCHARGE_30S:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_DISCHARGE_30S & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_DISCHARGE_30S & 0x00FF);
- poll_state = PID_MAX_CHARGE_10S;
- break;
- case PID_MAX_CHARGE_10S:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_CHARGE_10S & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_CHARGE_10S & 0x00FF);
- poll_state = PID_MAX_CHARGE_30S;
- break;
- case PID_MAX_CHARGE_30S:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_CHARGE_30S & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_CHARGE_30S & 0x00FF);
- poll_state = PID_ENERGY_CAPACITY;
- break;
- case PID_ENERGY_CAPACITY:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_ENERGY_CAPACITY & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_ENERGY_CAPACITY & 0x00FF);
- poll_state = PID_HIGH_CELL_NUM;
- break;
- case PID_HIGH_CELL_NUM:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HIGH_CELL_NUM & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HIGH_CELL_NUM & 0x00FF);
- poll_state = PID_LOW_CELL_NUM;
- break;
- case PID_LOW_CELL_NUM:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOW_CELL_NUM & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOW_CELL_NUM & 0x00FF);
- poll_state = PID_SUM_OF_CELLS;
- break;
- case PID_SUM_OF_CELLS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_SUM_OF_CELLS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_SUM_OF_CELLS & 0x00FF);
- poll_state = PID_CELL_MIN_CAPACITY;
- break;
- case PID_CELL_MIN_CAPACITY:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELL_MIN_CAPACITY & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELL_MIN_CAPACITY & 0x00FF);
- poll_state = PID_CELL_VOLTAGE_MEAS_STATUS;
- break;
- case PID_CELL_VOLTAGE_MEAS_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELL_VOLTAGE_MEAS_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELL_VOLTAGE_MEAS_STATUS & 0x00FF);
- poll_state = PID_INSULATION_RES;
- break;
- case PID_INSULATION_RES:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_INSULATION_RES & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_INSULATION_RES & 0x00FF);
- poll_state = PID_PACK_VOLTAGE;
- break;
- case PID_PACK_VOLTAGE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_PACK_VOLTAGE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_PACK_VOLTAGE & 0x00FF);
- poll_state = PID_HIGH_CELL_VOLTAGE;
- break;
- case PID_HIGH_CELL_VOLTAGE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HIGH_CELL_VOLTAGE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HIGH_CELL_VOLTAGE & 0x00FF);
- poll_state = PID_ALL_CELL_VOLTAGES;
- break;
- case PID_ALL_CELL_VOLTAGES:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_ALL_CELL_VOLTAGES & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_ALL_CELL_VOLTAGES & 0x00FF);
- poll_state = PID_LOW_CELL_VOLTAGE;
- break;
- case PID_LOW_CELL_VOLTAGE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOW_CELL_VOLTAGE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOW_CELL_VOLTAGE & 0x00FF);
- poll_state = PID_BATTERY_ENERGY;
- break;
- case PID_BATTERY_ENERGY:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_BATTERY_ENERGY & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_BATTERY_ENERGY & 0x00FF);
- poll_state = PID_CELLBALANCE_STATUS;
- break;
- case PID_CELLBALANCE_STATUS:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELLBALANCE_STATUS & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELLBALANCE_STATUS & 0x00FF);
- poll_state = PID_CELLBALANCE_HWERR_MASK;
- break;
- case PID_CELLBALANCE_HWERR_MASK:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELLBALANCE_HWERR_MASK & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELLBALANCE_HWERR_MASK & 0x00FF);
- poll_state = PID_CRASH_COUNTER;
- break;
- case PID_CRASH_COUNTER:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CRASH_COUNTER & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CRASH_COUNTER & 0x00FF);
- poll_state = PID_WIRE_CRASH;
- break;
- case PID_WIRE_CRASH:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_WIRE_CRASH & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_WIRE_CRASH & 0x00FF);
- poll_state = PID_CAN_CRASH;
- break;
- case PID_CAN_CRASH:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CAN_CRASH & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CAN_CRASH & 0x00FF);
- poll_state = PID_HISTORY_DATA;
- break;
- case PID_HISTORY_DATA:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HISTORY_DATA & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HISTORY_DATA & 0x00FF);
- poll_state = PID_LOWSOC_COUNTER;
- break;
- case PID_LOWSOC_COUNTER:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOWSOC_COUNTER & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOWSOC_COUNTER & 0x00FF);
- poll_state = PID_LAST_CAN_FAILURE_DETAIL;
- break;
- case PID_LAST_CAN_FAILURE_DETAIL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_LAST_CAN_FAILURE_DETAIL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_LAST_CAN_FAILURE_DETAIL & 0x00FF);
- poll_state = PID_HW_VERSION_NUM;
- break;
- case PID_HW_VERSION_NUM:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HW_VERSION_NUM & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HW_VERSION_NUM & 0x00FF);
- poll_state = PID_SW_VERSION_NUM;
- break;
- case PID_SW_VERSION_NUM:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_SW_VERSION_NUM & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_SW_VERSION_NUM & 0x00FF);
- poll_state = PID_FACTORY_MODE_CONTROL;
- break;
- case PID_FACTORY_MODE_CONTROL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_FACTORY_MODE_CONTROL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_FACTORY_MODE_CONTROL & 0x00FF);
- poll_state = PID_BATTERY_SERIAL;
- break;
- case PID_BATTERY_SERIAL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_BATTERY_SERIAL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_BATTERY_SERIAL & 0x00FF);
- poll_state = PID_AUX_FUSE_STATE;
- break;
- case PID_AUX_FUSE_STATE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_AUX_FUSE_STATE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_AUX_FUSE_STATE & 0x00FF);
- poll_state = PID_BATTERY_STATE;
- break;
- case PID_BATTERY_STATE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_BATTERY_STATE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_BATTERY_STATE & 0x00FF);
- poll_state = PID_PRECHARGE_SHORT_CIRCUIT;
- break;
- case PID_PRECHARGE_SHORT_CIRCUIT:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_PRECHARGE_SHORT_CIRCUIT & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_PRECHARGE_SHORT_CIRCUIT & 0x00FF);
- poll_state = PID_ESERVICE_PLUG_STATE;
- break;
- case PID_ESERVICE_PLUG_STATE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_ESERVICE_PLUG_STATE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_ESERVICE_PLUG_STATE & 0x00FF);
- poll_state = PID_MAINFUSE_STATE;
- break;
- case PID_MAINFUSE_STATE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAINFUSE_STATE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAINFUSE_STATE & 0x00FF);
- poll_state = PID_MOST_CRITICAL_FAULT;
- break;
- case PID_MOST_CRITICAL_FAULT:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_MOST_CRITICAL_FAULT & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_MOST_CRITICAL_FAULT & 0x00FF);
- poll_state = PID_CURRENT_TIME;
- break;
- case PID_CURRENT_TIME:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CURRENT_TIME & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CURRENT_TIME & 0x00FF);
- poll_state = PID_TIME_SENT_BY_CAR;
- break;
- case PID_TIME_SENT_BY_CAR:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_TIME_SENT_BY_CAR & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_TIME_SENT_BY_CAR & 0x00FF);
- poll_state = PID_12V;
- break;
- case PID_12V:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_12V & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_12V & 0x00FF);
- poll_state = PID_12V_ABNORMAL;
- break;
- case PID_12V_ABNORMAL:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_12V_ABNORMAL & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_12V_ABNORMAL & 0x00FF);
- poll_state = PID_HVIL_IN_VOLTAGE;
- break;
- case PID_HVIL_IN_VOLTAGE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HVIL_IN_VOLTAGE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HVIL_IN_VOLTAGE & 0x00FF);
- poll_state = PID_HVIL_OUT_VOLTAGE;
- break;
- case PID_HVIL_OUT_VOLTAGE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HVIL_OUT_VOLTAGE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HVIL_OUT_VOLTAGE & 0x00FF);
- poll_state = PID_HVIL_STATE;
- break;
- case PID_HVIL_STATE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_HVIL_STATE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_HVIL_STATE & 0x00FF);
- poll_state = PID_BMS_STATE;
- break;
- case PID_BMS_STATE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_BMS_STATE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_BMS_STATE & 0x00FF);
- poll_state = PID_VEHICLE_SPEED;
- break;
- case PID_VEHICLE_SPEED:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_VEHICLE_SPEED & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_VEHICLE_SPEED & 0x00FF);
- poll_state = PID_TIME_SPENT_OVER_55C;
- break;
- case PID_TIME_SPENT_OVER_55C:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_TIME_SPENT_OVER_55C & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_TIME_SPENT_OVER_55C & 0x00FF);
- poll_state = PID_CONTACTOR_CLOSING_COUNTER;
- break;
- case PID_CONTACTOR_CLOSING_COUNTER:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_CLOSING_COUNTER & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_CLOSING_COUNTER & 0x00FF);
- poll_state = PID_DATE_OF_MANUFACTURE;
- break;
- case PID_DATE_OF_MANUFACTURE:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_DATE_OF_MANUFACTURE & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_DATE_OF_MANUFACTURE & 0x00FF);
- poll_state = PID_ALL_CELL_SOH;
- break;
- case PID_ALL_CELL_SOH:
- ECMP_POLL.data.u8[2] = (uint8_t)((PID_ALL_CELL_SOH & 0xFF00) >> 8);
- ECMP_POLL.data.u8[3] = (uint8_t)(PID_ALL_CELL_SOH & 0x00FF);
- poll_state = PID_WELD_CHECK; // Loop back to beginning
- break;
- default:
- //We should not end up here. Reset poll_state to first poll
- poll_state = PID_WELD_CHECK;
- break;
+ // Sample High Precison Current every other time
+ if (HighPrecisionCurrentSampling) {
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CURRENT & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CURRENT & 0x00FF);
+ transmit_can_frame(&ECMP_POLL);
+ HighPrecisionCurrentSampling = 0;
+ } else {
+ HighPrecisionCurrentSampling = 1;
+
+ switch (poll_state) {
+ case PID_WELD_CHECK:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_WELD_CHECK & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_WELD_CHECK & 0x00FF);
+ poll_state = PID_CONT_REASON_OPEN;
+ break;
+ case PID_CONT_REASON_OPEN:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONT_REASON_OPEN & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONT_REASON_OPEN & 0x00FF);
+ poll_state = PID_CONTACTOR_STATUS;
+ break;
+ case PID_CONTACTOR_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_STATUS & 0x00FF);
+ poll_state = PID_NEG_CONT_CONTROL;
+ break;
+ case PID_NEG_CONT_CONTROL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_NEG_CONT_CONTROL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_NEG_CONT_CONTROL & 0x00FF);
+ poll_state = PID_NEG_CONT_STATUS;
+ break;
+ case PID_NEG_CONT_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_NEG_CONT_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_NEG_CONT_STATUS & 0x00FF);
+ poll_state = PID_POS_CONT_CONTROL;
+ break;
+ case PID_POS_CONT_CONTROL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_POS_CONT_CONTROL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_POS_CONT_CONTROL & 0x00FF);
+ poll_state = PID_POS_CONT_STATUS;
+ break;
+ case PID_POS_CONT_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_POS_CONT_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_POS_CONT_STATUS & 0x00FF);
+ poll_state = PID_CONTACTOR_NEGATIVE;
+ break;
+ case PID_CONTACTOR_NEGATIVE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_NEGATIVE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_NEGATIVE & 0x00FF);
+ poll_state = PID_CONTACTOR_POSITIVE;
+ break;
+ case PID_CONTACTOR_POSITIVE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_POSITIVE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_POSITIVE & 0x00FF);
+ poll_state = PID_PRECHARGE_RELAY_CONTROL;
+ break;
+ case PID_PRECHARGE_RELAY_CONTROL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_PRECHARGE_RELAY_CONTROL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_PRECHARGE_RELAY_CONTROL & 0x00FF);
+ poll_state = PID_PRECHARGE_RELAY_STATUS;
+ break;
+ case PID_PRECHARGE_RELAY_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_PRECHARGE_RELAY_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_PRECHARGE_RELAY_STATUS & 0x00FF);
+ poll_state = PID_RECHARGE_STATUS;
+ break;
+ case PID_RECHARGE_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_RECHARGE_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_RECHARGE_STATUS & 0x00FF);
+ poll_state = PID_DELTA_TEMPERATURE;
+ break;
+ case PID_DELTA_TEMPERATURE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_DELTA_TEMPERATURE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_DELTA_TEMPERATURE & 0x00FF);
+ poll_state = PID_COLDEST_MODULE;
+ break;
+ case PID_COLDEST_MODULE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_COLDEST_MODULE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_COLDEST_MODULE & 0x00FF);
+ poll_state = PID_LOWEST_TEMPERATURE;
+ break;
+ case PID_LOWEST_TEMPERATURE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOWEST_TEMPERATURE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOWEST_TEMPERATURE & 0x00FF);
+ poll_state = PID_AVERAGE_TEMPERATURE;
+ break;
+ case PID_AVERAGE_TEMPERATURE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_AVERAGE_TEMPERATURE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_AVERAGE_TEMPERATURE & 0x00FF);
+ poll_state = PID_HIGHEST_TEMPERATURE;
+ break;
+ case PID_HIGHEST_TEMPERATURE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HIGHEST_TEMPERATURE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HIGHEST_TEMPERATURE & 0x00FF);
+ poll_state = PID_HOTTEST_MODULE;
+ break;
+ case PID_HOTTEST_MODULE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HOTTEST_MODULE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HOTTEST_MODULE & 0x00FF);
+ poll_state = PID_AVG_CELL_VOLTAGE;
+ break;
+ case PID_AVG_CELL_VOLTAGE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_AVG_CELL_VOLTAGE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_AVG_CELL_VOLTAGE & 0x00FF);
+ poll_state = PID_INSULATION_NEG;
+ break;
+ case PID_INSULATION_NEG:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_INSULATION_NEG & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_INSULATION_NEG & 0x00FF);
+ poll_state = PID_INSULATION_POS;
+ break;
+ case PID_INSULATION_POS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_INSULATION_POS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_INSULATION_POS & 0x00FF);
+ poll_state = PID_MAX_CURRENT_10S;
+ break;
+ case PID_MAX_CURRENT_10S:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_CURRENT_10S & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_CURRENT_10S & 0x00FF);
+ poll_state = PID_MAX_DISCHARGE_10S;
+ break;
+ case PID_MAX_DISCHARGE_10S:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_DISCHARGE_10S & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_DISCHARGE_10S & 0x00FF);
+ poll_state = PID_MAX_DISCHARGE_30S;
+ break;
+ case PID_MAX_DISCHARGE_30S:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_DISCHARGE_30S & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_DISCHARGE_30S & 0x00FF);
+ poll_state = PID_MAX_CHARGE_10S;
+ break;
+ case PID_MAX_CHARGE_10S:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_CHARGE_10S & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_CHARGE_10S & 0x00FF);
+ poll_state = PID_MAX_CHARGE_30S;
+ break;
+ case PID_MAX_CHARGE_30S:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAX_CHARGE_30S & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAX_CHARGE_30S & 0x00FF);
+ poll_state = PID_ENERGY_CAPACITY;
+ break;
+ case PID_ENERGY_CAPACITY:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_ENERGY_CAPACITY & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_ENERGY_CAPACITY & 0x00FF);
+ poll_state = PID_HIGH_CELL_NUM;
+ break;
+ case PID_HIGH_CELL_NUM:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HIGH_CELL_NUM & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HIGH_CELL_NUM & 0x00FF);
+ poll_state = PID_LOW_CELL_NUM;
+ break;
+ case PID_LOW_CELL_NUM:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOW_CELL_NUM & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOW_CELL_NUM & 0x00FF);
+ poll_state = PID_SUM_OF_CELLS;
+ break;
+ case PID_SUM_OF_CELLS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_SUM_OF_CELLS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_SUM_OF_CELLS & 0x00FF);
+ poll_state = PID_CELL_MIN_CAPACITY;
+ break;
+ case PID_CELL_MIN_CAPACITY:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELL_MIN_CAPACITY & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELL_MIN_CAPACITY & 0x00FF);
+ poll_state = PID_CELL_VOLTAGE_MEAS_STATUS;
+ break;
+ case PID_CELL_VOLTAGE_MEAS_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELL_VOLTAGE_MEAS_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELL_VOLTAGE_MEAS_STATUS & 0x00FF);
+ poll_state = PID_INSULATION_RES;
+ break;
+ case PID_INSULATION_RES:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_INSULATION_RES & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_INSULATION_RES & 0x00FF);
+ poll_state = PID_PACK_VOLTAGE;
+ break;
+ case PID_PACK_VOLTAGE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_PACK_VOLTAGE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_PACK_VOLTAGE & 0x00FF);
+ poll_state = PID_HIGH_CELL_VOLTAGE;
+ break;
+ case PID_HIGH_CELL_VOLTAGE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HIGH_CELL_VOLTAGE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HIGH_CELL_VOLTAGE & 0x00FF);
+ poll_state = PID_ALL_CELL_VOLTAGES;
+ break;
+ case PID_ALL_CELL_VOLTAGES:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_ALL_CELL_VOLTAGES & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_ALL_CELL_VOLTAGES & 0x00FF);
+ poll_state = PID_LOW_CELL_VOLTAGE;
+ break;
+ case PID_LOW_CELL_VOLTAGE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOW_CELL_VOLTAGE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOW_CELL_VOLTAGE & 0x00FF);
+ poll_state = PID_BATTERY_ENERGY;
+ break;
+ case PID_BATTERY_ENERGY:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_BATTERY_ENERGY & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_BATTERY_ENERGY & 0x00FF);
+ poll_state = PID_CELLBALANCE_STATUS;
+ break;
+ case PID_CELLBALANCE_STATUS:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELLBALANCE_STATUS & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELLBALANCE_STATUS & 0x00FF);
+ poll_state = PID_CELLBALANCE_HWERR_MASK;
+ break;
+ case PID_CELLBALANCE_HWERR_MASK:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CELLBALANCE_HWERR_MASK & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CELLBALANCE_HWERR_MASK & 0x00FF);
+ poll_state = PID_CRASH_COUNTER;
+ break;
+ case PID_CRASH_COUNTER:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CRASH_COUNTER & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CRASH_COUNTER & 0x00FF);
+ poll_state = PID_WIRE_CRASH;
+ break;
+ case PID_WIRE_CRASH:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_WIRE_CRASH & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_WIRE_CRASH & 0x00FF);
+ poll_state = PID_CAN_CRASH;
+ break;
+ case PID_CAN_CRASH:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CAN_CRASH & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CAN_CRASH & 0x00FF);
+ poll_state = PID_HISTORY_DATA;
+ break;
+ case PID_HISTORY_DATA:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HISTORY_DATA & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HISTORY_DATA & 0x00FF);
+ poll_state = PID_LOWSOC_COUNTER;
+ break;
+ case PID_LOWSOC_COUNTER:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_LOWSOC_COUNTER & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_LOWSOC_COUNTER & 0x00FF);
+ poll_state = PID_LAST_CAN_FAILURE_DETAIL;
+ break;
+ case PID_LAST_CAN_FAILURE_DETAIL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_LAST_CAN_FAILURE_DETAIL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_LAST_CAN_FAILURE_DETAIL & 0x00FF);
+ poll_state = PID_HW_VERSION_NUM;
+ break;
+ case PID_HW_VERSION_NUM:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HW_VERSION_NUM & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HW_VERSION_NUM & 0x00FF);
+ poll_state = PID_SW_VERSION_NUM;
+ break;
+ case PID_SW_VERSION_NUM:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_SW_VERSION_NUM & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_SW_VERSION_NUM & 0x00FF);
+ poll_state = PID_FACTORY_MODE_CONTROL;
+ break;
+ case PID_FACTORY_MODE_CONTROL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_FACTORY_MODE_CONTROL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_FACTORY_MODE_CONTROL & 0x00FF);
+ poll_state = PID_BATTERY_SERIAL;
+ break;
+ case PID_BATTERY_SERIAL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_BATTERY_SERIAL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_BATTERY_SERIAL & 0x00FF);
+ poll_state = PID_AUX_FUSE_STATE;
+ break;
+ case PID_AUX_FUSE_STATE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_AUX_FUSE_STATE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_AUX_FUSE_STATE & 0x00FF);
+ poll_state = PID_BATTERY_STATE;
+ break;
+ case PID_BATTERY_STATE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_BATTERY_STATE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_BATTERY_STATE & 0x00FF);
+ poll_state = PID_PRECHARGE_SHORT_CIRCUIT;
+ break;
+ case PID_PRECHARGE_SHORT_CIRCUIT:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_PRECHARGE_SHORT_CIRCUIT & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_PRECHARGE_SHORT_CIRCUIT & 0x00FF);
+ poll_state = PID_ESERVICE_PLUG_STATE;
+ break;
+ case PID_ESERVICE_PLUG_STATE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_ESERVICE_PLUG_STATE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_ESERVICE_PLUG_STATE & 0x00FF);
+ poll_state = PID_MAINFUSE_STATE;
+ break;
+ case PID_MAINFUSE_STATE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MAINFUSE_STATE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MAINFUSE_STATE & 0x00FF);
+ poll_state = PID_MOST_CRITICAL_FAULT;
+ break;
+ case PID_MOST_CRITICAL_FAULT:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_MOST_CRITICAL_FAULT & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_MOST_CRITICAL_FAULT & 0x00FF);
+ poll_state = PID_CURRENT_TIME;
+ break;
+ case PID_CURRENT_TIME:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CURRENT_TIME & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CURRENT_TIME & 0x00FF);
+ poll_state = PID_TIME_SENT_BY_CAR;
+ break;
+ case PID_TIME_SENT_BY_CAR:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_TIME_SENT_BY_CAR & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_TIME_SENT_BY_CAR & 0x00FF);
+ poll_state = PID_12V;
+ break;
+ case PID_12V:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_12V & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_12V & 0x00FF);
+ poll_state = PID_12V_ABNORMAL;
+ break;
+ case PID_12V_ABNORMAL:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_12V_ABNORMAL & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_12V_ABNORMAL & 0x00FF);
+ poll_state = PID_HVIL_IN_VOLTAGE;
+ break;
+ case PID_HVIL_IN_VOLTAGE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HVIL_IN_VOLTAGE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HVIL_IN_VOLTAGE & 0x00FF);
+ poll_state = PID_HVIL_OUT_VOLTAGE;
+ break;
+ case PID_HVIL_OUT_VOLTAGE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HVIL_OUT_VOLTAGE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HVIL_OUT_VOLTAGE & 0x00FF);
+ poll_state = PID_HVIL_STATE;
+ break;
+ case PID_HVIL_STATE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_HVIL_STATE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_HVIL_STATE & 0x00FF);
+ poll_state = PID_BMS_STATE;
+ break;
+ case PID_BMS_STATE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_BMS_STATE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_BMS_STATE & 0x00FF);
+ poll_state = PID_VEHICLE_SPEED;
+ break;
+ case PID_VEHICLE_SPEED:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_VEHICLE_SPEED & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_VEHICLE_SPEED & 0x00FF);
+ poll_state = PID_TIME_SPENT_OVER_55C;
+ break;
+ case PID_TIME_SPENT_OVER_55C:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_TIME_SPENT_OVER_55C & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_TIME_SPENT_OVER_55C & 0x00FF);
+ poll_state = PID_CONTACTOR_CLOSING_COUNTER;
+ break;
+ case PID_CONTACTOR_CLOSING_COUNTER:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_CONTACTOR_CLOSING_COUNTER & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_CONTACTOR_CLOSING_COUNTER & 0x00FF);
+ poll_state = PID_DATE_OF_MANUFACTURE;
+ break;
+ case PID_DATE_OF_MANUFACTURE:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_DATE_OF_MANUFACTURE & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_DATE_OF_MANUFACTURE & 0x00FF);
+ poll_state = PID_ALL_CELL_SOH;
+ break;
+ case PID_ALL_CELL_SOH:
+ ECMP_POLL.data.u8[2] = (uint8_t)((PID_ALL_CELL_SOH & 0xFF00) >> 8);
+ ECMP_POLL.data.u8[3] = (uint8_t)(PID_ALL_CELL_SOH & 0x00FF);
+ poll_state = PID_WELD_CHECK; // Loop back to beginning
+ break;
+ default:
+ //We should not end up here. Reset poll_state to first poll
+ poll_state = PID_WELD_CHECK;
+ break;
+ }
+ transmit_can_frame(&ECMP_POLL);
}
- transmit_can_frame(&ECMP_POLL);
}
}
}
@@ -1680,11 +1686,11 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&ECMP_0C5); //DC2_0C5
transmit_can_frame(&ECMP_17B); //VCU_PCANInfo_17B
transmit_can_frame(&ECMP_0F2); //CtrlMCU1_0F2
- if (simulateEntireCar) {
- transmit_can_frame(&ECMP_111);
- transmit_can_frame(&ECMP_110);
- transmit_can_frame(&ECMP_114);
- }
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
+ transmit_can_frame(&ECMP_111);
+ transmit_can_frame(&ECMP_110);
+ transmit_can_frame(&ECMP_114);
+#endif
}
// Send 20ms periodic CAN Message simulating the car still being attached
@@ -1727,11 +1733,13 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
if (datalayer.battery.status.bms_status == FAULT) {
//Make vehicle appear as in idle HV state. Useful for clearing DTCs
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
ECMP_31E.data.u8[0] = 0x48;
- ECMP_345.data = {0x45, 0x57, 0x00, 0x04, 0x00, 0x00, 0x06, 0x31};
ECMP_351.data = {0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0E};
ECMP_372.data = {0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
ECMP_383.data.u8[0] = 0x00;
+#endif
+ ECMP_345.data = {0x45, 0x57, 0x00, 0x04, 0x00, 0x00, 0x06, 0x31};
ECMP_3A2.data = {0x03, 0xE8, 0x00, 0x00, 0x81, 0x00, 0x08, 0x02};
ECMP_3A3.data = {0x4A, 0x4A, 0x40, 0x00, 0x00, 0x08, 0x00, 0x0F};
data_345_content[0] = 0x04; // Allows for DTCs to clear
@@ -1769,11 +1777,13 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&ECMP_3D0); //Not in logs, but makes speed go to 0km/h
} else {
//Normal operation for contactor closing
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
ECMP_31E.data.u8[0] = 0x50;
- ECMP_345.data = {0x45, 0x52, 0x00, 0x04, 0xDD, 0x00, 0x02, 0x30};
ECMP_351.data = {0x00, 0x00, 0x00, 0x00, 0x0E, 0xA0, 0x00, 0xE0};
ECMP_372.data = {0x9A, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
ECMP_383.data.u8[0] = 0x40;
+#endif
+ ECMP_345.data = {0x45, 0x52, 0x00, 0x04, 0xDD, 0x00, 0x02, 0x30};
ECMP_3A2.data = {0x01, 0x68, 0x00, 0x00, 0x81, 0x00, 0x08, 0x02};
ECMP_3A3.data = {0x49, 0x49, 0x40, 0x00, 0xDD, 0x08, 0x00, 0x0F};
data_345_content[0] = 0x00; // Allows for contactor closing
@@ -1810,14 +1820,11 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
data_3A2_CRC[15] = 0xF7;
}
- ECMP_31E.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_31E);
ECMP_3A2.data.u8[6] = data_3A2_CRC[counter_100ms];
ECMP_3A3.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_3A3);
ECMP_010.data.u8[0] = data_010_CRC[counter_010];
ECMP_345.data.u8[3] = (uint8_t)((data_345_content[counter_100ms] & 0XF0) | 0x4);
ECMP_345.data.u8[7] = (uint8_t)(0x3 << 4 | (data_345_content[counter_100ms] & 0X0F));
- ECMP_351.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_351);
- ECMP_31D.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_31D);
ECMP_3D0.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_3D0);
transmit_can_frame(&ECMP_382); //PSA Specific VCU (BSIInfo_382)
@@ -1825,37 +1832,30 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&ECMP_3A2); //OBC2_3A2
transmit_can_frame(&ECMP_3A3); //OBC1_3A3
transmit_can_frame(&ECMP_010); //VCU_BCM_Crash
- if (simulateEntireCar) {
- transmit_can_frame(&ECMP_31E);
- transmit_can_frame(&ECMP_383);
- transmit_can_frame(&ECMP_0A6); //Not in all logs
- transmit_can_frame(&ECMP_37F); //Seems to be temperatures of some sort
- transmit_can_frame(&ECMP_372);
- transmit_can_frame(&ECMP_351);
- transmit_can_frame(&ECMP_31D);
- }
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
+ ECMP_31E.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_31E);
+ ECMP_351.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_351);
+ ECMP_31D.data.u8[7] = counter_100ms << 4 | checksum_calc(counter_100ms, ECMP_31D);
+ transmit_can_frame(&ECMP_31E);
+ transmit_can_frame(&ECMP_383);
+ transmit_can_frame(&ECMP_0A6); //Not in all logs
+ transmit_can_frame(&ECMP_37F); //Seems to be temperatures of some sort
+ transmit_can_frame(&ECMP_372);
+ transmit_can_frame(&ECMP_351);
+ transmit_can_frame(&ECMP_31D);
+#endif
}
// Send 500ms periodic CAN Message simulating the car still being attached
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
- if (simulateEntireCar) {
- transmit_can_frame(&ECMP_0AE);
- }
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
+ transmit_can_frame(&ECMP_0AE);
+#endif
}
// Send 1s CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
- if (datalayer.battery.status.bms_status == FAULT) {
- //Make vehicle appear as in idle HV state. Useful for clearing DTCs
- ECMP_486.data.u8[0] = 0x80;
- ECMP_794.data.u8[0] = 0xB8; //Not sure if needed, could be static?
- } else {
- //Normal operation for contactor closing
- ECMP_486.data.u8[0] = 0x00;
- ECMP_794.data.u8[0] = 0x38; //Not sure if needed, could be static?
- }
-
//552 seems to be tracking time in byte 0-3 , distance in km in byte 4-6, temporal reset counter in byte 7
ticks_552 = (ticks_552 + 10);
ECMP_552.data.u8[0] = ((ticks_552 & 0xFF000000) >> 24);
@@ -1865,20 +1865,29 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&ECMP_439); //OBC4
transmit_can_frame(&ECMP_552); //VCU_552 timetracking
- if (simulateEntireCar) {
- transmit_can_frame(&ECMP_486); //Not in all logs
- transmit_can_frame(&ECMP_041); //Not in all logs
- transmit_can_frame(&ECMP_786); //Not in all logs
- transmit_can_frame(&ECMP_591); //Not in all logs
- transmit_can_frame(&ECMP_794); //Not in all logs
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
+ if (datalayer.battery.status.bms_status == FAULT) {
+ //Make vehicle appear as in idle HV state. Useful for clearing DTCs
+ ECMP_486.data.u8[0] = 0x80;
+ ECMP_794.data.u8[0] = 0xB8; //Not sure if needed, could be static?
+ } else {
+ //Normal operation for contactor closing
+ ECMP_486.data.u8[0] = 0x00;
+ ECMP_794.data.u8[0] = 0x38; //Not sure if needed, could be static?
}
+ transmit_can_frame(&ECMP_486); //Not in all logs
+ transmit_can_frame(&ECMP_041); //Not in all logs
+ transmit_can_frame(&ECMP_786); //Not in all logs
+ transmit_can_frame(&ECMP_591); //Not in all logs
+ transmit_can_frame(&ECMP_794); //Not in all logs
+#endif
}
// Send 5s periodic CAN Message simulating the car still being attached
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
- if (simulateEntireCar) {
- transmit_can_frame(&ECMP_55F);
- }
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
+ transmit_can_frame(&ECMP_55F);
+#endif
}
}
diff --git a/Software/src/battery/ECMP-BATTERY.h b/Software/src/battery/ECMP-BATTERY.h
index 9a5ad7e15..37999136d 100644
--- a/Software/src/battery/ECMP-BATTERY.h
+++ b/Software/src/battery/ECMP-BATTERY.h
@@ -3,6 +3,9 @@
#include "CanBattery.h"
#include "ECMP-HTML.h"
+//#define SIMULATE_ENTIRE_VEHICLE_ECMP
+//Enable this to simulate the whole car (useful for when using external diagnostic tools)
+
class EcmpBattery : public CanBattery {
public:
virtual void setup(void);
@@ -23,176 +26,18 @@ class EcmpBattery : public CanBattery {
bool supports_contactor_reset() { return true; }
void reset_contactor() { datalayer_extended.stellantisECMP.UserRequestContactorReset = true; }
+ bool supports_reset_DTC() { return true; }
+ void reset_DTC() { datalayer_extended.stellantisECMP.UserRequestDTCreset = true; }
+
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private:
EcmpHtmlRenderer renderer;
static const int MAX_PACK_VOLTAGE_DV = 4546;
- static const int MIN_PACK_VOLTAGE_DV = 3210;
+ static const int MIN_PACK_VOLTAGE_DV = 3580;
static const int MAX_CELL_DEVIATION_MV = 100;
static const int MAX_CELL_VOLTAGE_MV = 4250;
- static const int MIN_CELL_VOLTAGE_MV = 2700;
- bool simulateEntireCar =
- false; //Set this to true to simulate the whole car (useful for when using external diagnostic tools)
- static const int NOT_SAMPLED_YET = 255;
- static const int COMPLETED_STATE = 0;
- bool battery_RelayOpenRequest = false;
- bool battery_InterlockOpen = false;
- bool MysteryVan = false;
- uint8_t ContactorResetStatemachine = 0;
- uint8_t CollisionResetStatemachine = 0;
- uint8_t IsolationResetStatemachine = 0;
- uint8_t DisableIsoMonitoringStatemachine = 0;
- uint8_t timeSpentDisableIsoMonitoring = 0;
- uint8_t timeSpentContactorReset = 0;
- uint8_t timeSpentCollisionReset = 0;
- uint8_t timeSpentIsolationReset = 0;
- uint8_t countIsolationReset = 0;
- uint8_t counter_10ms = 0;
- uint8_t counter_20ms = 0;
- uint8_t counter_50ms = 0;
- uint8_t counter_100ms = 0;
- uint8_t counter_010 = 0;
- uint32_t ticks_552 = 0x0BC8CFC6;
- uint8_t battery_MainConnectorState = 0;
- int16_t battery_current = 0;
- uint16_t battery_voltage = 370;
- uint16_t battery_soc = 0;
- uint16_t cellvoltages[108];
- uint16_t battery_AllowedMaxChargeCurrent = 0;
- uint16_t battery_AllowedMaxDischargeCurrent = 0;
- uint16_t battery_insulationResistanceKOhm = 0;
- uint8_t battery_insulation_failure_diag = 0;
- int16_t battery_highestTemperature = 0;
- int16_t battery_lowestTemperature = 0;
- uint8_t pid_welding_detection = NOT_SAMPLED_YET;
- uint8_t pid_reason_open = NOT_SAMPLED_YET;
- uint8_t pid_contactor_status = NOT_SAMPLED_YET;
- uint8_t pid_negative_contactor_control = NOT_SAMPLED_YET;
- uint8_t pid_negative_contactor_status = NOT_SAMPLED_YET;
- uint8_t pid_positive_contactor_control = NOT_SAMPLED_YET;
- uint8_t pid_positive_contactor_status = NOT_SAMPLED_YET;
- uint8_t pid_contactor_negative = NOT_SAMPLED_YET;
- uint8_t pid_contactor_positive = NOT_SAMPLED_YET;
- uint8_t pid_precharge_relay_control = NOT_SAMPLED_YET;
- uint8_t pid_precharge_relay_status = NOT_SAMPLED_YET;
- uint8_t pid_recharge_status = NOT_SAMPLED_YET;
- uint8_t pid_delta_temperature = NOT_SAMPLED_YET;
- uint8_t pid_coldest_module = NOT_SAMPLED_YET;
- uint8_t pid_lowest_temperature = NOT_SAMPLED_YET;
- uint8_t pid_average_temperature = NOT_SAMPLED_YET;
- int8_t pid_highest_temperature = NOT_SAMPLED_YET;
- int8_t pid_hottest_module = NOT_SAMPLED_YET;
- uint16_t pid_avg_cell_voltage = NOT_SAMPLED_YET;
- int32_t pid_current = NOT_SAMPLED_YET;
- uint32_t pid_insulation_res_neg = NOT_SAMPLED_YET;
- uint32_t pid_insulation_res_pos = NOT_SAMPLED_YET;
- uint32_t pid_max_current_10s = NOT_SAMPLED_YET;
- uint32_t pid_max_discharge_10s = NOT_SAMPLED_YET;
- uint32_t pid_max_discharge_30s = NOT_SAMPLED_YET;
- uint32_t pid_max_charge_10s = NOT_SAMPLED_YET;
- uint32_t pid_max_charge_30s = NOT_SAMPLED_YET;
- uint32_t pid_energy_capacity = NOT_SAMPLED_YET;
- uint8_t pid_highest_cell_voltage_num = NOT_SAMPLED_YET;
- uint8_t pid_lowest_cell_voltage_num = NOT_SAMPLED_YET;
- uint16_t pid_sum_of_cells = NOT_SAMPLED_YET;
- uint16_t pid_cell_min_capacity = NOT_SAMPLED_YET;
- uint8_t pid_cell_voltage_measurement_status = NOT_SAMPLED_YET;
- uint32_t pid_insulation_res = NOT_SAMPLED_YET;
- uint16_t pid_pack_voltage = NOT_SAMPLED_YET;
- uint16_t pid_high_cell_voltage = NOT_SAMPLED_YET;
- uint16_t pid_low_cell_voltage = NOT_SAMPLED_YET;
- uint8_t pid_battery_energy = NOT_SAMPLED_YET;
- uint32_t pid_crash_counter = NOT_SAMPLED_YET;
- uint8_t pid_wire_crash = NOT_SAMPLED_YET;
- uint8_t pid_CAN_crash = NOT_SAMPLED_YET;
- uint32_t pid_history_data = NOT_SAMPLED_YET;
- uint32_t pid_lowsoc_counter = NOT_SAMPLED_YET;
- uint32_t pid_last_can_failure_detail = NOT_SAMPLED_YET;
- uint32_t pid_hw_version_num = NOT_SAMPLED_YET;
- uint32_t pid_sw_version_num = NOT_SAMPLED_YET;
- uint32_t pid_factory_mode_control = NOT_SAMPLED_YET;
- uint8_t pid_battery_serial[13] = {0};
- uint32_t pid_aux_fuse_state = NOT_SAMPLED_YET;
- uint32_t pid_battery_state = NOT_SAMPLED_YET;
- uint32_t pid_precharge_short_circuit = NOT_SAMPLED_YET;
- uint32_t pid_eservice_plug_state = NOT_SAMPLED_YET;
- uint32_t pid_mainfuse_state = NOT_SAMPLED_YET;
- uint32_t pid_most_critical_fault = NOT_SAMPLED_YET;
- uint32_t pid_current_time = NOT_SAMPLED_YET;
- uint32_t pid_time_sent_by_car = NOT_SAMPLED_YET;
- uint32_t pid_12v = 12345; //Initialized to over 12V to not trigger low 12V event
- uint32_t pid_12v_abnormal = NOT_SAMPLED_YET;
- uint32_t pid_hvil_in_voltage = NOT_SAMPLED_YET;
- uint32_t pid_hvil_out_voltage = NOT_SAMPLED_YET;
- uint32_t pid_hvil_state = NOT_SAMPLED_YET;
- uint32_t pid_bms_state = NOT_SAMPLED_YET;
- uint32_t pid_vehicle_speed = NOT_SAMPLED_YET;
- uint32_t pid_time_spent_over_55c = NOT_SAMPLED_YET;
- uint32_t pid_contactor_closing_counter = NOT_SAMPLED_YET;
- uint32_t pid_date_of_manufacture = NOT_SAMPLED_YET;
- uint16_t pid_SOH_cell_1 = NOT_SAMPLED_YET;
-
- //MysteryVan platform (allcaps to make code easer to co-exist)
- uint16_t SOE_MAX_CURRENT_TEMP = 0;
- uint16_t FRONT_MACHINE_POWER_LIMIT = 0;
- uint16_t REAR_MACHINE_POWER_LIMIT = 0;
- uint16_t EVSE_INSTANT_DC_HV_CURRENT = 0;
- uint8_t EVSE_STATE = 0;
- uint16_t HV_BATT_SOE_HD = 0;
- uint16_t HV_BATT_SOE_MAX = 0;
- uint8_t CHECKSUM_FRAME_314, CHECKSUM_FRAME_3B4, CHECKSUM_FRAME_554, CHECKSUM_FRAME_373, CHECKSUM_FRAME_4F4,
- CHECKSUM_FRAME_414, CHECKSUM_FRAME_353, CHECKSUM_FRAME_474, CHECKSUM_FRAME_4D4 = 0;
- uint16_t HV_STORAGE_MAX_I = 0;
- int8_t BMS_PROBETEMP[7] = {0};
- uint8_t COUNTER_314, COUNTER_554, COUNTER_373, COUNTER_3B4, COUNTER_4F4, COUNTER_414, COUNTER_353, COUNTER_474,
- COUNTER_4D4 = 0;
- uint16_t HV_BATT_PEAK_DISCH_POWER_HD = 0;
- uint16_t HV_BATT_PEAK_CH_POWER_HD = 0;
- uint16_t HV_BATT_NOM_CH_POWER_HD = 0;
- uint16_t MAX_ALLOW_CHRG_CURRENT = 0;
- int16_t HV_BATT_REAL_CURR_HD = 0;
- uint16_t HV_BATT_REAL_VOLT_HD = 0;
- uint8_t TBMU_FAULT_TYPE = 0;
- int16_t HV_BATT_MAX_REAL_CURR = 0;
- bool TBCU_48V_WAKEUP = false;
- bool REQ_CLEAR_DTC_TBMU = false;
- bool HV_BATT_DISCONT_WARNING_OPEN = false;
- uint8_t CONTACTORS_STATE = 0;
- uint16_t HV_BATT_SOC = 0;
- bool CMD_RESET_MIL = 0;
- bool REQ_BLINK_STOP_AND_SERVICE_LAMP = false;
- bool REQ_MIL_LAMP_CONTINOUS = false;
- uint16_t HV_BATT_GENERATED_HEAT_RATE = 0;
- bool HV_BATT_CRASH_MEMORIZED = false;
- bool HV_BATT_COLD_CRANK_ACK = false;
- bool HV_BATT_CHARGE_NEEDED_STATE = false;
- uint8_t HV_BATT_NOM_CH_CURRENT = 0;
- uint16_t HV_BATT_NOM_CH_VOLTAGE = 0;
- uint16_t HV_BATT_REAL_POWER_HD = 0;
- uint16_t MAX_ALLOW_CHRG_POWER = 0;
- uint16_t MAX_ALLOW_DISCHRG_POWER = 0;
- bool ALERT_CELL_POOR_CONSIST, ALERT_OVERCHARGE, ALERT_BATT, ALERT_LOW_SOC, ALERT_HIGH_SOC, ALERT_SOC_JUMP,
- ALERT_TEMP_DIFF, ALERT_HIGH_TEMP, ALERT_OVERVOLTAGE, ALERT_CELL_OVERVOLTAGE, ALERT_CELL_UNDERVOLTAGE = false;
- uint8_t NUMBER_PROBE_TEMP_MAX, NUMBER_PROBE_TEMP_MIN = 0;
- int8_t TEMPERATURE_MINIMUM_C = 0;
- uint8_t CONTACTOR_OPENING_REASON = 0;
- uint8_t NUMBER_OF_TEMPERATURE_SENSORS_IN_BATTERY, NUMBER_OF_CELL_MEASUREMENTS_IN_BATTERY = 0;
- uint16_t HV_BATT_COP_VOLTAGE = 0;
- int16_t HV_BATT_COP_CURRENT = 0;
- uint16_t BMS_DC_RELAY_MES_EVSE_VOLTAGE = 0;
- uint8_t FAST_CHARGE_CONTACTOR_STATE = 0;
- uint8_t BMS_FASTCHARGE_STATUS = 0;
- uint16_t HV_BATT_FC_INSU_MINUS_RES, HV_BATT_FC_INSU_PLUS_RES, HV_BATT_FC_VHL_INSU_PLUS_RES,
- HV_BATT_ONLY_INSU_MINUS_RES = 0;
- uint16_t MIN_ALLOW_DISCHRG_VOLTAGE = 0;
- uint16_t HV_BATT_NOMINAL_DISCH_CURR_HD, HV_BATT_PEAK_DISCH_CURR_HD, HV_BATT_STABLE_DISCH_CURR_HD = 0;
- uint16_t HV_BATT_NOMINAL_CHARGE_CURR_HD, HV_BATT_PEAK_CHARGE_CURR_HD, HV_BATT_STABLE_CHARGE_CURR_HD = 0;
- bool RC01_PERM_SYNTH_TBMU = false;
- uint16_t HV_BATT_STABLE_CHARGE_POWER_HD = 0;
- uint16_t HV_BATT_STABLE_DISCH_POWER_HD = 0;
- uint16_t HV_BATT_NOMINAL_DISCH_POWER_HD = 0;
- uint16_t MAX_ALLOW_DISCHRG_CURRENT = 0;
+ static const int MIN_CELL_VOLTAGE_MV = 3280;
unsigned long previousMillis10 = 0; //- will store last time a 10ms CAN Message was sent
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
@@ -202,89 +47,7 @@ class EcmpBattery : public CanBattery {
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
unsigned long previousMillis5000 = 0; // will store last time a 1000ms CAN Message was sent
-
- static const uint16_t PID_WELD_CHECK = 0xD814;
- static const uint16_t PID_CONT_REASON_OPEN = 0xD812;
- static const uint16_t PID_CONTACTOR_STATUS = 0xD813;
- static const uint16_t PID_NEG_CONT_CONTROL = 0xD44F;
- static const uint16_t PID_NEG_CONT_STATUS = 0xD453;
- static const uint16_t PID_POS_CONT_CONTROL = 0xD44E;
- static const uint16_t PID_POS_CONT_STATUS = 0xD452;
- static const uint16_t PID_CONTACTOR_NEGATIVE = 0xD44C;
- static const uint16_t PID_CONTACTOR_POSITIVE = 0xD44D;
- static const uint16_t PID_PRECHARGE_RELAY_CONTROL = 0xD44B;
- static const uint16_t PID_PRECHARGE_RELAY_STATUS = 0xD451;
- static const uint16_t PID_RECHARGE_STATUS = 0xD864;
- static const uint16_t PID_DELTA_TEMPERATURE = 0xD878;
- static const uint16_t PID_COLDEST_MODULE = 0xD446;
- static const uint16_t PID_LOWEST_TEMPERATURE = 0xD87D;
- static const uint16_t PID_AVERAGE_TEMPERATURE = 0xD877;
- static const uint16_t PID_HIGHEST_TEMPERATURE = 0xD817;
- static const uint16_t PID_HOTTEST_MODULE = 0xD445;
- static const uint16_t PID_AVG_CELL_VOLTAGE = 0xD43D;
- static const uint16_t PID_CURRENT = 0xD816;
- static const uint16_t PID_INSULATION_NEG = 0xD87C;
- static const uint16_t PID_INSULATION_POS = 0xD87B;
- static const uint16_t PID_MAX_CURRENT_10S = 0xD876;
- static const uint16_t PID_MAX_DISCHARGE_10S = 0xD873;
- static const uint16_t PID_MAX_DISCHARGE_30S = 0xD874;
- static const uint16_t PID_MAX_CHARGE_10S = 0xD871;
- static const uint16_t PID_MAX_CHARGE_30S = 0xD872;
- static const uint16_t PID_ENERGY_CAPACITY = 0xD860;
- static const uint16_t PID_HIGH_CELL_NUM = 0xD43B;
- static const uint16_t PID_LOW_CELL_NUM = 0xD43C;
- static const uint16_t PID_SUM_OF_CELLS = 0xD438;
- static const uint16_t PID_CELL_MIN_CAPACITY = 0xD413;
- static const uint16_t PID_CELL_VOLTAGE_MEAS_STATUS = 0xD48A;
- static const uint16_t PID_INSULATION_RES = 0xD47A;
- static const uint16_t PID_PACK_VOLTAGE = 0xD815;
- static const uint16_t PID_HIGH_CELL_VOLTAGE = 0xD870;
- static const uint16_t PID_ALL_CELL_VOLTAGES = 0xD440; //Multi-frame
- static const uint16_t PID_LOW_CELL_VOLTAGE = 0xD86F;
- static const uint16_t PID_BATTERY_ENERGY = 0xD865;
- static const uint16_t PID_CELLBALANCE_STATUS = 0xD46F; //Multi-frame?
- static const uint16_t PID_CELLBALANCE_HWERR_MASK = 0xD470; //Multi-frame
- static const uint16_t PID_CRASH_COUNTER = 0xD42F;
- static const uint16_t PID_WIRE_CRASH = 0xD87F;
- static const uint16_t PID_CAN_CRASH = 0xD48D;
- static const uint16_t PID_HISTORY_DATA = 0xD465;
- static const uint16_t PID_LOWSOC_COUNTER = 0xD492; //Not supported on all batteris
- static const uint16_t PID_LAST_CAN_FAILURE_DETAIL = 0xD89E; //Not supported on all batteris
- static const uint16_t PID_HW_VERSION_NUM = 0xF193; //Not supported on all batteris
- static const uint16_t PID_SW_VERSION_NUM = 0xF195; //Not supported on all batteris
- static const uint16_t PID_FACTORY_MODE_CONTROL = 0xD900;
- static const uint16_t PID_BATTERY_SERIAL = 0xD901;
- static const uint16_t PID_ALL_CELL_SOH = 0xD4B5;
- static const uint16_t PID_AUX_FUSE_STATE = 0xD86C;
- static const uint16_t PID_BATTERY_STATE = 0xD811;
- static const uint16_t PID_PRECHARGE_SHORT_CIRCUIT = 0xD4D8;
- static const uint16_t PID_ESERVICE_PLUG_STATE = 0xD86A;
- static const uint16_t PID_MAINFUSE_STATE = 0xD86B;
- static const uint16_t PID_MOST_CRITICAL_FAULT = 0xD481;
- static const uint16_t PID_CURRENT_TIME = 0xD47F;
- static const uint16_t PID_TIME_SENT_BY_CAR = 0xD4CA;
- static const uint16_t PID_12V = 0xD822;
- static const uint16_t PID_12V_ABNORMAL = 0xD42B;
- static const uint16_t PID_HVIL_IN_VOLTAGE = 0xD46B;
- static const uint16_t PID_HVIL_OUT_VOLTAGE = 0xD46A;
- static const uint16_t PID_HVIL_STATE = 0xD869;
- static const uint16_t PID_BMS_STATE = 0xD45A;
- static const uint16_t PID_VEHICLE_SPEED = 0xD802;
- static const uint16_t PID_TIME_SPENT_OVER_55C = 0xE082;
- static const uint16_t PID_CONTACTOR_CLOSING_COUNTER = 0xD416;
- static const uint16_t PID_DATE_OF_MANUFACTURE = 0xF18B;
-
- uint16_t poll_state = PID_WELD_CHECK;
- uint16_t incoming_poll = 0;
-
CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; //VCU_BCM_Crash 100ms
- static constexpr CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}};
- static constexpr CAN_frame ECMP_0A6 = {
- .FD = false,
- .ext_ID = false,
- .DLC = 2,
- .ID = 0x0A6,
- .data = {0x02, 0x00}}; //Content changes after 12minutes of runtime (not emulated)
CAN_frame ECMP_0F0 = {.FD = false, //VCU2_0F0 (Common) 20ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false,
.DLC = 8,
@@ -295,11 +58,7 @@ class EcmpBattery : public CanBattery {
.DLC = 8,
.ID = 0x0F2,
.data = {0x7D, 0x00, 0x4E, 0x20, 0x00, 0x00, 0x60, 0x0D}};
- static constexpr CAN_frame ECMP_0AE = {.FD = false,
- .ext_ID = false,
- .DLC = 5,
- .ID = 0x0AE,
- .data = {0x04, 0x77, 0x7A, 0x5E, 0xDF}};
+
CAN_frame ECMP_110 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, // NOTE. Changes on BMS state
.DLC = 8,
@@ -341,16 +100,6 @@ class EcmpBattery : public CanBattery {
.DLC = 8, //Contains SEV main state, position of the BSI shunt park, ACC status
.ID = 0x27A, // electric network state, powetrain status, Wakeups, diagmux, APC activation
.data = {0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame ECMP_31E = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, // NOTE. Changes on BMS state
- .DLC = 8,
- .ID = 0x31E,
- .data = {0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08}};
- CAN_frame ECMP_31D = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, //Same content always, fully static
- .DLC = 8,
- .ID = 0x31D,
- .data = {0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42}};
CAN_frame ECMP_3D0 = {.FD = false, //Not in logs, but makes speed go to 0km/h in diag tool when we send this
.ext_ID = false, //Only sent in idle state
.DLC = 8,
@@ -361,22 +110,6 @@ class EcmpBattery : public CanBattery {
.DLC = 8,
.ID = 0x345,
.data = {0x45, 0x57, 0x00, 0x04, 0x00, 0x00, 0x06, 0x31}};
- CAN_frame ECMP_351 = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, // NOTE. Changes on BMS state
- .DLC = 8,
- .ID = 0x351,
- .data = {0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0E}};
- CAN_frame ECMP_372 = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, // NOTE. Changes on BMS state
- .DLC = 8,
- .ID = 0x372,
- .data = {0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // NOTE. Changes on BMS state
- static constexpr CAN_frame ECMP_37F = {
- .FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, // Seems to be a bunch of temperature measurements? Static for now
- .DLC = 8,
- .ID = 0x37F,
- .data = {0x45, 0x49, 0x51, 0x45, 0x45, 0x00, 0x45, 0x45}};
static constexpr CAN_frame ECMP_382 = {
//BSIInfo_382 (VCU) PSA specific 100ms periodic (Perfectly emulated in Battery-Emulator)
.FD = false, //Same content always, fully static
@@ -384,11 +117,6 @@ class EcmpBattery : public CanBattery {
.DLC = 8,
.ID = 0x382, //Frame1 has rollerbenchmode request, frame2 has generic powertrain cycle sync status
.data = {0x02, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame ECMP_383 = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, // NOTE. Changes on BMS state
- .DLC = 8,
- .ID = 0x383,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ECMP_3A2 = {.FD = false, //OBC2_3A2 100ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, // NOTE. Changes on BMS state
.DLC = 8,
@@ -404,36 +132,13 @@ class EcmpBattery : public CanBattery {
.DLC = 8,
.ID = 0x439,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame ECMP_486 = {.FD = false, //??? 1s periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, // NOTE. Changes on BMS state
- .DLC = 8,
- .ID = 0x486,
- .data = {0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
+
CAN_frame ECMP_552 = {.FD = false, //VCU_552 1s periodic (Perfectly handled in Battery-Emulator)
.ext_ID = false,
.DLC = 8, //552 seems to be tracking time in byte 0-3
.ID = 0x552, // distance in km in byte 4-6, temporal reset counter in byte 7
.data = {0x00, 0x02, 0x95, 0x6D, 0x00, 0xD7, 0xB5, 0xFE}};
- static constexpr CAN_frame ECMP_55F = {.FD = false, //5s periodic (Perfectly emulated in Battery-Emulator)
- .ext_ID = false, //Same content always, fully static
- .DLC = 1,
- .ID = 0x55F,
- .data = {0x82}};
- static constexpr CAN_frame ECMP_591 = {.FD = false, //1s periodic
- .ext_ID = false, //Always static in HV mode
- .DLC = 8,
- .ID = 0x591,
- .data = {0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
- static constexpr CAN_frame ECMP_786 = {.FD = false, //1s periodic
- .ext_ID = false, //Always static in HV mode
- .DLC = 8,
- .ID = 0x786,
- .data = {0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
- CAN_frame ECMP_794 = {.FD = false, //Unsure who sends this. Could it be BMU?
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x794,
- .data = {0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // NOTE. Changes on BMS state
+
CAN_frame ECMP_POLL = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6B4, .data = {0x03, 0x22, 0xD8, 0x66}};
static constexpr CAN_frame ECMP_ACK = {.FD = false, //Ack frame
.ext_ID = false,
@@ -496,6 +201,318 @@ class EcmpBattery : public CanBattery {
.DLC = 3,
.ID = 0x6B4,
.data = {0x02, 0x3E, 0x00}};
+ static constexpr CAN_frame ECMP_CLEAR_DTC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 5,
+ .ID = 0x6B4,
+ .data = {0x04, 0x14, 0xFF, 0xFF, 0xFF}};
+
+#ifdef SIMULATE_ENTIRE_VEHICLE_ECMP
+ static constexpr CAN_frame ECMP_0AE = {.FD = false,
+ .ext_ID = false,
+ .DLC = 5,
+ .ID = 0x0AE,
+ .data = {0x04, 0x77, 0x7A, 0x5E, 0xDF}};
+ static constexpr CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}};
+ CAN_frame ECMP_486 = {.FD = false, //??? 1s periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, // NOTE. Changes on BMS state
+ .DLC = 8,
+ .ID = 0x486,
+ .data = {0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
+ static constexpr CAN_frame ECMP_786 = {.FD = false, //1s periodic
+ .ext_ID = false, //Always static in HV mode
+ .DLC = 8,
+ .ID = 0x786,
+ .data = {0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
+ static constexpr CAN_frame ECMP_591 = {.FD = false, //1s periodic
+ .ext_ID = false, //Always static in HV mode
+ .DLC = 8,
+ .ID = 0x591,
+ .data = {0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
+ CAN_frame ECMP_794 = {.FD = false, //Unsure who sends this. Could it be BMU?
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x794,
+ .data = {0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // NOTE. Changes on BMS state
+ static constexpr CAN_frame ECMP_55F = {.FD = false, //5s periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, //Same content always, fully static
+ .DLC = 1,
+ .ID = 0x55F,
+ .data = {0x82}};
+ CAN_frame ECMP_31D = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, //Same content always, fully static
+ .DLC = 8,
+ .ID = 0x31D,
+ .data = {0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42}};
+ CAN_frame ECMP_351 = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, // NOTE. Changes on BMS state
+ .DLC = 8,
+ .ID = 0x351,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0E}};
+ CAN_frame ECMP_372 = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, // NOTE. Changes on BMS state
+ .DLC = 8,
+ .ID = 0x372,
+ .data = {0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // NOTE. Changes on BMS state
+ static constexpr CAN_frame ECMP_37F = {
+ .FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, // Seems to be a bunch of temperature measurements? Static for now
+ .DLC = 8,
+ .ID = 0x37F,
+ .data = {0x45, 0x49, 0x51, 0x45, 0x45, 0x00, 0x45, 0x45}};
+ static constexpr CAN_frame ECMP_0A6 = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 2,
+ .ID = 0x0A6,
+ .data = {0x02, 0x00}}; //Content changes after 12minutes of runtime (not emulated)
+ CAN_frame ECMP_383 = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, // NOTE. Changes on BMS state
+ .DLC = 8,
+ .ID = 0x383,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame ECMP_31E = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
+ .ext_ID = false, // NOTE. Changes on BMS state
+ .DLC = 8,
+ .ID = 0x31E,
+ .data = {0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08}};
+#endif
+
+ uint32_t ticks_552 = 0x0BC8CFC6;
+ uint32_t pid_insulation_res_neg = NOT_SAMPLED_YET;
+ uint32_t pid_insulation_res_pos = NOT_SAMPLED_YET;
+ uint32_t pid_max_current_10s = NOT_SAMPLED_YET;
+ uint32_t pid_max_discharge_10s = NOT_SAMPLED_YET;
+ uint32_t pid_max_discharge_30s = NOT_SAMPLED_YET;
+ uint32_t pid_max_charge_10s = NOT_SAMPLED_YET;
+ uint32_t pid_max_charge_30s = NOT_SAMPLED_YET;
+ uint32_t pid_energy_capacity = NOT_SAMPLED_YET;
+ uint32_t pid_insulation_res = NOT_SAMPLED_YET;
+ uint32_t pid_crash_counter = NOT_SAMPLED_YET;
+ uint32_t pid_history_data = NOT_SAMPLED_YET;
+ uint32_t pid_last_can_failure_detail = NOT_SAMPLED_YET;
+ uint32_t pid_hw_version_num = NOT_SAMPLED_YET;
+ uint32_t pid_sw_version_num = NOT_SAMPLED_YET;
+ uint32_t pid_vehicle_speed = NOT_SAMPLED_YET;
+ uint32_t pid_time_spent_over_55c = NOT_SAMPLED_YET;
+ uint32_t pid_contactor_closing_counter = NOT_SAMPLED_YET;
+ uint32_t pid_date_of_manufacture = NOT_SAMPLED_YET;
+ uint32_t pid_current_time = NOT_SAMPLED_YET;
+ uint32_t pid_time_sent_by_car = NOT_SAMPLED_YET;
+
+ int32_t pid_current = NOT_SAMPLED_YET;
+
+ static const uint8_t NOT_SAMPLED_YET = 255;
+ static const uint8_t COMPLETED_STATE = 0;
+ static const uint16_t PID_WELD_CHECK = 0xD814;
+ static const uint16_t PID_CONT_REASON_OPEN = 0xD812;
+ static const uint16_t PID_CONTACTOR_STATUS = 0xD813;
+ static const uint16_t PID_NEG_CONT_CONTROL = 0xD44F;
+ static const uint16_t PID_NEG_CONT_STATUS = 0xD453;
+ static const uint16_t PID_POS_CONT_CONTROL = 0xD44E;
+ static const uint16_t PID_POS_CONT_STATUS = 0xD452;
+ static const uint16_t PID_CONTACTOR_NEGATIVE = 0xD44C;
+ static const uint16_t PID_CONTACTOR_POSITIVE = 0xD44D;
+ static const uint16_t PID_PRECHARGE_RELAY_CONTROL = 0xD44B;
+ static const uint16_t PID_PRECHARGE_RELAY_STATUS = 0xD451;
+ static const uint16_t PID_RECHARGE_STATUS = 0xD864;
+ static const uint16_t PID_DELTA_TEMPERATURE = 0xD878;
+ static const uint16_t PID_COLDEST_MODULE = 0xD446;
+ static const uint16_t PID_LOWEST_TEMPERATURE = 0xD87D;
+ static const uint16_t PID_AVERAGE_TEMPERATURE = 0xD877;
+ static const uint16_t PID_HIGHEST_TEMPERATURE = 0xD817;
+ static const uint16_t PID_HOTTEST_MODULE = 0xD445;
+ static const uint16_t PID_AVG_CELL_VOLTAGE = 0xD43D;
+ static const uint16_t PID_CURRENT = 0xD816;
+ static const uint16_t PID_INSULATION_NEG = 0xD87C;
+ static const uint16_t PID_INSULATION_POS = 0xD87B;
+ static const uint16_t PID_MAX_CURRENT_10S = 0xD876;
+ static const uint16_t PID_MAX_DISCHARGE_10S = 0xD873;
+ static const uint16_t PID_MAX_DISCHARGE_30S = 0xD874;
+ static const uint16_t PID_MAX_CHARGE_10S = 0xD871;
+ static const uint16_t PID_MAX_CHARGE_30S = 0xD872;
+ static const uint16_t PID_ENERGY_CAPACITY = 0xD860;
+ static const uint16_t PID_HIGH_CELL_NUM = 0xD43B;
+ static const uint16_t PID_LOW_CELL_NUM = 0xD43C;
+ static const uint16_t PID_SUM_OF_CELLS = 0xD438;
+ static const uint16_t PID_CELL_MIN_CAPACITY = 0xD413;
+ static const uint16_t PID_CELL_VOLTAGE_MEAS_STATUS = 0xD48A;
+ static const uint16_t PID_INSULATION_RES = 0xD47A;
+ static const uint16_t PID_PACK_VOLTAGE = 0xD815;
+ static const uint16_t PID_HIGH_CELL_VOLTAGE = 0xD870;
+ static const uint16_t PID_ALL_CELL_VOLTAGES = 0xD440; //Multi-frame
+ static const uint16_t PID_LOW_CELL_VOLTAGE = 0xD86F;
+ static const uint16_t PID_BATTERY_ENERGY = 0xD865;
+ static const uint16_t PID_CELLBALANCE_STATUS = 0xD46F; //Multi-frame?
+ static const uint16_t PID_CELLBALANCE_HWERR_MASK = 0xD470; //Multi-frame
+ static const uint16_t PID_CRASH_COUNTER = 0xD42F;
+ static const uint16_t PID_WIRE_CRASH = 0xD87F;
+ static const uint16_t PID_CAN_CRASH = 0xD48D;
+ static const uint16_t PID_HISTORY_DATA = 0xD465;
+ static const uint16_t PID_LOWSOC_COUNTER = 0xD492; //Not supported on all batteris
+ static const uint16_t PID_LAST_CAN_FAILURE_DETAIL = 0xD89E; //Not supported on all batteris
+ static const uint16_t PID_HW_VERSION_NUM = 0xF193; //Not supported on all batteris
+ static const uint16_t PID_SW_VERSION_NUM = 0xF195; //Not supported on all batteris
+ static const uint16_t PID_FACTORY_MODE_CONTROL = 0xD900;
+ static const uint16_t PID_BATTERY_SERIAL = 0xD901;
+ static const uint16_t PID_ALL_CELL_SOH = 0xD4B5;
+ static const uint16_t PID_AUX_FUSE_STATE = 0xD86C;
+ static const uint16_t PID_BATTERY_STATE = 0xD811;
+ static const uint16_t PID_PRECHARGE_SHORT_CIRCUIT = 0xD4D8;
+ static const uint16_t PID_ESERVICE_PLUG_STATE = 0xD86A;
+ static const uint16_t PID_MAINFUSE_STATE = 0xD86B;
+ static const uint16_t PID_MOST_CRITICAL_FAULT = 0xD481;
+ static const uint16_t PID_CURRENT_TIME = 0xD47F;
+ static const uint16_t PID_TIME_SENT_BY_CAR = 0xD4CA;
+ static const uint16_t PID_12V = 0xD822;
+ static const uint16_t PID_12V_ABNORMAL = 0xD42B;
+ static const uint16_t PID_HVIL_IN_VOLTAGE = 0xD46B;
+ static const uint16_t PID_HVIL_OUT_VOLTAGE = 0xD46A;
+ static const uint16_t PID_HVIL_STATE = 0xD869;
+ static const uint16_t PID_BMS_STATE = 0xD45A;
+ static const uint16_t PID_VEHICLE_SPEED = 0xD802;
+ static const uint16_t PID_TIME_SPENT_OVER_55C = 0xE082;
+ static const uint16_t PID_CONTACTOR_CLOSING_COUNTER = 0xD416;
+ static const uint16_t PID_DATE_OF_MANUFACTURE = 0xF18B;
+ uint16_t battery_voltage = 370;
+ uint16_t battery_soc = 0;
+ uint16_t cellvoltages[108];
+ uint16_t battery_AllowedMaxChargeCurrent = 0;
+ uint16_t battery_AllowedMaxDischargeCurrent = 0;
+ uint16_t battery_insulationResistanceKOhm = 0;
+ uint16_t pid_lowsoc_counter = NOT_SAMPLED_YET;
+ uint16_t poll_state = PID_WELD_CHECK;
+ uint16_t incoming_poll = 0;
+ uint16_t pid_hvil_out_voltage = NOT_SAMPLED_YET;
+ uint16_t pid_most_critical_fault = NOT_SAMPLED_YET;
+ uint16_t pid_avg_cell_voltage = NOT_SAMPLED_YET;
+ uint16_t pid_sum_of_cells = NOT_SAMPLED_YET;
+ uint16_t pid_cell_min_capacity = NOT_SAMPLED_YET;
+ uint16_t pid_pack_voltage = NOT_SAMPLED_YET;
+ uint16_t pid_high_cell_voltage = NOT_SAMPLED_YET;
+ uint16_t pid_low_cell_voltage = NOT_SAMPLED_YET;
+ uint16_t pid_12v = 12345; //Initialized to over 12V to not trigger low 12V event
+ uint16_t pid_hvil_in_voltage = NOT_SAMPLED_YET;
+ uint16_t pid_SOH_cell_1 = NOT_SAMPLED_YET;
+ uint16_t SOE_MAX_CURRENT_TEMP = 0;
+ uint16_t FRONT_MACHINE_POWER_LIMIT = 0;
+ uint16_t REAR_MACHINE_POWER_LIMIT = 0;
+ uint16_t EVSE_INSTANT_DC_HV_CURRENT = 0;
+ uint16_t HV_BATT_SOE_HD = 0;
+ uint16_t HV_BATT_SOE_MAX = 0;
+ uint16_t HV_BATT_STABLE_CHARGE_POWER_HD = 0;
+ uint16_t HV_BATT_STABLE_DISCH_POWER_HD = 0;
+ uint16_t HV_BATT_NOMINAL_DISCH_POWER_HD = 0;
+ uint16_t MAX_ALLOW_DISCHRG_CURRENT = 0;
+ uint16_t HV_STORAGE_MAX_I = 0;
+ uint16_t HV_BATT_PEAK_DISCH_POWER_HD = 0;
+ uint16_t HV_BATT_PEAK_CH_POWER_HD = 0;
+ uint16_t HV_BATT_NOM_CH_POWER_HD = 0;
+ uint16_t MAX_ALLOW_CHRG_CURRENT = 0;
+ uint16_t HV_BATT_FC_INSU_MINUS_RES, HV_BATT_FC_INSU_PLUS_RES, HV_BATT_FC_VHL_INSU_PLUS_RES,
+ HV_BATT_ONLY_INSU_MINUS_RES = 0;
+ uint16_t MIN_ALLOW_DISCHRG_VOLTAGE = 0;
+ uint16_t HV_BATT_NOMINAL_DISCH_CURR_HD, HV_BATT_PEAK_DISCH_CURR_HD, HV_BATT_STABLE_DISCH_CURR_HD = 0;
+ uint16_t HV_BATT_NOMINAL_CHARGE_CURR_HD, HV_BATT_PEAK_CHARGE_CURR_HD, HV_BATT_STABLE_CHARGE_CURR_HD = 0;
+ uint16_t HV_BATT_REAL_VOLT_HD = 0;
+ uint16_t HV_BATT_NOM_CH_VOLTAGE = 0;
+ uint16_t HV_BATT_REAL_POWER_HD = 0;
+ uint16_t MAX_ALLOW_CHRG_POWER = 0;
+ uint16_t MAX_ALLOW_DISCHRG_POWER = 0;
+ uint16_t HV_BATT_SOC = 0;
+ uint16_t HV_BATT_GENERATED_HEAT_RATE = 0;
+ uint16_t BMS_DC_RELAY_MES_EVSE_VOLTAGE = 0;
+ uint16_t HV_BATT_COP_VOLTAGE = 0;
+
+ int16_t HV_BATT_MAX_REAL_CURR = 0;
+ int16_t HV_BATT_REAL_CURR_HD = 0;
+ int16_t battery_current = 0;
+ int16_t battery_highestTemperature = 0;
+ int16_t battery_lowestTemperature = 0;
+ int16_t HV_BATT_COP_CURRENT = 0;
+
+ uint8_t ContactorResetStatemachine = 0;
+ uint8_t CollisionResetStatemachine = 0;
+ uint8_t IsolationResetStatemachine = 0;
+ uint8_t DisableIsoMonitoringStatemachine = 0;
+ uint8_t timeSpentDisableIsoMonitoring = 0;
+ uint8_t timeSpentContactorReset = 0;
+ uint8_t timeSpentCollisionReset = 0;
+ uint8_t timeSpentIsolationReset = 0;
+ uint8_t countIsolationReset = 0;
+ uint8_t counter_10ms = 0;
+ uint8_t counter_20ms = 0;
+ uint8_t counter_50ms = 0;
+ uint8_t counter_100ms = 0;
+ uint8_t counter_010 = 0;
+ uint8_t battery_MainConnectorState = 0;
+ uint8_t battery_insulation_failure_diag = 0;
+ uint8_t pid_welding_detection = NOT_SAMPLED_YET;
+ uint8_t pid_reason_open = NOT_SAMPLED_YET;
+ uint8_t pid_contactor_status = NOT_SAMPLED_YET;
+ uint8_t pid_negative_contactor_control = NOT_SAMPLED_YET;
+ uint8_t pid_negative_contactor_status = NOT_SAMPLED_YET;
+ uint8_t pid_positive_contactor_control = NOT_SAMPLED_YET;
+ uint8_t pid_positive_contactor_status = NOT_SAMPLED_YET;
+ uint8_t pid_contactor_negative = NOT_SAMPLED_YET;
+ uint8_t pid_contactor_positive = NOT_SAMPLED_YET;
+ uint8_t pid_precharge_relay_control = NOT_SAMPLED_YET;
+ uint8_t pid_precharge_relay_status = NOT_SAMPLED_YET;
+ uint8_t pid_recharge_status = NOT_SAMPLED_YET;
+ uint8_t pid_coldest_module = NOT_SAMPLED_YET;
+ uint8_t pid_hottest_module = NOT_SAMPLED_YET;
+ uint8_t pid_highest_cell_voltage_num = NOT_SAMPLED_YET;
+ uint8_t pid_lowest_cell_voltage_num = NOT_SAMPLED_YET;
+ uint8_t pid_cell_voltage_measurement_status = NOT_SAMPLED_YET;
+ uint8_t pid_battery_energy = NOT_SAMPLED_YET;
+ uint8_t pid_12v_abnormal = NOT_SAMPLED_YET;
+ uint8_t pid_factory_mode_control = NOT_SAMPLED_YET;
+ uint8_t pid_battery_serial[14] = {0};
+ uint8_t pid_aux_fuse_state = NOT_SAMPLED_YET;
+ uint8_t pid_battery_state = NOT_SAMPLED_YET;
+ uint8_t pid_precharge_short_circuit = NOT_SAMPLED_YET;
+ uint8_t pid_eservice_plug_state = NOT_SAMPLED_YET;
+ uint8_t pid_mainfuse_state = NOT_SAMPLED_YET;
+ uint8_t pid_hvil_state = NOT_SAMPLED_YET;
+ uint8_t pid_bms_state = NOT_SAMPLED_YET;
+ uint8_t pid_wire_crash = NOT_SAMPLED_YET;
+ uint8_t pid_CAN_crash = NOT_SAMPLED_YET;
+ uint8_t EVSE_STATE = 0;
+ uint8_t CHECKSUM_FRAME_314, CHECKSUM_FRAME_3B4, CHECKSUM_FRAME_554, CHECKSUM_FRAME_373, CHECKSUM_FRAME_4F4,
+ CHECKSUM_FRAME_414, CHECKSUM_FRAME_353, CHECKSUM_FRAME_474, CHECKSUM_FRAME_4D4 = 0;
+ uint8_t FAST_CHARGE_CONTACTOR_STATE = 0;
+ uint8_t BMS_FASTCHARGE_STATUS = 0;
+ uint8_t HV_BATT_NOM_CH_CURRENT = 0;
+ uint8_t TBMU_FAULT_TYPE = 0;
+ uint8_t CONTACTORS_STATE = 0;
+ uint8_t NUMBER_PROBE_TEMP_MAX, NUMBER_PROBE_TEMP_MIN = 0;
+ uint8_t NUMBER_OF_TEMPERATURE_SENSORS_IN_BATTERY, NUMBER_OF_CELL_MEASUREMENTS_IN_BATTERY = 0;
+ uint8_t CONTACTOR_OPENING_REASON = 0;
+
+ int8_t pid_delta_temperature = 127;
+ int8_t pid_lowest_temperature = 127;
+ int8_t pid_average_temperature = 127;
+ int8_t pid_highest_temperature = 127;
+ int8_t BMS_PROBETEMP[7] = {0};
+ int8_t TEMPERATURE_MINIMUM_C = 0;
+
+ bool HighPrecisionCurrentSampling = 1;
+ bool CMD_RESET_MIL = false;
+ bool REQ_BLINK_STOP_AND_SERVICE_LAMP = false;
+ bool REQ_MIL_LAMP_CONTINOUS = false;
+ bool HV_BATT_CRASH_MEMORIZED = false;
+ bool HV_BATT_COLD_CRANK_ACK = false;
+ bool HV_BATT_CHARGE_NEEDED_STATE = false;
+ bool RC01_PERM_SYNTH_TBMU = false;
+ bool battery_RelayOpenRequest = false;
+ bool battery_InterlockOpen = false;
+ bool MysteryVan = false;
+ bool TBCU_48V_WAKEUP = false;
+ bool REQ_CLEAR_DTC_TBMU = false;
+ bool HV_BATT_DISCONT_WARNING_OPEN = false;
+ bool ALERT_CELL_POOR_CONSIST, ALERT_OVERCHARGE, ALERT_BATT, ALERT_LOW_SOC, ALERT_HIGH_SOC, ALERT_SOC_JUMP,
+ ALERT_TEMP_DIFF, ALERT_HIGH_TEMP, ALERT_OVERVOLTAGE, ALERT_CELL_OVERVOLTAGE, ALERT_CELL_UNDERVOLTAGE = false;
+
uint8_t data_010_CRC[8] = {0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0xF0, 0xD2};
uint8_t data_3A2_CRC[16] = {0x0C, 0x1B, 0x2A, 0x39, 0x48, 0x57,
0x66, 0x75, 0x84, 0x93, 0xA2, 0xB1}; // NOTE. Changes on BMS state
diff --git a/Software/src/battery/ECMP-HTML.h b/Software/src/battery/ECMP-HTML.h
index 0b177febf..ef1e5a1de 100644
--- a/Software/src/battery/ECMP-HTML.h
+++ b/Software/src/battery/ECMP-HTML.h
@@ -103,22 +103,22 @@ class EcmpHtmlRenderer : public BatteryHtmlRenderer {
: String(datalayer_extended.stellantisECMP.pid_recharge_status)) +
"";
content += "Delta temperature: " +
- (datalayer_extended.stellantisECMP.pid_delta_temperature == 255
+ (datalayer_extended.stellantisECMP.pid_delta_temperature == 127
? "N/A"
: String(datalayer_extended.stellantisECMP.pid_delta_temperature)) +
"°C ";
content += "Lowest temperature: " +
- (datalayer_extended.stellantisECMP.pid_lowest_temperature == 255
+ (datalayer_extended.stellantisECMP.pid_lowest_temperature == 127
? "N/A"
: String(datalayer_extended.stellantisECMP.pid_lowest_temperature)) +
"°C ";
content += "Average temperature: " +
- (datalayer_extended.stellantisECMP.pid_average_temperature == 255
+ (datalayer_extended.stellantisECMP.pid_average_temperature == 127
? "N/A"
: String(datalayer_extended.stellantisECMP.pid_average_temperature)) +
"°C ";
content += "Highest temperature: " +
- (datalayer_extended.stellantisECMP.pid_highest_temperature == 255
+ (datalayer_extended.stellantisECMP.pid_highest_temperature == 127
? "N/A"
: String(datalayer_extended.stellantisECMP.pid_highest_temperature)) +
"°C ";
@@ -509,6 +509,7 @@ class EcmpHtmlRenderer : public BatteryHtmlRenderer {
} else {
content += "No";
}
+ content += "Remember to press Open Contactors from main menu before running the dianostic commands below: ";
return content;
}
};
diff --git a/Software/src/battery/FORD-MACH-E-BATTERY.cpp b/Software/src/battery/FORD-MACH-E-BATTERY.cpp
new file mode 100644
index 000000000..34b2ab9c5
--- /dev/null
+++ b/Software/src/battery/FORD-MACH-E-BATTERY.cpp
@@ -0,0 +1,432 @@
+#include "FORD-MACH-E-BATTERY.h"
+#include
+#include "../datalayer/datalayer.h"
+#include "../devboard/utils/events.h"
+#include "../devboard/utils/logging.h"
+
+void FordMachEBattery::update_values() {
+
+ datalayer.battery.status.real_soc = battery_soc;
+
+ //datalayer.battery.status.soh_pptt; //TODO: Locate
+
+ datalayer.battery.status.voltage_dV = battery_voltage * 10;
+
+ datalayer.battery.status.current_dA = -(battery_current * 2);
+
+ datalayer.battery.status.remaining_capacity_Wh = static_cast(
+ (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
+
+ datalayer.battery.status.max_discharge_power_W =
+ datalayer.battery.status.override_discharge_power_W; //TODO, fix when v alue is found
+
+ //We have not found allowed charge power yet. Estimate it for now absed on UI setting. TODO. remove this once found
+ if (battery_soc > 9900) {
+ datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W;
+ } else if ((battery_soc / 10) >
+ RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
+ datalayer.battery.status.max_charge_power_W =
+ RAMPDOWNPOWERALLOWED * (1 - ((battery_soc / 10) - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC));
+ //If the cellvoltages start to reach overvoltage, only allow a small amount of power in
+ if (maximum_cellvoltage_mV > (MAX_CELL_VOLTAGE_MV - FLOAT_START_MV)) {
+ datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W;
+ }
+ } else { // No limits, max charging power allowed
+ datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
+ }
+
+ maximum_cellvoltage_mV = datalayer.battery.status.cell_voltages_mV[0];
+ minimum_cellvoltage_mV = datalayer.battery.status.cell_voltages_mV[0];
+
+ // Loop through the array to find min and max cellvoltages, ignoring 0 values
+ for (uint8_t i = 0; i < datalayer.battery.info.number_of_cells; i++) {
+ if (datalayer.battery.status.cell_voltages_mV[i] > 1000) { // Ignore unavailable values
+ if (datalayer.battery.status.cell_voltages_mV[i] < minimum_cellvoltage_mV) {
+ minimum_cellvoltage_mV = datalayer.battery.status.cell_voltages_mV[i];
+ }
+ if (datalayer.battery.status.cell_voltages_mV[i] > maximum_cellvoltage_mV) {
+ maximum_cellvoltage_mV = datalayer.battery.status.cell_voltages_mV[i];
+ }
+ }
+ }
+
+ if (maximum_cellvoltage_mV > 0) {
+ datalayer.battery.status.cell_max_voltage_mV = maximum_cellvoltage_mV;
+ }
+
+ if (minimum_cellvoltage_mV > 0) {
+ datalayer.battery.status.cell_min_voltage_mV = minimum_cellvoltage_mV;
+ }
+
+ // Initialize highest and lowest to the first element
+ maximum_temperature = cell_temperature[0];
+ minimum_temperature = cell_temperature[0];
+
+ // Iterate through the array to find the highest and lowest values
+ for (uint8_t i = 1; i < 6; ++i) {
+ if (cell_temperature[i] > maximum_temperature) {
+ maximum_temperature = cell_temperature[i];
+ }
+ if (cell_temperature[i] < minimum_temperature) {
+ minimum_temperature = cell_temperature[i];
+ }
+ }
+ datalayer.battery.status.temperature_min_dC = minimum_temperature * 10;
+
+ datalayer.battery.status.temperature_max_dC = maximum_temperature * 10;
+
+ // Loop through the array to find how many cellvoltages we have
+ // We should get either 90/94/96/108
+ for (uint8_t i = 0; i < MAX_AMOUNT_CELLS; i++) {
+ if (datalayer.battery.status.cell_voltages_mV[i] > 1000) { //If we have a valid cellvoltage measurement
+ datalayer.battery.info.number_of_cells = i + 1; //Set amount of cells to that amount
+ }
+ }
+
+ if (datalayer.battery.info.number_of_cells == 90) { //NMC
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_90S_WH;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV;
+ } else if (datalayer.battery.info.number_of_cells == 96) { //75.7 kWh NMC LGES
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_96S_WH;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV;
+ } else if (datalayer.battery.info.number_of_cells == 94) { //98.8 kWh NMC LGES
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_94S_WH;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94S_DV;
+ } else if (datalayer.battery.info.number_of_cells == 108) { //78.2 kWh LFP CATL
+ datalayer.battery.info.chemistry = LFP;
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_108S_WH;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_108S_DV;
+ datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_LFP_MV;
+ }
+
+ // Check vehicle specific safeties
+ if (polled_12V < 11800) {
+ set_event(EVENT_12V_LOW, 0);
+ }
+}
+
+void FordMachEBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ switch (rx_frame.ID) { //These frames are transmitted by the battery
+ case 0x07a: //10ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ battery_voltage = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]) / 2;
+ break;
+ case 0x07b: //10ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ battery_current = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1];
+ break;
+ case 0x073: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x24b: //100ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x24c: //100ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ battery_soc = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
+ break;
+ case 0x24d: //100ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x24e: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x24f: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x2e4: //100ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x444: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x458: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x47d: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ battery_soh = rx_frame.data.u8[1]; //Not correct!
+ break;
+ case 0x490: //1s Cellvoltages
+ case 0x491:
+ case 0x492:
+ case 0x493:
+ case 0x494:
+ case 0x495:
+ case 0x496:
+ case 0x497:
+ case 0x498:
+ case 0x499:
+ case 0x49a:
+ case 0x49b:
+ case 0x49c:
+ case 0x49d:
+ case 0x49e:
+ case 0x49f:
+ case 0x4a0:
+ case 0x4a1:
+ case 0x4a2: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+
+ const uint8_t start_index = (rx_frame.ID - 0x490) * 5;
+
+ // Process 5 cells per message
+ for (uint8_t i = 0; i < 5; i++) {
+ uint16_t voltage = 0;
+
+ if (i == 0) {
+ voltage = (rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4);
+ } else if (i == 1) {
+ voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2];
+ } else if (i == 2) {
+ voltage = (rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4);
+ } else if (i == 3) {
+ voltage = ((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5];
+ } else if (i == 4) {
+ voltage = (rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[7] >> 4);
+ }
+
+ if (voltage == 0xFFE) {
+ //Voltage measurement is unavailable (Happens on 90/94S battery on last message)
+ } else {
+ //Voltage measurement is available
+ datalayer.battery.status.cell_voltages_mV[start_index + i] = voltage + 1000;
+ }
+ }
+ break;
+ }
+ case 0x4a3: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if ((rx_frame.data.u8[0] == 0xFF) && (rx_frame.data.u8[1] == 0xE0)) {
+ //Do nothing, no voltage here (for instance on 90/94S battery)
+ } else { //96th voltage resides in this frame
+ datalayer.battery.status.cell_voltages_mV[95] =
+ ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4)) + 1000;
+ }
+
+ //Celltemperatures
+ cell_temperature[0] = ((rx_frame.data.u8[2] - 40) / 2);
+ cell_temperature[1] = ((rx_frame.data.u8[3] - 40) / 2);
+ cell_temperature[2] = ((rx_frame.data.u8[4] - 40) / 2);
+ cell_temperature[3] = ((rx_frame.data.u8[5] - 40) / 2);
+ cell_temperature[4] = ((rx_frame.data.u8[6] - 40) / 2);
+ cell_temperature[5] = ((rx_frame.data.u8[7] - 40) / 2);
+ break;
+ case 0x4a4: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //Some batteries have more celltemps here, (LFP 90S), but not the 108S
+ //celltemp6 = rx_frame.data.u8[0]
+ //celltemp7 = rx_frame.data.u8[1]
+ //celltemp8 = rx_frame.data.u8[2]
+ //celltemp9 = rx_frame.data.u8[3]
+ break;
+ case 0x4a5: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //Some batteries have more info here, unknown content
+ break;
+ case 0x4a7: //1s
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //Some batteries have more celltemps here
+ //celltemp10 = rx_frame.data.u8[0]
+ //celltemp11 = rx_frame.data.u8[1]
+ //celltemp12 = rx_frame.data.u8[2]
+ //celltemp13 = rx_frame.data.u8[3]
+ //celltemp14 = rx_frame.data.u8[4]
+ break;
+ case 0x1BC: //1s cellvoltages (only on 108S)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer.battery.status.cell_voltages_mV[96] = ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4)) + 1000;
+ datalayer.battery.status.cell_voltages_mV[97] = ((rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[3] >> 4)) + 1000;
+ break;
+ case 0x1BD: //1s cellvoltages (only on 108S)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer.battery.status.cell_voltages_mV[98] = ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4)) + 1000;
+ datalayer.battery.status.cell_voltages_mV[99] =
+ (((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) + 1000;
+ datalayer.battery.status.cell_voltages_mV[100] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4)) + 1000;
+ datalayer.battery.status.cell_voltages_mV[101] =
+ (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]) + 1000;
+ datalayer.battery.status.cell_voltages_mV[102] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[7] >> 4)) + 1000;
+ break;
+ case 0x1BE: //1s cellvoltages (only on 108S)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer.battery.status.cell_voltages_mV[103] = ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] >> 4)) + 1000;
+ datalayer.battery.status.cell_voltages_mV[104] =
+ (((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) + 1000;
+ datalayer.battery.status.cell_voltages_mV[105] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4)) + 1000;
+ datalayer.battery.status.cell_voltages_mV[106] =
+ (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]) + 1000;
+ datalayer.battery.status.cell_voltages_mV[107] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[7] >> 4)) + 1000;
+ break;
+ case 0x46f: //100ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x7EC: //OBD2 diag reply from BMS (Replies to both 7DF and 7E4)
+
+ if (rx_frame.data.u8[0] < 0x10) { //One line response
+ pid_reply = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2];
+ }
+
+ if (rx_frame.data.u8[0] == 0x10) { //Multiframe response, send ACK
+ //transmit_can_frame(&FORD_PID_ACK); //Not seen yet
+ //pid_reply = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
+ }
+
+ switch (pid_reply) {
+ case 0x142: //12V battery
+ polled_12V = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
+ break;
+ default:
+ break;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void FordMachEBattery::transmit_can(unsigned long currentMillis) {
+ // Send 20ms CAN Message
+ if (currentMillis - previousMillis20 >= INTERVAL_20_MS) {
+ previousMillis20 = currentMillis;
+
+ if (datalayer.battery.status.bms_status == FAULT) {
+ FORD_25B.data.u8[2] = 0x01;
+ } else {
+ FORD_25B.data.u8[2] = 0x09;
+ }
+
+ transmit_can_frame(&FORD_25B);
+
+ //Full vehicle emulation, not required
+ /*
+ //transmit_can_frame(&FORD_217); Not needed for contactor closing
+ //transmit_can_frame(&FORD_442); Not needed for contactor closing
+ */
+ }
+
+ // Send 30ms CAN Message
+ if (currentMillis - previousMillis30 >= INTERVAL_30_MS) {
+ previousMillis30 = currentMillis;
+
+ //Full vehicle emulation, not required
+ /*
+
+ counter_30ms = (counter_30ms + 1) % 16; // cycles 0-15
+
+ // Byte 2: upper nibble = 0xF, lower nibble = (0xF - counter_10ms) % 16
+ FORD_7D.data.u8[2] = 0xF0 | ((0xF - counter_30ms) & 0x0F);
+ // Byte 3: upper nibble = counter_10ms, lower nibble = 0x0
+ FORD_7D.data.u8[3] = (counter_30ms << 4) & 0xF0;
+
+ // Byte 0: starts at 0xC0 and increments by 4 each step, wraps every 16 steps
+ FORD_204.data.u8[0] = 0xC0 + (counter_30ms * 4);
+ // Byte 5: starts at 0xFC and decrements by 1 each step, wraps every 16 steps
+ FORD_204.data.u8[5] = 0xFC - counter_30ms;
+
+ // Byte 4: upper nibble = counter_10ms, lower nibble = 0x0
+ FORD_4B0.data.u8[4] = (counter_30ms << 4) & 0xF0;
+ // Byte 7: upper nibble = 0xF, lower nibble = (0xF - counter_10ms) % 16
+ FORD_4B0.data.u8[7] = 0xF0 | ((0xF - counter_30ms) & 0x0F);
+
+ // Byte 2: starts at 0xC0 and increments by 4 each step, wraps every 16 steps
+ FORD_415.data.u8[2] = 0xC0 + (counter_30ms * 4);
+ // Byte 3: starts at 0xFC and decrements by 1 each step, wraps every 16 steps
+ FORD_415.data.u8[3] = 0xFC - counter_30ms;
+
+ //TODO: handle FORD_4C properly very odd looping
+
+ counter_8_30ms = (counter_8_30ms + 1) % 8; // cycles 0-7 (8 values before wrap)
+
+ // Byte 6: counts up by 2 each step (0, 2, 4, 6, 8, 10, 12, 14)
+ FORD_200.data.u8[6] = counter_8_30ms * 2;
+
+ // Byte 7: counts down by 2 each step, maintaining byte6 + byte7 = 0x7F
+ FORD_200.data.u8[7] = 0x7F - (counter_8_30ms * 2);
+
+ //transmit_can_frame(&FORD_77); Not needed for contactor closing
+ //transmit_can_frame(&FORD_7D); Not needed for contactor closing
+ //transmit_can_frame(&FORD_167); Not needed for contactor closing
+ //transmit_can_frame(&FORD_48F); //Only sent in AC charging logs! Not needed for contactor closing
+ //transmit_can_frame(&FORD_204); Not needed for contactor closing
+ //transmit_can_frame(&FORD_4B0); Not needed for contactor closing
+ //transmit_can_frame(&FORD_47); Not needed for contactor closing
+ //transmit_can_frame(&FORD_230); Not needed for contactor closing
+ //transmit_can_frame(&FORD_415); Not needed for contactor closing
+ //transmit_can_frame(&FORD_4C); Not needed for contactor closing
+ //transmit_can_frame(&FORD_7E); Not needed for contactor closing
+ //transmit_can_frame(&FORD_48); Not needed for contactor closing
+ //transmit_can_frame(&FORD_165); Not needed for contactor closing
+ //transmit_can_frame(&FORD_7F); Not needed for contactor closing
+ transmit_can_frame(&FORD_200);
+ */
+ }
+ // Send 50ms CAN Message
+ if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
+ previousMillis50 = currentMillis;
+ //transmit_can_frame(&FORD_42C); Not needed for contactor closing
+ //transmit_can_frame(&FORD_42F); Not needed for contactor closing
+ //transmit_can_frame(&FORD_43D);
+ }
+
+ // Send 100ms CAN Message
+ if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
+ previousMillis100 = currentMillis;
+
+ transmit_can_frame(&FORD_185); // Required to close contactors
+
+ //Full vehicle emulation, not required
+ /*
+ transmit_can_frame(
+ &FORD_12F); //This message actually has checksum/counter, but it seems to close contactors without those
+ transmit_can_frame(&FORD_332);
+ transmit_can_frame(&FORD_333);
+ transmit_can_frame(&FORD_42B);
+ transmit_can_frame(&FORD_2EC);
+ transmit_can_frame(&FORD_156);
+ transmit_can_frame(
+ &FORD_5A); //This message actually has checksum/counter, but it seems to close contactors without those
+ transmit_can_frame(&FORD_166);
+ transmit_can_frame(&FORD_175);
+ transmit_can_frame(&FORD_178);
+ transmit_can_frame(&FORD_203); //MANDATORY FOR CONTACTOR OPERATION
+ transmit_can_frame(
+ &FORD_176); //This message actually has checksum/counter, but it seems to close contactors without those
+*/
+ }
+
+ // Send 250ms CAN Message
+ if (currentMillis - previousMillis250 >= INTERVAL_250_MS) {
+ previousMillis250 = currentMillis;
+
+ transmit_can_frame(&FORD_PID_REQUEST_7DF);
+ }
+
+ // Send 1s CAN Message
+ if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
+ previousMillis1000 = currentMillis;
+ //Full vehicle emulation, not required
+ /*
+ transmit_can_frame(&FORD_3C3);
+ transmit_can_frame(&FORD_581);
+ */
+ }
+}
+
+void FordMachEBattery::setup(void) { // Performs one time setup at startup
+ strncpy(datalayer.system.info.battery_protocol, Name, 63);
+ datalayer.system.info.battery_protocol[63] = '\0';
+ datalayer.battery.info.total_capacity_Wh = 88000; //Start in 88kWh mode, update later
+ datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
+ datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV; //Startup in extreme ends
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV;
+ datalayer.system.status.battery_allows_contactor_closing = true;
+}
diff --git a/Software/src/battery/FORD-MACH-E-BATTERY.h b/Software/src/battery/FORD-MACH-E-BATTERY.h
new file mode 100644
index 000000000..71a9141eb
--- /dev/null
+++ b/Software/src/battery/FORD-MACH-E-BATTERY.h
@@ -0,0 +1,269 @@
+#ifndef FORD_MACH_E_BATTERY_H
+#define FORD_MACH_E_BATTERY_H
+#include "../datalayer/datalayer.h"
+#include "CanBattery.h"
+
+class FordMachEBattery : public CanBattery {
+ public:
+ virtual void setup(void);
+ virtual void handle_incoming_can_frame(CAN_frame rx_frame);
+ virtual void update_values();
+ virtual void transmit_can(unsigned long currentMillis);
+ static constexpr const char* Name = "Ford Mustang Mach-E battery";
+
+ private:
+ //90S NMC
+ static const int MAX_PACK_VOLTAGE_90S_DV = 3902;
+ static const int MIN_PACK_VOLTAGE_90S_DV = 2490;
+ static const int MAX_CAPACITY_90S_WH = 98800;
+
+ //94S 98.8 kWh NMC LGES
+ static const int MAX_PACK_VOLTAGE_94S_DV = 4072;
+ static const int MIN_PACK_VOLTAGE_94S_DV = 2612;
+ static const int MAX_CAPACITY_94S_WH = 98800;
+
+ //75.7 kWh NMC LGES
+ static const int MAX_PACK_VOLTAGE_96S_DV = 4140;
+ static const int MIN_PACK_VOLTAGE_96S_DV = 2680;
+ static const int MAX_CAPACITY_96S_WH = 75700;
+
+ //78.2 kWh LFP CATL
+ static const int MAX_PACK_VOLTAGE_108S_DV = 3870;
+ static const int MIN_PACK_VOLTAGE_108S_DV = 3200;
+ static const int MAX_CAPACITY_108S_WH = 78200;
+ static const int MAX_CELL_VOLTAGE_LFP_MV = 3670;
+
+ //Common
+ static const int MAX_CELL_DEVIATION_MV = 250;
+ static const int MAX_CELL_VOLTAGE_MV = 4250;
+ static const int MIN_CELL_VOLTAGE_MV = 2900;
+
+ static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
+ static const int RAMPDOWNPOWERALLOWED = 8000; // What power we ramp down from towards top balancing
+ static const int FLOAT_MAX_POWER_W = 200; // W, what power to allow for top balancing battery
+ static const int FLOAT_START_MV = 20; // mV, how many mV under overvoltage to start float charging
+
+ unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
+ unsigned long previousMillis30 = 0; // will store last time a 10ms CAN Message was send
+ unsigned long previousMillis50 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis250 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was send
+ unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
+
+ int16_t cell_temperature[6] = {0};
+ int16_t maximum_temperature = 0;
+ int16_t minimum_temperature = 0;
+ uint16_t battery_soc = 5000;
+ uint16_t battery_soh = 99;
+ uint16_t battery_voltage = 370;
+ int16_t battery_current = 0;
+ uint16_t maximum_cellvoltage_mV = 3700;
+ uint16_t minimum_cellvoltage_mV = 3700;
+
+ uint8_t counter_30ms = 0;
+ uint8_t counter_8_30ms = 0;
+ uint16_t pid_reply = 0;
+
+ uint16_t polled_12V = 12000;
+
+ CAN_frame FORD_PID_REQUEST_7DF = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x7DF,
+ .data = {0x02, 0x01, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_PID_ACK = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x7DF,
+ .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ //Message needed for contactor closing
+ CAN_frame FORD_25B = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x25B,
+ .data = {0x01, 0xF4, 0x09, 0xF4, 0xE0, 0x00, 0x80, 0x00}};
+ CAN_frame FORD_185 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x185,
+ .data = {0x03, 0x4E, 0x75, 0x32, 0x00, 0x80, 0x00, 0x00}};
+ //Messages to emulate full vehicle
+ /*
+ CAN_frame FORD_47 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x047,
+ .data = {0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_48 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x048,
+ .data = {0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_4C = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x04C,
+ .data = {0x70, 0xAA, 0xBF, 0xDE, 0xCC, 0xEC, 0x00, 0x00}};
+ CAN_frame FORD_5A = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x05A,
+ .data = {0x00, 0x00, 0x00, 0x0B, 0xF2, 0x90, 0x10, 0x00}};
+ CAN_frame FORD_77 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x077,
+ .data = {0x00, 0x00, 0x0F, 0xFE, 0xFF, 0xFF, 0xFB, 0xFE}};
+ CAN_frame FORD_7D = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x07D,
+ .data = {0x00, 0x00, 0xF0, 0xF0, 0x00, 0x3F, 0xEF, 0xFE}};
+ CAN_frame FORD_7E = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x07E,
+ .data = {0x00, 0x00, 0x3E, 0x80, 0x00, 0x04, 0x00, 0x00}};
+ CAN_frame FORD_7F = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x07F,
+ .data = {0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_156 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x156,
+ .data = {0x4B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_165 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x165,
+ .data = {0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_166 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x166,
+ .data = {0x00, 0x00, 0x00, 0x01, 0x5C, 0x89, 0x00, 0x00}};
+ CAN_frame FORD_167 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x167,
+ .data = {0x00, 0x80, 0x00, 0x11, 0xFF, 0xE0, 0x00, 0x00}};
+ CAN_frame FORD_175 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x175,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_176 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x176,
+ .data = {0x00, 0x0E, 0xF0, 0x10, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_178 = {.FD = false, //Static content
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x175,
+ .data = {0x01, 0xB6, 0x02, 0x00, 0x4E, 0x46, 0xC6, 0x17}};
+ CAN_frame FORD_12F = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x12F,
+ .data = {0x0A, 0xF8, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
+
+ CAN_frame FORD_200 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x200,
+ .data = {0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x70}};
+ CAN_frame FORD_203 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x203,
+ .data = {0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_204 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x204,
+ .data = {0xD4, 0x00, 0x7D, 0x00, 0x00, 0xF7, 0x00, 0x00}};
+ CAN_frame FORD_217 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x217,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_230 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x230,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}};
+
+ CAN_frame FORD_2EC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x2EC,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}};
+ CAN_frame FORD_332 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x332,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_333 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x333,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_3C3 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x3C3,
+ .data = {0x5C, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}};
+ CAN_frame FORD_415 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x415,
+ .data = {0x00, 0x00, 0xC0, 0xFC, 0x0F, 0xFE, 0xEF, 0xFE}};
+ CAN_frame FORD_42B = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x42B,
+ .data = {0xCB, 0xBE, 0x00, 0x02, 0x00, 0x00, 0xCE, 0x00}};
+ CAN_frame FORD_42C = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x42C,
+ .data = {0x80, 0x02, 0x00, 0x00, 0x19, 0xA0, 0x00, 0x00}};
+ CAN_frame FORD_42F = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x42F,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame FORD_43D = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x43D,
+ .data = {0x00, 0x00, 0xDC, 0x00, 0x00, 0x77, 0x00, 0x00}};
+ CAN_frame FORD_442 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x442,
+ .data = {0x4E, 0x20, 0x78, 0x7E, 0x7C, 0x00, 0x00, 0x40}};
+ CAN_frame FORD_48F = {.FD = false, //Only sent in active charging logs (OBC?)
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x48F,
+ .data = {0x30, 0x4E, 0x20, 0x80, 0x00, 0x00, 0x80, 0x00}};
+ CAN_frame FORD_4B0 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x4B0,
+ .data = {0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0xF0}};
+ CAN_frame FORD_581 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x4B0,
+ .data = {0x81, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
+ */
+};
+
+#endif
diff --git a/Software/src/battery/FOXESS-BATTERY.cpp b/Software/src/battery/FOXESS-BATTERY.cpp
index 22a9d3b8c..556db6635 100644
--- a/Software/src/battery/FOXESS-BATTERY.cpp
+++ b/Software/src/battery/FOXESS-BATTERY.cpp
@@ -23,9 +23,6 @@ void FoxessBattery::
datalayer.battery.status.max_charge_power_W = ((datalayer.battery.status.voltage_dV * max_charge_power_dA) / 100);
- //Map all cell voltages to the global array
- memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, 128 * sizeof(uint16_t));
-
switch (NUMBER_OF_PACKS) {
case 1: //HS2.6 (48V invalid combo for most HV inverters)
datalayer.battery.info.number_of_cells = 16;
@@ -206,196 +203,196 @@ void FoxessBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x0D59:
break;
case 0x0C1D: // Cellvolts 1
- cellvoltages_mV[0] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[1] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[2] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[3] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[0] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[1] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[2] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[3] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C21: // Cellvolts 2
- cellvoltages_mV[4] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[5] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[6] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[7] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[4] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[5] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[6] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[7] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C25: // Cellvolts 3
- cellvoltages_mV[8] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[9] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[10] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[11] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[8] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[9] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[10] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[11] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C29: // Cellvolts 4
- cellvoltages_mV[12] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[13] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[14] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[15] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[12] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[13] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[14] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[15] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C2D: // Cellvolts 5
- cellvoltages_mV[16] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[17] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[18] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[19] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[16] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[17] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[18] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[19] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C31: // Cellvolts 6
- cellvoltages_mV[20] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[21] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[22] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[23] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[20] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[21] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[22] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[23] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C35: // Cellvolts 7
- cellvoltages_mV[24] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[25] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[26] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[27] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[24] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[25] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[26] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[27] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C39: // Cellvolts 8
- cellvoltages_mV[28] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[29] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[30] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[31] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[28] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[29] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[30] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[31] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C3D: // Cellvolts 9
- cellvoltages_mV[32] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[33] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[34] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[35] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[32] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[33] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[34] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[35] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C41: // Cellvolts 10
- cellvoltages_mV[36] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[37] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[38] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[39] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[36] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[37] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[38] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[39] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C45: // Cellvolts 11
- cellvoltages_mV[40] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[41] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[42] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[43] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[40] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[41] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[42] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[43] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C49: // Cellvolts 12
- cellvoltages_mV[44] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[45] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[46] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[47] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[44] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[45] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[46] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[47] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C4D: // Cellvolts 13
- cellvoltages_mV[48] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[49] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[50] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[51] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[48] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[49] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[50] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[51] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C51: // Cellvolts 14
- cellvoltages_mV[52] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[53] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[54] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[55] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[52] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[53] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[54] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[55] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C55: // Cellvolts 15
- cellvoltages_mV[56] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[57] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[58] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[59] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[56] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[57] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[58] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[59] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C59: // Cellvolts 16
- cellvoltages_mV[60] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[61] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[62] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[63] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[60] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[61] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[62] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[63] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C5D: // Cellvolts 17
- cellvoltages_mV[64] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[65] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[66] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[67] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[64] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[65] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[66] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[67] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C61: // Cellvolts 18
- cellvoltages_mV[68] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[69] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[70] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[71] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[68] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[69] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[70] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[71] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C65: // Cellvolts 19
- cellvoltages_mV[72] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[73] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[74] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[75] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[72] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[73] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[74] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[75] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C69: // Cellvolts 20
- cellvoltages_mV[76] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[77] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[78] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[79] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[76] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[77] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[78] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[79] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C6D: // Cellvolts 21
- cellvoltages_mV[80] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[81] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[82] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[83] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[80] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[81] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[82] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[83] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C71: // Cellvolts 22
- cellvoltages_mV[84] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[85] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[86] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[87] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[84] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[85] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[86] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[87] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C75: // Cellvolts 23
- cellvoltages_mV[88] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[89] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[90] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[91] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[88] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[89] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[90] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[91] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C79: // Cellvolts 24
- cellvoltages_mV[92] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[93] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[94] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[95] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[92] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[93] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[94] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[95] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C7D: // Cellvolts 25
- cellvoltages_mV[96] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[97] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[98] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[99] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[96] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[97] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[98] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[99] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C81: // Cellvolts 26
- cellvoltages_mV[100] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[101] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[102] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[103] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[100] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[101] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[102] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[103] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C85: // Cellvolts 27
- cellvoltages_mV[104] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[105] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[106] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[107] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[104] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[105] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[106] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[107] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C89: // Cellvolts 28
- cellvoltages_mV[108] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[109] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[110] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[111] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[108] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[109] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[110] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[111] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C8D: // Cellvolts 29
- cellvoltages_mV[112] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[113] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[114] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[115] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[112] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[113] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[114] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[115] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C91: // Cellvolts 30
- cellvoltages_mV[116] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[117] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[118] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[119] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[116] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[117] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[118] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[119] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C95: // Cellvolts 31
- cellvoltages_mV[120] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[121] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[122] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[123] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[120] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[121] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[122] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[123] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x0C99: // Cellvolts 32
- cellvoltages_mV[124] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
- cellvoltages_mV[125] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
- cellvoltages_mV[126] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
- cellvoltages_mV[127] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[124] = (uint16_t)(rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
+ datalayer.battery.status.cell_voltages_mV[125] = (uint16_t)(rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
+ datalayer.battery.status.cell_voltages_mV[126] = (uint16_t)(rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
+ datalayer.battery.status.cell_voltages_mV[127] = (uint16_t)(rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
default:
break;
@@ -575,6 +572,7 @@ void FoxessBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 0; //Startup with no cells, populates later when we know packsize
+ datalayer.battery.info.chemistry = LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
diff --git a/Software/src/battery/FOXESS-BATTERY.h b/Software/src/battery/FOXESS-BATTERY.h
index a10d62fd0..2d6e23417 100644
--- a/Software/src/battery/FOXESS-BATTERY.h
+++ b/Software/src/battery/FOXESS-BATTERY.h
@@ -31,7 +31,6 @@ class FoxessBattery : public CanBattery {
uint16_t cut_mv_min = 0;
uint16_t cycle_count = 0;
uint16_t max_ac_voltage = 0;
- uint16_t cellvoltages_mV[128] = {0};
int16_t temperature_average = 0;
int16_t pack1_current_sensor = 0;
int16_t pack2_current_sensor = 0;
diff --git a/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp b/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp
index 35b458294..43f729f95 100644
--- a/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp
+++ b/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp
@@ -3,6 +3,7 @@
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
/* TODO
@@ -55,13 +56,13 @@ smart booster, ESC, airbag control module, automatic parking module
/* Do not change code below unless you are sure what you are doing */
void GeelyGeometryCBattery::update_values() {
- datalayer_battery->status.soh_pptt;
+ //datalayer_battery->status.soh_pptt; //TODO: Fix
datalayer_battery->status.real_soc = poll_soc * 10;
datalayer_battery->status.voltage_dV = battery_voltage;
- datalayer_battery->status.current_dA;
+ //datalayer_battery->status.current_dA; //TODO: Fix
if (poll_amount_cells == 102) { // We have determined that we are on 70kWh pack
datalayer_battery->info.total_capacity_Wh = 70000;
@@ -75,7 +76,7 @@ void GeelyGeometryCBattery::update_values() {
datalayer_battery->status.max_discharge_power_W = discharge_power_allowed * 100;
- datalayer_battery->status.max_charge_power_W;
+ //datalayer_battery->status.max_charge_power_W; //TODO: Fix
datalayer_battery->status.cell_min_voltage_mV = maximum_cell_voltage - 10; //TODO: Fix once we have min value
@@ -125,26 +126,10 @@ void GeelyGeometryCBattery::update_values() {
datalayer_geometryc->unknown8 = poll_unknown8;
}
-const unsigned char crctable[256] = { // CRC8_SAE_J1850_ZER0 formula,0x2F Poly,initial value 0xFF,Final XOR value 0xFF
- 0x00, 0x2F, 0x5E, 0x71, 0xBC, 0x93, 0xE2, 0xCD, 0x57, 0x78, 0x09, 0x26, 0xEB, 0xC4, 0xB5, 0x9A, 0xAE, 0x81, 0xF0,
- 0xDF, 0x12, 0x3D, 0x4C, 0x63, 0xF9, 0xD6, 0xA7, 0x88, 0x45, 0x6A, 0x1B, 0x34, 0x73, 0x5C, 0x2D, 0x02, 0xCF, 0xE0,
- 0x91, 0xBE, 0x24, 0x0B, 0x7A, 0x55, 0x98, 0xB7, 0xC6, 0xE9, 0xDD, 0xF2, 0x83, 0xAC, 0x61, 0x4E, 0x3F, 0x10, 0x8A,
- 0xA5, 0xD4, 0xFB, 0x36, 0x19, 0x68, 0x47, 0xE6, 0xC9, 0xB8, 0x97, 0x5A, 0x75, 0x04, 0x2B, 0xB1, 0x9E, 0xEF, 0xC0,
- 0x0D, 0x22, 0x53, 0x7C, 0x48, 0x67, 0x16, 0x39, 0xF4, 0xDB, 0xAA, 0x85, 0x1F, 0x30, 0x41, 0x6E, 0xA3, 0x8C, 0xFD,
- 0xD2, 0x95, 0xBA, 0xCB, 0xE4, 0x29, 0x06, 0x77, 0x58, 0xC2, 0xED, 0x9C, 0xB3, 0x7E, 0x51, 0x20, 0x0F, 0x3B, 0x14,
- 0x65, 0x4A, 0x87, 0xA8, 0xD9, 0xF6, 0x6C, 0x43, 0x32, 0x1D, 0xD0, 0xFF, 0x8E, 0xA1, 0xE3, 0xCC, 0xBD, 0x92, 0x5F,
- 0x70, 0x01, 0x2E, 0xB4, 0x9B, 0xEA, 0xC5, 0x08, 0x27, 0x56, 0x79, 0x4D, 0x62, 0x13, 0x3C, 0xF1, 0xDE, 0xAF, 0x80,
- 0x1A, 0x35, 0x44, 0x6B, 0xA6, 0x89, 0xF8, 0xD7, 0x90, 0xBF, 0xCE, 0xE1, 0x2C, 0x03, 0x72, 0x5D, 0xC7, 0xE8, 0x99,
- 0xB6, 0x7B, 0x54, 0x25, 0x0A, 0x3E, 0x11, 0x60, 0x4F, 0x82, 0xAD, 0xDC, 0xF3, 0x69, 0x46, 0x37, 0x18, 0xD5, 0xFA,
- 0x8B, 0xA4, 0x05, 0x2A, 0x5B, 0x74, 0xB9, 0x96, 0xE7, 0xC8, 0x52, 0x7D, 0x0C, 0x23, 0xEE, 0xC1, 0xB0, 0x9F, 0xAB,
- 0x84, 0xF5, 0xDA, 0x17, 0x38, 0x49, 0x66, 0xFC, 0xD3, 0xA2, 0x8D, 0x40, 0x6F, 0x1E, 0x31, 0x76, 0x59, 0x28, 0x07,
- 0xCA, 0xE5, 0x94, 0xBB, 0x21, 0x0E, 0x7F, 0x50, 0x9D, 0xB2, 0xC3, 0xEC, 0xD8, 0xF7, 0x86, 0xA9, 0x64, 0x4B, 0x3A,
- 0x15, 0x8F, 0xA0, 0xD1, 0xFE, 0x33, 0x1C, 0x6D, 0x42};
-
bool is_message_corrupt(CAN_frame* rx_frame) {
uint8_t crc = 0xFF; // Initial value
for (uint8_t j = 0; j < 7; j++) {
- crc = crctable[crc ^ rx_frame->data.u8[j]];
+ crc = crctable_geely_geometryC[crc ^ rx_frame->data.u8[j]];
}
crc = (crc ^ 0xFF); // Final XOR
return crc != rx_frame->data.u8[7];
@@ -155,7 +140,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x0B0: //10ms
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//Contains:
@@ -172,7 +157,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x178: //10ms (64 13 88 00 0E 30 0A 85)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
battery_voltage = ((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5];
@@ -182,7 +167,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x179: //20ms (3E 52 BA 5D A4 3F 0C D9)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//2BA = 69.8 //Potentially charge power allowed
@@ -193,7 +178,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
//(Car log 0A 3D EE F1 BD C6 67 F7)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
@@ -201,7 +186,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x17B: //20ms (00 00 10 00 0F FE 03 C9) (car is the same, static)
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
@@ -211,7 +196,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x210: //100ms (38 04 3A 01 38 22 22 39)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
discharge_power_allowed = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]; //TODO, not confirmed
@@ -222,7 +207,7 @@ void GeelyGeometryCBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x211: //100ms (00 D8 C6 00 00 00 0F 3A)
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(&rx_frame)) {
- datalayer.battery2.status.CAN_error_counter++;
+ datalayer_battery->status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
//frame7, CRC
@@ -458,7 +443,7 @@ uint8_t calc_crc8_geely(CAN_frame* rx_frame) {
uint8_t crc = 0xFF; // Initial value
for (uint8_t j = 0; j < 7; j++) {
- crc = crctable[crc ^ rx_frame->data.u8[j]];
+ crc = crctable_geely_geometryC[crc ^ rx_frame->data.u8[j]];
}
return crc ^ 0xFF; // Final XOR
diff --git a/Software/src/battery/GEELY-SEA-BATTERY.cpp b/Software/src/battery/GEELY-SEA-BATTERY.cpp
new file mode 100644
index 000000000..ce3dd8b47
--- /dev/null
+++ b/Software/src/battery/GEELY-SEA-BATTERY.cpp
@@ -0,0 +1,355 @@
+#include "GEELY-SEA-BATTERY.h"
+#include //For unit test
+#include "../communication/can/comm_can.h"
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
+#include "../devboard/utils/common_functions.h"
+#include "../devboard/utils/events.h"
+#include "../devboard/utils/logging.h"
+
+void GeelySeaBattery::
+
+ update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
+
+ // Update requests from webserver datalayer
+ if (datalayer_extended.GeelySEA.UserRequestDTCreset) {
+ pause_polling_seconds = 10;
+ transmit_can_frame(&SEA_DTC_Erase); //Send global DTC erase command
+ datalayer_extended.GeelySEA.UserRequestDTCreset = false;
+ }
+ if (datalayer_extended.GeelySEA.UserRequestBECMecuReset) {
+ pause_polling_seconds = 10;
+ transmit_can_frame(&SEA_BECM_ECUreset); //Send BECM ecu reset command
+ datalayer_extended.GeelySEA.UserRequestBECMecuReset = false;
+ }
+ if (datalayer_extended.GeelySEA.UserRequestDTCreadout) {
+ pause_polling_seconds = 10;
+ DTC_readout_in_progress = true;
+ transmit_can_frame(&SEA_DTC_Req); //Send DTC readout command
+ datalayer_extended.GeelySEA.DTCcount = 0;
+ datalayer_extended.GeelySEA.UserRequestDTCreadout = false;
+ }
+ if (datalayer_extended.GeelySEA.UserRequestCrashReset) {
+ pause_polling_seconds = 10;
+ transmit_can_frame(&SEA_StartDiag); //Start sequene to reset crash status
+ datalayer_extended.GeelySEA.UserRequestCrashReset = false;
+ }
+
+ datalayer.battery.status.voltage_dV = pack_voltage_dV;
+
+ datalayer.battery.status.current_dA = -pack_current_dA;
+
+ if (datalayer_extended.GeelySEA.soc_bms > 0) {
+ datalayer.battery.status.real_soc = datalayer_extended.GeelySEA.soc_bms;
+ }
+
+ if (datalayer_extended.GeelySEA.soh_bms > 0) {
+ datalayer.battery.status.soh_pptt = datalayer_extended.GeelySEA.soh_bms;
+ }
+
+ if (datalayer_extended.GeelySEA.CellTempHighest > 0) {
+ datalayer.battery.status.temperature_max_dC = ((datalayer_extended.GeelySEA.CellTempHighest / 100.0) - 50.0) * 10;
+ }
+
+ if (datalayer_extended.GeelySEA.CellTempLowest > 0) {
+ datalayer.battery.status.temperature_min_dC = ((datalayer_extended.GeelySEA.CellTempLowest / 100.0) - 50.0) * 10;
+ }
+
+ datalayer.battery.status.remaining_capacity_Wh = static_cast(
+ (static_cast(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 = 5000; //TODO: Take from CAN!
+
+ if (datalayer.battery.info.chemistry == LFP) { //If configured LFP in use (or we autodetected it), switch to it
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_LFP_WH; //EX30 51kWh LFP battery
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP_120S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP_120S_DV;
+ } else if (datalayer.battery.info.chemistry ==
+ NMC) { //If configured NCM in use (or we autodetected it), switch to it
+ if (datalayer.battery.info.number_of_cells == 107) { //EX30 69kWh battery
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_NCM_107S_WH;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCM_107S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCM_107S_DV;
+ } else if (datalayer.battery.info.number_of_cells == 110) { //Zeekr 100kWh battery
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_NCM_110S_WH;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCM_110S_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCM_110S_DV;
+ }
+ }
+
+ /* Check safeties */
+ if (datalayer_extended.GeelySEA.BECMsupplyVoltage > 0) {
+ if (datalayer_extended.GeelySEA.BECMsupplyVoltage < 11800) { // 11.8 V
+ set_event(EVENT_12V_LOW, (datalayer_extended.GeelySEA.BECMsupplyVoltage / 10));
+ } else {
+ clear_event(EVENT_12V_LOW);
+ }
+ }
+}
+
+void GeelySeaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ switch (rx_frame.ID) {
+ case 0x53: //100ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x140: //20ms EX30 only 00 00 00 00 01 7F F9 03
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x141: //20ms EX30+Zeekr
+ battery_alive = true;
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //frame0 counter, 0x00 -> 0x0D and repeating
+ //frame1 CRC most likely
+ break;
+ case 0x142: //20ms EX30+Zeekr
+ battery_alive = true;
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (rx_frame.data.u8[0] == 0x00) {
+ pack_voltage_dV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]) >> 3;
+ } else if (rx_frame.data.u8[0] == 0x01) {
+ datalayer.battery.status.cell_max_voltage_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]) >> 3;
+ } else if (rx_frame.data.u8[0] == 0x02) {
+ datalayer.battery.status.cell_min_voltage_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]) >> 3;
+ } else if (rx_frame.data.u8[0] > 0x02) {
+ datalayer.battery.status.cell_voltages_mV[rx_frame.data.u8[2] - 1] =
+ ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]) >> 3;
+ if (rx_frame.data.u8[2] > datalayer.battery.info.number_of_cells) // Detect number of cells
+ {
+ datalayer.battery.info.number_of_cells = rx_frame.data.u8[2];
+ }
+ }
+ break;
+ case 0x143: //20ms EX30+Zeekr
+ battery_alive = true;
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ pack_current_dA = sign_extend_to_int16((((rx_frame.data.u8[0] & 0x0F) << 8) | rx_frame.data.u8[1]), 12);
+ pack_current_dA = pack_current_dA + 4;
+ break;
+ /*
+ case 0x145: //100ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x175: //100ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x17B: //100ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x178: //70ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x182: //100ms Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x183: //100ms EX30
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x18A: //100ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x286: //120ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x288: //100ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x290: //100ms EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x293: //EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x295: //Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x296: //Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x298: //Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x301: //Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x302: //EX30
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x309: //EX30
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x313: //EX30
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x315: //EX30+Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x316: //Zeekr
+ case 0x331: //Zeekr
+ case 0x341: //Zeekr
+ case 0x342: //Zeekr
+ case 0x345: //Zeekr
+ case 0x346: //Zeekr
+ case 0x347: //Zeekr
+ case 0x478: //Zeekr
+ case 0x479: //Zeekr
+ case 0x489: //Zeekr
+ case 0x493: //Zeekr
+ case 0x494: //Zeekr
+ case 0x499: //Zeekr
+ case 0x49C: //Zeekr
+ case 0x519: //Zeekr
+ case 0x5C0: //Zeekr
+ case 0x5C1: //Zeekr
+ case 0x5D0: //Zeekr
+ case 0x5D1: //Zeekr
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ */
+ case 0x635: //Diag
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+
+ if (DTC_readout_in_progress) {
+
+ if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[2] == 0x59) && (rx_frame.data.u8[3] == 0x03)) {
+ datalayer_extended.GeelySEA.DTCcount = ((rx_frame.data.u8[1] - 2) / 4);
+ transmit_can_frame(&SEA_Flowcontrol);
+ //DTC #1 will be in byte 4-5-6-7 (last byte is if it is permanent or intermittent)
+ DTC_readout_in_progress = false;
+ }
+
+ if ((rx_frame.data.u8[1] == 0x59) &&
+ (rx_frame.data.u8[2] == 0x03)) { // Response frame for DTC with 0 or 1 code
+ if (rx_frame.data.u8[0] != 0x02) {
+ datalayer_extended.GeelySEA.DTCcount = 1;
+ } else {
+ datalayer_extended.GeelySEA.DTCcount = 0;
+ }
+ DTC_readout_in_progress = false;
+ }
+
+ } else { //Normal handling of UDS polling
+
+ if (rx_frame.data.u8[0] == 0x10) { //First frame of a group (not used on this integration)
+ transmit_can_frame(&SEA_Flowcontrol);
+
+ reply_poll =
+ (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]; //No Group reads are done by the integration yet
+ }
+
+ if (rx_frame.data.u8[0] < 0x10) { //One line responses
+ if ((rx_frame.data.u8[1] == 0x50) && (rx_frame.data.u8[2] == 0x03)) { // Response to Diag init command
+ transmit_can_frame(&SEA_ClearCrash);
+ } else {
+ reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; //This is how all the replies are done
+ }
+ }
+ if (pause_polling_seconds == 0) { //Do not update values if DTC action is ongoing
+ switch (reply_poll) {
+ case POLL_BECMsupplyVoltage:
+ datalayer_extended.GeelySEA.BECMsupplyVoltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ break;
+ case POLL_HV_Voltage:
+ datalayer_extended.GeelySEA.BECMBatteryVoltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ break;
+ case POLL_CrashStatus:
+ datalayer_extended.GeelySEA.CrashStatus = rx_frame.data.u8[4];
+ break;
+ case POLL_SOC:
+ datalayer_extended.GeelySEA.soc_bms = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 5;
+ break;
+ case POLL_SOH:
+ datalayer_extended.GeelySEA.soh_bms = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ break;
+ case POLL_HighestCellTemp:
+ datalayer_extended.GeelySEA.CellTempHighest = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
+ break;
+ case POLL_AverageCellTemp:
+ datalayer_extended.GeelySEA.CellTempAverage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ break;
+ case POLL_LowestCellTemp:
+ datalayer_extended.GeelySEA.CellTempLowest = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
+ break;
+ case POLL_Interlock:
+ datalayer_extended.GeelySEA.Interlock = rx_frame.data.u8[4];
+ break;
+ case POLL_HighestCellVolt:
+ datalayer_extended.GeelySEA.CellVoltHighest = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
+ break;
+ case POLL_LowestCellVolt:
+ datalayer_extended.GeelySEA.CellVoltLowest = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
+ break;
+ case POLL_BatteryCurrent:
+ datalayer_extended.GeelySEA.BatteryCurrent = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ break;
+ default: //Not a PID reply, or unknown. Do nothing
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void GeelySeaBattery::readDiagData() {
+
+ // Update current poll from the array
+ currentpoll = poll_commands[poll_index];
+ poll_index = (poll_index + 1) % 12; //% ## comes from array size
+
+ SEA_Polling_Req.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
+ SEA_Polling_Req.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
+
+ if (pause_polling_seconds > 0) {
+ pause_polling_seconds--;
+ } else {
+ transmit_can_frame(&SEA_Polling_Req);
+ }
+}
+
+void GeelySeaBattery::transmit_can(unsigned long currentMillis) {
+
+ if (!battery_alive) {
+ // Send 1000ms CAN Message
+ if (currentMillis - previousMillisWakeup >= INTERVAL_1_S) {
+ previousMillisWakeup = currentMillis;
+ transmit_can_frame(&SEA_536); //Send 0x536 Network managing frame to wake up BMS
+ }
+ return; //Break, do not send more messages until battery is woken up
+ }
+
+ // Send 20ms CAN Message
+ if (currentMillis - previousMillis20 >= INTERVAL_20_MS) {
+ previousMillis20 = currentMillis;
+ transmit_can_frame(&SEA_060); //Send 0x060 Motor B info // 20ms
+ transmit_can_frame(&SEA_156); //Send 0x156 Motor A info //20ms? No good log
+ transmit_can_frame(&SEA_171); //Send 0x171 //50ms
+ transmit_can_frame(&SEA_103); //Send 0x103 //20ms
+ }
+ // Send 100ms CAN Message
+ if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
+ previousMillis100 = currentMillis;
+ transmit_can_frame(&SEA_218); //Send 0x218 //100ms
+ }
+ // Send 1000ms CAN Message
+ if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
+ previousMillis1000 = currentMillis;
+ transmit_can_frame(&SEA_536); //Send 0x536 Network managing frame to keep BMS alive
+ transmit_can_frame(&SEA_490); //Send 0x490 // 1sek
+ readDiagData();
+ }
+}
+
+void GeelySeaBattery::setup(void) { // Performs one time setup at startup
+ strncpy(datalayer.system.info.battery_protocol, Name, 63);
+ datalayer.system.info.battery_protocol[63] = '\0';
+ datalayer.battery.info.total_capacity_Wh = MAX_CAPACITY_NCM_110S_WH; //Startout in NCM mode
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCM_110S_DV; //Startout with max allowed range
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCM_110S_DV; //Startout with min allowed range
+ datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
+ datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
+}
diff --git a/Software/src/battery/GEELY-SEA-BATTERY.h b/Software/src/battery/GEELY-SEA-BATTERY.h
new file mode 100644
index 000000000..86979b642
--- /dev/null
+++ b/Software/src/battery/GEELY-SEA-BATTERY.h
@@ -0,0 +1,173 @@
+#ifndef GEELY_SEA_BATTERY_H
+#define GEELY_SEA_BATTERY_H
+
+#include "CanBattery.h"
+#include "GEELY-SEA-HTML.h"
+
+class GeelySeaBattery : public CanBattery {
+ public:
+ virtual void setup(void);
+ virtual void handle_incoming_can_frame(CAN_frame rx_frame);
+ virtual void update_values();
+ virtual void transmit_can(unsigned long currentMillis);
+ static constexpr const char* Name = "Volvo/Zeekr/Geely SEA battery";
+
+ bool supports_reset_DTC() { return true; }
+ void reset_DTC() { datalayer_extended.GeelySEA.UserRequestDTCreset = true; }
+
+ bool supports_read_DTC() { return true; }
+ void read_DTC() { datalayer_extended.GeelySEA.UserRequestDTCreadout = true; }
+
+ bool supports_reset_BECM() { return true; }
+ void reset_BECM() { datalayer_extended.GeelySEA.UserRequestBECMecuReset = true; }
+
+ bool supports_reset_crash() { return true; }
+ void reset_crash() { datalayer_extended.GeelySEA.UserRequestCrashReset = true; }
+
+ BatteryHtmlRenderer& get_status_renderer() { return renderer; }
+
+ private:
+ GeelySeaHtmlRenderer renderer;
+
+ void readDiagData();
+
+ static const int MAX_PACK_VOLTAGE_LFP_120S_DV = 4380;
+ static const int MIN_PACK_VOLTAGE_LFP_120S_DV = 3600;
+ static const int MAX_CAPACITY_LFP_WH = 51000;
+ static const int MAX_PACK_VOLTAGE_NCM_110S_DV = 4650;
+ static const int MIN_PACK_VOLTAGE_NCM_110S_DV = 3210;
+ static const int MAX_CAPACITY_NCM_110S_WH = 100000;
+ static const int MAX_PACK_VOLTAGE_NCM_107S_DV = 4523;
+ static const int MIN_PACK_VOLTAGE_NCM_107S_DV = 3122;
+ static const int MAX_CAPACITY_NCM_107S_WH = 69000;
+ static const int MAX_CELL_DEVIATION_MV = 250;
+ static const int MAX_CELL_VOLTAGE_MV = 4260; // Charging is halted if one cell goes above this
+ static const int MIN_CELL_VOLTAGE_MV = 2900; // Charging is halted if one cell goes below this
+
+ unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
+ unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was send
+ unsigned long previousMillisWakeup = 0;
+
+ static const uint16_t POLL_BECMsupplyVoltage = 0xEE02;
+ static const uint16_t POLL_HV_Voltage = 0x4803;
+ static const uint16_t POLL_SOC = 0x4801;
+ static const uint16_t POLL_SOH = 0x489A;
+ static const uint16_t POLL_HighestCellTemp = 0x4945;
+ static const uint16_t POLL_AverageCellTemp = 0x491B;
+ static const uint16_t POLL_LowestCellTemp = 0x49A1;
+ static const uint16_t POLL_Interlock = 0x491A;
+ static const uint16_t POLL_HighestCellVolt = 0x4907;
+ static const uint16_t POLL_LowestCellVolt = 0x4908;
+ static const uint16_t POLL_BatteryCurrent = 0x4802;
+ static const uint16_t POLL_CrashStatus = 0xFE42;
+
+ uint8_t pause_polling_seconds = 0;
+ bool DTC_readout_in_progress = false;
+
+ const uint16_t poll_commands[12] = {POLL_BECMsupplyVoltage,
+ POLL_HV_Voltage,
+ POLL_SOC,
+ POLL_SOH,
+ POLL_HighestCellTemp,
+ POLL_AverageCellTemp,
+ POLL_LowestCellTemp,
+ POLL_Interlock,
+ POLL_HighestCellVolt,
+ POLL_LowestCellVolt,
+ POLL_BatteryCurrent,
+ POLL_CrashStatus};
+
+ uint8_t poll_index = 0;
+ uint16_t currentpoll = POLL_BECMsupplyVoltage;
+ uint16_t reply_poll = 0;
+ bool battery_alive = false;
+
+ int16_t pack_current_dA = 0;
+ uint16_t pack_voltage_dV = 3700;
+
+ CAN_frame SEA_536 = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x536,
+ .data = {0x00, 0x41, 0x40, 0x21, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame (38 transmitted IDยดs)
+ //.data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame (33 transmitted IDยดs)
+
+ CAN_frame SEA_060 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x060,
+ .data = {0x80, 0x00, 0x01, 0x38, 0xEE, 0x47, 0x00, 0x40}}; //Motor B on Zeekr battery
+
+ CAN_frame SEA_156 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x156,
+ .data = {0x7F, 0x4E, 0x10, 0x00, 0x00, 0x00, 0x2E, 0x10}}; //Motor A on Zeekr battery
+ CAN_frame SEA_171 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x171,
+ .data = {0x00, 0x00, 0x80, 0x00, 0xA0, 0x00, 0x00, 0x6C}}; // Prevents DTC on EX30 battery
+ CAN_frame SEA_218 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x218,
+ .data = {0xC0, 0x00, 0x12, 0x3B, 0x1A, 0x30, 0x00, 0x01}}; // Prevents DTC on EX30 battery
+ CAN_frame SEA_490 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x490,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x4B, 0xA3}}; // Prevents DTC on EX30 battery
+ CAN_frame SEA_103 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x103,
+ .data = {0x20, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x00,
+ 0x00}}; // Prevents DTC on Zeekr battery (Engine Control Module)
+
+ CAN_frame SEA_Polling_Req = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x03, 0x22, 0x48, 0x02, 0x00, 0x00, 0x00, 0x00}}; //Battery current request frame
+
+ CAN_frame SEA_DTC_Req = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //DTC request frame
+
+ CAN_frame SEA_Flowcontrol = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Flowcontrol + 5ms delay
+
+ CAN_frame SEA_DTC_Erase = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase
+ CAN_frame SEA_StartDiag = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Start diag session
+ CAN_frame SEA_ClearCrash = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x04, 0x31, 0x01, 0x30, 0x03, 0x00, 0x00, 0x00}}; //Clear crash status
+
+ CAN_frame SEA_BECM_ECUreset = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x735,
+ .data = {0x02, 0x11, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}}; //BECM ECU reset command (reboot/powercycle BECM)
+};
+
+#endif
diff --git a/Software/src/battery/GEELY-SEA-HTML.h b/Software/src/battery/GEELY-SEA-HTML.h
new file mode 100644
index 000000000..e10a9bd09
--- /dev/null
+++ b/Software/src/battery/GEELY-SEA-HTML.h
@@ -0,0 +1,97 @@
+#ifndef _GEELY_SEA_HTML_H
+#define _GEELY_SEA_HTML_H
+
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
+#include "../devboard/webserver/BatteryHtmlRenderer.h"
+
+class GeelySeaHtmlRenderer : public BatteryHtmlRenderer {
+ public:
+ String get_status_html() {
+ String content;
+ content += "BECM reported number of DTCs: " + String(datalayer_extended.GeelySEA.DTCcount) + " ";
+ content += "Inhibition status (crash): " + String(datalayer_extended.GeelySEA.CrashStatus) + " ";
+ content += "BECM reported SOC: " + String(datalayer_extended.GeelySEA.soc_bms / 100.0) + " % ";
+ content += "BECM reported SOH: " + String(datalayer_extended.GeelySEA.soh_bms / 100.0) + " % ";
+ content += "HV voltage: " + String(datalayer_extended.GeelySEA.BECMBatteryVoltage / 100.0) + " V ";
+ //content += "Battery current: " + String((datalayer_extended.GeelySEA.BatteryCurrent / 10.0) - 1638) + " A ";
+ content += "Highest cell voltage: " + String(datalayer_extended.GeelySEA.CellVoltHighest / 1000.00) + " V ";
+ content += "Lowest cell voltage: " + String(datalayer_extended.GeelySEA.CellVoltLowest / 1000.00) + " V ";
+ content += "BECM supply voltage: " + String(datalayer_extended.GeelySEA.BECMsupplyVoltage / 1000.0) + " V ";
+ content += "Cell count: " + String(datalayer.battery.info.number_of_cells) + " ";
+ content +=
+ "Highest cell temp: " + String((datalayer_extended.GeelySEA.CellTempHighest / 100.0) - 50.0) + " ยบC ";
+ content +=
+ "Average cell temp: " + String((datalayer_extended.GeelySEA.CellTempAverage / 100.0) - 50.0) + " ยบC ";
+ content +=
+ "Lowest cell temp: " + String((datalayer_extended.GeelySEA.CellTempLowest / 100.0) - 50.0) + " ยบC ";
+ content += "HVIL Circuit 1 (M1+M2+FC connectors) status : ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x80) {
+ case 0x80:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "HVIL Circuit 2 (LV connector pin 9-10) status: ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x40) {
+ case 0x40:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "HVIL Circuit 3 (LV connector pin 8-12) status: ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x04) {
+ case 0x04:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "Unknow Contactor Status 1 (Negative FC?): ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x01) {
+ case 0x01:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "Unknown Contactor Status 2 (Positive FC?): ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x02) {
+ case 0x02:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "Negative Contactor Status: ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x08) {
+ case 0x08:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "Precharge Contactor Status: ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x10) {
+ case 0x10:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "Positive Contactor Status: ";
+ switch (datalayer_extended.GeelySEA.Interlock & 0x20) {
+ case 0x20:
+ content += String("Open");
+ break;
+ default:
+ content += String("Closed");
+ }
+ content += "";
+ return content;
+ }
+};
+
+#endif
diff --git a/Software/src/battery/GROWATT-HV-ARK-BATTERY.cpp b/Software/src/battery/GROWATT-HV-ARK-BATTERY.cpp
new file mode 100644
index 000000000..f20d30217
--- /dev/null
+++ b/Software/src/battery/GROWATT-HV-ARK-BATTERY.cpp
@@ -0,0 +1,213 @@
+#include "GROWATT-HV-ARK-BATTERY.h"
+
+#include "../battery/BATTERIES.h"
+#include "../communication/can/comm_can.h"
+#include "../datalayer/datalayer.h"
+
+// Helpers (Growatt HV protocol is big-endian)
+static inline uint16_t read_u16_be(const CAN_frame& f, int idx) {
+ return (uint16_t)((f.data.u8[idx] << 8) | f.data.u8[idx + 1]);
+}
+
+static inline int16_t read_s16_be(const CAN_frame& f, int idx) {
+ return (int16_t)read_u16_be(f, idx);
+}
+
+static inline void write_u16_be(CAN_frame& f, int idx, uint16_t v) {
+ f.data.u8[idx] = (uint8_t)(v >> 8);
+ f.data.u8[idx + 1] = (uint8_t)(v & 0xFF);
+}
+
+static inline void write_u32_be(CAN_frame& f, int idx, uint32_t v) {
+ f.data.u8[idx] = (uint8_t)(v >> 24);
+ f.data.u8[idx + 1] = (uint8_t)((v >> 16) & 0xFF);
+ f.data.u8[idx + 2] = (uint8_t)((v >> 8) & 0xFF);
+ f.data.u8[idx + 3] = (uint8_t)(v & 0xFF);
+}
+
+void GrowattHvArkBattery::setup(void) {
+ strncpy(datalayer.system.info.battery_protocol, Name, 63);
+ datalayer.system.info.battery_protocol[63] = '\0';
+
+ // Provide sane defaults (will be overwritten by incoming frames once the battery is talking)
+ datalayer.battery.info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
+ datalayer.battery.info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
+ datalayer.battery.info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
+ datalayer.battery.info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
+ datalayer.battery.info.max_cell_voltage_deviation_mV = 150;
+
+ // Allow contactor closing once we have a healthy, awake battery.
+ datalayer.system.status.battery_allows_contactor_closing = false;
+}
+
+void GrowattHvArkBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ switch (rx_frame.ID) {
+ case 0x3110: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+
+ max_charge_voltage_dV = read_u16_be(rx_frame, 0);
+ max_charge_current_dA = (int16_t)read_u16_be(rx_frame, 2);
+ max_discharge_current_dA = (int16_t)read_u16_be(rx_frame, 4);
+
+ const uint8_t b6 = rx_frame.data.u8[6];
+ const uint8_t b7 = rx_frame.data.u8[7];
+
+ // Table 1 bits (0x3110)
+ battery_fault_present = (b7 & (1u << 2)) != 0;
+ battery_sleeping = (b7 & (1u << 4)) != 0;
+ battery_no_discharge = (b7 & (1u << 5)) != 0;
+ battery_no_charge = (b7 & (1u << 6)) != 0;
+
+ (void)b6; // reserved bits currently unused
+ break;
+ }
+
+ case 0x3120:
+ // Protection + Alarm bitfields (not yet mapped into the generic datalayer)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+
+ case 0x3130: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+
+ pack_voltage_dV = read_u16_be(rx_frame, 0);
+ pack_current_dA = read_s16_be(rx_frame, 2);
+ temp_max_dC = read_s16_be(rx_frame, 4);
+ soc_pct = rx_frame.data.u8[6];
+ soh_pct = rx_frame.data.u8[7] & 0x7F; // bit7 is a flag per spec
+ break;
+ }
+
+ case 0x3140: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ remaining_capacity_10mAh = read_u16_be(rx_frame, 0);
+ full_capacity_10mAh = read_u16_be(rx_frame, 2);
+ break;
+ }
+
+ case 0x3150: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ discharge_cutoff_voltage_dV = read_u16_be(rx_frame, 0);
+ // Byte2-3: main control unit temperature (0.1C)
+ // We treat it as an additional temperature source; max already comes from 0x3130.
+ // int16_t mcu_temp_dC = read_s16_be(rx_frame, 2);
+ const uint16_t total_cells = read_u16_be(rx_frame, 4);
+ // const uint16_t modules_series = read_u16_be(rx_frame, 6);
+
+ if (total_cells > 0) {
+ datalayer.battery.info.number_of_cells = total_cells;
+ }
+ break;
+ }
+
+ case 0x3160: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ temp_min_dC = read_s16_be(rx_frame, 6);
+ break;
+ }
+
+ case 0x3190: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ cell_max_mV = read_u16_be(rx_frame, 1);
+ cell_min_mV = read_u16_be(rx_frame, 3);
+ break;
+ }
+
+ case 0x3200: {
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ const uint16_t cell_charge_cutoff_mV = read_u16_be(rx_frame, 6);
+ if (cell_charge_cutoff_mV > 0) {
+ datalayer.battery.info.max_cell_voltage_mV = cell_charge_cutoff_mV;
+ }
+ break;
+ }
+
+ // Other frames (0x3170..0x3290, 0x3F00) are currently ignored.
+ default:
+ break;
+ }
+}
+
+void GrowattHvArkBattery::update_values() {
+ // Core measurements
+ datalayer.battery.status.voltage_dV = pack_voltage_dV;
+ datalayer.battery.status.current_dA = pack_current_dA;
+ datalayer.battery.status.temperature_max_dC = temp_max_dC;
+ datalayer.battery.status.temperature_min_dC = temp_min_dC;
+
+ // SOC/SOH scaling (0-100 -> 0-10000 / 0-10000)
+ datalayer.battery.status.real_soc = (uint16_t)soc_pct * 100;
+ datalayer.battery.status.soh_pptt = (uint16_t)soh_pct * 100;
+
+ // Limits
+ datalayer.battery.status.max_charge_current_dA = battery_no_charge ? 0 : max_charge_current_dA;
+ datalayer.battery.status.max_discharge_current_dA = battery_no_discharge ? 0 : max_discharge_current_dA;
+
+ if (max_charge_voltage_dV > 0) {
+ datalayer.battery.info.max_design_voltage_dV = max_charge_voltage_dV;
+ }
+ if (discharge_cutoff_voltage_dV > 0) {
+ datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage_dV;
+ }
+
+ // Cell voltages
+ datalayer.battery.status.cell_max_voltage_mV = cell_max_mV;
+ datalayer.battery.status.cell_min_voltage_mV = cell_min_mV;
+ // Populate at least two entries for UIs/logic that expect some per-cell values.
+ datalayer.battery.status.cell_voltages_mV[0] = cell_max_mV;
+ datalayer.battery.status.cell_voltages_mV[1] = cell_min_mV;
+
+ // Capacity: Use full capacity (bytes 2-3 in 0x3140, in 0.01Ah units) and scale by SOC.
+ // Remaining capacity = full_capacity * (SOC% / 100)
+ if (pack_voltage_dV > 0 && full_capacity_10mAh > 0) {
+ // Calculate total capacity in Wh: (0.01Ah * 10000 counts) * voltage(dV) / 1000
+ const uint32_t full_Wh = ((uint32_t)full_capacity_10mAh * (uint32_t)pack_voltage_dV) / 1000u;
+ datalayer.battery.info.total_capacity_Wh = full_Wh;
+
+ // Calculate remaining capacity based on SOC: full_capacity * SOC% / 100
+ // soc_pct is 0-100, so: (full_capacity_10mAh * soc_pct / 100) gives remaining in 0.01Ah
+ const uint32_t rem_capacity_10mAh = ((uint32_t)full_capacity_10mAh * (uint32_t)soc_pct) / 100u;
+ const uint32_t rem_Wh = (rem_capacity_10mAh * (uint32_t)pack_voltage_dV) / 1000u;
+ datalayer.battery.status.remaining_capacity_Wh = rem_Wh;
+ }
+
+ // Power limits (W): dA*dV/100 = (A*10)*(V*10)/100
+ const int32_t v_dV = (int32_t)pack_voltage_dV;
+ datalayer.battery.status.max_charge_power_W = (int32_t)datalayer.battery.status.max_charge_current_dA * v_dV / 100;
+ datalayer.battery.status.max_discharge_power_W =
+ (int32_t)datalayer.battery.status.max_discharge_current_dA * v_dV / 100;
+
+ // Contactor closing policy (conservative): allow only when awake and no fault indicated.
+ datalayer.system.status.battery_allows_contactor_closing = (!battery_sleeping) && (!battery_fault_present);
+}
+
+void GrowattHvArkBattery::transmit_can(unsigned long currentMillis) {
+ // Send 1 Hz command set.
+ if (currentMillis - previousMillis1000 < INTERVAL_1_S) {
+ return;
+ }
+ previousMillis1000 = currentMillis;
+
+ // --- 0x3010 Heartbeat ---
+ send_times++;
+ write_u16_be(PCS_3010, 0, send_times);
+ PCS_3010.data.u8[2] = 0x00; // Safety specification / region marker (0 = ignore)
+
+ // --- 0x3020 Control ---
+ PCS_3020.data.u8[0] = 0xAA; // Charging command
+ PCS_3020.data.u8[1] = 0xAA; // Discharging command
+ PCS_3020.data.u8[2] = 0x00; // Shielding external communication failure
+ PCS_3020.data.u8[3] = 0x00; // Clearing battery fault
+ PCS_3020.data.u8[4] = 0x00; // ISO detection command
+ PCS_3020.data.u8[7] = 0xAA; // Wake-up (exit sleeping)
+
+ // --- 0x3030 Time ---
+ // If no real RTC is available, we still provide a monotonically increasing counter.
+ epoch_time_s++;
+ write_u32_be(PCS_3030, 0, epoch_time_s);
+ PCS_3030.data.u8[7] = 0x01; // PCS working status: 01 = Operating
+
+ transmit_can_frame(&PCS_3010);
+ transmit_can_frame(&PCS_3020);
+ transmit_can_frame(&PCS_3030);
+}
diff --git a/Software/src/battery/GROWATT-HV-ARK-BATTERY.h b/Software/src/battery/GROWATT-HV-ARK-BATTERY.h
new file mode 100644
index 000000000..cf6f96e09
--- /dev/null
+++ b/Software/src/battery/GROWATT-HV-ARK-BATTERY.h
@@ -0,0 +1,78 @@
+#ifndef GROWATT_HV_ARK_BATTERY_H
+#define GROWATT_HV_ARK_BATTERY_H
+
+#include "../datalayer/datalayer.h"
+#include "CanBattery.h"
+
+// Battery-facing integration for Growatt HV batteries.
+//
+// Role:
+// - This implementation behaves as the "Storage Inverter" (PCS) side.
+// - It sends PCS->Battery frames (0x3010/0x3020/0x3030) at 1 Hz.
+// - It parses Battery->PCS frames (0x3110..0x3290, 0x3F00) and populates the datalayer.
+//
+// Protocol reference:
+// Communication Protocol of Growatt High Voltage Battery CAN (V1.10.x)
+// 500 kbit/s, extended identifier used in this codebase.
+class GrowattHvArkBattery : public CanBattery {
+ public:
+ // Use the default constructor to create the first or single battery.
+ GrowattHvArkBattery() {}
+
+ ~GrowattHvArkBattery() {}
+
+ void setup(void) override;
+ void handle_incoming_can_frame(CAN_frame rx_frame) override;
+ void update_values() override;
+ void transmit_can(unsigned long currentMillis) override;
+
+ static constexpr const char* Name = "Growatt HV ARK battery (battery-facing CAN)";
+
+ private:
+ // --- Outgoing (PCS -> Battery) ---
+ CAN_frame PCS_3010 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x3010,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame PCS_3020 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x3020,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame PCS_3030 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x3030,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ unsigned long previousMillis1000 = 0;
+ uint16_t send_times = 0;
+ uint32_t epoch_time_s = 0;
+
+ // --- Parsed battery state ---
+ uint16_t max_charge_voltage_dV = 0;
+ uint16_t discharge_cutoff_voltage_dV = 0;
+ int16_t max_charge_current_dA = 0;
+ int16_t max_discharge_current_dA = 0;
+
+ uint16_t pack_voltage_dV = 0;
+ int16_t pack_current_dA = 0;
+ int16_t temp_max_dC = 0;
+ int16_t temp_min_dC = 0;
+ uint8_t soc_pct = 0;
+ uint8_t soh_pct = 0;
+
+ uint16_t cell_max_mV = 0;
+ uint16_t cell_min_mV = 0;
+
+ uint16_t remaining_capacity_10mAh = 0; // unit 0.01Ah
+ uint16_t full_capacity_10mAh = 0; // unit 0.01Ah
+
+ bool battery_sleeping = false;
+ bool battery_fault_present = false;
+ bool battery_no_charge = false;
+ bool battery_no_discharge = false;
+};
+
+#endif
diff --git a/Software/src/battery/HYUNDAI-IONIQ-28-BATTERY-HTML.cpp b/Software/src/battery/HYUNDAI-IONIQ-28-BATTERY-HTML.cpp
index 3b013dc41..b52e168a4 100644
--- a/Software/src/battery/HYUNDAI-IONIQ-28-BATTERY-HTML.cpp
+++ b/Software/src/battery/HYUNDAI-IONIQ-28-BATTERY-HTML.cpp
@@ -3,7 +3,7 @@
String HyundaiIoniq28BatteryHtmlRenderer::get_status_html() {
String content;
- content += "12V voltage: " + String(batt.get_lead_acid_voltage() / 10.0, 1) + " ";
+ content += "12V voltage: " + String(batt.get_lead_acid_voltage() / 10.0f, 1) + " ";
content += "Temperature, power relay: " + String(batt.get_power_relay_temperature()) + " ";
content += "Batterymanagement mode: " + String(batt.get_battery_management_mode()) + " ";
content += "Isolation resistance: " + String(batt.get_isolation_resistance()) + " kOhm ";
diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp
index d6825caae..9a2943fa4 100644
--- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp
+++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp
@@ -50,7 +50,7 @@ void ImievCZeroIonBattery::
min_temp_cel = cell_temperatures[0]; // Initialize min with the first element of the array
for (int i = 1; i < m; i++) {
if (cell_temperatures[i] < min_temp_cel) {
- if (min_temp_cel != -50.00) { //-50.00 means this sensor not connected
+ if (min_temp_cel != -50.00f) { //-50.00 means this sensor not connected
min_temp_cel = cell_temperatures[i]; // Update max if we find a smaller element
}
}
@@ -61,11 +61,11 @@ void ImievCZeroIonBattery::
datalayer.battery.status.cell_voltages_mV[i] = (uint16_t)(cell_voltages[i] * 1000);
}
datalayer.battery.info.number_of_cells = 88;
- if (max_volt_cel > 2.2) { // Only update cellvoltage when we have a value
+ if (max_volt_cel > 2.2f) { // Only update cellvoltage when we have a value
datalayer.battery.status.cell_max_voltage_mV = (uint16_t)(max_volt_cel * 1000);
}
- if (min_volt_cel > 2.2) { // Only update cellvoltage when we have a value
+ if (min_volt_cel > 2.2f) { // Only update cellvoltage when we have a value
datalayer.battery.status.cell_min_voltage_mV = (uint16_t)(min_volt_cel * 1000);
}
@@ -104,8 +104,8 @@ void ImievCZeroIonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x373: //BMU message, 100ms - Pack Voltage and current
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- BMU_Current = ((((((rx_frame.data.u8[2] * 256.0) + rx_frame.data.u8[3])) - 32768)) * 0.01);
- BMU_PackVoltage = ((rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]) * 0.1);
+ BMU_Current = ((((((rx_frame.data.u8[2] * 256.0f) + rx_frame.data.u8[3])) - 32768)) * 0.01f);
+ BMU_PackVoltage = ((rx_frame.data.u8[4] * 256.0f + rx_frame.data.u8[5]) * 0.1f);
BMU_Power = (BMU_Current * BMU_PackVoltage);
break;
case 0x6e1: //BMU message, 25ms - Battery temperatures and voltages
@@ -120,17 +120,17 @@ void ImievCZeroIonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cmu_id = (rx_frame.data.u8[0] & 0x0f);
//
if (rx_frame.data.u8[1] != 0) { // Only update temperatures if value is available
- temp1 = rx_frame.data.u8[1] - 50.0;
+ temp1 = rx_frame.data.u8[1] - 50.0f;
}
if (rx_frame.data.u8[2] != 0) {
- temp2 = rx_frame.data.u8[1] - 50.0;
+ temp2 = rx_frame.data.u8[1] - 50.0f;
}
if (rx_frame.data.u8[3] != 0) {
- temp3 = rx_frame.data.u8[1] - 50.0;
+ temp3 = rx_frame.data.u8[1] - 50.0f;
}
- voltage1 = (((rx_frame.data.u8[4] * 256.0 + rx_frame.data.u8[5]) * 0.005) + 2.1);
- voltage2 = (((rx_frame.data.u8[6] * 256.0 + rx_frame.data.u8[7]) * 0.005) + 2.1);
+ voltage1 = (((rx_frame.data.u8[4] * 256.0f + rx_frame.data.u8[5]) * 0.005f) + 2.1f);
+ voltage2 = (((rx_frame.data.u8[6] * 256.0f + rx_frame.data.u8[7]) * 0.005f) + 2.1f);
voltage_index = ((cmu_id - 1) * 8 + (2 * pid_index));
temp_index = ((cmu_id - 1) * 6 + (2 * pid_index));
@@ -139,10 +139,10 @@ void ImievCZeroIonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
temp_index -= 3;
}
- if (voltage1 > 2.2) { // Only update cellvoltages incase we have a value
+ if (voltage1 > 2.2f) { // Only update cellvoltages incase we have a value
cell_voltages[voltage_index] = voltage1;
}
- if (voltage2 > 2.2) {
+ if (voltage2 > 2.2f) {
cell_voltages[voltage_index + 1] = voltage2;
}
diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h
index 90a3b6b16..a5a7e7132 100644
--- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h
+++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h
@@ -30,20 +30,20 @@ class ImievCZeroIonBattery : public CanBattery {
int temp_index = 0;
uint8_t BMU_SOC = 0;
int temp_value = 0;
- double temp1 = 0;
- double temp2 = 0;
- double temp3 = 0;
- double voltage1 = 0;
- double voltage2 = 0;
- double BMU_Current = 0;
- double BMU_PackVoltage = 0;
- double BMU_Power = 0;
- double cell_voltages[88]; //array with all the cellvoltages
- double cell_temperatures[88]; //array with all the celltemperatures
- double max_volt_cel = 3.70;
- double min_volt_cel = 3.70;
- double max_temp_cel = 20.00;
- double min_temp_cel = 19.00;
+ float temp1 = 0;
+ float temp2 = 0;
+ float temp3 = 0;
+ float voltage1 = 0;
+ float voltage2 = 0;
+ float BMU_Current = 0;
+ float BMU_PackVoltage = 0;
+ float BMU_Power = 0;
+ float cell_voltages[88]; //array with all the cellvoltages
+ float cell_temperatures[88]; //array with all the celltemperatures
+ float max_volt_cel = 3.70f;
+ float min_volt_cel = 3.70f;
+ float max_temp_cel = 20.00f;
+ float min_temp_cel = 19.00f;
};
#endif
diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
index 4bda3f0c5..6735541af 100644
--- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
+++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
@@ -57,12 +57,6 @@ CAN_frame ipace_keep_alive = {.FD = false,
.ID = 0x59e,
.data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/
-static void print_units(const char* header, int value, const char* units) {
- logging.printf(header);
- logging.printf("%d", value);
- logging.printf(units);
-}
-
void JaguarIpaceBattery::update_values() {
datalayer.battery.status.real_soc = HVBattAvgSOC * 100; //Add two decimals
diff --git a/Software/src/battery/KIA-64FD-BATTERY.cpp b/Software/src/battery/KIA-64FD-BATTERY.cpp
index 032b7c313..fa1ed20d3 100644
--- a/Software/src/battery/KIA-64FD-BATTERY.cpp
+++ b/Software/src/battery/KIA-64FD-BATTERY.cpp
@@ -1,6 +1,7 @@
#include "KIA-64FD-BATTERY.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
#include "../devboard/utils/logging.h"
#include "../system_settings.h"
@@ -76,7 +77,7 @@ void write_cell_voltages(CAN_frame rx_frame, int start, int length, int startCel
uint8_t Kia64FDBattery::calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
- crc = crc8_table[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
+ crc = crc8_table_SAE_J1850_ZER0[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
}
return crc;
}
@@ -112,12 +113,12 @@ void Kia64FDBattery::update_values() {
datalayer.battery.status.max_charge_power_W =
RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
} else { // No limits, max charging power allowed
- datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
+ datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
}
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
- //The allowed discharge power is not available. We hardcode this value for now
- datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
+ //The allowed discharge power is not available. We use user set value for now
+ datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
diff --git a/Software/src/battery/KIA-64FD-BATTERY.h b/Software/src/battery/KIA-64FD-BATTERY.h
index d714b0b4f..c0d0232e0 100644
--- a/Software/src/battery/KIA-64FD-BATTERY.h
+++ b/Software/src/battery/KIA-64FD-BATTERY.h
@@ -24,8 +24,6 @@ class Kia64FDBattery : public CanBattery {
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2950; //Battery is put into emergency stop if one cell goes below this value
- static const int MAXCHARGEPOWERALLOWED = 10000;
- static const int MAXDISCHARGEPOWERALLOWED = 10000;
static const int RAMPDOWN_SOC = 9000; // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
@@ -66,23 +64,6 @@ class Kia64FDBattery : public CanBattery {
unsigned long startMillis = 0;
uint8_t messageIndex = 0;
- const unsigned char crc8_table[256] = {
- // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
- 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
- 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
- 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
- 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
- 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
- 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
- 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
- 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
- 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
- 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
- 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
- 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
- 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
- 0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
-
// Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100;
diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp
index 516dfa43f..e85819bee 100644
--- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp
+++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp
@@ -2,6 +2,7 @@
#include
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
#include "../devboard/utils/logging.h"
#include "../system_settings.h"
@@ -107,7 +108,7 @@ void KiaEGmpBattery::set_voltage_minmax_limits() {
uint8_t KiaEGmpBattery::calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
- crc = crc8_table[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
+ crc = crc8_table_SAE_J1850_ZER0[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
}
return crc;
}
@@ -148,7 +149,7 @@ void KiaEGmpBattery::update_values() {
}
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
- //The allowed discharge power is not available. We hardcode this value for now
+ //The allowed discharge power is not available. We use user set value for now
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.h b/Software/src/battery/KIA-E-GMP-BATTERY.h
index f28b8529f..e0e3be1e7 100644
--- a/Software/src/battery/KIA-E-GMP-BATTERY.h
+++ b/Software/src/battery/KIA-E-GMP-BATTERY.h
@@ -80,22 +80,6 @@ class KiaEGmpBattery : public CanBattery {
int8_t heatertemp = 20;
bool set_voltage_limits = false;
- const unsigned char crc8_table[256] = {
- // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
- 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
- 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
- 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
- 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
- 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
- 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
- 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
- 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
- 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
- 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
- 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
- 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
- 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
- 0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
// Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100;
diff --git a/Software/src/battery/KIA-E-GMP-HTML.cpp b/Software/src/battery/KIA-E-GMP-HTML.cpp
index 67044dac5..e795c7535 100644
--- a/Software/src/battery/KIA-E-GMP-HTML.cpp
+++ b/Software/src/battery/KIA-E-GMP-HTML.cpp
@@ -4,7 +4,7 @@
String KiaEGMPHtmlRenderer::get_status_html() {
String content;
content += "Cells: " + String(datalayer.battery.info.number_of_cells) + "S ";
- content += "12V voltage: " + String(batt.get_battery_12V() / 10.0, 1) + " ";
+ content += "12V voltage: " + String(batt.get_battery_12V() / 10.0f, 1) + " ";
content += "Waterleakage: " + String(batt.get_waterleakageSensor()) + " ";
content += "Temperature, water inlet: " + String(batt.get_temperature_water_inlet()) + " ";
content += "Temperature, power relay: " + String(batt.get_powerRelayTemperature() * 2) + " ";
diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp
index af7ce7c48..f86cc0844 100644
--- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp
+++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp
@@ -159,7 +159,37 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_ECU_VERSION & 0x00FF);
poll_data_pid = 0;
}
- transmit_can_frame(&KIA64_7E4_poll);
+ if (datalayer.battery.status.bms_status == FAULT) {
+ //If we are in fault mode, request contactors to open via UDS
+ open_state++;
+ if (open_state == 1) { //Enter elevated mode
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[0] = 0x02;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[1] = 0x10;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[2] = 0x03;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[3] = 0x00;
+ } else if (open_state == 2) { //Request negative contactor OFF
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[0] = 0x04;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[1] = 0x2F;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[2] = 0xF0;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[3] = 0x32;
+ } else if (open_state == 3) { //Enter elevated mode
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[0] = 0x02;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[1] = 0x10;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[2] = 0x03;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[3] = 0x00;
+ } else if (open_state == 4) { //Request positive contactor OFF
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[0] = 0x04;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[1] = 0x2F;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[2] = 0xF0;
+ KIA64_7E4_OPEN_CONTACTOR_SEQUENCE.data.u8[3] = 0x31;
+ open_state = 0;
+ }
+ transmit_can_frame(&KIA64_7E4_OPEN_CONTACTOR_SEQUENCE);
+ set_event(EVENT_CONTACTOR_OPEN, 0);
+ } else {
+ //Normal operation, keep polling battery via UDS
+ transmit_can_frame(&KIA64_7E4_poll);
+ }
}
break;
case 0x7EC: //Data From polled PID group, BigEndian
@@ -439,8 +469,8 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
void KiaHyundai64Battery::transmit_can(unsigned long currentMillis) {
- if (!startedUp) {
- return; // Don't send any CAN messages towards battery until it has started up
+ if (!startedUp || (datalayer.battery.status.bms_status == FAULT)) {
+ return; // Don't send any CAN messages towards battery until it has started up. Also stop sending if we are in critical FAULT mode
}
//Send 100ms message
diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h
index 832c0e9a8..578bfed85 100644
--- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h
+++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h
@@ -70,11 +70,12 @@ class KiaHyundai64Battery : public CanBattery {
uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0;
uint16_t cellvoltages_mv[98];
- int16_t leadAcidBatteryVoltage = 120;
+ uint16_t leadAcidBatteryVoltage = 120;
int16_t batteryAmps = 0;
int16_t temperatureMax = 0;
int16_t temperatureMin = 0;
uint8_t poll_data_pid = 0;
+ uint8_t open_state = 0;
uint16_t pid_reply = 0;
bool holdPidCounter = false;
uint8_t CellVmaxNo = 0;
@@ -135,6 +136,11 @@ class KiaHyundai64Battery : public CanBattery {
.DLC = 8,
.ID = 0x2A1,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame KIA64_7E4_OPEN_CONTACTOR_SEQUENCE = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x7E4,
+ .data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame KIA64_7E4_poll = {.FD = false,
.ext_ID = false,
.DLC = 8,
diff --git a/Software/src/battery/KIA-HYUNDAI-64-HTML.h b/Software/src/battery/KIA-HYUNDAI-64-HTML.h
index bb6f12fa8..a1e3de8da 100644
--- a/Software/src/battery/KIA-HYUNDAI-64-HTML.h
+++ b/Software/src/battery/KIA-HYUNDAI-64-HTML.h
@@ -23,7 +23,7 @@ class KiaHyundai64HtmlRenderer : public BatteryHtmlRenderer {
content += "BMS serial number: " + String(readableSerialNumber) + " ";
content += "BMS software version: " + String(readableVersionNumber) + " ";
content += "Cells: " + String(data.total_cell_count) + " S ";
- content += "12V voltage: " + String(data.battery_12V / 10.0, 1) + " V ";
+ content += "12V voltage: " + String(data.battery_12V / 10.0f, 1) + " V ";
content += "Waterleakage: ";
if (data.waterleakageSensor == 0) {
content += " LEAK DETECTED ";
diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
index d17b4aca4..e63bdb9d5 100644
--- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
+++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
@@ -9,6 +9,22 @@
- We need to figure out how to keep the BMS alive. Most likely we need to send a specific CAN message
*/
+static uint8_t CalculateCRC8(const CAN_frame& frame) {
+ uint8_t crc = 0x00;
+
+ for (uint8_t i = 0; i < 8; i++) {
+ crc ^= frame.data.u8[i];
+ for (uint8_t b = 0; b < 8; b++) {
+ if (crc & 0x80) {
+ crc = (crc << 1) ^ 0x01;
+ } else {
+ crc <<= 1;
+ }
+ }
+ }
+ return crc;
+}
+
void KiaHyundaiHybridBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
@@ -33,8 +49,24 @@ void KiaHyundaiHybridBattery::
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage_mv;
+ if (battery_voltage > 3000) {
+ // If total pack voltage is above 300V, we can confirm that we are on 96S 8.9kWh PHEV battery
+ datalayer.battery.info.number_of_cells = 96;
+ datalayer.battery.info.total_capacity_Wh = 8900;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_PHEV_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_PHEV_DV;
+ }
+
+ if (battery_voltage < 2400) {
+ // If total pack voltage is under 240V, we can confirm that we are on 56S 1.56kWh HEV battery
+ datalayer.battery.info.number_of_cells = 56;
+ datalayer.battery.info.total_capacity_Wh = 1560;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_HEV_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_HEV_DV;
+ }
+
//Map all cell voltages to the global array
- memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
+ memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 96 * sizeof(uint16_t));
if (interlock_missing) {
set_event(EVENT_HVIL_FAILURE, 0);
@@ -76,19 +108,29 @@ void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
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);
+ cellvoltages_mv[0] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[1] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[2] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[3] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[4] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[5] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[6] = (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);
+ cellvoltages_mv[32] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[33] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[34] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[35] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[36] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[37] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[38] = (rx_frame.data.u8[7] * 20);
+ } else if (poll_data_pid == 4) { //21 04
+ cellvoltages_mv[64] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[65] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[66] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[67] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[68] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[69] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[70] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) { //21 05
}
break;
@@ -99,21 +141,29 @@ void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
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);
-
+ cellvoltages_mv[7] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[8] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[9] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[10] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[11] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[12] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[13] = (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);
+ cellvoltages_mv[39] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[40] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[41] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[42] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[43] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[44] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[45] = (rx_frame.data.u8[7] * 20);
+ } else if (poll_data_pid == 4) {
+ cellvoltages_mv[71] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[72] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[73] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[74] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[75] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[76] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[77] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
}
break;
@@ -121,20 +171,29 @@ void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
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);
+ cellvoltages_mv[14] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[15] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[16] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[17] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[18] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[19] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[20] = (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);
+ cellvoltages_mv[46] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[47] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[48] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[49] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[50] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[51] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[52] = (rx_frame.data.u8[7] * 20);
+ } else if (poll_data_pid == 4) {
+ cellvoltages_mv[78] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[79] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[80] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[81] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[82] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[83] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[84] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
}
break;
@@ -142,45 +201,65 @@ void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
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);
+ cellvoltages_mv[21] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[22] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[23] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[24] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[25] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[26] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[27] = (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);
+ cellvoltages_mv[53] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[54] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[55] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[56] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[57] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[58] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[59] = (rx_frame.data.u8[7] * 20);
+ } else if (poll_data_pid == 4) {
+ cellvoltages_mv[85] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[86] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[87] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[88] = (rx_frame.data.u8[4] * 20);
+ cellvoltages_mv[89] = (rx_frame.data.u8[5] * 20);
+ cellvoltages_mv[90] = (rx_frame.data.u8[6] * 20);
+ cellvoltages_mv[91] = (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) {
+ case 0x25: //Fifth datarow in PID group
+ if (poll_data_pid == 1) { //PHEV: 25 59 29 0 1 59 60 0
} 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);
+ cellvoltages_mv[28] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[29] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[30] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[31] = (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);
+ cellvoltages_mv[60] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[61] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[62] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[63] = (rx_frame.data.u8[4] * 20);
+ } else if (poll_data_pid == 4) {
+ cellvoltages_mv[92] = (rx_frame.data.u8[1] * 20);
+ cellvoltages_mv[93] = (rx_frame.data.u8[2] * 20);
+ cellvoltages_mv[94] = (rx_frame.data.u8[3] * 20);
+ cellvoltages_mv[95] = (rx_frame.data.u8[4] * 20);
} else if (poll_data_pid == 5) {
}
break;
- case 0x26: //Sixth datarow in PID group
+ case 0x26: //Sixth datarow in PID group
+ if (poll_data_pid == 1) { //PHEV: 26 0 77 C8 0 0 73 ED
+ }
break;
- case 0x27: //Seventh datarow in PID group
+ case 0x27: //Seventh datarow in PID group
+ if (poll_data_pid == 1) { //PHEV: 27 0 78 A3 47 6D 2 FF
+ }
break;
- case 0x28: //Eighth datarow in PID group
+ case 0x28: //Eighth datarow in PID group
+ if (poll_data_pid == 1) { //PHEV: 28 7F FF 7F FF 3 E8 0
+ }
break;
}
break;
@@ -191,6 +270,37 @@ void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
void KiaHyundaiHybridBattery::transmit_can(unsigned long currentMillis) {
+ //Send 10ms CAN message
+ if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
+ previousMillis10 = currentMillis;
+
+ KIA_200.data.u8[6] = (counter_200 & 0x0F) << 1;
+
+ // CRC8 โ Santa Fe style (byte 7)
+ KIA_200.data.u8[7] = 0x00;
+ KIA_200.data.u8[7] = CalculateCRC8(KIA_200);
+
+ transmit_can_frame(&KIA_200);
+
+ counter_200++;
+ if (counter_200 > 0x0F) {
+ counter_200 = 0;
+ }
+
+ KIA_2A1.data.u8[0] = 0x09;
+ transmit_can_frame(&KIA_2A1);
+
+ KIA_2F0.data.u8[0] = 0x0B;
+ transmit_can_frame(&KIA_2F0);
+ }
+
+ // Send 100ms CAN Message
+ if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
+ previousMillis100 = currentMillis;
+
+ transmit_can_frame(&KIA_523);
+ }
+
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
@@ -211,7 +321,8 @@ void KiaHyundaiHybridBattery::transmit_can(unsigned long currentMillis) {
KIA_7E4.data.u8[2] = 0x03;
transmit_can_frame(&KIA_7E4);
} else if (poll_data_pid == 4) {
- //Group 4 not polled
+ KIA_7E4.data.u8[2] = 0x04;
+ transmit_can_frame(&KIA_7E4);
} else if (poll_data_pid == 5) {
KIA_7E4.data.u8[2] = 0x05;
KIA_7E4.data.u8[3] = 0x04;
@@ -224,9 +335,9 @@ void KiaHyundaiHybridBattery::setup(void) { // Performs one time setup at start
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
- datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
- datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
- datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
+ datalayer.battery.info.number_of_cells = 56; // Startup in 56S mode, switch later
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_PHEV_DV; //Startup with widest range
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_HEV_DV; //Autodetect later
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
index e1b5366a3..77f5e01cc 100644
--- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
+++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
@@ -11,26 +11,31 @@ class KiaHyundaiHybridBattery : public CanBattery {
static constexpr const char* Name = "Kia/Hyundai Hybrid";
private:
- static const int MAX_PACK_VOLTAGE_DV = 2550; //5000 = 500.0V
- static const int MIN_PACK_VOLTAGE_DV = 1700;
+ static const int MAX_PACK_VOLTAGE_HEV_DV = 2550;
+ static const int MIN_PACK_VOLTAGE_HEV_DV = 1700;
+ static const int MAX_PACK_VOLTAGE_PHEV_DV = 4040;
+ static const int MIN_PACK_VOLTAGE_PHEV_DV = 3000;
static const int MAX_CELL_DEVIATION_MV = 100;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
- unsigned long previousMillis1000 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
+ unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
+ uint8_t counter_200 = 0;
uint16_t SOC = 0;
uint16_t SOC_display = 0;
bool interlock_missing = false;
int16_t battery_current = 0;
uint8_t battery_current_high_byte = 0;
- uint16_t battery_voltage = 0;
+ uint16_t battery_voltage = 2500;
uint32_t available_charge_power = 0;
uint32_t available_discharge_power = 0;
int8_t battery_module_max_temperature = 0;
int8_t battery_module_min_temperature = 0;
uint8_t poll_data_pid = 0;
- uint16_t cellvoltages_mv[98];
+ uint16_t cellvoltages_mv[96];
uint16_t min_cell_voltage_mv = 3700;
uint16_t max_cell_voltage_mv = 3700;
@@ -44,6 +49,29 @@ class KiaHyundaiHybridBattery : public CanBattery {
.DLC = 8,
.ID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame KIA_200 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x200,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ CAN_frame KIA_2A1 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x2A1,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ CAN_frame KIA_2F0 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x2F0,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ CAN_frame KIA_523 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x523,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif
diff --git a/Software/src/battery/MEB-BATTERY.cpp b/Software/src/battery/MEB-BATTERY.cpp
index 7238f5f86..d02d75d05 100644
--- a/Software/src/battery/MEB-BATTERY.cpp
+++ b/Software/src/battery/MEB-BATTERY.cpp
@@ -203,26 +203,26 @@ void MebBattery::
datalayer_battery->status.real_soc = battery_SOC * 5; //*0.05*100
- datalayer_battery->status.voltage_dV = BMS_voltage * 2.5; // *0.25*10
+ datalayer_battery->status.voltage_dV = BMS_voltage * 2.5f; // *0.25*10
datalayer_battery->status.current_dA = (BMS_current - 16300); // 0.1 * 10
if (nof_cells_determined) {
datalayer_battery->info.total_capacity_Wh =
- ((float)datalayer_battery->info.number_of_cells) * 3.67 * ((float)BMS_capacity_ah) * 0.2 * 1.02564;
+ ((float)datalayer_battery->info.number_of_cells) * 3.67f * ((float)BMS_capacity_ah) * 0.2f * 1.02564f;
// The factor 1.02564 = 1/0.975 is to correct for bottom 2.5% which is reported by the remaining_capacity_Wh,
// but which is not actually usable, but if we do not include it, the remaining_capacity_Wh can be larger than
// the total_capacity_Wh.
// 0.935 and 0.9025 are the different conversions for different battery sizes to go from design capacity to
// total_capacity_Wh calculated above.
- int Wh_max = 61832 * 0.935; // 108 cells
+ int Wh_max = 61832 * 0.935f; // 108 cells
if (datalayer_battery->info.number_of_cells <= 84)
- Wh_max = 48091 * 0.9025;
+ Wh_max = 48091 * 0.9025f;
else if (datalayer_battery->info.number_of_cells <= 96)
- Wh_max = 82442 * 0.9025;
+ Wh_max = 82442 * 0.9025f;
if (BMS_capacity_ah > 0)
- datalayer_battery->status.soh_pptt = 10000 * datalayer_battery->info.total_capacity_Wh / (Wh_max * 1.02564);
+ datalayer_battery->status.soh_pptt = 10000 * datalayer_battery->info.total_capacity_Wh / (Wh_max * 1.02564f);
}
datalayer_battery->status.remaining_capacity_Wh = usable_energy_amount_Wh * 5;
@@ -444,7 +444,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (mux) {
case 0: // Temperatures 1-56. Value is 0xFD if sensor not present
for (uint8_t i = 0; i < 56; i++) {
- datalayer_extended.meb.celltemperature_dC[i] = (rx_frame.data.u8[i + 1] * 5) - 400;
+ datalayer_extended.meb.celltemperature_dC[i] = ((int16_t)rx_frame.data.u8[i + 1] * 5) - 400;
}
break;
/*
@@ -881,6 +881,14 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
if (datalayer.battery.status.real_bms_status != BMS_FAULT) {
datalayer.battery.status.real_bms_status = BMS_DISCONNECTED;
datalayer.system.status.battery_allows_contactor_closing = false;
+
+ // Set the link voltage back to 0, so that when the BMS comes back, it
+ // doesn't immediately skip the precharge.
+ BMS_voltage_intermediate = 0;
+ datalayer_extended.meb.BMS_voltage_intermediate_dV = 0;
+
+ // Reset the HV requested state so that we don't skip the precharge.
+ hv_requested = false;
}
}
// Send 10ms CAN Message
@@ -944,15 +952,28 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
//HV request and DC/DC control lies in 0x503
- if ((!datalayer.system.settings.equipment_stop_active) && datalayer.battery.status.real_bms_status != BMS_FAULT &&
+ if ((!datalayer.system.info.equipment_stop_active) && datalayer.battery.status.real_bms_status != BMS_FAULT &&
(datalayer.battery.status.real_bms_status == BMS_ACTIVE ||
(datalayer.battery.status.real_bms_status == BMS_STANDBY &&
(hv_requested ||
(datalayer.battery.status.voltage_dV > 200 && datalayer_extended.meb.BMS_voltage_intermediate_dV > 0 &&
labs(((int32_t)datalayer.battery.status.voltage_dV) -
((int32_t)datalayer_extended.meb.BMS_voltage_intermediate_dV)) < 200))))) {
- hv_requested = true;
- datalayer.system.settings.start_precharging = false;
+ // We are either:
+ // - in BMS_ACTIVE state (contactors closed, normal operation)
+ // - or in BMS_STANDBY state, ready to request HV from the battery (our precharge is within 20V)
+ // - or in BMS_STANDBY state, having already requested HV (hv_requested = true)
+
+ if (datalayer.battery.status.real_bms_status != BMS_ACTIVE) {
+ // We're still awaiting contactor closure, so record that we've
+ // requested HV, so that we keep doing so even if the precharge voltage
+ // wavers.
+ hv_requested = true;
+ }
+
+ // We can stop precharging now.
+ datalayer.system.info.start_precharging = false;
+
if (MEB_503.data.u8[3] == BMS_TARGET_HV_OFF) {
logging.printf("MEB: Requesting HV\n");
}
@@ -970,11 +991,11 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
MEB_503.data.u8[5] = 0x82; // Bordnetz Active
MEB_503.data.u8[6] = 0xE0; // Request emergency shutdown HV system == 0, false
} else if ((first_can_msg > 0 && currentMillis > first_can_msg + 1000 && BMS_mode != 7) ||
- datalayer.system.settings.equipment_stop_active) { //FAULT STATE, open contactors
+ datalayer.system.info.equipment_stop_active) { //FAULT STATE, open contactors
if (datalayer.battery.status.bms_status != FAULT && datalayer.battery.status.real_bms_status == BMS_STANDBY &&
- !datalayer.system.settings.equipment_stop_active) {
- datalayer.system.settings.start_precharging = true;
+ !datalayer.system.info.equipment_stop_active) {
+ datalayer.system.info.start_precharging = true;
}
if (MEB_503.data.u8[3] != BMS_TARGET_HV_OFF) {
@@ -1202,6 +1223,45 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
transmit_obd_can_frame(0x18DA05F1, can_config.battery, true);
}
+
+ static auto last_real_bms_status = datalayer.battery.status.real_bms_status;
+ static auto last_start_precharging = datalayer.system.info.start_precharging;
+ static auto last_hv_requested = hv_requested;
+ static auto last_voltage_dV = datalayer.battery.status.voltage_dV;
+ static auto last_BMS_voltage_intermediate_dV = datalayer_extended.meb.BMS_voltage_intermediate_dV;
+ static auto BMS_mode = datalayer_extended.meb.BMS_mode;
+
+ if (last_real_bms_status != datalayer.battery.status.real_bms_status) {
+ logging.printf("MEB: BMS status %d -> %d\n", last_real_bms_status, datalayer.battery.status.real_bms_status);
+ last_real_bms_status = datalayer.battery.status.real_bms_status;
+ }
+
+ if (last_start_precharging != datalayer.system.info.start_precharging) {
+ logging.printf("MEB: Start precharging %d -> %d\n", last_start_precharging,
+ datalayer.system.info.start_precharging);
+ last_start_precharging = datalayer.system.info.start_precharging;
+ }
+
+ if (last_hv_requested != hv_requested) {
+ logging.printf("MEB: HV requested %d -> %d\n", last_hv_requested, hv_requested);
+ last_hv_requested = hv_requested;
+ }
+
+ if (last_voltage_dV != datalayer.battery.status.voltage_dV) {
+ logging.printf("MEB: Voltage dV %d -> %d\n", last_voltage_dV, datalayer.battery.status.voltage_dV);
+ last_voltage_dV = datalayer.battery.status.voltage_dV;
+ }
+
+ if (last_BMS_voltage_intermediate_dV != datalayer_extended.meb.BMS_voltage_intermediate_dV) {
+ logging.printf("MEB: BMS Voltage intermediate dV %d -> %d\n", last_BMS_voltage_intermediate_dV,
+ datalayer_extended.meb.BMS_voltage_intermediate_dV);
+ last_BMS_voltage_intermediate_dV = datalayer_extended.meb.BMS_voltage_intermediate_dV;
+ }
+
+ if (BMS_mode != datalayer_extended.meb.BMS_mode) {
+ logging.printf("MEB: BMS mode %d -> %d\n", BMS_mode, datalayer_extended.meb.BMS_mode);
+ BMS_mode = datalayer_extended.meb.BMS_mode;
+ }
}
void MebBattery::setup(void) { // Performs one time setup at startup
diff --git a/Software/src/battery/MEB-HTML.h b/Software/src/battery/MEB-HTML.h
index c6cfaa83c..9783af2e1 100644
--- a/Software/src/battery/MEB-HTML.h
+++ b/Software/src/battery/MEB-HTML.h
@@ -178,7 +178,7 @@ class MebHtmlRenderer : public BatteryHtmlRenderer {
default:
content += "?";
}
- content += " Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) +
+ content += " Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0f, 1) +
"V) status: ";
switch (datalayer_extended.meb.BMS_status_voltage_free) {
case 0:
@@ -225,7 +225,7 @@ class MebHtmlRenderer : public BatteryHtmlRenderer {
default:
content += "?";
}
- content += " BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + " ";
+ content += " BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0f, 1) + " ";
content += datalayer_extended.meb.BMS_OBD_MIL ? "OBD MIL: ON! " : "OBD MIL: Off ";
content +=
datalayer_extended.meb.BMS_error_lamp_req ? "Red error lamp: ON! " : "Red error lamp: Off ";
diff --git a/Software/src/battery/MG-5-BATTERY.cpp b/Software/src/battery/MG-5-BATTERY.cpp
index c5cd6cefc..5561c19fa 100644
--- a/Software/src/battery/MG-5-BATTERY.cpp
+++ b/Software/src/battery/MG-5-BATTERY.cpp
@@ -1,8 +1,13 @@
#include "MG-5-BATTERY.h"
+#include
+#include //For unit test
#include //For unit test
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
+#include "../devboard/utils/logging.h"
+
+#ifndef SMALL_FLASH_DEVICE
/* TODO:
- Get contactor closing working
@@ -11,43 +16,256 @@
- Most important ones
*/
-void Mg5Battery::
- update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
+inline const char* getBMStatus(int index) {
+ switch (index) {
+ case 0:
+ return "INVALID";
+ case 1:
+ return "DISCONNECTED";
+ case 2:
+ return "PRECHARGE";
+ case 3:
+ return "CONNECTED";
+ //case 6:
+ // return "AC CHARGING";
+ //case 7:
+ // return "DC CHARGING";
+ case 15:
+ return "ISOLATION FAULT";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void Mg5Battery::update_soc(uint16_t soc_times_ten) {
+
+#if MG5_USE_FULL_CAPACITY
+ // The SoC hits 100% at 4.1V/cell. To get the full 4.2V/cell we need to use
+ // voltage instead for the last bit.
+
+ if (cellVoltageValidTime == 0) {
+ // We don't have a recent cell max voltage reading, so can't do
+ // voltage-based SoC.
+ } else if (soc_times_ten > 900 && datalayer.battery.status.cell_max_voltage_mV < 4000) {
+ // Something is wrong with our max cell voltage reading (it is too low), so
+ // don't trust it - we'll just let the SoC hit 100%.
+ } else if (soc_times_ten == 1000 && datalayer.battery.status.cell_max_voltage_mV >= 4100) {
+ // We've hit 100%, so use voltage-based-SoC calculation for the last bit.
+
+ // We usually hit 92% at ~369V, and the pack max is 378V.
+
+ // Scale so that 100% becomes 92%
+ soc_times_ten = (uint16_t)(((uint32_t)soc_times_ten * 9200) / 10000);
+
+ // Add on the last 100mV as the last 8% of SoC.
+ soc_times_ten += (uint16_t)((((uint32_t)datalayer.battery.status.cell_max_voltage_mV - 4100) * 800) / 1000);
+ if (soc_times_ten > 1000) {
+ soc_times_ten = 1000; // Don't let it go above 100%
+ }
+ } else {
+ // Scale so that 100% becomes 92%
+ soc_times_ten = (uint16_t)(((uint32_t)soc_times_ten * 9200) / 10000);
+ }
+#endif
+
+ // Set the state of charge in the datalayer
+ datalayer.battery.status.real_soc = soc_times_ten * 10;
+
+ RealSoC = datalayer.battery.status.real_soc / 100;
+
+ // Calculate the remaining capacity.
+ tempfloat = datalayer.battery.info.total_capacity_Wh * (RealSoC) / 100;
+ if (tempfloat > 0) {
+ datalayer.battery.status.remaining_capacity_Wh = tempfloat;
+ } else {
+ datalayer.battery.status.remaining_capacity_Wh = 0;
+ }
+
+ //#if MG5_USE_FULL_CAPACITY
+
+ // Calculate the maximum charge power. Taper the charge power between 90% and 100% SoC, as 100% SoC is approached
+ if (RealSoC < StartChargeTaper) {
+ datalayer.battery.status.max_charge_power_W = MaxChargePower;
+ } else if (RealSoC >= 100) {
+ datalayer.battery.status.max_charge_power_W = TricklePower;
+ } else {
+ //Taper the charge to the Trickle value. The shape and start point of the taper is set by the constants
+ datalayer.battery.status.max_charge_power_W =
+ (MaxChargePower * pow(((100 - RealSoC) / (100 - StartChargeTaper)), ChargeTaperExponent)) + TricklePower;
+ }
+
+ // Calculate the maximum discharge power. Taper the discharge power between 10% and Min% SoC, as Min% SoC is approached
+ if (RealSoC > StartDischargeTaper) {
+ datalayer.battery.status.max_discharge_power_W = MaxDischargePower;
+ } else if (RealSoC < MinSoC) {
+ datalayer.battery.status.max_discharge_power_W = TricklePower;
+ } else {
+ //Taper the charge to the Trickle value. The shape and start point of the taper is set by the constants
+ datalayer.battery.status.max_discharge_power_W =
+ (MaxDischargePower * pow(((RealSoC - MinSoC) / (StartDischargeTaper - MinSoC)), DischargeTaperExponent)) +
+ TricklePower;
+ }
+
+ //#endif
+}
- datalayer.battery.status.real_soc;
+void Mg5Battery::print_formatted_dtc(uint32_t dtc24, uint8_t status) {
+ // DTC bytes: A B C (24 bits). SAE letter from top 2 bits of A.
+ uint8_t A = (dtc24 >> 16) & 0xFF;
+ uint8_t B = (dtc24 >> 8) & 0xFF;
+ // uint8_t C = dtc24 & 0xFF; // often a failure-type byte; keep if you need it
- datalayer.battery.status.voltage_dV;
+ const char sysMap[4] = {'P', 'C', 'B', 'U'};
+ char sys = sysMap[(A & 0xC0) >> 6];
- datalayer.battery.status.current_dA;
+ // Four digits: D1 D2 D3 D4 from the remaining nibbles of A and B
+ uint8_t d1 = (A & 0x30) >> 4;
+ uint8_t d2 = (A & 0x0F);
+ uint8_t d3 = (B & 0xF0) >> 4;
+ uint8_t d4 = (B & 0x0F);
- datalayer.battery.info.total_capacity_Wh;
+ logging.print("DTC ");
+ logging.print(sys);
+ logging.print(d1);
+ logging.print(d2, HEX);
+ logging.print(d3, HEX);
+ logging.print(d4, HEX);
- datalayer.battery.status.remaining_capacity_Wh;
+ // print hex byte first
+ logging.print(" status=0x");
+ logging.print(status, HEX);
+ logging.print(" [");
- datalayer.battery.status.max_discharge_power_W;
+ bool first = true;
+ auto add = [&](const char* s) {
+ if (!first)
+ logging.print(", ");
+ logging.print(s);
+ first = false;
+ };
- datalayer.battery.status.max_charge_power_W;
+ if (status & 0x08)
+ add("Confirmed");
+ if (status & 0x04)
+ add("Pending");
+ if (status & 0x20)
+ add("FailSinceClear");
+ if (status & 0x01)
+ add("Fail");
+ if (status & 0x10)
+ add("NotCompSinceClear");
+ if (status & 0x40)
+ add("NotCompThisCycle");
+ if (status & 0x80)
+ add("MIL");
+ if (status & 0x02)
+ add("FailThisCycle");
- datalayer.battery.status.temperature_min_dC;
+ if (first)
+ logging.print("NoFlags");
+ logging.println("]");
+}
+
+void Mg5Battery::startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) {
+ gUDSContext.UDS_inProgress = true;
+ gUDSContext.UDS_expectedLength = totalLength;
+ gUDSContext.UDS_bytesReceived = 0;
+ gUDSContext.UDS_moduleID = moduleID;
+ gUDSContext.receivedInBatch = 0;
+ memset(gUDSContext.UDS_buffer, 0, sizeof(gUDSContext.UDS_buffer));
+ gUDSContext.UDS_lastFrameMillis = millis(); // if you want to track timeouts
+}
+
+bool Mg5Battery::storeUDSPayload(const uint8_t* payload, uint8_t length) {
+ if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) {
+ // Overflow => abort
+ gUDSContext.UDS_inProgress = false;
+#ifdef DEBUG_LOG
+ logging.println("UDS Payload Overflow");
+#endif // DEBUG_LOG
+ return false;
+ }
+ memcpy(&gUDSContext.UDS_buffer[gUDSContext.UDS_bytesReceived], payload, length);
+ gUDSContext.UDS_bytesReceived += length;
+ gUDSContext.UDS_lastFrameMillis = millis();
+
+ // If weโve reached or exceeded the expected length, mark complete
+ if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) {
+ gUDSContext.UDS_inProgress = false;
+ logging.println("Recived all expected UDS bytes");
+ }
+ return true;
+}
+
+bool Mg5Battery::isUDSMessageComplete() {
+ return (!gUDSContext.UDS_inProgress && gUDSContext.UDS_bytesReceived > 0);
+}
+
+void Mg5Battery::
+ update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
+
+ //all data are already update when it is received via CAN
- datalayer.battery.status.temperature_max_dC;
+ //reduce timout valeue for cell voltage timeout, reduce by 1 every second
+ if (cellVoltageValidTime > 0) {
+ cellVoltageValidTime--;
+ }
}
void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ //datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
- case 0x171: //Following messages were detected on a MG5 battery BMS
+ case 0x297: { //BMS state
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
+
+ // Contains battery status in rx_frame.data.u8[1]
+ // Presumed mapping:
+ // 1 = disconnected
+ // 2 = precharge
+ // 3 = connected
+ // 15 = isolation fault
+ // 0/8 = checking
+
+ if (rx_frame.data.u8[1] != previousState) {
+ logging.print("MG5: Battery status changed to: ");
+ logging.println(getBMStatus(rx_frame.data.u8[1]));
+ }
+
+ if (rx_frame.data.u8[1] == 0xf && previousState != 0xf) {
+ // Isolation fault, set event
+ set_event(EVENT_BATTERY_ISOLATION, rx_frame.data.u8[0]);
+ } else if (rx_frame.data.u8[1] != 0xf && previousState == 0xf) {
+ // Isolation fault has cleared, clear event
+ clear_event(EVENT_BATTERY_ISOLATION);
+ }
+
+ if (rx_frame.data.u8[1] == 0x03 && previousState != 0x03) {
+ datalayer.system.status.battery_allows_contactor_closing = true; //signal to the UI that contactors are closed
+ } else {
+ datalayer.system.status.battery_allows_contactor_closing = false;
+ }
+
+ previousState = rx_frame.data.u8[1];
break;
+ }
case 0x172:
break;
case 0x173:
+ // Contains cell min/max voltages
+ v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ if (v > 0 && v < 0x2000) {
+ datalayer.battery.status.cell_max_voltage_mV = v;
+ cellVoltageValidTime = CELL_VOLTAGE_TIMEOUT;
+ }
+ v = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
+ if (v > 0 && v < 0x2000) {
+ datalayer.battery.status.cell_min_voltage_mV = v;
+ }
break;
case 0x293:
break;
case 0x295:
break;
- case 0x297:
- break;
case 0x29B:
break;
case 0x29C:
@@ -55,6 +273,15 @@ void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x2A0:
break;
case 0x2A2:
+
+ if (rx_frame.data.u8[0] < 0xfe) {
+ // Max cell temp
+ datalayer.battery.status.temperature_max_dC = ((rx_frame.data.u8[0] << 8) / 50) - 400;
+ }
+ if (rx_frame.data.u8[5] < 0xfe) {
+ // Min cell temp
+ datalayer.battery.status.temperature_min_dC = ((rx_frame.data.u8[5] << 8) / 50) - 400;
+ }
break;
case 0x322:
break;
@@ -68,7 +295,31 @@ void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x3AB:
break;
- case 0x3AC:
+ case 0x3AC: // battery summary: SoC, HV voltage, HV current
+
+ // Contains SoCs, voltage, current. Is emitted by both PTCAN and HybridCAN, but
+ // There does not seem to be 2 SOC"s present like in MG HS battery, so we just read the meassages from the PTCAN bus
+
+ if ((((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]) != 0) {
+ // 3AC message contains a nonzero voltage (so must have come from PTCAN)
+
+ // battery voltage
+ v = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]);
+ if (v > 0 && v < 4000) {
+ datalayer.battery.status.voltage_dV = v * 2.5;
+ }
+ // Current
+ v = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
+ if (v > 0 && v < 0xf000) {
+ datalayer.battery.status.current_dA = -(v - 20000) * 0.5;
+ }
+
+ // SOC
+ soc = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
+ if (soc < 1022) {
+ update_soc(soc);
+ }
+ }
break;
case 0x3B8:
break;
@@ -77,6 +328,14 @@ void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x3BC:
break;
case 0x3BE:
+
+ // Per-cell voltages and temps
+ cell_id = rx_frame.data.u8[5];
+ if (cell_id < 96) {
+ v = 1000 + ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
+ datalayer.battery.status.cell_voltages_mV[cell_id] = v < 10000 ? v : 0;
+ // cell temperature is rx_frame.data.u8[1]-40 but BE doesn't use it
+ }
break;
case 0x3C0:
break;
@@ -92,22 +351,372 @@ void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x620:
break;
+ case 0x789: { // response from UDS diagnostic service (ISO-TP)
+
+ uint8_t pciByte = rx_frame.data.u8[0]; // 0x10/0x21/etc.
+ uint8_t pciType = pciByte >> 4; // 0=SF,1=FF,2=CF,3=FC
+ uint8_t pciLower = pciByte & 0x0F; // length nibble or sequence
+
+ switch (pciType) {
+ case 0x0: { // Single Frame (SF)
+ uint8_t sfLength = pciLower;
+ (void)sfLength; // if unused, silence warning
+
+ uint8_t sid = rx_frame.data.u8[1];
+
+ // Positive response to Diagnostic Session Control (0x10 -> 0x50)
+ if (sid == 0x50) {
+ uint8_t sub = rx_frame.data.u8[2]; // 0x01=Default, 0x03=Extended, etc.
+
+ if (sub == 0x03 || sub == 0x01) {
+ logging.print("entered ");
+ logging.println(sub == 0x03 ? "extended diagnostic session" : "default session");
+
+ // mark this UDS transaction done
+ uds_tx_in_flight = false;
+ }
+ break;
+ }
+
+ // Positive response to clear DTC request
+ if (sid == 0x54) {
+ // [0] PCI
+ // [1] SID (0x54)
+ // [2] DTC high byte (0x02)
+ // [3] DTC mid byte (0x93)
+ // [4] DTC low byte (0x00)
+ uint8_t b2 = rx_frame.data.u8[2];
+ uint8_t b3 = rx_frame.data.u8[3];
+ uint8_t b4 = rx_frame.data.u8[4];
+
+ bool allDtcCleared = ((b2 == 0xFF && b3 == 0xFF && b4 == 0xFF) || (b2 == 0xAA && b3 == 0xAA && b4 == 0xAA));
+
+ if (allDtcCleared) {
+ logging.println("UDS: positive response, ALL DTCs cleared");
+ userRequestClearDTC = false; // reset the request
+ } else {
+ logging.print("UDS: positive ClearDTC response, group/DTC = ");
+ logging.print(b2, HEX);
+ logging.print(' ');
+ logging.print(b3, HEX);
+ logging.print(' ');
+ logging.print(b4, HEX);
+ logging.println();
+ }
+
+ uds_tx_in_flight = false;
+ break;
+ }
+
+ // Negative response
+ if (sid == 0x7F) {
+ uint8_t origSid = rx_frame.data.u8[2];
+ uint8_t nrc = rx_frame.data.u8[3];
+
+ logging.print("UDS negative response to 0x");
+ logging.print(origSid, HEX);
+ logging.print(": NRC=0x");
+ logging.print(nrc, HEX);
+ logging.println();
+
+ if (nrc != 0x78) { // 0x78 = Response Pending; otherwise weโre done with this tx
+ uds_tx_in_flight = false;
+ }
+ break;
+ }
+
+ if (sid == 0x62) { // ReadDataByIdentifier response
+ uint16_t did = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
+
+ // signal that UDS transaction is complete
+ uds_tx_in_flight = false;
+
+ switch (did) {
+ case 0xB041: {
+ float v = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ (void)v;
+ //logging.print("single frame UDS ReadDataByIdentifier bus voltage: ");
+ //logging.println(v); //scaling to be checked
+
+ break;
+ }
+ case 0xB042: {
+ float v = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.25f;
+ (void)v;
+ //logging.print("single frame UDS ReadDataByIdentifier battery voltage: ");
+ //logging.println(v);
+ break;
+ }
+ case 0xB043: {
+ float i = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5])) * 1;
+ (void)i;
+ //logging.print("single frame UDS ReadDataByIdentifier battery current: ");
+ //logging.println(i); //scaling to be checked
+ break;
+ }
+ case 0xB045: {
+ float r = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 1;
+ (void)r;
+ //logging.print("single frame UDS ReadDataByIdentifier battery resistance: ");
+ //logging.println(r); //scaling to be checked
+ break;
+ }
+ case 0xB046: {
+ float soc = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1f;
+ (void)soc;
+ //logging.print("single frame UDS ReadDataByIdentifier battery state of charge: ");
+ //logging.println(soc);
+ break;
+ }
+ case 0xB047: {
+ uint8_t e = rx_frame.data.u8[4];
+ (void)e;
+ //logging.print("single frame UDS ReadDataByIdentifier error: ");
+ //logging.println(e);
+ break;
+ }
+ case 0xB048: {
+ uint8_t s = rx_frame.data.u8[4];
+ (void)s;
+ //logging.print("single frame UDS ReadDataByIdentifier status: ");
+ //logging.println (getBMStatus(s));
+ break;
+ }
+ case 0xB049: {
+ uint8_t rB = rx_frame.data.u8[4];
+ (void)rB;
+ //logging.print("single frame UDS ReadDataByIdentifier relay B: ");
+ //logging.println (rB); //normally 1, seems to go to 0 during change of contactor state
+ break;
+ }
+ case 0xB04A: {
+ uint8_t rG = rx_frame.data.u8[4];
+ (void)rG;
+ //logging.print("single frame UDS ReadDataByIdentifier relay G: ");
+ //logging.println (rG); //normally 1, seems to go to 0 during change of contactor state
+ break;
+ }
+ case 0xB052: {
+ uint8_t rP = rx_frame.data.u8[4];
+ (void)rP;
+ //logging.print("single frame UDS ReadDataByIdentifier relay P: ");
+ //logging.println (rP); //normally 1, seems to go to 0 during change of contactor state
+ break;
+ }
+ case 0xB056: {
+ float t = rx_frame.data.u8[4] - 100;
+ (void)t;
+ //logging.print("single frame UDS ReadDataByIdentifier BATTERY TEMP: ");
+ //logging.println (t);
+ break;
+ }
+ case 0xB058: {
+ float mv = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.001f;
+ (void)mv;
+ //logging.print("single frame UDS ReadDataByIdentifier max cell voltage: ");
+ //logging.println (mv);
+ break;
+ }
+ case 0xB059: {
+ float nv = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.001f;
+ (void)nv;
+ //logging.print("single frame UDS ReadDataByIdentifier min cell voltage: ");
+ //logging.println (nv);
+ break;
+ }
+ case 0xB05C: {
+ float c = rx_frame.data.u8[4] - 100;
+ (void)c;
+ //logging.print("single frame UDS ReadDataByIdentifier coolant temperature: ");
+ //logging.println (c);
+ break;
+ }
+ case 0xB061: {
+ float soh = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ (void)soh;
+ //logging.print("single frame UDS ReadDataByIdentifier state of health: ");
+ //logging.println (soh*0.01f);
+ datalayer.battery.status.soh_pptt = soh;
+ break;
+ }
+ case 0xB06D: {
+ uint32_t t = (uint32_t(rx_frame.data.u8[4]) << 24) | (uint32_t(rx_frame.data.u8[5]) << 16) |
+ (uint32_t(rx_frame.data.u8[6]) << 8) | uint32_t(rx_frame.data.u8[7]);
+ (void)t;
+ //logging.print("single frame UDS ReadDataByIdentifier BMS time: ");
+ //logging.println (t); //scaling to be checked
+
+ break;
+ } break;
+ }
+ }
+ break;
+ }
+
+ case 0x1: { // First Frame (FF)
+ uint16_t totalLength = (uint16_t(pciLower) << 8) | rx_frame.data.u8[1];
+
+ // DTC ReadDTCInformation response (0x59 0x02) at bytes [2..3]
+ if (rx_frame.DLC >= 4 && rx_frame.data.u8[2] == 0x59 && rx_frame.data.u8[3] == 0x02) {
+ startUDSMultiFrameReception(totalLength, 0x59);
+ // FF payload for normal addressing starts at data[2]
+ uint8_t avail = (rx_frame.DLC > 2) ? (rx_frame.DLC - 2) : 0;
+ uint16_t remain = gUDSContext.UDS_expectedLength - gUDSContext.UDS_bytesReceived;
+ uint8_t toStore = uint8_t(remain < avail ? remain : avail);
+ if (toStore)
+ storeUDSPayload(&rx_frame.data.u8[2], toStore);
+
+ transmit_can_frame(&MG5_781_RQ_CONTINUE_MULTIFRAME);
+ uds_timeout_ms = UDS_TIMEOUT_AFTER_FF_MS; // extend while MF running
+ }
+ break;
+ }
+
+ case 0x2: { // Consecutive Frame (CF)
+ if (!gUDSContext.UDS_inProgress)
+ break;
+ uint8_t avail = (rx_frame.DLC > 1) ? (rx_frame.DLC - 1) : 0; // CF payload at data[1]
+ uint16_t remain = gUDSContext.UDS_expectedLength - gUDSContext.UDS_bytesReceived;
+ uint8_t toStore = uint8_t(remain < avail ? remain : avail);
+ if (toStore)
+ storeUDSPayload(&rx_frame.data.u8[1], toStore);
+
+ gUDSContext.receivedInBatch++;
+ if (gUDSContext.receivedInBatch == 3) {
+ transmit_can_frame(&MG5_781_RQ_CONTINUE_MULTIFRAME);
+ gUDSContext.receivedInBatch = 0;
+ }
+
+ if (isUDSMessageComplete()) {
+ if (gUDSContext.UDS_moduleID == 0x59) {
+ const uint8_t* p = gUDSContext.UDS_buffer;
+ uint16_t len = gUDSContext.UDS_bytesReceived;
+
+ // 0: SID(0x59), 1: subfunc(0x02), 2: status availability mask
+ if (len >= 3 && p[0] == 0x59 && p[1] == 0x02) {
+ uint16_t off = 3;
+
+ logging.print("UDS DTC list (");
+ logging.print(len - off);
+ logging.println(" bytes of data)");
+
+ // entries are 3-byte DTC + 1-byte status
+ while (off + 4 <= len) {
+ uint32_t dtc = (uint32_t(p[off]) << 16) | (uint32_t(p[off + 1]) << 8) | p[off + 2];
+ uint8_t status = p[off + 3];
+
+ print_formatted_dtc(dtc, status);
+ off += 4;
+ }
+ }
+ }
+
+ // signal that UDS transaction is complete
+ uds_tx_in_flight = false;
+ userRequestReadDTC = false;
+
+ // ready for the next transaction
+ gUDSContext.UDS_inProgress = false;
+ gUDSContext.UDS_expectedLength = 0;
+ gUDSContext.UDS_bytesReceived = 0;
+ }
+ break;
+ }
+ case 0x3: { // Flow Control from ECU (rare)
+ // optional: parse / ignore
+ break;
+ }
+ }
+ }
+
default:
break;
}
}
+
void Mg5Battery::transmit_can(unsigned long currentMillis) {
//Send 10ms message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
-
- transmit_can_frame(&MG_5_100);
}
+
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
- //transmit_can_frame(&MG_5_100);
+ if (datalayer.battery.status.bms_status != FAULT // Fault, so open contactors!
+ && userRequestContactorClose == true // User requested contactor closing
+ && datalayer.system.status.inverter_allows_contactor_closing // Inverter requests contactor closing
+ ) {
+ MG5_8A.data.u8[5] = 0x02; // Command to close contactors
+ //logging.println("contactor close command sent");
+ if (contactorClosed == false) {
+ // Just changed to closed
+ contactorClosed = true;
+ userRequestClearDTC = true; //clear DTCs to clear DTC 293, otherwise contactors won't close
+ //datalayer.battery.status.max_charge_power_W = MaxChargePower; //set the power limits, as they are set to zero when contactors are open
+ //datalayer.battery.status.max_discharge_power_W = MaxDischargePower;
+ }
+ } else {
+ contactorClosed = false;
+ MG5_8A.data.u8[5] = 0x00; // Command to open contactors
+ //userRequestClearDTC = true; //clear DTCs to be able to close the contactors afterwards
+ //logging.println("conctactor open command sent");
+ }
+
+ transmit_can_frame(&MG5_1F1);
+ transmit_can_frame(&MG5_8A);
+ }
+
+ if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
+ previousMillis200 = currentMillis;
+ }
+
+ if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
+ previousMillis1000 = currentMillis;
+ }
+
+ if (currentMillis - previousMillis2000 >= INTERVAL_2_S) {
+ previousMillis2000 = currentMillis;
+ }
+
+ if (uds_tx_in_flight == false) { // No UDS transaction is in progress
+ if (userRequestReadDTC == true) { // DTC requested by user
+ transmit_can_frame(&MG5_781_RQ_DTCs);
+ uds_tx_in_flight = true; //singal that a UDS transaction is in progress
+ uds_req_started_ms = currentMillis; //timestamp when request was sent for timeout tracking
+ uds_timeout_ms = UDS_TIMEOUT_BEFORE_FF_MS; //increase timeout for multi-frame response
+ logging.println("UDS DTC RQ sent");
+
+ } else {
+ if (userRequestClearDTC == true) { // Clear DTC requested by user
+ transmit_can_frame(&MG5_781_CLEAR_DTCs);
+ uds_tx_in_flight = true; //singal that a UDS transaction is in progress
+ uds_req_started_ms = currentMillis; //timestamp when request was sent for timeout tracking
+ uds_timeout_ms = UDS_TIMEOUT_BEFORE_FF_MS; //increase timeout for multi-frame response
+ logging.println("UDS Clear DTC RQ sent");
+ } else {
+ // Time to send next PID request, DTC has priority since it is slower
+ if (currentMillis - previousMillisPID >= UDS_PID_REFRESH_MS) {
+ previousMillisPID = currentMillis;
+ // normal single-frame poll round-robin
+ uds_slow_req_id_counter = increment_uds_req_id_counter(uds_slow_req_id_counter, numSlowUDSreqs);
+ transmit_can_frame(UDS_REQUESTS_SLOW[uds_slow_req_id_counter]);
+ uds_tx_in_flight = true;
+ uds_req_started_ms = currentMillis;
+ uds_timeout_ms = UDS_TIMEOUT_BEFORE_FF_MS;
+ }
+ }
+ }
+ } else { // UDS transaction in progress
+ // Timeout / retry Session Control ------------------------------------
+ if ((currentMillis - uds_req_started_ms) > uds_timeout_ms) {
+ // re-enter Extended Session
+ transmit_can_frame(&MG5_781_ses_ctrl);
+ uds_tx_in_flight = true;
+ uds_req_started_ms = currentMillis;
+ uds_timeout_ms = UDS_TIMEOUT_BEFORE_FF_MS;
+ }
}
}
@@ -119,4 +728,11 @@ void Mg5Battery::setup(void) { // Performs one time setup at startup
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer.battery.info.total_capacity_Wh = TOTAL_BATTERY_CAPACITY_WH;
+ datalayer.battery.info.number_of_cells = 96;
+ uds_tx_in_flight = true; // Make sure UDS doesn't start right away
+ uds_req_started_ms = millis(); // prevent immediate timeout
+ uds_timeout_ms = UDS_TIMEOUT_AFTER_BOOT; // initial delay to restart UDS after boot-up
}
+
+#endif
diff --git a/Software/src/battery/MG-5-BATTERY.h b/Software/src/battery/MG-5-BATTERY.h
index 3f8b86502..5f2453411 100644
--- a/Software/src/battery/MG-5-BATTERY.h
+++ b/Software/src/battery/MG-5-BATTERY.h
@@ -1,14 +1,30 @@
#ifndef MG_5_BATTERY_H
#define MG_5_BATTERY_H
+
#include "CanBattery.h"
+#ifndef SMALL_FLASH_DEVICE
+
class Mg5Battery : public CanBattery {
public:
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values();
+ virtual void update_soc(uint16_t soc_times_ten);
virtual void transmit_can(unsigned long currentMillis);
static constexpr const char* Name = "MG 5 battery";
+ void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID);
+ bool storeUDSPayload(const uint8_t* payload, uint8_t length);
+ void buildMG5_8AFrame();
+ bool isUDSMessageComplete();
+ virtual void print_formatted_dtc(uint32_t dtc24, uint8_t status);
+ bool supports_contactor_close() { return true; }
+ virtual bool supports_read_DTC() { return true; }
+ virtual bool supports_reset_DTC() { return true; }
+ void request_open_contactors() { userRequestContactorClose = false; }
+ void request_close_contactors() { userRequestContactorClose = true; }
+ virtual void read_DTC() { userRequestReadDTC = true; }
+ virtual void reset_DTC() { userRequestClearDTC = true; }
private:
static const int MAX_PACK_VOLTAGE_DV = 4040; //5000 = 500.0V
@@ -16,17 +32,269 @@ class Mg5Battery : public CanBattery {
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
+ static const int TOTAL_BATTERY_CAPACITY_WH = 52500; // 52.5 kWh
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis200 = 0;
+ unsigned long previousMillis1000 = 0;
+ unsigned long previousMillis2000 = 0;
+ unsigned long previousMillisDTC = 0;
+ unsigned long previousMillisPID = 0;
+
+ // For calculating charge and discharge power
+ float RealVoltage;
+ float RealSoC;
+ float tempfloat;
+
+ uint16_t soc = 0;
+ uint16_t cell_id = 0;
+ uint16_t v = 0;
+ uint16_t cellVoltageValidTime = 0;
+ static const uint8_t CELL_VOLTAGE_TIMEOUT = 10; // in seconds
+
+ uint8_t transmitIndex = 0; //For polling switchcase
+ uint8_t previousState = 0;
+
+ const int MaxChargePower = 11000; // Maximum allowable charge power for the battery cells, excluding the taper,
+ const int StartChargeTaper = 90; // Battery percentage above which the charge power will taper to zero
+ const float ChargeTaperExponent =
+ 1; // Shape of charge power taper to zero. 1 is linear. >1 reduces quickly and is small at nearly full.
+ const int TricklePower = 20; // Minimimum trickle charge or discharge power (W)
+
+ const int MaxDischargePower = 11000; // Maximum allowable discharge power, excluding the taper
+ const int MinSoC = 10; // Minimum SoC allowed
+ const int StartDischargeTaper = 10; // Battery percentage below which the discharge power will taper to zero
+ const float DischargeTaperExponent = 1; // Shape of discharge power taper to zero. 1 is linear. >1 red
+
+ // poll counters
+ int uds_slow_req_id_counter = -1;
+
+ // rolling counter for 0x8A (0x10..0x1F pattern)
+ uint8_t mg5_8a_counter = 0x10;
+
+ // simple toggle to alternate 0x80/0x00 and 0x7F/0xFF (alive / redundancy style)
+ bool mg5_8a_flip = false;
+
+ bool uds_tx_in_flight = false;
+ bool userRequestReadDTC = false;
+ bool userRequestClearDTC = false;
+ bool userRequestContactorClose = true;
+ bool contactorClosed = false;
+ unsigned long uds_req_started_ms = 0;
+ unsigned long uds_timeout_ms = 0;
+ const unsigned long UDS_PID_REFRESH_MS = 500; // inter-request gap
+ const unsigned long UDS_TIMEOUT_BEFORE_FF_MS = 1100; // no reply yet
+ const unsigned long UDS_TIMEOUT_AFTER_FF_MS = 1100; // multi-frame in progress
+ const unsigned long UDS_TIMEOUT_AFTER_BOOT = 2000; // DELAY TO START UDS AFTER BOOT-UP
+ const unsigned long TESTER_PRESENT_PERIOD_MS = 1000; // ~1 s
+
+ // A structure to keep track of the ongoing multi-frame UDS response
+ typedef struct {
+ bool UDS_inProgress; // Are we currently receiving a multi-frame message?
+ uint16_t UDS_expectedLength; // Expected total payload length
+ uint16_t UDS_bytesReceived; // How many bytes have been stored so far
+ uint8_t UDS_moduleID; // The "module" indicated by the first frame
+ uint8_t receivedInBatch; // Number of CFs received in the current batch
+ uint8_t UDS_buffer[1024]; // Buffer for the reassembled data
+ unsigned long UDS_lastFrameMillis; // Timestamp of last frame (for timeouts, if desired)
+ } UDS_RxContext;
+
+ // A single global UDS context, since only one module can respond at a time
+ UDS_RxContext gUDSContext;
+
+ //0x781 UDS diagnostic requests - Extended Session Control
+ CAN_frame MG5_781_ses_ctrl = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ //0x781 UDS diagnostic requests - Session Response
+ CAN_frame MG5_781_ses_resp = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x789,
+ .data = {0x02, 0x50, 0x03, 0x00, 0x32, 0x01, 0xF4, 0x00}};
+
+ //0x781 UDS diagnostic requests - keep alive
+ CAN_frame MG5_781_keep_alive = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x02, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ //0x781 UDS diagnostic requests - keep alive
+ CAN_frame MG5_781_RQ_CONTINUE_MULTIFRAME = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x30, 0x03, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ //0x781 UDS diagnostic requests - request all DTC's
+ CAN_frame MG5_781_RQ_DTCs = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x19, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00}};
+
+ //0x781 UDS diagnostic requests - clear all DTC's
+ CAN_frame MG5_781_CLEAR_DTCs = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}};
+
+ CAN_frame MG5_781_RQ_BUS_VOLTAGE = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x41, 0x00, 0x00, 0x00, 0x00}}; //battery bus voltage
+
+ CAN_frame MG5_781_RQ_BAT_VOLTAGE = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x42, 0x00, 0x00, 0x00, 0x00}}; // Battery Voltage
- int BMS_SOC = 0;
+ CAN_frame MG5_781_RQ_BAT_CURRENT = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x43, 0x00, 0x00, 0x00, 0x00}}; // Current
- CAN_frame MG_5_100 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x100,
- .data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
+ CAN_frame MG5_781_RQ_BAT_RESISTANCE = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x45, 0x00, 0x00, 0x00, 0x00}}; // Resistance
+
+ CAN_frame MG5_781_RQ_BAT_SOC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x46, 0x00, 0x00, 0x00, 0x00}}; // State of Charge
+
+ CAN_frame MG5_781_RQ_BMS_ERR = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x47, 0x00, 0x00, 0x00, 0x00}}; // BMS Error
+
+ CAN_frame MG5_781_RQ_BMS_STATE = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x48, 0x00, 0x00, 0x00, 0x00}}; // BMS Status
+
+ CAN_frame MG5_781_RQ_BAT_RELAY_B = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x49, 0x00, 0x00, 0x00, 0x00}}; // Battery Relay Status
+
+ CAN_frame MG5_781_RQ_BAT_RELAY_G = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x4A, 0x00, 0x00, 0x00, 0x00}}; // Battery Relay Status
+
+ CAN_frame MG5_781_RQ_BAT_RELAY_P = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x52, 0x00, 0x00, 0x00, 0x00}}; // Battery Relay Status
+
+ CAN_frame MG5_781_RQ_BAT_TEMP = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x56, 0x00, 0x00, 0x00, 0x00}}; // Battery Temperature Status
+
+ CAN_frame MG5_781_RQ_MAX_CELL = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x58, 0x00, 0x00, 0x00, 0x00}}; // MAX Cell Voltage
+
+ CAN_frame MG5_781_RQ_MIN_CELL = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x59, 0x00, 0x00, 0x00, 0x00}}; // MIN Cell Voltage
+
+ CAN_frame MG5_781_RQ_COOLANT_TEMP = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x5C, 0x00, 0x00, 0x00, 0x00}}; // Coolant Temperature Status
+
+ CAN_frame MG5_781_RQ_BAT_SOH = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x61, 0x00, 0x00, 0x00, 0x00}}; // Battery State of Health
+
+ CAN_frame MG5_781_RQ_BMS_TIME = {
+ .FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x781,
+ .data = {0x03, 0x22, 0xB0, 0x6D, 0x00, 0x00, 0x00, 0x00}}; // Battery Management System Time
+
+ CAN_frame MG5_8A = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x08A,
+ .data = {0x80, 0x00, 0x00, 0x04, 0x00, 0x02, 0xBB, 0x3F}};
+
+ CAN_frame MG5_1F1 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x1F1,
+ .data = {0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ //Setup Fast UDS values to poll for
+ //CAN_frame* UDS_REQUESTS_FAST[0] = {};
+ //int numFastUDSreqs =
+ // sizeof(UDS_REQUESTS_FAST) / sizeof(UDS_REQUESTS_FAST[0]); //Store Number of elements in the array
+
+ //Setup Slow UDS values to poll for
+ CAN_frame* UDS_REQUESTS_SLOW[15] = {
+ &MG5_781_RQ_BUS_VOLTAGE, &MG5_781_RQ_BAT_VOLTAGE, &MG5_781_RQ_BAT_CURRENT, &MG5_781_RQ_BAT_RESISTANCE,
+ &MG5_781_RQ_BAT_SOC, &MG5_781_RQ_BMS_ERR, &MG5_781_RQ_BMS_STATE, &MG5_781_RQ_BAT_RELAY_B,
+ &MG5_781_RQ_BAT_RELAY_G, &MG5_781_RQ_BAT_RELAY_P, &MG5_781_RQ_BAT_TEMP, &MG5_781_RQ_MAX_CELL,
+ &MG5_781_RQ_MIN_CELL, &MG5_781_RQ_COOLANT_TEMP, &MG5_781_RQ_BAT_SOH
+ //&MG5_781_RQ_DTCs
+ };
+ int numSlowUDSreqs =
+ sizeof(UDS_REQUESTS_SLOW) / sizeof(UDS_REQUESTS_SLOW[0]); // Store Number of elements in the array
+
+ // tiny helper for cycling request indices
+ static int increment_uds_req_id_counter(int cur, int n) {
+ if (n <= 0)
+ return -1;
+ cur++;
+ if (cur >= n)
+ cur = 0;
+ return cur;
+ }
+
+ //compute checksum for MG5 0x8A message
+ uint8_t computeMG5_8AChecksum(const uint8_t* bytes7) const {
+ uint8_t crc = 0;
+ for (int i = 0; i < 7; ++i) {
+ crc += bytes7[i];
+ }
+ return crc;
+ }
};
#endif
+
+#endif
diff --git a/Software/src/battery/MG-HS-PHEV-BATTERY.cpp b/Software/src/battery/MG-HS-PHEV-BATTERY.cpp
index 8d7466ce5..0a458b2b2 100644
--- a/Software/src/battery/MG-HS-PHEV-BATTERY.cpp
+++ b/Software/src/battery/MG-HS-PHEV-BATTERY.cpp
@@ -48,6 +48,8 @@ NOTES
void MgHsPHEVBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
+ datalayer.battery.info.total_capacity_Wh = TOTAL_CAPACITY_WH; // update to actual battery capacity.
+
// Should be called every second
if (cellVoltageValidTime > 0) {
cellVoltageValidTime--;
@@ -122,11 +124,9 @@ void MgHsPHEVBattery::update_soc(uint16_t soc_times_ten) {
}
void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
- uint16_t soc1, soc2, cell_id, v;
switch (rx_frame.ID) {
case 0x173:
// Contains cell min/max voltages
-
v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
if (v > 0 && v < 0x2000) {
datalayer.battery.status.cell_max_voltage_mV = v;
@@ -137,6 +137,7 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
}
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x297:
// Contains battery status in rx_frame.data.u8[1]
@@ -149,6 +150,12 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
if (rx_frame.data.u8[1] != previousState) {
logging.printf("MG_HS_PHEV: Battery status changed to %d (%d)\n", rx_frame.data.u8[1], rx_frame.data.u8[0]);
+
+ if (resetProgress == WAITING_RESET_COMPLETE) {
+ // We were waiting for a reset to complete, now has!
+ logging.printf("MG_HS_PHEV: Reset complete.\n");
+ resetProgress = IDLE;
+ }
}
if (rx_frame.data.u8[1] == 0xf && previousState != 0xf) {
@@ -165,16 +172,16 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
} else if ((rx_frame.data.u8[0] == 0x02 || rx_frame.data.u8[0] == 0x06) && rx_frame.data.u8[1] == 0x01) {
// A weird 'stuck' state where the battery won't reconnect
datalayer.system.status.battery_allows_contactor_closing = false;
- if (!datalayer.system.status.BMS_startup_in_progress) {
+ if (resetProgress == IDLE) {
logging.printf("MG_HS_PHEV: Stuck, resetting.\n");
- start_bms_reset();
+ resetProgress = SENDING_DIAG;
}
} else if (rx_frame.data.u8[1] == 0xf) {
// A fault state (likely isolation failure)
datalayer.system.status.battery_allows_contactor_closing = false;
- if (!datalayer.system.status.BMS_startup_in_progress) {
+ if (resetProgress == IDLE) {
logging.printf("MG_HS_PHEV: Fault, resetting.\n");
- start_bms_reset();
+ resetProgress = SENDING_DIAG;
}
} else {
datalayer.system.status.battery_allows_contactor_closing = true;
@@ -217,12 +224,11 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// soc2 is present in both CAN1 and CAN2 messages
if (soc2 < 1022) {
update_soc(soc2);
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
- if (((rx_frame.data.u8[4] << 8) & 0xf00 | rx_frame.data.u8[5]) != 0) {
+ if ((((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]) != 0) {
// 3AC message contains a nonzero voltage (so must have come from CAN1)
- v = (rx_frame.data.u8[4] << 8) & 0xf00 | rx_frame.data.u8[5];
+ v = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]);
if (v > 0 && v < 4000) {
datalayer.battery.status.voltage_dV = v * 2.5;
}
@@ -238,7 +244,7 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// Per-cell voltages and temps
cell_id = rx_frame.data.u8[5];
if (cell_id < 90) {
- v = 1000 + (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
+ v = 1000 + ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
datalayer.battery.status.cell_voltages_mV[cell_id] = v < 10000 ? v : 0;
// cell temperature is rx_frame.data.u8[1]-40 but BE doesn't use it
}
@@ -255,7 +261,7 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) * 2.5;
} else if (rx_frame.data.u8[3] == 0x42 && rx_frame.data.u8[0] == 0x05) {
// Battery voltage
- datalayer.battery.status.voltage_dV = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) * 2.5;
+ // datalayer.battery.status.voltage_dV = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]) * 2.5;
} else if (rx_frame.data.u8[3] == 0x43 && rx_frame.data.u8[0] == 0x05) {
// Battery current
// we won't update this as it differs in rounding from the CAN1 version
@@ -265,7 +271,7 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]);
} else if (rx_frame.data.u8[3] == 0x46 && rx_frame.data.u8[0] == 0x05) {
// The battery SoC, the same as soc1 in 3AC.
- soc1 = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]);
+ //soc1 = (rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5]);
// We won't use since we're using soc2
} else if (rx_frame.data.u8[3] == 0x47) {
// BMS error code
@@ -312,7 +318,7 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
}
void MgHsPHEVBattery::transmit_can(unsigned long currentMillis) {
- if (datalayer.system.status.BMS_reset_in_progress || datalayer.system.status.BMS_startup_in_progress) {
+ if (datalayer.system.status.bms_reset_status != BMS_RESET_IDLE) {
// Transmitting towards battery is halted while BMS is being reset
previousMillis100 = currentMillis;
previousMillis200 = currentMillis;
@@ -341,25 +347,59 @@ void MgHsPHEVBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&MG_HS_8A);
transmit_can_frame(&MG_HS_1F1);
+
+ switch (resetProgress) {
+ case IDLE:
+ // Nothing to do
+ break;
+ case SENDING_DIAG:
+ // Enter diag mode
+ transmit_can_frame(&MG_HS_7E5_DIAG);
+ resetProgress = SENDING_RESET;
+ break;
+ case SENDING_RESET:
+ // Send reset command
+ transmit_can_frame(&MG_HS_7E5_RESET);
+ resetProgress = WAITING_RESET_COMPLETE;
+ resetTimeout = 0;
+ break;
+ case WAITING_RESET_COMPLETE:
+ resetTimeout++;
+ if (resetTimeout >= 50) {
+ // 5 second timeout expired
+ logging.printf("MG_HS_PHEV: Reset timeout expired.\n");
+ resetProgress = IDLE;
+ }
+ break;
+ default:
+ break;
+ }
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
switch (transmitIndex) {
- case 1:
- transmit_can_frame(&MG_HS_7E5_B0_42); //Battery voltage
+ case 0:
+ // We don't actually use this value currently
+ MG_HS_7E5_POLL.data.u8[2] = (uint8_t)((POLL_BATTERY_VOLTAGE & 0xFF00) >> 8);
+ MG_HS_7E5_POLL.data.u8[3] = (uint8_t)(POLL_BATTERY_VOLTAGE & 0x00FF);
+ transmit_can_frame(&MG_HS_7E5_POLL);
break;
- case 2:
- transmit_can_frame(&MG_HS_7E5_B0_61); //Battery SoH
- transmitIndex = 0; //Return to the first message index. This goes in the last message entry
+ case 1:
+ MG_HS_7E5_POLL.data.u8[2] = (uint8_t)((POLL_BATTERY_SOH & 0xFF00) >> 8);
+ MG_HS_7E5_POLL.data.u8[3] = (uint8_t)(POLL_BATTERY_SOH & 0x00FF);
+ transmit_can_frame(&MG_HS_7E5_POLL);
break;
default:
break;
-
- } //switch
+ }
transmitIndex++; //Increment the message index
+ if (transmitIndex > 30) {
+ // Wrap after a while (we don't need to poll every tick)
+ transmitIndex = 0;
+ }
} //endif
}
diff --git a/Software/src/battery/MG-HS-PHEV-BATTERY.h b/Software/src/battery/MG-HS-PHEV-BATTERY.h
index 7a9ade7bd..1a05ffde2 100644
--- a/Software/src/battery/MG-HS-PHEV-BATTERY.h
+++ b/Software/src/battery/MG-HS-PHEV-BATTERY.h
@@ -15,11 +15,28 @@ class MgHsPHEVBattery : public CanBattery {
private:
void update_soc(uint16_t soc_times_ten);
- static const int MAX_PACK_VOLTAGE_DV = 3780; //5000 = 500.0V
- static const int MIN_PACK_VOLTAGE_DV = 2790;
- static const int MAX_CELL_DEVIATION_MV = 150;
- static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
- static const int MIN_CELL_VOLTAGE_MV = 2610; //Battery is put into emergency stop if one cell goes below this value
+ static const uint16_t TOTAL_CAPACITY_WH = 16600;
+ static const uint16_t MAX_PACK_VOLTAGE_DV = 3780; //5000 = 500.0V
+ static const uint16_t MIN_PACK_VOLTAGE_DV = 2790;
+ static const uint16_t MAX_CELL_DEVIATION_MV = 150;
+ static const uint16_t MAX_CELL_VOLTAGE_MV =
+ 4250; //Battery is put into emergency stop if one cell goes over this value
+ static const uint16_t MIN_CELL_VOLTAGE_MV =
+ 2610; //Battery is put into emergency stop if one cell goes below this value
+
+ static const uint16_t POLL_BATTERY_VOLTAGE = 0xB042;
+ static const uint16_t POLL_BATTERY_CURRENT = 0xB043;
+ static const uint16_t POLL_BATTERY_SOC = 0xB046;
+ static const uint16_t POLL_ERROR_CODE = 0xB047;
+ static const uint16_t POLL_BMS_STATUS = 0xB048;
+ static const uint16_t POLL_MAIN_RELAY_B_STATUS = 0xB049;
+ static const uint16_t POLL_MAIN_RELAY_G_STATUS = 0xB04A;
+ static const uint16_t POLL_MAIN_RELAY_P_STATUS = 0xB052;
+ static const uint16_t POLL_MAX_CELL_TEMPERATURE = 0xB056;
+ static const uint16_t POLL_MIN_CELL_TEMPERATURE = 0xB057;
+ static const uint16_t POLL_MAX_CELL_VOLTAGE = 0xB058;
+ static const uint16_t POLL_MIN_CELL_VOLTAGE = 0xB059;
+ static const uint16_t POLL_BATTERY_SOH = 0xB061;
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
@@ -29,22 +46,28 @@ class MgHsPHEVBattery : public CanBattery {
float RealSoC;
float tempfloat;
- uint8_t previousState = 0;
-
- static const uint16_t CELL_VOLTAGE_TIMEOUT = 10; // in seconds
uint16_t cellVoltageValidTime = 0;
+ uint16_t soc1 = 0;
+ uint16_t soc2 = 0;
+ uint16_t v = 0;
+ uint8_t cell_id = 0;
uint8_t transmitIndex = 0; //For polling switchcase
+ uint8_t previousState = 0;
+ enum MG_HS_RESET_STATE { IDLE, SENDING_DIAG, SENDING_RESET, WAITING_RESET_COMPLETE };
+ MG_HS_RESET_STATE resetProgress = IDLE;
+ uint8_t resetTimeout = 0;
+ static const uint8_t CELL_VOLTAGE_TIMEOUT = 10; // in seconds
- const int MaxChargePower = 3000; // Maximum allowable charge power, excluding the taper
- const int StartChargeTaper = 90; // Battery percentage above which the charge power will taper to zero
+ const uint16_t MaxChargePower = 3000; // Maximum allowable charge power, excluding the taper
+ const uint8_t StartChargeTaper = 90; // Battery percentage above which the charge power will taper to zero
const float ChargeTaperExponent =
1; // Shape of charge power taper to zero. 1 is linear. >1 reduces quickly and is small at nearly full.
- const int TricklePower = 20; // Minimimum trickle charge or discharge power (W)
+ const uint8_t TricklePower = 20; // Minimimum trickle charge or discharge power (W)
- const int MaxDischargePower = 4000; // Maximum allowable discharge power, excluding the taper
- const int MinSoC = 20; // Minimum SoC allowed
- const int StartDischargeTaper = 30; // Battery percentage below which the discharge power will taper to zero
+ const uint16_t MaxDischargePower = 4000; // Maximum allowable discharge power, excluding the taper
+ const uint8_t MinSoC = 20; // Minimum SoC allowed
+ const uint8_t StartDischargeTaper = 30; // Battery percentage below which the discharge power will taper to zero
const float DischargeTaperExponent =
1; // Shape of discharge power taper to zero. 1 is linear. >1 reduces quickly and is small at nearly full.
@@ -58,83 +81,25 @@ class MgHsPHEVBattery : public CanBattery {
.DLC = 8,
.ID = 0x1F1,
.data = {0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame MG_HS_7E5_B0_42 = {.FD = false, // Get Battery voltage
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x42, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_43 = {.FD = false, // Get Battery current
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x43, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_46 = {.FD = false, // Get Battery SoC
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x46, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_47 = {.FD = false, // Get BMS error code
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x47, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_48 = {.FD = false, // Get BMS status
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x48, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_49 = {.FD = false, // Get System main relay B status
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x49, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_4A = {.FD = false, // Get System main relay G status
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x4A, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_52 = {.FD = false, // Get System main relay P status
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x52, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_56 = {.FD = false, // Get Max cell temperature
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x56, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_57 = {.FD = false, // Get Min call temperature
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x57, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_58 = {.FD = false, // Get Max cell voltage
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x58, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_59 = {.FD = false, // Get Min call voltage
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x59, 0x00, 0x00, 0x00, 0x00}};
-
- CAN_frame MG_HS_7E5_B0_61 = {.FD = false, // Get Battery SoH
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x7E5,
- .data = {0x03, 0x22, 0xB0, 0x61, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame MG_HS_7E5_POLL = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x7E5,
+ .data = {0x03, 0x22, 0xB0, 0x42, 0x00, 0x00, 0x00, 0x00}};
+
+ // Enter UDS extended-diagnostics mode
+ static constexpr CAN_frame MG_HS_7E5_DIAG = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x7E5,
+ .data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // BMS hard reset
+ static constexpr CAN_frame MG_HS_7E5_RESET = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x7E5,
+ .data = {0x02, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif
diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp
index 2bebb92f4..240c6890a 100644
--- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp
+++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp
@@ -4,7 +4,8 @@
#include "../charger/CanCharger.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
-#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
+#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
#include "../devboard/utils/logging.h"
@@ -193,6 +194,7 @@ void NissanLeafBattery::
datalayer_nissan->temperature2 = ((Temp_fromRAW_to_F(battery_temp_raw_2) - 320) * 5) / 9; //Convert from F to C
datalayer_nissan->temperature3 = ((Temp_fromRAW_to_F(battery_temp_raw_3) - 320) * 5) / 9; //Convert from F to C
datalayer_nissan->temperature4 = ((Temp_fromRAW_to_F(battery_temp_raw_4) - 320) * 5) / 9; //Convert from F to C
+#ifndef SMALL_FLASH_DEVICE
datalayer_nissan->CryptoChallenge = incomingChallenge;
datalayer_nissan->SolvedChallengeMSB =
((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]);
@@ -205,6 +207,8 @@ void NissanLeafBattery::
stateMachineClearSOH = 0; //Start the statemachine
datalayer_nissan->UserRequestSOHreset = false;
}
+
+#endif
}
}
@@ -316,6 +320,7 @@ void NissanLeafBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x7BB:
+#ifndef SMALL_FLASH_DEVICE
// This section checks if we are doing a SOH reset towards BMS. If we do, all 7BB handling is halted
if (stateMachineClearSOH < 255) {
//Intercept the messages based on state machine
@@ -330,6 +335,7 @@ void NissanLeafBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
break;
}
+#endif
if (stop_battery_query) { //Leafspy is active, stop our own polling
break;
@@ -558,7 +564,7 @@ void NissanLeafBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
- if (datalayer.system.status.BMS_reset_in_progress || datalayer.system.status.BMS_startup_in_progress) {
+ if (datalayer.system.status.bms_reset_status != BMS_RESET_IDLE) {
// Transmitting towards battery is halted while BMS is being reset
previousMillis10 = currentMillis;
previousMillis100 = currentMillis;
@@ -791,7 +797,7 @@ void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
uint8_t NissanLeafBattery::calculate_crc(CAN_frame& rx_frame) {
uint8_t crc = 0;
for (uint8_t j = 0; j < 7; j++) {
- crc = crctable[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
+ crc = crctable_nissan_leaf[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
}
return crc;
}
@@ -835,6 +841,7 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
}
void NissanLeafBattery::clearSOH(void) {
+#ifndef SMALL_FLASH_DEVICE
stop_battery_query = true;
hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds
@@ -899,8 +906,11 @@ void NissanLeafBattery::clearSOH(void) {
default:
break;
}
+#endif
}
+#ifndef SMALL_FLASH_DEVICE
+
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2) {
bool bVar1;
unsigned int uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12;
@@ -965,13 +975,13 @@ unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2) {
return uVar10;
}
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
- return (param_3 ^ 0x7F88 | param_2 ^ 0x8FE7) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff;
+ return ((param_3 ^ 0x7F88) | (param_2 ^ 0x8FE7)) * ((((param_1 & 0xffff) >> 8) ^ (param_1 & 0xff))) & 0xffff;
}
short ShortMaskedSumAndProduct(short param_1, short param_2) {
unsigned short uVar1;
- uVar1 = param_2 + param_1 * 0x0006 & 0xff;
+ uVar1 = (param_2 + (param_1 * 0x0006)) & 0xff;
return (uVar1 + param_1) * (uVar1 + param_2);
}
@@ -981,8 +991,8 @@ unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int para
param_1 = param_1 & 0xffff;
param_2 = param_2 & 0xffff;
uVar1 = param_2 & (param_1 | 0x0006) & 0xf;
- return ((unsigned int)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) *
- (param_2 << uVar1 | (unsigned int)param_2 >> (0x10 - uVar1 & 0x1f)) &
+ return ((unsigned int)param_1 >> uVar1 | param_1 << (0x10 - (uVar1 & 0x1f))) *
+ (param_2 << uVar1 | (unsigned int)param_2 >> (0x10 - (uVar1 & 0x1f))) &
0xffff;
}
@@ -1014,6 +1024,8 @@ void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedCh
return;
}
+#endif
+
void NissanLeafBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h
index c6a5f8681..a0ed95ff1 100644
--- a/Software/src/battery/NISSAN-LEAF-BATTERY.h
+++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h
@@ -10,6 +10,12 @@ extern bool user_selected_LEAF_interlock_mandatory;
class NissanLeafBattery : public CanBattery {
public:
+ // Use the default constructor to create the first or single battery.battery_Total_Voltage2
+ NissanLeafBattery() {
+ datalayer_battery = &datalayer.battery;
+ allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
+ datalayer_nissan = &datalayer_extended.nissanleaf;
+ }
// Use this constructor for the second battery.
NissanLeafBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_NISSAN_LEAF* extended,
CAN_Interface targetCan)
@@ -18,14 +24,7 @@ class NissanLeafBattery : public CanBattery {
allows_contactor_closing = nullptr;
datalayer_nissan = extended;
- battery_Total_Voltage2 = 0;
- }
-
- // Use the default constructor to create the first or single battery.
- NissanLeafBattery() {
- datalayer_battery = &datalayer.battery;
- allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
- datalayer_nissan = &datalayer_extended.nissanleaf;
+ battery_Total_Voltage2 = 0; //Zero out pack voltage to avoid contactor closing before we know value via CAN
}
virtual void setup(void);
@@ -37,9 +36,9 @@ class NissanLeafBattery : public CanBattery {
void reset_SOH() { datalayer_extended.nissanleaf.UserRequestSOHreset = true; }
bool soc_plausible() {
- // When pack voltage is close to max, and SOC% is still low, SOC is not plausible
+ // When pack voltage is close to max, and SOC% is still low (<65.0%), SOC is not plausible
return !((datalayer.battery.status.voltage_dV > (datalayer.battery.info.max_design_voltage_dV - 100)) &&
- (datalayer.battery.status.real_soc < 6500));
+ (battery_SOC < 650));
}
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
@@ -140,20 +139,6 @@ class NissanLeafBattery : public CanBattery {
// There are also two more groups: group 61, which replies with lots of CAN messages (up to 48); here we
// found the SOH value, and group 84 that replies with the HV battery production serial.
- uint8_t crctable[256] = {
- 0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
- 247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
- 107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
- 9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
- 214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
- 98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
- 18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
- 86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
- 41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
- 27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
- 196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
- 32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
-
//Nissan LEAF battery parameters from constantly sent CAN
uint8_t LEAF_battery_Type = ZE0_BATTERY;
bool battery_can_alive = false;
@@ -208,9 +193,12 @@ class NissanLeafBattery : public CanBattery {
uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
uint8_t BMSIDcode[8] = {0};
+ uint8_t stateMachineClearSOH = 0xFF;
+
+#ifndef SMALL_FLASH_DEVICE
// Clear SOH values
- uint8_t stateMachineClearSOH = 0xFF;
+
uint32_t incomingChallenge = 0xFFFFFFFF;
uint8_t solvedChallenge[8];
bool challengeFailed = false;
@@ -220,6 +208,8 @@ class NissanLeafBattery : public CanBattery {
.DLC = 8,
.ID = 0x79B,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
+
+#endif
};
#endif
diff --git a/Software/src/battery/ORION-BMS.h b/Software/src/battery/ORION-BMS.h
index 45d267d66..58a7e8c9a 100644
--- a/Software/src/battery/ORION-BMS.h
+++ b/Software/src/battery/ORION-BMS.h
@@ -13,27 +13,29 @@ class OrionBms : public CanBattery {
static constexpr const char* Name = "DIY battery with Orion BMS (Victron setting)";
private:
- uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages
uint16_t Maximum_Cell_Voltage = 3700;
uint16_t Minimum_Cell_Voltage = 3700;
uint16_t Pack_Health = 99;
- int16_t Pack_Current = 0;
- int16_t Average_Temperature = 0;
uint16_t Pack_Summed_Voltage = 0;
- int16_t Average_Current = 0;
uint16_t High_Temperature = 0;
uint16_t Pack_SOC_ppt = 0;
uint16_t Pack_CCL = 0; //Charge current limit (A)
uint16_t Pack_DCL = 0; //Discharge current limit (A)
uint16_t Maximum_Pack_Voltage = 0;
uint16_t Minimum_Pack_Voltage = 0;
- uint16_t CellID = 0;
uint16_t CellVoltage = 0;
uint16_t CellResistance = 0;
uint16_t CellOpenVoltage = 0;
uint16_t Checksum = 0;
- uint16_t CellBalancing = 0;
+
+ int16_t Average_Current = 0;
+
uint8_t amount_of_detected_cells = 0;
+ uint8_t CellID = 0;
+
+ bool CellBalancing = false;
+
+ uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages
};
#endif
diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp
index c1aacccbb..834b6cabe 100644
--- a/Software/src/battery/PYLON-BATTERY.cpp
+++ b/Software/src/battery/PYLON-BATTERY.cpp
@@ -2,8 +2,11 @@
#include "../battery/BATTERIES.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
+/*Based on CAN-Bus-Protocol-Pylon-high-voltage-V1.26-20210903.pdf , which is the Pylontech 1.26 std */
+
void PylonBattery::update_values() {
datalayer_battery->status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00
@@ -21,11 +24,20 @@ void PylonBattery::update_values() {
datalayer_battery->status.remaining_capacity_Wh = static_cast(
(static_cast(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
- datalayer_battery->status.cell_max_voltage_mV = cellvoltage_max_mV;
- datalayer_battery->status.cell_voltages_mV[0] = cellvoltage_max_mV;
+ // Update cell count if we've received individual cell data
+ if (actual_cell_count > 0) {
+ datalayer_battery->info.number_of_cells = actual_cell_count;
+ }
+ // Use Pylon protocol min/max for alarms
+ datalayer_battery->status.cell_max_voltage_mV = cellvoltage_max_mV;
datalayer_battery->status.cell_min_voltage_mV = cellvoltage_min_mV;
- datalayer_battery->status.cell_voltages_mV[1] = cellvoltage_min_mV;
+
+ // Only populate cell voltages if we don't have individual cell data
+ if (actual_cell_count == 0) {
+ datalayer_battery->status.cell_voltages_mV[0] = cellvoltage_max_mV;
+ datalayer_battery->status.cell_voltages_mV[1] = cellvoltage_min_mV;
+ }
datalayer_battery->status.temperature_min_dC = celltemperature_min_dC;
@@ -37,26 +49,60 @@ void PylonBattery::update_values() {
}
void PylonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ // Handle EMUS extended ID frames for cell monitoring
+ if (rx_frame.ID == EMUS_BASE_ID) {
+ // EMUS configuration frame containing cell count
+ uint8_t cell_count = rx_frame.data.u8[7];
+ if (cell_count > 0 && cell_count <= MAX_CELLS) {
+ actual_cell_count = cell_count;
+ datalayer_battery->info.number_of_cells = actual_cell_count;
+ }
+ return;
+ }
+
switch (rx_frame.ID) {
- case 0x7310:
+ case 0x7310: //System equipment info
case 0x7311:
- ensemble_info_ack = true;
- // This message contains software/hardware version info. No interest to us
+ hardware_version = rx_frame.data.u8[0];
+ hardware_version_V = rx_frame.data.u8[2];
+ hardware_version_R = rx_frame.data.u8[3];
+ software_version_major = rx_frame.data.u8[4];
+ software_version_minor = rx_frame.data.u8[5];
break;
case 0x7320:
case 0x7321:
- ensemble_info_ack = true;
- battery_module_quantity = rx_frame.data.u8[0];
+ battery_module_quantity = ((rx_frame.data.u8[1] << 8) | 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];
+ voltage_level = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
+ ah_number = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
+ break;
+ case 0x7330:
+ manufacturer_name[0] = rx_frame.data.u8[0];
+ manufacturer_name[1] = rx_frame.data.u8[1];
+ manufacturer_name[2] = rx_frame.data.u8[2];
+ manufacturer_name[3] = rx_frame.data.u8[3];
+ manufacturer_name[4] = rx_frame.data.u8[4];
+ manufacturer_name[5] = rx_frame.data.u8[5];
+ manufacturer_name[6] = rx_frame.data.u8[6];
+ manufacturer_name[7] = rx_frame.data.u8[7];
+ break;
+ case 0x7340:
+ manufacturer_name[8] = rx_frame.data.u8[0];
+ manufacturer_name[9] = rx_frame.data.u8[1];
+ manufacturer_name[10] = rx_frame.data.u8[2];
+ manufacturer_name[11] = rx_frame.data.u8[3];
+ manufacturer_name[12] = rx_frame.data.u8[4];
+ manufacturer_name[13] = rx_frame.data.u8[5];
+ manufacturer_name[14] = rx_frame.data.u8[6];
+ manufacturer_name[15] = rx_frame.data.u8[7];
break;
case 0x4210:
case 0x4211:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
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;
+ BMS_temperature_dC = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4])) - 1000;
SOC = rx_frame.data.u8[6];
SOH = rx_frame.data.u8[7];
break;
@@ -108,6 +154,40 @@ void PylonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x4291:
break;
default:
+ // Handle EMUS individual cell voltage messages (0x19B50100-0x19B5011F)
+ if (rx_frame.ID >= CELL_VOLTAGE_BASE_ID && rx_frame.ID < (CELL_VOLTAGE_BASE_ID + 32)) {
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Keep battery alive on cell data
+ uint8_t group = rx_frame.ID - CELL_VOLTAGE_BASE_ID;
+ uint8_t cell_start = group * 8;
+
+ for (uint8_t i = 0; i < 8; i++) {
+ uint8_t cell_index = cell_start + i;
+ if (cell_index < MAX_CELLS && (actual_cell_count == 0 || cell_index < actual_cell_count)) {
+ // Cell voltage: 2000mV base + (byte value ร 10mV)
+ uint16_t cell_voltage = 2000 + (rx_frame.data.u8[i] * 10);
+ // Only update if voltage is in valid range (2500-4200mV)
+ if (cell_voltage >= 2500 && cell_voltage <= 4200) {
+ uint16_t current_voltage = datalayer_battery->status.cell_voltages_mV[cell_index];
+ // Reject sudden large changes (>1000mV)
+ if (current_voltage == 0 || abs((int)cell_voltage - (int)current_voltage) <= 1000) {
+ datalayer_battery->status.cell_voltages_mV[cell_index] = cell_voltage;
+ }
+ }
+ }
+ }
+ }
+ // Handle EMUS individual cell balancing status messages (0x19B50300-0x19B5031F)
+ else if (rx_frame.ID >= CELL_BALANCING_BASE_ID && rx_frame.ID < (CELL_BALANCING_BASE_ID + 32)) {
+ uint8_t group = rx_frame.ID - CELL_BALANCING_BASE_ID;
+ uint8_t cell_start = group * 8;
+
+ for (uint8_t i = 0; i < 8; i++) {
+ uint8_t cell_index = cell_start + i;
+ if (cell_index < MAX_CELLS && (actual_cell_count == 0 || cell_index < actual_cell_count)) {
+ datalayer_battery->status.cell_balancing_status[cell_index] = (rx_frame.data.u8[i] > 0);
+ }
+ }
+ }
break;
}
}
@@ -117,14 +197,41 @@ void PylonBattery::transmit_can(unsigned long currentMillis) {
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
+ PYLON_8200.data.u8[0] = 0xAA; //AA = Quit sleep, 55 = Goto sleep
+
+ PYLON_8210.data.u8[0] = 0xAA; //TODO: how should we control this?
+ /*Charge Command: When the battery is in under-voltage protection, the contactors are open. When
+ we are about to charge the battery, send this command, then the battery will close contactors.
+ If the battery is in sleep status, wake up first then use this command.*/
+
+ PYLON_8210.data.u8[1] = 0x00; //TODO: how should we control this?
+ /*Discharge Command: When the battery is in over-voltage protection, the contactors are open. When
+ we are about to discharge the battery, send this command, then the battery will close contactors.
+ If the battery is in sleep status, wake up first then use this command.*/
+
transmit_can_frame(&PYLON_3010); // Heartbeat
transmit_can_frame(&PYLON_4200); // Ensemble OR System equipment info, depends on frame0
transmit_can_frame(&PYLON_8200); // Control device quit sleep status
transmit_can_frame(&PYLON_8210); // Charge command
- if (ensemble_info_ack) {
- PYLON_4200.data.u8[0] = 0x00; //Request system equipment info
- }
+ //transmit_can_frame(&PYLON_8240); // Emergency Charge command
+ //TODO: Implement? This message can be used to force battery on for 5 minutes, ignoring ext comm errors
+
+ mux = (mux + 1) % 3; // mux cycles between 0-1-2-0-1...
+ PYLON_4200.data.u8[0] = mux;
+ /*00 Request Ensamble Information (Battery will respond 0x42XX messages)
+ 01 Request Cellvoltages (Battery will respond 0x5XXX messages)
+ 02 Request System equipment info (Battery will respond 0x73XX messages)*/
+ }
+
+ // Poll for individual cell voltages every 5 seconds
+ if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
+ previousMillis5000 = currentMillis;
+
+ // Request cell voltage data from EMUS BMS
+ transmit_can_frame(&EMUS_CELL_VOLTAGE_REQUEST);
+ // Request cell balancing status from EMUS BMS
+ transmit_can_frame(&EMUS_CELL_BALANCING_REQUEST);
}
}
@@ -132,12 +239,19 @@ void PylonBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer_battery->info.number_of_cells = 2;
- datalayer_battery->info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
- datalayer_battery->info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
- datalayer_battery->info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
- datalayer_battery->info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
-
- datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
+ if (user_selected_max_pack_voltage_dV > 0) {
+ datalayer_battery->info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
+ }
+ if (user_selected_min_pack_voltage_dV > 0) {
+ datalayer_battery->info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
+ }
+ if (user_selected_max_cell_voltage_mV > 0) {
+ datalayer_battery->info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
+ }
+ if (user_selected_min_cell_voltage_mV > 0) {
+ datalayer_battery->info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
+ }
+ datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
if (allows_contactor_closing) {
*allows_contactor_closing = true;
diff --git a/Software/src/battery/PYLON-BATTERY.h b/Software/src/battery/PYLON-BATTERY.h
index f6b5d75b2..590e259c8 100644
--- a/Software/src/battery/PYLON-BATTERY.h
+++ b/Software/src/battery/PYLON-BATTERY.h
@@ -3,19 +3,24 @@
#include "../datalayer/datalayer.h"
#include "CanBattery.h"
+#include "PYLON-HTML.h"
+
+extern uint16_t user_selected_pylon_baudrate;
class PylonBattery : public CanBattery {
public:
// Use this constructor for the second battery.
PylonBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, CAN_Interface targetCan)
- : CanBattery(targetCan) {
+ : CanBattery(targetCan,
+ user_selected_pylon_baudrate == 500 ? CAN_Speed::CAN_SPEED_500KBPS : CAN_Speed::CAN_SPEED_250KBPS) {
datalayer_battery = datalayer_ptr;
contactor_closing_allowed = contactor_closing_allowed_ptr;
allows_contactor_closing = nullptr;
}
// Use the default constructor to create the first or single battery.
- PylonBattery() {
+ PylonBattery()
+ : CanBattery(user_selected_pylon_baudrate == 500 ? CAN_Speed::CAN_SPEED_500KBPS : CAN_Speed::CAN_SPEED_250KBPS) {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
contactor_closing_allowed = nullptr;
@@ -27,8 +32,15 @@ class PylonBattery : public CanBattery {
virtual void transmit_can(unsigned long currentMillis);
static constexpr const char* Name = "Pylon compatible battery";
+ BatteryHtmlRenderer& get_status_renderer() { return renderer; }
+
private:
+ PylonHtmlRenderer renderer;
static const int MAX_CELL_DEVIATION_MV = 150;
+ static const int MAX_CELLS = 192; // Maximum cells supported
+ static const uint32_t EMUS_BASE_ID = 0x19B50000; // EMUS extended ID base for cell count
+ static const uint32_t CELL_VOLTAGE_BASE_ID = 0x19B50100; // Base CAN ID for cell voltages
+ static const uint32_t CELL_BALANCING_BASE_ID = 0x19B50300; // Base CAN ID for balancing status
DATALAYER_BATTERY_TYPE* datalayer_battery;
@@ -39,6 +51,7 @@ class PylonBattery : public CanBattery {
bool* contactor_closing_allowed;
unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
+ unsigned long previousMillis5000 = 0; // will store last time a 5s CAN Message was sent
//Actual content messages
CAN_frame PYLON_3010 = {.FD = false,
@@ -49,7 +62,7 @@ class PylonBattery : public CanBattery {
CAN_frame PYLON_8200 = {.FD = false,
.ext_ID = true,
.DLC = 8,
- .ID = 0x8200,
+ .ID = 0x8200, //AA quit sleep //55 goto sleep
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_8210 = {.FD = false,
.ext_ID = true,
@@ -61,18 +74,30 @@ class PylonBattery : public CanBattery {
.DLC = 8,
.ID = 0x4200,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // EMUS request for individual cell voltages
+ CAN_frame EMUS_CELL_VOLTAGE_REQUEST = {.FD = false,
+ .ext_ID = true,
+ .DLC = 1,
+ .ID = 0x19B50100,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // EMUS request for individual cell balancing status
+ CAN_frame EMUS_CELL_BALANCING_REQUEST = {.FD = false,
+ .ext_ID = true,
+ .DLC = 1,
+ .ID = 0x19B50300,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
int16_t celltemperature_max_dC = 0;
int16_t celltemperature_min_dC = 0;
int16_t current_dA = 0;
uint16_t voltage_dV = 0;
- uint16_t cellvoltage_max_mV = 3700;
- uint16_t cellvoltage_min_mV = 3700;
+ uint16_t cellvoltage_max_mV = 3300;
+ uint16_t cellvoltage_min_mV = 3300;
uint16_t charge_cutoff_voltage = 0;
uint16_t discharge_cutoff_voltage = 0;
int16_t max_charge_current = 0;
int16_t max_discharge_current = 0;
- uint8_t ensemble_info_ack = 0;
+ int16_t BMS_temperature_dC = 0;
uint8_t battery_module_quantity = 0;
uint8_t battery_modules_in_series = 0;
uint8_t cell_quantity_in_module = 0;
@@ -82,6 +107,14 @@ class PylonBattery : public CanBattery {
uint8_t SOH = 100;
uint8_t charge_forbidden = 0;
uint8_t discharge_forbidden = 0;
+ uint8_t manufacturer_name[16] = {0};
+ uint8_t mux = 0;
+ uint8_t hardware_version = 0;
+ uint8_t hardware_version_V = 0;
+ uint8_t hardware_version_R = 0;
+ uint8_t software_version_major = 0;
+ uint8_t software_version_minor = 0;
+ uint8_t actual_cell_count = 0; // Actual number of cells detected from EMUS
};
#endif
diff --git a/Software/src/battery/PYLON-HTML.h b/Software/src/battery/PYLON-HTML.h
new file mode 100644
index 000000000..3a7eba659
--- /dev/null
+++ b/Software/src/battery/PYLON-HTML.h
@@ -0,0 +1,18 @@
+#ifndef _PYLON_HTML_H
+#define _PYLON_HTML_H
+
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
+#include "../devboard/webserver/BatteryHtmlRenderer.h"
+
+class PylonHtmlRenderer : public BatteryHtmlRenderer {
+ public:
+ String get_status_html() {
+ String content;
+ content += "Charge cutoff voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV ";
+ content += "Discharge cutoff voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV ";
+ return content;
+ }
+};
+
+#endif
diff --git a/Software/src/battery/RELION-LV-BATTERY.cpp b/Software/src/battery/RELION-LV-BATTERY.cpp
index 5a760f8a6..0d588ad97 100644
--- a/Software/src/battery/RELION-LV-BATTERY.cpp
+++ b/Software/src/battery/RELION-LV-BATTERY.cpp
@@ -8,42 +8,119 @@ BPS:250kbps
Data Length: 8
Data Encoded Format:Motorola*/
+uint16_t RelionBattery::estimateSOCfromCellvoltage(uint16_t cellVoltage) {
+ for (int i = 1; i < numPoints; ++i) {
+ if (cellVoltage >= cellVoltageLookup[i]) {
+ // Cast to float for proper division
+ float t = (float)(cellVoltage - cellVoltageLookup[i]) / (float)(cellVoltageLookup[i - 1] - cellVoltageLookup[i]);
+
+ // Calculate interpolated SOC value
+ uint16_t socDiff = SOC[i - 1] - SOC[i];
+ uint16_t interpolatedValue = SOC[i] + (uint16_t)(t * socDiff);
+
+ return interpolatedValue;
+ }
+ }
+ return 0; // Default return for safety, should never reach here
+}
+
+uint16_t RelionBattery::estimateSOC() {
+
+ if (max_cell_voltage >= cellVoltageLookup[0]) {
+ return 10000; //One cell is in overvoltage, report 100.00% SOC
+ }
+
+ if (user_selected_max_cell_voltage_mV > 0) { //If value configured by user
+ if (max_cell_voltage >= user_selected_max_cell_voltage_mV) {
+ return 10000; //One cell is in overvoltage from user specified limit, report 100.00% SOC
+ }
+ }
+
+ if (min_cell_voltage <= cellVoltageLookup[numPoints - 1]) {
+ return 0; //Cell overvoltage, report 0% SOC
+ }
+
+ if (user_selected_min_cell_voltage_mV > 0) { //If value configured by user
+ if (min_cell_voltage <= user_selected_min_cell_voltage_mV) {
+ return 0; //Cell overvoltage from user specified limit, report 0% SOC
+ }
+ }
+
+ SOC_from_max_cell_voltage = estimateSOCfromCellvoltage(max_cell_voltage);
+ SOC_from_min_cell_voltage = estimateSOCfromCellvoltage(min_cell_voltage);
+
+ if (max_cell_voltage > 3380) { // We are in the higher end of SOC, use SOC% based on highest cell reading
+ return SOC_from_max_cell_voltage;
+ } else { //We are in the lower end of SOC, use SOC% based on lowest cell reading
+ return SOC_from_min_cell_voltage;
+ }
+
+ return 0; // Default return for safety, should never reach here
+}
+
void RelionBattery::update_values() {
- datalayer.battery.status.real_soc = battery_soc * 100;
+ if (user_selected_use_estimated_SOC) {
+ // Use the simplified pack-based SOC estimation with proper compensation
+ datalayer_battery->status.real_soc = estimateSOC();
+ } else {
+ datalayer_battery->status.real_soc = battery_soc * 100;
+ }
- datalayer.battery.status.remaining_capacity_Wh = static_cast(
- (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
+ datalayer_battery->status.remaining_capacity_Wh = static_cast(
+ (static_cast(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
- datalayer.battery.status.soh_pptt = battery_soh * 100;
+ datalayer_battery->status.soh_pptt = battery_soh * 100;
- datalayer.battery.status.voltage_dV = battery_total_voltage;
+ datalayer_battery->status.voltage_dV = battery_total_voltage;
- datalayer.battery.status.current_dA = battery_total_current; //Charging negative, discharge positive
+ datalayer_battery->status.current_dA = -battery_total_current; //Charging negative, discharge positive
- datalayer.battery.status.max_charge_power_W =
+ /* Shows 0A all the time?
+ datalayer_battery->status.max_charge_power_W =
((battery_total_voltage / 10) * charge_current_A); //90A recommended charge current
- datalayer.battery.status.max_discharge_power_W =
+ datalayer_battery->status.max_discharge_power_W =
((battery_total_voltage / 10) * discharge_current_A); //150A max continous discharge current
+ */
+ datalayer_battery->status.max_discharge_power_W =
+ datalayer_battery->status.override_discharge_power_W; //TODO, fix when value is found
+ if (datalayer_battery->status.cell_min_voltage_mV < MIN_CELL_VOLTAGE_MV) {
+ datalayer_battery->status.max_discharge_power_W = 0;
+ }
+
+ //We have not found allowed charge power yet. Estimate it for now absed on UI setting. TODO. remove this once found
+ if (datalayer_battery->status.real_soc > 9900) {
+ datalayer_battery->status.max_charge_power_W = FLOAT_MAX_POWER_W;
+ } else if ((datalayer_battery->status.real_soc / 10) >
+ RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
+ datalayer_battery->status.max_charge_power_W =
+ RAMPDOWNPOWERALLOWED * (1 - ((battery_soc / 10) - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC));
+ //If the cellvoltages start to reach overvoltage, only allow a small amount of power in
+ if (datalayer_battery->status.cell_max_voltage_mV > (MAX_CELL_VOLTAGE_MV - FLOAT_START_MV)) {
+ datalayer_battery->status.max_charge_power_W = FLOAT_MAX_POWER_W;
+ }
+ } else { // No limits, max charging power allowed
+ datalayer_battery->status.max_charge_power_W = datalayer_battery->status.override_charge_power_W;
+ }
- datalayer.battery.status.temperature_min_dC = max_cell_temperature * 10;
+ datalayer_battery->status.temperature_min_dC = max_cell_temperature * 10;
- datalayer.battery.status.temperature_max_dC = max_cell_temperature * 10;
+ datalayer_battery->status.temperature_max_dC = max_cell_temperature * 10;
- datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage;
+ datalayer_battery->status.cell_max_voltage_mV = max_cell_voltage;
- datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage;
+ datalayer_battery->status.cell_min_voltage_mV = min_cell_voltage;
}
void RelionBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x02018100: //ID1 (Example frame 10 08 01 F0 00 00 00 00)
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_total_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
break;
case 0x02028100: //ID2 (Example frame 00 00 00 63 64 10 00 00)
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_total_current = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
system_state = rx_frame.data.u8[2];
battery_soc = rx_frame.data.u8[3];
@@ -51,87 +128,88 @@ void RelionBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
most_serious_fault = rx_frame.data.u8[5];
break;
case 0x02038100: //ID3 (Example frame 0C F9 01 04 0C A7 01 0F)
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
max_cell_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case 0x02648100: //Charging limitis
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
charge_current_A = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) - 800;
regen_charge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800;
discharge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800;
break;
case 0x02048100: ///Temperatures min/max 2048100 [8] 47 01 01 47 01 01 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
max_cell_temperature = rx_frame.data.u8[0] - 50;
min_cell_temperature = rx_frame.data.u8[2] - 50;
break;
case 0x02468100: ///Raw temperatures 2468100 [8] 47 47 47 47 47 47 47 47
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02478100: ///? 2478100 [8] 32 32 32 32 32 32 32 32
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
//ID6 = 0x02108100 ~ 0x023F8100****** Cell Voltage 1~192******
case 0x02108100: ///Cellvoltages 1 2108100 [8] 0C F9 0C F8 0C F8 0C F9
- datalayer.battery.status.cell_voltages_mV[0] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
- datalayer.battery.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
- datalayer.battery.status.cell_voltages_mV[2] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- datalayer.battery.status.cell_voltages_mV[3] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.cell_voltages_mV[0] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
+ datalayer_battery->status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
+ datalayer_battery->status.cell_voltages_mV[2] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ datalayer_battery->status.cell_voltages_mV[3] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02118100: ///Cellvoltages 2 2118100 [8] 0C F8 0C F8 0C F9 0C F8
- datalayer.battery.status.cell_voltages_mV[4] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
- datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
- datalayer.battery.status.cell_voltages_mV[6] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- datalayer.battery.status.cell_voltages_mV[7] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.cell_voltages_mV[4] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
+ datalayer_battery->status.cell_voltages_mV[5] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
+ datalayer_battery->status.cell_voltages_mV[6] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ datalayer_battery->status.cell_voltages_mV[7] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02128100: ///Cellvoltages 3 2128100 [8] 0C F8 0C F8 0C F9 0C F8
- datalayer.battery.status.cell_voltages_mV[8] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
- datalayer.battery.status.cell_voltages_mV[9] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
- datalayer.battery.status.cell_voltages_mV[10] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- datalayer.battery.status.cell_voltages_mV[11] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.cell_voltages_mV[8] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
+ datalayer_battery->status.cell_voltages_mV[9] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
+ datalayer_battery->status.cell_voltages_mV[10] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ datalayer_battery->status.cell_voltages_mV[11] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02138100: ///Cellvoltages 4 2138100 [8] 0C F9 0C CD 0C A7 00 00
- datalayer.battery.status.cell_voltages_mV[12] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
- datalayer.battery.status.cell_voltages_mV[13] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
- datalayer.battery.status.cell_voltages_mV[14] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.cell_voltages_mV[12] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
+ datalayer_battery->status.cell_voltages_mV[13] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
+ datalayer_battery->status.cell_voltages_mV[14] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
+ datalayer_battery->status.cell_voltages_mV[15] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02058100: ///? 2058100 [8] 00 0C 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02068100: ///? 2068100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02148100: ///? 2148100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02508100: ///? 2508100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02518100: ///? 2518100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02528100: ///? 2528100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02548100: ///? 2548100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x024A8100: ///? 24A8100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02558100: ///? 2558100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02538100: ///? 2538100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02568100: ///? 2568100 [8] 00 00 00 00 00 00 00 00
- datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
default:
break;
@@ -145,11 +223,30 @@ void RelionBattery::transmit_can(unsigned long currentMillis) {
void RelionBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
- datalayer.battery.info.chemistry = LFP;
- datalayer.battery.info.number_of_cells = 16;
- datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
- datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
- datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
- datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer_battery->info.chemistry = LFP;
+ datalayer_battery->info.number_of_cells = 16;
+ if (user_selected_max_pack_voltage_dV > 0) {
+ datalayer_battery->info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
+ } else {
+ datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
+ }
+
+ if (user_selected_min_pack_voltage_dV > 0) {
+ datalayer_battery->info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
+ } else {
+ datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
+ }
+
+ if (user_selected_max_cell_voltage_mV > 0) {
+ datalayer_battery->info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
+ } else {
+ datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
+ }
+
+ if (user_selected_min_cell_voltage_mV > 0) {
+ datalayer_battery->info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
+ } else {
+ datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ }
datalayer.system.status.battery_allows_contactor_closing = true;
}
diff --git a/Software/src/battery/RELION-LV-BATTERY.h b/Software/src/battery/RELION-LV-BATTERY.h
index bee6f09b2..70d52ab14 100644
--- a/Software/src/battery/RELION-LV-BATTERY.h
+++ b/Software/src/battery/RELION-LV-BATTERY.h
@@ -1,12 +1,21 @@
#ifndef RELION_BATTERY_H
#define RELION_BATTERY_H
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
#include "../system_settings.h"
#include "CanBattery.h"
class RelionBattery : public CanBattery {
public:
- RelionBattery() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {}
+ // Use this constructor for the second battery.
+ RelionBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, CAN_Interface targetCan)
+ : CanBattery(targetCan, CAN_Speed::CAN_SPEED_250KBPS) {
+ datalayer_battery = datalayer_ptr;
+ }
+
+ // Use the default constructor to create the first or single battery.
+ RelionBattery() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) { datalayer_battery = &datalayer.battery; }
virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
@@ -15,12 +24,48 @@ class RelionBattery : public CanBattery {
static constexpr const char* Name = "Relion LV protocol via 250kbps CAN";
private:
+ DATALAYER_BATTERY_TYPE* datalayer_battery;
+ uint16_t estimateSOC();
+ uint16_t estimateSOCfromCellvoltage(uint16_t cellVoltage);
+
static const int MAX_PACK_VOLTAGE_DV = 584; //58.4V recommended charge voltage. BMS protection steps in at 60.8V
static const int MIN_PACK_VOLTAGE_DV = 440; //44.0V Recommended LV disconnect. BMS protection steps in at 40.0V
static const int MAX_CELL_DEVIATION_MV = 300;
- static const int MAX_CELL_VOLTAGE_MV = 3800; //Battery is put into emergency stop if one cell goes over this value
- static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
+ static const int MAX_CELL_VOLTAGE_MV = 3750;
+ static const int MIN_CELL_VOLTAGE_MV = 2800;
+
+ static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
+ static const int RAMPDOWNPOWERALLOWED = 1000; // What power we ramp down from towards top balancing
+ static const int FLOAT_MAX_POWER_W = 150; // W, what power to allow for top balancing battery
+ static const int FLOAT_START_MV = 20; // mV, how many mV under overvoltage to start float charging
+ const uint16_t SOC[101] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9100, 9000, 8900, 8800, 8700, 8600,
+ 8500, 8400, 8300, 8200, 8100, 8000, 7900, 7800, 7700, 7600, 7500, 7400, 7300, 7200, 7100,
+ 7000, 6900, 6800, 6700, 6600, 6500, 6400, 6300, 6200, 6100, 6000, 5900, 5800, 5700, 5600,
+ 5500, 5400, 5300, 5200, 5100, 5000, 4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100,
+ 4000, 3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
+ 2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100,
+ 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0};
+ // Define the data points for %SOC depending on cell voltage
+ const uint8_t numPoints = 100;
+ // LFP cell voltage lookup table (2900mV - 3600mV) with flat middle section at 3.1V-3.3V
+ const uint16_t cellVoltageLookup[101] = {
+ 3600, 3570, 3540, 3510, 3490, 3470, 3450, 3430, 3410, 3390, // 100%-91% - Steep drop from full
+ 3370, 3350, 3330, 3320, 3315, 3310, 3305, 3300, 3295, 3290, // 90%-81% - Transition to flat zone
+ // Flat region - typical LFP working range (80%-20%) - 3.3V to 3.1V
+ 3285, 3280, 3275, 3270, 3265, 3260, 3255, 3250, 3245, 3240, // 80%-71%
+ 3235, 3230, 3225, 3220, 3215, 3210, 3205, 3200, 3195, 3190, // 70%-61%
+ 3185, 3180, 3175, 3170, 3165, 3160, 3155, 3150, 3145, 3140, // 60%-51%
+ 3135, 3130, 3125, 3120, 3115, 3110, 3105, 3100, 3095, 3090, // 50%-41%
+ 3085, 3080, 3075, 3070, 3065, 3060, 3055, 3050, 3045, 3040, // 40%-31%
+ 3035, 3030, 3025, 3020, 3015, 3010, 3005, 3000, 2995, 2990, // 30%-21%
+ // End of flat region, steep drop to empty
+ 2980, 2960, 2940, 2920, 2910, 2905, 2902, 2901, 2900, 2890, // 20%-11%
+ 2880, 2870, 2860, 2850, 2840, 2830, 2820, 2810, 2805, 2803, // 10%-1%
+ 2800 // 0%
+ };
+ uint16_t SOC_from_max_cell_voltage = 0;
+ uint16_t SOC_from_min_cell_voltage = 0;
uint16_t battery_total_voltage = 500;
int16_t battery_total_current = 0;
uint8_t system_state = 0;
diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp
index 3de224e9f..a5a4d008f 100644
--- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp
+++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp
@@ -29,7 +29,15 @@ void RenaultKangooBattery::
datalayer.battery.status.soh_pptt = 10000;
}
- datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
+ if (LB_Battery_Voltage == 3700) {
+ //If we do not have battery voltage available, estimate it from cellvoltages
+ uint16_t avg_cellvoltage = 0;
+
+ avg_cellvoltage = (LB_Cell_Min_Voltage + LB_Cell_Max_Voltage) / 2;
+ datalayer.battery.status.voltage_dV = (avg_cellvoltage * 96) / 100;
+ } else { //Use the PID polled value
+ datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
+ }
datalayer.battery.status.current_dA = LB_Current * 10;
@@ -37,12 +45,20 @@ void RenaultKangooBattery::
(static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
/* Define power able to be discharged from battery */
- datalayer.battery.status.max_discharge_power_W =
- (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
+ if (LB_Discharge_Power_Limit > 0) { //If polled value available
+ datalayer.battery.status.max_discharge_power_W =
+ (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
+ } else { //If no polled value available, use hardcoded value
+ datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_W;
+ }
LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts
- //The above value is 0 on some packs. We instead hardcode this now.
- datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W;
+ if (LB_MaxChargeAllowed_W != 76500) { //If the constantly sent value is available, use it!
+ datalayer.battery.status.max_charge_power_W = LB_MaxChargeAllowed_W;
+ } else {
+ //The above value is invalid/0 on some packs. We instead hardcode this now.
+ datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W;
+ }
datalayer.battery.status.temperature_min_dC = (LB_MIN_TEMPERATURE * 10);
@@ -102,23 +118,19 @@ void RenaultKangooBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
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;
+ transmit_can_frame(&KANGOO_79B_Continue);
}
if (rx_frame.data.u8[0] == 0x21) { //2nd response Bytes 8-15
- GVB_79B_Continue = true;
}
if (rx_frame.data.u8[0] == 0x22) { //3rd response Bytes 16-23
- GVB_79B_Continue = true;
}
if (rx_frame.data.u8[0] == 0x23) { //4th response Bytes 16-23
LB_Charge_Power_Limit = word(rx_frame.data.u8[5], rx_frame.data.u8[6]) * 100; //OK!
LB_Discharge_Power_Limit_Byte1 = rx_frame.data.u8[7];
- GVB_79B_Continue = true;
}
if (rx_frame.data.u8[0] == 0x24) { //5th response Bytes 24-31
LB_Discharge_Power_Limit = word(LB_Discharge_Power_Limit_Byte1, rx_frame.data.u8[1]) * 100; //OK!
LB_Battery_Voltage = word(rx_frame.data.u8[2], rx_frame.data.u8[3]) / 10; //OK!
- GVB_79B_Continue = false;
}
break;
default:
@@ -141,9 +153,7 @@ void RenaultKangooBattery::transmit_can(unsigned long currentMillis) {
// 1000ms CAN handling
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
- if (GVB_79B_Continue)
- transmit_can_frame(&KANGOO_79B_Continue);
- } else {
+
transmit_can_frame(&KANGOO_79B);
}
}
diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.h b/Software/src/battery/RENAULT-KANGOO-BATTERY.h
index 0fc8c8cf1..915f6ea84 100644
--- a/Software/src/battery/RENAULT-KANGOO-BATTERY.h
+++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.h
@@ -18,9 +18,11 @@ class RenaultKangooBattery : public CanBattery {
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CHARGE_POWER_W = 5000; // Battery can be charged with this amount of power
+ static const int MAX_DISCHARGE_POWER_W = 10000; // Battery can be charged with this amount of power
uint32_t LB_Battery_Voltage = 3700;
uint32_t LB_Charge_Power_Limit_Watts = 0;
+ uint32_t LB_MaxChargeAllowed_W = 0;
int32_t LB_Current = 0;
int16_t LB_MAX_TEMPERATURE = 0;
int16_t LB_MIN_TEMPERATURE = 0;
@@ -31,7 +33,6 @@ class RenaultKangooBattery : public CanBattery {
uint16_t LB_kWh_Remaining = 0;
uint16_t LB_Cell_Max_Voltage = 3700;
uint16_t LB_Cell_Min_Voltage = 3700;
- uint16_t LB_MaxChargeAllowed_W = 0;
uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
uint8_t GVI_Pollcounter = 0;
uint8_t LB_EOCR = 0;
@@ -44,7 +45,6 @@ class RenaultKangooBattery : public CanBattery {
uint8_t LB_HVBOC = 0;
uint8_t LB_MaxInput_kW = 0;
uint8_t LB_MaxOutput_kW = 0;
- bool GVB_79B_Continue = false;
CAN_frame KANGOO_423 = {.FD = false,
.ext_ID = false,
diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
index a0615d2c7..67947eee4 100644
--- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
+++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
@@ -10,36 +10,8 @@
The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage
Still TODO:
- Automatically detect what vehicle and battery size we are on (Zoe 22/41 , Kangoo 33, Fluence ZE 22/36)
-/*
-
-/* Do not change code below unless you are sure what you are doing */
-
-CAN_frame ZOE_423 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x423,
- .data = {0x07, 0x1d, 0x00, 0x02, 0x5d, 0x80, 0x5d, 0xc8}};
-CAN_frame ZOE_POLL_79B = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x79B,
- .data = {0x02, 0x21, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}};
-CAN_frame ZOE_ACK_79B = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x79B,
- .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
-
-#define GROUP1_CELLVOLTAGES_1_POLL 0x41
-#define GROUP2_CELLVOLTAGES_2_POLL 0x42
-#define GROUP3_METRICS 0x61
-#define GROUP4_SOC 0x03
-#define GROUP5_TEMPERATURE_POLL 0x04
-
-static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
-static unsigned long previousMillis250 = 0; // will store last time a 250ms CAN Message was sent
-static uint8_t counter_423 = 0;
+ Do not change code below unless you are sure what you are doing */
void RenaultZoeGen1Battery::
update_values() { //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%
@@ -185,6 +157,44 @@ void RenaultZoeGen1Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
//10,4D,61,04,09,12,3A,09,
cell_1_temperature_polled = (rx_frame.data.u8[6] - 40);
}
+ if (requested_poll == GROUP6_BALANCING) {
+ //RX0 7BB [8] 10 0E 61 07 00 00 00 00
+ datalayer_battery->status.cell_balancing_status[0] = (rx_frame.data.u8[4] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[1] = (rx_frame.data.u8[4] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[2] = (rx_frame.data.u8[4] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[3] = (rx_frame.data.u8[4] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[4] = (rx_frame.data.u8[4] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[5] = (rx_frame.data.u8[4] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[6] = (rx_frame.data.u8[4] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[7] = (rx_frame.data.u8[4] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[8] = (rx_frame.data.u8[5] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[9] = (rx_frame.data.u8[5] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[10] = (rx_frame.data.u8[5] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[11] = (rx_frame.data.u8[5] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[12] = (rx_frame.data.u8[5] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[13] = (rx_frame.data.u8[5] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[14] = (rx_frame.data.u8[5] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[15] = (rx_frame.data.u8[5] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[16] = (rx_frame.data.u8[6] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[17] = (rx_frame.data.u8[6] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[18] = (rx_frame.data.u8[6] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[19] = (rx_frame.data.u8[6] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[20] = (rx_frame.data.u8[6] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[21] = (rx_frame.data.u8[6] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[22] = (rx_frame.data.u8[6] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[23] = (rx_frame.data.u8[6] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[24] = (rx_frame.data.u8[7] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[25] = (rx_frame.data.u8[7] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[26] = (rx_frame.data.u8[7] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[27] = (rx_frame.data.u8[7] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[28] = (rx_frame.data.u8[7] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[29] = (rx_frame.data.u8[7] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[30] = (rx_frame.data.u8[7] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[31] = (rx_frame.data.u8[7] & 0x01);
+ }
break;
case 0x21: //First datarow in PID group
if ((requested_poll == GROUP1_CELLVOLTAGES_1_POLL) && (looping_over_20 == false)) {
@@ -217,6 +227,72 @@ void RenaultZoeGen1Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_3_temperature_polled = (rx_frame.data.u8[5] - 40);
//21,11,3A,09,14,3A,09,0D,
}
+ if (requested_poll == GROUP6_BALANCING) {
+ //(62934.118) RX0 7BB [8] 21 00 00 00 00 00 00 00
+ datalayer_battery->status.cell_balancing_status[32] = (rx_frame.data.u8[1] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[33] = (rx_frame.data.u8[1] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[34] = (rx_frame.data.u8[1] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[35] = (rx_frame.data.u8[1] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[36] = (rx_frame.data.u8[1] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[37] = (rx_frame.data.u8[1] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[38] = (rx_frame.data.u8[1] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[39] = (rx_frame.data.u8[1] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[40] = (rx_frame.data.u8[2] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[41] = (rx_frame.data.u8[2] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[42] = (rx_frame.data.u8[2] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[43] = (rx_frame.data.u8[2] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[44] = (rx_frame.data.u8[2] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[45] = (rx_frame.data.u8[2] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[46] = (rx_frame.data.u8[2] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[47] = (rx_frame.data.u8[2] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[48] = (rx_frame.data.u8[3] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[49] = (rx_frame.data.u8[3] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[50] = (rx_frame.data.u8[3] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[51] = (rx_frame.data.u8[3] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[52] = (rx_frame.data.u8[3] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[53] = (rx_frame.data.u8[3] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[54] = (rx_frame.data.u8[3] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[55] = (rx_frame.data.u8[3] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[56] = (rx_frame.data.u8[4] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[57] = (rx_frame.data.u8[4] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[58] = (rx_frame.data.u8[4] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[59] = (rx_frame.data.u8[4] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[60] = (rx_frame.data.u8[4] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[61] = (rx_frame.data.u8[4] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[62] = (rx_frame.data.u8[4] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[63] = (rx_frame.data.u8[4] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[64] = (rx_frame.data.u8[5] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[65] = (rx_frame.data.u8[5] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[66] = (rx_frame.data.u8[5] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[67] = (rx_frame.data.u8[5] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[68] = (rx_frame.data.u8[5] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[69] = (rx_frame.data.u8[5] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[70] = (rx_frame.data.u8[5] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[71] = (rx_frame.data.u8[5] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[72] = (rx_frame.data.u8[6] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[73] = (rx_frame.data.u8[6] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[74] = (rx_frame.data.u8[6] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[75] = (rx_frame.data.u8[6] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[76] = (rx_frame.data.u8[6] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[77] = (rx_frame.data.u8[6] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[78] = (rx_frame.data.u8[6] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[79] = (rx_frame.data.u8[6] & 0x01);
+
+ datalayer_battery->status.cell_balancing_status[80] = (rx_frame.data.u8[7] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[81] = (rx_frame.data.u8[7] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[82] = (rx_frame.data.u8[7] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[83] = (rx_frame.data.u8[7] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[84] = (rx_frame.data.u8[7] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[85] = (rx_frame.data.u8[7] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[86] = (rx_frame.data.u8[7] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[87] = (rx_frame.data.u8[7] & 0x01);
+ }
+
break;
case 0x22: //Second datarow in PID group
if ((requested_poll == GROUP1_CELLVOLTAGES_1_POLL) && (looping_over_20 == false)) {
@@ -249,6 +325,17 @@ void RenaultZoeGen1Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_6_temperature_polled = (rx_frame.data.u8[7] - 40);
//22,3A,08,F6,3B,08,EE,3B,
}
+ if (requested_poll == GROUP6_BALANCING) {
+ //(62934.128) RX0 7BB [8] 22 00 00 00 00 00 00 00
+ datalayer_battery->status.cell_balancing_status[88] = (rx_frame.data.u8[1] & 0x80) >> 7;
+ datalayer_battery->status.cell_balancing_status[89] = (rx_frame.data.u8[1] & 0x40) >> 6;
+ datalayer_battery->status.cell_balancing_status[90] = (rx_frame.data.u8[1] & 0x20) >> 5;
+ datalayer_battery->status.cell_balancing_status[91] = (rx_frame.data.u8[1] & 0x10) >> 4;
+ datalayer_battery->status.cell_balancing_status[92] = (rx_frame.data.u8[1] & 0x08) >> 3;
+ datalayer_battery->status.cell_balancing_status[93] = (rx_frame.data.u8[1] & 0x04) >> 2;
+ datalayer_battery->status.cell_balancing_status[94] = (rx_frame.data.u8[1] & 0x02) >> 1;
+ datalayer_battery->status.cell_balancing_status[95] = (rx_frame.data.u8[1] & 0x01);
+ }
break;
case 0x23: //Third datarow in PID group
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
@@ -499,11 +586,14 @@ void RenaultZoeGen1Battery::transmit_can(unsigned long currentMillis) {
case 4:
current_poll = GROUP5_TEMPERATURE_POLL;
break;
+ case 5:
+ current_poll = GROUP6_BALANCING;
+ break;
default:
break;
}
- group = (group + 1) % 5; // Cycle 0-1-2-3-4-0-1...
+ group = (group + 1) % 6; // Cycle 0-1-2-3-4-5-0-1...
ZOE_POLL_79B.data.u8[2] = current_poll;
diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
index baa759d05..5d0997c1d 100644
--- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
+++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
@@ -1,6 +1,8 @@
#ifndef RENAULT_ZOE_GEN1_BATTERY_H
#define RENAULT_ZOE_GEN1_BATTERY_H
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
#include "CanBattery.h"
#include "RENAULT-ZOE-GEN1-HTML.h"
@@ -46,6 +48,33 @@ class RenaultZoeGen1Battery : public CanBattery {
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
+ unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
+ unsigned long previousMillis250 = 0; // will store last time a 250ms CAN Message was sent
+ uint8_t counter_423 = 0;
+
+ CAN_frame ZOE_423 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x423,
+ .data = {0x07, 0x1d, 0x00, 0x02, 0x5d, 0x80, 0x5d, 0xc8}};
+ CAN_frame ZOE_POLL_79B = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x79B,
+ .data = {0x02, 0x21, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame ZOE_ACK_79B = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x79B,
+ .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+#define GROUP1_CELLVOLTAGES_1_POLL 0x41
+#define GROUP2_CELLVOLTAGES_2_POLL 0x42
+#define GROUP3_METRICS 0x61
+#define GROUP4_SOC 0x03
+#define GROUP5_TEMPERATURE_POLL 0x04
+#define GROUP6_BALANCING 0x07
+
uint16_t LB_SOC = 50;
uint16_t LB_Display_SOC = 50;
uint16_t LB_SOH = 99;
diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
index 44a8014b5..09d3a2c89 100644
--- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
+++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
@@ -2,7 +2,8 @@
#include
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
-#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
+#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
/* TODO
@@ -25,7 +26,7 @@ every time the power is reset which can be dangerous. In this state, the voltage
uint8_t RenaultZoeGen2Battery::calculate_crc_zoe(CAN_frame& rx_frame, uint8_t crc_xor) {
uint8_t crc = 0; //init value 0x00
for (uint8_t j = 0; j < 7; j++) {
- crc = crctable[(crc ^ static_cast(rx_frame.data.u8[j])) & 0xFF];
+ crc = crc8_table_SAE_J1850_ZER0[(crc ^ static_cast(rx_frame.data.u8[j])) & 0xFF];
}
return crc ^ crc_xor;
}
@@ -47,7 +48,10 @@ void RenaultZoeGen2Battery::update_values() {
datalayer_battery->status.voltage_dV = battery_pack_voltage_periodic_dV;
- datalayer_battery->status.current_dA = ((battery_current - 32640) * 0.3125);
+ datalayer_battery->status.current_dA = ((battery_current - 32640) * 0.3125f);
+
+ //Calculate the total Wh amount from SOH%
+ datalayer_battery->info.total_capacity_Wh = 52000 * (datalayer_battery->status.soh_pptt / 10000.0);
//Calculate the remaining Wh amount from SOC% and max Wh value.
datalayer_battery->status.remaining_capacity_Wh = static_cast(
@@ -59,8 +63,8 @@ void RenaultZoeGen2Battery::update_values() {
//Temperatures and voltages update at slow rate. Only publish new values once both have been sampled to avoid events
if ((battery_min_temp != 920) && (battery_max_temp != 920)) {
- datalayer_battery->status.temperature_min_dC = ((battery_min_temp - 640) * 0.625);
- datalayer_battery->status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
+ datalayer_battery->status.temperature_min_dC = ((battery_min_temp - 640) * 0.625f);
+ datalayer_battery->status.temperature_max_dC = ((battery_max_temp - 640) * 0.625f);
}
datalayer_battery->status.cell_min_voltage_mV = battery_minimum_cell_voltage_mV;
@@ -76,6 +80,12 @@ void RenaultZoeGen2Battery::update_values() {
clear_event(EVENT_HVIL_FAILURE);
}
+ for (int i = 0; i < 96; i++) {
+ if (datalayer_battery->status.cell_balancing_status[i]) {
+ set_event_latched(EVENT_BALANCING_START, datalayer_battery->status.cell_balancing_status[i]);
+ }
+ }
+
// Update webserver datalayer
datalayer_extended.zoePH2.battery_soc = battery_soc;
datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc;
@@ -229,7 +239,7 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
break;
case POLL_12V:
- battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ battery_12v = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 350; //350, calibration from testing
break;
case POLL_AVG_TEMP:
battery_avg_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
@@ -293,117 +303,22 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case POLL_BALANCE_SWITCHES:
if (rx_frame.data.u8[0] == 0x23) {
- battery_balancing_shunts[0] = (rx_frame.data.u8[4] & 0x80) >> 7;
- battery_balancing_shunts[1] = (rx_frame.data.u8[4] & 0x40) >> 6;
- battery_balancing_shunts[2] = (rx_frame.data.u8[4] & 0x20) >> 5;
- battery_balancing_shunts[3] = (rx_frame.data.u8[4] & 0x10) >> 4;
- battery_balancing_shunts[4] = (rx_frame.data.u8[4] & 0x08) >> 3;
- battery_balancing_shunts[5] = (rx_frame.data.u8[4] & 0x04) >> 2;
- battery_balancing_shunts[6] = (rx_frame.data.u8[4] & 0x02) >> 1;
- battery_balancing_shunts[7] = (rx_frame.data.u8[4] & 0x01);
-
- battery_balancing_shunts[8] = (rx_frame.data.u8[5] & 0x80) >> 7;
- battery_balancing_shunts[9] = (rx_frame.data.u8[5] & 0x40) >> 6;
- battery_balancing_shunts[10] = (rx_frame.data.u8[5] & 0x20) >> 5;
- battery_balancing_shunts[11] = (rx_frame.data.u8[5] & 0x10) >> 4;
- battery_balancing_shunts[12] = (rx_frame.data.u8[5] & 0x08) >> 3;
- battery_balancing_shunts[13] = (rx_frame.data.u8[5] & 0x04) >> 2;
- battery_balancing_shunts[14] = (rx_frame.data.u8[5] & 0x02) >> 1;
- battery_balancing_shunts[15] = (rx_frame.data.u8[5] & 0x01);
-
- battery_balancing_shunts[16] = (rx_frame.data.u8[6] & 0x80) >> 7;
- battery_balancing_shunts[17] = (rx_frame.data.u8[6] & 0x40) >> 6;
- battery_balancing_shunts[18] = (rx_frame.data.u8[6] & 0x20) >> 5;
- battery_balancing_shunts[19] = (rx_frame.data.u8[6] & 0x10) >> 4;
- battery_balancing_shunts[20] = (rx_frame.data.u8[6] & 0x08) >> 3;
- battery_balancing_shunts[21] = (rx_frame.data.u8[6] & 0x04) >> 2;
- battery_balancing_shunts[22] = (rx_frame.data.u8[6] & 0x02) >> 1;
- battery_balancing_shunts[23] = (rx_frame.data.u8[6] & 0x01);
-
- battery_balancing_shunts[24] = (rx_frame.data.u8[7] & 0x80) >> 7;
- battery_balancing_shunts[25] = (rx_frame.data.u8[7] & 0x40) >> 6;
- battery_balancing_shunts[26] = (rx_frame.data.u8[7] & 0x20) >> 5;
- battery_balancing_shunts[27] = (rx_frame.data.u8[7] & 0x10) >> 4;
- battery_balancing_shunts[28] = (rx_frame.data.u8[7] & 0x08) >> 3;
- battery_balancing_shunts[29] = (rx_frame.data.u8[7] & 0x04) >> 2;
- battery_balancing_shunts[30] = (rx_frame.data.u8[7] & 0x02) >> 1;
- battery_balancing_shunts[31] = (rx_frame.data.u8[7] & 0x01);
+ for (int i = 0; i < 32; i++) {
+ datalayer_battery->status.cell_balancing_status[i] =
+ (rx_frame.data.u8[4 + (i / 8)] >> (7 - (i % 8))) & 0x01;
+ }
}
if (rx_frame.data.u8[0] == 0x24) {
- battery_balancing_shunts[32] = (rx_frame.data.u8[1] & 0x80) >> 7;
- battery_balancing_shunts[33] = (rx_frame.data.u8[1] & 0x40) >> 6;
- battery_balancing_shunts[34] = (rx_frame.data.u8[1] & 0x20) >> 5;
- battery_balancing_shunts[35] = (rx_frame.data.u8[1] & 0x10) >> 4;
- battery_balancing_shunts[36] = (rx_frame.data.u8[1] & 0x08) >> 3;
- battery_balancing_shunts[37] = (rx_frame.data.u8[1] & 0x04) >> 2;
- battery_balancing_shunts[38] = (rx_frame.data.u8[1] & 0x02) >> 1;
- battery_balancing_shunts[39] = (rx_frame.data.u8[1] & 0x01);
-
- battery_balancing_shunts[40] = (rx_frame.data.u8[2] & 0x80) >> 7;
- battery_balancing_shunts[41] = (rx_frame.data.u8[2] & 0x40) >> 6;
- battery_balancing_shunts[42] = (rx_frame.data.u8[2] & 0x20) >> 5;
- battery_balancing_shunts[43] = (rx_frame.data.u8[2] & 0x10) >> 4;
- battery_balancing_shunts[44] = (rx_frame.data.u8[2] & 0x08) >> 3;
- battery_balancing_shunts[45] = (rx_frame.data.u8[2] & 0x04) >> 2;
- battery_balancing_shunts[46] = (rx_frame.data.u8[2] & 0x02) >> 1;
- battery_balancing_shunts[47] = (rx_frame.data.u8[2] & 0x01);
-
- battery_balancing_shunts[48] = (rx_frame.data.u8[3] & 0x80) >> 7;
- battery_balancing_shunts[49] = (rx_frame.data.u8[3] & 0x40) >> 6;
- battery_balancing_shunts[50] = (rx_frame.data.u8[3] & 0x20) >> 5;
- battery_balancing_shunts[51] = (rx_frame.data.u8[3] & 0x10) >> 4;
- battery_balancing_shunts[52] = (rx_frame.data.u8[3] & 0x08) >> 3;
- battery_balancing_shunts[53] = (rx_frame.data.u8[3] & 0x04) >> 2;
- battery_balancing_shunts[54] = (rx_frame.data.u8[3] & 0x02) >> 1;
- battery_balancing_shunts[55] = (rx_frame.data.u8[3] & 0x01);
-
- battery_balancing_shunts[56] = (rx_frame.data.u8[4] & 0x80) >> 7;
- battery_balancing_shunts[57] = (rx_frame.data.u8[4] & 0x40) >> 6;
- battery_balancing_shunts[58] = (rx_frame.data.u8[4] & 0x20) >> 5;
- battery_balancing_shunts[59] = (rx_frame.data.u8[4] & 0x10) >> 4;
- battery_balancing_shunts[60] = (rx_frame.data.u8[4] & 0x08) >> 3;
- battery_balancing_shunts[61] = (rx_frame.data.u8[4] & 0x04) >> 2;
- battery_balancing_shunts[62] = (rx_frame.data.u8[4] & 0x02) >> 1;
- battery_balancing_shunts[63] = (rx_frame.data.u8[4] & 0x01);
-
- battery_balancing_shunts[64] = (rx_frame.data.u8[5] & 0x80) >> 7;
- battery_balancing_shunts[65] = (rx_frame.data.u8[5] & 0x40) >> 6;
- battery_balancing_shunts[66] = (rx_frame.data.u8[5] & 0x20) >> 5;
- battery_balancing_shunts[67] = (rx_frame.data.u8[5] & 0x10) >> 4;
- battery_balancing_shunts[68] = (rx_frame.data.u8[5] & 0x08) >> 3;
- battery_balancing_shunts[69] = (rx_frame.data.u8[5] & 0x04) >> 2;
- battery_balancing_shunts[70] = (rx_frame.data.u8[5] & 0x02) >> 1;
- battery_balancing_shunts[71] = (rx_frame.data.u8[5] & 0x01);
-
- battery_balancing_shunts[72] = (rx_frame.data.u8[6] & 0x80) >> 7;
- battery_balancing_shunts[73] = (rx_frame.data.u8[6] & 0x40) >> 6;
- battery_balancing_shunts[74] = (rx_frame.data.u8[6] & 0x20) >> 5;
- battery_balancing_shunts[75] = (rx_frame.data.u8[6] & 0x10) >> 4;
- battery_balancing_shunts[76] = (rx_frame.data.u8[6] & 0x08) >> 3;
- battery_balancing_shunts[77] = (rx_frame.data.u8[6] & 0x04) >> 2;
- battery_balancing_shunts[78] = (rx_frame.data.u8[6] & 0x02) >> 1;
- battery_balancing_shunts[79] = (rx_frame.data.u8[6] & 0x01);
-
- battery_balancing_shunts[80] = (rx_frame.data.u8[7] & 0x80) >> 7;
- battery_balancing_shunts[81] = (rx_frame.data.u8[7] & 0x40) >> 6;
- battery_balancing_shunts[82] = (rx_frame.data.u8[7] & 0x20) >> 5;
- battery_balancing_shunts[83] = (rx_frame.data.u8[7] & 0x10) >> 4;
- battery_balancing_shunts[84] = (rx_frame.data.u8[7] & 0x08) >> 3;
- battery_balancing_shunts[85] = (rx_frame.data.u8[7] & 0x04) >> 2;
- battery_balancing_shunts[86] = (rx_frame.data.u8[7] & 0x02) >> 1;
- battery_balancing_shunts[87] = (rx_frame.data.u8[7] & 0x01);
+ for (int i = 0; i < 56; i++) {
+ datalayer_battery->status.cell_balancing_status[32 + i] =
+ (rx_frame.data.u8[1 + (i / 8)] >> (7 - (i % 8))) & 0x01;
+ }
}
if (rx_frame.data.u8[0] == 0x25) {
- battery_balancing_shunts[88] = (rx_frame.data.u8[1] & 0x80) >> 7;
- battery_balancing_shunts[89] = (rx_frame.data.u8[1] & 0x40) >> 6;
- battery_balancing_shunts[90] = (rx_frame.data.u8[1] & 0x20) >> 5;
- battery_balancing_shunts[91] = (rx_frame.data.u8[1] & 0x10) >> 4;
- battery_balancing_shunts[92] = (rx_frame.data.u8[1] & 0x08) >> 3;
- battery_balancing_shunts[93] = (rx_frame.data.u8[1] & 0x04) >> 2;
- battery_balancing_shunts[94] = (rx_frame.data.u8[1] & 0x02) >> 1;
- battery_balancing_shunts[95] = (rx_frame.data.u8[1] & 0x01);
-
- memcpy(datalayer_battery->status.cell_balancing_status, battery_balancing_shunts, 96 * sizeof(bool));
+ for (int i = 0; i < 8; i++) {
+ datalayer_battery->status.cell_balancing_status[88 + i] =
+ (rx_frame.data.u8[1 + (i / 8)] >> (7 - (i % 8))) & 0x01;
+ }
}
break;
case POLL_ENERGY_COMPLETE:
@@ -445,295 +360,25 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
case POLL_SOC_MAX:
battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
- case POLL_CELL_0:
- datalayer_battery->status.cell_voltages_mV[0] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_1:
- datalayer_battery->status.cell_voltages_mV[1] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_2:
- datalayer_battery->status.cell_voltages_mV[2] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_3:
- datalayer_battery->status.cell_voltages_mV[3] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_4:
- datalayer_battery->status.cell_voltages_mV[4] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_5:
- datalayer_battery->status.cell_voltages_mV[5] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_6:
- datalayer_battery->status.cell_voltages_mV[6] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_7:
- datalayer_battery->status.cell_voltages_mV[7] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_8:
- datalayer_battery->status.cell_voltages_mV[8] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_9:
- datalayer_battery->status.cell_voltages_mV[9] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_10:
- datalayer_battery->status.cell_voltages_mV[10] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_11:
- datalayer_battery->status.cell_voltages_mV[11] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_12:
- datalayer_battery->status.cell_voltages_mV[12] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_13:
- datalayer_battery->status.cell_voltages_mV[13] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_14:
- datalayer_battery->status.cell_voltages_mV[14] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_15:
- datalayer_battery->status.cell_voltages_mV[15] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_16:
- datalayer_battery->status.cell_voltages_mV[16] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_17:
- datalayer_battery->status.cell_voltages_mV[17] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_18:
- datalayer_battery->status.cell_voltages_mV[18] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_19:
- datalayer_battery->status.cell_voltages_mV[19] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_20:
- datalayer_battery->status.cell_voltages_mV[20] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_21:
- datalayer_battery->status.cell_voltages_mV[21] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_22:
- datalayer_battery->status.cell_voltages_mV[22] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_23:
- datalayer_battery->status.cell_voltages_mV[23] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_24:
- datalayer_battery->status.cell_voltages_mV[24] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_25:
- datalayer_battery->status.cell_voltages_mV[25] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_26:
- datalayer_battery->status.cell_voltages_mV[26] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_27:
- datalayer_battery->status.cell_voltages_mV[27] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_28:
- datalayer_battery->status.cell_voltages_mV[28] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_29:
- datalayer_battery->status.cell_voltages_mV[29] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_30:
- datalayer_battery->status.cell_voltages_mV[30] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_31:
- datalayer_battery->status.cell_voltages_mV[31] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_32:
- datalayer_battery->status.cell_voltages_mV[32] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_33:
- datalayer_battery->status.cell_voltages_mV[33] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_34:
- datalayer_battery->status.cell_voltages_mV[34] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_35:
- datalayer_battery->status.cell_voltages_mV[35] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_36:
- datalayer_battery->status.cell_voltages_mV[36] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_37:
- datalayer_battery->status.cell_voltages_mV[37] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_38:
- datalayer_battery->status.cell_voltages_mV[38] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_39:
- datalayer_battery->status.cell_voltages_mV[39] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_40:
- datalayer_battery->status.cell_voltages_mV[40] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_41:
- datalayer_battery->status.cell_voltages_mV[41] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_42:
- datalayer_battery->status.cell_voltages_mV[42] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_43:
- datalayer_battery->status.cell_voltages_mV[43] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_44:
- datalayer_battery->status.cell_voltages_mV[44] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_45:
- datalayer_battery->status.cell_voltages_mV[45] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_46:
- datalayer_battery->status.cell_voltages_mV[46] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_47:
- datalayer_battery->status.cell_voltages_mV[47] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_48:
- datalayer_battery->status.cell_voltages_mV[48] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_49:
- datalayer_battery->status.cell_voltages_mV[49] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_50:
- datalayer_battery->status.cell_voltages_mV[50] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_51:
- datalayer_battery->status.cell_voltages_mV[51] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_52:
- datalayer_battery->status.cell_voltages_mV[52] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_53:
- datalayer_battery->status.cell_voltages_mV[53] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_54:
- datalayer_battery->status.cell_voltages_mV[54] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_55:
- datalayer_battery->status.cell_voltages_mV[55] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_56:
- datalayer_battery->status.cell_voltages_mV[56] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_57:
- datalayer_battery->status.cell_voltages_mV[57] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_58:
- datalayer_battery->status.cell_voltages_mV[58] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_59:
- datalayer_battery->status.cell_voltages_mV[59] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_60:
- datalayer_battery->status.cell_voltages_mV[60] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_61:
- datalayer_battery->status.cell_voltages_mV[61] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_62:
- datalayer_battery->status.cell_voltages_mV[62] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_63:
- datalayer_battery->status.cell_voltages_mV[63] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_64:
- datalayer_battery->status.cell_voltages_mV[64] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_65:
- datalayer_battery->status.cell_voltages_mV[65] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_66:
- datalayer_battery->status.cell_voltages_mV[66] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_67:
- datalayer_battery->status.cell_voltages_mV[67] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_68:
- datalayer_battery->status.cell_voltages_mV[68] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_69:
- datalayer_battery->status.cell_voltages_mV[69] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_70:
- datalayer_battery->status.cell_voltages_mV[70] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_71:
- datalayer_battery->status.cell_voltages_mV[71] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_72:
- datalayer_battery->status.cell_voltages_mV[72] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_73:
- datalayer_battery->status.cell_voltages_mV[73] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_74:
- datalayer_battery->status.cell_voltages_mV[74] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_75:
- datalayer_battery->status.cell_voltages_mV[75] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_76:
- datalayer_battery->status.cell_voltages_mV[76] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_77:
- datalayer_battery->status.cell_voltages_mV[77] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_78:
- datalayer_battery->status.cell_voltages_mV[78] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_79:
- datalayer_battery->status.cell_voltages_mV[79] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_80:
- datalayer_battery->status.cell_voltages_mV[80] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_81:
- datalayer_battery->status.cell_voltages_mV[81] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_82:
- datalayer_battery->status.cell_voltages_mV[82] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_83:
- datalayer_battery->status.cell_voltages_mV[83] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_84:
- datalayer_battery->status.cell_voltages_mV[84] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_85:
- datalayer_battery->status.cell_voltages_mV[85] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_86:
- datalayer_battery->status.cell_voltages_mV[86] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_87:
- datalayer_battery->status.cell_voltages_mV[87] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_88:
- datalayer_battery->status.cell_voltages_mV[88] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_89:
- datalayer_battery->status.cell_voltages_mV[89] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_90:
- datalayer_battery->status.cell_voltages_mV[90] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_91:
- datalayer_battery->status.cell_voltages_mV[91] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_92:
- datalayer_battery->status.cell_voltages_mV[92] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_93:
- datalayer_battery->status.cell_voltages_mV[93] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_94:
- datalayer_battery->status.cell_voltages_mV[94] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- case POLL_CELL_95:
- datalayer_battery->status.cell_voltages_mV[95] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
- break;
- default: // Unknown reply
+ default:
+ // Handle cell voltages
+ if (reply_poll >= POLL_CELL_0 && reply_poll <= POLL_CELL_95) {
+ int cell_index = reply_poll - POLL_CELL_0;
+
+ // Three offsets are skipped in the polling sequence, account for that.
+ if (reply_poll > POLL_CELL_30) {
+ cell_index -= 1; // Account for missing 0x9040
+ }
+ if (reply_poll > POLL_CELL_61) {
+ cell_index -= 1; // Account for missing 0x9060
+ }
+ if (reply_poll > POLL_CELL_92) {
+ cell_index -= 1; // Account for missing 0x9080
+ }
+
+ datalayer_battery->status.cell_voltages_mV[cell_index] =
+ (uint16_t)(((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.976563);
+ }
break;
}
break;
@@ -810,6 +455,7 @@ void RenaultZoeGen2Battery::setup(void) { // Performs one time setup at startup
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer_battery->info.number_of_cells = 96;
+ datalayer_battery->info.total_capacity_Wh = 52000;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
@@ -819,14 +465,14 @@ void RenaultZoeGen2Battery::setup(void) { // Performs one time setup at startup
void RenaultZoeGen2Battery::transmit_can_frame_376(void) {
unsigned int secondsSinceProduction = ZOE_376_time_now_s - kProductionTimestamp_s;
- float minutesSinceProduction = (float)secondsSinceProduction / 60.0;
- float yearUnfloored = minutesSinceProduction / 255.0 / 255.0;
+ float minutesSinceProduction = (float)secondsSinceProduction / 60.0f;
+ float yearUnfloored = minutesSinceProduction / 255.0f / 255.0f;
int yearSeg = floor(yearUnfloored);
float remainderYears = yearUnfloored - yearSeg;
- float remainderHoursUnfloored = (remainderYears * 255.0);
+ float remainderHoursUnfloored = (remainderYears * 255.0f);
int hourSeg = floor(remainderHoursUnfloored);
float remainderHours = remainderHoursUnfloored - hourSeg;
- int minuteSeg = floor(remainderHours * 255.0);
+ int minuteSeg = floor(remainderHours * 255.0f);
ZOE_376.data.u8[0] = yearSeg;
ZOE_376.data.u8[1] = hourSeg;
diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
index 04a604653..196a4ed42 100644
--- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
+++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
@@ -243,23 +243,7 @@ class RenaultZoeGen2Battery : public CanBattery {
uint32_t ZOE_376_time_now_s = 1745452800; // Initialized to make the battery think it is April 24, 2025
unsigned long kProductionTimestamp_s =
1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT
- bool battery_balancing_shunts[96];
- const uint8_t crctable[256] = {
- 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
- 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
- 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
- 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
- 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
- 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
- 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
- 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
- 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
- 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
- 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
- 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
- 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
- 0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
CAN_frame ZOE_0EE = {//Pedal position
.FD = false,
.ext_ID = false,
diff --git a/Software/src/battery/RIVIAN-BATTERY.cpp b/Software/src/battery/RIVIAN-BATTERY.cpp
index 8b4c429ba..252b59101 100644
--- a/Software/src/battery/RIVIAN-BATTERY.cpp
+++ b/Software/src/battery/RIVIAN-BATTERY.cpp
@@ -3,6 +3,8 @@
#include "../battery/BATTERIES.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h" //For Advanced Battery Insights webpage
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "../devboard/utils/events.h"
/*
@@ -13,40 +15,147 @@ The battery has 3x CAN channels
- Battery CAN (500kbps) lots of content, not required for operation
*/
+uint8_t RivianBattery::calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
+ uint8_t crc = initial_value;
+ for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
+ crc = crc8_table_SAE_J1850_ZER0[(crc ^ static_cast(rx_frame.data.u8[j])) % 256];
+ }
+ crc ^= 0xFF;
+ return crc;
+}
+
void RivianBattery::update_values() {
- datalayer.battery.status.real_soc = battery_SOC;
+ datalayer.battery.status.real_soc = battery_SOC_average;
- datalayer.battery.status.soh_pptt;
+ //datalayer.battery.status.soh_pptt; //TODO: Find usable SOH
- datalayer.battery.status.voltage_dV = battery_voltage;
+ datalayer.battery.status.voltage_dV = pre_contactor_voltage;
datalayer.battery.status.current_dA = ((int16_t)battery_current / 10.0 - 3200) * 10;
datalayer.battery.status.remaining_capacity_Wh = static_cast(
(static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
- datalayer.battery.status.max_charge_power_W = ((battery_voltage / 10) * battery_charge_limit_amp);
- datalayer.battery.status.max_discharge_power_W = ((battery_voltage / 10) * battery_discharge_limit_amp);
+ datalayer.battery.status.max_charge_power_W = ((pre_contactor_voltage / 10) * battery_charge_limit_amp);
+ datalayer.battery.status.max_discharge_power_W = ((pre_contactor_voltage / 10) * battery_discharge_limit_amp);
- //datalayer.battery.status.cell_min_voltage_mV = 3700; //TODO: Take from failover CAN?
- //datalayer.battery.status.cell_max_voltage_mV = 3700; //TODO: Take from failover CAN?
+ if (cell_min_voltage_mV > 0) {
+ datalayer.battery.status.cell_min_voltage_mV = cell_min_voltage_mV;
+ }
+
+ if (cell_max_voltage_mV > 0) {
+ datalayer.battery.status.cell_max_voltage_mV = cell_max_voltage_mV;
+ }
datalayer.battery.status.temperature_min_dC = battery_min_temperature * 10;
datalayer.battery.status.temperature_max_dC = battery_max_temperature * 10;
+
+ if (battery_thermal_runaway) {
+ set_event(EVENT_THERMAL_RUNAWAY, 0); //Hope nobody will ever get this event!
+ }
+
+ //Update extended datalayer for HTML page
+ datalayer_extended.rivian.BMS_state = BMS_state;
+ datalayer_extended.rivian.HVIL = HVIL;
+ datalayer_extended.rivian.error_flags_from_BMS = error_flags_from_BMS;
+ datalayer_extended.rivian.contactor_state = contactor_state;
+ datalayer_extended.rivian.error_relay_open = error_relay_open;
+ datalayer_extended.rivian.puncture_fault = puncture_fault;
+ datalayer_extended.rivian.liquid_fault = liquid_fault;
+ datalayer_extended.rivian.IsolationMeasurementOngoing = IsolationMeasurementOngoing;
+ datalayer_extended.rivian.isolation_fault_status = isolation_fault_status;
+ datalayer_extended.rivian.contactor_DCFC_welded = contactor_DCFC_welded;
+ datalayer_extended.rivian.system_safe_state = system_safe_state;
+ datalayer_extended.rivian.pre_contactor_voltage = pre_contactor_voltage;
+ datalayer_extended.rivian.main_contactor_voltage = main_contactor_voltage;
+ datalayer_extended.rivian.voltage_reference = voltage_reference;
+ datalayer_extended.rivian.DCFC_contactor_voltage = DCFC_contactor_voltage;
+ datalayer_extended.rivian.slewrate_potential_violation = slewrate_potential_violation;
+ datalayer_extended.rivian.minimum_power_potential_violation = minimum_power_potential_violation;
+ datalayer_extended.rivian.operation_limit_violation_warning = operation_limit_violation_warning;
}
void RivianBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
+ case 0x00A: //DCDC status [Platform CAN]+ 20ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x0A0: //Cellvoltage min/max (Not available on all packs)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ cell_min_voltage_mV = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]);
+ cell_max_voltage_mV = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4));
+ break;
+ case 0x06E: //Status flags [Platform CAN]+ 10ms
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ error_flags_from_BMS = rx_frame.data.u8[5];
+ error_relay_open = (rx_frame.data.u8[6] & 0x01);
+ IsolationMeasurementOngoing = (rx_frame.data.u8[6] & 0x02) >> 1;
+ contactor_state = (((rx_frame.data.u8[7] & 0x01) << 3) | (rx_frame.data.u8[6] >> 5));
+ break;
+ case 0x100: //Discharge/Charge speed [Platform CAN]+ 10ms
+ battery_charge_limit_amp =
+ (((rx_frame.data.u8[3] & 0x0F) << 8) | (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4)) / 20;
+ battery_discharge_limit_amp =
+ (((rx_frame.data.u8[5] & 0x0F) << 8) | (rx_frame.data.u8[4] << 4) | (rx_frame.data.u8[3] >> 4)) / 20;
+ slewrate_potential_violation = (rx_frame.data.u8[5] & 0x10) >> 4;
+ minimum_power_potential_violation = (rx_frame.data.u8[5] & 0x20) >> 5;
+ operation_limit_violation_warning = (rx_frame.data.u8[5] & 0x40) >> 6;
+ break;
+ case 0x1E3: //HMI [Platform CAN]+
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (rx_frame.data.u8[0] != calculateCRC(rx_frame, 3, 0xEC)) {
+ datalayer.battery.status.CAN_error_counter++;
+ break;
+ }
+ HMI_part1 = rx_frame.data.u8[1];
+ HMI_part2 = rx_frame.data.u8[2];
+ break;
+ case 0x120: //Voltages [Platform CAN]+
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ DCFC_contactor_voltage = (((rx_frame.data.u8[1] & 0x1F) << 8) | rx_frame.data.u8[0]);
+ voltage_reference = (((rx_frame.data.u8[3] & 0x1F) << 8) | rx_frame.data.u8[2]);
+ main_contactor_voltage = (((rx_frame.data.u8[5] & 0x1F) << 8) | rx_frame.data.u8[4]);
+ pre_contactor_voltage = (((rx_frame.data.u8[7] & 0x1F) << 8) | rx_frame.data.u8[6]);
+ break;
+ case 0x145: //NACS charger status [Platform CAN]+
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ NACS_charger_detected = true;
+ break;
+ case 0x151: //Celltemps (requires other CAN channel)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x153: //Temperatures [Platform CAN]+
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ battery_max_temperature = (rx_frame.data.u8[5] / 2) - 40;
+ battery_min_temperature = (rx_frame.data.u8[6] / 2) - 40;
+ break;
+ case 0x154: //Status flags [Platform CAN]+
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ if (rx_frame.data.u8[0] != calculateCRC(rx_frame, 8, 0xFD)) {
+ datalayer.battery.status.CAN_error_counter++;
+ break;
+ }
+ puncture_fault = ((rx_frame.data.u8[1] & 0x40) >> 6);
+ liquid_fault = ((rx_frame.data.u8[4] & 0x02) >> 1);
+ battery_thermal_runaway = ((rx_frame.data.u8[4] & 0x04) >> 2);
+ system_safe_state = ((rx_frame.data.u8[4] & 0x78) >> 3);
+ isolation_fault_status = ((rx_frame.data.u8[5] << 1) | ((rx_frame.data.u8[4] & 0x80) >> 7));
+ break;
+ case 0x156: //States [Platform CAN]+
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ contactor_DCFC_welded = (rx_frame.data.u8[5] & 0x01);
+ break;
case 0x160: //Current [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_current = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
break;
- case 0x151: //Celltemps (requires other CAN channel)
+ case 0x162: //Departuretime [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //frame0-1 minutes to departure time
break;
- case 0x120: //Voltages [Platform CAN]+
+ case 0x164: //End of drive message [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- battery_voltage = (((rx_frame.data.u8[7] & 0x1F) << 8) | rx_frame.data.u8[6]);
+ //bit0-1 , 0 = normal, 1 = derate, 2 = stop , 3 = modereq100
break;
case 0x25A: //SOC and kWh [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@@ -56,24 +165,17 @@ void RivianBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
kWh_available_total =
(((rx_frame.data.u8[5] & 0x03) << 14) | (rx_frame.data.u8[4] << 6) | (rx_frame.data.u8[3] >> 2)) / 200;
break;
- case 0x405: //State [Platform CAN]+
+ case 0x299: //Status flags [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- BMS_state = (rx_frame.data.u8[0] & 0x03);
+ HVIL = (rx_frame.data.u8[2] & 0x07);
break;
- case 0x100: //Discharge/Charge speed
- battery_charge_limit_amp =
- (((rx_frame.data.u8[3] & 0x0F) << 8) | (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4)) / 20;
- battery_discharge_limit_amp =
- (((rx_frame.data.u8[5] & 0x0F) << 8) | (rx_frame.data.u8[4] << 4) | (rx_frame.data.u8[3] >> 4)) / 20;
- break;
- case 0x153: //Temperatures
+ case 0x405: //State [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- battery_max_temperature = (rx_frame.data.u8[5] / 2) - 40;
- battery_min_temperature = (rx_frame.data.u8[6] / 2) - 40;
+ BMS_state = (rx_frame.data.u8[0] & 0x03);
break;
- case 0x55B: //Temperatures
+ case 0x55B: //SOC [Platform CAN]+
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- battery_SOC = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
+ battery_SOC_average = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
default:
break;
diff --git a/Software/src/battery/RIVIAN-BATTERY.h b/Software/src/battery/RIVIAN-BATTERY.h
index 156ad84a5..d7cfcee2b 100644
--- a/Software/src/battery/RIVIAN-BATTERY.h
+++ b/Software/src/battery/RIVIAN-BATTERY.h
@@ -1,6 +1,7 @@
#ifndef RIVIAN_BATTERY_H
#define RIVIAN_BATTERY_H
#include "CanBattery.h"
+#include "RIVIAN-HTML.h"
class RivianBattery : public CanBattery {
public:
@@ -10,7 +11,12 @@ class RivianBattery : public CanBattery {
virtual void transmit_can(unsigned long currentMillis);
static constexpr const char* Name = "Rivian R1T large 135kWh battery";
+ BatteryHtmlRenderer& get_status_renderer() { return renderer; }
+
private:
+ RivianHtmlRenderer renderer;
+ uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value);
+
static const int MAX_PACK_VOLTAGE_DV = 4480;
static const int MIN_PACK_VOLTAGE_DV = 2920;
static const int MAX_CELL_DEVIATION_MV = 150;
@@ -18,8 +24,13 @@ class RivianBattery : public CanBattery {
static const int MIN_CELL_VOLTAGE_MV = 3300; //Battery is put into emergency stop if one cell goes below this value
uint8_t BMS_state = 0;
- uint16_t battery_voltage = 3700;
- uint16_t battery_SOC = 5000;
+ uint16_t pre_contactor_voltage = 3700;
+ uint16_t main_contactor_voltage = 0;
+ uint16_t voltage_reference = 0;
+ uint16_t DCFC_contactor_voltage = 0;
+ uint16_t battery_SOC_average = 5000;
+ uint16_t battery_SOC_max = 0;
+ uint16_t battery_SOC_min = 0;
int32_t battery_current = 32000;
uint16_t kWh_available_total = 135;
uint16_t kWh_available_max = 135;
@@ -27,6 +38,25 @@ class RivianBattery : public CanBattery {
int16_t battery_max_temperature = 0;
uint16_t battery_discharge_limit_amp = 0;
uint16_t battery_charge_limit_amp = 0;
+ uint16_t cell_min_voltage_mV = 0;
+ uint16_t cell_max_voltage_mV = 0;
+ uint8_t error_flags_from_BMS = 0;
+ uint8_t contactor_state = 0;
+ uint8_t HVIL = 0;
+ uint8_t HMI_part1 = 0;
+ uint8_t HMI_part2 = 0;
+ uint8_t isolation_fault_status = 0;
+ uint8_t system_safe_state = 0;
+ bool error_relay_open = false;
+ bool IsolationMeasurementOngoing = false;
+ bool battery_thermal_runaway = false;
+ bool puncture_fault = false;
+ bool liquid_fault = false;
+ bool contactor_DCFC_welded = false;
+ bool NACS_charger_detected = false;
+ bool slewrate_potential_violation = false;
+ bool minimum_power_potential_violation = false;
+ bool operation_limit_violation_warning = false;
static const uint8_t SLEEP = 0;
static const uint8_t STANDBY = 1;
static const uint8_t READY = 2;
diff --git a/Software/src/battery/RIVIAN-HTML.h b/Software/src/battery/RIVIAN-HTML.h
new file mode 100644
index 000000000..c71ea1c05
--- /dev/null
+++ b/Software/src/battery/RIVIAN-HTML.h
@@ -0,0 +1,181 @@
+#ifndef _RIVIAN_HTML_H
+#define _RIVIAN_HTML_H
+
+#include
+#include "../datalayer/datalayer.h"
+#include "../datalayer/datalayer_extended.h"
+#include "../devboard/webserver/BatteryHtmlRenderer.h"
+
+class RivianHtmlRenderer : public BatteryHtmlRenderer {
+ public:
+ String get_status_html() {
+ String content;
+
+ content += "Voltage, pre contactors: " + String(datalayer_extended.rivian.pre_contactor_voltage) + " dV ";
+ content += "Voltage, main contactors: " + String(datalayer_extended.rivian.main_contactor_voltage) + " dV ";
+ content += "Voltage, reference: " + String(datalayer_extended.rivian.voltage_reference) + " dV ";
+ content += "Voltage, DCFC contactors: " + String(datalayer_extended.rivian.DCFC_contactor_voltage) + " dV ";
+
+ content += "SOC, max: " + String(datalayer_extended.rivian.battery_SOC_max) + " pptt ";
+ content += "SOC, min: " + String(datalayer_extended.rivian.battery_SOC_min) + " pptt ";
+
+ if (datalayer_extended.rivian.NACS_charger_detected) {
+ content += "NACS charger detected! ";
+ }
+
+ content += "Isolation measurement ongoing: ";
+ if (datalayer_extended.rivian.IsolationMeasurementOngoing) {
+ content += "Yes ";
+ } else {
+ content += "No ";
+ }
+
+ content += "Isolation Status: ";
+ if (datalayer_extended.rivian.isolation_fault_status == 0) {
+ content += "Undefined";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 1) {
+ content += "Stable";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 2) {
+ content += "No Fault";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 3) {
+ content += "High Side Fault";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 4) {
+ content += "Low Side Fault";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 5) {
+ content += "Dual Side Fault";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 6) {
+ content += "Iso Circuit Failure";
+ } else if (datalayer_extended.rivian.isolation_fault_status == 7) {
+ content += "Iso Circuit Check timeout";
+ }
+ content += " ";
+
+ content += "Interlock status: ";
+ if (datalayer_extended.rivian.HVIL == 0) {
+ content += "NOT OK";
+ } else if (datalayer_extended.rivian.HVIL == 1) {
+ content += "NOT OK";
+ } else if (datalayer_extended.rivian.HVIL == 2) {
+ content += "NOT OK";
+ } else if (datalayer_extended.rivian.HVIL == 3) {
+ content += "OK";
+ }
+ content += " ";
+
+ content += "BMS State: ";
+ if (datalayer_extended.rivian.BMS_state == 0) {
+ content += "Sleep";
+ } else if (datalayer_extended.rivian.BMS_state == 1) {
+ content += "Standby";
+ } else if (datalayer_extended.rivian.BMS_state == 2) {
+ content += "Ready";
+ } else if (datalayer_extended.rivian.BMS_state == 3) {
+ content += "Go";
+ }
+ content += " ";
+
+ content += "Contactor State: ";
+ if (datalayer_extended.rivian.contactor_state == 0) {
+ content += "Open";
+ } else if (datalayer_extended.rivian.contactor_state == 1) {
+ content += "Closed";
+ } else if (datalayer_extended.rivian.contactor_state == 2) {
+ content += "Precharge";
+ } else if (datalayer_extended.rivian.contactor_state == 3) {
+ content += "Turning off";
+ } else if (datalayer_extended.rivian.contactor_state == 4) {
+ content += "Initialization";
+ } else if (datalayer_extended.rivian.contactor_state == 5) {
+ content += "FAILURE";
+ }
+ content += " ";
+
+ content += "Active errors and faults: ";
+
+ if (datalayer_extended.rivian.error_relay_open) {
+ content += "Error Relay Open ";
+ }
+ if (datalayer_extended.rivian.error_flags_from_BMS & 0x01) {
+ content += "Error Isolation Single ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x02) >> 1) {
+ content += "Error Isolation Double ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x04) >> 2) {
+ content += "Error Emergency Off CRASH ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x08) >> 3) {
+ content += "Error Emergency Off Pilot ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x10) >> 4) {
+ content += "Error Emergency Off Request ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x20) >> 5) {
+ content += "Error Emergency Off ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x40) >> 6) {
+ content += "Error Contactors Welded ";
+ }
+ if ((datalayer_extended.rivian.error_flags_from_BMS & 0x80) >> 7) {
+ content += "Error Limited Power ";
+ }
+
+ //HMI errors / status codes, bundle them also under errors
+
+ if ((datalayer_extended.rivian.HMI_part1 & 0x10) >> 4) {
+ content += "Vehicle Battery Issue ";
+ }
+ if ((datalayer_extended.rivian.HMI_part1 & 0x20) >> 5) {
+ content += "Critical Battery Issue ";
+ }
+ if ((datalayer_extended.rivian.HMI_part1 & 0x40) >> 6) {
+ content += "AC performance limited ";
+ }
+ if ((datalayer_extended.rivian.HMI_part1 & 0x80) >> 7) {
+ content += "DC performance limited ";
+ }
+ if (datalayer_extended.rivian.HMI_part2 & 0x01) {
+ content += "DC charging disabled ";
+ }
+ if ((datalayer_extended.rivian.HMI_part2 & 0x02) >> 1) {
+ content += "Electric hazard ";
+ }
+ if ((datalayer_extended.rivian.HMI_part2 & 0x04) >> 2) {
+ content += "Fire risk ";
+ }
+ if ((datalayer_extended.rivian.HMI_part2 & 0x08) >> 3) {
+ content += "Vehicle system fault ";
+ }
+ if ((datalayer_extended.rivian.HMI_part2 & 0x10) >> 4) {
+ content += "Battery electric malfunction ";
+ }
+
+ //Misc
+ if (datalayer_extended.rivian.system_safe_state > 1) {
+ content += "System safe state A active ";
+ }
+ if (datalayer_extended.rivian.puncture_fault) {
+ content += "Puncture fault detected ";
+ }
+ if (datalayer_extended.rivian.liquid_fault) {
+ content += "Liquid fault detected ";
+ }
+ if (datalayer_extended.rivian.contactor_DCFC_welded) {
+ content += "DCFC contactor welded ";
+ }
+
+ if (datalayer_extended.rivian.slewrate_potential_violation) {
+ content += "Slewrate potential violation ";
+ }
+ if (datalayer_extended.rivian.minimum_power_potential_violation) {
+ content += "Min power potential violation ";
+ }
+ if (datalayer_extended.rivian.operation_limit_violation_warning) {
+ content += "Operation limit violation warning ";
+ }
+
+ return content;
+ }
+};
+
+#endif
diff --git a/Software/src/battery/RJXZS-BMS.cpp b/Software/src/battery/RJXZS-BMS.cpp
index 3017ae8e2..a3c291d80 100644
--- a/Software/src/battery/RJXZS-BMS.cpp
+++ b/Software/src/battery/RJXZS-BMS.cpp
@@ -21,7 +21,7 @@ void RjxzsBms::update_values() {
datalayer.battery.status.remaining_capacity_Wh = static_cast(
(static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
- datalayer.battery.status.soh_pptt; // This BMS does not have a SOH% formula
+ //datalayer.battery.status.soh_pptt; // This BMS does not have a SOH% formula
datalayer.battery.status.voltage_dV = total_voltage;
@@ -78,9 +78,9 @@ void RjxzsBms::update_values() {
datalayer.battery.info.number_of_cells = populated_cellvoltages; // 1-192S
- datalayer.battery.info.max_design_voltage_dV; // Set according to cells?
+ //datalayer.battery.info.max_design_voltage_dV; // TODO: Set according to cells?
- datalayer.battery.info.min_design_voltage_dV; // Set according to cells?
+ //datalayer.battery.info.min_design_voltage_dV; // TODO: Set according to cells?
datalayer.battery.status.cell_max_voltage_mV = maximum_cell_voltage;
@@ -89,20 +89,6 @@ void RjxzsBms::update_values() {
void RjxzsBms::handle_incoming_can_frame(CAN_frame rx_frame) {
- /*
- // All CAN messages recieved will be logged via serial
- logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
- logging.print(" ");
- logging.print(rx_frame.ID, HEX);
- logging.print(" ");
- logging.print(rx_frame.DLC);
- logging.print(" ");
- for (int i = 0; i < rx_frame.DLC; ++i) {
- logging.print(rx_frame.data.u8[i], HEX);
- logging.print(" ");
- }
- logging.println("");
- */
switch (rx_frame.ID) {
case 0xF5: // This is the only message is sent from BMS
setup_completed = true; // Let the function know we no longer need to send startup messages
@@ -133,6 +119,11 @@ void RjxzsBms::handle_incoming_can_frame(CAN_frame rx_frame) {
host_temperature = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
status_accounting = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
equalization_starting_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
+ if ((status_accounting & 0x020) >> 5) { //balancing active
+ datalayer.battery.status.balancing_status = BALANCING_STATUS_ACTIVE;
+ } else { //balancing off
+ datalayer.battery.status.balancing_status = BALANCING_STATUS_READY;
+ }
if ((rx_frame.data.u8[4] & 0x40) >> 6) {
charging_active = true;
discharging_active = false;
@@ -143,13 +134,20 @@ void RjxzsBms::handle_incoming_can_frame(CAN_frame rx_frame) {
} else if (mux >= 0x07 && mux <= 0x46) {
// Cell voltages 1-192 (3 per message, 0x07=1-3, 0x08=4-6, ..., 0x46=190-192)
int cell_index = (mux - 0x07) * 3;
+ bool has_valid_data = false;
for (int i = 0; i < 3; i++) {
if (cell_index + i >= MAX_AMOUNT_CELLS) {
break;
}
- cellvoltages[cell_index + i] = (rx_frame.data.u8[1 + i * 2] << 8) | rx_frame.data.u8[2 + i * 2];
+ uint16_t cell_voltage = (rx_frame.data.u8[1 + i * 2] << 8) | rx_frame.data.u8[2 + i * 2];
+ cellvoltages[cell_index + i] = cell_voltage;
+ // Check if this cell has valid (non-zero) voltage data
+ if (cell_voltage != 0) {
+ has_valid_data = true;
+ }
}
- if (cell_index + 2 >= populated_cellvoltages) {
+ // Only update populated cell count if we received valid voltage data
+ if (has_valid_data && cell_index + 2 >= populated_cellvoltages) {
populated_cellvoltages = cell_index + 2 + 1;
}
} else if (mux == 0x47) {
@@ -259,8 +257,10 @@ void RjxzsBms::transmit_can(unsigned long currentMillis) {
}
if (!setup_completed) {
- transmit_can_frame(&RJXZS_10); // Communication connected flag
- transmit_can_frame(&RJXZS_1C); // CAN OK
+ RJXZS_F4.data.u8[0] = 0x10; // Communication connected flag
+ transmit_can_frame(&RJXZS_F4);
+ RJXZS_F4.data.u8[0] = 0x1C; //CAN OK
+ transmit_can_frame(&RJXZS_F4);
}
}
}
diff --git a/Software/src/battery/RJXZS-BMS.h b/Software/src/battery/RJXZS-BMS.h
index aadc4f8d9..713e9512e 100644
--- a/Software/src/battery/RJXZS-BMS.h
+++ b/Software/src/battery/RJXZS-BMS.h
@@ -22,8 +22,7 @@ class RjxzsBms : public CanBattery {
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was sent
//Actual content messages
- CAN_frame RJXZS_1C = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x1C, 0x00, 0x02}};
- CAN_frame RJXZS_10 = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x10, 0x00, 0x02}};
+ CAN_frame RJXZS_F4 = {.FD = false, .ext_ID = true, .DLC = 3, .ID = 0xF4, .data = {0x1C, 0x00, 0x02}};
static const int FIVE_MINUTES = 60;
diff --git a/Software/src/battery/SIMPBMS-BATTERY.cpp b/Software/src/battery/SIMPBMS-BATTERY.cpp
index 2c199b965..b4425a333 100644
--- a/Software/src/battery/SIMPBMS-BATTERY.cpp
+++ b/Software/src/battery/SIMPBMS-BATTERY.cpp
@@ -34,8 +34,6 @@ void SimpBmsBattery::update_values() {
datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage;
- memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, SIMPBMS_MAX_CELLS * sizeof(uint16_t));
-
datalayer.battery.info.number_of_cells = cells_in_series;
}
@@ -57,7 +55,7 @@ void SimpBmsBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
case 0x356:
//current status
- current_dA = ((rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2]) / 1000;
+ current_dA = ((rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2]) / 100;
voltage_dV = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 10;
break;
case 0x373:
@@ -74,7 +72,7 @@ void SimpBmsBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
if (cellnumber > cells_in_series) {
cells_in_series = cellnumber;
}
- cellvoltages_mV[cellnumber - 1] = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
+ datalayer.battery.status.cell_voltages_mV[cellnumber - 1] = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
}
break;
diff --git a/Software/src/battery/SIMPBMS-BATTERY.h b/Software/src/battery/SIMPBMS-BATTERY.h
index 3d7cce8b8..7a4383fd5 100644
--- a/Software/src/battery/SIMPBMS-BATTERY.h
+++ b/Software/src/battery/SIMPBMS-BATTERY.h
@@ -12,7 +12,7 @@ class SimpBmsBattery : public CanBattery {
static constexpr const char* Name = "SIMPBMS battery";
private:
- static const int SIMPBMS_MAX_CELLS = 128;
+ static const uint8_t SIMPBMS_MAX_CELLS = 128;
unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
@@ -36,7 +36,6 @@ class SimpBmsBattery : public CanBattery {
uint8_t SOH = 99;
uint8_t charge_forbidden = 0;
uint8_t discharge_forbidden = 0;
- uint16_t cellvoltages_mV[SIMPBMS_MAX_CELLS] = {0};
};
#endif
diff --git a/Software/src/battery/SONO-BATTERY.h b/Software/src/battery/SONO-BATTERY.h
index 1544c02ad..290f4f329 100644
--- a/Software/src/battery/SONO-BATTERY.h
+++ b/Software/src/battery/SONO-BATTERY.h
@@ -42,7 +42,7 @@ class SonoBattery : public CanBattery {
CAN_frame SONO_401 = {.FD = false, //Message of Vehicle Date, 1000ms
.ext_ID = false,
.DLC = 8,
- .ID = 0x400,
+ .ID = 0x401,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
diff --git a/Software/src/battery/Shunt.h b/Software/src/battery/Shunt.h
index 330988dd1..cb67062f4 100644
--- a/Software/src/battery/Shunt.h
+++ b/Software/src/battery/Shunt.h
@@ -9,7 +9,7 @@
#include
-enum class ShuntType { None = 0, BmwSbox = 1, Highest };
+enum class ShuntType { None = 0, BmwSbox = 1, Inverter = 2, Highest };
class CanShunt : public Transmitter, CanReceiver {
public:
@@ -41,7 +41,6 @@ class CanShunt : public Transmitter, CanReceiver {
};
extern CanShunt* shunt;
-
extern std::vector supported_shunt_types();
extern const char* name_for_shunt_type(ShuntType type);
extern ShuntType user_selected_shunt_type;
diff --git a/Software/src/battery/Shunts.cpp b/Software/src/battery/Shunts.cpp
index b8f5fcc70..1140ccfed 100644
--- a/Software/src/battery/Shunts.cpp
+++ b/Software/src/battery/Shunts.cpp
@@ -1,10 +1,11 @@
+#include "../inverter/INVERTERS.h"
#include "BMW-SBOX.h"
#include "Shunt.h"
CanShunt* shunt = nullptr;
ShuntType user_selected_shunt_type = ShuntType::None;
-void setup_can_shunt() {
+void setup_shunt() {
if (shunt) {
return;
}
@@ -15,22 +16,23 @@ void setup_can_shunt() {
return;
case ShuntType::BmwSbox:
shunt = new BmwSbox();
+ shunt->setup();
break;
+ case ShuntType::Inverter:
+ if (inverter && inverter->provides_shunt())
+ inverter->enable_shunt();
default:
return;
}
-
- if (shunt) {
- shunt->setup();
- }
}
extern std::vector supported_shunt_types() {
std::vector types;
+ types.push_back(ShuntType::None);
+ types.push_back(ShuntType::BmwSbox);
- for (int i = 0; i < (int)ShuntType::Highest; i++) {
- types.push_back((ShuntType)i);
- }
+ if (inverter && inverter->provides_shunt())
+ types.push_back(ShuntType::Inverter);
return types;
}
@@ -41,6 +43,8 @@ extern const char* name_for_shunt_type(ShuntType type) {
return "None";
case ShuntType::BmwSbox:
return BmwSbox::Name;
+ case ShuntType::Inverter:
+ return "Using inverter values";
default:
return nullptr;
}
diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp
index b83e18616..406346c9a 100644
--- a/Software/src/battery/TESLA-BATTERY.cpp
+++ b/Software/src/battery/TESLA-BATTERY.cpp
@@ -406,14 +406,47 @@ char* dayOfYearToDate(int year, int dayOfYear) {
int month = 0;
// Find the month and the day within the month
- while (dayOfYear > daysInMonth[month]) {
+ while (month < 12 && dayOfYear > daysInMonth[month]) {
dayOfYear -= daysInMonth[month];
month++;
}
+ // Ensure month is in valid range [0, 11]
+ if (month >= 12) {
+ month = 11;
+ dayOfYear = daysInMonth[11]; // Set to last day of December
+ }
+
+ // Ensure day is in valid range [1, daysInMonth[month]]
+ if (dayOfYear < 1) {
+ dayOfYear = 1;
+ } else if (dayOfYear > daysInMonth[month]) {
+ dayOfYear = daysInMonth[month];
+ }
+
static char dateString[11]; // For "YYYY-MM-DD\0"
+
+ // Clamp values to ensure they fit in the expected number of digits
+ int safeYear = year % 10000;
+ if (safeYear < 0)
+ safeYear = 0;
+ if (safeYear > 9999)
+ safeYear = 9999;
+
+ int safeMonth = month + 1;
+ if (safeMonth < 1)
+ safeMonth = 1;
+ if (safeMonth > 12)
+ safeMonth = 12;
+
+ int safeDay = dayOfYear;
+ if (safeDay < 1)
+ safeDay = 1;
+ if (safeDay > 31)
+ safeDay = 31;
+
// Format the date string in "YYYY-MM-DD" format
- snprintf(dateString, sizeof(dateString), "%d-%02d-%02d", year, month + 1, dayOfYear);
+ snprintf(dateString, sizeof(dateString), "%04d-%02d-%02d", safeYear, safeMonth, safeDay);
return dateString;
}
@@ -471,6 +504,14 @@ void TeslaBattery::
/* Value mapping is completed. Start to check all safeties */
+ //12V battery too low for contactor operation. Inform user via Event
+ if (battery_dcdcLvBusVolt > 0) { //If value has been read
+ if ((battery_dcdcLvBusVolt * 0.0390625) < 11.7) {
+ set_event(EVENT_12V_LOW, 0);
+ } else {
+ clear_event(EVENT_12V_LOW);
+ }
+ }
//INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
if (battery_hvil_status == 3) {
set_event(EVENT_INTERNAL_OPEN_FAULT, 0);
@@ -799,7 +840,7 @@ void TeslaBattery::
//Safety checks for CAN message sending
if ((datalayer.system.status.inverter_allows_contactor_closing == true) &&
- (datalayer.battery.status.bms_status != FAULT) && (!datalayer.system.settings.equipment_stop_active)) {
+ (datalayer.battery.status.bms_status != FAULT) && (!datalayer.system.info.equipment_stop_active)) {
// Carry on: 0x221 DRIVE state & reset power down timer
vehicleState = CAR_DRIVE;
powerDownSeconds = 9;
@@ -1890,9 +1931,27 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
if (battery_contactor == 4) { // Contactors closed
// Frames to be sent only when contactors closed
-
+ if (timeToMux3A1) {
+ timeToMux3A1 = false;
+ TESLA_3A1.data.u8[0] = 0xC3;
+ TESLA_3A1.data.u8[1] = 0xFF;
+ TESLA_3A1.data.u8[2] = 0xFF;
+ TESLA_3A1.data.u8[3] = 0xFF;
+ TESLA_3A1.data.u8[4] = 0x3D;
+ TESLA_3A1.data.u8[5] = 0x00;
+ } else { //!timeToMux3A1
+ TESLA_3A1.data.u8[0] = 0x08;
+ TESLA_3A1.data.u8[1] = 0x62;
+ TESLA_3A1.data.u8[2] = 0x0B;
+ TESLA_3A1.data.u8[3] = 0x18;
+ TESLA_3A1.data.u8[4] = 0x00;
+ TESLA_3A1.data.u8[5] = 0x28;
+ timeToMux3A1 = true;
+ }
+ TESLA_3A1.data.u8[6] = frame6_3A1[frameCounter_TESLA_3A1];
+ TESLA_3A1.data.u8[7] = frame7_3A1[frameCounter_TESLA_3A1];
//0x3A1 VCFRONT_vehicleStatus, critical otherwise VCFRONT_MIA triggered
- transmit_can_frame(&TESLA_3A1[frameCounter_TESLA_3A1]);
+ transmit_can_frame(&TESLA_3A1);
frameCounter_TESLA_3A1 = (frameCounter_TESLA_3A1 + 1) % 16;
}
diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h
index 851522cc4..faa9bf7ec 100644
--- a/Software/src/battery/TESLA-BATTERY.h
+++ b/Software/src/battery/TESLA-BATTERY.h
@@ -33,6 +33,8 @@ class TeslaBattery : public CanBattery {
bool supports_charged_energy() { return true; }
+ bool supports_manual_balancing() { return true; }
+
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private:
@@ -89,6 +91,7 @@ class TeslaBattery : public CanBattery {
bool TESLA_334_INITIAL_SENT = false;
//0x3A1 VCFRONT_vehicleStatus, 15 frame counter (temporary)
uint8_t frameCounter_TESLA_3A1 = 0;
+ bool timeToMux3A1 = true;
//0x504 TWC_status
bool TESLA_504_INITIAL_SENT = false;
//0x7FF GTW_carConfig, 5 mux tracker
@@ -286,25 +289,15 @@ class TeslaBattery : public CanBattery {
//0x3A1 VCFRONT_vehicleStatus: "cycle_time" 50ms, VCFRONT_vehicleStatusChecksum/VCFRONT_vehicleStatusCounter eventually need to be generated via generateMuxFrameCounterChecksum
//Looks like 2 muxes, counter at bit 52 width 4 and checksum at bit 56 width 8? Need later software Model3_ETH.compact.json signal file or DBC.
- //Migrated to an array until figured out
- static constexpr CAN_frame TESLA_3A1[16] = {
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0xD0, 0x01}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0xE2, 0xCB}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0xF0, 0x21}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0x02, 0xEB}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x10, 0x41}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0x22, 0x0B}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x30, 0x61}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0x42, 0x2B}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x50, 0x81}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0x62, 0x4B}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x70, 0xA1}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0x82, 0x6B}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x90, 0xC1}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0xA2, 0x8B}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0xB0, 0xE1}},
- {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x3A1, .data = {0x08, 0x62, 0x0B, 0x18, 0x00, 0x28, 0xC2, 0xAB}}};
-
+ CAN_frame TESLA_3A1 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x3A1,
+ .data = {0xC3, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0xD0, 0x01}};
+ uint8_t frame6_3A1[16] = {0xD0, 0xE2, 0xF0, 0x02, 0x10, 0x22, 0x30, 0x42,
+ 0x50, 0x62, 0x70, 0x82, 0x90, 0xA2, 0xB0, 0xC2};
+ uint8_t frame7_3A1[16] = {0x01, 0xCB, 0x21, 0xEB, 0x41, 0x0B, 0x61, 0x2B,
+ 0x81, 0x4B, 0xA1, 0x6B, 0xC1, 0x8B, 0xE1, 0xAB};
//0x313 UI_powertrainControl: "cycle_time" 500ms, UI_powertrainControlChecksum/UI_powertrainControlCounter generated via generateFrameCounterChecksum
CAN_frame TESLA_313 = {.FD = false,
.ext_ID = false,
@@ -632,7 +625,7 @@ class TeslaBattery : public CanBattery {
bool BMS_pcsNoFlowRequest = false;
bool BMS_noFlowRequest = false;
//0x3C4: PCS_info
- uint8_t PCS_partNumber[12] = {0}; //stores raw HEX values for ASCII chars
+ uint8_t PCS_partNumber[13] = {0}; //stores raw HEX values for ASCII chars
bool parsed_PCS_partNumber = false;
uint16_t PCS_info_buildConfigId = 0;
uint16_t PCS_info_hardwareId = 0;
@@ -899,14 +892,12 @@ class TeslaBattery : public CanBattery {
class TeslaModel3YBattery : public TeslaBattery {
public:
- TeslaModel3YBattery(battery_chemistry_enum chemistry) { datalayer.battery.info.chemistry = chemistry; }
static constexpr const char* Name = "Tesla Model 3/Y";
virtual void setup(void);
};
class TeslaModelSXBattery : public TeslaBattery {
public:
- TeslaModelSXBattery() {}
static constexpr const char* Name = "Tesla Model S/X";
virtual void setup(void);
};
diff --git a/Software/src/battery/TESLA-HTML.h b/Software/src/battery/TESLA-HTML.h
index c5f270dc0..7416f859c 100644
--- a/Software/src/battery/TESLA-HTML.h
+++ b/Software/src/battery/TESLA-HTML.h
@@ -12,119 +12,119 @@ class TeslaHtmlRenderer : public BatteryHtmlRenderer {
String content;
float beginning_of_life = static_cast(datalayer_extended.tesla.battery_beginning_of_life);
- float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct) * 0.4;
- float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625;
- float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484;
- float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1;
+ float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct) * 0.4f;
+ float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625f;
+ float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484f;
+ float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1f;
float nominal_full_pack_energy =
- static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1;
+ static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1f;
float nominal_full_pack_energy_m0 =
- static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy_m0) * 0.02;
+ static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy_m0) * 0.02f;
float nominal_energy_remaining =
- static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1;
+ static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1f;
float nominal_energy_remaining_m0 =
- static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining_m0) * 0.02;
- float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1;
+ static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining_m0) * 0.02f;
+ float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1f;
float ideal_energy_remaining_m0 =
- static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining_m0) * 0.02;
+ static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining_m0) * 0.02f;
float energy_to_charge_complete =
- static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1;
+ static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1f;
float energy_to_charge_complete_m1 =
- static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete_m1) * 0.02;
- float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer) * 0.1;
- float energy_buffer_m1 = static_cast(datalayer_extended.tesla.battery_energy_buffer_m1) * 0.01;
+ static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete_m1) * 0.02f;
+ float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer) * 0.1f;
+ float energy_buffer_m1 = static_cast(datalayer_extended.tesla.battery_energy_buffer_m1) * 0.01f;
float expected_energy_remaining_m1 =
- static_cast(datalayer_extended.tesla.battery_expected_energy_remaining_m1) * 0.02;
- float total_discharge = static_cast(datalayer.battery.status.total_discharged_battery_Wh) * 0.001;
- float total_charge = static_cast(datalayer.battery.status.total_charged_battery_Wh) * 0.001;
+ static_cast(datalayer_extended.tesla.battery_expected_energy_remaining_m1) * 0.02f;
+ float total_discharge = static_cast(datalayer.battery.status.total_discharged_battery_Wh) * 0.001f;
+ float total_charge = static_cast(datalayer.battery.status.total_charged_battery_Wh) * 0.001f;
float packMass = static_cast(datalayer_extended.tesla.battery_packMass);
float platformMaxBusVoltage =
- static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375;
- float bms_min_voltage = static_cast(datalayer_extended.tesla.BMS_min_voltage) * 0.01 * 2;
- float bms_max_voltage = static_cast(datalayer_extended.tesla.BMS_max_voltage) * 0.01 * 2;
+ static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1f + 375;
+ float bms_min_voltage = static_cast(datalayer_extended.tesla.BMS_min_voltage) * 0.01f * 2;
+ float bms_max_voltage = static_cast(datalayer_extended.tesla.BMS_max_voltage) * 0.01f * 2;
float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current);
float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current);
- float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave) * 0.1;
- float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max) * 0.1;
- float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min) * 0.1;
- float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui) * 0.1;
- float BrickVoltageMax = static_cast(datalayer_extended.tesla.battery_BrickVoltageMax) * 0.002;
- float BrickVoltageMin = static_cast(datalayer_extended.tesla.battery_BrickVoltageMin) * 0.002;
- float BrickModelTMax = static_cast(datalayer_extended.tesla.battery_BrickModelTMax) * 0.5 - 40;
- float BrickModelTMin = static_cast(datalayer_extended.tesla.battery_BrickModelTMin) * 0.5 - 40;
+ float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave) * 0.1f;
+ float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max) * 0.1f;
+ float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min) * 0.1f;
+ float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui) * 0.1f;
+ float BrickVoltageMax = static_cast(datalayer_extended.tesla.battery_BrickVoltageMax) * 0.002f;
+ float BrickVoltageMin = static_cast(datalayer_extended.tesla.battery_BrickVoltageMin) * 0.002f;
+ //float BrickModelTMax = static_cast(datalayer_extended.tesla.battery_BrickModelTMax) * 0.5 - 40;
+ //float BrickModelTMin = static_cast(datalayer_extended.tesla.battery_BrickModelTMin) * 0.5 - 40;
float isolationResistance = static_cast(datalayer_extended.tesla.BMS_isolationResistance) * 10;
float PCS_dcdcMaxOutputCurrentAllowed =
- static_cast(datalayer_extended.tesla.PCS_dcdcMaxOutputCurrentAllowed) * 0.1;
- float PCS_dcdcTemp = static_cast(datalayer_extended.tesla.PCS_dcdcTemp) * 0.1 + 40;
- float PCS_ambientTemp = static_cast(datalayer_extended.tesla.PCS_ambientTemp) * 0.1 + 40;
- float PCS_chgPhATemp = static_cast(datalayer_extended.tesla.PCS_chgPhATemp) * 0.1 + 40;
- float PCS_chgPhBTemp = static_cast(datalayer_extended.tesla.PCS_chgPhBTemp) * 0.1 + 40;
- float PCS_chgPhCTemp = static_cast(datalayer_extended.tesla.PCS_chgPhCTemp) * 0.1 + 40;
- float BMS_maxRegenPower = static_cast(datalayer_extended.tesla.BMS_maxRegenPower) * 0.01;
- float BMS_maxDischargePower = static_cast(datalayer_extended.tesla.BMS_maxDischargePower) * 0.013;
- float BMS_maxStationaryHeatPower = static_cast(datalayer_extended.tesla.BMS_maxStationaryHeatPower) * 0.01;
- float BMS_hvacPowerBudget = static_cast(datalayer_extended.tesla.BMS_hvacPowerBudget) * 0.02;
- float BMS_powerDissipation = static_cast(datalayer_extended.tesla.BMS_powerDissipation) * 0.02;
- float BMS_flowRequest = static_cast(datalayer_extended.tesla.BMS_flowRequest) * 0.3;
+ static_cast(datalayer_extended.tesla.PCS_dcdcMaxOutputCurrentAllowed) * 0.1f;
+ float PCS_dcdcTemp = static_cast(datalayer_extended.tesla.PCS_dcdcTemp) * 0.1f + 40;
+ float PCS_ambientTemp = static_cast(datalayer_extended.tesla.PCS_ambientTemp) * 0.1f + 40;
+ float PCS_chgPhATemp = static_cast(datalayer_extended.tesla.PCS_chgPhATemp) * 0.1f + 40;
+ float PCS_chgPhBTemp = static_cast(datalayer_extended.tesla.PCS_chgPhBTemp) * 0.1f + 40;
+ float PCS_chgPhCTemp = static_cast(datalayer_extended.tesla.PCS_chgPhCTemp) * 0.1f + 40;
+ float BMS_maxRegenPower = static_cast(datalayer_extended.tesla.BMS_maxRegenPower) * 0.01f;
+ float BMS_maxDischargePower = static_cast(datalayer_extended.tesla.BMS_maxDischargePower) * 0.013f;
+ //float BMS_maxStationaryHeatPower = static_cast(datalayer_extended.tesla.BMS_maxStationaryHeatPower) * 0.01f;
+ //float BMS_hvacPowerBudget = static_cast(datalayer_extended.tesla.BMS_hvacPowerBudget) * 0.02;
+ float BMS_powerDissipation = static_cast(datalayer_extended.tesla.BMS_powerDissipation) * 0.02f;
+ float BMS_flowRequest = static_cast(datalayer_extended.tesla.BMS_flowRequest) * 0.3f;
float BMS_inletActiveCoolTargetT =
- static_cast(datalayer_extended.tesla.BMS_inletActiveCoolTargetT) * 0.25 - 25;
- float BMS_inletPassiveTargetT = static_cast(datalayer_extended.tesla.BMS_inletPassiveTargetT) * 0.25 - 25;
+ static_cast(datalayer_extended.tesla.BMS_inletActiveCoolTargetT) * 0.25f - 25;
+ float BMS_inletPassiveTargetT = static_cast(datalayer_extended.tesla.BMS_inletPassiveTargetT) * 0.25f - 25;
float BMS_inletActiveHeatTargetT =
- static_cast(datalayer_extended.tesla.BMS_inletActiveHeatTargetT) * 0.25 - 25;
- float BMS_packTMin = static_cast(datalayer_extended.tesla.BMS_packTMin) * 0.25 - 25;
- float BMS_packTMax = static_cast(datalayer_extended.tesla.BMS_packTMax) * 0.25 - 25;
- float PCS_dcdcMaxLvOutputCurrent = static_cast(datalayer_extended.tesla.PCS_dcdcMaxLvOutputCurrent) * 0.1;
- float PCS_dcdcCurrentLimit = static_cast(datalayer_extended.tesla.PCS_dcdcCurrentLimit) * 0.1;
+ static_cast(datalayer_extended.tesla.BMS_inletActiveHeatTargetT) * 0.25f - 25;
+ float BMS_packTMin = static_cast(datalayer_extended.tesla.BMS_packTMin) * 0.25f - 25;
+ float BMS_packTMax = static_cast(datalayer_extended.tesla.BMS_packTMax) * 0.25f - 25;
+ float PCS_dcdcMaxLvOutputCurrent = static_cast(datalayer_extended.tesla.PCS_dcdcMaxLvOutputCurrent) * 0.1f;
+ float PCS_dcdcCurrentLimit = static_cast(datalayer_extended.tesla.PCS_dcdcCurrentLimit) * 0.1f;
float PCS_dcdcLvOutputCurrentTempLimit =
- static_cast(datalayer_extended.tesla.PCS_dcdcLvOutputCurrentTempLimit) * 0.1;
- float PCS_dcdcUnifiedCommand = static_cast(datalayer_extended.tesla.PCS_dcdcUnifiedCommand) * 0.001;
+ static_cast(datalayer_extended.tesla.PCS_dcdcLvOutputCurrentTempLimit) * 0.1f;
+ float PCS_dcdcUnifiedCommand = static_cast(datalayer_extended.tesla.PCS_dcdcUnifiedCommand) * 0.001f;
float PCS_dcdcCLAControllerOutput =
- static_cast(datalayer_extended.tesla.PCS_dcdcCLAControllerOutput * 0.001);
+ static_cast(datalayer_extended.tesla.PCS_dcdcCLAControllerOutput * 0.001f);
float PCS_dcdcTankVoltage = static_cast(datalayer_extended.tesla.PCS_dcdcTankVoltage);
float PCS_dcdcTankVoltageTarget = static_cast(datalayer_extended.tesla.PCS_dcdcTankVoltageTarget);
- float PCS_dcdcClaCurrentFreq = static_cast(datalayer_extended.tesla.PCS_dcdcClaCurrentFreq) * 0.0976563;
- float PCS_dcdcTCommMeasured = static_cast(datalayer_extended.tesla.PCS_dcdcTCommMeasured) * 0.00195313;
- float PCS_dcdcShortTimeUs = static_cast(datalayer_extended.tesla.PCS_dcdcShortTimeUs) * 0.000488281;
- float PCS_dcdcHalfPeriodUs = static_cast(datalayer_extended.tesla.PCS_dcdcHalfPeriodUs) * 0.000488281;
+ float PCS_dcdcClaCurrentFreq = static_cast(datalayer_extended.tesla.PCS_dcdcClaCurrentFreq) * 0.0976563f;
+ float PCS_dcdcTCommMeasured = static_cast(datalayer_extended.tesla.PCS_dcdcTCommMeasured) * 0.00195313f;
+ float PCS_dcdcShortTimeUs = static_cast(datalayer_extended.tesla.PCS_dcdcShortTimeUs) * 0.000488281f;
+ float PCS_dcdcHalfPeriodUs = static_cast(datalayer_extended.tesla.PCS_dcdcHalfPeriodUs) * 0.000488281f;
float PCS_dcdcIntervalMaxFrequency = static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxFrequency);
float PCS_dcdcIntervalMaxHvBusVolt =
- static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxHvBusVolt) * 0.1;
+ static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxHvBusVolt) * 0.1f;
float PCS_dcdcIntervalMaxLvBusVolt =
- static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvBusVolt) * 0.1;
+ static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvBusVolt) * 0.1f;
float PCS_dcdcIntervalMaxLvOutputCurr =
static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvOutputCurr);
float PCS_dcdcIntervalMinFrequency = static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinFrequency);
float PCS_dcdcIntervalMinHvBusVolt =
- static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinHvBusVolt) * 0.1;
+ static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinHvBusVolt) * 0.1f;
float PCS_dcdcIntervalMinLvBusVolt =
- static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinLvBusVolt) * 0.1;
+ static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinLvBusVolt) * 0.1f;
float PCS_dcdcIntervalMinLvOutputCurr =
static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinLvOutputCurr);
float PCS_dcdc12vSupportLifetimekWh =
- static_cast(datalayer_extended.tesla.PCS_dcdc12vSupportLifetimekWh) * 0.01;
- float HVP_hvp1v5Ref = static_cast(datalayer_extended.tesla.HVP_hvp1v5Ref) * 0.1;
- float HVP_shuntCurrentDebug = static_cast(datalayer_extended.tesla.HVP_shuntCurrentDebug) * 0.1;
- float HVP_dcLinkVoltage = static_cast(datalayer_extended.tesla.HVP_dcLinkVoltage) * 0.1;
- float HVP_packVoltage = static_cast(datalayer_extended.tesla.HVP_packVoltage) * 0.1;
- float HVP_fcLinkVoltage = static_cast(datalayer_extended.tesla.HVP_fcLinkVoltage) * 0.1;
- float HVP_packContVoltage = static_cast(datalayer_extended.tesla.HVP_packContVoltage) * 0.1;
- float HVP_packNegativeV = static_cast(datalayer_extended.tesla.HVP_packNegativeV) * 0.1;
- float HVP_packPositiveV = static_cast(datalayer_extended.tesla.HVP_packPositiveV) * 0.1;
- float HVP_pyroAnalog = static_cast(datalayer_extended.tesla.HVP_pyroAnalog) * 0.1;
- float HVP_dcLinkNegativeV = static_cast(datalayer_extended.tesla.HVP_dcLinkNegativeV) * 0.1;
- float HVP_dcLinkPositiveV = static_cast(datalayer_extended.tesla.HVP_dcLinkPositiveV) * 0.1;
- float HVP_fcLinkNegativeV = static_cast(datalayer_extended.tesla.HVP_fcLinkNegativeV) * 0.1;
- float HVP_fcContCoilCurrent = static_cast(datalayer_extended.tesla.HVP_fcContCoilCurrent) * 0.1;
- float HVP_fcContVoltage = static_cast(datalayer_extended.tesla.HVP_fcContVoltage) * 0.1;
- float HVP_hvilInVoltage = static_cast(datalayer_extended.tesla.HVP_hvilInVoltage) * 0.1;
- float HVP_hvilOutVoltage = static_cast(datalayer_extended.tesla.HVP_hvilOutVoltage) * 0.1;
- float HVP_fcLinkPositiveV = static_cast(datalayer_extended.tesla.HVP_fcLinkPositiveV) * 0.1;
- float HVP_packContCoilCurrent = static_cast(datalayer_extended.tesla.HVP_packContCoilCurrent) * 0.1;
- float HVP_battery12V = static_cast(datalayer_extended.tesla.HVP_battery12V) * 0.1;
- float HVP_shuntRefVoltageDbg = static_cast(datalayer_extended.tesla.HVP_shuntRefVoltageDbg) * 0.001;
- float HVP_shuntAuxCurrentDbg = static_cast(datalayer_extended.tesla.HVP_shuntAuxCurrentDbg) * 0.1;
- float HVP_shuntBarTempDbg = static_cast(datalayer_extended.tesla.HVP_shuntBarTempDbg) * 0.01;
- float HVP_shuntAsicTempDbg = static_cast(datalayer_extended.tesla.HVP_shuntAsicTempDbg) * 0.01;
+ static_cast(datalayer_extended.tesla.PCS_dcdc12vSupportLifetimekWh) * 0.01f;
+ float HVP_hvp1v5Ref = static_cast(datalayer_extended.tesla.HVP_hvp1v5Ref) * 0.1f;
+ float HVP_shuntCurrentDebug = static_cast(datalayer_extended.tesla.HVP_shuntCurrentDebug) * 0.1f;
+ float HVP_dcLinkVoltage = static_cast(datalayer_extended.tesla.HVP_dcLinkVoltage) * 0.1f;
+ float HVP_packVoltage = static_cast(datalayer_extended.tesla.HVP_packVoltage) * 0.1f;
+ //float HVP_fcLinkVoltage = static_cast(datalayer_extended.tesla.HVP_fcLinkVoltage) * 0.1f;
+ float HVP_packContVoltage = static_cast(datalayer_extended.tesla.HVP_packContVoltage) * 0.1f;
+ //float HVP_packNegativeV = static_cast(datalayer_extended.tesla.HVP_packNegativeV) * 0.1f;
+ //float HVP_packPositiveV = static_cast(datalayer_extended.tesla.HVP_packPositiveV) * 0.1f;
+ float HVP_pyroAnalog = static_cast(datalayer_extended.tesla.HVP_pyroAnalog) * 0.1f;
+ //float HVP_dcLinkNegativeV = static_cast(datalayer_extended.tesla.HVP_dcLinkNegativeV) * 0.1f;
+ //float HVP_dcLinkPositiveV = static_cast(datalayer_extended.tesla.HVP_dcLinkPositiveV) * 0.1f;
+ //float HVP_fcLinkNegativeV = static_cast(datalayer_extended.tesla.HVP_fcLinkNegativeV) * 0.1f;
+ //float HVP_fcContCoilCurrent = static_cast(datalayer_extended.tesla.HVP_fcContCoilCurrent) * 0.1f;
+ //float HVP_fcContVoltage = static_cast(datalayer_extended.tesla.HVP_fcContVoltage) * 0.1f;
+ float HVP_hvilInVoltage = static_cast(datalayer_extended.tesla.HVP_hvilInVoltage) * 0.1f;
+ float HVP_hvilOutVoltage = static_cast(datalayer_extended.tesla.HVP_hvilOutVoltage) * 0.1f;
+ //float HVP_fcLinkPositiveV = static_cast(datalayer_extended.tesla.HVP_fcLinkPositiveV) * 0.1f;
+ float HVP_packContCoilCurrent = static_cast(datalayer_extended.tesla.HVP_packContCoilCurrent) * 0.1f;
+ float HVP_battery12V = static_cast(datalayer_extended.tesla.HVP_battery12V) * 0.1f;
+ //float HVP_shuntRefVoltageDbg = static_cast(datalayer_extended.tesla.HVP_shuntRefVoltageDbg) * 0.001f;
+ //float HVP_shuntAuxCurrentDbg = static_cast(datalayer_extended.tesla.HVP_shuntAuxCurrentDbg) * 0.1f;
+ //float HVP_shuntBarTempDbg = static_cast(datalayer_extended.tesla.HVP_shuntBarTempDbg) * 0.01f;
+ //float HVP_shuntAsicTempDbg = static_cast(datalayer_extended.tesla.HVP_shuntAsicTempDbg) * 0.01f;
static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING",
"CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL",
@@ -178,7 +178,7 @@ class TeslaHtmlRenderer : public BatteryHtmlRenderer {
"FAULTED",
"NUM"};
static const char* BMS_powerLimitState[] = {"NOT_CALCULATED_FOR_DRIVE", "CALCULATED_FOR_DRIVE"};
- static const char* HVP_status[] = {"INVALID", "NOT_AVAILABLE", "STALE", "VALID"};
+ //static const char* HVP_status[] = {"INVALID", "NOT_AVAILABLE", "STALE", "VALID"};
static const char* HVP_contactor[] = {"NOT_ACTIVE", "ACTIVE", "COMPLETED"};
static const char* falseTrue[] = {"False", "True"};
static const char* noYes[] = {"No", "Yes"};
@@ -199,7 +199,7 @@ class TeslaHtmlRenderer : public BatteryHtmlRenderer {
char readablePCSPartNumber[13]; // One extra space for null terminator
memcpy(readablePCSPartNumber, datalayer_extended.tesla.PCS_partNumber,
sizeof(datalayer_extended.tesla.PCS_partNumber));
- readablePCSPartNumber[13] = '\0'; // Null terminate the string
+ readablePCSPartNumber[12] = '\0'; // Null terminate the string
content += "PCS Part Number: " + String(readablePCSPartNumber) + " ";
content += "Battery Manufacture Date: " + String(datalayer_extended.tesla.battery_manufactureDate) + " ";
content += "Battery Pack Mass: " + String(packMass) + " KG ";
diff --git a/Software/src/battery/TESLA-LEGACY-BATTERY.cpp b/Software/src/battery/TESLA-LEGACY-BATTERY.cpp
new file mode 100644
index 000000000..731b289d5
--- /dev/null
+++ b/Software/src/battery/TESLA-LEGACY-BATTERY.cpp
@@ -0,0 +1,279 @@
+#include "TESLA-LEGACY-BATTERY.h"
+#include
+#include //For unit test
+#include "../datalayer/datalayer.h"
+#include "../devboard/utils/events.h"
+#include "../devboard/utils/logging.h"
+
+inline const char* getBMSState(uint8_t index) {
+ switch (index) {
+ case 0:
+ clear_event(EVENT_CONTACTOR_WELDED);
+ clear_event(EVENT_CONTACTOR_OPEN);
+ return "STANDBY";
+ case 1:
+ clear_event(EVENT_CONTACTOR_WELDED);
+ clear_event(EVENT_CONTACTOR_OPEN);
+ return "DRIVE";
+ case 2:
+ return "SUPPORT";
+ case 3:
+ return "CHARGE";
+ case 4:
+ return "FASTCHARGE";
+ case 5:
+ return "CHARGERVOLTAGE";
+ case 6:
+ return "CLEAR_FAULT";
+ case 7:
+ set_event(EVENT_CONTACTOR_OPEN, 0);
+ return "FAULT";
+ case 8:
+ set_event(EVENT_CONTACTOR_WELDED, 0);
+ return "WELD";
+ case 15:
+ return "SNA";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void TeslaLegacyBattery::update_values() {
+
+ switch (battery_hwID) {
+ case 0: //Not read yet
+ break;
+ case 27: //60kWh
+ case 39:
+ case 42:
+ case 50:
+ datalayer.battery.info.total_capacity_Wh = 60000;
+ break;
+ case 61: //70kWh
+ case 65:
+ case 67:
+ datalayer.battery.info.total_capacity_Wh = 70000;
+ break;
+ case 74: //75kWh
+ case 80:
+ case 84:
+ datalayer.battery.info.total_capacity_Wh = 75000;
+ break;
+ case 24: //85kWh
+ case 31:
+ case 41:
+ case 46:
+ case 49:
+ case 57:
+ case 60:
+ case 64:
+ case 69:
+ case 70:
+ case 86:
+ datalayer.battery.info.total_capacity_Wh = 85000;
+ break;
+ case 68: //90kWh
+ case 71:
+ case 73:
+ case 75:
+ case 76:
+ case 77:
+ case 81:
+ case 82:
+ case 83:
+ case 85:
+ case 94:
+ datalayer.battery.info.total_capacity_Wh = 90000;
+ break;
+ case 79: //100kWh
+ case 89:
+ datalayer.battery.info.total_capacity_Wh = 70000;
+ break;
+ default: //Unknown hwID. Raise event
+ set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, battery_hwID);
+ break;
+ }
+
+ datalayer.battery.status.soh_pptt = BMS_CAC_min / 2316; // uitgelezen minimale CAC / CAC bij nieuw (231,6 Ah)
+
+ datalayer.battery.status.real_soc = (battery_soc_ui * 10); //increase SOC range from 0-100.0 -> 100.00
+
+ datalayer.battery.status.voltage_dV = (battery_volts * 10); //One more decimal needed (370 -> 3700)
+
+ datalayer.battery.status.current_dA = (battery_amps * 10); //13.0A
+
+ //Calculate the remaining Wh amount from SOC% and max Wh value.
+ datalayer.battery.status.remaining_capacity_Wh = static_cast(
+ (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
+
+ datalayer.battery.status.max_charge_power_W = (datalayer.battery.status.voltage_dV * battery_max_charge_current) / 10;
+
+ datalayer.battery.status.max_discharge_power_W =
+ (datalayer.battery.status.voltage_dV * battery_max_discharge_current) / 10;
+
+ datalayer.battery.status.temperature_min_dC = battery_min_temp;
+
+ datalayer.battery.status.temperature_max_dC = battery_max_temp;
+
+ datalayer.battery.status.cell_max_voltage_mV = battery_cell_max_v;
+
+ datalayer.battery.status.cell_min_voltage_mV = battery_cell_min_v;
+}
+
+void TeslaLegacyBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ static uint8_t mux = 0;
+ switch (rx_frame.ID) {
+ case 0x212: // 530 BMS_status: 5
+ battery_BMS_rapidDCLinkDchgRequest = (rx_frame.data.u8[0] & 0x01U);
+ battery_BMS_chargingActiveOrTrans = ((rx_frame.data.u8[0] >> 2) & 0x01U);
+ battery_BMS_dcdcEnableOn = ((rx_frame.data.u8[0] >> 3) & 0x01U);
+ battery_BMS_hvacPowerRequest = ((rx_frame.data.u8[0] >> 4) & 0x01U);
+ battery_BMS_notEnoughPowerForDrive = ((rx_frame.data.u8[0] >> 5) & 0x01U);
+ battery_BMS_hvilStatus = ((rx_frame.data.u8[0] >> 6) & 0x01U);
+ battery_BMS_activeHeatingWorthwhile = ((rx_frame.data.u8[0] >> 7) & 0x01U);
+
+ battery_BMS_highestFaultCategory = (rx_frame.data.u8[1] & 0x07U);
+ battery_BMS_hvilOn = ((rx_frame.data.u8[1] >> 3) & 0x01U);
+
+ battery_BMS_contactorState = ((rx_frame.data.u8[2] >> 0) & 0x0FU);
+ battery_BMS_state = ((rx_frame.data.u8[2] >> 4) & 0x0FU);
+
+ battery_BMS_isolationResistance = (rx_frame.data.u8[3] * 20);
+
+ battery_BMS_contactorStateFC = ((rx_frame.data.u8[4] >> 0) & 0x03U);
+ battery_BMS_cpChargeStatus = ((rx_frame.data.u8[4] >> 2) & 0x07U);
+ battery_BMS_okToShipByAir = ((rx_frame.data.u8[4] >> 6) & 0x01U);
+ battery_BMS_okToShipByLand = ((rx_frame.data.u8[4] >> 7) & 0x01U);
+ break;
+ case 0x102: // BMS_hvBusStatus
+ {
+ battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; // 0|16@1+ (0.01,0) [0|430] "V"
+ int16_t raw_amps = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2];
+ battery_amps = raw_amps * 0.1; // 16|16@1- (0.1,0) [-3276.7|3276.7] "A"
+ } break;
+ case 0x7E2: // read CACmin to determine SOH
+ {
+ if (rx_frame.data.u8[0] == 0) {
+ BMS_CAC_min = (((rx_frame.data.u8[2] >> 4) & 0x0F) | ((rx_frame.data.u8[3]) << 4)) * 10000;
+ }
+ } break;
+ case 0x3D2: //TotalChargeDischarge:
+ battery_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) |
+ (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
+ //0|32@1+ (0.001,0) [0|4294970] "kWh"
+ battery_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) |
+ rx_frame.data.u8[4]);
+ //32|32@1+ (0.001,0) [0|4294970] "kWh"
+ break;
+ case 0x332: //min/max hist values //BattBrickMinMax:
+ cellvoltagesRead = true;
+
+ battery_BrickVoltageMax =
+ (((rx_frame.data.u8[1] & (0x0F)) << 8) | (rx_frame.data.u8[0])) * 2; //to datalayer_extended
+ battery_cell_max_v = battery_BrickVoltageMax;
+ battery_BrickVoltageMin =
+ (((rx_frame.data.u8[5] & (0x0F)) << 8) | (rx_frame.data.u8[4])) * 2; //to datalayer_extended
+ battery_cell_min_v = battery_BrickVoltageMin;
+
+ battery_BrickModelTMax = ((rx_frame.data.u8[3] * 0.5) - 40); //to datalayer_extended
+ battery_max_temp = battery_BrickModelTMax * 10;
+ battery_BrickModelTMin = ((rx_frame.data.u8[7] * 0.5) - 40);
+ //to datalayer_extended
+ battery_min_temp = battery_BrickModelTMin * 10;
+ break;
+ case 0x5D2:
+ if (rx_frame.data.u8[0] == 0x0A) {
+ battery_hwID = rx_frame.data.u8[4] + rx_frame.data.u8[5];
+ }
+ break;
+ case 0x6F2:
+ static uint16_t volts;
+ static uint8_t mux_zero_counter = 0u;
+ static uint8_t mux_max = 0u;
+
+ mux = rx_frame.data.u8[0];
+
+ if (mux <= 23) {
+ volts = (((rx_frame.data.u8[2] & 0x3F) << 8) | rx_frame.data.u8[1]) * 0.30517578125;
+ datalayer.battery.status.cell_voltages_mV[mux * 4] = volts;
+
+ volts =
+ (((rx_frame.data.u8[4] & 0x0F) << 10) | (rx_frame.data.u8[3] << 2) | (rx_frame.data.u8[2] & 0xC0) >> 6) *
+ 0.30517578125;
+ datalayer.battery.status.cell_voltages_mV[mux * 4 + 1] = volts;
+
+ volts =
+ (((rx_frame.data.u8[6] & 0x03) << 12) | (rx_frame.data.u8[5] << 4) | (rx_frame.data.u8[4] & 0xF0) >> 4) *
+ 0.30517578125;
+ datalayer.battery.status.cell_voltages_mV[mux * 4 + 2] = volts;
+
+ volts = ((rx_frame.data.u8[7] << 6) | (rx_frame.data.u8[6] & 0xFC) >> 2) * 0.30517578125;
+ datalayer.battery.status.cell_voltages_mV[mux * 4 + 3] = volts;
+
+ mux_max = (mux > mux_max) ? mux : mux_max;
+ if (mux_zero_counter < 2 && mux == 0u) {
+ mux_zero_counter++;
+ if (mux_zero_counter == 2u) {
+ // The max index will be 2 + mux_max * 3 (see above), so "+ 1" for the number of cells
+ datalayer.battery.info.number_of_cells = (4 * mux_max) + 4;
+ mux_zero_counter++;
+ }
+ }
+ }
+ break;
+ case 0x202: // BMS_driveLimits (CAN-ID 0x202) - Legacy batteries
+ battery_bms_min_voltage =
+ ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; // 0|16@1+ (0.01,0) [0|430] "V"
+ battery_bms_max_voltage =
+ ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01; // 16|16@1+ (0.01,0) [0|430] "V"
+ battery_max_charge_current =
+ (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; // 32|14@1+ (0.1,0) [0|1638.2] "A"
+ battery_max_discharge_current =
+ (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; // 48|14@1+ (0.128,0) [0|2096.896] "A"
+ break;
+
+ case 0x302: // BMS_socStatus (Nieuwe CAN-ID)
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS
+ battery_soc_ui = ((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]; // 0|10@1+ (0.1,0) [0|102.2] "%"
+ break;
+ default:
+ break;
+ }
+}
+
+void TeslaLegacyBattery::transmit_can(unsigned long currentMillis) {
+ //Send 1000ms message
+ if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
+ previousMillis1000 = currentMillis;
+
+ transmit_can_frame(&TESLA_408);
+ logging.println(getBMSState(battery_BMS_state));
+ }
+
+ if (!cellvoltagesRead) {
+ return; //All cellvoltages not read yet, do not proceed with contactor closing
+ }
+
+ if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) {
+ if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
+ previousMillis100 = currentMillis;
+
+ transmit_can_frame(&TESLA_21C);
+ transmit_can_frame(&TESLA_25C);
+ transmit_can_frame(&TESLA_2C8);
+ transmit_can_frame(&TESLA_20E);
+ }
+ }
+}
+
+void TeslaLegacyBattery::setup(void) { // Performs one time setup at startup
+ strncpy(datalayer.system.info.battery_protocol, Name, 63);
+ datalayer.system.info.battery_protocol[63] = '\0';
+ datalayer.battery.info.number_of_cells = 14;
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_100_DV; //Startup in wide range
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60_DV; //Autodetect later
+ datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
+ datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
+ datalayer.system.status.battery_allows_contactor_closing = true;
+}
diff --git a/Software/src/battery/TESLA-LEGACY-BATTERY.h b/Software/src/battery/TESLA-LEGACY-BATTERY.h
new file mode 100644
index 000000000..e8c4f3da3
--- /dev/null
+++ b/Software/src/battery/TESLA-LEGACY-BATTERY.h
@@ -0,0 +1,103 @@
+#ifndef TESLA_LEGACY_BATTERY_H
+#define TESLA_LEGACY_BATTERY_H
+#include "CanBattery.h"
+
+class TeslaLegacyBattery : public CanBattery {
+ public:
+ virtual void setup(void);
+ virtual void handle_incoming_can_frame(CAN_frame rx_frame);
+ virtual void update_values();
+ virtual void transmit_can(unsigned long currentMillis);
+ static constexpr const char* Name = "Tesla Model S/X 2012-2020";
+
+ private:
+ static const int MAX_PACK_VOLTAGE_60_DV = 5000; //TODO, set
+ static const int MIN_PACK_VOLTAGE_60_DV = 3000;
+ static const int MAX_PACK_VOLTAGE_70_DV = 5000; //TODO, set
+ static const int MIN_PACK_VOLTAGE_70_DV = 3000;
+ static const int MAX_PACK_VOLTAGE_75_DV = 5000; //TODO, set
+ static const int MIN_PACK_VOLTAGE_75_DV = 3000;
+ static const int MAX_PACK_VOLTAGE_85_DV = 5000; //TODO, set
+ static const int MIN_PACK_VOLTAGE_85_DV = 3000;
+ static const int MAX_PACK_VOLTAGE_90_DV = 5000; //TODO, set
+ static const int MIN_PACK_VOLTAGE_90_DV = 3000;
+ static const int MAX_PACK_VOLTAGE_100_DV = 5000; //TODO, set
+ static const int MIN_PACK_VOLTAGE_100_DV = 3000;
+ static const int MAX_CELL_DEVIATION_MV = 150;
+ static const int MAX_CELL_VOLTAGE_MV = 4200; //Battery is put into emergency stop if one cell goes over this value
+ static const int MIN_CELL_VOLTAGE_MV = 3400; //Battery is put into emergency stop if one cell goes below this value
+ CAN_frame TESLA_25C = {.FD = false, // fast charge status
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x25C,
+ .data = {0x00, 0x02, 0x2A, 0x09, 0x40, 0xC7, 0x72, 0x81}};
+ CAN_frame TESLA_2C8 = {.FD = false, // GTW status
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x2C8,
+ .data = {0x6F, 0xE8, 0x13, 0x71, 0x1D, 0x24, 0x80, 0x7B}};
+ CAN_frame TESLA_21C = {.FD = false, // charger status
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x21C,
+ .data = {0x31, 0x58, 0x20, 0x89, 0x8C, 0x08, 0x03, 0x08}};
+ CAN_frame TESLA_20E = {.FD = false, // charge port status
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x20E,
+ .data = {0x05, 0x56, 0x22, 0x00, 0xC3, 0x00, 0x02, 0x08}};
+ // Keep alive
+ CAN_frame TESLA_408 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x408, .data = {0x00}};
+ unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
+ unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was send
+ uint32_t BMS_CAC_min = 23160000;
+ uint16_t battery_cell_max_v = 3300;
+ uint16_t battery_cell_min_v = 3300;
+ uint16_t battery_soc_ui = 0;
+ uint8_t battery_BMS_state = 0;
+ bool cellvoltagesRead = false;
+ //0x102: BMS_hvBusStatus
+ uint16_t battery_volts = 0;
+ int16_t battery_amps = 0;
+ //0x2d2: 722 BMSVAlimits
+ uint16_t battery_max_discharge_current = 0;
+ uint16_t battery_max_charge_current = 0;
+ uint16_t battery_bms_max_voltage = 0;
+ uint16_t battery_bms_min_voltage = 0;
+ //0x3d2: 978 BMS_kwhCounter
+ uint32_t battery_total_discharge = 0;
+ uint32_t battery_total_charge = 0;
+ //0x332: 818 BattBrickMinMax:BMS_bmbMinMax
+ int16_t battery_max_temp = 0; // C*
+ int16_t battery_min_temp = 0; // C*
+ uint16_t battery_BrickVoltageMax = 0;
+ uint16_t battery_BrickVoltageMin = 0;
+ uint8_t battery_BrickTempMaxNum = 0;
+ uint8_t battery_BrickTempMinNum = 0;
+ uint8_t battery_BrickModelTMax = 0;
+ uint8_t battery_BrickModelTMin = 0;
+ uint8_t battery_BrickVoltageMaxNum = 0;
+ uint8_t battery_BrickVoltageMinNum = 0;
+ //0x5D2
+ uint8_t battery_hwID = 0;
+ //0x212: 530 BMS_status
+ // Nieuwe variablen voor Classic:
+ bool battery_BMS_rapidDCLinkDchgRequest = false;
+ bool battery_BMS_chargingActiveOrTrans = false;
+ bool battery_BMS_dcdcEnableOn = false;
+ bool battery_BMS_hvilStatus = false;
+ bool battery_BMS_okToShipByAir = false;
+ bool battery_BMS_okToShipByLand = false;
+ bool battery_BMS_activeHeatingWorthwhile = false;
+ bool battery_BMS_notEnoughPowerForSupport = false;
+ bool battery_BMS_hvacPowerRequest = false;
+ bool battery_BMS_notEnoughPowerForDrive = false;
+ bool battery_BMS_hvilOn = false;
+ uint8_t battery_BMS_highestFaultCategory = 0;
+ uint8_t battery_BMS_contactorStateFC = 0;
+ uint8_t battery_BMS_cpChargeStatus = 0;
+ uint16_t battery_BMS_isolationResistance = 0;
+ uint8_t battery_BMS_contactorState = 0;
+};
+
+#endif
diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp
index 9803e7eeb..68b178b0d 100644
--- a/Software/src/battery/TEST-FAKE-BATTERY.cpp
+++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp
@@ -10,7 +10,7 @@ void TestFakeBattery::
datalayer_battery->status.soh_pptt = 9900; // 99.00%
- //datalayer_battery->status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
+ //datalayer_battery->status.voltage_dV = 3700; // 370.0V , value editable via webserver
datalayer_battery->status.current_dA = 0; // 0 A
@@ -43,12 +43,7 @@ void TestFakeBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
void TestFakeBattery::transmit_can(unsigned long currentMillis) {
- // Send 100ms CAN Message
- if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
- previousMillis100 = currentMillis;
- // Put fake messages here incase you want to test sending CAN
- //transmit_can_frame(&TEST);
- }
+ // Fake battery has no CAN sending
}
void TestFakeBattery::setup(void) { // Performs one time setup at startup
diff --git a/Software/src/battery/TEST-FAKE-BATTERY.h b/Software/src/battery/TEST-FAKE-BATTERY.h
index b2d6bae55..6dc84006d 100644
--- a/Software/src/battery/TEST-FAKE-BATTERY.h
+++ b/Software/src/battery/TEST-FAKE-BATTERY.h
@@ -33,16 +33,6 @@ class TestFakeBattery : public CanBattery {
bool* allows_contactor_closing;
static const int MAX_CELL_DEVIATION_MV = 9999;
-
- unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
- unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
- unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
-
- CAN_frame TEST = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x123,
- .data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
};
#endif
diff --git a/Software/src/battery/THINK-BATTERY.cpp b/Software/src/battery/THINK-BATTERY.cpp
new file mode 100644
index 000000000..d449255c4
--- /dev/null
+++ b/Software/src/battery/THINK-BATTERY.cpp
@@ -0,0 +1,151 @@
+#include "THINK-BATTERY.h"
+#include "../battery/BATTERIES.h"
+#include "../communication/can/comm_can.h"
+#include "../datalayer/datalayer.h"
+#include "../devboard/utils/events.h"
+
+void ThinkBattery::update_values() {
+
+ datalayer.battery.status.real_soc = (1000 - sys_dod) * 10;
+
+ datalayer.battery.status.soh_pptt = 9900;
+
+ datalayer.battery.status.voltage_dV = sys_voltage;
+
+ datalayer.battery.status.current_dA = sys_current;
+
+ datalayer.battery.status.max_charge_power_W = (sys_voltage * sys_currentMaxCharge) / 100;
+
+ datalayer.battery.status.max_discharge_power_W = (sys_voltage * sys_currentMaxDischarge) / 100;
+
+ datalayer.battery.status.remaining_capacity_Wh = static_cast(
+ (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
+
+ datalayer.battery.status.temperature_min_dC = (int16_t)(min_pack_temperature * 10);
+
+ datalayer.battery.status.temperature_max_dC = (int16_t)(max_pack_temperature * 10);
+
+ if (max_cellvoltage > 2800) { //Value is low when unavailable
+ datalayer.battery.status.cell_max_voltage_mV = max_cellvoltage;
+ }
+
+ if (min_cellvoltage > 2800) { //Value is low when unavailable
+ datalayer.battery.status.cell_min_voltage_mV = min_cellvoltage;
+ }
+}
+
+void ThinkBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
+ switch (rx_frame.ID) {
+ case 0x300:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //BMSSoftwareTag = rx_frame.data.u8[0] << 16 | rx_frame.data.u8[1] << 8 | rx_frame.data.u8[2];
+ //BMSHardwareTag = rx_frame.data.u8[5];
+ //BMI_Init_Successful = rx_frame.data.u8[6];
+ break;
+ case 0x301:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ sys_current = rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1];
+ sys_voltage = rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3];
+ sys_dod = rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5];
+ sys_tempMean = rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7];
+ break;
+ case 0x302:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ sys_errGeneral = (rx_frame.data.u8[0] & 0x01);
+ sys_isolationError = (rx_frame.data.u8[2] & 0x01);
+ sys_voltageMinDischarge = rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5];
+ sys_currentMaxDischarge = rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7];
+ break;
+ case 0x303:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ sys_voltageMaxCharge = rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1];
+ sys_currentMaxCharge = rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3];
+ //Lots of error flags here, TODO, read them all!
+ break;
+ case 0x304: //Status message
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x305:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ Battery_Type = rx_frame.data.u8[3] >> 5;
+ sys_ZebraTempError = (rx_frame.data.u8[6] & 0x18) >> 3;
+ break;
+ case 0x306:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //EquivalentInternalResistance = rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1];
+ //RelativeEnergyContent = rx_frame.data.u8[2];
+ //BatteryLinearAge = rx_frame.data.u8[3] << 8 | rx_frame.data.u8[4];
+ break;
+ case 0x307:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ sys_numberFailedCells = rx_frame.data.u8[7];
+ break;
+ case 0x308:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //sys_ratedCycles = rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1];
+ //sys_cumOperatingTime = rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3]; //Days
+ //sys_numberOfEOC = rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5];
+ //sys_numberOfRegenBreaking = rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7];
+ break;
+ case 0x309:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //sys_numberSOC_20 = rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1];
+ //sys_numberSOC_10 = rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3];
+ //sys_numberSOC_3 = rx_frame.data.u8[4] << 8 | rx_frame.data.u8[5];
+ //sys_minimumISOResistance = rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7];
+ break;
+ case 0x30A:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ //sys_cumCapacity270_285 = rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1];
+ //sys_cumCapacity285_310 = rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3];
+ //sys_ignition = rx_frame.data.u8[4] & 0x01;
+ BatterySOC = rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7];
+ break;
+ case 0x30E: //Battery serial number 1
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x30F: //Battery serial number 2
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x610:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ max_cellvoltage = ((rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]) * 2.44141);
+ min_cellvoltage = ((rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3]) * 2.44141);
+ max_pack_temperature = (int8_t)rx_frame.data.u8[4];
+ min_pack_temperature = (int8_t)rx_frame.data.u8[5];
+ break;
+ case 0x642:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x644:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x646:
+ datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
+ break;
+ default:
+ break;
+ }
+}
+
+void ThinkBattery::transmit_can(unsigned long currentMillis) {
+ //Send 200ms messages
+ if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
+ previousMillis200 = currentMillis;
+
+ if (datalayer.battery.status.bms_status != FAULT) {
+ transmit_can_frame(&PCU_310);
+ transmit_can_frame(&PCU_311);
+ }
+ }
+}
+
+void ThinkBattery::setup(void) { // Performs one time setup at startup
+ strncpy(datalayer.system.info.battery_protocol, Name, 63);
+ datalayer.system.info.battery_protocol[63] = '\0';
+ datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
+ datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
+ datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
+ datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
+ datalayer.system.status.battery_allows_contactor_closing = true;
+}
diff --git a/Software/src/battery/THINK-BATTERY.h b/Software/src/battery/THINK-BATTERY.h
new file mode 100644
index 000000000..23e29a939
--- /dev/null
+++ b/Software/src/battery/THINK-BATTERY.h
@@ -0,0 +1,51 @@
+#ifndef THINK_CITY_H
+#define THINK_CITY_H
+
+#include "../system_settings.h"
+#include "CanBattery.h"
+
+class ThinkBattery : public CanBattery {
+ public:
+ virtual void setup(void);
+ virtual void handle_incoming_can_frame(CAN_frame rx_frame);
+ virtual void update_values();
+ virtual void transmit_can(unsigned long currentMillis);
+ static constexpr const char* Name = "Think City";
+
+ private:
+ static const int MAX_PACK_VOLTAGE_DV = 3940;
+ static const int MIN_PACK_VOLTAGE_DV = 2880;
+ static const int MAX_CELL_DEVIATION_MV = 150;
+ static const int MAX_CELL_VOLTAGE_MV = 4100;
+ static const int MIN_CELL_VOLTAGE_MV = 3000;
+
+ unsigned long previousMillis200 = 0; // will store last time a 100ms CAN Message was sent
+
+ CAN_frame PCU_310 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 3,
+ .ID = 0x310,
+ .data = {0x00, 0x00, 0x06}}; //Charger Status & TIM Status = TRUE
+ CAN_frame PCU_311 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x311, .data = {0x00, 0x00}};
+
+ uint16_t min_cellvoltage = 3700;
+ uint16_t max_cellvoltage = 3700;
+ uint16_t sys_voltage = 3600;
+ uint16_t sys_dod = 0;
+ uint16_t sys_voltageMinDischarge = MIN_PACK_VOLTAGE_DV;
+ uint16_t sys_currentMaxDischarge = 0;
+ uint16_t sys_currentMaxCharge = 0;
+ uint16_t sys_voltageMaxCharge = MAX_PACK_VOLTAGE_DV;
+ uint16_t BatterySOC = 500;
+ int16_t sys_tempMean = 0;
+ int16_t sys_current = 0;
+ uint8_t Battery_Type = 0;
+ uint8_t sys_ZebraTempError = 0;
+ uint8_t sys_numberFailedCells = 0;
+ int8_t min_pack_temperature = 0;
+ int8_t max_pack_temperature = 0;
+ bool sys_errGeneral = false;
+ bool sys_isolationError = false;
+};
+
+#endif
diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp
index 0ed7a29c4..8843aa802 100644
--- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp
+++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp
@@ -3,20 +3,13 @@
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
+#include "../devboard/utils/common_functions.h"
#include "../devboard/utils/events.h"
-#include "../devboard/utils/logging.h"
void VolvoSpaBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
// Update webserver datalayer
- datalayer_extended.VolvoPolestar.soc_bms = SOC_BMS;
- datalayer_extended.VolvoPolestar.soc_calc = SOC_CALC;
- datalayer_extended.VolvoPolestar.soc_rescaled = datalayer.battery.status.reported_soc;
- datalayer_extended.VolvoPolestar.soh_bms = datalayer.battery.status.soh_pptt;
-
- datalayer_extended.VolvoPolestar.BECMBatteryVoltage = BATT_U;
- datalayer_extended.VolvoPolestar.BECMBatteryCurrent = BATT_I;
datalayer_extended.VolvoPolestar.BECMUDynMaxLim = MAX_U;
datalayer_extended.VolvoPolestar.BECMUDynMinLim = MIN_U;
@@ -41,22 +34,9 @@ void VolvoSpaBattery::
datalayer.battery.status.remaining_capacity_Wh = (datalayer.battery.info.total_capacity_Wh - CHARGE_ENERGY);
- //datalayer.battery.status.real_soc = SOC_BMS; // Use BMS reported SOC, havent figured out how to get the BMS to calibrate empty/full yet
- // Use calculated SOC based on remaining_capacity
- SOC_CALC = (datalayer.battery.status.remaining_capacity_Wh / (datalayer.battery.info.total_capacity_Wh / 1000));
-
- datalayer.battery.status.real_soc = SOC_CALC * 10; //Add one decimal to make it pptt
-
- if (BATT_U > MAX_U) // Protect if overcharged
- {
- datalayer.battery.status.real_soc = 10000;
- } else if (BATT_U < MIN_U) //Protect if undercharged
- {
- datalayer.battery.status.real_soc = 0;
- }
-
- datalayer.battery.status.voltage_dV = BATT_U * 10;
- datalayer.battery.status.current_dA = BATT_I * 10;
+ datalayer.battery.status.real_soc = SOC_BMS * 10; //Add one decimal to make it pptt
+ datalayer.battery.status.voltage_dV = BATT_U / 10; //Remove one decimal
+ datalayer.battery.status.current_dA = -BATT_I; //Invert direction
datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSlowAgi * 1000; //kW to W
datalayer.battery.status.max_charge_power_W = HvBattPwrLimChrgSlowAgi * 1000; //kW to W
@@ -100,29 +80,20 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x3A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
- if ((rx_frame.data.u8[6] & 0x80) == 0x80)
- BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
- else {
- BATT_I = 0;
- logging.println("BATT_I not valid");
+ if ((rx_frame.data.u8[6] & 0x80) == 0x80) {
+ BATT_I = ((((rx_frame.data.u8[6] & 0x7F) << 8) | rx_frame.data.u8[7]) - 16380);
}
- if ((rx_frame.data.u8[2] & 0x08) == 0x08)
- MAX_U = (((rx_frame.data.u8[2] & 0x07) * 256.0 + rx_frame.data.u8[3]) * 0.25);
- else {
- //MAX_U = 0;
- //logging.println("MAX_U not valid"); // Value toggles between true/false from BMS
+ if ((rx_frame.data.u8[2] & 0x08) == 0x08) {
+ MAX_U = ((((rx_frame.data.u8[2] & 0x07) << 8) | rx_frame.data.u8[3]) / 4);
}
- if ((rx_frame.data.u8[4] & 0x08) == 0x08)
- MIN_U = (((rx_frame.data.u8[4] & 0x07) * 256.0 + rx_frame.data.u8[5]) * 0.25);
- else {
- //MIN_U = 0;
- //logging.println("MIN_U not valid"); // Value toggles between true/false from BMS
+ if ((rx_frame.data.u8[4] & 0x08) == 0x08) {
+ MIN_U = ((((rx_frame.data.u8[4] & 0x07) << 8) | rx_frame.data.u8[5]) / 4);
}
if ((rx_frame.data.u8[0] & 0x08) == 0x08) {
- BATT_U = (((rx_frame.data.u8[0] & 0x07) * 256.0 + rx_frame.data.u8[1]) * 0.25);
+ BATT_U = ((((rx_frame.data.u8[0] & 0x07) << 8) | rx_frame.data.u8[1]) * 25);
}
if ((rx_frame.data.u8[0] & 0x40) == 0x40)
@@ -153,29 +124,18 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
break;
case 0x413:
- if ((rx_frame.data.u8[0] & 0x80) == 0x80)
- BATT_ERR_INDICATION = ((rx_frame.data.u8[0] & 0x40) >> 6);
- else {
- BATT_ERR_INDICATION = 0;
- logging.println("BATT_ERR_INDICATION not valid");
+ if ((rx_frame.data.u8[0] & 0x80) == 0x80) {
+ BATT_ERR_INDICATION = ((rx_frame.data.u8[0] & 0x40) >> 6); //TODO, do something with this value?
}
if ((rx_frame.data.u8[0] & 0x20) == 0x20) {
- BATT_T_MAX = ((rx_frame.data.u8[2] & 0x1F) * 256.0 + rx_frame.data.u8[3]);
- BATT_T_MIN = ((rx_frame.data.u8[4] & 0x1F) * 256.0 + rx_frame.data.u8[5]);
- BATT_T_AVG = ((rx_frame.data.u8[0] & 0x1F) * 256.0 + rx_frame.data.u8[1]);
- } else {
- BATT_T_MAX = 0;
- BATT_T_MIN = 0;
- BATT_T_AVG = 0;
- logging.println("BATT_T not valid");
+ BATT_T_MAX = sign_extend_to_int16((((rx_frame.data.u8[2] & 0x1F) << 8) | rx_frame.data.u8[3]), 13);
+ BATT_T_MIN = sign_extend_to_int16((((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5]), 13);
+ BATT_T_AVG = sign_extend_to_int16((((rx_frame.data.u8[0] & 0x1F) << 8) | rx_frame.data.u8[1]), 13);
}
break;
case 0x369:
if ((rx_frame.data.u8[0] & 0x80) == 0x80) {
HvBattPwrLimDchaSoft = (((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[6]) >> 2);
- } else {
- HvBattPwrLimDchaSoft = 0;
- logging.println("HvBattPwrLimDchaSoft not valid");
}
break;
case 0x175:
@@ -200,9 +160,6 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 0x37D:
if ((rx_frame.data.u8[0] & 0x40) == 0x40) {
SOC_BMS = ((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[7]);
- } else {
- SOC_BMS = 0;
- logging.println("SOC_BMS not valid");
}
if ((rx_frame.data.u8[0] & 0x04) == 0x04) {
@@ -254,7 +211,8 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer_extended.VolvoPolestar.DTCcount = 0;
}
} else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.h b/Software/src/battery/VOLVO-SPA-BATTERY.h
index 3c5dbbce6..7a1350793 100644
--- a/Software/src/battery/VOLVO-SPA-BATTERY.h
+++ b/Software/src/battery/VOLVO-SPA-BATTERY.h
@@ -40,17 +40,16 @@ class VolvoSpaBattery : public CanBattery {
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
- float BATT_U = 0; //0x3A
- float MAX_U = 0; //0x3A
- float MIN_U = 0; //0x3A
- float BATT_I = 0; //0x3A
- int32_t CHARGE_ENERGY = 0; //0x1A1
- uint8_t BATT_ERR_INDICATION = 0; //0x413
- float BATT_T_MAX = 0; //0x413
- float BATT_T_MIN = 0; //0x413
- float BATT_T_AVG = 0; //0x413
- uint16_t SOC_BMS = 0; //0X37D
- uint16_t SOC_CALC = 0;
+ int32_t CHARGE_ENERGY = 0; //0x1A1
+ uint16_t BATT_U = 0; //0x3A
+ uint16_t MAX_U = 0; //0x3A
+ uint16_t MIN_U = 0; //0x3A
+ int16_t BATT_I = 0; //0x3A
+ uint8_t BATT_ERR_INDICATION = 0; //0x413
+ int16_t BATT_T_MAX = 0; //0x413
+ int16_t BATT_T_MIN = 0; //0x413
+ int16_t BATT_T_AVG = 0; //0x413
+ uint16_t SOC_BMS = 0; //0X37D
uint16_t CELL_U_MAX = 370; //0x37D
uint16_t CELL_U_MIN = 370; //0x37D
uint8_t CELL_ID_U_MAX = 0; //0x37D
diff --git a/Software/src/battery/VOLVO-SPA-HTML.h b/Software/src/battery/VOLVO-SPA-HTML.h
index 822cafd1d..bffe11c33 100644
--- a/Software/src/battery/VOLVO-SPA-HTML.h
+++ b/Software/src/battery/VOLVO-SPA-HTML.h
@@ -10,14 +10,8 @@ class VolvoSpaHtmlRenderer : public BatteryHtmlRenderer {
String get_status_html() {
String content;
content += " BECM reported number of DTCs: " + String(datalayer_extended.VolvoPolestar.DTCcount) + " ";
- content += "BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms / 10.0) + " % ";
- content += "Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc / 10.0) + " % ";
- content += "Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 100.0) + " % ";
- content += "BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms / 100.0) + " % ";
content += "BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV ";
- content += "HV voltage: " + String(datalayer_extended.VolvoPolestar.BECMBatteryVoltage) + " V ";
- content += "HV current: " + String(datalayer_extended.VolvoPolestar.BECMBatteryCurrent) + " A ";
content += "Dynamic max voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMaxLim) + " V ";
content += "Dynamic min voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMinLim) + " V ";
diff --git a/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp
index d4c68d072..2894ee900 100644
--- a/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp
+++ b/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp
@@ -8,7 +8,6 @@
void VolvoSpaHybridBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
- uint8_t cnt = 0;
// Update webserver datalayer
datalayer_extended.VolvoHybrid.soc_bms = SOC_BMS;
@@ -239,7 +238,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
{
transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -251,7 +251,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x23) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -263,7 +264,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x25) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -275,7 +277,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x27) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -287,7 +290,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x29) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -299,7 +303,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2B) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -311,7 +316,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2D) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -323,7 +329,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2F) && (rxConsecutiveFrames == 1)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -336,7 +343,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -348,7 +356,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x23) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -360,7 +369,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x25) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -372,7 +382,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x27) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -384,7 +395,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x29) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -396,7 +408,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2B) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
@@ -408,7 +421,8 @@ void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8);
//transmit_can_frame(&VOLVO_FlowControl); // Send flow control
} else if ((rx_frame.data.u8[0] == 0x2D) && (rxConsecutiveFrames == 2)) {
- cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1];
+ cell_voltages[battery_request_idx] |= rx_frame.data.u8[1];
+ battery_request_idx++;
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
//cell_voltages[battery_request_idx++] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
diff --git a/Software/src/charger/CHARGERS.cpp b/Software/src/charger/CHARGERS.cpp
index 8c9080e74..a5b6095d7 100644
--- a/Software/src/charger/CHARGERS.cpp
+++ b/Software/src/charger/CHARGERS.cpp
@@ -12,8 +12,8 @@ volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cel
volatile float CHARGER_MAX_HV = 420; // Max permissible output (VDC) of charger
volatile float CHARGER_MIN_HV = 200; // Min permissible output (VDC) of charger
volatile float CHARGER_MAX_POWER = 3300; // Max power capable of charger, as a ceiling for validating config
-volatile float CHARGER_MAX_A = 11.5; // Max current output (amps) of charger
-volatile float CHARGER_END_A = 1.0; // Current at which charging is considered complete
+volatile float CHARGER_MAX_A = 11.5f; // Max current output (amps) of charger
+volatile float CHARGER_END_A = 1.0f; // Current at which charging is considered complete
std::vector supported_charger_types() {
std::vector types;
@@ -32,6 +32,7 @@ extern const char* name_for_charger_type(ChargerType type) {
case ChargerType::NissanLeaf:
return NissanLeafCharger::Name;
case ChargerType::None:
+ case ChargerType::Highest:
return "None";
}
@@ -47,5 +48,8 @@ void setup_charger() {
case ChargerType::NissanLeaf:
charger = new NissanLeafCharger();
break;
+ case ChargerType::None:
+ case ChargerType::Highest:
+ break;
}
}
diff --git a/Software/src/charger/CHARGERS.h b/Software/src/charger/CHARGERS.h
index 89d46bff0..f8188a93b 100644
--- a/Software/src/charger/CHARGERS.h
+++ b/Software/src/charger/CHARGERS.h
@@ -1,6 +1,8 @@
#ifndef CHARGERS_H
#define CHARGERS_H
+#include "CanCharger.h"
+
#include "CHEVY-VOLT-CHARGER.h"
#include "NISSAN-LEAF-CHARGER.h"
diff --git a/Software/src/charger/CHEVY-VOLT-CHARGER.cpp b/Software/src/charger/CHEVY-VOLT-CHARGER.cpp
index c7317e80c..a1b855538 100644
--- a/Software/src/charger/CHEVY-VOLT-CHARGER.cpp
+++ b/Software/src/charger/CHEVY-VOLT-CHARGER.cpp
@@ -35,16 +35,16 @@ void ChevyVoltCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
case 0x212:
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
charger_stat_HVcur_temp = (uint16_t)(rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]);
- datalayer.charger.charger_stat_HVcur = (float)(charger_stat_HVcur_temp >> 3) * 0.05;
+ datalayer.charger.charger_stat_HVcur = (float)(charger_stat_HVcur_temp >> 3) * 0.05f;
charger_stat_HVvol_temp = (uint16_t)((((rx_frame.data.u8[1] << 8 | rx_frame.data.u8[2])) >> 1) & 0x3ff);
- datalayer.charger.charger_stat_HVvol = (float)(charger_stat_HVvol_temp) * .5;
+ datalayer.charger.charger_stat_HVvol = (float)(charger_stat_HVvol_temp) * .5f;
charger_stat_LVcur_temp = (uint16_t)(((rx_frame.data.u8[2] << 8 | rx_frame.data.u8[3]) >> 1) & 0x00ff);
- datalayer.charger.charger_stat_LVcur = (float)(charger_stat_LVcur_temp) * .2;
+ datalayer.charger.charger_stat_LVcur = (float)(charger_stat_LVcur_temp) * .2f;
charger_stat_LVvol_temp = (uint16_t)(((rx_frame.data.u8[3] << 8 | rx_frame.data.u8[4]) >> 1) & 0x00ff);
- datalayer.charger.charger_stat_LVvol = (float)(charger_stat_LVvol_temp) * .1;
+ datalayer.charger.charger_stat_LVvol = (float)(charger_stat_LVvol_temp) * .1f;
break;
@@ -52,7 +52,7 @@ void ChevyVoltCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
case 0x30A:
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
charger_stat_ACcur_temp = (uint16_t)((rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]) >> 4);
- datalayer.charger.charger_stat_ACcur = (float)(charger_stat_ACcur_temp) * 0.2;
+ datalayer.charger.charger_stat_ACcur = (float)(charger_stat_ACcur_temp) * 0.2f;
charger_stat_ACvol_temp = (uint16_t)(((rx_frame.data.u8[1] << 8 | rx_frame.data.u8[2]) >> 4) & 0x00ff);
datalayer.charger.charger_stat_ACvol = (float)(charger_stat_ACvol_temp) * 2;
@@ -147,9 +147,9 @@ void ChevyVoltCharger::transmit_can(unsigned long currentMillis) {
/* Serial echo every 5s of charger stats */
if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) {
previousMillis5000ms = currentMillis;
- logging.printf("Charger AC in IAC=%fA VAC=%fV\n", AC_input_current(), AC_input_voltage());
- logging.printf("Charger HV out IDC=%fA VDC=%fV\n", HVDC_output_current(), HVDC_output_voltage());
- logging.printf("Charger LV out IDC=%fA VDC=%fV\n", LVDC_output_current(), LVDC_output_voltage());
+ logging.printf("Charger AC in IAC=%fA VAC=%fV\n", (double)AC_input_current(), (double)AC_input_voltage());
+ logging.printf("Charger HV out IDC=%fA VDC=%fV\n", (double)HVDC_output_current(), (double)HVDC_output_voltage());
+ logging.printf("Charger LV out IDC=%fA VDC=%fV\n", (double)LVDC_output_current(), (double)LVDC_output_voltage());
logging.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
logging.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
}
diff --git a/Software/src/charger/CHEVY-VOLT-CHARGER.h b/Software/src/charger/CHEVY-VOLT-CHARGER.h
index 2d17aa882..8ddf48cc4 100644
--- a/Software/src/charger/CHEVY-VOLT-CHARGER.h
+++ b/Software/src/charger/CHEVY-VOLT-CHARGER.h
@@ -32,9 +32,9 @@ class ChevyVoltCharger : public CanCharger {
* Relative to runtime settings, expectations are:
* hw minimum <= setting minimum <= setting maximum <= hw max
*/
- const float CHEVYVOLT_MAX_HVDC = 420.0;
- const float CHEVYVOLT_MIN_HVDC = 200.0;
- const float CHEVYVOLT_MAX_AMP = 11.5;
+ const float CHEVYVOLT_MAX_HVDC = 420.0f;
+ const float CHEVYVOLT_MIN_HVDC = 200.0f;
+ const float CHEVYVOLT_MAX_AMP = 11.5f;
const float CHEVYVOLT_MAX_POWER = 3300;
/* CAN cycles and timers */
diff --git a/Software/src/charger/CanCharger.h b/Software/src/charger/CanCharger.h
index 68a04b09a..4c3e7acad 100644
--- a/Software/src/charger/CanCharger.h
+++ b/Software/src/charger/CanCharger.h
@@ -72,4 +72,6 @@ class CanCharger : public Charger, Transmitter, CanReceiver {
void transmit_can_frame(CAN_frame* frame) { transmit_can_frame_to_interface(frame, can_interface); }
};
+extern CanCharger* charger;
+
#endif
diff --git a/Software/src/charger/NISSAN-LEAF-CHARGER.cpp b/Software/src/charger/NISSAN-LEAF-CHARGER.cpp
index 92f7a7802..e5a38c70e 100644
--- a/Software/src/charger/NISSAN-LEAF-CHARGER.cpp
+++ b/Software/src/charger/NISSAN-LEAF-CHARGER.cpp
@@ -1,6 +1,7 @@
#include "NISSAN-LEAF-CHARGER.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
#include "CHARGERS.h"
/* This implements Nissan LEAF PDM charger support. 2013-2024 Gen2/3 PDMs are supported
@@ -20,24 +21,10 @@
* battery onto the CAN bus.
*/
-static uint8_t crctable[256] = {
- 0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
- 247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
- 107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
- 9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
- 214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
- 98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
- 18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
- 86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
- 41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
- 27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
- 196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
- 32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
-
static uint8_t calculate_CRC_Nissan(CAN_frame* frame) {
uint8_t crc = 0;
for (uint8_t j = 0; j < 7; j++) {
- crc = crctable[(crc ^ static_cast(frame->data.u8[j])) % 256];
+ crc = crctable_nissan_leaf[(crc ^ static_cast(frame->data.u8[j])) % 256];
}
return crc;
}
@@ -67,7 +54,7 @@ void NissanLeafCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
case 0x390:
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
OBC_Charge_Status = ((rx_frame.data.u8[5] & 0x7E) >> 1);
- if (OBC_Charge_Status == PLUGGED_IN_WAITING_ON_TIMER || CHARGING_OR_INTERRUPTED) {
+ if ((OBC_Charge_Status == PLUGGED_IN_WAITING_ON_TIMER) || (OBC_Charge_Status == CHARGING_OR_INTERRUPTED)) {
PPStatus = true; //plug inserted
} else {
PPStatus = false; //plug not inserted
diff --git a/Software/src/communication/can/comm_can.cpp b/Software/src/communication/can/comm_can.cpp
index 8775b82b7..03dd82f36 100644
--- a/Software/src/communication/can/comm_can.cpp
+++ b/Software/src/communication/can/comm_can.cpp
@@ -18,9 +18,20 @@
#include
#include
+// The spare ESP32 SPI buses are called HSPI and VSPI, whereas on a ESP32S3
+// they are called FSPI and HSPI.
+#ifdef CONFIG_IDF_TARGET_ESP32S3
+#define SPI2515_BUS HSPI
+#define SPI2517_BUS FSPI
+#else
+#define SPI2515_BUS VSPI
+#define SPI2517_BUS HSPI
+#endif
+
volatile CAN_Configuration can_config = {.battery = CAN_NATIVE,
.inverter = CAN_NATIVE,
.battery_double = CAN_ADDON_MCP2515,
+ .battery_triple = CAN_ADDON_MCP2515,
.charger = CAN_NATIVE,
.shunt = CAN_NATIVE};
@@ -47,7 +58,7 @@ uint32_t init_native_can(CAN_Speed speed, gpio_num_t tx_pin, gpio_num_t rx_pin);
ACAN_ESP32_Settings* settingsespcan = nullptr;
static uint32_t QUARTZ_FREQUENCY;
-SPIClass SPI2515;
+SPIClass SPI2515(SPI2515_BUS);
uint8_t user_selected_can_addon_crystal_frequency_mhz = 0;
ACAN2515* can2515;
@@ -56,14 +67,14 @@ ACAN2515Settings* settings2515;
static ACAN2515_Buffer16 gBuffer;
static ACAN2517FDSettings::Oscillator quartz_fd_frequency;
-SPIClass SPI2517;
+SPIClass SPI2517(SPI2517_BUS);
uint8_t user_selected_canfd_addon_crystal_frequency_mhz = 0;
ACAN2517FD* canfd;
ACAN2517FDSettings* settings2517;
bool use_canfd_as_can = false;
-// Initialization functions
-
bool native_can_initialized = false;
+//CAN logging filter settings
+uint16_t user_selected_CAN_ID_cutoff_filter = 0; //Messages below this ID will not be logged in webserver
bool init_CAN() {
@@ -224,7 +235,7 @@ bool init_CAN() {
} else {
logging.print("CAN-FD Configuration error 0x");
logging.println(errorCode2517, HEX);
- set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517);
+ set_event(EVENT_CANMCP2518FD_INIT_FAILURE, (uint8_t)errorCode2517);
return false;
}
}
@@ -399,7 +410,9 @@ void print_can_frame(CAN_frame frame, CAN_Interface interface, frameDirection ms
}
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
- dump_can_frame(frame, interface, msgDir);
+ if (frame.ID > user_selected_CAN_ID_cutoff_filter) { //Only log the message if CAN ID is higher than user set value
+ dump_can_frame(frame, interface, msgDir);
+ }
}
}
@@ -446,7 +459,7 @@ void dump_can_frame(CAN_frame& frame, CAN_Interface interface, frameDirection ms
(int)(interface * 2) + (msgDir == MSG_RX ? 0 : 1));
// Add ID and DLC
- offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
+ offset += snprintf(message_string + offset, message_string_size - offset, "%lX [%u] ", frame.ID, frame.DLC);
// Add data bytes
for (uint8_t i = 0; i < frame.DLC; i++) {
@@ -490,7 +503,7 @@ void restart_can() {
if (canfd) {
SPI2517.begin();
- canfd->begin(*settings2517, [] { can2515->isr(); });
+ canfd->begin(*settings2517, [] { canfd->isr(); });
}
}
diff --git a/Software/src/communication/can/comm_can.h b/Software/src/communication/can/comm_can.h
index e77e86c25..4c3b6af6f 100644
--- a/Software/src/communication/can/comm_can.h
+++ b/Software/src/communication/can/comm_can.h
@@ -6,6 +6,7 @@
extern bool use_canfd_as_can;
extern uint8_t user_selected_can_addon_crystal_frequency_mhz;
extern uint8_t user_selected_canfd_addon_crystal_frequency_mhz;
+extern uint16_t user_selected_CAN_ID_cutoff_filter;
void dump_can_frame(CAN_frame& frame, CAN_Interface interface, frameDirection msgDir);
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, CAN_Interface interface);
@@ -20,6 +21,7 @@ typedef struct {
CAN_Interface battery;
CAN_Interface inverter;
CAN_Interface battery_double;
+ CAN_Interface battery_triple;
CAN_Interface charger;
CAN_Interface shunt;
} CAN_Configuration;
diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp
index dfa84089a..d597273ba 100644
--- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp
+++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp
@@ -5,10 +5,12 @@
// TODO: Ensure valid values at run-time
// User can update all these values via Settings page
-bool contactor_control_enabled = false; //Should GPIO contactor control be performed?
-uint16_t precharge_time_ms = 100; //Precharge time in ms. Adjust depending on capacitance in inverter
-bool pwm_contactor_control = false; //Should the contactors be economized via PWM after they are engaged?
+bool contactor_control_enabled = false; //Should GPIO contactor control be performed?
+bool contactor_control_inverted_logic = false; //Should we control NC contactors? Extremely rare option
+uint16_t precharge_time_ms = 100; //Precharge time in ms. Adjust depending on capacitance in inverter
+bool pwm_contactor_control = false; //Should the contactors be economized via PWM after they are engaged?
bool contactor_control_enabled_double_battery = false; //Should a contactor for the secondary battery be operated?
+bool contactor_control_enabled_triple_battery = false; //Should a contactor for the third battery be operated?
bool remote_bms_reset = false; //Is it possible to actuate BMS reset via MQTT?
bool periodic_bms_reset = false; //Should periodic BMS reset be performed each 24h?
@@ -16,15 +18,8 @@ bool periodic_bms_reset = false; //Should periodic BMS re
enum State { DISCONNECTED, START_PRECHARGE, PRECHARGE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
State contactorStatus = DISCONNECTED;
-const int ON = 1;
-const int OFF = 0;
-
-#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic
-#undef ON
-#define ON 0
-#undef OFF
-#define OFF 1
-#endif //NC_CONTACTORS
+const uint8_t ON = 1;
+const uint8_t OFF = 0;
#define MAX_ALLOWED_FAULT_TICKS 1000 //1000 = 10 seconds
#define NEGATIVE_CONTACTOR_TIME_MS \
@@ -49,6 +44,11 @@ const unsigned long powerRemovalInterval = 24 * 60 * 60 * 1000; // 24 hours in
const unsigned long bmsWarmupDuration = 3000;
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
+
+ if (contactor_control_inverted_logic) {
+ direction = !direction; //Invert direction for NC contactors
+ }
+
if (pwm_contactor_control) {
if (pwm_freq != 0xFFFF) {
ledcWrite(pin, pwm_freq);
@@ -80,16 +80,9 @@ bool init_contactors() {
if (pwm_contactor_control) {
// Setup PWM Channel Frequency and Resolution
-#ifdef CONFIG_IDF_TARGET_ESP32S3
- // ESP32-S3 uses older LEDC API
- ledcSetup(PWM_Positive_Channel, pwm_frequency, PWM_RESOLUTION);
- ledcAttachPin(posPin, PWM_Positive_Channel);
- ledcSetup(PWM_Negative_Channel, pwm_frequency, PWM_RESOLUTION);
- ledcAttachPin(negPin, PWM_Negative_Channel);
-#else
+ // ESP-IDF 5.x uses newer LEDC API (both ESP32 and ESP32-S3)
ledcAttachChannel(posPin, pwm_frequency, PWM_RESOLUTION, PWM_Positive_Channel);
ledcAttachChannel(negPin, pwm_frequency, PWM_RESOLUTION, PWM_Negative_Channel);
-#endif
// Set all pins OFF (0% PWM)
ledcWrite(posPin, PWM_OFF_DUTY);
ledcWrite(negPin, PWM_OFF_DUTY);
@@ -114,6 +107,17 @@ bool init_contactors() {
set(second_contactors, OFF);
}
+ if (contactor_control_enabled_triple_battery) {
+ auto triple_contactors = esp32hal->TRIPLE_BATTERY_CONTACTORS_PIN();
+ if (!esp32hal->alloc_pins(contactors, triple_contactors)) {
+ DEBUG_PRINTF("Triple battery contactor control setup failed\n");
+ return false;
+ }
+
+ pinMode(triple_contactors, OUTPUT);
+ set(triple_contactors, OFF);
+ }
+
// Init BMS contactor
if (periodic_bms_reset || remote_bms_reset || esp32hal->always_enable_bms_power()) {
auto pin = esp32hal->BMS_POWER();
@@ -154,6 +158,10 @@ void handle_contactors() {
handle_contactors_battery2();
}
+ if (contactor_control_enabled_triple_battery) {
+ handle_contactors_battery3();
+ }
+
if (contactor_control_enabled) {
// First check if we have any active errors, incase we do, turn off the battery
if (datalayer.battery.status.bms_status == FAULT) {
@@ -183,8 +191,7 @@ void handle_contactors() {
set(posPin, OFF, PWM_OFF_DUTY);
datalayer.system.status.contactors_engaged = 0;
- if (datalayer.system.status.inverter_allows_contactor_closing &&
- !datalayer.system.settings.equipment_stop_active) {
+ if (datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.info.equipment_stop_active) {
contactorStatus = START_PRECHARGE;
}
}
@@ -192,8 +199,7 @@ void handle_contactors() {
// In case the inverter requests contactors to open, set the state accordingly
if (contactorStatus == COMPLETED) {
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
- if (!datalayer.system.status.inverter_allows_contactor_closing ||
- datalayer.system.settings.equipment_stop_active) {
+ if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.info.equipment_stop_active) {
contactorStatus = DISCONNECTED;
}
// Skip running the state machine below if it has already completed
@@ -215,6 +221,7 @@ void handle_contactors() {
dbg_contactors("NEGATIVE");
prechargeStartTime = currentTime;
contactorStatus = PRECHARGE;
+ datalayer.system.status.contactors_engaged = 3;
break;
case PRECHARGE:
@@ -223,6 +230,7 @@ void handle_contactors() {
dbg_contactors("PRECHARGE");
negativeStartTime = currentTime;
contactorStatus = POSITIVE;
+ datalayer.system.status.contactors_engaged = 3;
}
break;
@@ -232,6 +240,7 @@ void handle_contactors() {
dbg_contactors("POSITIVE");
prechargeCompletedTime = currentTime;
contactorStatus = PRECHARGE_OFF;
+ datalayer.system.status.contactors_engaged = 3;
}
break;
@@ -263,6 +272,18 @@ void handle_contactors_battery2() {
}
}
+void handle_contactors_battery3() {
+ auto third_contactors = esp32hal->SECOND_BATTERY_CONTACTORS_PIN();
+
+ if ((contactorStatus == COMPLETED) && datalayer.system.status.battery3_allowed_contactor_closing) {
+ set(third_contactors, ON);
+ datalayer.system.status.contactors_battery3_engaged = true;
+ } else { // Closing contactors on secondary battery not allowed
+ set(third_contactors, OFF);
+ datalayer.system.status.contactors_battery3_engaged = false;
+ }
+}
+
/* PERIODIC_BMS_RESET - Once every 24 hours we remove power from the BMS_power pin for 30 seconds.
REMOTE_BMS_RESET - Allows the user to remotely powercycle the BMS by sending a command to the emulator via MQTT.
@@ -270,56 +291,98 @@ This makes the BMS recalculate all SOC% and avoid memory leaks
During that time we also set the emulator state to paused in order to not try and send CAN messages towards the battery
Feature is only used if user has enabled PERIODIC_BMS_RESET */
+void bms_power_off() {
+ digitalWrite(esp32hal->BMS_POWER(), LOW);
+}
+
+void bms_power_on() {
+ digitalWrite(esp32hal->BMS_POWER(), HIGH);
+}
+
void handle_BMSpower() {
if (periodic_bms_reset || remote_bms_reset) {
- auto bms_power_pin = esp32hal->BMS_POWER();
-
- // Get current time
currentTime = millis();
- if (periodic_bms_reset) {
- // Check if 24 hours have passed since the last power removal
- if (currentTime - lastPowerRemovalTime >= powerRemovalInterval) {
+ if (datalayer.system.status.bms_reset_status == BMS_RESET_IDLE) {
+ // Idle state, no reset ongoing
+
+ // Check if it's time to perform a periodic BMS reset
+ if (periodic_bms_reset && currentTime - lastPowerRemovalTime >= powerRemovalInterval) {
start_bms_reset();
}
- }
-
- // If power has been removed for user configured interval (1-59 seconds), restore the power
- if (datalayer.system.status.BMS_reset_in_progress &&
- currentTime - lastPowerRemovalTime >= datalayer.battery.settings.user_set_bms_reset_duration_ms) {
- // Reapply power to the BMS
- digitalWrite(bms_power_pin, HIGH);
- bmsPowerOnTime = currentTime;
- datalayer.system.status.BMS_reset_in_progress = false; // Reset the power removal flag
- datalayer.system.status.BMS_startup_in_progress = true; // Set the BMS warmup flag
- }
- //if power has been restored we need to wait a couple of seconds to unpause the battery
- if (datalayer.system.status.BMS_startup_in_progress && currentTime - bmsPowerOnTime >= bmsWarmupDuration) {
+ } else if (datalayer.system.status.bms_reset_status == BMS_RESET_WAITING_FOR_PAUSE) {
+ // We've already issued a pause, now we're waiting for that to take effect.
+
+ int16_t battery_current_dA = datalayer.battery.status.current_dA;
+ int16_t battery2_current_dA = datalayer.battery2.status.current_dA; // Should be 0 if no battery2
+
+ if (
+ // No current, safe to cut power
+ (battery_current_dA == 0 && battery2_current_dA == 0)
+ // or reasonably low current and 5 seconds has passed
+ || (abs(battery_current_dA) < 10 && abs(battery2_current_dA) < 10 &&
+ currentTime - lastPowerRemovalTime >= 5000)) {
+
+ bms_power_off();
+ lastPowerRemovalTime = currentTime;
+ datalayer.system.status.bms_reset_status = BMS_RESET_POWERED_OFF;
+ } else if (currentTime - lastPowerRemovalTime >= 10000) {
+ // There's still current, and we don't want to weld the contactors, so give up.
+
+ logging.printf("BMS reset: Aborting, contactors are still under load.\n");
+
+ datalayer.system.status.bms_reset_status = BMS_RESET_IDLE;
+ set_event(EVENT_PERIODIC_BMS_RESET_FAILURE, 0);
+ clear_event(EVENT_PERIODIC_BMS_RESET_FAILURE);
+ }
+ } else if (datalayer.system.status.bms_reset_status == BMS_RESET_POWERED_OFF) {
+ // Check if the user configured duration has passed
+ if (currentTime - lastPowerRemovalTime >= datalayer.battery.settings.user_set_bms_reset_duration_ms) {
+ bms_power_on();
+ bmsPowerOnTime = currentTime;
+ datalayer.system.status.bms_reset_status = BMS_RESET_POWERING_ON;
+ }
+ } else if (datalayer.system.status.bms_reset_status == BMS_RESET_POWERING_ON) {
+ // Wait for BMS to start up before unpausing
+ if (currentTime - bmsPowerOnTime >= bmsWarmupDuration) {
+ // Unpause the battery
+ setBatteryPause(false, false, false, false);
- setBatteryPause(false, false, false, false);
+ // Reset is complete
- datalayer.system.status.BMS_startup_in_progress = false; // Reset the BMS warmup removal flag
- set_event(EVENT_PERIODIC_BMS_RESET, 0);
+ datalayer.system.status.bms_reset_status = BMS_RESET_IDLE;
+ set_event(EVENT_PERIODIC_BMS_RESET, 0);
+ clear_event(EVENT_PERIODIC_BMS_RESET);
+ }
}
}
}
void start_bms_reset() {
if (periodic_bms_reset || remote_bms_reset) {
- auto bms_power_pin = esp32hal->BMS_POWER();
-
- if (!datalayer.system.status.BMS_reset_in_progress) {
- lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started
- // we are now resetting at the correct time. We don't need to offset anymore
- // Set a flag to let the rest of the system know we are cutting power to the BMS.
- // The battery CAN sending routine will then know not to try guto send anything towards battery while active
- datalayer.system.status.BMS_reset_in_progress = true;
+ if (datalayer.system.status.bms_reset_status == BMS_RESET_IDLE) {
+ // Record when we started the BMS reset process
+ lastPowerRemovalTime = millis();
- // Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop)
- // We try to keep contactors engaged during this pause, and just ramp power down to 0.
+ // Issue a pause, which should stop charge/discharge whilst the reset is ongoing
setBatteryPause(true, false, false, false);
- digitalWrite(bms_power_pin, LOW); // Remove power by setting the BMS power pin to LOW
+ if (contactor_control_enabled) {
+ // We power the contactors directly, so we can avoid closing/opening them
+ // during reset.
+
+ // Thus we can cut the BMS power now
+ bms_power_off();
+
+ // and jump straight to powered off state, no need to wait.
+ datalayer.system.status.bms_reset_status = BMS_RESET_POWERED_OFF;
+ } else {
+ // The BMS powers the contactors, so we need to wait for the pause to
+ // take effect before cutting power to it, or the contactors might drop
+ // out under load and be damaged.
+
+ datalayer.system.status.bms_reset_status = BMS_RESET_WAITING_FOR_PAUSE;
+ }
}
}
}
diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h
index 9441c1211..d2da741cf 100644
--- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h
+++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h
@@ -6,7 +6,9 @@
// Settings that can be changed at run-time
extern bool contactor_control_enabled;
+extern bool contactor_control_inverted_logic;
extern bool contactor_control_enabled_double_battery;
+extern bool contactor_control_enabled_triple_battery;
extern bool pwm_contactor_control;
extern bool periodic_bms_reset;
extern bool remote_bms_reset;
@@ -59,4 +61,13 @@ void handle_contactors();
*/
void handle_contactors_battery2();
+/**
+ * @brief Handle contactors of battery 3
+ *
+ * @param[in] void
+ *
+ * @return void
+ */
+void handle_contactors_battery3();
+
#endif
diff --git a/Software/src/communication/nvm/comm_nvm.cpp b/Software/src/communication/nvm/comm_nvm.cpp
index c42c01ad4..07cf38414 100644
--- a/Software/src/communication/nvm/comm_nvm.cpp
+++ b/Software/src/communication/nvm/comm_nvm.cpp
@@ -22,8 +22,8 @@ void init_stored_settings() {
settings.begin("batterySettings", false);
// Always get the equipment stop status
- datalayer.system.settings.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false);
- if (datalayer.system.settings.equipment_stop_active) {
+ datalayer.system.info.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false);
+ if (datalayer.system.info.equipment_stop_active) {
DEBUG_PRINTF("Equipment stop status set in boot.");
set_event(EVENT_EQUIPMENT_STOP, 1);
}
@@ -43,9 +43,9 @@ void init_stored_settings() {
if (temp != 0) {
datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
- temp = settings.getUInt("MINPERCENTAGE", false);
- if (temp < 499) {
- datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility
+ int32_t temp2 = settings.getInt("MINPERCENTAGE", false);
+ if (temp2 <= 500 && temp2 >= -100) {
+ datalayer.battery.settings.min_percentage = temp2 * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
@@ -87,12 +87,14 @@ void init_stored_settings() {
user_selected_pylon_send = settings.getUInt("PYLONSEND", 0);
user_selected_pylon_30koffset = settings.getBool("PYLONOFFSET", false);
user_selected_pylon_invert_byteorder = settings.getBool("PYLONORDER", false);
+ user_selected_pylon_baudrate = settings.getUInt("PYLONBAUD", 500);
user_selected_inverter_cells = settings.getUInt("INVCELLS", 0);
user_selected_inverter_modules = settings.getUInt("INVMODULES", 0);
user_selected_inverter_cells_per_module = settings.getUInt("INVCELLSPER", 0);
user_selected_inverter_voltage_level = settings.getUInt("INVVLEVEL", 0);
user_selected_inverter_ah_capacity = settings.getUInt("INVAHCAPACITY", 0);
user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0);
+ user_selected_inverter_sungrow_type = settings.getUInt("INVSUNTYPE", 0);
user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false);
user_selected_inverter_deye_workaround = settings.getBool("DEYEBYD", false);
user_selected_can_addon_crystal_frequency_mhz = settings.getUInt("CANFREQ", 8);
@@ -117,32 +119,47 @@ void init_stored_settings() {
return CAN_Interface::CAN_ADDON_MCP2515;
case comm_interface::CanFdAddonMcp2518:
return CAN_Interface::CANFD_ADDON_MCP2518;
+ case comm_interface::RS485:
+ case comm_interface::Modbus:
+ case comm_interface::Highest:
+ return CAN_Interface::NO_CAN_INTERFACE;
}
- return CAN_Interface::CAN_NATIVE;
+ return CAN_Interface::CAN_NATIVE; //Failed to determine, return CAN native
};
can_config.battery = readIf("BATTCOMM");
can_config.battery_double = readIf("BATT2COMM");
+ can_config.battery_triple = readIf("BATT3COMM");
can_config.inverter = readIf("INVCOMM");
can_config.charger = readIf("CHGCOMM");
can_config.shunt = readIf("SHUNTCOMM");
equipment_stop_behavior = (STOP_BUTTON_BEHAVIOR)settings.getUInt("EQSTOP", (int)STOP_BUTTON_BEHAVIOR::NOT_CONNECTED);
user_selected_second_battery = settings.getBool("DBLBTR", false);
+ user_selected_triple_battery = settings.getBool("TRIBTR", false);
contactor_control_enabled = settings.getBool("CNTCTRL", false);
+ contactor_control_inverted_logic = settings.getBool("NCCONTACTOR", false);
precharge_time_ms = settings.getUInt("PRECHGMS", 100);
contactor_control_enabled_double_battery = settings.getBool("CNTCTRLDBL", false);
+ contactor_control_enabled_triple_battery = settings.getBool("CNTCTRLTRI", false);
pwm_contactor_control = settings.getBool("PWMCNTCTRL", false);
pwm_frequency = settings.getUInt("PWMFREQ", 20000);
pwm_hold_duty = settings.getUInt("PWMHOLD", 250);
periodic_bms_reset = settings.getBool("PERBMSRESET", false);
remote_bms_reset = settings.getBool("REMBMSRESET", false);
use_canfd_as_can = settings.getBool("CANFDASCAN", false);
+#ifdef HW_LILYGO2CAN
+ user_selected_gpioopt1 = (GPIOOPT1)settings.getUInt("GPIOOPT1", 0);
+#endif
+ user_selected_gpioopt2 = (GPIOOPT2)settings.getUInt("GPIOOPT2", 0);
+ user_selected_gpioopt3 = (GPIOOPT3)settings.getUInt("GPIOOPT3", 0);
+ user_selected_gpioopt4 = (GPIOOPT4)settings.getUInt("GPIOOPT4", 0);
precharge_control_enabled = settings.getBool("EXTPRECHARGE", false);
precharge_inverter_normally_open_contactor = settings.getBool("NOINVDISC", false);
precharge_max_precharge_time_before_fault = settings.getUInt("MAXPRETIME", 15000);
+ Precharge_max_PWM_Freq = settings.getUInt("MAXPREFREQ", 34000);
datalayer.system.info.performance_measurement_active = settings.getBool("PERFPROFILE", false);
datalayer.system.info.CAN_usb_logging_active = settings.getBool("CANLOGUSB", false);
@@ -161,8 +178,10 @@ void init_stored_settings() {
wifi_channel = settings.getUInt("WIFICHANNEL", 0);
ssidAP = settings.getString("APNAME", "BatteryEmulator").c_str();
passwordAP = settings.getString("APPASSWORD", "123456789").c_str();
+ espnow_enabled = settings.getBool("ESPNOWENABLED", false);
mqtt_enabled = settings.getBool("MQTTENABLED", false);
mqtt_timeout_ms = settings.getUInt("MQTTTIMEOUT", 2000);
+ mqtt_publish_interval_ms = settings.getUInt("MQTTPUBLISHMS", 5000);
ha_autodiscovery_enabled = settings.getBool("HADISC", false);
mqtt_transmit_all_cellvoltages = settings.getBool("MQTTCELLV", false);
custom_hostname = settings.getString("HOSTNAME").c_str();
@@ -191,7 +210,7 @@ void init_stored_settings() {
void store_settings_equipment_stop() {
settings.begin("batterySettings", false);
- settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
+ settings.putBool("EQUIPMENT_STOP", datalayer.system.info.equipment_stop_active);
settings.end();
}
@@ -211,7 +230,7 @@ void store_settings() {
if (!settings.putUInt("MAXPERCENTAGE", datalayer.battery.settings.max_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 5);
}
- if (!settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) {
+ if (!settings.putInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 6);
}
if (!settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA)) {
diff --git a/Software/src/communication/precharge_control/precharge_control.cpp b/Software/src/communication/precharge_control/precharge_control.cpp
index 95d89980f..d3accd3e7 100644
--- a/Software/src/communication/precharge_control/precharge_control.cpp
+++ b/Software/src/communication/precharge_control/precharge_control.cpp
@@ -3,16 +3,17 @@
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../devboard/hal/hal.h"
+#include "src/battery/BATTERIES.h"
// Parameters adjustable by user in Settings page
bool precharge_control_enabled = false;
bool precharge_inverter_normally_open_contactor = false;
uint16_t precharge_max_precharge_time_before_fault = 15000;
+uint16_t Precharge_max_PWM_Freq = 34000;
// Hardcoded parameters
#define Precharge_default_PWM_Freq 11000
#define Precharge_min_PWM_Freq 5000
-#define Precharge_max_PWM_Freq 34000
#define Precharge_PWM_Res 8
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Precharge_Channel 0
@@ -61,24 +62,25 @@ void handle_precharge_control(unsigned long currentMillis) {
return; // Exit immediately - no further processing allowed. Reboot required to recover
}
+ // If we are running in test mode (No battery configured, enable precharge sequence so user can test HW)
+ if (battery == NULL) {
+ datalayer.system.info.start_precharging = true;
+ datalayer.battery.status.real_bms_status = BMS_STANDBY;
+ }
+
int32_t target_voltage = datalayer.battery.status.voltage_dV;
int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV;
switch (datalayer.system.status.precharge_status) {
case AUTO_PRECHARGE_IDLE:
- if (datalayer.system.settings.start_precharging) {
+ if (datalayer.system.info.start_precharging) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_START;
}
break;
case AUTO_PRECHARGE_START:
freq = Precharge_default_PWM_Freq;
-#ifdef CONFIG_IDF_TARGET_ESP32S3
- // ESP32-S3 uses older LEDC API
- ledcSetup(PWM_Precharge_Channel, freq, Precharge_PWM_Res);
- ledcAttachPin(hia4v1_pin, PWM_Precharge_Channel);
-#else
+ // ESP-IDF 5.x uses newer LEDC API (both ESP32 and ESP32-S3)
ledcAttachChannel(hia4v1_pin, freq, Precharge_PWM_Res, PWM_Precharge_Channel);
-#endif
ledcWriteTone(hia4v1_pin, freq); // Set frequency and set dutycycle to 50%
prechargeStartTime = currentMillis;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING;
@@ -121,10 +123,10 @@ void handle_precharge_control(unsigned long currentMillis) {
logging.printf("Precharge: CRITICAL FAILURE (timeout/BMS fault) -> REQUIRES REBOOT\n");
set_event(EVENT_AUTOMATIC_PRECHARGE_FAILURE, 0);
// Force stop any further precharge attempts
- datalayer.system.settings.start_precharging = false;
+ datalayer.system.info.start_precharging = false;
} else if ((datalayer.battery.status.real_bms_status != BMS_STANDBY &&
datalayer.battery.status.real_bms_status != BMS_ACTIVE) ||
- datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) {
+ datalayer.battery.status.bms_status != ACTIVE || datalayer.system.info.equipment_stop_active) {
pinMode(hia4v1_pin, OUTPUT);
digitalWrite(hia4v1_pin, LOW);
digitalWrite(inverter_disconnect_contactor_pin, CONTACTOR_ON);
@@ -140,16 +142,21 @@ void handle_precharge_control(unsigned long currentMillis) {
break;
case AUTO_PRECHARGE_COMPLETED:
- if (datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != ACTIVE) {
+ // If equipment stop is activated, or BE is in a non-active state, or the
+ // BMS has gone back to standby (eg, after a BMS reset), then we'll allow
+ // the precharge to be restarted.
+ if (datalayer.system.info.equipment_stop_active || datalayer.battery.status.bms_status != ACTIVE ||
+ datalayer.battery.status.real_bms_status == BMS_STANDBY) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
logging.printf("Precharge: equipment stop activated -> IDLE\n");
}
break;
case AUTO_PRECHARGE_OFF:
+ // This is not used anymore?
if (!datalayer.system.status.battery_allows_contactor_closing ||
- !datalayer.system.status.inverter_allows_contactor_closing ||
- datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) {
+ !datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.info.equipment_stop_active ||
+ datalayer.battery.status.bms_status != FAULT) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
pinMode(hia4v1_pin, OUTPUT);
digitalWrite(hia4v1_pin, LOW);
diff --git a/Software/src/communication/precharge_control/precharge_control.h b/Software/src/communication/precharge_control/precharge_control.h
index f02e082e5..6bedebb6a 100644
--- a/Software/src/communication/precharge_control/precharge_control.h
+++ b/Software/src/communication/precharge_control/precharge_control.h
@@ -8,6 +8,7 @@
extern bool precharge_control_enabled;
extern bool precharge_inverter_normally_open_contactor;
extern uint16_t precharge_max_precharge_time_before_fault;
+extern uint16_t Precharge_max_PWM_Freq;
/**
* @brief Contactor initialization
*
diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h
index 579ce13ab..b7aa81250 100644
--- a/Software/src/datalayer/datalayer.h
+++ b/Software/src/datalayer/datalayer.h
@@ -4,6 +4,8 @@
#include "../devboard/utils/types.h"
#include "../system_settings.h"
+/*Note when editing this file. Order of datatypes matter heavily to keep padding and flash size in check*/
+
struct DATALAYER_BATTERY_INFO_TYPE {
/** uint32_t */
/** Total energy capacity in Watt-hours
@@ -17,7 +19,7 @@ struct DATALAYER_BATTERY_INFO_TYPE {
uint16_t max_design_voltage_dV = 5000;
/** The minimum intended packvoltage, in deciVolt. 3300 = 330.0 V */
uint16_t min_design_voltage_dV = 2500;
- /** The maximum cellvoltage before shutting down, in milliVolt. 4300 = 4.250 V */
+ /** The maximum cellvoltage before shutting down, in milliVolt. 4300 = 4.300 V */
uint16_t max_cell_voltage_mV = 4300;
/** The minimum cellvoltage before shutting down, in milliVolt. 2700 = 2.700 V */
uint16_t min_cell_voltage_mV = 2700;
@@ -34,41 +36,18 @@ struct DATALAYER_BATTERY_INFO_TYPE {
};
struct DATALAYER_BATTERY_STATUS_TYPE {
- /** int32_t */
- /** Instantaneous battery power in Watts. Calculated based on voltage_dV and current_dA */
- /* Positive value = Battery Charging */
- /* Negative value = Battery Discharging */
- int32_t active_power_W;
-
/** uint32_t */
/** Remaining energy capacity in Watt-hours */
- uint32_t remaining_capacity_Wh;
+ uint32_t remaining_capacity_Wh = 0;
/** The remaining capacity reported to the inverter based on min percentage setting, in Watt-hours
* This value will either be scaled or not scaled depending on the value of
* battery.settings.soc_scaling_active
*/
uint32_t reported_remaining_capacity_Wh;
-
- int32_t total_charged_battery_Wh = 0;
- int32_t total_discharged_battery_Wh = 0;
-
/** Maximum allowed battery discharge power in Watts. Set by battery */
uint32_t max_discharge_power_W = 0;
/** Maximum allowed battery charge power in Watts. Set by battery */
uint32_t max_charge_power_W = 0;
- /** Maximum allowed battery discharge current in dA. Calculated based on allowed W and Voltage */
- uint16_t max_discharge_current_dA = 0;
- /** Maximum allowed battery charge current in dA. Calculated based on allowed W and Voltage */
- uint16_t max_charge_current_dA = 0;
-
- /** int16_t */
- /** Maximum temperature currently measured in the pack, in dยฐC. 150 = 15.0 ยฐC */
- int16_t temperature_max_dC;
- /** Minimum temperature currently measured in the pack, in dยฐC. 150 = 15.0 ยฐC */
- int16_t temperature_min_dC;
- /** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
- int16_t current_dA;
-
/* Some early integrations do not support reading allowed charge power from battery
On these integrations we need to have the user specify what limits the battery can take */
/** Overriden allowed battery discharge power in Watts. Set by user */
@@ -76,7 +55,19 @@ struct DATALAYER_BATTERY_STATUS_TYPE {
/** Overriden allowed battery charge power in Watts. Set by user */
uint32_t override_charge_power_W = 0;
+ /** int32_t */
+ /** Instantaneous battery power in Watts. Calculated based on voltage_dV and current_dA */
+ /* Positive value = Battery Charging */
+ /* Negative value = Battery Discharging */
+ int32_t active_power_W = 0;
+ int32_t total_charged_battery_Wh = 0;
+ int32_t total_discharged_battery_Wh = 0;
+
/** uint16_t */
+ /** Maximum allowed battery discharge current in dA. Calculated based on allowed W and Voltage */
+ uint16_t max_discharge_current_dA = 0;
+ /** Maximum allowed battery charge current in dA. Calculated based on allowed W and Voltage */
+ uint16_t max_charge_current_dA = 0;
/** State of health in integer-percent x 100. 9900 = 99.00% */
uint16_t soh_pptt = 9900;
/** Instantaneous battery voltage in deciVolts. 3700 = 370.0 V */
@@ -85,15 +76,6 @@ struct DATALAYER_BATTERY_STATUS_TYPE {
uint16_t cell_max_voltage_mV = 3700;
/** Minimum cell voltage currently measured in the pack, in mV */
uint16_t cell_min_voltage_mV = 3700;
- /** All cell voltages currently measured in the pack, in mV.
- * Use with battery.info.number_of_cells to get valid data.
- */
- uint16_t cell_voltages_mV[MAX_AMOUNT_CELLS];
- /** All balancing resistors status inside the pack, either on(1) or off(0).
- * Use with battery.info.number_of_cells to get valid data.
- * Not available for all battery manufacturers.
- */
- bool cell_balancing_status[MAX_AMOUNT_CELLS];
/** The "real" SOC reported from the battery, in integer-percent x 100. 9550 = 95.50% */
uint16_t real_soc;
/** The SOC reported to the inverter, in integer-percent x 100. 9550 = 95.50%.
@@ -103,66 +85,103 @@ struct DATALAYER_BATTERY_STATUS_TYPE {
uint16_t reported_soc;
/** A counter that increases incase a CAN CRC read error occurs */
uint16_t CAN_error_counter;
+
+ /** int16_t */
+ /** Maximum temperature currently measured in the pack, in dยฐC. 150 = 15.0 ยฐC */
+ int16_t temperature_max_dC;
+ /** Minimum temperature currently measured in the pack, in dยฐC. 150 = 15.0 ยฐC */
+ int16_t temperature_min_dC;
+ /** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
+ int16_t current_dA;
+ /** Instantaneous battery current in deciAmpere. Sum of all batteries in the system 95 = 9.5 A */
+ int16_t reported_current_dA;
+
/** uint8_t */
/** A counter set each time a new message comes from battery.
* This value then gets decremented every second. 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 system status, which for now still has the name bms_status */
bms_status_enum bms_status = ACTIVE;
-
/** The current battery status, which for now has the name real_bms_status */
real_bms_status_enum real_bms_status = BMS_DISCONNECTED;
-
/** LED mode, customizable by user */
led_mode_enum led_mode = CLASSIC;
+ /** Balancing status */
+ balancing_status_enum balancing_status = BALANCING_STATUS_UNKNOWN;
+
+ /** All cell voltages currently measured in the pack, in mV.
+ * Use with battery.info.number_of_cells to get valid data.
+ */
+ uint16_t cell_voltages_mV[MAX_AMOUNT_CELLS];
+ /** All balancing resistors status inside the pack, either on(1) or off(0).
+ * Use with battery.info.number_of_cells to get valid data.
+ * Not available for all battery manufacturers.
+ */
+ bool cell_balancing_status[MAX_AMOUNT_CELLS];
};
struct DATALAYER_BATTERY_SETTINGS_TYPE {
- /** SOC scaling setting. Increases battery life.
- * If true will rescale SOC between the configured min/max-percentage */
- bool soc_scaling_active = true;
- /** Minimum percentage setting. Set this value to the lowest real SOC
- * you want the inverter to be able to use. At this real SOC, the inverter
- * will "see" 0% , Example 2000 = 20.0%*/
- int16_t min_percentage = 2000;
+
+ /** Last time a remote set command was received to enable timeout of settings */
+ unsigned long remote_set_timestamp = 0;
+ /** Timeout time for remote limits */
+ unsigned long remote_set_timeout = 0;
+ /* Forced balancing max time & start timestamp */
+ uint32_t balancing_max_time_ms = 3600000; //1h default, (60min*60sec*1000ms)
+ uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started
+ /* Emergency recovery charge max time & start timestamp */
+ uint32_t recovery_charge_max_time_ms = 1800000; //30min default, (30min*60sec*1000ms)
+ uint32_t recovery_charge_start_time_ms = 0; //For keeping track when recovery started
+
/** Maximum percentage setting. Set this value to the highest real SOC
* you want the inverter to be able to use. At this real SOC, the inverter
- * will "see" 100% Example 8000 = 80.0%*/
+ * will "see" 100% Example 8000 = 80.0% */
uint16_t max_percentage = 8000;
-
/** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A, persisted to memory
- * Updates later on via Settings
- */
+ * Updates later on via Settings */
uint16_t max_user_set_charge_dA = 300;
/** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A, persisted to memory
- * Updates later on via Settings
- */
+ * Updates later on via Settings */
uint16_t max_user_set_discharge_dA = 300;
-
- /** Last time a remote set command was received to enable timeout of settings */
- unsigned long remote_set_timestamp = 0;
- /** Timeout time for remote limits */
- unsigned long remote_set_timeout = 0;
/** The remote specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A, NOT persisted to memory */
uint16_t max_remote_set_charge_dA = max_user_set_charge_dA;
/** The remote specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A, NOT persisted to memory */
uint16_t max_remote_set_discharge_dA = max_user_set_discharge_dA;
-
- /** User specified discharge/charge voltages in use. Set to true to use user specified values */
- /** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/
- bool user_set_voltage_limits_active = false;
/** The user specified maximum allowed charge voltage, in deciVolt. 4000 = 400.0 V */
uint16_t max_user_set_charge_voltage_dV = 4500;
/** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */
uint16_t max_user_set_discharge_voltage_dV = 3000;
-
/** The user specified BMS reset period. Keeps track on how many milliseconds should we keep power off during daily BMS reset */
uint16_t user_set_bms_reset_duration_ms = 30000;
+ /* Max cell voltage during forced balancing */
+ uint16_t balancing_max_cell_voltage_mV = 3650;
+ /* Max cell deviation allowed during forced balancing */
+ uint16_t balancing_max_deviation_cell_voltage_mV = 400;
+ /* Float max power during forced balancing */
+ uint16_t balancing_float_power_W = 1000;
+ /* Maximum voltage for entire battery pack during forced balancing */
+ uint16_t balancing_max_pack_voltage_dV = 3940;
+ /** Minimum percentage setting. Set this value to the lowest real SOC
+ * you want the inverter to be able to use. At this real SOC, the inverter
+ * will "see" 0% , Example 2000 = 20.0% */
+ int16_t min_percentage = 2000;
+
+ /** Sofar CAN Battery ID (0-15) used to parallel multiple packs */
+ uint8_t sofar_user_specified_battery_id = 0;
+
+ /** User is trying to recover charge a severely undercharged battery. Temporarily allow low power charging for 30 minutes and force ACTIVE mode
+ * Great caution must be taken while in this mode to avoid a battery fire, since we override any BMS value.
+ */
+ bool user_requests_forced_charging_recovery_mode = false;
+ /** User specified discharge/charge voltages in use. Set to true to use user specified values */
+ /** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/
+ bool user_set_voltage_limits_active = false;
+ /** SOC scaling setting. Increases battery life.
+ * If true will rescale SOC between the configured min/max-percentage */
+ bool soc_scaling_active = true;
/** Parameters for keeping track of the limiting factor in the system */
bool user_settings_limit_discharge = false;
bool user_settings_limit_charge = false;
@@ -170,27 +189,12 @@ struct DATALAYER_BATTERY_SETTINGS_TYPE {
bool remote_settings_limit_charge = false;
bool inverter_limits_discharge = false;
bool inverter_limits_charge = false;
-
/** Tesla specific settings that are edited on the fly when manually forcing a balance charge for LFP chemistry */
/* Bool for specifying if user has requested manual function */
bool user_requests_balancing = false;
bool user_requests_tesla_isolation_clear = false;
bool user_requests_tesla_bms_reset = false;
bool user_requests_tesla_soc_reset = false;
- /* Forced balancing max time & start timestamp */
- uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms)
- uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started
- /* Max cell voltage during forced balancing */
- uint16_t balancing_max_cell_voltage_mV = 3650;
- /* Max cell deviation allowed during forced balancing */
- uint16_t balancing_max_deviation_cell_voltage_mV = 400;
- /* Float max power during forced balancing */
- uint16_t balancing_float_power_W = 1000;
- /* Maximum voltage for entire battery pack during forced balancing */
- uint16_t balancing_max_pack_voltage_dV = 3940;
-
- /** Sofar CAN Battery ID (0-15) used to parallel multiple packs */
- uint8_t sofar_user_specified_battery_id = 0;
};
typedef struct {
@@ -218,31 +222,34 @@ struct DATALAYER_CHARGER_TYPE {
float charger_stat_LVcur = 0;
/** Measured LV voltage from charger **/
float charger_stat_LVvol = 0;
- /** True if charger is enabled */
- bool charger_HV_enabled = false;
- /** True if the 12V DC/DC output is enabled */
- bool charger_aux12V_enabled = false;
/** uint8_t */
/** A counter set each time a new message comes from charger.
* This value then gets decremented every second. Incase we reach 0
* we report the battery as missing entirely on the CAN bus.
*/
uint8_t CAN_charger_still_alive = CAN_STILL_ALIVE;
+ /** True if charger is enabled */
+ bool charger_HV_enabled = false;
+ /** True if the 12V DC/DC output is enabled */
+ bool charger_aux12V_enabled = false;
};
struct DATALAYER_SHUNT_TYPE {
- /** 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;
/** measured battery voltage in mV (S-BOX) **/
uint32_t measured_voltage_mV = 0;
/** measured output voltage in mV (eg. S-BOX) **/
uint32_t measured_outvoltage_mV = 0;
+
/** measured amperage in mA (eg. S-BOX) **/
int32_t measured_amperage_mA = 0;
/** Average current from last 1s **/
int32_t measured_avg1S_amperage_mA = 0;
+
+ /** 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;
+
/** True if contactors are precharging state */
bool precharging = false;
/** True if the contactor controlled by battery-emulator is closed */
@@ -252,17 +259,24 @@ struct DATALAYER_SHUNT_TYPE {
};
struct DATALAYER_SYSTEM_INFO_TYPE {
- /** ESP32 main CPU temperature, for displaying on webserver and for safeties */
- float CPU_temperature = 0;
+ /** array with incoming CAN messages, for displaying on webserver */
+ char logged_can_messages[15000] = {0};
/** array with type of battery used, for displaying on webserver */
char battery_protocol[64] = {0};
/** array with type of battery used, for displaying on webserver */
- char shunt_protocol[64] = {0};
+ char shunt_protocol[32] = {0};
/** array with type of inverter brand used, for displaying on webserver */
char inverter_brand[8] = {0};
- /** array with incoming CAN messages, for displaying on webserver */
- char logged_can_messages[15000] = {0};
+
size_t logged_can_messages_offset = 0;
+ /** ESP32 main CPU temperature, for displaying on webserver and for safeties */
+ float CPU_temperature = 0;
+ /** ESP32 free heap amount, for displaying on webserver and for safeties */
+ uint32_t CPU_free_heap = 0;
+
+ /** uint8_t, enumeration which CAN interface should be used for log playback */
+ uint8_t can_replay_interface = CAN_NATIVE;
+
/** bool, determines if CAN messages should be logged for webserver */
bool can_logging_active = false;
/** bool, determines if USB serial logging should occur */
@@ -275,8 +289,6 @@ struct DATALAYER_SYSTEM_INFO_TYPE {
bool web_logging_active = false;
/** bool, determines if general logging to SD card should be active */
bool SD_logging_active = false;
- /** uint8_t, enumeration which CAN interface should be used for log playback */
- uint8_t can_replay_interface = CAN_NATIVE;
/** bool, determines if CAN replay should loop or not */
bool loop_playback = false;
/** bool, Native CAN failed to send flag */
@@ -287,6 +299,8 @@ struct DATALAYER_SYSTEM_INFO_TYPE {
bool can_2518_send_fail = false;
/** bool, determines if detailed performance measurement should be shown on webserver */
bool performance_measurement_active = false;
+ bool equipment_stop_active = false; //Has user enabled equipment stop?
+ bool start_precharging = false; //Is precharge ongoing?
};
struct DATALAYER_SYSTEM_STATUS_TYPE {
@@ -298,9 +312,6 @@ struct DATALAYER_SYSTEM_STATUS_TYPE {
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;
-
- /** 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 */
@@ -309,11 +320,6 @@ struct DATALAYER_SYSTEM_STATUS_TYPE {
int64_t time_values_us = 0;
/** CAN TX function measurement variable */
int64_t time_cantx_us = 0;
-
- /** Function measurement snapshot variable.
- * This will show the performance of OTA handling when the total time reached a new worst case
- */
- 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
*/
@@ -330,50 +336,42 @@ struct DATALAYER_SYSTEM_STATUS_TYPE {
* This will show the performance of CAN TX when the total time reached a new worst case
*/
int64_t time_snap_cantx_us = 0;
+
/** uint8_t */
/** A counter set each time a new message comes from inverter.
* This value then gets decremented every second. Incase we reach 0
* we report the inverter as missing entirely on the CAN bus.
*/
uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ /** 0 if starting up, 1 if contactors engaged, 2 if the contactors controlled by battery-emulator is opened */
+ uint8_t contactors_engaged = 0;
+ /** State of automatic precharge sequence */
+ PrechargeState precharge_status = AUTO_PRECHARGE_IDLE;
/** True if the primary battery allows for the contactors to close */
bool battery_allows_contactor_closing = false;
-
/** True if the second battery is allowed to close the contactors */
bool battery2_allowed_contactor_closing = false;
-
+ /** True if the third battery is allowed to close the contactors */
+ bool battery3_allowed_contactor_closing = false;
/** True if the inverter allows for the contactors to close */
bool inverter_allows_contactor_closing = true;
-
- /** 0 if starting up, 1 if contactors engaged, 2 if the contactors controlled by battery-emulator is opened */
- uint8_t contactors_engaged = 0;
/** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */
bool contactors_battery2_engaged = false;
-
- /** True if the BMS is being reset, by cutting power towards it */
- bool BMS_reset_in_progress = false;
- /** True if the BMS is starting up */
- bool BMS_startup_in_progress = false;
-
- /** State of automatic precharge sequence */
- PrechargeState precharge_status = AUTO_PRECHARGE_IDLE;
-};
-
-struct DATALAYER_SYSTEM_SETTINGS_TYPE {
- bool equipment_stop_active = false;
- bool start_precharging = false;
+ bool contactors_battery3_engaged = false;
+ /** State of BMS reset sequence */
+ BMSResetState bms_reset_status = BMS_RESET_IDLE;
};
struct DATALAYER_SYSTEM_TYPE {
DATALAYER_SYSTEM_INFO_TYPE info;
DATALAYER_SYSTEM_STATUS_TYPE status;
- DATALAYER_SYSTEM_SETTINGS_TYPE settings;
};
class DataLayer {
public:
DATALAYER_BATTERY_TYPE battery;
DATALAYER_BATTERY_TYPE battery2;
+ DATALAYER_BATTERY_TYPE battery3;
DATALAYER_SHUNT_TYPE shunt;
DATALAYER_CHARGER_TYPE charger;
DATALAYER_SYSTEM_TYPE system;
diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h
index 19c16bf13..cdd8df4b2 100644
--- a/Software/src/datalayer/datalayer_extended.h
+++ b/Software/src/datalayer/datalayer_extended.h
@@ -7,17 +7,6 @@ struct DATALAYER_INFO_BOLTAMPERA {
/** uint16_t */
/** PID polling parameters */
uint16_t battery_5V_ref = 0;
- int16_t battery_module_temp_1 = 0;
- int16_t battery_module_temp_2 = 0;
- int16_t battery_module_temp_3 = 0;
- int16_t battery_module_temp_4 = 0;
- int16_t battery_module_temp_5 = 0;
- int16_t battery_module_temp_6 = 0;
- uint16_t battery_cell_average_voltage = 0;
- uint16_t battery_cell_average_voltage_2 = 0;
- uint16_t battery_terminal_voltage = 0;
- uint16_t battery_ignition_power_mode = 0;
- int16_t battery_current_7E7 = 0;
uint16_t battery_capacity_my17_18 = 0;
uint16_t battery_capacity_my19plus = 0;
uint16_t battery_SOC_display = 0;
@@ -36,10 +25,38 @@ struct DATALAYER_INFO_BOLTAMPERA {
uint16_t battery_crash_event = 0;
uint16_t battery_HVIL = 0;
uint16_t battery_HVIL_status = 0;
+ uint16_t battery_cell_average_voltage = 0;
+ uint16_t battery_cell_average_voltage_2 = 0;
+ uint16_t battery_terminal_voltage = 0;
+ uint16_t battery_ignition_power_mode = 0;
+
+ int16_t battery_module_temp_1 = 0;
+ int16_t battery_module_temp_2 = 0;
+ int16_t battery_module_temp_3 = 0;
+ int16_t battery_module_temp_4 = 0;
+ int16_t battery_module_temp_5 = 0;
+ int16_t battery_module_temp_6 = 0;
+ int16_t battery_current_7E7 = 0;
int16_t battery_current_7E4 = 0;
};
struct DATALAYER_INFO_BMWPHEV {
+ uint64_t min_cell_voltage_data_age = 0;
+ uint64_t max_cell_voltage_data_age = 0;
+
+ /** uint16_t */
+ /** Min/Max Cell SOH*/
+ uint16_t min_soh_state = 0;
+ uint16_t max_soh_state = 0;
+ uint16_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT
+ uint16_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT
+ uint16_t iso_safety_trg_kohm = 0;
+ uint16_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT
+
+ int16_t battery_voltage_after_contactor = 0;
+ int16_t allowable_charge_amps = 0;
+ int16_t allowable_discharge_amps = 0;
+
/** uint8_t */
/** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/
uint8_t ST_iso_ext = 0;
@@ -70,74 +87,51 @@ struct DATALAYER_INFO_BMWPHEV {
/** uint8_t */
/** Status cold shutoff valve, 0 OK, 1 Short circuit to GND, 2 Short circuit to 12V, 3 Line break, 6 Driver error, 12 Stuck, 13 Stuck, 15 Invalid Signal*/
uint8_t ST_cold_shutoff_valve = 0;
- /** uint16_t */
- /** Terminal 30 - 12V SME Supply Voltage */
- uint16_t T30_Voltage = 0;
/** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/
- uint8_t hvil_status = 0;
- /** Min/Max Cell SOH*/
- uint16_t min_soh_state = 0;
- uint16_t max_soh_state = 0;
- int32_t allowable_charge_amps = 0;
- int32_t allowable_discharge_amps = 0;
- int16_t balancing_status = 0;
- int16_t battery_voltage_after_contactor = 0;
- uint64_t min_cell_voltage_data_age = 0;
- uint64_t max_cell_voltage_data_age = 0;
- int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT
- int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT
- int32_t iso_safety_trg_kohm = 0;
- int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS
- int32_t iso_safety_int_plausible = 0;
- int32_t iso_safety_trg_plausible = 0;
- int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT
- int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better)
+ //uint8_t hvil_status = 0;
uint8_t battery_request_open_contactors = 0;
uint8_t battery_request_open_contactors_instantly = 0;
uint8_t battery_request_open_contactors_fast = 0;
uint8_t battery_charging_condition_delta = 0;
uint8_t battery_DC_link_voltage = 0;
- // DTC Information
- uint8_t dtc_count; // Number of DTCs present
- uint32_t dtc_codes[32]; // Array of DTC codes (3 bytes each, stored as uint32)
- uint8_t dtc_status[32]; // Status byte for each DTC
+ uint8_t dtc_count; // Number of DTCs present
+ uint8_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS
+ uint8_t iso_safety_int_plausible = 0;
+ uint8_t iso_safety_trg_plausible = 0;
+ uint8_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better)
+ uint8_t balancing_status = 0;
+
+ bool dtc_read_failed = false; // Indicates last read attempt failed
+ bool UserRequestDTCreset = false; /** User requesting DTC reset via WebUI*/
+ bool UserRequestBMSReset = false; /** User requesting BMS reset via WebUI*/
+};
+
+struct DATALAYER_INFO_BMWIX {
+ uint32_t
+ dtc_codes[32]; // Array of DTC codes (3 bytes each, stored as uint32) (Same array used on other BMW integrations)
+ uint8_t dtc_status[32]; // Status byte for each DTC (Same array used on other BMW integrations)
unsigned long dtc_last_read_millis; // Timestamp of last successful read
+ uint8_t dtc_count; // Number of DTCs present
bool dtc_read_in_progress; // Flag to prevent concurrent reads
bool dtc_read_failed; // Indicates last read attempt failed
- /** User requesting DTC reset via WebUI*/
- bool UserRequestDTCreset = false;
- /** User requesting BMS reset via WebUI*/
- bool UserRequestBMSReset = false;
+ bool UserRequestDTCreset = false; /** User requesting DTC reset via WebUI*/
+ bool UserRequestBMSReset = false; /** User requesting BMS reset via WebUI*/
};
struct DATALAYER_INFO_BYDATTO3 {
- /** bool */
- /** User requesting crash reset via WebUI*/
- bool UserRequestCrashReset = false;
- /** bool */
- /** Which SOC method currently used. 0 = Estimated, 1 = Measured */
- bool SOC_method = 0;
- /** uint16_t */
+ uint32_t unknown0 = 0; //Unknown polled value
+ uint32_t unknown1 = 0; //Unknown polled value
+
/** SOC% estimate. Estimated from total pack voltage */
uint16_t SOC_estimated = 0;
- /** uint16_t */
/** SOC% raw battery value. Highprecision. Can be locked if pack is crashed */
uint16_t SOC_highprec = 0;
- /** uint16_t */
/** SOC% polled OBD2 value. Can be locked if pack is crashed */
uint16_t SOC_polled = 0;
- /** uint16_t */
/** Voltage raw battery value */
uint16_t voltage_periodic = 0;
- /** uint16_t */
/** Voltage polled OBD2*/
uint16_t voltage_polled = 0;
- /** int16_t */
- /** All the temperature sensors inside the battery pack*/
- int16_t battery_temperatures[10];
- /** unknown values polled */
- uint32_t unknown0 = 0;
- uint32_t unknown1 = 0;
uint16_t chargePower = 0;
uint16_t charge_times = 0;
uint16_t dischargePower = 0;
@@ -146,10 +140,21 @@ struct DATALAYER_INFO_BYDATTO3 {
uint16_t total_charged_kwh = 0;
uint16_t total_discharged_kwh = 0;
uint16_t times_full_power = 0;
- uint8_t unknown10 = 0;
- uint8_t unknown11 = 0;
- uint8_t unknown12 = 0;
- uint8_t unknown13 = 0;
+
+ /** int16_t */
+ /** All the temperature sensors inside the battery pack*/
+ int16_t battery_temperatures[10];
+
+ uint8_t unknown10 = 0; //Unknown polled value
+ uint8_t unknown11 = 0; //Unknown polled value
+ uint8_t unknown12 = 0; //Unknown polled value
+ uint8_t unknown13 = 0; //Unknown polled value
+ /** bool */
+ /** User requesting crash reset via WebUI*/
+ bool UserRequestCrashReset = false;
+ /** bool */
+ /** Which SOC method currently used. 0 = Estimated, 1 = Measured */
+ bool SOC_method = 0;
};
struct DATALAYER_INFO_CELLPOWER {
@@ -219,6 +224,8 @@ struct DATALAYER_INFO_CELLPOWER {
};
struct DATALAYER_INFO_CHADEMO {
+ uint8_t CHADEMO_Status = 0;
+ uint8_t ControlProtocolNumberEV = 0;
bool UserRequestRestart = false;
bool UserRequestStop = false;
bool FaultBatteryVoltageDeviation = false;
@@ -226,70 +233,60 @@ struct DATALAYER_INFO_CHADEMO {
bool FaultBatteryCurrentDeviation = false;
bool FaultBatteryUnderVoltage = false;
bool FaultBatteryOverVoltage = false;
- uint8_t CHADEMO_Status = 0;
- uint8_t ControlProtocolNumberEV = 0;
};
struct DATALAYER_INFO_CMFAEV {
+ uint64_t cumulative_energy_when_discharging = 0;
+ uint64_t cumulative_energy_when_charging = 0;
+ uint64_t cumulative_energy_in_regen = 0;
+
+ uint32_t average_voltage_of_cells = 0;
+
uint16_t soc_z = 0;
uint16_t soc_u = 0;
uint16_t soh_average = 0;
uint16_t max_regen_power = 0;
uint16_t max_discharge_power = 0;
- int16_t average_temperature = 0;
- int16_t minimum_temperature = 0;
- int16_t maximum_temperature = 0;
uint16_t maximum_charge_power = 0;
uint16_t SOH_available_power = 0;
uint16_t SOH_generated_power = 0;
uint16_t lead_acid_voltage = 0;
+
+ int16_t average_temperature = 0;
+ int16_t minimum_temperature = 0;
+ int16_t maximum_temperature = 0;
+
uint8_t highest_cell_voltage_number = 0;
uint8_t lowest_cell_voltage_number = 0;
- uint64_t cumulative_energy_when_discharging = 0;
- uint64_t cumulative_energy_when_charging = 0;
- uint64_t cumulative_energy_in_regen = 0;
+};
+
+struct DATALAYER_INFO_CMPSMART {
+ uint8_t battery_negative_contactor_state = 0;
+ uint8_t battery_precharge_contactor_state = 0;
+ uint8_t battery_positive_contactor_state = 0;
+ uint8_t battery_state = 0;
+ uint8_t eplug_status = 0;
+ uint8_t HVIL_status = 0;
+ uint8_t ev_warning = 0;
+ uint8_t insulation_fault = 0;
+ uint8_t insulation_circuit_status = 0;
+ uint8_t hardware_fault_status = 0;
+ uint8_t l3_fault = 0;
+ uint8_t plausibility_error = 0;
+ uint8_t battery_charging_status = 0;
+ uint8_t battery_fault = 0;
+ uint8_t hvbat_wakeup_state = 0;
+ uint8_t active_DTC_code = 0;
+ uint8_t alert_frame3 = 0;
+ uint8_t alert_frame4 = 0;
+ bool rcd_line_active = false;
+ bool power_auth = false;
+ bool battery_balancing_active = false;
+ bool UserRequestDTCreset = false; /** User requesting DTC reset via WebUI*/
};
struct DATALAYER_INFO_ECMP {
- //mysteryvan parameters
- bool MysteryVan = false;
- bool CrashMemorized = false;
- uint8_t CONTACTOR_OPENING_REASON = 0;
- uint8_t TBMU_FAULT_TYPE = 0;
- uint8_t CONTACTORS_STATE = 0;
- uint16_t HV_BATT_FC_INSU_MINUS_RES, HV_BATT_FC_INSU_PLUS_RES, HV_BATT_FC_VHL_INSU_PLUS_RES,
- HV_BATT_ONLY_INSU_MINUS_RES = 0;
- bool ALERT_CELL_POOR_CONSIST, ALERT_OVERCHARGE, ALERT_BATT, ALERT_LOW_SOC, ALERT_HIGH_SOC, ALERT_SOC_JUMP,
- ALERT_TEMP_DIFF, ALERT_HIGH_TEMP, ALERT_OVERVOLTAGE, ALERT_CELL_OVERVOLTAGE, ALERT_CELL_UNDERVOLTAGE = false;
- //ecmp below
- uint8_t MainConnectorState = 0;
- uint16_t InsulationResistance = 0;
- uint8_t InsulationDiag = 0;
- bool InterlockOpen = false;
- bool UserRequestContactorReset = false;
- bool UserRequestCollisionReset = false;
- bool UserRequestIsolationReset = false;
- bool UserRequestDisableIsoMonitoring = false;
- uint8_t pid_welding_detection = 0;
- uint8_t pid_reason_open = 0;
- uint8_t pid_contactor_status = 0;
- uint8_t pid_negative_contactor_control = 0;
- uint8_t pid_negative_contactor_status = 0;
- uint8_t pid_positive_contactor_control = 0;
- uint8_t pid_positive_contactor_status = 0;
- uint8_t pid_contactor_negative = 0;
- uint8_t pid_contactor_positive = 0;
- uint8_t pid_precharge_relay_control = 0;
- uint8_t pid_precharge_relay_status = 0;
- uint8_t pid_recharge_status = 0;
- uint8_t pid_delta_temperature = 0;
- uint8_t pid_coldest_module = 0;
- uint8_t pid_lowest_temperature = 0;
- uint8_t pid_average_temperature = 0;
- uint8_t pid_highest_temperature = 0;
- uint8_t pid_hottest_module = 0;
- uint16_t pid_avg_cell_voltage = 0;
- int32_t pid_current = 0;
+
uint32_t pid_insulation_res_neg = 0;
uint32_t pid_insulation_res_pos = 0;
uint32_t pid_max_current_10s = 0;
@@ -298,56 +295,113 @@ struct DATALAYER_INFO_ECMP {
uint32_t pid_max_charge_10s = 0;
uint32_t pid_max_charge_30s = 0;
uint32_t pid_energy_capacity = 0;
- uint8_t pid_highest_cell_voltage_num = 0;
- uint8_t pid_lowest_cell_voltage_num = 0;
- uint16_t pid_sum_of_cells = 0;
- uint16_t pid_cell_min_capacity = 0;
- uint8_t pid_cell_voltage_measurement_status = 0;
uint32_t pid_insulation_res = 0;
- uint16_t pid_pack_voltage = 0;
- uint16_t pid_high_cell_voltage = 0;
- uint16_t pid_low_cell_voltage = 0;
- uint8_t pid_battery_energy = 0;
uint32_t pid_crash_counter = 0;
- uint8_t pid_wire_crash = 0;
- uint8_t pid_CAN_crash = 0;
uint32_t pid_history_data = 0;
- uint32_t pid_lowsoc_counter = 0;
uint32_t pid_last_can_failure_detail = 0;
uint32_t pid_hw_version_num = 0;
uint32_t pid_sw_version_num = 0;
- uint32_t pid_factory_mode_control = 0;
- uint8_t pid_battery_serial[13] = {0};
- uint32_t pid_aux_fuse_state = 0;
- uint32_t pid_battery_state = 0;
- uint32_t pid_precharge_short_circuit = 0;
- uint32_t pid_eservice_plug_state = 0;
- uint32_t pid_mainfuse_state = 0;
- uint32_t pid_most_critical_fault = 0;
uint32_t pid_current_time = 0;
uint32_t pid_time_sent_by_car = 0;
- uint32_t pid_12v = 0;
- uint32_t pid_12v_abnormal = 0;
- uint32_t pid_hvil_in_voltage = 0;
- uint32_t pid_hvil_out_voltage = 0;
- uint32_t pid_hvil_state = 0;
- uint32_t pid_bms_state = 0;
uint32_t pid_vehicle_speed = 0;
uint32_t pid_time_spent_over_55c = 0;
uint32_t pid_contactor_closing_counter = 0;
uint32_t pid_date_of_manufacture = 0;
+
+ int32_t pid_current = 0;
+
+ uint16_t pid_most_critical_fault = 0;
+ uint16_t HV_BATT_FC_INSU_MINUS_RES = 0; //mysteryvan parameters
+ uint16_t HV_BATT_FC_INSU_PLUS_RES = 0; //mysteryvan parameters
+ uint16_t HV_BATT_FC_VHL_INSU_PLUS_RES = 0; //mysteryvan parameters
+ uint16_t HV_BATT_ONLY_INSU_MINUS_RES = 0; //mysteryvan parameters
+ uint16_t InsulationResistance = 0;
+ uint16_t pid_avg_cell_voltage = 0;
+ uint16_t pid_lowsoc_counter = 0;
+ uint16_t pid_sum_of_cells = 0;
+ uint16_t pid_cell_min_capacity = 0;
+ uint16_t pid_pack_voltage = 0;
+ uint16_t pid_high_cell_voltage = 0;
+ uint16_t pid_low_cell_voltage = 0;
uint16_t pid_SOH_cell_1 = 0;
+ uint16_t pid_12v = 0;
+ uint16_t pid_hvil_in_voltage = 0;
+ uint16_t pid_hvil_out_voltage = 0;
+
+ uint8_t pid_bms_state = 0;
+ uint8_t pid_hvil_state = 0;
+ uint8_t pid_mainfuse_state = 0;
+ uint8_t pid_precharge_short_circuit = 0;
+ uint8_t pid_eservice_plug_state = 0;
+ uint8_t pid_battery_state = 0;
+ uint8_t pid_aux_fuse_state = 0;
+ uint8_t pid_12v_abnormal = 0;
+ uint8_t InsulationDiag = 0;
+ uint8_t MainConnectorState = 0;
+ uint8_t CONTACTOR_OPENING_REASON = 0; //mysteryvan parameters
+ uint8_t TBMU_FAULT_TYPE = 0; //mysteryvan parameters
+ uint8_t CONTACTORS_STATE = 0; //mysteryvan parameters
+ uint8_t pid_factory_mode_control = 0;
+ uint8_t pid_welding_detection = 0;
+ uint8_t pid_reason_open = 0;
+ uint8_t pid_contactor_status = 0;
+ uint8_t pid_negative_contactor_control = 0;
+ uint8_t pid_negative_contactor_status = 0;
+ uint8_t pid_positive_contactor_control = 0;
+ uint8_t pid_positive_contactor_status = 0;
+ uint8_t pid_contactor_negative = 0;
+ uint8_t pid_contactor_positive = 0;
+ uint8_t pid_precharge_relay_control = 0;
+ uint8_t pid_precharge_relay_status = 0;
+ uint8_t pid_recharge_status = 0;
+ uint8_t pid_coldest_module = 0;
+ uint8_t pid_hottest_module = 0;
+ uint8_t pid_battery_energy = 0;
+ uint8_t pid_wire_crash = 0;
+ uint8_t pid_CAN_crash = 0;
+ uint8_t pid_highest_cell_voltage_num = 0;
+ uint8_t pid_lowest_cell_voltage_num = 0;
+ uint8_t pid_cell_voltage_measurement_status = 0;
+
+ int8_t pid_delta_temperature = 0;
+ int8_t pid_lowest_temperature = 0;
+ int8_t pid_average_temperature = 0;
+ int8_t pid_highest_temperature = 0;
+
+ bool MysteryVan = false; //mysteryvan parameters
+ bool CrashMemorized = false; //mysteryvan parameters
+ bool InterlockOpen = false;
+ bool UserRequestDTCreset = false;
+ bool UserRequestContactorReset = false;
+ bool UserRequestCollisionReset = false;
+ bool UserRequestIsolationReset = false;
+ bool UserRequestDisableIsoMonitoring = false;
+ bool ALERT_CELL_POOR_CONSIST = false; //mysteryvan parameters
+ bool ALERT_OVERCHARGE = false; //mysteryvan parameters
+ bool ALERT_BATT = false; //mysteryvan parameters
+ bool ALERT_LOW_SOC = false; //mysteryvan parameters
+ bool ALERT_HIGH_SOC = false; //mysteryvan parameters
+ bool ALERT_SOC_JUMP = false; //mysteryvan parameters
+ bool ALERT_TEMP_DIFF = false; //mysteryvan parameters
+ bool ALERT_HIGH_TEMP = false; //mysteryvan parameters
+ bool ALERT_OVERVOLTAGE = false; //mysteryvan parameters
+ bool ALERT_CELL_OVERVOLTAGE = false; //mysteryvan parameters
+ bool ALERT_CELL_UNDERVOLTAGE = false; //mysteryvan parameters
+
+ uint8_t pid_battery_serial[13] = {0};
};
struct DATALAYER_INFO_GEELY_GEOMETRY_C {
+ /** int16_t */
+ /** Module temperatures 1-6 */
+ int16_t ModuleTemperatures[6] = {0};
+
/** uint8_t */
/** Battery software/hardware/serial versions, stores raw HEX values for ASCII chars */
uint8_t BatterySoftwareVersion[16] = {0};
uint8_t BatteryHardwareVersion[16] = {0};
uint8_t BatterySerialNumber[28] = {0};
- /** int16_t */
- /** Module temperatures 1-6 */
- int16_t ModuleTemperatures[6] = {0};
+
/** uint16_t */
/** Various values polled via OBD2 PIDs */
uint16_t soc = 0;
@@ -367,78 +421,91 @@ struct DATALAYER_INFO_GEELY_GEOMETRY_C {
};
struct DATALAYER_INFO_KIAHYUNDAI64 {
- uint8_t total_cell_count = 0;
- int16_t battery_12V = 0;
- uint8_t waterleakageSensor = 0;
- int8_t temperature_water_inlet = 0;
- int8_t powerRelayTemperature = 0;
- uint8_t batteryManagementMode = 0;
- uint8_t BMS_ign = 0;
- uint8_t batteryRelay = 0;
- uint16_t inverterVoltage = 0;
- uint8_t ecu_serial_number[16] = {0};
- uint8_t ecu_version_number[16] = {0};
uint32_t cumulative_charge_current_ah = 0;
uint32_t cumulative_discharge_current_ah = 0;
uint32_t cumulative_energy_charged_kWh = 0;
uint32_t cumulative_energy_discharged_kWh = 0;
uint32_t powered_on_total_time = 0;
+
+ uint16_t inverterVoltage = 0;
uint16_t isolation_resistance_kOhm = 0;
uint16_t number_of_standard_charging_sessions = 0;
uint16_t number_of_fastcharging_sessions = 0;
uint16_t accumulated_normal_charging_energy_kWh = 0;
uint16_t accumulated_fastcharging_energy_kWh = 0;
+ uint16_t battery_12V = 0;
+
+ int8_t temperature_water_inlet = 0;
+ int8_t powerRelayTemperature = 0;
+
+ uint8_t total_cell_count = 0;
+ uint8_t waterleakageSensor = 0;
+ uint8_t batteryManagementMode = 0;
+ uint8_t BMS_ign = 0;
+ uint8_t batteryRelay = 0;
+
+ uint8_t ecu_serial_number[16] = {0};
+ uint8_t ecu_version_number[16] = {0};
+};
+
+struct DATALAYER_INFO_RIVIAN {
+ uint16_t pre_contactor_voltage = 3700;
+ uint16_t main_contactor_voltage = 0;
+ uint16_t voltage_reference = 0;
+ uint16_t DCFC_contactor_voltage = 0;
+ uint16_t battery_SOC_max = 0;
+ uint16_t battery_SOC_min = 0;
+ uint8_t BMS_state = 0;
+ uint8_t HVIL = 0;
+ uint8_t error_flags_from_BMS = 0;
+ uint8_t contactor_state = 0;
+ uint8_t HMI_part1 = 0;
+ uint8_t HMI_part2 = 0;
+ uint8_t isolation_fault_status = 0;
+ uint8_t system_safe_state = 0;
+ bool error_relay_open = false;
+ bool IsolationMeasurementOngoing = false;
+ bool puncture_fault = false;
+ bool liquid_fault = false;
+ bool contactor_DCFC_welded = false;
+ bool NACS_charger_detected = false;
+ bool slewrate_potential_violation = false;
+ bool minimum_power_potential_violation = false;
+ bool operation_limit_violation_warning = false;
};
struct DATALAYER_INFO_TESLA {
- /** uint8_t */
- /** Contactor status */
- //uint8_t status_contactor = 0;
- /** uint8_t */
- /** HVIL status */
- uint8_t hvil_status = 0;
- /** uint8_t */
- /** Negative contactor state */
- uint8_t packContNegativeState = 0;
- /** uint8_t */
- /** Positive contactor state */
- uint8_t packContPositiveState = 0;
- /** uint8_t */
- /** HVP set state of contactors */
- uint8_t packContactorSetState = 0;
- /** bool */
- /** Battery pack allows closing of contactors */
- bool packCtrsClosingBlocked = false;
- /** bool */
- /** Pyro test in progress */
- bool pyroTestInProgress = false;
- bool battery_packCtrsOpenNowRequested = false;
- bool battery_packCtrsOpenRequested = false;
- uint8_t battery_packCtrsRequestStatus = 0;
- bool battery_packCtrsResetRequestRequired = false;
- bool battery_dcLinkAllowedToEnergize = false;
- uint8_t BMS_partNumber[12] = {0}; //stores raw HEX values for ASCII chars
+ uint64_t BMS_info_bootGitHash = 0;
+ uint64_t PCS_info_bootGitHash = 0;
+ uint64_t HVP_info_bootGitHash = 0;
+
+ uint32_t HVP_info_bootCrc = 0;
+ uint32_t HVP_info_appCrc = 0;
+ uint32_t PCS_info_appCrc = 0;
+ uint32_t PCS_info_cpu2AppCrc = 0;
+ uint32_t PCS_info_bootCrc = 0;
+ uint32_t PCS_dcdc12vSupportLifetimekWh = 0;
+ uint32_t BMS_info_appCrc = 0;
+ uint32_t BMS_info_bootCrc = 0;
+ uint32_t battery_packMass = 0;
+ uint32_t battery_platformMaxBusVoltage = 0;
+ uint32_t BMS_min_voltage = 0;
+ uint32_t BMS_max_voltage = 0;
+ uint32_t battery_max_charge_current = 0;
+ uint32_t battery_max_discharge_current = 0;
+ uint32_t battery_soc_min = 0;
+ uint32_t battery_soc_max = 0;
+ uint32_t battery_soc_ave = 0;
+ uint32_t battery_soc_ui = 0;
+
uint16_t BMS_info_buildConfigId = 0;
uint16_t BMS_info_hardwareId = 0;
uint16_t BMS_info_componentId = 0;
- uint8_t BMS_info_pcbaId = 0;
- uint8_t BMS_info_assemblyId = 0;
uint16_t BMS_info_usageId = 0;
uint16_t BMS_info_subUsageId = 0;
- uint8_t BMS_info_platformType = 0;
- uint32_t BMS_info_appCrc = 0;
- uint64_t BMS_info_bootGitHash = 0;
- uint8_t BMS_info_bootUdsProtoVersion = 0;
- uint32_t BMS_info_bootCrc = 0;
- uint8_t battery_serialNumber[15] = {0}; //stores raw HEX values for ASCII chars
- uint8_t battery_partNumber[12] = {0}; //stores raw HEX values for ASCII chars
- char* battery_manufactureDate;
- uint8_t battery_beginning_of_life = 0;
- uint8_t battery_battTempPct = 0;
uint16_t battery_dcdcLvBusVolt = 0;
uint16_t battery_dcdcHvBusVolt = 0;
uint16_t battery_dcdcLvOutputCurrent = 0;
- bool BMS352_mux = false; // variable to store when 0x352 mux is present
uint16_t battery_nominal_full_pack_energy = 0;
uint16_t battery_nominal_full_pack_energy_m0 = 0;
uint16_t battery_nominal_energy_remaining = 0;
@@ -451,62 +518,112 @@ struct DATALAYER_INFO_TESLA {
uint16_t battery_energy_buffer_m1 = 0;
uint16_t battery_expected_energy_remaining = 0;
uint16_t battery_expected_energy_remaining_m1 = 0;
- bool battery_full_charge_complete = false;
- bool battery_fully_charged = false;
uint16_t battery_BrickVoltageMax = 0;
uint16_t battery_BrickVoltageMin = 0;
- uint8_t battery_BrickVoltageMaxNum = 0;
- uint8_t battery_BrickVoltageMinNum = 0;
- uint8_t battery_BrickTempMaxNum = 0;
- uint8_t battery_BrickTempMinNum = 0;
- uint8_t battery_BrickModelTMax = 0;
- uint8_t battery_BrickModelTMin = 0;
+ uint16_t HVP_hvp1v5Ref = 0;
+ uint16_t HVP_shuntCurrentDebug = 0;
+ uint16_t PCS_dcdcTemp = 0;
+ uint16_t PCS_ambientTemp = 0;
+ uint16_t PCS_chgPhATemp = 0;
+ uint16_t PCS_chgPhBTemp = 0;
+ uint16_t PCS_chgPhCTemp = 0;
+ uint16_t PCS_dcdcMaxLvOutputCurrent = 0;
+ uint16_t PCS_dcdcCurrentLimit = 0;
+ uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0;
+ uint16_t PCS_dcdcUnifiedCommand = 0;
+ uint16_t PCS_dcdcCLAControllerOutput = 0;
+ uint16_t PCS_dcdcTankVoltage = 0;
+ uint16_t PCS_dcdcTankVoltageTarget = 0;
+ uint16_t PCS_dcdcClaCurrentFreq = 0;
+ uint16_t PCS_dcdcTCommMeasured = 0;
+ uint16_t PCS_dcdcShortTimeUs = 0;
+ uint16_t PCS_dcdcHalfPeriodUs = 0;
+ uint16_t PCS_dcdcIntervalMaxFrequency = 0;
+ uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0;
+ uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0;
+ uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0;
+ uint16_t PCS_dcdcIntervalMinFrequency = 0;
+ uint16_t PCS_dcdcIntervalMinHvBusVolt = 0;
+ uint16_t PCS_dcdcIntervalMinLvBusVolt = 0;
+ uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0;
uint16_t battery_packConfigMultiplexer = 0;
uint16_t battery_moduleType = 0;
uint16_t battery_reservedConfig = 0;
- uint32_t battery_packMass = 0;
- uint32_t battery_platformMaxBusVoltage = 0;
- uint32_t BMS_min_voltage = 0;
- uint32_t BMS_max_voltage = 0;
- uint32_t battery_max_charge_current = 0;
- uint32_t battery_max_discharge_current = 0;
- uint32_t battery_soc_min = 0;
- uint32_t battery_soc_max = 0;
- uint32_t battery_soc_ave = 0;
- uint32_t battery_soc_ui = 0;
- bool BMS_hvilFault = false;
- uint8_t BMS_contactorState = 0;
- uint8_t BMS_state = 0;
- uint8_t BMS_hvState = 0;
uint16_t BMS_isolationResistance = 0;
- uint8_t BMS_uiChargeStatus = 0;
- bool BMS_diLimpRequest = false;
uint16_t BMS_chgPowerAvailable = 0;
- bool BMS_pcsPwmEnabled = false;
uint16_t BMS_maxRegenPower = 0;
uint16_t BMS_maxDischargePower = 0;
uint16_t BMS_maxStationaryHeatPower = 0;
uint16_t BMS_hvacPowerBudget = 0;
- uint8_t BMS_notEnoughPowerForHeatPump = 0;
- uint8_t BMS_powerLimitState = 0;
- uint8_t BMS_inverterTQF = 0;
uint16_t BMS_powerDissipation = 0;
- uint8_t BMS_flowRequest = 0;
uint16_t BMS_inletActiveCoolTargetT = 0;
uint16_t BMS_inletPassiveTargetT = 0;
uint16_t BMS_inletActiveHeatTargetT = 0;
uint16_t BMS_packTMin = 0;
uint16_t BMS_packTMax = 0;
- bool BMS_pcsNoFlowRequest = false;
- bool BMS_noFlowRequest = false;
+ uint16_t PCS_info_buildConfigId = 0;
+ uint16_t PCS_info_hardwareId = 0;
+ uint16_t PCS_info_componentId = 0;
+ uint16_t PCS_dcdcMaxOutputCurrentAllowed = 0;
+ uint16_t PCS_info_usageId = 0;
+ uint16_t PCS_info_subUsageId = 0;
+ uint16_t HVP_dcLinkVoltage = 0;
+ uint16_t HVP_packVoltage = 0;
+ uint16_t HVP_fcLinkVoltage = 0;
+ uint16_t HVP_packContVoltage = 0;
+ uint16_t HVP_packNegativeV = 0;
+ uint16_t HVP_packPositiveV = 0;
+ uint16_t HVP_pyroAnalog = 0;
+ uint16_t HVP_dcLinkNegativeV = 0;
+ uint16_t HVP_dcLinkPositiveV = 0;
+ uint16_t HVP_fcLinkNegativeV = 0;
+ uint16_t HVP_fcContCoilCurrent = 0;
+ uint16_t HVP_fcContVoltage = 0;
+ uint16_t HVP_hvilInVoltage = 0;
+ uint16_t HVP_hvilOutVoltage = 0;
+ uint16_t HVP_fcLinkPositiveV = 0;
+ uint16_t HVP_packContCoilCurrent = 0;
+ uint16_t HVP_battery12V = 0;
+ uint16_t HVP_shuntRefVoltageDbg = 0;
+ uint16_t HVP_shuntAuxCurrentDbg = 0;
+ uint16_t HVP_shuntBarTempDbg = 0;
+ uint16_t HVP_shuntAsicTempDbg = 0;
+ uint16_t HVP_info_buildConfigId = 0;
+ uint16_t HVP_info_hardwareId = 0;
+ uint16_t HVP_info_componentId = 0;
+ uint16_t HVP_info_usageId = 0;
+ uint16_t HVP_info_subUsageId = 0;
+
+ uint8_t hvil_status = 0;
+ uint8_t packContNegativeState = 0;
+ uint8_t packContPositiveState = 0;
+ uint8_t packContactorSetState = 0;
+ uint8_t battery_packCtrsRequestStatus = 0;
+ uint8_t BMS_info_pcbaId = 0;
+ uint8_t BMS_info_assemblyId = 0;
+ uint8_t BMS_info_platformType = 0;
+ uint8_t BMS_info_bootUdsProtoVersion = 0;
+ uint8_t battery_beginning_of_life = 0;
+ uint8_t battery_battTempPct = 0;
+ uint8_t battery_BrickVoltageMaxNum = 0;
+ uint8_t battery_BrickVoltageMinNum = 0;
+ uint8_t battery_BrickTempMaxNum = 0;
+ uint8_t battery_BrickTempMinNum = 0;
+ uint8_t battery_BrickModelTMax = 0;
+ uint8_t battery_BrickModelTMin = 0;
+ uint8_t BMS_flowRequest = 0;
+ uint8_t BMS_uiChargeStatus = 0;
+ uint8_t BMS_contactorState = 0;
+ uint8_t BMS_state = 0;
+ uint8_t BMS_hvState = 0;
+ uint8_t BMS_notEnoughPowerForHeatPump = 0;
+ uint8_t BMS_powerLimitState = 0;
+ uint8_t BMS_inverterTQF = 0;
uint8_t PCS_dcdcPrechargeStatus = 0;
uint8_t PCS_dcdc12VSupportStatus = 0;
uint8_t PCS_dcdcHvBusDischargeStatus = 0;
uint8_t PCS_dcdcMainState = 0;
uint8_t PCS_dcdcSubState = 0;
- bool PCS_dcdcFaulted = false;
- bool PCS_dcdcOutputIsLimited = false;
- uint16_t PCS_dcdcMaxOutputCurrentAllowed = 0;
uint8_t PCS_dcdcPrechargeRtyCnt = 0;
uint8_t PCS_dcdc12VSupportRtyCnt = 0;
uint8_t PCS_dcdcDischargeRtyCnt = 0;
@@ -514,45 +631,35 @@ struct DATALAYER_INFO_TESLA {
uint8_t PCS_dcdcSupportingFixedLvTarget = 0;
uint8_t PCS_dcdcPrechargeRestartCnt = 0;
uint8_t PCS_dcdcInitialPrechargeSubState = 0;
- uint8_t PCS_partNumber[13] = {0}; //stores raw HEX values for ASCII chars
- uint16_t PCS_info_buildConfigId = 0;
- uint16_t PCS_info_hardwareId = 0;
- uint16_t PCS_info_componentId = 0;
uint8_t PCS_info_pcbaId = 0;
uint8_t PCS_info_assemblyId = 0;
- uint16_t PCS_info_usageId = 0;
- uint16_t PCS_info_subUsageId = 0;
uint8_t PCS_info_platformType = 0;
- uint32_t PCS_info_appCrc = 0;
- uint32_t PCS_info_cpu2AppCrc = 0;
- uint64_t PCS_info_bootGitHash = 0;
uint8_t PCS_info_bootUdsProtoVersion = 0;
- uint32_t PCS_info_bootCrc = 0;
- uint16_t PCS_dcdcTemp = 0;
- uint16_t PCS_ambientTemp = 0;
- uint16_t PCS_chgPhATemp = 0;
- uint16_t PCS_chgPhBTemp = 0;
- uint16_t PCS_chgPhCTemp = 0;
- uint16_t PCS_dcdcMaxLvOutputCurrent = 0;
- uint16_t PCS_dcdcCurrentLimit = 0;
- uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0;
- uint16_t PCS_dcdcUnifiedCommand = 0;
- uint16_t PCS_dcdcCLAControllerOutput = 0;
- uint16_t PCS_dcdcTankVoltage = 0;
- uint16_t PCS_dcdcTankVoltageTarget = 0;
- uint16_t PCS_dcdcClaCurrentFreq = 0;
- uint16_t PCS_dcdcTCommMeasured = 0;
- uint16_t PCS_dcdcShortTimeUs = 0;
- uint16_t PCS_dcdcHalfPeriodUs = 0;
- uint16_t PCS_dcdcIntervalMaxFrequency = 0;
- uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0;
- uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0;
- uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0;
- uint16_t PCS_dcdcIntervalMinFrequency = 0;
- uint16_t PCS_dcdcIntervalMinHvBusVolt = 0;
- uint16_t PCS_dcdcIntervalMinLvBusVolt = 0;
- uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0;
- uint32_t PCS_dcdc12vSupportLifetimekWh = 0;
+ uint8_t HVP_info_platformType = 0;
+ uint8_t HVP_info_pcbaId = 0;
+ uint8_t HVP_info_assemblyId = 0;
+ uint8_t HVP_info_bootUdsProtoVersion = 0;
+ uint8_t HVP_shuntHwMia = 0;
+ uint8_t HVP_shuntAuxCurrentStatus = 0;
+ uint8_t HVP_shuntBarTempStatus = 0;
+ uint8_t HVP_shuntAsicTempStatus = 0;
+
+ bool packCtrsClosingBlocked = false;
+ bool pyroTestInProgress = false;
+ bool battery_packCtrsOpenNowRequested = false;
+ bool battery_packCtrsOpenRequested = false;
+ bool battery_packCtrsResetRequestRequired = false;
+ bool battery_dcLinkAllowedToEnergize = false;
+ bool BMS352_mux = false; // variable to store when 0x352 mux is present
+ bool battery_full_charge_complete = false;
+ bool battery_fully_charged = false;
+ bool BMS_hvilFault = false;
+ bool BMS_diLimpRequest = false;
+ bool BMS_pcsPwmEnabled = false;
+ bool BMS_pcsNoFlowRequest = false;
+ bool BMS_noFlowRequest = false;
+ bool PCS_dcdcFaulted = false;
+ bool PCS_dcdcOutputIsLimited = false;
bool HVP_gpioPassivePyroDepl = false;
bool HVP_gpioPyroIsoEn = false;
bool HVP_gpioCpFaultIn = false;
@@ -577,150 +684,87 @@ struct DATALAYER_INFO_TESLA {
bool HVP_gpioFcContPowerEnable = false;
bool HVP_gpioHvilEnable = false;
bool HVP_gpioSecDrdy = false;
- uint16_t HVP_hvp1v5Ref = 0;
- uint16_t HVP_shuntCurrentDebug = 0;
bool HVP_packCurrentMia = false;
bool HVP_auxCurrentMia = false;
bool HVP_currentSenseMia = false;
bool HVP_shuntRefVoltageMismatch = false;
bool HVP_shuntThermistorMia = false;
- uint8_t HVP_partNumber[13] = {0}; //stores raw HEX values for ASCII chars
- uint16_t HVP_info_buildConfigId = 0;
- uint16_t HVP_info_hardwareId = 0;
- uint16_t HVP_info_componentId = 0;
- uint8_t HVP_info_pcbaId = 0;
- uint8_t HVP_info_assemblyId = 0;
- uint16_t HVP_info_usageId = 0;
- uint16_t HVP_info_subUsageId = 0;
- uint8_t HVP_info_platformType = 0;
- uint32_t HVP_info_appCrc = 0;
- uint64_t HVP_info_bootGitHash = 0;
- uint8_t HVP_info_bootUdsProtoVersion = 0;
- uint32_t HVP_info_bootCrc = 0;
- uint8_t HVP_shuntHwMia = 0;
- uint16_t HVP_dcLinkVoltage = 0;
- uint16_t HVP_packVoltage = 0;
- uint16_t HVP_fcLinkVoltage = 0;
- uint16_t HVP_packContVoltage = 0;
- uint16_t HVP_packNegativeV = 0;
- uint16_t HVP_packPositiveV = 0;
- uint16_t HVP_pyroAnalog = 0;
- uint16_t HVP_dcLinkNegativeV = 0;
- uint16_t HVP_dcLinkPositiveV = 0;
- uint16_t HVP_fcLinkNegativeV = 0;
- uint16_t HVP_fcContCoilCurrent = 0;
- uint16_t HVP_fcContVoltage = 0;
- uint16_t HVP_hvilInVoltage = 0;
- uint16_t HVP_hvilOutVoltage = 0;
- uint16_t HVP_fcLinkPositiveV = 0;
- uint16_t HVP_packContCoilCurrent = 0;
- uint16_t HVP_battery12V = 0;
- uint16_t HVP_shuntRefVoltageDbg = 0;
- uint16_t HVP_shuntAuxCurrentDbg = 0;
- uint16_t HVP_shuntBarTempDbg = 0;
- uint16_t HVP_shuntAsicTempDbg = 0;
- uint8_t HVP_shuntAuxCurrentStatus = 0;
- uint8_t HVP_shuntBarTempStatus = 0;
- uint8_t HVP_shuntAsicTempStatus = 0;
+
+ uint8_t BMS_partNumber[12] = {0}; //stores raw HEX values for ASCII chars
+ uint8_t battery_serialNumber[15] = {0}; //stores raw HEX values for ASCII chars
+ uint8_t battery_partNumber[12] = {0}; //stores raw HEX values for ASCII chars
+ uint8_t PCS_partNumber[13] = {0}; //stores raw HEX values for ASCII chars
+ uint8_t HVP_partNumber[13] = {0}; //stores raw HEX values for ASCII chars
+ char* battery_manufactureDate;
};
struct DATALAYER_INFO_NISSAN_LEAF {
- /** uint8_t */
- /** Battery info, stores raw HEX values for ASCII chars */
- uint8_t BatterySerialNumber[15] = {0};
- uint8_t BatteryPartNumber[7] = {0};
- uint8_t BMSIDcode[8] = {0};
- /** uint8_t */
- /** Enum, ZE0 = 0, AZE0 = 1, ZE1 = 2 */
- uint8_t LEAF_gen = 0;
- /** uint16_t */
+ /** Cryptographic challenge to be solved */
+ uint32_t CryptoChallenge = 0;
+ /** Solution for crypto challenge, MSBs */
+ uint32_t SolvedChallengeMSB = 0;
+ /** Solution for crypto challenge, LSBs */
+ uint32_t SolvedChallengeLSB = 0;
+
/** 77Wh per gid. LEAF specific unit */
uint16_t GIDS = 0;
- /** uint16_t */
/** Max regen power in kW */
uint16_t ChargePowerLimit = 0;
- /** uint16_t */
/** Internal resistance in percentage */
uint16_t battery_HX = 0;
- /** int16_t */
- /** Max charge power in kW */
- int16_t MaxPowerForCharger = 0;
- /** bool */
- /** Interlock status */
- bool Interlock = false;
- /** int16_t */
/** Insulation resistance, most likely kOhm */
uint16_t Insulation = 0;
- /** int16_t */
+
+ /** Max charge power in kW */
+ int16_t MaxPowerForCharger = 0;
/** Temperature sensoros 1-4 */
int16_t temperature1 = 0;
int16_t temperature2 = 0;
int16_t temperature3 = 0; // This sensor not available on 2013+ packs
int16_t temperature4 = 0;
- /** uint8_t */
+
+ /** Enum, ZE0 = 0, AZE0 = 1, ZE1 = 2 */
+ uint8_t LEAF_gen = 0;
/** battery_FAIL status */
uint8_t RelayCutRequest = 0;
- /** uint8_t */
/** battery_STATUS status */
uint8_t FailsafeStatus = 0;
- /** bool */
+
+ /** Interlock status */
+ bool Interlock = false;
/** True if fully charged */
bool Full = false;
- /** bool */
/** True if battery empty */
bool Empty = false;
- /** bool */
/** Battery pack allows closing of contacors */
bool MainRelayOn = false;
- /** bool */
/** True if heater exists */
bool HeatExist = false;
- /** bool */
/** Heater stopped */
bool HeatingStop = false;
- /** bool */
/** Heater starting */
bool HeatingStart = false;
- /** bool */
/** Heat request sent*/
bool HeaterSendRequest = false;
- /** bool */
/** User requesting SOH reset via WebUI*/
bool UserRequestSOHreset = false;
- /** bool */
/** True if the crypto challenge response from BMS is signalling a failed attempt*/
bool challengeFailed = false;
- /** uint32_t */
- /** Cryptographic challenge to be solved */
- uint32_t CryptoChallenge = 0;
- /** uint32_t */
- /** Solution for crypto challenge, MSBs */
- uint32_t SolvedChallengeMSB = 0;
- /** uint32_t */
- /** Solution for crypto challenge, LSBs */
- uint32_t SolvedChallengeLSB = 0;
+
+ /** Battery info, stores raw HEX values for ASCII chars */
+ uint8_t BatterySerialNumber[15] = {0};
+ uint8_t BatteryPartNumber[7] = {0};
+ uint8_t BMSIDcode[8] = {0};
};
struct DATALAYER_INFO_MEB {
- /** uint8_t */
- /** Service disconnect switch status */
- bool SDSW = 0;
- /** uint8_t */
- /** Pilotline status */
- bool pilotline = 0;
- /** uint8_t */
- /** Transportation mode status */
- bool transportmode = 0;
- /** uint8_t */
- /** Componentprotection mode status */
- bool componentprotection = 0;
- /** uint8_t */
- /** Shutdown status */
- bool shutdown_active = 0;
- /** uint8_t */
- /** Battery heating status */
- bool battery_heating = 0;
- /** uint8_t */
+ /** Isolation resistance in kOhm */
+ uint32_t isolation_resistance = 0;
+ int32_t BMS_voltage_intermediate_dV = 0;
+ int32_t BMS_voltage_dV = 0;
+
+ uint16_t battery_temperature_dC = 0;
+
/** All realtime_ warnings have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3 */
uint8_t rt_overcurrent = 0;
uint8_t rt_CAN_fault = 0;
@@ -737,69 +781,52 @@ struct DATALAYER_INFO_MEB {
uint8_t rt_cell_undervol = 0;
uint8_t rt_cell_imbalance = 0;
uint8_t rt_battery_unathorized = 0;
- /** uint8_t */
/** HVIL status, 0 = Init, 1 = Closed, 2 = Open!, 3 = Fault */
uint8_t HVIL = 0;
- /** uint8_t */
/** 0 = HV inactive, 1 = HV active, 2 = Balancing, 3 = Extern charging, 4 = AC charging, 5 = Battery error, 6 = DC charging, 7 = init */
uint8_t BMS_mode = 0;
- /** uint8_t */
/** 1 = Battery display, 4 = Battery display OK, 4 = Display battery charging, 6 = Display battery check, 7 = Fault */
uint8_t battery_diagnostic = 0;
- /** uint8_t */
/** 0 = init, 1 = no open HV line detected, 2 = open HV line , 3 = fault */
uint8_t status_HV_line = 0;
- /** uint8_t */
/** 0 = OK, 1 = Not OK, 0x06 = init, 0x07 = fault */
- bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure)
- bool BMS_fault_emergency_shutdown_crash =
- false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link
-
- bool BMS_error_shutdown_request =
- false; // Fault: Fault condition, requires battery contactors to be opened internal battery error; Advance notification of an impending opening of the battery contactors by the BMS
- bool BMS_error_shutdown =
- false; // Fault: Fault condition, requires battery contactors to be opened Internal battery error, battery contactors opened without notice by the BMS
uint8_t warning_support = 0;
- /** uint32_t */
- /** Isolation resistance in kOhm */
- uint32_t isolation_resistance = 0;
- /** uint8_t */
/** 0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error */
uint8_t BMS_status_voltage_free = 0;
- /** uint8_t */
/** 0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init */
uint8_t BMS_error_status = 0;
- /** uint8_t */
/** 0 init, 1 closed, 2 open, 3 fault */
uint8_t BMS_Kl30c_Status = 0;
- /** bool */
- /** true if BMS requests error/warning light */
- bool BMS_OBD_MIL = 0;
- bool BMS_error_lamp_req = 0;
- bool BMS_warning_lamp_req = 0;
- int32_t BMS_voltage_intermediate_dV = 0;
- int32_t BMS_voltage_dV = 0;
uint8_t balancing_active = 0;
+ uint8_t BMS_welded_contactors_status = 0;
+
bool balancing_request = 0;
bool charging_active = 0;
+ bool BMS_OBD_MIL = 0; /** true if BMS requests error/warning light */
+ bool BMS_error_lamp_req = 0;
+ bool BMS_warning_lamp_req = 0;
+ bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure)
+ bool BMS_fault_emergency_shutdown_crash =
+ false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link
+ bool BMS_error_shutdown_request =
+ false; // Fault: Fault condition, requires battery contactors to be opened internal battery error; Advance notification of an impending opening of the battery contactors by the BMS
+ bool BMS_error_shutdown =
+ false; // Fault: Fault condition, requires battery contactors to be opened Internal battery error, battery contactors opened without notice by the BMS
+ bool SDSW = 0; /** Service disconnect switch status */
+ bool pilotline = 0; /** Pilotline status */
+ bool transportmode = 0; /** Transportation mode status */
+ bool componentprotection = 0; /** Componentprotection mode status */
+ bool shutdown_active = 0; /** Shutdown status */
+ bool battery_heating = 0; /** Battery heating status */
+
float temp_points[18] = {0};
- uint16_t celltemperature_dC[56] = {0};
- uint16_t battery_temperature_dC = 0;
- uint8_t BMS_welded_contactors_status = 0;
+ int16_t celltemperature_dC[56] = {0};
};
struct DATALAYER_INFO_VOLVO_POLESTAR {
- uint16_t soc_bms = 0;
- uint16_t soc_calc = 0;
- uint16_t soc_rescaled = 0;
- uint16_t soh_bms = 0;
uint16_t BECMsupplyVoltage = 12000;
-
- uint16_t BECMBatteryVoltage = 0;
- int16_t BECMBatteryCurrent = 0;
uint16_t BECMUDynMaxLim = 0;
uint16_t BECMUDynMinLim = 0;
-
uint16_t HvBattPwrLimDcha1 = 0;
uint16_t HvBattPwrLimDchaSoft = 0;
uint16_t HvBattPwrLimDchaSlowAgi = 0;
@@ -848,8 +875,34 @@ struct DATALAYER_INFO_VOLVO_HYBRID {
bool UserRequestBECMecuReset = false;
};
+struct DATALAYER_INFO_GEELY_SEA {
+ uint16_t soc_bms = 0;
+ uint16_t soh_bms = 0;
+ uint16_t BECMsupplyVoltage = 0;
+ uint16_t BECMBatteryVoltage = 0;
+ uint16_t BatteryCurrent = 0;
+ uint16_t CellTempHighest = 0;
+ uint16_t CellTempAverage = 0;
+ uint16_t CellTempLowest = 0;
+ uint8_t Interlock = 255;
+ uint16_t CellVoltHighest = 0;
+ uint16_t CellVoltLowest = 0;
+ uint8_t DTCcount = 0;
+ uint8_t CrashStatus = 0;
+ /** User requesting DTC reset via WebUI*/
+ bool UserRequestDTCreset = false;
+ /** User requesting DTC readout via WebUI*/
+ bool UserRequestDTCreadout = false;
+ /** User requesting BECM reset via WebUI*/
+ bool UserRequestBECMecuReset = false;
+ /** User requesting reset of crash status via WebUI*/
+ bool UserRequestCrashReset = false;
+};
+
struct DATALAYER_INFO_ZOE {
- /** uint8_t */
+ uint16_t mileage_km = 0;
+ uint16_t alltime_kWh = 0;
+
uint8_t CUV = 0;
uint8_t HVBIR = 0;
uint8_t HVBUV = 0;
@@ -858,15 +911,10 @@ struct DATALAYER_INFO_ZOE {
uint8_t HVBOT = 0;
uint8_t HVBOV = 0;
uint8_t COV = 0;
- uint16_t mileage_km = 0;
- uint16_t alltime_kWh = 0;
};
struct DATALAYER_INFO_ZOE_PH2 {
- /** User requesting NVROL reset via WebUI*/
- bool UserRequestNVROLReset = false;
/** uint16_t */
- /** Values WIP*/
uint16_t battery_soc = 0;
uint16_t battery_usable_soc = 0;
uint16_t battery_soh = 0;
@@ -907,25 +955,31 @@ struct DATALAYER_INFO_ZOE_PH2 {
uint16_t battery_pack_time = 0;
uint16_t battery_soc_min = 0;
uint16_t battery_soc_max = 0;
+ /** User requesting NVROL reset via WebUI*/
+ bool UserRequestNVROLReset = false;
};
class DataLayerExtended {
public:
DATALAYER_INFO_BOLTAMPERA boltampera;
DATALAYER_INFO_BMWPHEV bmwphev;
+ DATALAYER_INFO_BMWIX bmwix;
DATALAYER_INFO_BYDATTO3 bydAtto3;
DATALAYER_INFO_CELLPOWER cellpower;
DATALAYER_INFO_CHADEMO chademo;
DATALAYER_INFO_CMFAEV CMFAEV;
+ DATALAYER_INFO_CMPSMART stellantisCMPsmart;
DATALAYER_INFO_ECMP stellantisECMP;
DATALAYER_INFO_GEELY_GEOMETRY_C geometryC;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64_2;
+ DATALAYER_INFO_RIVIAN rivian;
DATALAYER_INFO_TESLA tesla;
DATALAYER_INFO_NISSAN_LEAF nissanleaf;
DATALAYER_INFO_MEB meb;
DATALAYER_INFO_VOLVO_POLESTAR VolvoPolestar;
DATALAYER_INFO_VOLVO_HYBRID VolvoHybrid;
+ DATALAYER_INFO_GEELY_SEA GeelySEA;
DATALAYER_INFO_ZOE zoe;
DATALAYER_INFO_ZOE_PH2 zoePH2;
};
diff --git a/Software/src/devboard/display/display.cpp b/Software/src/devboard/display/display.cpp
new file mode 100644
index 000000000..c3984bc97
--- /dev/null
+++ b/Software/src/devboard/display/display.cpp
@@ -0,0 +1,490 @@
+// A realtime display of battery status and events, using a I2C-connected 128x64
+// OLED display based on the SSD1306 driver.
+
+#include "../../battery/BATTERIES.h"
+#include "../../datalayer/datalayer.h"
+#include "../hal/hal.h"
+#include "../utils/events.h"
+#include "../utils/logging.h"
+#include "fonts.h"
+
+#include "Arduino.h"
+
+// ESP32-S3 doesn't support the new i2c_master API yet (ESP-IDF 4.4 vs 5.x)
+// Disable display feature for ESP32-S3 until IDF upgrade
+#ifndef CONFIG_IDF_TARGET_ESP32S3
+
+#include "driver/i2c_master.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+
+#define I2C_MASTER_FREQ_HZ 1000000 // Use a ridiculously fast I2C speed (seems to work!)
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+i2c_master_bus_handle_t bus_handle;
+i2c_master_dev_handle_t dev_handle;
+bool display_initialized = false;
+unsigned long lastUpdateMillis = 0;
+static std::vector order_events;
+int num_batteries = 1;
+
+static esp_err_t i2c_write(const uint8_t* data, size_t len) {
+ return i2c_master_transmit(dev_handle, data, len, 1000 / portTICK_PERIOD_MS);
+}
+
+static void i2c_data(const uint8_t* data, size_t len) {
+ uint8_t write_buf[1025];
+ write_buf[0] = 0x40; // Control byte 0x40 for data
+ memcpy(&write_buf[1], data, len);
+ i2c_write(write_buf, len + 1);
+}
+
+static void set_ram_pointer(int x, int y) {
+ uint8_t cmd[] = {
+ 0x80, (uint8_t)(0x00 | (x & 0x0F)), // Set lower column address
+ 0x80, (uint8_t)(0x10 | ((x >> 4) & 0x0F)), // Set higher column address
+ 0x00, (uint8_t)(0xB0 | (y & 0x0F)), // Set page address
+ };
+ i2c_write(cmd, sizeof(cmd));
+}
+
+static void write_text(int x, int y, const char* str, bool invert) {
+ set_ram_pointer(x, y);
+
+ while (*str) {
+ char c = *str++;
+ if (c < ' ' || c > '~') {
+ c = '!'; // Replace unsupported characters
+ }
+
+ uint8_t data[6];
+ if (invert) {
+ for (int i = 0; i < 6; i++) {
+ data[i] = ~font6x8_basic[c - ' '][i];
+ }
+ i2c_data(data, 6);
+ } else {
+ i2c_data(font6x8_basic[c - ' '], 6);
+ }
+ }
+}
+
+/*
+static void write_big_text(int x, int y, const char* str, bool invert) {
+ for (int r = 0; r < 2; r++) {
+ set_ram_pointer(x, y + r);
+
+ char* ptr = (char*)str;
+ while (*ptr) {
+ char c = *ptr++;
+ if (c < ' ' || c > '~') {
+ c = '!'; // Replace unsupported characters
+ }
+
+ char slice[16];
+ for (int col = 0; col < 6; col++) {
+ uint8_t byte = font6x8_basic[c - ' '][col];
+
+ byte = r == 0 ? (byte & 0xf) : (byte >> 4);
+ byte = (byte | (byte << 2)) & 0x33;
+ byte = (byte | (byte << 1)) & 0x55;
+ byte *= 0x03;
+
+ if(invert) {
+ byte = ~byte;
+ }
+
+ slice[col * 2] = byte;
+ slice[col * 2 + 1] = byte;
+ }
+
+ i2c_data((uint8_t*)slice, 12);
+ }
+ }
+}
+*/
+
+static void write_tall_text(int x, int y, const char* str, bool invert) {
+ for (int r = 0; r < 2; r++) {
+ set_ram_pointer(x, y + r);
+
+ char* ptr = (char*)str;
+ while (*ptr) {
+ char c = *ptr++;
+ if (c < ' ' || c > '9') {
+ c = '!'; // Replace unsupported characters
+ }
+
+ char slice[8];
+ for (int col = 0; col < 8; col++) {
+ uint8_t byte = font8x8_basic[c - ' '][col];
+
+ byte = r == 0 ? (byte & 0xf) : (byte >> 4);
+ byte = (byte | (byte << 2)) & 0x33;
+ byte = (byte | (byte << 1)) & 0x55;
+ byte *= 0x03;
+
+ if (invert) {
+ byte = ~byte;
+ }
+
+ slice[col] = byte;
+ }
+
+ i2c_data((uint8_t*)slice, 8);
+ }
+ }
+}
+
+static void clear() {
+ uint8_t blank[128] = {
+ 0,
+ };
+ for (int i = 0; i < 8; i++) {
+ set_ram_pointer(0, i);
+ i2c_data(blank, sizeof(blank));
+ }
+}
+
+void init_display() {
+ auto display_sda = esp32hal->DISPLAY_SDA_PIN();
+ auto display_scl = esp32hal->DISPLAY_SCL_PIN();
+
+ if (display_sda == GPIO_NUM_NC || display_scl == GPIO_NUM_NC) {
+ return;
+ }
+
+ if (!esp32hal->alloc_pins("I2C Display", display_sda, display_scl)) {
+ logging.printf("Failed to allocate pins for I2C Display\n");
+ return;
+ }
+
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = -1, // autoselect
+ .sda_io_num = display_sda,
+ .scl_io_num = display_scl,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ .glitch_ignore_cnt = 7,
+ .intr_priority = 0,
+ .trans_queue_depth = 0,
+ .flags =
+ {
+ .enable_internal_pullup = true,
+ .allow_pd = false,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
+
+ i2c_device_config_t dev_config = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = 0x3c,
+ .scl_speed_hz = I2C_MASTER_FREQ_HZ,
+ .scl_wait_us = 0,
+ .flags = {},
+ };
+ ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle));
+
+ static const uint8_t init[] = {
+ 0xae, // display off
+ 0xd5, // set display clock divider
+ 0x80, // /1, middle freq
+ 0xa8, // set multiplex
+ 64 - 1, // height - 1
+
+ 0xd3, // set display offset
+ 0x00, // no offset
+ 0x40, // set start line at 0
+
+ 0x8d, // charge pump
+ 0x14, // enable
+
+ 0x20, // memory mode
+ 0x00, // horizontal addressing
+ 0xa1, // seg remap = 1 (mirror x)
+ 0xc8, // com scan = dec
+
+ 0x81, // contrast
+ 0x0f, // 0x0f/0xff
+
+ 0xaf, // display on
+ };
+ uint8_t buf[sizeof(init) * 2];
+ for (size_t i = 0; i < sizeof(init); i++) {
+ buf[i * 2] = 0x80; // Control byte for command
+ buf[i * 2 + 1] = init[i];
+ }
+ if (i2c_write(buf, sizeof(buf)) != ESP_OK) {
+ logging.printf("Failed to initialize I2C Display\n");
+ return;
+ }
+
+ clear();
+ display_initialized = true;
+
+ // Count configured batteries
+ if (battery2)
+ num_batteries++;
+ if (battery3)
+ num_batteries++;
+}
+
+static void printn(char* buf, int value, int digits) {
+ bool neg = value < 0;
+ if (neg)
+ value = -value;
+ for (int i = digits - 1; i >= 0; i--) {
+ buf[i] = '0' + (value % 10);
+ if (value < 10) {
+ if (neg && i > 0)
+ buf[i - 1] = '-';
+ break;
+ }
+ value = value / 10;
+ }
+}
+
+inline void print2(char* buf, int value) {
+ printn(buf, value, 2);
+}
+
+inline void print3(char* buf, int value) {
+ printn(buf, value, 3);
+}
+
+inline void print4(char* buf, int value) {
+ printn(buf, value, 4);
+}
+
+static void print3d1(char* buf, int value) {
+ print3(buf, value / 10);
+
+ buf[3] = '.';
+ buf[4] = '0' + (abs(value) % 10);
+}
+
+static void print_interval(char* buf, uint64_t elapsed) {
+ if (elapsed < 100000) {
+ print2(buf, int(elapsed / 1000));
+ buf[2] = 's';
+ } else if (elapsed < 6000000) {
+ print2(buf, int(elapsed / 60000));
+ buf[2] = 'm';
+ } else if (elapsed < 360000000) {
+ print2(buf, int(elapsed / 3600000));
+ buf[2] = 'h';
+ } else {
+ print2(buf, MIN(99, int(elapsed / 86400000)));
+ buf[2] = 'd';
+ }
+}
+
+static void cpy(char* dst, const char* src) {
+ while (*src && *dst) {
+ *dst++ = *src++;
+ }
+}
+
+static void print_battery_status(int row, DATALAYER_BATTERY_STATUS_TYPE& status, int num, int page) {
+ char buf[22];
+ memset(buf, ' ', sizeof(buf));
+
+ print3(buf, status.reported_soc / 100);
+ buf[3] = '%';
+ buf[4] = '\0';
+ write_tall_text(0, row, buf, false);
+
+ if (num_batteries > 1) {
+ buf[6] = 'B';
+ buf[7] = 'a';
+ buf[8] = 't';
+ buf[9] = '0' + num;
+ }
+ printn(buf + 14, status.active_power_W, 6);
+ buf[20] = 'W';
+ buf[21] = '\0';
+ write_text(6 * 6, row++, buf + 6, false);
+
+ memset(buf, ' ', sizeof(buf));
+
+ if (page == 0) {
+ // First page, voltage + remaining capacity
+
+ print3d1(buf + 6, status.voltage_dV);
+ buf[11] = 'V';
+
+ print3d1(buf + 13, status.remaining_capacity_Wh / 100);
+ buf[18] = 'k';
+ buf[19] = 'W';
+ buf[20] = 'h';
+ buf[21] = '\0';
+ } else if (page == 1) {
+ // Second page, cell delta + current
+
+ print4(buf + 6, status.cell_max_voltage_mV - status.cell_min_voltage_mV);
+ buf[10] = 'm';
+ buf[11] = 'V';
+ buf[12] = ' ';
+ print3d1(buf + 15, status.current_dA);
+ buf[20] = 'A';
+ buf[21] = '\0';
+ } else if (page == 2) {
+ // Third page, cell voltage range
+
+ print4(buf + 6, status.cell_min_voltage_mV);
+ buf[10] = 'm';
+ buf[11] = 'V';
+ buf[13] = '/';
+ print4(buf + 15, status.cell_max_voltage_mV);
+ buf[19] = 'm';
+ buf[20] = 'V';
+ buf[21] = '\0';
+ } else if (page == 3) {
+ // Foutrh page, temperature range
+
+ print3d1(buf + 6, status.temperature_min_dC);
+ buf[11] = 'c';
+ buf[13] = '/';
+ print3d1(buf + 15, status.temperature_max_dC);
+ buf[20] = 'c';
+ buf[21] = '\0';
+ }
+
+ write_text(6 * 6, row, buf + 6, false);
+}
+
+static const int PRE_SCROLL = 4; // How long to wait before scrolling
+static const int POST_SCROLL = 4; // How long to wait after scrolling
+int scroll_x = 0;
+
+static void print_events(int row, int count) {
+ char buf[22];
+
+ // Collect all events
+ order_events.clear();
+ for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
+ auto event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
+ if (event_pointer->occurences > 0 && event_pointer->level > EVENT_LEVEL_INFO) {
+ order_events.push_back({static_cast(i), event_pointer});
+ }
+ }
+ // Sort events by timestamp
+ std::sort(order_events.begin(), order_events.end(), compareEventsByTimestampDesc);
+ uint64_t current_timestamp = millis64();
+ int longest_event_str = 0;
+
+ int i;
+ for (i = 0; i < order_events.size() && i < count; i++) {
+ memset(buf, ' ', sizeof(buf));
+
+ auto ev = order_events[i];
+ uint64_t elapsed = MAX((int64_t)(current_timestamp - ev.event_pointer->timestamp), 0);
+ print_interval(buf, elapsed);
+
+ const char* event_str = get_event_enum_string(ev.event_handle);
+ int event_str_len = strlen(event_str);
+ longest_event_str = MAX(longest_event_str, event_str_len);
+
+ buf[21] = '\0';
+ cpy(buf + 4, event_str + MIN(MAX(scroll_x - PRE_SCROLL, 0), MAX(event_str_len - 17, 0)));
+
+ // Error-level events are highlighted
+ write_text(0, i + row, buf, get_event_level() == EVENT_LEVEL_ERROR && ev.event_pointer->level == EVENT_LEVEL_ERROR);
+ }
+
+ // Clear remaining lines
+ memset(buf, ' ', sizeof(buf));
+ buf[21] = '\0';
+ for (; i < count; i++) {
+ write_text(0, i + row, buf, false);
+ }
+
+ // Update scrolling (if event names are too long)
+ if (longest_event_str > 17) {
+ scroll_x++;
+ if (scroll_x > longest_event_str - 17 + PRE_SCROLL + POST_SCROLL) {
+ scroll_x = 0;
+ }
+ } else {
+ scroll_x = 0;
+ }
+}
+
+static void print_wifi_status(int row) {
+ wl_status_t status = WiFi.status();
+ char buf[22];
+ memset(buf, ' ', sizeof(buf));
+ buf[21] = '\0';
+
+ if (status == WL_CONNECTED) {
+ cpy(buf, WiFi.localIP().toString().c_str());
+ print3(buf + 16, WiFi.RSSI());
+ buf[19] = 'd';
+ buf[20] = 'B';
+ }
+
+ write_text(0, row, buf, false);
+}
+
+void update_display() {
+ if (!display_initialized) {
+ return;
+ }
+
+ // We update the display every 500ms
+ auto currentMillis = millis();
+ if (currentMillis - lastUpdateMillis < 500) {
+ return;
+ }
+
+ // We cycle through several pages of battery data
+ const int NUM_PAGES = 4;
+ const int PAGE_TIME = 3;
+ static int phase = 0;
+
+ // Calculate total phases: NUM_PAGES per battery, PAGE_TIME seconds each
+ int total_phases = NUM_PAGES * num_batteries * PAGE_TIME * 2; // *2 for 500ms updates
+
+ int current_phase = phase / (PAGE_TIME * 2);
+ int battery_index = current_phase % num_batteries;
+ int page = (current_phase / num_batteries) % NUM_PAGES;
+
+ // Print the battery status for current battery
+ if (battery_index == 0) {
+ print_battery_status(0, datalayer.battery.status, 1, page);
+ } else if (battery_index == 1) {
+ print_battery_status(0, datalayer.battery2.status, 2, page);
+ } else {
+ print_battery_status(0, datalayer.battery3.status, 3, page);
+ }
+
+ write_text(0, 2, "---------------------", false);
+
+ // Print the events below
+ print_events(3, 3);
+
+ write_text(0, 6, "---------------------", false);
+
+ // Then IP/RSSI at the bottom
+ print_wifi_status(7);
+
+ phase++;
+ if (phase >= total_phases) {
+ phase = 0;
+ }
+ lastUpdateMillis = currentMillis;
+}
+
+#else // CONFIG_IDF_TARGET_ESP32S3
+
+// Stub functions for ESP32-S3 (display not supported on ESP-IDF 4.4)
+void init_display() {
+ // Display feature not available on ESP32-S3 due to ESP-IDF version
+}
+
+void update_display() {
+ // Display feature not available on ESP32-S3 due to ESP-IDF version
+}
+
+#endif // CONFIG_IDF_TARGET_ESP32S3
diff --git a/Software/src/devboard/display/display.h b/Software/src/devboard/display/display.h
new file mode 100644
index 000000000..375b8a33b
--- /dev/null
+++ b/Software/src/devboard/display/display.h
@@ -0,0 +1,7 @@
+#ifndef _DISPLAY_H_
+#define _DISPLAY_H_
+
+void init_display();
+void update_display();
+
+#endif // _DISPLAY_H_
diff --git a/Software/src/devboard/display/fonts.h b/Software/src/devboard/display/fonts.h
new file mode 100644
index 000000000..91c775614
--- /dev/null
+++ b/Software/src/devboard/display/fonts.h
@@ -0,0 +1,864 @@
+#include
+
+const uint8_t font8x8_basic[][8] = {
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 ( )
+ {0x00, 0x00, 0x06, 0x5F, 0x5F, 0x06, 0x00, 0x00}, // U+0021 (!)
+ {0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00}, // U+0022 (")
+ {0x14, 0x7F, 0x7F, 0x14, 0x7F, 0x7F, 0x14, 0x00}, // U+0023 (#)
+ {0x24, 0x2E, 0x6B, 0x6B, 0x3A, 0x12, 0x00, 0x00}, // U+0024 ($)
+ {0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, 0x00}, // U+0025 (%)
+ {0x30, 0x7A, 0x4F, 0x5D, 0x37, 0x7A, 0x48, 0x00}, // U+0026 (&)
+ {0x04, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
+ {0x00, 0x1C, 0x3E, 0x63, 0x41, 0x00, 0x00, 0x00}, // U+0028 (()
+ {0x00, 0x41, 0x63, 0x3E, 0x1C, 0x00, 0x00, 0x00}, // U+0029 ())
+ {0x08, 0x2A, 0x3E, 0x1C, 0x1C, 0x3E, 0x2A, 0x08}, // U+002A (*)
+ {0x08, 0x08, 0x3E, 0x3E, 0x08, 0x08, 0x00, 0x00}, // U+002B (+)
+ {0x00, 0x80, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x00}, // U+002C (,)
+ {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00}, // U+002D (-)
+ {0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00}, // U+002E (.)
+ {0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
+ {0x3E, 0x7F, 0x71, 0x59, 0x4D, 0x7F, 0x3E, 0x00}, // U+0030 (0)
+ {0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00}, // U+0031 (1)
+ {0x62, 0x73, 0x59, 0x49, 0x6F, 0x66, 0x00, 0x00}, // U+0032 (2)
+ {0x22, 0x63, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00}, // U+0033 (3)
+ {0x18, 0x1C, 0x16, 0x53, 0x7F, 0x7F, 0x50, 0x00}, // U+0034 (4)
+ {0x27, 0x67, 0x45, 0x45, 0x7D, 0x39, 0x00, 0x00}, // U+0035 (5)
+ {0x3C, 0x7E, 0x4B, 0x49, 0x79, 0x30, 0x00, 0x00}, // U+0036 (6)
+ {0x03, 0x03, 0x71, 0x79, 0x0F, 0x07, 0x00, 0x00}, // U+0037 (7)
+ {0x36, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00}, // U+0038 (8)
+ {0x06, 0x4F, 0x49, 0x69, 0x3F, 0x1E, 0x00, 0x00}, // U+0039 (9)
+ /*
+ { 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003A (:)
+ { 0x00, 0x80, 0xE6, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003B (;)
+ { 0x08, 0x1C, 0x36, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+003C (<)
+ { 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00 }, // U+003D (=)
+ { 0x00, 0x41, 0x63, 0x36, 0x1C, 0x08, 0x00, 0x00 }, // U+003E (>)
+ { 0x02, 0x03, 0x51, 0x59, 0x0F, 0x06, 0x00, 0x00 }, // U+003F (?)
+ { 0x3E, 0x7F, 0x41, 0x5D, 0x5D, 0x1F, 0x1E, 0x00 }, // U+0040 (@)
+ { 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0x7C, 0x00, 0x00 }, // U+0041 (A)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00 }, // U+0042 (B)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x41, 0x63, 0x22, 0x00 }, // U+0043 (C)
+ { 0x41, 0x7F, 0x7F, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+0044 (D)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x5D, 0x41, 0x63, 0x00 }, // U+0045 (E)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x1D, 0x01, 0x03, 0x00 }, // U+0046 (F)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00 }, // U+0047 (G)
+ { 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00 }, // U+0048 (H)
+ { 0x00, 0x41, 0x7F, 0x7F, 0x41, 0x00, 0x00, 0x00 }, // U+0049 (I)
+ { 0x30, 0x70, 0x40, 0x41, 0x7F, 0x3F, 0x01, 0x00 }, // U+004A (J)
+ { 0x41, 0x7F, 0x7F, 0x08, 0x1C, 0x77, 0x63, 0x00 }, // U+004B (K)
+ { 0x41, 0x7F, 0x7F, 0x41, 0x40, 0x60, 0x70, 0x00 }, // U+004C (L)
+ { 0x7F, 0x7F, 0x0E, 0x1C, 0x0E, 0x7F, 0x7F, 0x00 }, // U+004D (M)
+ { 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00 }, // U+004E (N)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+004F (O)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x09, 0x0F, 0x06, 0x00 }, // U+0050 (P)
+ { 0x1E, 0x3F, 0x21, 0x71, 0x7F, 0x5E, 0x00, 0x00 }, // U+0051 (Q)
+ { 0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00 }, // U+0052 (R)
+ { 0x26, 0x6F, 0x4D, 0x59, 0x73, 0x32, 0x00, 0x00 }, // U+0053 (S)
+ { 0x03, 0x41, 0x7F, 0x7F, 0x41, 0x03, 0x00, 0x00 }, // U+0054 (T)
+ { 0x7F, 0x7F, 0x40, 0x40, 0x7F, 0x7F, 0x00, 0x00 }, // U+0055 (U)
+ { 0x1F, 0x3F, 0x60, 0x60, 0x3F, 0x1F, 0x00, 0x00 }, // U+0056 (V)
+ { 0x7F, 0x7F, 0x30, 0x18, 0x30, 0x7F, 0x7F, 0x00 }, // U+0057 (W)
+ { 0x43, 0x67, 0x3C, 0x18, 0x3C, 0x67, 0x43, 0x00 }, // U+0058 (X)
+ { 0x07, 0x4F, 0x78, 0x78, 0x4F, 0x07, 0x00, 0x00 }, // U+0059 (Y)
+ { 0x47, 0x63, 0x71, 0x59, 0x4D, 0x67, 0x73, 0x00 }, // U+005A (Z)
+ { 0x00, 0x7F, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00 }, // U+005B ([)
+ { 0x01, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00 }, // U+005C (\)
+ { 0x00, 0x41, 0x41, 0x7F, 0x7F, 0x00, 0x00, 0x00 }, // U+005D (])
+ { 0x08, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x08, 0x00 }, // U+005E (^)
+ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, // U+005F (_)
+ { 0x00, 0x00, 0x03, 0x07, 0x04, 0x00, 0x00, 0x00 }, // U+0060 (`)
+ { 0x20, 0x74, 0x54, 0x54, 0x3C, 0x78, 0x40, 0x00 }, // U+0061 (a)
+ { 0x41, 0x7F, 0x3F, 0x48, 0x48, 0x78, 0x30, 0x00 }, // U+0062 (b)
+ { 0x38, 0x7C, 0x44, 0x44, 0x6C, 0x28, 0x00, 0x00 }, // U+0063 (c)
+ { 0x30, 0x78, 0x48, 0x49, 0x3F, 0x7F, 0x40, 0x00 }, // U+0064 (d)
+ { 0x38, 0x7C, 0x54, 0x54, 0x5C, 0x18, 0x00, 0x00 }, // U+0065 (e)
+ { 0x48, 0x7E, 0x7F, 0x49, 0x03, 0x02, 0x00, 0x00 }, // U+0066 (f)
+ { 0x98, 0xBC, 0xA4, 0xA4, 0xF8, 0x7C, 0x04, 0x00 }, // U+0067 (g)
+ { 0x41, 0x7F, 0x7F, 0x08, 0x04, 0x7C, 0x78, 0x00 }, // U+0068 (h)
+ { 0x00, 0x44, 0x7D, 0x7D, 0x40, 0x00, 0x00, 0x00 }, // U+0069 (i)
+ { 0x60, 0xE0, 0x80, 0x80, 0xFD, 0x7D, 0x00, 0x00 }, // U+006A (j)
+ { 0x41, 0x7F, 0x7F, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+006B (k)
+ { 0x00, 0x41, 0x7F, 0x7F, 0x40, 0x00, 0x00, 0x00 }, // U+006C (l)
+ { 0x7C, 0x7C, 0x18, 0x38, 0x1C, 0x7C, 0x78, 0x00 }, // U+006D (m)
+ { 0x7C, 0x7C, 0x04, 0x04, 0x7C, 0x78, 0x00, 0x00 }, // U+006E (n)
+ { 0x38, 0x7C, 0x44, 0x44, 0x7C, 0x38, 0x00, 0x00 }, // U+006F (o)
+ { 0x84, 0xFC, 0xF8, 0xA4, 0x24, 0x3C, 0x18, 0x00 }, // U+0070 (p)
+ { 0x18, 0x3C, 0x24, 0xA4, 0xF8, 0xFC, 0x84, 0x00 }, // U+0071 (q)
+ { 0x44, 0x7C, 0x78, 0x4C, 0x04, 0x1C, 0x18, 0x00 }, // U+0072 (r)
+ { 0x48, 0x5C, 0x54, 0x54, 0x74, 0x24, 0x00, 0x00 }, // U+0073 (s)
+ { 0x00, 0x04, 0x3E, 0x7F, 0x44, 0x24, 0x00, 0x00 }, // U+0074 (t)
+ { 0x3C, 0x7C, 0x40, 0x40, 0x3C, 0x7C, 0x40, 0x00 }, // U+0075 (u)
+ { 0x1C, 0x3C, 0x60, 0x60, 0x3C, 0x1C, 0x00, 0x00 }, // U+0076 (v)
+ { 0x3C, 0x7C, 0x70, 0x38, 0x70, 0x7C, 0x3C, 0x00 }, // U+0077 (w)
+ { 0x44, 0x6C, 0x38, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+0078 (x)
+ { 0x9C, 0xBC, 0xA0, 0xA0, 0xFC, 0x7C, 0x00, 0x00 }, // U+0079 (y)
+ { 0x4C, 0x64, 0x74, 0x5C, 0x4C, 0x64, 0x00, 0x00 }, // U+007A (z)
+ { 0x08, 0x08, 0x3E, 0x77, 0x41, 0x41, 0x00, 0x00 }, // U+007B ({)
+ { 0x00, 0x00, 0x00, 0x77, 0x77, 0x00, 0x00, 0x00 }, // U+007C (|)
+ { 0x41, 0x41, 0x77, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+007D (})
+ { 0x02, 0x03, 0x01, 0x03, 0x02, 0x03, 0x01, 0x00 }, // U+007E (~)
+ */
+};
+
+const uint8_t font6x8_basic[][6] = {
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ }, //
+ {
+ 0x00,
+ 0x00,
+ 0x06,
+ 0x5F,
+ 0x06,
+ 0x00,
+ }, // !
+ {
+ 0x00,
+ 0x07,
+ 0x03,
+ 0x00,
+ 0x07,
+ 0x03,
+ }, // "
+ {
+ 0x00,
+ 0x24,
+ 0x7E,
+ 0x24,
+ 0x7E,
+ 0x24,
+ }, // #
+ {
+ 0x00,
+ 0x24,
+ 0x2B,
+ 0x6A,
+ 0x12,
+ 0x00,
+ }, // $
+ {
+ 0x00,
+ 0x23,
+ 0x13,
+ 0x08,
+ 0x64,
+ 0x62,
+ }, // %
+ {
+ 0x00,
+ 0x36,
+ 0x49,
+ 0x56,
+ 0x20,
+ 0x50,
+ }, // &
+ {
+ 0x00,
+ 0x00,
+ 0x07,
+ 0x03,
+ 0x00,
+ 0x00,
+ }, // '
+ {
+ 0x00,
+ 0x00,
+ 0x3E,
+ 0x41,
+ 0x00,
+ 0x00,
+ }, // (
+ {
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x3E,
+ 0x00,
+ 0x00,
+ }, // )
+ {
+ 0x00,
+ 0x08,
+ 0x3E,
+ 0x1C,
+ 0x3E,
+ 0x08,
+ }, // *
+ {
+ 0x00,
+ 0x08,
+ 0x08,
+ 0x3E,
+ 0x08,
+ 0x08,
+ }, // +
+ {
+ 0x00,
+ 0x00,
+ 0xE0,
+ 0x60,
+ 0x00,
+ 0x00,
+ }, // ,
+ {
+ 0x00,
+ 0x08,
+ 0x08,
+ 0x08,
+ 0x08,
+ 0x08,
+ }, // -
+ {
+ 0x00,
+ 0x00,
+ 0x60,
+ 0x60,
+ 0x00,
+ 0x00,
+ }, // .
+ {
+ 0x00,
+ 0x20,
+ 0x10,
+ 0x08,
+ 0x04,
+ 0x02,
+ }, // /
+ {
+ 0x00,
+ 0x3E,
+ 0x51,
+ 0x49,
+ 0x45,
+ 0x3E,
+ }, // 0
+ {
+ 0x00,
+ 0x00,
+ 0x42,
+ 0x7F,
+ 0x40,
+ 0x00,
+ }, // 1
+ {
+ 0x00,
+ 0x62,
+ 0x51,
+ 0x49,
+ 0x49,
+ 0x46,
+ }, // 2
+ {
+ 0x00,
+ 0x22,
+ 0x49,
+ 0x49,
+ 0x49,
+ 0x36,
+ }, // 3
+ {
+ 0x00,
+ 0x18,
+ 0x14,
+ 0x12,
+ 0x7F,
+ 0x10,
+ }, // 4
+ {
+ 0x00,
+ 0x2F,
+ 0x49,
+ 0x49,
+ 0x49,
+ 0x31,
+ }, // 5
+ {
+ 0x00,
+ 0x3C,
+ 0x4A,
+ 0x49,
+ 0x49,
+ 0x30,
+ }, // 6
+ {
+ 0x00,
+ 0x01,
+ 0x71,
+ 0x09,
+ 0x05,
+ 0x03,
+ }, // 7
+ {
+ 0x00,
+ 0x36,
+ 0x49,
+ 0x49,
+ 0x49,
+ 0x36,
+ }, // 8
+ {
+ 0x00,
+ 0x06,
+ 0x49,
+ 0x49,
+ 0x29,
+ 0x1E,
+ }, // 9
+ {
+ 0x00,
+ 0x00,
+ 0x6C,
+ 0x6C,
+ 0x00,
+ 0x00,
+ }, // :
+ {
+ 0x00,
+ 0x00,
+ 0xEC,
+ 0x6C,
+ 0x00,
+ 0x00,
+ }, // ;
+ {
+ 0x00,
+ 0x08,
+ 0x14,
+ 0x22,
+ 0x41,
+ 0x00,
+ }, // <
+ {
+ 0x00,
+ 0x24,
+ 0x24,
+ 0x24,
+ 0x24,
+ 0x24,
+ }, // =
+ {
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x22,
+ 0x14,
+ 0x08,
+ }, // >
+ {
+ 0x00,
+ 0x02,
+ 0x01,
+ 0x59,
+ 0x09,
+ 0x06,
+ }, // ?
+ {
+ 0x00,
+ 0x3E,
+ 0x41,
+ 0x5D,
+ 0x55,
+ 0x1E,
+ }, // @
+ {
+ 0x00,
+ 0x7E,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x7E,
+ }, // A
+ {
+ 0x00,
+ 0x7F,
+ 0x49,
+ 0x49,
+ 0x49,
+ 0x36,
+ }, // B
+ {
+ 0x00,
+ 0x3E,
+ 0x41,
+ 0x41,
+ 0x41,
+ 0x22,
+ }, // C
+ {
+ 0x00,
+ 0x7F,
+ 0x41,
+ 0x41,
+ 0x41,
+ 0x3E,
+ }, // D
+ {
+ 0x00,
+ 0x7F,
+ 0x49,
+ 0x49,
+ 0x49,
+ 0x41,
+ }, // E
+ {
+ 0x00,
+ 0x7F,
+ 0x09,
+ 0x09,
+ 0x09,
+ 0x01,
+ }, // F
+ {
+ 0x00,
+ 0x3E,
+ 0x41,
+ 0x49,
+ 0x49,
+ 0x7A,
+ }, // G
+ {
+ 0x00,
+ 0x7F,
+ 0x08,
+ 0x08,
+ 0x08,
+ 0x7F,
+ }, // H
+ {
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x7F,
+ 0x41,
+ 0x00,
+ }, // I
+ {
+ 0x00,
+ 0x30,
+ 0x40,
+ 0x40,
+ 0x40,
+ 0x3F,
+ }, // J
+ {
+ 0x00,
+ 0x7F,
+ 0x08,
+ 0x14,
+ 0x22,
+ 0x41,
+ }, // K
+ {
+ 0x00,
+ 0x7F,
+ 0x40,
+ 0x40,
+ 0x40,
+ 0x40,
+ }, // L
+ {
+ 0x00,
+ 0x7F,
+ 0x02,
+ 0x04,
+ 0x02,
+ 0x7F,
+ }, // M
+ {
+ 0x00,
+ 0x7F,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x7F,
+ }, // N
+ {
+ 0x00,
+ 0x3E,
+ 0x41,
+ 0x41,
+ 0x41,
+ 0x3E,
+ }, // O
+ {
+ 0x00,
+ 0x7F,
+ 0x09,
+ 0x09,
+ 0x09,
+ 0x06,
+ }, // P
+ {
+ 0x00,
+ 0x3E,
+ 0x41,
+ 0x51,
+ 0x21,
+ 0x5E,
+ }, // Q
+ {
+ 0x00,
+ 0x7F,
+ 0x09,
+ 0x09,
+ 0x19,
+ 0x66,
+ }, // R
+ {
+ 0x00,
+ 0x26,
+ 0x49,
+ 0x49,
+ 0x49,
+ 0x32,
+ }, // S
+ {
+ 0x00,
+ 0x01,
+ 0x01,
+ 0x7F,
+ 0x01,
+ 0x01,
+ }, // T
+ {
+ 0x00,
+ 0x3F,
+ 0x40,
+ 0x40,
+ 0x40,
+ 0x3F,
+ }, // U
+ {
+ 0x00,
+ 0x1F,
+ 0x20,
+ 0x40,
+ 0x20,
+ 0x1F,
+ }, // V
+ {
+ 0x00,
+ 0x3F,
+ 0x40,
+ 0x3C,
+ 0x40,
+ 0x3F,
+ }, // W
+ {
+ 0x00,
+ 0x63,
+ 0x14,
+ 0x08,
+ 0x14,
+ 0x63,
+ }, // X
+ {
+ 0x00,
+ 0x07,
+ 0x08,
+ 0x70,
+ 0x08,
+ 0x07,
+ }, // Y
+ {
+ 0x00,
+ 0x71,
+ 0x49,
+ 0x45,
+ 0x43,
+ 0x00,
+ }, // Z
+ {
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x41,
+ 0x41,
+ 0x00,
+ }, // [
+ {
+ 0x00,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ }, //
+ {
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x41,
+ 0x7F,
+ 0x00,
+ }, // ]
+ {
+ 0x00,
+ 0b01100000,
+ 0b01011000,
+ 0b01000100,
+ 0b01011000,
+ 0b01100000,
+ }, // delta
+ {
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ }, // _
+ {
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x07,
+ 0x00,
+ 0x00,
+ }, // `
+ {
+ 0x00,
+ 0x20,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x78,
+ }, // a
+ {
+ 0x00,
+ 0x7F,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x38,
+ }, // b
+ {
+ 0x00,
+ 0x38,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x28,
+ }, // c
+ {
+ 0x00,
+ 0x38,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x7F,
+ }, // d
+ {
+ 0x00,
+ 0x38,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x08,
+ }, // e
+ {
+ 0x00,
+ 0x08,
+ 0x7E,
+ 0x09,
+ 0x09,
+ 0x00,
+ }, // f
+ {
+ 0x00,
+ 0x18,
+ 0xA4,
+ 0xA4,
+ 0xA4,
+ 0x7C,
+ }, // g
+ {
+ 0x00,
+ 0x7F,
+ 0x04,
+ 0x04,
+ 0x78,
+ 0x00,
+ }, // h
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7D,
+ 0x40,
+ 0x00,
+ }, // i
+ {
+ 0x00,
+ 0x40,
+ 0x80,
+ 0x84,
+ 0x7D,
+ 0x00,
+ }, // j
+ {
+ 0x00,
+ 0x7F,
+ 0x10,
+ 0x28,
+ 0x44,
+ 0x00,
+ }, // k
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x40,
+ 0x00,
+ }, // l
+ {
+ 0x00,
+ 0x7C,
+ 0x04,
+ 0x18,
+ 0x04,
+ 0x78,
+ }, // m
+ {
+ 0x00,
+ 0x7C,
+ 0x04,
+ 0x04,
+ 0x78,
+ 0x00,
+ }, // n
+ {
+ 0x00,
+ 0x38,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x38,
+ }, // o
+ {
+ 0x00,
+ 0xFC,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0x38,
+ }, // p
+ {
+ 0x00,
+ 0x38,
+ 0x44,
+ 0x44,
+ 0x44,
+ 0xFC,
+ }, // q
+ {
+ 0x00,
+ 0x44,
+ 0x78,
+ 0x44,
+ 0x04,
+ 0x08,
+ }, // r
+ {
+ 0x00,
+ 0x08,
+ 0x54,
+ 0x54,
+ 0x54,
+ 0x20,
+ }, // s
+ {
+ 0x00,
+ 0x04,
+ 0x3E,
+ 0x44,
+ 0x24,
+ 0x00,
+ }, // t
+ {
+ 0x00,
+ 0x3C,
+ 0x40,
+ 0x20,
+ 0x7C,
+ 0x00,
+ }, // u
+ {
+ 0x00,
+ 0x1C,
+ 0x20,
+ 0x40,
+ 0x20,
+ 0x1C,
+ }, // v
+ {
+ 0x00,
+ 0x3C,
+ 0x60,
+ 0x30,
+ 0x60,
+ 0x3C,
+ }, // w
+ {
+ 0x00,
+ 0x6C,
+ 0x10,
+ 0x10,
+ 0x6C,
+ 0x00,
+ }, // x
+ {
+ 0x00,
+ 0x9C,
+ 0xA0,
+ 0x60,
+ 0x3C,
+ 0x00,
+ }, // y
+ {
+ 0x00,
+ 0x64,
+ 0x54,
+ 0x54,
+ 0x4C,
+ 0x00,
+ }, // z
+ {
+ 0x00,
+ 0x08,
+ 0x3E,
+ 0x41,
+ 0x41,
+ 0x00,
+ }, // {
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x77,
+ 0x00,
+ 0x00,
+ }, // |
+ {
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x41,
+ 0x3E,
+ 0x08,
+ }, // }
+ {
+ 0x00,
+ 0x02,
+ 0x01,
+ 0x02,
+ 0x01,
+ 0x00,
+ }, // ~
+};
diff --git a/Software/src/devboard/espnow/espnow.cpp b/Software/src/devboard/espnow/espnow.cpp
new file mode 100644
index 000000000..792b768b4
--- /dev/null
+++ b/Software/src/devboard/espnow/espnow.cpp
@@ -0,0 +1,203 @@
+// This is sending the Battery Emulator data over ESPNow to nearby devices
+// Maximum message size for ESPNow V1 is 250bytes
+
+#include "espnow.h"
+#include
+#include
+#include "../../battery/BATTERIES.h"
+#include "../../datalayer/datalayer.h"
+#include "../hal/hal.h"
+#include "../utils/events.h"
+#include "../utils/logging.h"
+#include "Arduino.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+
+// Create structures for keeping the send data
+struct BATTERY_INFO_TYPE b_info;
+struct BATTERY_STATUS_TYPE b_status;
+struct BATTERY_CELL_STATUS_TYPE b_cells;
+struct BATTERY_BALANCING_STATUS_TYPE b_bal_status;
+struct ESPNOW_BATTERY_MESSAGE b_message;
+
+// TODO: Add support for configurable MAC address instead of broadcast
+uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+esp_now_peer_info_t peerInfo;
+
+bool espnow_initialized = false;
+unsigned long b_lastUpdateMillis = 0;
+int b_num_batteries = 1;
+
+uint16_t emulator_id;
+
+// ESPNow callback when data is sent
+void OnDataSent(const uint8_t* mac_addr, esp_now_send_status_t status) {
+ // logging.print("Last Packet Send Status:");
+ // logging.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
+ if (status != ESP_NOW_SEND_SUCCESS) {
+ logging.println("Last ESPNow Packet Send Status: Delivery Fail");
+ }
+}
+
+static void send_battery_status(DATALAYER_BATTERY_STATUS_TYPE& status, uint8_t b_index) {
+ // Set values to send
+ memcpy(&b_message.esp_message, &status, 80); //80 bytes without ballancing info
+ b_message.emulator_id = emulator_id;
+ b_message.battery_id = b_index + 1;
+ b_message.esp_message_type = BAT_STATUS;
+
+ // Send message via ESPNow
+ esp_err_t result =
+ esp_now_send(broadcastAddress, (uint8_t*)&b_message, sizeof(struct BATTERY_STATUS_TYPE) + 4 * sizeof(uint8_t));
+
+ if (result == ESP_OK) {
+ // logging.println("Sent with success");
+ ;
+ } else {
+ logging.println("Error sending the ESPNow Battery Status data");
+ }
+}
+
+static void send_battery_info(DATALAYER_BATTERY_INFO_TYPE& info, uint8_t b_index) {
+ // Set values to send
+ memcpy(&b_message.esp_message, &info, 24); //24 bytes
+ b_message.emulator_id = emulator_id;
+ b_message.battery_id = b_index + 1;
+ b_message.esp_message_type = BAT_INFO;
+
+ // Send message via ESPNow
+ esp_err_t result =
+ esp_now_send(broadcastAddress, (uint8_t*)&b_message, sizeof(struct BATTERY_INFO_TYPE) + 4 * sizeof(uint8_t));
+
+ if (result == ESP_OK) {
+ // logging.println("Sent with success");
+ ;
+ } else {
+ logging.println("Error sending the ESPNow Battery Info data");
+ }
+}
+
+static void send_battery_balancing(DATALAYER_BATTERY_INFO_TYPE& info, DATALAYER_BATTERY_STATUS_TYPE& status,
+ uint8_t b_index) {
+ // Set values to send
+ b_bal_status.number_of_cells = info.number_of_cells;
+ for (int i = 0; i < b_bal_status.number_of_cells; i++) {
+ b_bal_status.cell_balancing_status[i] = status.cell_balancing_status[i];
+ }
+ memcpy(&b_message.esp_message, &b_bal_status, sizeof(b_bal_status)); //193 bytes
+ b_message.emulator_id = emulator_id;
+ b_message.battery_id = b_index + 1;
+ b_message.esp_message_type = BAT_BALANCE;
+
+ // Send message via ESPNow
+ esp_err_t result = esp_now_send(broadcastAddress, (uint8_t*)&b_message,
+ sizeof(struct BATTERY_BALANCING_STATUS_TYPE) + 4 * sizeof(uint8_t));
+
+ if (result == ESP_OK) {
+ // logging.println("Sent with success");
+ ;
+ } else {
+ logging.println("Error sending the ESPNow Battery Balancing data");
+ }
+}
+
+static void send_battery_cell_status(DATALAYER_BATTERY_INFO_TYPE& info, DATALAYER_BATTERY_STATUS_TYPE& status,
+ uint8_t b_index) {
+ // Set values to send
+ b_cells.number_of_cells = info.number_of_cells;
+ for (int i = 0; i < b_cells.number_of_cells; i++) {
+ b_cells.cell_voltages_mV[i] = status.cell_voltages_mV[i] / 20;
+ }
+ memcpy(&b_message.esp_message, &b_cells, sizeof(b_cells)); //193 bytes
+ b_message.emulator_id = emulator_id;
+ b_message.battery_id = b_index + 1;
+ b_message.esp_message_type = BAT_CELL_STATUS;
+
+ // Send message via ESPNow
+ esp_err_t result = esp_now_send(broadcastAddress, (uint8_t*)&b_message,
+ sizeof(struct BATTERY_CELL_STATUS_TYPE) + 4 * sizeof(uint8_t));
+
+ if (result == ESP_OK) {
+ // logging.println("Sent with success");
+ ;
+ } else {
+ logging.println("Error sending the ESPNow Cell Status data");
+ }
+}
+
+void init_espnow() {
+ // Check wifi status
+ // Wifi Should be already set before initializing ESPNow
+ if ((WiFi.getMode() != WIFI_AP_STA) && (WiFi.getMode() != WIFI_STA)) {
+ logging.println("Wifi should be initialized before using ESPNow");
+ return;
+ }
+
+ // Init ESPNow
+ if (esp_now_init() != ESP_OK) {
+ logging.println("Error initializing ESPNow");
+ return;
+ }
+
+ // Once ESPNow is successfully Init, we will register for Send CB to
+ // get the status of Transmitted packet
+ ESP_ERROR_CHECK(esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent)));
+
+ // Register peer
+ memcpy(peerInfo.peer_addr, broadcastAddress, 6);
+ peerInfo.channel = 0;
+ peerInfo.encrypt = false;
+
+ // Add peer
+ if (esp_now_add_peer(&peerInfo) != ESP_OK) {
+ logging.println("Failed to add ESPNow peer");
+ return;
+ }
+
+ // Get last 2 bytes from Mac as Emulator ID
+ emulator_id = ESP.getEfuseMac() & 0xFFFF;
+
+ // Count configured batteries
+ if (battery2)
+ b_num_batteries++;
+ if (battery3)
+ b_num_batteries++;
+
+ espnow_initialized = true;
+}
+
+void update_espnow() {
+ if (!espnow_initialized) {
+ return;
+ }
+
+ // We send the ESPNow messages every 1000ms
+ auto currentMillis = millis();
+ if (currentMillis - b_lastUpdateMillis < 1000) {
+ return;
+ }
+
+ int battery_index = 0;
+
+ // Print the battery status for current battery
+ if (battery_index == 0 && battery != nullptr) {
+ send_battery_info(datalayer.battery.info, battery_index);
+ send_battery_status(datalayer.battery.status, battery_index);
+ send_battery_cell_status(datalayer.battery.info, datalayer.battery.status, battery_index);
+ send_battery_balancing(datalayer.battery3.info, datalayer.battery.status, battery_index);
+
+ } else if (battery_index == 1) {
+ send_battery_info(datalayer.battery2.info, battery_index);
+ send_battery_status(datalayer.battery2.status, battery_index);
+ send_battery_cell_status(datalayer.battery2.info, datalayer.battery2.status, battery_index);
+ send_battery_balancing(datalayer.battery3.info, datalayer.battery2.status, battery_index);
+ } else {
+ send_battery_info(datalayer.battery3.info, battery_index);
+ send_battery_status(datalayer.battery3.status, battery_index);
+ send_battery_cell_status(datalayer.battery3.info, datalayer.battery3.status, battery_index);
+ send_battery_balancing(datalayer.battery3.info, datalayer.battery3.status, battery_index);
+ }
+
+ b_lastUpdateMillis = currentMillis;
+}
diff --git a/Software/src/devboard/espnow/espnow.h b/Software/src/devboard/espnow/espnow.h
new file mode 100644
index 000000000..e8fa40f92
--- /dev/null
+++ b/Software/src/devboard/espnow/espnow.h
@@ -0,0 +1,150 @@
+#ifndef _ESPNOW_H_
+#define _ESPNOW_H_
+
+#include "../../system_settings.h"
+#include "../utils/types.h"
+
+void init_espnow();
+void update_espnow();
+
+struct BATTERY_INFO_TYPE {
+ /** uint32_t */
+ /** Total energy capacity in Watt-hours
+ * Automatically updates depending on battery integration OR from settings page
+ */
+ uint32_t total_capacity_Wh = 30000;
+ uint32_t reported_total_capacity_Wh = 30000;
+
+ /** uint16_t */
+ /** The maximum intended packvoltage, in deciVolt. 4900 = 490.0 V */
+ uint16_t max_design_voltage_dV = 5000;
+ /** The minimum intended packvoltage, in deciVolt. 3300 = 330.0 V */
+ uint16_t min_design_voltage_dV = 2500;
+ /** The maximum cellvoltage before shutting down, in milliVolt. 4300 = 4.300 V */
+ uint16_t max_cell_voltage_mV = 4300;
+ /** The minimum cellvoltage before shutting down, in milliVolt. 2700 = 2.700 V */
+ uint16_t min_cell_voltage_mV = 2700;
+ /** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */
+ uint16_t max_cell_voltage_deviation_mV = 500;
+
+ /** uint8_t */
+ /** Total number of cells in the pack */
+ uint8_t number_of_cells;
+
+ /** Other */
+ /** Chemistry of the pack. Autodetect, or force specific chemistry */
+ battery_chemistry_enum chemistry = battery_chemistry_enum::NCA;
+}; // 24 bytes
+
+struct BATTERY_STATUS_TYPE {
+ /** uint32_t */
+ /** Remaining energy capacity in Watt-hours */
+ uint32_t remaining_capacity_Wh = 0;
+ /** The remaining capacity reported to the inverter based on min percentage setting, in Watt-hours
+ * This value will either be scaled or not scaled depending on the value of
+ * battery.settings.soc_scaling_active
+ */
+ uint32_t reported_remaining_capacity_Wh;
+ /** Maximum allowed battery discharge power in Watts. Set by battery */
+ uint32_t max_discharge_power_W = 0;
+ /** Maximum allowed battery charge power in Watts. Set by battery */
+ uint32_t max_charge_power_W = 0;
+ /* Some early integrations do not support reading allowed charge power from battery
+ On these integrations we need to have the user specify what limits the battery can take */
+ /** Overriden allowed battery discharge power in Watts. Set by user */
+ uint32_t override_discharge_power_W = 0;
+ /** Overriden allowed battery charge power in Watts. Set by user */
+ uint32_t override_charge_power_W = 0;
+
+ /** int32_t */
+ /** Instantaneous battery power in Watts. Calculated based on voltage_dV and current_dA */
+ /* Positive value = Battery Charging */
+ /* Negative value = Battery Discharging */
+ int32_t active_power_W = 0;
+ int32_t total_charged_battery_Wh = 0;
+ int32_t total_discharged_battery_Wh = 0;
+
+ /** uint16_t */
+ /** Maximum allowed battery discharge current in dA. Calculated based on allowed W and Voltage */
+ uint16_t max_discharge_current_dA = 0;
+ /** Maximum allowed battery charge current in dA. Calculated based on allowed W and Voltage */
+ uint16_t max_charge_current_dA = 0;
+ /** State of health in integer-percent x 100. 9900 = 99.00% */
+ uint16_t soh_pptt = 9900;
+ /** Instantaneous battery voltage in deciVolts. 3700 = 370.0 V */
+ uint16_t voltage_dV = 3700;
+ /** Maximum cell voltage currently measured in the pack, in mV */
+ uint16_t cell_max_voltage_mV = 3700;
+ /** Minimum cell voltage currently measured in the pack, in mV */
+ uint16_t cell_min_voltage_mV = 3700;
+ /** The "real" SOC reported from the battery, in integer-percent x 100. 9550 = 95.50% */
+ uint16_t real_soc;
+ /** The SOC reported to the inverter, in integer-percent x 100. 9550 = 95.50%.
+ * This value will either be scaled or not scaled depending on the value of
+ * 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;
+
+ /** int16_t */
+ /** Maximum temperature currently measured in the pack, in dยฐC. 150 = 15.0 ยฐC */
+ int16_t temperature_max_dC;
+ /** Minimum temperature currently measured in the pack, in dยฐC. 150 = 15.0 ยฐC */
+ int16_t temperature_min_dC;
+ /** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
+ int16_t current_dA;
+ /** Instantaneous battery current in deciAmpere. Sum of all batteries in the system 95 = 9.5 A */
+ int16_t reported_current_dA;
+
+ /** uint8_t */
+ /** A counter set each time a new message comes from battery.
+ * This value then gets decremented every second. Incase we reach 0
+ * we report the battery as missing entirely on the CAN bus.
+ */
+ uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
+ /** The current system status, which for now still has the name bms_status */
+ bms_status_enum bms_status = ACTIVE;
+ /** The current battery status, which for now has the name real_bms_status */
+ real_bms_status_enum real_bms_status = BMS_DISCONNECTED;
+ /** LED mode, customizable by user */
+ led_mode_enum led_mode = CLASSIC;
+ /** Balancing status */
+ balancing_status_enum balancing_status = BALANCING_STATUS_UNKNOWN;
+}; // 80 bytes
+
+struct BATTERY_BALANCING_STATUS_TYPE {
+ /** All balancing resistors status inside the pack, either on(1) or off(0).
+ * Use with battery.info.number_of_cells to get valid data.
+ * Not available for all battery manufacturers.
+ */
+ bool cell_balancing_status[MAX_AMOUNT_CELLS];
+
+ /** uint8_t */
+ /** Total number of cells in the pack */
+ uint8_t number_of_cells;
+
+}; // 193 bytes
+
+struct BATTERY_CELL_STATUS_TYPE {
+
+ /** All cell voltages currently measured in the pack, in mV. 212 * 20 = 4240 mV
+ * Use with battery.info.number_of_cells to get valid data.
+ */
+ uint8_t cell_voltages_mV[MAX_AMOUNT_CELLS];
+
+ /** uint8_t */
+ /** Total number of cells in the pack */
+ uint8_t number_of_cells;
+}; // 193 bytes
+
+enum espnow_message_enum { BAT_INFO = 1, BAT_STATUS = 2, BAT_BALANCE = 3, BAT_CELL_STATUS = 4 };
+
+struct ESPNOW_BATTERY_MESSAGE {
+ uint16_t emulator_id;
+ uint8_t battery_id;
+ uint8_t esp_message_type;
+ uint8_t esp_message[];
+} __packed;
+
+#endif // _ESPNOW_H_
diff --git a/Software/src/devboard/hal/hal.cpp b/Software/src/devboard/hal/hal.cpp
index e8b0a9589..b041f5863 100644
--- a/Software/src/devboard/hal/hal.cpp
+++ b/Software/src/devboard/hal/hal.cpp
@@ -17,6 +17,9 @@ void init_hal() {
#elif defined(HW_3LB)
#include "hw_3LB.h"
esp32hal = new ThreeLBHal();
+#elif defined(HW_BECOM)
+#include "hw_becom.h"
+ esp32hal = new BEComHal();
#elif defined(HW_DEVKIT)
#include "hw_devkit.h"
esp32hal = new DevKitHal();
diff --git a/Software/src/devboard/hal/hal.h b/Software/src/devboard/hal/hal.h
index a23939b72..21e7fd328 100644
--- a/Software/src/devboard/hal/hal.h
+++ b/Software/src/devboard/hal/hal.h
@@ -136,6 +136,7 @@ class Esp32Hal {
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t BMS_POWER() { return GPIO_NUM_NC; }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
+ virtual gpio_num_t TRIPLE_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_NC; }
@@ -156,6 +157,10 @@ class Esp32Hal {
virtual gpio_num_t LED_PIN() { return GPIO_NUM_NC; }
virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; }
+ // i2c display
+ virtual gpio_num_t DISPLAY_SDA_PIN() { return GPIO_NUM_NC; }
+ virtual gpio_num_t DISPLAY_SCL_PIN() { return GPIO_NUM_NC; }
+
// Equipment stop pin
virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_NC; }
diff --git a/Software/src/devboard/hal/hw_3LB.h b/Software/src/devboard/hal/hw_3LB.h
index 5db9ae297..c2a258a06 100644
--- a/Software/src/devboard/hal/hw_3LB.h
+++ b/Software/src/devboard/hal/hw_3LB.h
@@ -45,6 +45,7 @@ class ThreeLBHal : public Esp32Hal {
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; }
virtual gpio_num_t BMS_POWER() { return GPIO_NUM_2; }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_13; }
+ virtual gpio_num_t TRIPLE_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_25; }
diff --git a/Software/src/devboard/hal/hw_becom.h b/Software/src/devboard/hal/hw_becom.h
new file mode 100644
index 000000000..b758a6b69
--- /dev/null
+++ b/Software/src/devboard/hal/hw_becom.h
@@ -0,0 +1,102 @@
+#ifndef __HW_BECOM_H__
+#define __HW_BECOM_H__
+
+#include "hal.h"
+
+class BEComHal : public Esp32Hal {
+ public:
+ const char* name() { return "BECom"; }
+
+ virtual void set_default_configuration_values() {
+ BatteryEmulatorSettingsStore settings;
+ if (!settings.settingExists("CANFREQ")) {
+ settings.saveUInt("CANFREQ", 40);
+ }
+ }
+
+ virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_8; }
+ virtual gpio_num_t CAN_RX_PIN() { return GPIO_NUM_18; }
+
+ virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_15; }
+ virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_17; }
+ // New Drive Enable Pin for RS485 Half Duplex communication
+ virtual gpio_num_t RS485_DE_PIN() { return GPIO_NUM_16; }
+
+ // 1st CANFD Interface: MCP2518
+ virtual gpio_num_t MCP2517_SCK() { return GPIO_NUM_12; }
+ virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_11; }
+ virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_13; }
+ virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_14; }
+ virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_10; }
+
+ // 2nd CANFD Interface: MCP2518
+ // SPI Bus is shared with the 1st interface, only INT and CS pins are needed
+ virtual gpio_num_t MCP2517_CS2() { return GPIO_NUM_21; }
+ virtual gpio_num_t MCP2517_INT2() { return GPIO_NUM_9; }
+
+ // Contactor handling
+ virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_47; }
+ virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_48; }
+ virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_45; }
+ // BMS Power Pin is inverted, High Value shuts down the Battery
+ // TODO: Adapt the rest of the code to invert the power control
+ virtual gpio_num_t BMS_POWER() { return GPIO_NUM_1; }
+ // SECOND_BATTERY_CONTACTORS_PIN mapped to the Battery 2 Negative Contactor Pin
+ virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_37; }
+
+ // Automatic precharging
+ // HIA4V1_PIN mapped to Battery 1 Pre-Chage Contactor Pin
+ virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_45; }
+ // INVERTER_DISCONNECT_CONTACTOR_PIN mapped to Battery 1 Positive Contactor Pin
+ virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_47; }
+
+ // SMA CAN contactor pin
+ virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_3; }
+
+ // LED
+ virtual gpio_num_t LED_PIN() { return GPIO_NUM_5; }
+ virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; }
+
+ // Equipment stop pin
+ // Mapped to and internal allways low pin, in hardware v1
+ virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_46; }
+
+ // Battery wake up pins
+ virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_2; }
+ virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_41; }
+
+ std::vector available_interfaces() {
+ return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanFdNative};
+ }
+
+ virtual const char* name_for_comm_interface(comm_interface comm) {
+ switch (comm) {
+ case comm_interface::CanNative:
+ return "Inverter CAN (Native)";
+ case comm_interface::CanFdNative:
+ return "Battery CANFD (Native)";
+ case comm_interface::CanAddonMcp2515:
+ return "";
+ case comm_interface::CanFdAddonMcp2518:
+ return "CAN FD (MCP2518 add-on)";
+ case comm_interface::Modbus:
+ return "Modbus";
+ case comm_interface::RS485:
+ return "RS485";
+ case comm_interface::Highest:
+ return "";
+ }
+ return Esp32Hal::name_for_comm_interface(comm);
+ }
+};
+
+#define HalClass BEComHal
+
+/* ----- 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
diff --git a/Software/src/devboard/hal/hw_devkit.h b/Software/src/devboard/hal/hw_devkit.h
index 23ceeffe3..e8a365397 100644
--- a/Software/src/devboard/hal/hw_devkit.h
+++ b/Software/src/devboard/hal/hw_devkit.h
@@ -108,6 +108,7 @@ class DevKitHal : public Esp32Hal {
virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_16; }
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_17; }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_32; }
+ virtual gpio_num_t TRIPLE_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_4; }
diff --git a/Software/src/devboard/hal/hw_lilygo.h b/Software/src/devboard/hal/hw_lilygo.h
index 06d53a742..710579b14 100644
--- a/Software/src/devboard/hal/hw_lilygo.h
+++ b/Software/src/devboard/hal/hw_lilygo.h
@@ -3,6 +3,8 @@
#include "hal.h"
+#include "../utils/types.h"
+
class LilyGoHal : public Esp32Hal {
public:
const char* name() { return "LilyGo T-CAN485"; }
@@ -47,23 +49,54 @@ class LilyGoHal : public Esp32Hal {
virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_32; }
virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_33; }
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; }
- virtual gpio_num_t BMS_POWER() { return GPIO_NUM_18; }
+ virtual gpio_num_t BMS_POWER() { //BMS power (Modifiable via Webserver)
+ if (user_selected_gpioopt2 == GPIOOPT2::DEFAULT_OPT_BMS_POWER_18) {
+ return GPIO_NUM_18;
+ } //Else user_selected_gpioopt2 == GPIOOPT2::BMS_POWER_25
+ return GPIO_NUM_25;
+ }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_15; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_25; }
virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_32; }
-
- // SMA CAN contactor pins
- virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_5; }
+ virtual gpio_num_t TRIPLE_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
+
+ // SMA CAN contactor pins (Modifiable via Webserver)
+ virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() {
+ if (user_selected_gpioopt3 == GPIOOPT3::DEFAULT_SMA_ENABLE_05) {
+ return GPIO_NUM_5;
+ } //Else user_selected_gpioopt3 == GPIOOPT3::SMA_ENABLE_33
+ return GPIO_NUM_33;
+ }
// virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_LED_PIN() { return GPIO_NUM_NC; }
// SD card
- virtual gpio_num_t SD_MISO_PIN() { return GPIO_NUM_2; }
- virtual gpio_num_t SD_MOSI_PIN() { return GPIO_NUM_15; }
- virtual gpio_num_t SD_SCLK_PIN() { return GPIO_NUM_14; }
- virtual gpio_num_t SD_CS_PIN() { return GPIO_NUM_13; }
+ virtual gpio_num_t SD_MISO_PIN() {
+ if (user_selected_gpioopt4 == GPIOOPT4::DEFAULT_SD_CARD) {
+ return GPIO_NUM_2;
+ } //Else user_selected_gpioopt4 == GPIOOPT4::I2C_DISPLAY_SSD1306
+ return GPIO_NUM_NC;
+ }
+ virtual gpio_num_t SD_MOSI_PIN() {
+ if (user_selected_gpioopt4 == GPIOOPT4::DEFAULT_SD_CARD) {
+ return GPIO_NUM_15;
+ } //Else user_selected_gpioopt4 == GPIOOPT4::I2C_DISPLAY_SSD1306
+ return GPIO_NUM_NC;
+ }
+ virtual gpio_num_t SD_SCLK_PIN() {
+ if (user_selected_gpioopt4 == GPIOOPT4::DEFAULT_SD_CARD) {
+ return GPIO_NUM_14;
+ } //Else user_selected_gpioopt4 == GPIOOPT4::I2C_DISPLAY_SSD1306
+ return GPIO_NUM_NC;
+ }
+ virtual gpio_num_t SD_CS_PIN() {
+ if (user_selected_gpioopt4 == GPIOOPT4::DEFAULT_SD_CARD) {
+ return GPIO_NUM_13;
+ } //Else user_selected_gpioopt4 == GPIOOPT4::I2C_DISPLAY_SSD1306
+ return GPIO_NUM_NC;
+ }
// LED
virtual gpio_num_t LED_PIN() { return GPIO_NUM_4; }
@@ -75,6 +108,20 @@ class LilyGoHal : public Esp32Hal {
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; }
+ // i2c display
+ virtual gpio_num_t DISPLAY_SDA_PIN() {
+ if (user_selected_gpioopt4 == GPIOOPT4::I2C_DISPLAY_SSD1306) {
+ return GPIO_NUM_15;
+ }
+ return GPIO_NUM_NC;
+ }
+ virtual gpio_num_t DISPLAY_SCL_PIN() {
+ if (user_selected_gpioopt4 == GPIOOPT4::I2C_DISPLAY_SSD1306) {
+ return GPIO_NUM_14;
+ }
+ return GPIO_NUM_NC;
+ }
+
std::vector available_interfaces() {
return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanAddonMcp2515,
comm_interface::CanFdAddonMcp2518};
@@ -90,6 +137,12 @@ class LilyGoHal : public Esp32Hal {
return "CAN (MCP2515 add-on)";
case comm_interface::CanFdAddonMcp2518:
return "CAN FD (MCP2518 add-on)";
+ case comm_interface::Modbus:
+ return "Modbus";
+ case comm_interface::RS485:
+ return "RS485";
+ case comm_interface::Highest:
+ return "";
}
return Esp32Hal::name_for_comm_interface(comm);
}
diff --git a/Software/src/devboard/hal/hw_lilygo2can.h b/Software/src/devboard/hal/hw_lilygo2can.h
index 84e3f898e..2e036e19a 100644
--- a/Software/src/devboard/hal/hw_lilygo2can.h
+++ b/Software/src/devboard/hal/hw_lilygo2can.h
@@ -3,6 +3,8 @@
#include "hal.h"
+#include "../utils/types.h"
+
/*
The 2CAN has four GPIOs on the top (43/44 and 1/2 on each of the SH-1mm
connectors) and 21 on the bottom (on the 2x13 header). GPIO35-37 aren't usable
@@ -20,7 +22,7 @@ if opi PSRAM is enabled.
38:SCK 42:SDI
x37:SDO 41:nCS
x36:ESTOP 40
- 16 4
+ 16 4:BAT3_CTRS
15 5:BAT2_CTRS
45 48:POS_CTR
47: 21:PRECHARGE
@@ -70,8 +72,14 @@ class LilyGo2CANHal : public Esp32Hal {
virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_48; }
virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_17; }
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_21; }
- virtual gpio_num_t BMS_POWER() { return GPIO_NUM_3; }
+ virtual gpio_num_t BMS_POWER() {
+ if (user_selected_gpioopt1 == GPIOOPT1::ESTOP_BMS_POWER) {
+ return GPIO_NUM_2;
+ }
+ return GPIO_NUM_3;
+ }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_5; }
+ virtual gpio_num_t TRIPLE_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_4; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_18; }
@@ -84,12 +92,50 @@ class LilyGo2CANHal : public Esp32Hal {
virtual gpio_num_t LED_PIN() { return GPIO_NUM_35; }
virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; }
+ // CHAdeMO support pin dependencies
+ virtual gpio_num_t CHADEMO_PIN_2() { return GPIO_NUM_16; }
+ virtual gpio_num_t CHADEMO_PIN_10() { return GPIO_NUM_15; }
+ virtual gpio_num_t CHADEMO_PIN_7() { return GPIO_NUM_47; }
+ virtual gpio_num_t CHADEMO_PIN_4() { return GPIO_NUM_4; }
+ virtual gpio_num_t CHADEMO_LOCK() { return GPIO_NUM_40; }
+
+ // i2c display
+ virtual gpio_num_t DISPLAY_SDA_PIN() {
+ if (user_selected_gpioopt1 == GPIOOPT1::I2C_DISPLAY_SSD1306) {
+ return GPIO_NUM_1;
+ }
+ return GPIO_NUM_NC;
+ }
+ virtual gpio_num_t DISPLAY_SCL_PIN() {
+ if (user_selected_gpioopt1 == GPIOOPT1::I2C_DISPLAY_SSD1306) {
+ return GPIO_NUM_2;
+ }
+ return GPIO_NUM_NC;
+ }
+
// Equipment stop pin
- virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_36; }
+ virtual gpio_num_t EQUIPMENT_STOP_PIN() {
+ if (user_selected_gpioopt1 == GPIOOPT1::ESTOP_BMS_POWER) {
+ return GPIO_NUM_1;
+ }
+ return GPIO_NUM_36;
+ }
// Battery wake up pins
- virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_1; }
- virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_2; }
+ virtual gpio_num_t WUP_PIN1() {
+ if (user_selected_gpioopt1 == GPIOOPT1::DEFAULT_OPT) {
+ // WUP1 on top port
+ return GPIO_NUM_1;
+ }
+ return GPIO_NUM_18;
+ }
+ virtual gpio_num_t WUP_PIN2() {
+ if (user_selected_gpioopt1 == GPIOOPT1::DEFAULT_OPT) {
+ // WUP2 on top port
+ return GPIO_NUM_2;
+ }
+ return GPIO_NUM_14;
+ }
std::vector available_interfaces() {
return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanAddonMcp2515,
@@ -104,6 +150,14 @@ class LilyGo2CANHal : public Esp32Hal {
return "";
case comm_interface::CanAddonMcp2515:
return "CAN A (MCP2515)";
+ case comm_interface::CanFdAddonMcp2518:
+ return "CAN FD (MCP2518 add-on)";
+ case comm_interface::Modbus:
+ return "Modbus (Add-on)";
+ case comm_interface::RS485:
+ return "RS485 (Add-on)";
+ case comm_interface::Highest:
+ return "";
}
return Esp32Hal::name_for_comm_interface(comm);
}
diff --git a/Software/src/devboard/hal/hw_stark.h b/Software/src/devboard/hal/hw_stark.h
index ab86707b8..e1c2c8081 100644
--- a/Software/src/devboard/hal/hw_stark.h
+++ b/Software/src/devboard/hal/hw_stark.h
@@ -62,6 +62,7 @@ class StarkHal : public Esp32Hal {
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; }
virtual gpio_num_t BMS_POWER() { return GPIO_NUM_23; }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_19; }
+ virtual gpio_num_t TRIPLE_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_19; }
@@ -95,6 +96,13 @@ class StarkHal : public Esp32Hal {
return "";
case comm_interface::CanFdAddonMcp2518:
return "";
+ case comm_interface::Modbus:
+ return "Modbus";
+ case comm_interface::RS485:
+ return "RS485";
+ case comm_interface::Highest:
+ return "";
+ break;
}
return Esp32Hal::name_for_comm_interface(comm);
}
diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp
index 59cc4d130..45e9892ee 100644
--- a/Software/src/devboard/mqtt/mqtt.cpp
+++ b/Software/src/devboard/mqtt/mqtt.cpp
@@ -7,16 +7,23 @@
#include "../../battery/BATTERIES.h"
#include "../../communication/contactorcontrol/comm_contactorcontrol.h"
#include "../../datalayer/datalayer.h"
+#include "../../devboard/hal/hal.h"
+#include "../../devboard/safety/safety.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../utils/events.h"
#include "../utils/timer.h"
+#include "../webserver/webserver.h"
#include "mqtt.h"
#include "mqtt_client.h"
+std::string mqtt_user;
+std::string mqtt_password;
+
bool mqtt_enabled = false;
bool ha_autodiscovery_enabled = false;
bool mqtt_transmit_all_cellvoltages = false;
uint16_t mqtt_timeout_ms = 2000;
+uint16_t mqtt_publish_interval_ms = 5000;
const int mqtt_port_default = 0;
const char* mqtt_server_default = "";
@@ -43,8 +50,8 @@ const char* ha_device_id =
esp_mqtt_client_config_t mqtt_cfg;
esp_mqtt_client_handle_t client;
char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
-MyTimer publish_global_timer(5000); //publish timer
-MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical.
+MyTimer publish_global_timer(0); // Will be configured with mqtt_publish_interval_ms on first use
+MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical.
bool client_started = false;
static String lwt_topic = "";
@@ -128,7 +135,8 @@ SensorConfig batterySensorConfigTemplate[] = {
{"max_charge_power", "Battery Max Charge Power", "", "W", "power", always},
{"charged_energy", "Battery Charged Energy", "", "Wh", "energy", supports_charged},
{"discharged_energy", "Battery Discharged Energy", "", "Wh", "energy", supports_charged},
- {"balancing_active_cells", "Balancing Active Cells", "", "", "", always}};
+ {"balancing_active_cells", "Balancing Active Cells", "", "", "", always},
+ {"balancing_status", "Balancing Status", "", "", "", always}};
SensorConfig globalSensorConfigTemplate[] = {{"bms_status", "BMS Status", "", "", "", always},
{"pause_status", "Pause Status", "", "", "", always},
@@ -209,20 +217,35 @@ static String generateButtonTopic(const char* subtype) {
return topic_name + "/command/" + String(subtype);
}
+static const char* get_balancing_status_text(balancing_status_enum status) {
+ switch (status) {
+ case BALANCING_STATUS_UNKNOWN:
+ return "Unknown";
+ case BALANCING_STATUS_ERROR:
+ return "Error";
+ case BALANCING_STATUS_READY:
+ return "Ready";
+ case BALANCING_STATUS_ACTIVE:
+ return "Active";
+ default:
+ return "Unknown";
+ }
+}
+
void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& battery, const String& suffix,
bool supports_charged) {
- doc["SOC" + suffix] = ((float)battery.status.reported_soc) / 100.0;
- doc["SOC_real" + suffix] = ((float)battery.status.real_soc) / 100.0;
- doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0;
- doc["temperature_min" + suffix] = ((float)((int16_t)battery.status.temperature_min_dC)) / 10.0;
- doc["temperature_max" + suffix] = ((float)((int16_t)battery.status.temperature_max_dC)) / 10.0;
+ doc["SOC" + suffix] = ((float)battery.status.reported_soc) / 100.0f;
+ doc["SOC_real" + suffix] = ((float)battery.status.real_soc) / 100.0f;
+ doc["state_of_health" + suffix] = ((float)battery.status.soh_pptt) / 100.0f;
+ doc["temperature_min" + suffix] = ((float)((int16_t)battery.status.temperature_min_dC)) / 10.0f;
+ doc["temperature_max" + suffix] = ((float)((int16_t)battery.status.temperature_max_dC)) / 10.0f;
doc["cpu_temp" + suffix] = datalayer.system.info.CPU_temperature;
doc["stat_batt_power" + suffix] = ((float)((int32_t)battery.status.active_power_W));
- doc["battery_current" + suffix] = ((float)((int16_t)battery.status.current_dA)) / 10.0;
- doc["battery_voltage" + suffix] = ((float)battery.status.voltage_dV) / 10.0;
+ doc["battery_current" + suffix] = ((float)((int16_t)battery.status.current_dA)) / 10.0f;
+ doc["battery_voltage" + suffix] = ((float)battery.status.voltage_dV) / 10.0f;
if (battery.info.number_of_cells != 0u && battery.status.cell_voltages_mV[battery.info.number_of_cells - 1] != 0u) {
- doc["cell_max_voltage" + suffix] = ((float)battery.status.cell_max_voltage_mV) / 1000.0;
- doc["cell_min_voltage" + suffix] = ((float)battery.status.cell_min_voltage_mV) / 1000.0;
+ doc["cell_max_voltage" + suffix] = ((float)battery.status.cell_max_voltage_mV) / 1000.0f;
+ doc["cell_min_voltage" + suffix] = ((float)battery.status.cell_min_voltage_mV) / 1000.0f;
doc["cell_voltage_delta" + suffix] =
((float)battery.status.cell_max_voltage_mV) - ((float)battery.status.cell_min_voltage_mV);
}
@@ -250,6 +273,7 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat
}
}
doc["balancing_active_cells" + suffix] = active_cells;
+ doc["balancing_status" + suffix] = get_balancing_status_text(battery.status.balancing_status);
}
static std::vector order_events;
@@ -323,7 +347,7 @@ static bool publish_cell_voltages(void) {
static String state_topic_2 = topic_name + "/spec_data_2";
if (ha_autodiscovery_enabled) {
- bool failed_to_publish = false;
+ bool successfully_published = false;
if (ha_cell_voltages_published == false) {
// If the cell voltage number isn't initialized...
@@ -336,34 +360,35 @@ static bool publish_cell_voltages(void) {
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true) == false) {
- failed_to_publish = true;
return false;
}
}
+ successfully_published = true;
doc.clear(); // clear after sending autoconfig
}
if (battery2) {
+ successfully_published = false;
// TODO: Combine this identical block with the previous one.
// If the cell voltage number isn't initialized...
if (datalayer.battery2.info.number_of_cells != 0u) {
- for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
+ for (int i = 0; i < datalayer.battery2.info.number_of_cells; i++) {
int cellNumber = i + 1;
set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_", " 2");
set_common_discovery_attributes(doc);
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) {
- failed_to_publish = true;
return false;
}
}
+ successfully_published = true;
doc.clear(); // clear after sending autoconfig
}
}
}
- if (failed_to_publish == false) {
+ if (successfully_published) {
ha_cell_voltages_published = true;
}
}
@@ -374,7 +399,7 @@ static bool publish_cell_voltages(void) {
JsonArray cell_voltages = doc["cell_voltages"].to();
for (size_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) {
- cell_voltages.add(((float)datalayer.battery.status.cell_voltages_mV[i]) / 1000.0);
+ cell_voltages.add(((float)datalayer.battery.status.cell_voltages_mV[i]) / 1000.0f);
}
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
@@ -393,7 +418,7 @@ static bool publish_cell_voltages(void) {
JsonArray cell_voltages = doc["cell_voltages"].to();
for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) {
- cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0);
+ cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0f);
}
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
@@ -551,13 +576,12 @@ void mqtt_message_received(char* topic_raw, int topic_len, char* data, int data_
logging.printf("MQTT message arrived: [%.*s]\n", topic_len, topic);
-#ifdef REMOTE_BMS_RESET
- const char* bmsreset_topic = generateButtonTopic("BMSRESET").c_str();
- if (strcmp(topic, bmsreset_topic) == 0) {
- logging.println("Triggering BMS reset");
- start_bms_reset();
+ if (remote_bms_reset) {
+ if (strcmp(topic, generateButtonTopic("BMSRESET").c_str()) == 0) {
+ logging.println("Triggering BMS reset");
+ start_bms_reset();
+ }
}
-#endif // REMOTE_BMS_RESET
if (strcmp(topic, generateButtonTopic("PAUSE").c_str()) == 0) {
setBatteryPause(true, false);
@@ -639,6 +663,22 @@ static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_
logging.print("captured as transport's socket errno");
logging.println(strerror(event->error_handle->esp_transport_sock_errno));
break;
+ case MQTT_EVENT_SUBSCRIBED:
+ break;
+ case MQTT_EVENT_UNSUBSCRIBED:
+ break;
+ case MQTT_EVENT_PUBLISHED:
+ break;
+ case MQTT_EVENT_BEFORE_CONNECT:
+ break;
+ case MQTT_EVENT_DELETED:
+ break;
+#ifndef CONFIG_IDF_TARGET_ESP32S3
+ case MQTT_USER_EVENT:
+ break;
+#endif
+ case MQTT_EVENT_ANY:
+ break;
}
}
@@ -682,23 +722,7 @@ bool init_mqtt(void) {
String clientId = String("BatteryEmulatorClient-") + WiFi.getHostname();
-#ifdef CONFIG_IDF_TARGET_ESP32S3
- // ESP32-S3 uses flat MQTT config structure
- mqtt_cfg.transport = MQTT_TRANSPORT_OVER_TCP;
- mqtt_cfg.host = mqtt_server.c_str();
- mqtt_cfg.port = mqtt_port;
- mqtt_cfg.client_id = clientId.c_str();
- mqtt_cfg.username = mqtt_user.c_str();
- mqtt_cfg.password = mqtt_password.c_str();
- lwt_topic = topic_name + "/status";
- mqtt_cfg.lwt_topic = lwt_topic.c_str();
- mqtt_cfg.lwt_qos = 1;
- mqtt_cfg.lwt_retain = true;
- mqtt_cfg.lwt_msg = "offline";
- mqtt_cfg.lwt_msg_len = strlen(mqtt_cfg.lwt_msg);
- mqtt_cfg.network_timeout_ms = mqtt_timeout_ms;
-#else
- // ESP32 classic uses nested MQTT config structure
+ // ESP-IDF 5.x uses newer nested MQTT config structure (both ESP32 and ESP32-S3)
mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
mqtt_cfg.broker.address.hostname = mqtt_server.c_str();
mqtt_cfg.broker.address.port = mqtt_port;
@@ -712,7 +736,6 @@ bool init_mqtt(void) {
mqtt_cfg.session.last_will.msg = "offline";
mqtt_cfg.session.last_will.msg_len = strlen(mqtt_cfg.session.last_will.msg);
mqtt_cfg.network.timeout_ms = mqtt_timeout_ms;
-#endif
client = esp_mqtt_client_init(&mqtt_cfg);
if (client == nullptr) {
@@ -727,18 +750,20 @@ bool init_mqtt(void) {
}
void mqtt_client_loop(void) {
- // Only attempt to publish/reconnect MQTT if Wi-Fi is connectedand checkTimmer is elapsed
+ // Only attempt to publish/reconnect MQTT if Wi-Fi is connected and checkTimmer is elapsed
if (check_global_timer.elapsed() && WiFi.status() == WL_CONNECTED) {
if (client_started == false) {
+ // Configure timer with the loaded interval on first use
+ publish_global_timer = MyTimer(mqtt_publish_interval_ms);
esp_mqtt_client_start(client);
client_started = true;
logging.println("MQTT initialized");
return;
}
- if (publish_global_timer.elapsed()) // Every 5s
- {
+ // Skip publishing if OTA update is in progress to avoid interference
+ if (publish_global_timer.elapsed() && !ota_active) {
publish_values();
}
}
diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h
index 9ea4e50e1..c1ff1089f 100644
--- a/Software/src/devboard/mqtt/mqtt.h
+++ b/Software/src/devboard/mqtt/mqtt.h
@@ -45,6 +45,7 @@ extern const char* version_number; // The current software version, used for mq
extern bool mqtt_enabled;
extern bool mqtt_transmit_all_cellvoltages;
extern uint16_t mqtt_timeout_ms;
+extern uint16_t mqtt_publish_interval_ms;
extern bool ha_autodiscovery_enabled;
extern std::string mqtt_server;
extern std::string mqtt_user;
diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp
index a07d3fa98..a2164c265 100644
--- a/Software/src/devboard/safety/safety.cpp
+++ b/Software/src/devboard/safety/safety.cpp
@@ -13,6 +13,8 @@ static bool battery_empty_event_fired = false;
#define MAX_SOH_DEVIATION_PPTT 2500
#define CELL_CRITICAL_MV 100 // If cells go this much outside design voltage, shut battery down!
+#define LOWEST_ALLOWED_CELLVOLTAGE_RECOVERY_CHARGE_MV 2000 //If cells are below this, recovery charge not allowed
+#define MAX_CHARGEPOWER_RECOVERY_CHARGE_DA 50
//battery pause status begin
bool emulator_pause_request_ON = false;
@@ -38,6 +40,12 @@ void update_machineryprotection() {
clear_event(EVENT_CPU_OVERHEATED); //Hysteresis on the clearing
}
+ if (datalayer.system.info.CPU_free_heap < 62000) {
+ set_event(EVENT_LOW_HEAP_MEMORY, (datalayer.system.info.CPU_free_heap / 1000));
+ } else {
+ clear_event(EVENT_LOW_HEAP_MEMORY);
+ }
+
// Check health status of CAN interfaces
if (datalayer.system.info.can_native_send_fail) {
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);
@@ -278,7 +286,8 @@ void update_machineryprotection() {
}
// Check diff between highest and lowest cell
- cell_deviation_mV = (datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV);
+ cell_deviation_mV =
+ std::abs(datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
@@ -303,6 +312,66 @@ void update_machineryprotection() {
}
}
+ // Additional Triple-Battery safeties are checked here
+ if (battery3) {
+ // Check if the Battery 3 BMS is still sending CAN messages. If we go 60s without messages we raise a warning
+
+ // Pause function is on
+ if (emulator_pause_request_ON) {
+ datalayer.battery3.status.max_discharge_power_W = 0;
+ datalayer.battery3.status.max_charge_power_W = 0;
+ }
+
+ if (!datalayer.battery3.status.CAN_battery_still_alive) {
+ set_event(EVENT_CAN_BATTERY3_MISSING, can_config.battery_triple);
+ } else {
+ datalayer.battery3.status.CAN_battery_still_alive--;
+ clear_event(EVENT_CAN_BATTERY3_MISSING);
+ }
+
+ // Too many malformed CAN messages recieved!
+ if (datalayer.battery3.status.CAN_error_counter > MAX_CAN_FAILURES) {
+ set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery_triple);
+ } else {
+ clear_event(EVENT_CAN_CORRUPTED_WARNING);
+ }
+
+ // Cell overvoltage, critical latching error without automatic reset. Requires user action.
+ if (datalayer.battery3.status.cell_max_voltage_mV >= datalayer.battery3.info.max_cell_voltage_mV) {
+ set_event(EVENT_CELL_OVER_VOLTAGE, 0);
+ }
+ // Cell undervoltage, critical latching error without automatic reset. Requires user action.
+ if (datalayer.battery3.status.cell_min_voltage_mV <= datalayer.battery3.info.min_cell_voltage_mV) {
+ set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
+ }
+
+ // Check diff between highest and lowest cell
+ cell_deviation_mV =
+ std::abs(datalayer.battery3.status.cell_max_voltage_mV - datalayer.battery3.status.cell_min_voltage_mV);
+ if (cell_deviation_mV > datalayer.battery3.info.max_cell_voltage_deviation_mV) {
+ set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
+ } else {
+ clear_event(EVENT_CELL_DEVIATION_HIGH);
+ }
+
+ // Check if SOH% between the packs is too large
+ if ((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery3.status.soh_pptt != 9900)) {
+ // Both values available, check diff
+ uint16_t soh_diff_pptt;
+ if (datalayer.battery.status.soh_pptt > datalayer.battery3.status.soh_pptt) {
+ soh_diff_pptt = datalayer.battery.status.soh_pptt - datalayer.battery3.status.soh_pptt;
+ } else {
+ soh_diff_pptt = datalayer.battery3.status.soh_pptt - datalayer.battery.status.soh_pptt;
+ }
+
+ if (soh_diff_pptt > MAX_SOH_DEVIATION_PPTT) {
+ set_event(EVENT_SOH_DIFFERENCE, (uint8_t)(MAX_SOH_DEVIATION_PPTT / 100));
+ } else {
+ clear_event(EVENT_SOH_DIFFERENCE);
+ }
+ }
+ }
+
//Safeties verified, Zero charge/discharge ampere values incase any safety wrote the W to 0
if (datalayer.battery.status.max_discharge_power_W == 0) {
datalayer.battery.status.max_discharge_current_dA = 0;
@@ -310,6 +379,40 @@ void update_machineryprotection() {
if (datalayer.battery.status.max_charge_power_W == 0) {
datalayer.battery.status.max_charge_current_dA = 0;
}
+ //One exception. If user has enabled the emergency recovery charge mode, still allow small amount of charging
+ if (datalayer.battery.settings.user_requests_forced_charging_recovery_mode) {
+
+ //We allow the user set value as long as it does not exceed MAX_CHARGEPOWER_RECOVERY_CHARGE_DA
+ if (datalayer.battery.settings.max_user_set_charge_dA > MAX_CHARGEPOWER_RECOVERY_CHARGE_DA) {
+ datalayer.battery.status.max_charge_current_dA = MAX_CHARGEPOWER_RECOVERY_CHARGE_DA;
+ } else {
+ datalayer.battery.status.max_charge_current_dA = datalayer.battery.settings.max_user_set_charge_dA;
+ }
+
+ // If this is the start of the emergency recovery charge period, capture the current time
+ if (datalayer.battery.settings.recovery_charge_start_time_ms == 0) {
+ datalayer.battery.settings.recovery_charge_start_time_ms = millis();
+ set_event(EVENT_RECOVERY_START, 0);
+ } else {
+ clear_event(EVENT_RECOVERY_START);
+ }
+
+ // Check if the elapsed time exceeds the max recovery charge time
+ if (millis() - datalayer.battery.settings.recovery_charge_start_time_ms >=
+ datalayer.battery.settings.recovery_charge_max_time_ms) {
+ datalayer.battery.settings.user_requests_forced_charging_recovery_mode = false;
+ datalayer.battery.settings.recovery_charge_start_time_ms = 0; // Reset the start time
+ set_event(EVENT_RECOVERY_END, 0);
+ } else {
+ clear_event(EVENT_RECOVERY_END);
+ }
+
+ //Check if cellvoltage is too low to safely start recovery. If so, abort!
+ if (datalayer.battery.status.cell_min_voltage_mV < LOWEST_ALLOWED_CELLVOLTAGE_RECOVERY_CHARGE_MV) {
+ datalayer.battery.settings.user_requests_forced_charging_recovery_mode = false;
+ set_event(EVENT_RECOVERY_END, 255);
+ }
+ }
//Decrement the forced balancing timer incase user requested it
if (datalayer.battery.settings.user_requests_balancing) {
@@ -322,7 +425,8 @@ void update_machineryprotection() {
}
// Check if the elapsed time exceeds the balancing time
- if (millis() - datalayer.battery.settings.balancing_start_time_ms >= datalayer.battery.settings.balancing_time_ms) {
+ if (millis() - datalayer.battery.settings.balancing_start_time_ms >=
+ datalayer.battery.settings.balancing_max_time_ms) {
datalayer.battery.settings.user_requests_balancing = false;
datalayer.battery.settings.balancing_start_time_ms = 0; // Reset the start time
set_event(EVENT_BALANCING_END, 0);
@@ -337,15 +441,15 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo
DEBUG_PRINTF("Battery pause begin %d %d %d %d\n", pause_battery, pause_CAN, equipment_stop, store_settings);
// First handle equipment stop / resume
- if (equipment_stop && !datalayer.system.settings.equipment_stop_active) {
- datalayer.system.settings.equipment_stop_active = true;
+ if (equipment_stop && !datalayer.system.info.equipment_stop_active) {
+ datalayer.system.info.equipment_stop_active = true;
if (store_settings) {
store_settings_equipment_stop();
}
set_event(EVENT_EQUIPMENT_STOP, 1);
- } else if (!equipment_stop && datalayer.system.settings.equipment_stop_active) {
- datalayer.system.settings.equipment_stop_active = false;
+ } else if (!equipment_stop && datalayer.system.info.equipment_stop_active) {
+ datalayer.system.info.equipment_stop_active = false;
if (store_settings) {
store_settings_equipment_stop();
}
@@ -365,6 +469,10 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
}
+ if (battery3) {
+ datalayer.battery3.status.max_discharge_power_W = 0;
+ datalayer.battery3.status.max_charge_power_W = 0;
+ }
} else {
clear_event(EVENT_PAUSE_BEGIN);
diff --git a/Software/src/devboard/sdcard/sdcard.cpp b/Software/src/devboard/sdcard/sdcard.cpp
index 5dfddebd3..9799b9080 100644
--- a/Software/src/devboard/sdcard/sdcard.cpp
+++ b/Software/src/devboard/sdcard/sdcard.cpp
@@ -58,9 +58,9 @@ void add_can_frame_to_buffer(CAN_frame frame, frameDirection msgDir) {
unsigned long currentTime = millis();
static char messagestr_buffer[32];
- size_t size =
- snprintf(messagestr_buffer + size, sizeof(messagestr_buffer) - size, "(%lu.%03lu) %s %X [%u] ",
- currentTime / 1000, currentTime % 1000, (msgDir == MSG_RX ? "RX0" : "TX1"), frame.ID, frame.DLC);
+ size_t size = 0;
+ size = snprintf(messagestr_buffer + size, sizeof(messagestr_buffer) - size, "(%lu.%03lu) %s %lX [%u] ",
+ currentTime / 1000, currentTime % 1000, (msgDir == MSG_RX ? "RX0" : "TX1"), frame.ID, frame.DLC);
if (xRingbufferSend(can_bufferHandle, &messagestr_buffer, size, pdMS_TO_TICKS(2)) != pdTRUE) {
logging.println("Failed to send message to can ring buffer!");
@@ -173,6 +173,17 @@ void init_logging_buffers() {
}
}
+void deinit_logging_buffers() {
+ if ((!datalayer.system.info.CAN_SD_logging_active) && (!datalayer.system.info.CAN_SD_logging_active)) {
+ if (can_bufferHandle != NULL) {
+ vRingbufferDelete(can_bufferHandle);
+ }
+ if (log_bufferHandle != NULL) {
+ vRingbufferDelete(log_bufferHandle);
+ }
+ }
+}
+
bool init_sdcard() {
auto miso_pin = esp32hal->SD_MISO_PIN();
auto mosi_pin = esp32hal->SD_MOSI_PIN();
diff --git a/Software/src/devboard/sdcard/sdcard.h b/Software/src/devboard/sdcard/sdcard.h
index 745ab1bd0..4a479a893 100644
--- a/Software/src/devboard/sdcard/sdcard.h
+++ b/Software/src/devboard/sdcard/sdcard.h
@@ -10,6 +10,7 @@
#define LOG_FILE "/log.txt"
void init_logging_buffers();
+void deinit_logging_buffers();
bool init_sdcard();
void log_sdcard_details();
diff --git a/Software/src/devboard/utils/common_functions.cpp b/Software/src/devboard/utils/common_functions.cpp
new file mode 100644
index 000000000..4e303ea9d
--- /dev/null
+++ b/Software/src/devboard/utils/common_functions.cpp
@@ -0,0 +1,62 @@
+#include "common_functions.h"
+
+int16_t sign_extend_to_int16(uint16_t input, unsigned input_bit_width) {
+ // Sign-extend the value from the original bit width up to 16 bits. This
+ // ensures that twos-complement negative values are correctly interpreted.
+
+ // For example, -26 represented in 13 bits is 0x1FE6.
+ // After sign extension to 16 bits, it becomes 0xFFE6, which is -26 in 16-bit signed integer.
+
+ uint16_t mask = 1 << (input_bit_width - 1);
+ return (input ^ mask) - mask;
+}
+
+/* CRC tables for various integrations to call*/
+
+const uint8_t crc8_table_SAE_J1850_ZER0[256] = { //0x1D Poly,initial value 0x3F,Final XOR value varies
+ 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
+ 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
+ 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
+ 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
+ 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
+ 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
+ 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
+ 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
+ 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
+ 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
+ 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
+ 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
+ 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
+ 0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
+
+const uint8_t crctable_nissan_leaf[256] = {
+ 0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
+ 247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
+ 107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
+ 9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
+ 214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
+ 98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
+ 18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
+ 86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
+ 41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
+ 27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
+ 196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
+ 32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
+
+const uint8_t crctable_geely_geometryC[256] =
+ { // CRC8_SAE_J1850_ZER0 formula,0x2F Poly,initial value 0xFF,Final XOR value 0xFF
+ 0x00, 0x2F, 0x5E, 0x71, 0xBC, 0x93, 0xE2, 0xCD, 0x57, 0x78, 0x09, 0x26, 0xEB, 0xC4, 0xB5, 0x9A, 0xAE, 0x81,
+ 0xF0, 0xDF, 0x12, 0x3D, 0x4C, 0x63, 0xF9, 0xD6, 0xA7, 0x88, 0x45, 0x6A, 0x1B, 0x34, 0x73, 0x5C, 0x2D, 0x02,
+ 0xCF, 0xE0, 0x91, 0xBE, 0x24, 0x0B, 0x7A, 0x55, 0x98, 0xB7, 0xC6, 0xE9, 0xDD, 0xF2, 0x83, 0xAC, 0x61, 0x4E,
+ 0x3F, 0x10, 0x8A, 0xA5, 0xD4, 0xFB, 0x36, 0x19, 0x68, 0x47, 0xE6, 0xC9, 0xB8, 0x97, 0x5A, 0x75, 0x04, 0x2B,
+ 0xB1, 0x9E, 0xEF, 0xC0, 0x0D, 0x22, 0x53, 0x7C, 0x48, 0x67, 0x16, 0x39, 0xF4, 0xDB, 0xAA, 0x85, 0x1F, 0x30,
+ 0x41, 0x6E, 0xA3, 0x8C, 0xFD, 0xD2, 0x95, 0xBA, 0xCB, 0xE4, 0x29, 0x06, 0x77, 0x58, 0xC2, 0xED, 0x9C, 0xB3,
+ 0x7E, 0x51, 0x20, 0x0F, 0x3B, 0x14, 0x65, 0x4A, 0x87, 0xA8, 0xD9, 0xF6, 0x6C, 0x43, 0x32, 0x1D, 0xD0, 0xFF,
+ 0x8E, 0xA1, 0xE3, 0xCC, 0xBD, 0x92, 0x5F, 0x70, 0x01, 0x2E, 0xB4, 0x9B, 0xEA, 0xC5, 0x08, 0x27, 0x56, 0x79,
+ 0x4D, 0x62, 0x13, 0x3C, 0xF1, 0xDE, 0xAF, 0x80, 0x1A, 0x35, 0x44, 0x6B, 0xA6, 0x89, 0xF8, 0xD7, 0x90, 0xBF,
+ 0xCE, 0xE1, 0x2C, 0x03, 0x72, 0x5D, 0xC7, 0xE8, 0x99, 0xB6, 0x7B, 0x54, 0x25, 0x0A, 0x3E, 0x11, 0x60, 0x4F,
+ 0x82, 0xAD, 0xDC, 0xF3, 0x69, 0x46, 0x37, 0x18, 0xD5, 0xFA, 0x8B, 0xA4, 0x05, 0x2A, 0x5B, 0x74, 0xB9, 0x96,
+ 0xE7, 0xC8, 0x52, 0x7D, 0x0C, 0x23, 0xEE, 0xC1, 0xB0, 0x9F, 0xAB, 0x84, 0xF5, 0xDA, 0x17, 0x38, 0x49, 0x66,
+ 0xFC, 0xD3, 0xA2, 0x8D, 0x40, 0x6F, 0x1E, 0x31, 0x76, 0x59, 0x28, 0x07, 0xCA, 0xE5, 0x94, 0xBB, 0x21, 0x0E,
+ 0x7F, 0x50, 0x9D, 0xB2, 0xC3, 0xEC, 0xD8, 0xF7, 0x86, 0xA9, 0x64, 0x4B, 0x3A, 0x15, 0x8F, 0xA0, 0xD1, 0xFE,
+ 0x33, 0x1C, 0x6D, 0x42};
diff --git a/Software/src/devboard/utils/common_functions.h b/Software/src/devboard/utils/common_functions.h
new file mode 100644
index 000000000..5d89c604f
--- /dev/null
+++ b/Software/src/devboard/utils/common_functions.h
@@ -0,0 +1,15 @@
+#include
+
+/**
+ * @brief Sign-extend the value from the original bit width up to 16 bits. This ensures that twos-complement negative values are correctly interpreted.
+ *
+ * @param[in]
+ *
+ * @return int16_t Extended int16_t value
+ *
+ */
+extern int16_t sign_extend_to_int16(uint16_t input, unsigned input_bit_width);
+
+extern const uint8_t crc8_table_SAE_J1850_ZER0[256];
+extern const uint8_t crctable_nissan_leaf[256];
+extern const uint8_t crctable_geely_geometryC[256];
diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp
index 513426e54..eace52b8e 100644
--- a/Software/src/devboard/utils/events.cpp
+++ b/Software/src/devboard/utils/events.cpp
@@ -29,15 +29,17 @@ void init_events(void) {
events.entries[i].MQTTpublished = false; // Not published by default
}
- events.entries[EVENT_CANMCP2517FD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
+ events.entries[EVENT_CANMCP2518FD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANMCP2515_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANFD_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_TASK_OVERRUN].level = EVENT_LEVEL_INFO;
+ events.entries[EVENT_THERMAL_RUNAWAY].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_CORRUPTED_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_BATTERY_MISSING].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_BATTERY2_MISSING].level = EVENT_LEVEL_WARNING;
+ events.entries[EVENT_CAN_BATTERY3_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
@@ -74,12 +76,13 @@ void init_events(void) {
events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
+ events.entries[EVENT_LOW_HEAP_MEMORY].level = EVENT_LEVEL_INFO;
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
events.entries[EVENT_AUTOMATIC_PRECHARGE_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_INTERFACE_MISSING].level = EVENT_LEVEL_INFO;
- events.entries[EVENT_MODBUS_INVERTER_MISSING].level = EVENT_LEVEL_INFO;
+ events.entries[EVENT_MODBUS_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_NO_ENABLE_DETECTED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_ERROR_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CELL_CRITICAL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
@@ -100,6 +103,8 @@ 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_SMA_PAIRING].level = EVENT_LEVEL_INFO;
+ events.entries[EVENT_RECOVERY_START].level = EVENT_LEVEL_WARNING;
+ events.entries[EVENT_RECOVERY_END].level = EVENT_LEVEL_WARNING;
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;
@@ -169,7 +174,7 @@ void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) {
String get_event_message_string(EVENTS_ENUM_TYPE event) {
switch (event) {
- case EVENT_CANMCP2517FD_INIT_FAILURE:
+ case EVENT_CANMCP2518FD_INIT_FAILURE:
return "CAN-FD initialization failed. Check hardware or bitrate settings";
case EVENT_CANMCP2515_INIT_FAILURE:
return "CAN-MCP addon initialization failed. Check hardware";
@@ -179,6 +184,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
return "MCP2515 message failed to send. Buffer full or no one on the bus to ACK the message!";
case EVENT_TASK_OVERRUN:
return "Task took too long to complete. CPU load might be too high. Info message, no action required.";
+ case EVENT_THERMAL_RUNAWAY:
+ return "THERMAL RUNAWAY! POTENTIAL FIRE OR EXPLOSION IMMINENT!";
case EVENT_CAN_CORRUPTED_WARNING:
return "High amount of corrupted CAN messages detected. Check CAN wire shielding!";
case EVENT_CAN_NATIVE_TX_FAILURE:
@@ -187,6 +194,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Battery not sending messages via CAN for the last 60 seconds. Check wiring!";
case EVENT_CAN_BATTERY2_MISSING:
return "Secondary battery not sending messages via CAN for the last 60 seconds. Check wiring!";
+ case EVENT_CAN_BATTERY3_MISSING:
+ return "Third battery not sending messages via CAN for the last 60 seconds. Check wiring!";
case EVENT_CAN_CHARGER_MISSING:
return "Charger not sending messages via CAN for the last 60 seconds. Check wiring!";
case EVENT_CAN_INVERTER_MISSING:
@@ -265,6 +274,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_HVIL_FAILURE:
return "Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. "
"Battery will be disabled!";
+ case EVENT_LOW_HEAP_MEMORY:
+ return "Memory almost full. Inform developers.";
case EVENT_PRECHARGE_FAILURE:
return "Battery failed to precharge. Check that capacitor is seated on high voltage output.";
case EVENT_AUTOMATIC_PRECHARGE_FAILURE:
@@ -318,6 +329,10 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
return "OTA update started!";
case EVENT_OTA_UPDATE_TIMEOUT:
return "OTA update timed out!";
+ case EVENT_RECOVERY_START:
+ return "CAUTION! Emergency low charge recovery started! Make sure battery cells do not overheat!";
+ case EVENT_RECOVERY_END:
+ return "Emergency charge recovery max time reached. Reboot and inspect if battery is able to continue normally";
case EVENT_RESET_UNKNOWN:
return "The board was reset unexpectedly, and reason can't be determined";
case EVENT_RESET_POWERON:
@@ -373,6 +388,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
case EVENT_PERIODIC_BMS_RESET:
return "BMS reset event completed.";
+ case EVENT_PERIODIC_BMS_RESET_FAILURE:
+ return "BMS reset aborted - contactors were still under load.";
case EVENT_BMS_RESET_REQ_SUCCESS:
return "BMS reset request completed successfully.";
case EVENT_BMS_RESET_REQ_FAIL:
@@ -472,7 +489,12 @@ static void update_bms_status(void) {
datalayer.battery.status.bms_status = UPDATING;
break;
case EVENT_LEVEL_ERROR:
- datalayer.battery.status.bms_status = FAULT;
+ // Normally FAULT mode is set if a catastrophic event has triggered, but incase user has forced a recovery charge, we override any FAULT and continue temporarily in active mode
+ if (datalayer.battery.settings.user_requests_forced_charging_recovery_mode) {
+ datalayer.battery.status.bms_status = ACTIVE; //Edge case which is active for 30min max
+ } else {
+ datalayer.battery.status.bms_status = FAULT; //We will in 99.999% of the time go here
+ }
break;
default:
break;
diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h
index 3f4090be9..b1557043a 100644
--- a/Software/src/devboard/utils/events.h
+++ b/Software/src/devboard/utils/events.h
@@ -10,13 +10,14 @@
#define GENERATE_STRING(STRING) #STRING,
#define EVENTS_ENUM_TYPE(XX) \
- XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
+ XX(EVENT_CANMCP2518FD_INIT_FAILURE) \
XX(EVENT_CANMCP2515_INIT_FAILURE) \
XX(EVENT_CANFD_BUFFER_FULL) \
XX(EVENT_CAN_BUFFER_FULL) \
XX(EVENT_CAN_CORRUPTED_WARNING) \
XX(EVENT_CAN_BATTERY_MISSING) \
XX(EVENT_CAN_BATTERY2_MISSING) \
+ XX(EVENT_CAN_BATTERY3_MISSING) \
XX(EVENT_CAN_CHARGER_MISSING) \
XX(EVENT_CAN_INVERTER_MISSING) \
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
@@ -56,6 +57,7 @@
XX(EVENT_SOH_DIFFERENCE) \
XX(EVENT_SOH_LOW) \
XX(EVENT_HVIL_FAILURE) \
+ XX(EVENT_LOW_HEAP_MEMORY) \
XX(EVENT_PRECHARGE_FAILURE) \
XX(EVENT_INTERNAL_OPEN_FAULT) \
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
@@ -82,6 +84,9 @@
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
XX(EVENT_SMA_PAIRING) \
XX(EVENT_TASK_OVERRUN) \
+ XX(EVENT_THERMAL_RUNAWAY) \
+ XX(EVENT_RECOVERY_START) \
+ XX(EVENT_RECOVERY_END) \
XX(EVENT_RESET_UNKNOWN) \
XX(EVENT_RESET_POWERON) \
XX(EVENT_RESET_EXT) \
@@ -110,6 +115,7 @@
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
XX(EVENT_SD_INIT_FAILED) \
XX(EVENT_PERIODIC_BMS_RESET) \
+ XX(EVENT_PERIODIC_BMS_RESET_FAILURE) \
XX(EVENT_BMS_RESET_REQ_SUCCESS) \
XX(EVENT_BMS_RESET_REQ_FAIL) \
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
diff --git a/Software/src/devboard/utils/led_handler.cpp b/Software/src/devboard/utils/led_handler.cpp
index babcb0c50..30896b566 100644
--- a/Software/src/devboard/utils/led_handler.cpp
+++ b/Software/src/devboard/utils/led_handler.cpp
@@ -9,15 +9,20 @@
#define COLOR_RED(x) (((uint32_t)x << 16) | ((uint32_t)0 << 8) | 0)
#define COLOR_BLUE(x) (((uint32_t)0 << 16) | ((uint32_t)0 << 8) | x)
-#define BPM_TO_MS(x) ((uint16_t)((60.0f / ((float)x)) * 1000))
+#define BPM_TO_MS(x) ((60000 / (x))) // 60 * 1000 = 60000
-static const float heartbeat_base = 0.15;
-static const float heartbeat_peak1 = 0.80;
-static const float heartbeat_peak2 = 0.55;
-static const float heartbeat_deviation = 0.05;
+#define HEARTBEAT_BASE 150 // 0.15 * 1000
+#define HEARTBEAT_PEAK1 800 // 0.80 * 1000
+#define HEARTBEAT_PEAK2 550 // 0.55 * 1000
+#define HEARTBEAT_DEVIATION 50 // 0.05 * 1000
+#define SCALE_FACTOR 1000
static LED* led;
+uint16_t map_int(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) {
+ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
+}
+
bool led_init(void) {
if (!esp32hal->alloc_pins("LED", esp32hal->LED_PIN())) {
DEBUG_PRINTF("LED setup failed\n");
@@ -34,7 +39,6 @@ void led_exe(void) {
}
void LED::exe(void) {
-
// Update brightness
switch (datalayer.battery.status.led_mode) {
case led_mode_enum::FLOW:
@@ -70,19 +74,19 @@ void LED::exe(void) {
void LED::classic_run(void) {
// Determine how bright the LED should be
- brightness = up_down(0.5);
+ brightness = up_down(500);
}
void LED::flow_run(void) {
// Determine how bright the LED should be
if (datalayer.battery.status.active_power_W < -50) {
// Discharging
- brightness = max_brightness - up_down(0.95);
+ brightness = max_brightness - up_down(950);
} else if (datalayer.battery.status.active_power_W > 50) {
// Charging
- brightness = up_down(0.95);
- } else {
- brightness = up_down(0.5);
+ brightness = up_down(950);
+ } else { // Idle
+ brightness = up_down(500);
}
}
@@ -90,54 +94,56 @@ void LED::heartbeat_run(void) {
uint16_t period;
switch (get_event_level()) {
case EVENT_LEVEL_WARNING:
- // phew, starting to sweat here
period = BPM_TO_MS(70);
break;
case EVENT_LEVEL_ERROR:
- // omg omg OMG OMGG
period = BPM_TO_MS(100);
break;
default:
- // Keep default chill bpm (ba-dunk... ba-dunk... ba-dunk...)
period = BPM_TO_MS(35);
break;
}
- uint16_t ms = (uint16_t)(millis() % period);
-
- float period_pct = ((float)ms) / period;
- float brightness_f;
-
- if (period_pct < 0.10) {
- brightness_f = map_float(period_pct, 0.00f, 0.10f, heartbeat_base, heartbeat_base - heartbeat_deviation);
- } else if (period_pct < 0.20) {
- brightness_f = map_float(period_pct, 0.10f, 0.20f, heartbeat_base - heartbeat_deviation,
- heartbeat_base - heartbeat_deviation * 2);
- } else if (period_pct < 0.25) {
- brightness_f = map_float(period_pct, 0.20f, 0.25f, heartbeat_base - heartbeat_deviation * 2, heartbeat_peak1);
- } else if (period_pct < 0.30) {
- brightness_f = map_float(period_pct, 0.25f, 0.30f, heartbeat_peak1, heartbeat_base - heartbeat_deviation);
- } else if (period_pct < 0.40) {
- brightness_f = map_float(period_pct, 0.30f, 0.40f, heartbeat_base - heartbeat_deviation, heartbeat_peak2);
- } else if (period_pct < 0.55) {
- brightness_f = map_float(period_pct, 0.40f, 0.55f, heartbeat_peak2, heartbeat_base + heartbeat_deviation * 2);
+ uint16_t ms = millis() % period;
+
+ // Calculate percentage as integer (0-1000 represents 0.0-1.0)
+ uint16_t period_pct = (ms * SCALE_FACTOR) / period;
+ uint16_t brightness_scaled;
+
+ if (period_pct < 100) { // 0.10 * 1000
+ brightness_scaled = map_int(period_pct, 0, 100, HEARTBEAT_BASE, HEARTBEAT_BASE - HEARTBEAT_DEVIATION);
+ } else if (period_pct < 200) { // 0.20 * 1000
+ brightness_scaled =
+ map_int(period_pct, 100, 200, HEARTBEAT_BASE - HEARTBEAT_DEVIATION, HEARTBEAT_BASE - HEARTBEAT_DEVIATION * 2);
+ } else if (period_pct < 250) { // 0.25 * 1000
+ brightness_scaled = map_int(period_pct, 200, 250, HEARTBEAT_BASE - HEARTBEAT_DEVIATION * 2, HEARTBEAT_PEAK1);
+ } else if (period_pct < 300) { // 0.30 * 1000
+ brightness_scaled = map_int(period_pct, 250, 300, HEARTBEAT_PEAK1, HEARTBEAT_BASE - HEARTBEAT_DEVIATION);
+ } else if (period_pct < 400) { // 0.40 * 1000
+ brightness_scaled = map_int(period_pct, 300, 400, HEARTBEAT_BASE - HEARTBEAT_DEVIATION, HEARTBEAT_PEAK2);
+ } else if (period_pct < 550) { // 0.55 * 1000
+ brightness_scaled = map_int(period_pct, 400, 550, HEARTBEAT_PEAK2, HEARTBEAT_BASE + HEARTBEAT_DEVIATION * 2);
} else {
- brightness_f = map_float(period_pct, 0.55f, 1.00f, heartbeat_base + heartbeat_deviation * 2, heartbeat_base);
+ brightness_scaled =
+ map_int(period_pct, 550, SCALE_FACTOR, HEARTBEAT_BASE + HEARTBEAT_DEVIATION * 2, HEARTBEAT_BASE);
}
- brightness = (uint8_t)(brightness_f * esp32hal->LED_MAX_BRIGHTNESS());
+ // Convert scaled brightness (0-1000) to actual brightness (0-255)
+ brightness = (brightness_scaled * max_brightness) / SCALE_FACTOR;
}
-uint8_t LED::up_down(float middle_point_f) {
- // Determine how bright the LED should be
- middle_point_f = CONSTRAIN(middle_point_f, 0.0f, 1.0f);
- uint16_t middle_point = (uint16_t)(middle_point_f * LED_PERIOD_MS);
- uint16_t ms = (uint16_t)(millis() % LED_PERIOD_MS);
- brightness = map_uint16(ms, 0, middle_point, 0, max_brightness);
+uint8_t LED::up_down(uint16_t middle_point_scaled) {
+ // Convert scaled middle point to actual milliseconds
+ uint16_t middle_point = (middle_point_scaled * LED_PERIOD_MS) / SCALE_FACTOR;
+ middle_point = CONSTRAIN(middle_point, 0, LED_PERIOD_MS);
+
+ uint16_t ms = millis() % LED_PERIOD_MS;
+
if (ms < middle_point) {
brightness = map_uint16(ms, 0, middle_point, 0, max_brightness);
} else {
- brightness = esp32hal->LED_MAX_BRIGHTNESS() - map_uint16(ms, middle_point, LED_PERIOD_MS, 0, max_brightness);
+ brightness = max_brightness - map_uint16(ms, middle_point, LED_PERIOD_MS, 0, max_brightness);
}
+
return CONSTRAIN(brightness, 0, max_brightness);
}
diff --git a/Software/src/devboard/utils/led_handler.h b/Software/src/devboard/utils/led_handler.h
index 202cf65a3..aeca176e1 100644
--- a/Software/src/devboard/utils/led_handler.h
+++ b/Software/src/devboard/utils/led_handler.h
@@ -24,8 +24,8 @@ class LED {
void flow_run(void);
void heartbeat_run(void);
- uint8_t up_down(float middle_point_f);
- int LED_PERIOD_MS = 3000;
+ uint8_t up_down(uint16_t middle_point_f);
+ uint16_t LED_PERIOD_MS = 3000;
};
bool led_init(void);
diff --git a/Software/src/devboard/utils/logging.cpp b/Software/src/devboard/utils/logging.cpp
index 2b4b1d30a..191c119e8 100644
--- a/Software/src/devboard/utils/logging.cpp
+++ b/Software/src/devboard/utils/logging.cpp
@@ -13,7 +13,6 @@ void Logging::add_timestamp(size_t size) {
return;
}
- char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
unsigned long currentTime = millis();
@@ -118,7 +117,7 @@ void Logging::printf(const char* fmt, ...) {
message_buffer = buffer;
}
- va_list(args);
+ va_list args;
va_start(args, fmt);
int size = min(MAX_LINE_LENGTH_PRINTF - 1, vsnprintf(message_buffer, MAX_LINE_LENGTH_PRINTF, fmt, args));
va_end(args);
diff --git a/Software/src/devboard/utils/time_meas.h b/Software/src/devboard/utils/time_meas.h
index aeac69dad..58b7ed3da 100644
--- a/Software/src/devboard/utils/time_meas.h
+++ b/Software/src/devboard/utils/time_meas.h
@@ -6,7 +6,7 @@
/** Start time measurement in microseconds
* Input parameter must be a unique "tag", e.g: START_TIME_MEASUREMENT(wifi);
*/
-#define START_TIME_MEASUREMENT(x) int64_t start_time_##x = esp_timer_get_time()
+#define START_TIME_MEASUREMENT(x) int64_t start_time_##x __attribute__((unused)) = esp_timer_get_time()
/** End time measurement in microseconds
* Input parameters are the unique tag and the name of the ALREADY EXISTING
* destination variable (int64_t), e.g: END_TIME_MEASUREMENT(wifi, my_wifi_time_int64_t);
diff --git a/Software/src/devboard/utils/types.cpp b/Software/src/devboard/utils/types.cpp
index c354ab67d..18cea11d7 100644
--- a/Software/src/devboard/utils/types.cpp
+++ b/Software/src/devboard/utils/types.cpp
@@ -19,3 +19,9 @@ std::string getBMSStatus(bms_status_enum status) {
return "UNKNOWN";
}
}
+#ifdef HW_LILYGO2CAN
+GPIOOPT1 user_selected_gpioopt1 = GPIOOPT1::DEFAULT_OPT;
+#endif
+GPIOOPT2 user_selected_gpioopt2 = GPIOOPT2::DEFAULT_OPT_BMS_POWER_18;
+GPIOOPT3 user_selected_gpioopt3 = GPIOOPT3::DEFAULT_SMA_ENABLE_05;
+GPIOOPT4 user_selected_gpioopt4 = GPIOOPT4::DEFAULT_SD_CARD;
diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h
index fab3d4d97..c847fe6ed 100644
--- a/Software/src/devboard/utils/types.h
+++ b/Software/src/devboard/utils/types.h
@@ -9,7 +9,13 @@ using duration = std::chrono::duration>;
enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 };
enum real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 };
-enum battery_chemistry_enum { Autodetect = 0, NCA = 1, NMC = 2, LFP = 3, Highest };
+enum balancing_status_enum {
+ BALANCING_STATUS_UNKNOWN = 0,
+ BALANCING_STATUS_ERROR = 1,
+ BALANCING_STATUS_READY = 2, //No balancing active, system supports balancing
+ BALANCING_STATUS_ACTIVE = 3 //Balancing active!
+};
+enum battery_chemistry_enum { Autodetect = 0, NCA = 1, NMC = 2, LFP = 3, ZEBRA = 4, Highest };
enum class comm_interface {
Modbus = 1,
@@ -30,6 +36,12 @@ enum PrechargeState {
AUTO_PRECHARGE_COMPLETED,
AUTO_PRECHARGE_FAILURE
};
+enum BMSResetState {
+ BMS_RESET_IDLE = 0,
+ BMS_RESET_WAITING_FOR_PAUSE,
+ BMS_RESET_POWERED_OFF,
+ BMS_RESET_POWERING_ON,
+};
#define DISCHARGING 1
#define CHARGING 2
@@ -39,6 +51,7 @@ enum PrechargeState {
#define INTERVAL_30_MS 30
#define INTERVAL_40_MS 40
#define INTERVAL_50_MS 50
+#define INTERVAL_60_MS 60
#define INTERVAL_70_MS 70
#define INTERVAL_100_MS 100
#define INTERVAL_200_MS 200
@@ -68,7 +81,10 @@ enum CAN_Interface {
CAN_ADDON_MCP2515 = 2,
// Add-on CAN-FD MCP2518 connected to GPIO pins
- CANFD_ADDON_MCP2518 = 3
+ CANFD_ADDON_MCP2518 = 3,
+
+ // No CAN interface
+ NO_CAN_INTERFACE = 4
};
extern const char* getCANInterfaceName(CAN_Interface interface);
@@ -95,4 +111,43 @@ typedef struct {
std::string getBMSStatus(bms_status_enum status);
+#ifdef HW_LILYGO2CAN
+/* Configurable GPIO options (device specific) */
+enum class GPIOOPT1 {
+ // T-2CAN: WUP1/WUP2 on GPIO1/GPIO2
+ DEFAULT_OPT = 0,
+ // T-2CAN: SDA/SCL on GPIO1/GPIO2
+ I2C_DISPLAY_SSD1306 = 1,
+ // T-2CAN: ESTOP on GPIO1, BMS_POWER on GPIO2
+ ESTOP_BMS_POWER = 2,
+ Highest
+};
+extern GPIOOPT1 user_selected_gpioopt1;
+#endif
+enum class GPIOOPT2 {
+ // T-CAN485: Default, BMS power on PIN18
+ DEFAULT_OPT_BMS_POWER_18 = 0,
+ // T-CAN485: Move BMS power to PIN25
+ BMS_POWER_25 = 1,
+ Highest
+};
+enum class GPIOOPT3 {
+ // T-CAN485: Default, SMA inverter pin PIN5
+ DEFAULT_SMA_ENABLE_05 = 0,
+ // T-CAN485: Move SMA inverter pin to PIN33
+ SMA_ENABLE_33 = 1,
+ Highest
+};
+enum class GPIOOPT4 {
+ // T-CAN485: Default, uSD Card
+ DEFAULT_SD_CARD = 0,
+ // T-CAN485: Disable SD,Enable Display on Pins 14,15
+ I2C_DISPLAY_SSD1306 = 1,
+ Highest
+};
+
+extern GPIOOPT2 user_selected_gpioopt2;
+extern GPIOOPT3 user_selected_gpioopt3;
+extern GPIOOPT4 user_selected_gpioopt4;
+
#endif
diff --git a/Software/src/devboard/utils/watchdog.cpp b/Software/src/devboard/utils/watchdog.cpp
new file mode 100644
index 000000000..d33beee07
--- /dev/null
+++ b/Software/src/devboard/utils/watchdog.cpp
@@ -0,0 +1,17 @@
+#include "watchdog.h"
+#include "logging.h"
+
+#include
+
+void Watchdog::update() {
+ last_update_time_ms = millis();
+}
+
+void Watchdog::panic_if_exceeded_ms(uint32_t timeout_ms, const char* message) {
+ unsigned long current_time_ms = millis();
+ unsigned long last_time_ms = last_update_time_ms.load();
+ if (last_time_ms > 0 && (current_time_ms - last_time_ms) > timeout_ms) {
+ logging.println(message);
+ esp_system_abort(message);
+ }
+}
diff --git a/Software/src/devboard/utils/watchdog.h b/Software/src/devboard/utils/watchdog.h
new file mode 100644
index 000000000..5e476bec4
--- /dev/null
+++ b/Software/src/devboard/utils/watchdog.h
@@ -0,0 +1,14 @@
+#include
+
+class Watchdog {
+ public:
+ // Call this periodically to reset the watchdog timer. The timer is inactive
+ // until the first call.
+ void update();
+ // Panic if the time since the last update exceeds timeout_ms. If update has
+ // never been called, this does nothing.
+ void panic_if_exceeded_ms(uint32_t timeout_ms, const char* message);
+
+ private:
+ std::atomic last_update_time_ms = 0;
+};
diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp
index a03e19aa2..a5b899c66 100644
--- a/Software/src/devboard/webserver/advanced_battery_html.cpp
+++ b/Software/src/devboard/webserver/advanced_battery_html.cpp
@@ -13,10 +13,17 @@ std::vector battery_commands = {
[](Battery* b) { return b && b->supports_chademo_restart(); }, [](Battery* b) { b->chademo_restart(); }},
{"chademoStop", "Stop", "stop V2X?", [](Battery* b) { return b && b->supports_chademo_restart(); },
[](Battery* b) { b->chademo_restart(); }},
- {"resetBMS", "BMS reset", "reset the BMS?", [](Battery* b) { return b && b->supports_reset_BMS(); },
+ {"resetBMS", "BMS Reset", "Reset the BMS?", [](Battery* b) { return b && b->supports_reset_BMS(); },
[](Battery* b) { b->reset_BMS(); }},
- {"resetSOC", "SOC reset", "reset SOC?", [](Battery* b) { return b && b->supports_reset_SOC(); },
+ {"resetSOC", "SOC Reset", "Reset SOC?", [](Battery* b) { return b && b->supports_reset_SOC(); },
[](Battery* b) { b->reset_SOC(); }},
+ {"startOfflineBalancing", "Start Offline Balancing",
+ "continue? Please charge battery fully for this to work. After a couple of minutes, battery will sleep and do "
+ "balancing. It often takes many hours. There will be no progress indication.",
+ [](Battery* b) { return b && b->supports_offline_balancing(); },
+ [](Battery* b) { b->initiate_offline_balancing(); }},
+ {"endOfflineBalancing", "End Offline Balancing", "end offline balancing?",
+ [](Battery* b) { return b && b->supports_offline_balancing(); }, [](Battery* b) { b->end_offline_balancing(); }},
{"resetCrash", "Unlock crashed BMS",
"reset crash data? Note this will unlock your BMS and enable contactor closing and SOC calculation.",
[](Battery* b) { return b && b->supports_reset_crash(); }, [](Battery* b) { b->reset_crash(); }},
@@ -27,8 +34,8 @@ std::vector battery_commands = {
[](Battery* b) { return b && b->supports_contactor_reset(); }, [](Battery* b) { b->reset_contactor(); }},
{"resetDTC", "Erase DTC", "erase DTCs?", [](Battery* b) { return b && b->supports_reset_DTC(); },
[](Battery* b) { b->reset_DTC(); }},
- {"readDTC", "Read DTC (result must be checked in CANlog)", nullptr,
- [](Battery* b) { return b && b->supports_read_DTC(); }, [](Battery* b) { b->read_DTC(); }},
+ {"readDTC", "Read DTC", nullptr, [](Battery* b) { return b && b->supports_read_DTC(); },
+ [](Battery* b) { b->read_DTC(); }},
{"resetBECM", "Restart BECM module", "restart BECM??", [](Battery* b) { return b && b->supports_reset_BECM(); },
[](Battery* b) { b->reset_BECM(); }},
{"contactorClose", "Close Contactors", "a contactor close request?",
@@ -42,6 +49,9 @@ std::vector battery_commands = {
{"toggleSOC", "Toggle SOC method",
"toggle SOC method? This will toggle between ESTIMATED and MEASURED SOC methods.",
[](Battery* b) { return b && b->supports_toggle_SOC_method(); }, [](Battery* b) { b->toggle_SOC_method(); }},
+ {"resetEnergySavingMode", "Reset Energy Saving Mode", "reset energy saving mode to normal?",
+ [](Battery* b) { return b && b->supports_energy_saving_mode_reset(); },
+ [](Battery* b) { b->reset_energy_saving_mode(); }},
};
String advanced_battery_processor(const String& var) {
@@ -51,7 +61,7 @@ String advanced_battery_processor(const String& var) {
content += "";
+
+ // Configuration section
+ content += "";
+ content += "
CAN Logger Configuration ";
+ content += "
CAN ID Cutoff Filter: " + String(user_selected_CAN_ID_cutoff_filter) +
+ " Edit
";
content += "
Refresh data ";
content += "
Export to .txt ";
#ifdef LOG_CAN_TO_SD
content += "
Delete log file ";
#endif
content += "
Stop & Back to main page ";
+ content += "
";
// Start a new block for the CAN messages
content += "";
@@ -51,7 +60,7 @@ String can_logger_processor(void) {
content += "
";
- // Add JavaScript for navigation
+ // Add JavaScript for navigation and configuration
content += "";
content += index_html_footer;
return content;
diff --git a/Software/src/devboard/webserver/cellmonitor_html.cpp b/Software/src/devboard/webserver/cellmonitor_html.cpp
index 69404138a..36a76533b 100644
--- a/Software/src/devboard/webserver/cellmonitor_html.cpp
+++ b/Software/src/devboard/webserver/cellmonitor_html.cpp
@@ -6,7 +6,7 @@
String cellmonitor_processor(const String& var) {
if (var == "X") {
String content = "";
- // Page format
+ // Page formatH
content += "Event Type
Severity
Last Event
Count
Data
Message
+
Event Type
Severity
Last Event
Count
Data
Message
)=====";
const char EVENTS_HTML_END[] = R"=====(
diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp
index 7fe59edc2..c6b1142e4 100644
--- a/Software/src/devboard/webserver/settings_html.cpp
+++ b/Software/src/devboard/webserver/settings_html.cpp
@@ -9,6 +9,7 @@
#include "html_escape.h"
#include "index_html.h"
#include "src/battery/BATTERIES.h"
+#include "src/battery/Shunt.h"
#include "src/inverter/INVERTERS.h"
extern bool settingsUpdated;
@@ -111,6 +112,11 @@ static const std::map
tesla_chassis = {{0, "Model S"}, {1, "Model X
static const std::map tesla_pack = {{0, "50 kWh"}, {2, "62 kWh"}, {1, "74 kWh"}, {3, "100 kWh"}};
+static const std::map sungrow_models = {
+ {0, "SBR064 (6.4 kWh, 2 modules)"}, {1, "SBR096 (9.6 kWh, 3 modules)"}, {2, "SBR128 (12.8 kWh, 4 modules)"},
+ {3, "SBR160 (16.0 kWh, 5 modules)"}, {4, "SBR192 (19.2 kWh, 6 modules)"}, {5, "SBR224 (22.4 kWh, 7 modules)"},
+ {6, "SBR256 (25.6 kWh, 8 modules)"}};
+
const char* name_for_button_type(STOP_BUTTON_BEHAVIOR behavior) {
switch (behavior) {
case STOP_BUTTON_BEHAVIOR::LATCHING_SWITCH:
@@ -123,6 +129,51 @@ const char* name_for_button_type(STOP_BUTTON_BEHAVIOR behavior) {
return nullptr;
}
}
+#ifdef HW_LILYGO2CAN
+const char* name_for_gpioopt1(GPIOOPT1 option) {
+ switch (option) {
+ case GPIOOPT1::DEFAULT_OPT:
+ return "WUP1 / WUP2";
+ case GPIOOPT1::I2C_DISPLAY_SSD1306:
+ return "I2C Display (SSD1306)";
+ case GPIOOPT1::ESTOP_BMS_POWER:
+ return "E-Stop / BMS Power";
+ default:
+ return nullptr;
+ }
+}
+#endif
+const char* name_for_gpioopt2(GPIOOPT2 option) {
+ switch (option) {
+ case GPIOOPT2::DEFAULT_OPT_BMS_POWER_18:
+ return "Pin 18";
+ case GPIOOPT2::BMS_POWER_25:
+ return "Pin 25";
+ default:
+ return nullptr;
+ }
+}
+const char* name_for_gpioopt3(GPIOOPT3 option) {
+ switch (option) {
+ case GPIOOPT3::DEFAULT_SMA_ENABLE_05:
+ return "Pin 5";
+ case GPIOOPT3::SMA_ENABLE_33:
+ return "Pin 33";
+ default:
+ return nullptr;
+ }
+}
+
+const char* name_for_gpioopt4(GPIOOPT4 option) {
+ switch (option) {
+ case GPIOOPT4::DEFAULT_SD_CARD:
+ return "uSD Card";
+ case GPIOOPT4::I2C_DISPLAY_SSD1306:
+ return "I2C Display (SSD1306)";
+ default:
+ return nullptr;
+ }
+}
// Special unicode characters
const char* TRUE_CHAR_CODE = "\u2713"; //✓";
@@ -134,11 +185,6 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
// HTML-ready values (such as select options) are returned here. These don't
// get any additional escaping.
- if (var == "SHUNTCOMM") {
- return options_for_enum((comm_interface)settings.getUInt("SHUNTCOMM", (int)comm_interface::CanNative),
- name_for_comm_interface);
- }
-
if (var == "BATTTYPE") {
return options_for_enum_with_none((BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None),
name_for_battery_type, BatteryType::None);
@@ -191,6 +237,11 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
name_for_comm_interface);
}
+ if (var == "BATT3COMM") {
+ return options_for_enum((comm_interface)settings.getUInt("BATT3COMM", (int)comm_interface::CanNative),
+ name_for_comm_interface);
+ }
+
if (var == "GTWCOUNTRY") {
return options_from_map(settings.getUInt("GTWCOUNTRY", 0), tesla_countries);
}
@@ -211,6 +262,31 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
return options_from_map(settings.getUInt("LEDMODE", 0), led_modes);
}
+ if (var == "SUNGROW_MODEL") {
+ return options_from_map(settings.getUInt("INVSUNTYPE", 1), sungrow_models); // Default: SBR096
+ }
+
+#ifdef HW_LILYGO2CAN
+ if (var == "GPIOOPT1") {
+ return options_for_enum_with_none((GPIOOPT1)settings.getUInt("GPIOOPT1", (int)GPIOOPT1::DEFAULT_OPT),
+ name_for_gpioopt1, GPIOOPT1::DEFAULT_OPT);
+ }
+#endif
+ if (var == "GPIOOPT2") {
+ return options_for_enum_with_none((GPIOOPT2)settings.getUInt("GPIOOPT2", (int)GPIOOPT2::DEFAULT_OPT_BMS_POWER_18),
+ name_for_gpioopt2, GPIOOPT2::DEFAULT_OPT_BMS_POWER_18);
+ }
+
+ if (var == "GPIOOPT3") {
+ return options_for_enum_with_none((GPIOOPT3)settings.getUInt("GPIOOPT3", (int)GPIOOPT3::DEFAULT_SMA_ENABLE_05),
+ name_for_gpioopt3, GPIOOPT3::DEFAULT_SMA_ENABLE_05);
+ }
+
+ if (var == "GPIOOPT4") {
+ return options_for_enum_with_none((GPIOOPT4)settings.getUInt("GPIOOPT4", (int)GPIOOPT4::DEFAULT_SD_CARD),
+ name_for_gpioopt4, GPIOOPT4::DEFAULT_SD_CARD);
+ }
+
// All other values are wrapped by html_escape to avoid HTML injection.
return html_escape(raw_settings_processor(var, settings));
@@ -279,6 +355,12 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
}
}
+ if (var == "SHUNTINTF") {
+ if (shunt) {
+ return shunt->interface_name();
+ }
+ }
+
if (var == "SHUNTCLASS") {
if (!shunt) {
return "hidden";
@@ -295,6 +377,10 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
return settings.getBool("DBLBTR") ? "checked" : "";
}
+ if (var == "TRIBTR") {
+ return settings.getBool("TRIBTR") ? "checked" : "";
+ }
+
if (var == "SOCESTIMATED") {
return settings.getBool("SOCESTIMATED") ? "checked" : "";
}
@@ -303,10 +389,18 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
return settings.getBool("CNTCTRL") ? "checked" : "";
}
+ if (var == "NCCONTACTOR") {
+ return settings.getBool("NCCONTACTOR") ? "checked" : "";
+ }
+
if (var == "CNTCTRLDBL") {
return settings.getBool("CNTCTRLDBL") ? "checked" : "";
}
+ if (var == "CNTCTRLTRI") {
+ return settings.getBool("CNTCTRLTRI") ? "checked" : "";
+ }
+
if (var == "PWMCNTCTRL") {
return settings.getBool("PWMCNTCTRL") ? "checked" : "";
}
@@ -327,6 +421,10 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
return String(settings.getUInt("MAXPRETIME", 15000));
}
+ if (var == "MAXPREFREQ") {
+ return String(settings.getUInt("MAXPREFREQ", 34000));
+ }
+
if (var == "NOINVDISC") {
return settings.getBool("NOINVDISC") ? "checked" : "";
}
@@ -435,6 +533,10 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
return settings.getBool("SDLOGENABLED") ? "checked" : "";
}
+ if (var == "ESPNOWENABLED") {
+ return settings.getBool("ESPNOWENABLED") ? "checked" : "";
+ }
+
if (var == "MQTTENABLED") {
return settings.getBool("MQTTENABLED") ? "checked" : "";
}
@@ -467,6 +569,10 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
return String(settings.getUInt("MQTTTIMEOUT", 2000));
}
+ if (var == "MQTTPUBLISHMS") {
+ return String(settings.getUInt("MQTTPUBLISHMS", 5000) / 1000);
+ }
+
if (var == "MQTTOBJIDPREFIX") {
return settings.getString("MQTTOBJIDPREFIX");
}
@@ -496,11 +602,11 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
}
if (var == "BATTPVMAX") {
- return String(static_cast(settings.getUInt("BATTPVMAX", 0)) / 10.0, 1);
+ return String(static_cast(settings.getUInt("BATTPVMAX", 0)) / 10.0f, 1);
}
if (var == "BATTPVMIN") {
- return String(static_cast(settings.getUInt("BATTPVMIN", 0)) / 10.0, 1);
+ return String(static_cast(settings.getUInt("BATTPVMIN", 0)) / 10.0f, 1);
}
if (var == "BATTCVMAX") {
@@ -516,27 +622,27 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
}
if (var == "MAX_CHARGE_SPEED") {
- return String(datalayer.battery.settings.max_user_set_charge_dA / 10.0, 1);
+ return String(datalayer.battery.settings.max_user_set_charge_dA / 10.0f, 1);
}
if (var == "MAX_DISCHARGE_SPEED") {
- return String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1);
+ return String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0f, 1);
}
if (var == "SOC_MAX_PERCENTAGE") {
- return String(datalayer.battery.settings.max_percentage / 100.0, 1);
+ return String(datalayer.battery.settings.max_percentage / 100.0f, 1);
}
if (var == "SOC_MIN_PERCENTAGE") {
- return String(datalayer.battery.settings.min_percentage / 100.0, 1);
+ return String(datalayer.battery.settings.min_percentage / 100.0f, 1);
}
if (var == "CHARGE_VOLTAGE") {
- return String(datalayer.battery.settings.max_user_set_charge_voltage_dV / 10.0, 1);
+ return String(datalayer.battery.settings.max_user_set_charge_voltage_dV / 10.0f, 1);
}
if (var == "DISCHARGE_VOLTAGE") {
- return String(datalayer.battery.settings.max_user_set_discharge_voltage_dV / 10.0, 1);
+ return String(datalayer.battery.settings.max_user_set_discharge_voltage_dV / 10.0f, 1);
}
if (var == "SOC_SCALING_ACTIVE_CLASS") {
@@ -590,25 +696,25 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
}
if (var == "BALANCING_MAX_TIME") {
- return String(datalayer.battery.settings.balancing_time_ms / 60000.0, 1);
+ return String(datalayer.battery.settings.balancing_max_time_ms / 60000.0f, 1);
}
if (var == "BAL_POWER") {
- return String(datalayer.battery.settings.balancing_float_power_W / 1.0, 0);
+ return String(datalayer.battery.settings.balancing_float_power_W / 1.0f, 0);
}
if (var == "BAL_MAX_PACK_VOLTAGE") {
- return String(datalayer.battery.settings.balancing_max_pack_voltage_dV / 10.0, 0);
+ return String(datalayer.battery.settings.balancing_max_pack_voltage_dV / 10.0f, 0);
}
if (var == "BAL_MAX_CELL_VOLTAGE") {
- return String(datalayer.battery.settings.balancing_max_cell_voltage_mV / 1.0, 0);
+ return String(datalayer.battery.settings.balancing_max_cell_voltage_mV / 1.0f, 0);
}
if (var == "BAL_MAX_DEV_CELL_VOLTAGE") {
- return String(datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV / 1.0, 0);
+ return String(datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV / 1.0f, 0);
}
if (var == "BMS_RESET_DURATION") {
- return String(datalayer.battery.settings.user_set_bms_reset_duration_ms / 1000.0, 0);
+ return String(datalayer.battery.settings.user_set_bms_reset_duration_ms / 1000.0f, 0);
}
if (var == "CHARGER_CLASS") {
@@ -672,6 +778,9 @@ String raw_settings_processor(const String& var, BatteryEmulatorSettingsStore& s
if (var == "PYLONORDER") {
return settings.getBool("PYLONORDER") ? "checked" : "";
}
+ if (var == "PYLONBAUD") {
+ return String(settings.getUInt("PYLONBAUD", 500));
+ }
if (var == "INVCELLS") {
return String(settings.getUInt("INVCELLS", 0));
@@ -763,14 +872,933 @@ const char* getCANInterfaceName(CAN_Interface interface) {
}
}
+#ifdef HW_LILYGO2CAN
+#define GPIOOPT1_SETTING \
+ R"rawliteral(
+ Configurable port:
+
+ %GPIOOPT1%
+
+ )rawliteral"
+#else
+#define GPIOOPT1_SETTING ""
+#endif
+
+#ifdef HW_LILYGO
+#define GPIOOPT2_SETTING \
+ R"rawliteral(
+ BMS Power pin:
+
+ %GPIOOPT2%
+
+ )rawliteral"
+#else
+#define GPIOOPT2_SETTING ""
+#endif
+
+#ifdef HW_LILYGO
+#define GPIOOPT3_SETTING \
+ R"rawliteral(
+ SMA enable pin:
+
+ %GPIOOPT3%
+
+ )rawliteral"
+#else
+#define GPIOOPT3_SETTING ""
+#endif
+
+#ifdef HW_LILYGO
+#define GPIOOPT4_SETTING \
+ R"rawliteral(
+ uSD Slot:
+
+ %GPIOOPT4%
+
+ )rawliteral"
+#else
+#define GPIOOPT4_SETTING ""
+#endif
+
#define SETTINGS_HTML_SCRIPTS \
- R"rawliteral()rawliteral"
+ R"rawliteral(
+
+)rawliteral"
#define SETTINGS_STYLE \
- R"rawliteral()rawliteral"
+ R"rawliteral(
+
+)rawliteral"
#define SETTINGS_HTML_BODY \
- R"rawliteral(Back to main page Factory reset Battery interface: %BATTERYINTF% Battery interface: %BATTERY2INTF% Inverter interface: %INVINTF% Shunt interface: %SHUNTINTF%
Battery capacity: %BATTERY_WH_MAX% Wh Edit Rescale SOC: %SOC_SCALING% Edit SOC max percentage: %SOC_MAX_PERCENTAGE% Edit SOC min percentage: %SOC_MIN_PERCENTAGE% Edit Max charge speed: %MAX_CHARGE_SPEED% A Edit Max discharge speed: %MAX_DISCHARGE_SPEED% A Edit Manual charge voltage limits: %VOLTAGE_LIMITS% Edit Target charge voltage: %CHARGE_VOLTAGE% V Edit Target discharge voltage: %DISCHARGE_VOLTAGE% V Edit Periodic BMS reset off time: %BMS_RESET_DURATION% s Edit
Fake battery voltage: %BATTERY_VOLTAGE% V Edit
Manual LFP balancing: %MANUAL_BALANCING% Edit Balancing max time: %BAL_MAX_TIME% Minutes Edit Balancing float power: %BAL_POWER% W Edit Max battery voltage: %BAL_MAX_PACK_VOLTAGE% V Edit Max cell voltage: %BAL_MAX_CELL_VOLTAGE% mV Edit Max cell voltage deviation: %BAL_MAX_DEV_CELL_VOLTAGE% mV Edit
Charger HVDC Enabled: %CHG_HV% Edit Charger Aux12VDC Enabled: %CHG_AUX12V% Edit Charger Voltage Setpoint: %CHG_VOLTAGE_SETPOINT% V Edit Charger Current Setpoint: %CHG_CURRENT_SETPOINT% A Edit )rawliteral"
+ R"rawliteral(
+ Back to main page
+ Factory reset
+
+
+
+
+ Battery interface: %BATTERYINTF%
+
+ Battery interface: %BATTERY2INTF%
+
+ Inverter interface: %INVINTF%
+
+ Shunt interface: %SHUNTINTF%
+
+
+
+
+
+
Battery capacity: %BATTERY_WH_MAX% Wh Edit
+
+ Rescale SOC: %SOC_SCALING%
+ Edit
+
+ SOC max percentage: %SOC_MAX_PERCENTAGE% Edit
+
+ SOC min percentage: %SOC_MIN_PERCENTAGE% Edit
+
+ Max charge speed: %MAX_CHARGE_SPEED% A Edit
+
+ Max discharge speed: %MAX_DISCHARGE_SPEED% A Edit
+
+ Manual charge voltage limits:
+ %VOLTAGE_LIMITS%
+ Edit
+
+ Target charge voltage: %CHARGE_VOLTAGE% V Edit
+
+ Target discharge voltage: %DISCHARGE_VOLTAGE% V Edit
+
+ Periodic BMS reset off time: %BMS_RESET_DURATION% s Edit
+
+ Undercharged emergency recovery mode: Start
+
+
+
+
+
Fake battery voltage: %BATTERY_VOLTAGE% V Edit
+
+
+
+
+
+
+
Manual LFP balancing: %MANUAL_BALANCING%
+ Edit
+
+ Balancing max time: %BAL_MAX_TIME% Minutes Edit
+
+ Balancing float power: %BAL_POWER% W Edit
+
+ Max battery voltage: %BAL_MAX_PACK_VOLTAGE% V Edit
+
+ Max cell voltage: %BAL_MAX_CELL_VOLTAGE% mV Edit
+
+ Max cell voltage deviation: %BAL_MAX_DEV_CELL_VOLTAGE% mV Edit
+
+
+
+
+
+
+ Charger HVDC Enabled: %CHG_HV%
+ Edit
+
+
+
+ Charger Aux12VDC Enabled: %CHG_AUX12V%
+ Edit
+
+
+ Charger Voltage Setpoint: %CHG_VOLTAGE_SETPOINT% V Edit
+
+ Charger Current Setpoint: %CHG_CURRENT_SETPOINT% A Edit
+
+
+
+
+
+)rawliteral"
const char settings_html[] =
INDEX_HTML_HEADER COMMON_JAVASCRIPT SETTINGS_STYLE SETTINGS_HTML_BODY SETTINGS_HTML_SCRIPTS INDEX_HTML_FOOTER;
diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp
index b8547dde9..0eaa0587d 100644
--- a/Software/src/devboard/webserver/webserver.cpp
+++ b/Software/src/devboard/webserver/webserver.cpp
@@ -4,6 +4,7 @@
#include
#include "../../battery/BATTERIES.h"
#include "../../battery/Battery.h"
+#include "../../battery/Shunt.h"
#include "../../charger/CHARGERS.h"
#include "../../communication/can/comm_can.h"
#include "../../communication/contactorcontrol/comm_contactorcontrol.h"
@@ -11,6 +12,7 @@
#include "../../communication/nvm/comm_nvm.h"
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
+#include "../../devboard/safety/safety.h"
#include "../../inverter/INVERTERS.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../sdcard/sdcard.h"
@@ -21,8 +23,9 @@
#include "html_escape.h"
#include
-extern std::string http_username;
-extern std::string http_password;
+
+std::string http_username;
+std::string http_password;
bool webserver_auth = false;
@@ -88,8 +91,8 @@ void canReplayTask(void* param) {
}
do {
- float firstTimestamp = -1.0;
- float lastTimestamp = 0.0;
+ float firstTimestamp = -1.0f;
+ float lastTimestamp = 0.0f;
bool firstMessageSent = false; // Track first message
for (size_t i = 0; i < messages.size(); i++) {
@@ -187,8 +190,11 @@ void init_webserver() {
});
// Route for root / web page
- def_route_with_auth("/", server, HTTP_GET,
- [](AsyncWebServerRequest* request) { request->send(200, "text/html", index_html, processor); });
+ def_route_with_auth("/", server, HTTP_GET, [](AsyncWebServerRequest* request) {
+ // Clear OTA active flag as a safeguard in case onOTAEnd() wasn't called
+ ota_active = false;
+ request->send(200, "text/html", index_html, processor);
+ });
// Route for going to settings web page
def_route_with_auth("/settings", server, HTTP_GET, [](AsyncWebServerRequest* request) {
@@ -388,230 +394,117 @@ void init_webserver() {
request->send(200, "text/html", "OK");
});
- struct BoolSetting {
- const char* name;
- bool existingValue;
- bool newValue;
- };
-
const char* boolSettingNames[] = {
- "DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "SDLOGENABLED",
- "STATICIP", "REMBMSRESET", "EXTPRECHARGE", "USBENABLED", "CANLOGUSB", "WEBENABLED",
- "CANFDASCAN", "CANLOGSD", "WIFIAPENABLED", "MQTTENABLED", "NOINVDISC", "HADISC",
- "MQTTTOPICS", "MQTTCELLV", "INVICNT", "GTWRHD", "DIGITALHVIL", "PERFPROFILE",
- "INTERLOCKREQ", "SOCESTIMATED", "PYLONOFFSET", "PYLONORDER", "DEYEBYD",
+ "DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "SDLOGENABLED", "STATICIP",
+ "REMBMSRESET", "EXTPRECHARGE", "USBENABLED", "CANLOGUSB", "WEBENABLED", "CANFDASCAN", "CANLOGSD",
+ "WIFIAPENABLED", "MQTTENABLED", "NOINVDISC", "HADISC", "MQTTTOPICS", "MQTTCELLV", "INVICNT",
+ "GTWRHD", "DIGITALHVIL", "PERFPROFILE", "INTERLOCKREQ", "SOCESTIMATED", "PYLONOFFSET", "PYLONORDER",
+ "DEYEBYD", "NCCONTACTOR", "TRIBTR", "CNTCTRLTRI", "ESPNOWENABLED",
};
- // Handles the form POST from UI to save settings of the common image
- server.on("/saveSettings", HTTP_POST, [boolSettingNames](AsyncWebServerRequest* request) {
- BatteryEmulatorSettingsStore settings;
-
- std::vector boolSettings;
-
- for (auto& name : boolSettingNames) {
- boolSettings.push_back({name, settings.getBool(name, name == std::string("WIFIAPENABLED")), false});
- }
-
- int numParams = request->params();
- for (int i = 0; i < numParams; i++) {
- auto p = request->getParam(i);
- if (p->name() == "inverter") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("INVTYPE", (int)type);
- } else if (p->name() == "INVCOMM") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("INVCOMM", (int)type);
- } else if (p->name() == "battery") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("BATTTYPE", (int)type);
- } else if (p->name() == "BATTCHEM") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("BATTCHEM", (int)type);
- } else if (p->name() == "BATTCOMM") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("BATTCOMM", (int)type);
- } else if (p->name() == "BATTPVMAX") {
- auto type = p->value().toFloat() * 10.0;
- settings.saveUInt("BATTPVMAX", (int)type);
- } else if (p->name() == "BATTPVMIN") {
- auto type = p->value().toFloat() * 10.0;
- settings.saveUInt("BATTPVMIN", (int)type);
- } else if (p->name() == "BATTCVMAX") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("BATTCVMAX", type);
- } else if (p->name() == "BATTCVMIN") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("BATTCVMIN", type);
- } else if (p->name() == "charger") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("CHGTYPE", (int)type);
- } else if (p->name() == "CHGCOMM") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("CHGCOMM", (int)type);
- } else if (p->name() == "EQSTOP") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("EQSTOP", (int)type);
- } else if (p->name() == "BATT2COMM") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("BATT2COMM", (int)type);
- } else if (p->name() == "shunt") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("SHUNTTYPE", (int)type);
- } else if (p->name() == "SHUNTCOMM") {
- auto type = static_cast(atoi(p->value().c_str()));
- settings.saveUInt("SHUNTCOMM", (int)type);
- } else if (p->name() == "MAXPRETIME") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("MAXPRETIME", type);
- } else if (p->name() == "WIFICHANNEL") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("WIFICHANNEL", type);
- } else if (p->name() == "DCHGPOWER") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("DCHGPOWER", type);
- } else if (p->name() == "CHGPOWER") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("CHGPOWER", type);
- } else if (p->name() == "LOCALIP1") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("LOCALIP1", type);
- } else if (p->name() == "LOCALIP2") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("LOCALIP2", type);
- } else if (p->name() == "LOCALIP3") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("LOCALIP3", type);
- } else if (p->name() == "LOCALIP4") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("LOCALIP4", type);
- } else if (p->name() == "GATEWAY1") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GATEWAY1", type);
- } else if (p->name() == "GATEWAY2") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GATEWAY2", type);
- } else if (p->name() == "GATEWAY3") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GATEWAY3", type);
- } else if (p->name() == "GATEWAY4") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GATEWAY4", type);
- } else if (p->name() == "SUBNET1") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("SUBNET1", type);
- } else if (p->name() == "SUBNET2") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("SUBNET2", type);
- } else if (p->name() == "SUBNET3") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("SUBNET3", type);
- } else if (p->name() == "SUBNET4") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("SUBNET4", type);
- } else if (p->name() == "SSID") {
- settings.saveString("SSID", p->value().c_str());
- ssid = settings.getString("SSID", "").c_str();
- } else if (p->name() == "PASSWORD") {
- settings.saveString("PASSWORD", p->value().c_str());
- password = settings.getString("PASSWORD", "").c_str();
- } else if (p->name() == "APNAME") {
- settings.saveString("APNAME", p->value().c_str());
- } else if (p->name() == "APPASSWORD") {
- settings.saveString("APPASSWORD", p->value().c_str());
- } else if (p->name() == "HOSTNAME") {
- settings.saveString("HOSTNAME", p->value().c_str());
- } else if (p->name() == "MQTTSERVER") {
- settings.saveString("MQTTSERVER", p->value().c_str());
- } else if (p->name() == "MQTTPORT") {
- auto port = atoi(p->value().c_str());
- settings.saveUInt("MQTTPORT", port);
- } else if (p->name() == "MQTTUSER") {
- settings.saveString("MQTTUSER", p->value().c_str());
- } else if (p->name() == "MQTTPASSWORD") {
- settings.saveString("MQTTPASSWORD", p->value().c_str());
- } else if (p->name() == "MQTTTOPIC") {
- settings.saveString("MQTTTOPIC", p->value().c_str());
- } else if (p->name() == "MQTTTIMEOUT") {
- auto port = atoi(p->value().c_str());
- settings.saveUInt("MQTTTIMEOUT", port);
- } else if (p->name() == "MQTTOBJIDPREFIX") {
- settings.saveString("MQTTOBJIDPREFIX", p->value().c_str());
- } else if (p->name() == "MQTTDEVICENAME") {
- settings.saveString("MQTTDEVICENAME", p->value().c_str());
- } else if (p->name() == "HADEVICEID") {
- settings.saveString("HADEVICEID", p->value().c_str());
- } else if (p->name() == "SOFAR_ID") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("SOFAR_ID", type);
- } else if (p->name() == "PYLONSEND") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("PYLONSEND", type);
- } else if (p->name() == "INVCELLS") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("INVCELLS", type);
- } else if (p->name() == "INVMODULES") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("INVMODULES", type);
- } else if (p->name() == "INVCELLSPER") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("INVCELLSPER", type);
- } else if (p->name() == "INVVLEVEL") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("INVVLEVEL", type);
- } else if (p->name() == "INVCAPACITY") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("INVCAPACITY", type);
- } else if (p->name() == "INVBTYPE") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("INVBTYPE", (int)type);
- } else if (p->name() == "CANFREQ") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("CANFREQ", type);
- } else if (p->name() == "CANFDFREQ") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("CANFDFREQ", type);
- } else if (p->name() == "PRECHGMS") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("PRECHGMS", type);
- } else if (p->name() == "PWMFREQ") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("PWMFREQ", type);
- } else if (p->name() == "PWMHOLD") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("PWMHOLD", type);
- } else if (p->name() == "GTWCOUNTRY") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GTWCOUNTRY", type);
- } else if (p->name() == "GTWMAPREG") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GTWMAPREG", type);
- } else if (p->name() == "GTWCHASSIS") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GTWCHASSIS", type);
- } else if (p->name() == "GTWPACK") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("GTWPACK", type);
- } else if (p->name() == "LEDMODE") {
- auto type = atoi(p->value().c_str());
- settings.saveUInt("LEDMODE", type);
- }
-
- for (auto& boolSetting : boolSettings) {
- if (p->name() == boolSetting.name) {
- boolSetting.newValue = p->value() == "on";
- }
- }
- }
+ const char* uintSettingNames[] = {
+ "BATTCVMAX", "BATTCVMIN", "MAXPRETIME", "MAXPREFREQ", "WIFICHANNEL", "DCHGPOWER", "CHGPOWER", "LOCALIP1",
+ "LOCALIP2", "LOCALIP3", "LOCALIP4", "GATEWAY1", "GATEWAY2", "GATEWAY3", "GATEWAY4", "SUBNET1",
+ "SUBNET2", "SUBNET3", "SUBNET4", "MQTTPORT", "MQTTTIMEOUT", "SOFAR_ID", "PYLONSEND", "INVCELLS",
+ "INVMODULES", "INVCELLSPER", "INVVLEVEL", "INVCAPACITY", "INVBTYPE", "CANFREQ", "CANFDFREQ", "PRECHGMS",
+ "PWMFREQ", "PWMHOLD", "GTWCOUNTRY", "GTWMAPREG", "GTWCHASSIS", "GTWPACK", "LEDMODE", "GPIOOPT1",
+ "GPIOOPT2", "GPIOOPT3", "INVSUNTYPE", "GPIOOPT4",
+ };
- for (auto& boolSetting : boolSettings) {
- if (boolSetting.existingValue != boolSetting.newValue) {
- settings.saveBool(boolSetting.name, boolSetting.newValue);
- }
- }
+ const char* stringSettingNames[] = {"APNAME", "APPASSWORD", "HOSTNAME", "MQTTSERVER", "MQTTUSER",
+ "MQTTPASSWORD", "MQTTTOPIC", "MQTTOBJIDPREFIX", "MQTTDEVICENAME", "HADEVICEID"};
- settingsUpdated = settings.were_settings_updated();
- request->redirect("/settings");
- });
+ // Handles the form POST from UI to save settings of the common image
+ server.on("/saveSettings", HTTP_POST,
+ [boolSettingNames, stringSettingNames, uintSettingNames](AsyncWebServerRequest* request) {
+ BatteryEmulatorSettingsStore settings;
+
+ int numParams = request->params();
+ for (int i = 0; i < numParams; i++) {
+ auto p = request->getParam(i);
+ if (p->name() == "inverter") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("INVTYPE", (int)type);
+ } else if (p->name() == "INVCOMM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("INVCOMM", (int)type);
+ } else if (p->name() == "battery") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("BATTTYPE", (int)type);
+ } else if (p->name() == "BATTCHEM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("BATTCHEM", (int)type);
+ } else if (p->name() == "BATTCOMM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("BATTCOMM", (int)type);
+ } else if (p->name() == "BATTPVMAX") {
+ auto type = p->value().toFloat() * 10.0f;
+ settings.saveUInt("BATTPVMAX", (int)type);
+ } else if (p->name() == "BATTPVMIN") {
+ auto type = p->value().toFloat() * 10.0f;
+ settings.saveUInt("BATTPVMIN", (int)type);
+ } else if (p->name() == "charger") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("CHGTYPE", (int)type);
+ } else if (p->name() == "CHGCOMM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("CHGCOMM", (int)type);
+ } else if (p->name() == "EQSTOP") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("EQSTOP", (int)type);
+ } else if (p->name() == "BATT2COMM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("BATT2COMM", (int)type);
+ } else if (p->name() == "BATT3COMM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("BATT3COMM", (int)type);
+ } else if (p->name() == "shunt") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("SHUNTTYPE", (int)type);
+ } else if (p->name() == "SHUNTCOMM") {
+ auto type = static_cast(atoi(p->value().c_str()));
+ settings.saveUInt("SHUNTCOMM", (int)type);
+ } else if (p->name() == "SSID") {
+ settings.saveString("SSID", p->value().c_str());
+ ssid = settings.getString("SSID", "").c_str();
+ } else if (p->name() == "PASSWORD") {
+ settings.saveString("PASSWORD", p->value().c_str());
+ password = settings.getString("PASSWORD", "").c_str();
+ } else if (p->name() == "MQTTPUBLISHMS") {
+ auto interval = atoi(p->value().c_str()) * 1000; // Convert seconds to milliseconds
+ settings.saveUInt("MQTTPUBLISHMS", interval);
+ }
+
+ for (auto& uintSetting : uintSettingNames) {
+ if (p->name() == uintSetting) {
+ auto value = atoi(p->value().c_str());
+ if (settings.getUInt(uintSetting, 0) != value) {
+ settings.saveUInt(uintSetting, value);
+ }
+ }
+ }
+
+ for (auto& stringSetting : stringSettingNames) {
+ if (p->name() == stringSetting) {
+ if (settings.getString(stringSetting) != p->value()) {
+ settings.saveString(stringSetting, p->value().c_str());
+ }
+ }
+ }
+ }
+
+ for (auto& boolSetting : boolSettingNames) {
+ auto p = request->getParam(boolSetting, true);
+ const bool default_value = (std::string(boolSetting) == std::string("WIFIAPENABLED"));
+ const bool value = p != nullptr && p->value() == "on";
+ if (settings.getBool(boolSetting, default_value) != value) {
+ settings.saveBool(boolSetting, value);
+ }
+ }
+
+ settingsUpdated = settings.were_settings_updated();
+ request->redirect("/settings");
+ });
auto update_string = [](const char* route, std::function setter,
std::function validator = nullptr) {
@@ -653,11 +546,18 @@ void init_webserver() {
// Route for editing USE_SCALED_SOC
update_int_setting("/updateUseScaledSOC", [](int value) { datalayer.battery.settings.soc_scaling_active = value; });
+ // Route for enabling recovery mode charging
+ update_int_setting("/enableRecoveryMode",
+ [](int value) { datalayer.battery.settings.user_requests_forced_charging_recovery_mode = value; });
+
// Route for editing SOCMax
update_string_setting("/updateSocMax", [](String value) {
datalayer.battery.settings.max_percentage = static_cast(value.toFloat() * 100);
});
+ // Route for editing CAN ID cutoff filter
+ update_int_setting("/set_can_id_cutoff", [](int value) { user_selected_CAN_ID_cutoff_filter = value; });
+
// Route for pause/resume Battery emulator
update_string("/pause", [](String value) { setBatteryPause(value == "true" || value == "1", false); });
@@ -704,6 +604,9 @@ void init_webserver() {
if (battIndex == "1") {
batt = battery2;
}
+ if (battIndex == "2") {
+ batt = battery3;
+ }
if (batt) {
cmd.action(batt);
}
@@ -738,7 +641,7 @@ void init_webserver() {
// Route for editing balancing max time
update_string_setting("/BalTime", [](String value) {
- datalayer.battery.settings.balancing_time_ms = static_cast(value.toFloat() * 60000);
+ datalayer.battery.settings.balancing_max_time_ms = static_cast(value.toFloat() * 60000);
});
// Route for editing balancing max power
@@ -839,6 +742,9 @@ String getConnectResultString(wl_status_t status) {
}
void ota_monitor() {
+
+ ElegantOTA.loop();
+
if (ota_active && ota_timeout_timer.elapsed()) {
// OTA timeout, try to restore can and clear the update event
set_event(EVENT_OTA_UPDATE_TIMEOUT, 0);
@@ -911,8 +817,6 @@ String processor(const String& var) {
content += " padding: 8px;";
content += " position: absolute;";
content += " z-index: 1;";
- content += " bottom: 125%;";
- content += " left: 50%;";
content += " margin-left: -100px;";
content += " opacity: 0;";
content += " transition: opacity 0.3s;";
@@ -938,12 +842,25 @@ String processor(const String& var) {
#ifdef HW_LILYGO2CAN
content += " Hardware: LilyGo T_2CAN";
#endif // HW_LILYGO2CAN
+#ifdef HW_BECOM
+ content += " Hardware: BECom";
+#endif // HW_BECOM
#ifdef HW_STARK
content += " Hardware: Stark CMR Module";
#endif // HW_STARK
content += " @ " + String(datalayer.system.info.CPU_temperature, 1) + " °C ";
content += "Uptime: " + get_uptime() + " ";
if (datalayer.system.info.performance_measurement_active) {
+ content +=
+ "Free heap: " + String(ESP.getFreeHeap()) + ", max alloc: " + String(ESP.getMaxAllocHeap()) + " ";
+ FlashMode_t mode = ESP.getFlashChipMode();
+ content += "Flash mode: " +
+ String(mode == FM_QIO ? "QIO"
+ : mode == FM_QOUT ? "QOUT"
+ : mode == FM_DIO ? "DIO"
+ : mode == FM_DOUT ? "DOUT"
+ : /*mode == FM_UNKNOWN*/ "Unknown") +
+ ", size: " + String(ESP.getFlashChipSize() / (1024 * 1024)) + " MB ";
// Load information
content += "Core task max load: " + String(datalayer.system.status.core_task_max_us) + " us ";
content +=
@@ -959,7 +876,6 @@ String processor(const String& var) {
content += "Values function timing: " + String(datalayer.system.status.time_snap_values_us) + " us ";
content += "CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us ";
content += "CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us ";
- content += "OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us ";
}
wl_status_t status = WiFi.status();
@@ -979,7 +895,7 @@ String processor(const String& var) {
// Close the block
content += "";
- if (inverter || battery || shunt || charger) {
+ if (inverter || battery || charger || user_selected_shunt_type != ShuntType::None) {
// Start a new block with a specific background color
content += " ";
@@ -995,7 +911,9 @@ String processor(const String& var) {
if (battery) {
content += "
Battery protocol: ";
content += datalayer.system.info.battery_protocol;
- if (battery2) {
+ if (battery3) {
+ content += " (Triple battery)";
+ } else if (battery2) {
content += " (Double battery)";
}
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
@@ -1004,7 +922,7 @@ String processor(const String& var) {
content += " ";
}
- if (shunt) {
+ if (user_selected_shunt_type != ShuntType::None) {
content += "Shunt protocol: ";
content += datalayer.system.info.shunt_protocol;
content += " ";
@@ -1050,22 +968,22 @@ String processor(const String& var) {
// Display battery statistics within this block
float socRealFloat =
- static_cast(datalayer.battery.status.real_soc) / 100.0; // Convert to float and divide by 100
+ static_cast(datalayer.battery.status.real_soc) / 100.0f; // Convert to float and divide by 100
float socScaledFloat =
- static_cast(datalayer.battery.status.reported_soc) / 100.0; // Convert to float and divide by 100
+ static_cast(datalayer.battery.status.reported_soc) / 100.0f; // Convert to float and divide by 100
float sohFloat =
- static_cast(datalayer.battery.status.soh_pptt) / 100.0; // Convert to float and divide by 100
+ static_cast(datalayer.battery.status.soh_pptt) / 100.0f; // Convert to float and divide by 100
float voltageFloat =
- static_cast(datalayer.battery.status.voltage_dV) / 10.0; // Convert to float and divide by 10
+ static_cast(datalayer.battery.status.voltage_dV) / 10.0f; // Convert to float and divide by 10
float currentFloat =
- static_cast(datalayer.battery.status.current_dA) / 10.0; // Convert to float and divide by 10
- float powerFloat = static_cast(datalayer.battery.status.active_power_W); // Convert to float
- float tempMaxFloat = static_cast(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
- float tempMinFloat = static_cast(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
+ static_cast(datalayer.battery.status.current_dA) / 10.0f; // Convert to float and divide by 10
+ float powerFloat = static_cast(datalayer.battery.status.active_power_W); // Convert to float
+ float tempMaxFloat = static_cast(datalayer.battery.status.temperature_max_dC) / 10.0f; // Convert to float
+ float tempMinFloat = static_cast(datalayer.battery.status.temperature_min_dC) / 10.0f; // Convert to float
float maxCurrentChargeFloat =
- static_cast(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float
+ static_cast(datalayer.battery.status.max_charge_current_dA) / 10.0f; // Convert to float
float maxCurrentDischargeFloat =
- static_cast(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float
+ static_cast(datalayer.battery.status.max_discharge_current_dA) / 10.0f; // Convert to float
uint16_t cell_delta_mv =
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
@@ -1094,7 +1012,7 @@ String processor(const String& var) {
else
content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
- if (datalayer.system.settings.equipment_stop_active) {
+ if (datalayer.system.info.equipment_stop_active) {
content +=
formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
@@ -1224,17 +1142,17 @@ String processor(const String& var) {
// Display battery statistics within this block
socRealFloat =
- static_cast(datalayer.battery2.status.real_soc) / 100.0; // Convert to float and divide by 100
+ static_cast(datalayer.battery2.status.real_soc) / 100.0f; // Convert to float and divide by 100
//socScaledFloat; // Same value used for bat2
sohFloat =
- static_cast(datalayer.battery2.status.soh_pptt) / 100.0; // Convert to float and divide by 100
+ static_cast(datalayer.battery2.status.soh_pptt) / 100.0f; // Convert to float and divide by 100
voltageFloat =
- static_cast(datalayer.battery2.status.voltage_dV) / 10.0; // Convert to float and divide by 10
+ static_cast(datalayer.battery2.status.voltage_dV) / 10.0f; // Convert to float and divide by 10
currentFloat =
- static_cast(datalayer.battery2.status.current_dA) / 10.0; // Convert to float and divide by 10
+ static_cast(datalayer.battery2.status.current_dA) / 10.0f; // Convert to float and divide by 10
powerFloat = static_cast(datalayer.battery2.status.active_power_W); // Convert to float
- tempMaxFloat = static_cast(datalayer.battery2.status.temperature_max_dC) / 10.0; // Convert to float
- tempMinFloat = static_cast(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float
+ tempMaxFloat = static_cast(datalayer.battery2.status.temperature_max_dC) / 10.0f; // Convert to float
+ tempMinFloat = static_cast(datalayer.battery2.status.temperature_min_dC) / 10.0f; // Convert to float
cell_delta_mv = datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV;
if (datalayer.battery.settings.soc_scaling_active)
@@ -1262,7 +1180,7 @@ String processor(const String& var) {
else
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
- if (datalayer.system.settings.equipment_stop_active) {
+ if (datalayer.system.info.equipment_stop_active) {
content +=
formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
@@ -1301,6 +1219,104 @@ String processor(const String& var) {
content += "Battery charging! ";
}
content += " ";
+ if (battery3) {
+ content += "";
+
+ // Display battery statistics within this block
+ socRealFloat =
+ static_cast(datalayer.battery3.status.real_soc) / 100.0f; // Convert to float and divide by 100
+ //socScaledFloat; // Same value used for bat2
+ sohFloat =
+ static_cast(datalayer.battery3.status.soh_pptt) / 100.0f; // Convert to float and divide by 100
+ voltageFloat =
+ static_cast(datalayer.battery3.status.voltage_dV) / 10.0f; // Convert to float and divide by 10
+ currentFloat =
+ static_cast(datalayer.battery3.status.current_dA) / 10.0f; // Convert to float and divide by 10
+ powerFloat = static_cast(datalayer.battery3.status.active_power_W); // Convert to float
+ tempMaxFloat = static_cast(datalayer.battery3.status.temperature_max_dC) / 10.0f; // Convert to float
+ tempMinFloat = static_cast(datalayer.battery3.status.temperature_min_dC) / 10.0f; // Convert to float
+ cell_delta_mv = datalayer.battery3.status.cell_max_voltage_mV - datalayer.battery3.status.cell_min_voltage_mV;
+
+ if (datalayer.battery.settings.soc_scaling_active)
+ content += "Scaled SOC: " + String(socScaledFloat, 2) +
+ "% (real: " + String(socRealFloat, 2) + "%) ";
+ else
+ content += "SOC: " + String(socRealFloat, 2) + "% ";
+
+ content += "SOH: " + String(sohFloat, 2) + "% ";
+ content += "Voltage: " + String(voltageFloat, 1) +
+ " V Current: " + String(currentFloat, 1) + " A ";
+ content += formatPowerValue("Power", powerFloat, "", 1);
+
+ if (datalayer.battery.settings.soc_scaling_active)
+ content += "Scaled total capacity: " +
+ formatPowerValue(datalayer.battery3.info.reported_total_capacity_Wh, "h", 1) +
+ " (real: " + formatPowerValue(datalayer.battery3.info.total_capacity_Wh, "h", 1) + ") ";
+ else
+ content += formatPowerValue("Total capacity", datalayer.battery3.info.total_capacity_Wh, "h", 1);
+
+ if (datalayer.battery.settings.soc_scaling_active)
+ content += "Scaled remaining capacity: " +
+ formatPowerValue(datalayer.battery3.status.reported_remaining_capacity_Wh, "h", 1) +
+ " (real: " + formatPowerValue(datalayer.battery3.status.remaining_capacity_Wh, "h", 1) +
+ ") ";
+ else
+ content += formatPowerValue("Remaining capacity", datalayer.battery3.status.remaining_capacity_Wh, "h", 1);
+
+ if (datalayer.system.info.equipment_stop_active) {
+ content +=
+ formatPowerValue("Max discharge power", datalayer.battery3.status.max_discharge_power_W, "", 1, "red");
+ content += formatPowerValue("Max charge power", datalayer.battery3.status.max_charge_power_W, "", 1, "red");
+ content +=
+ "Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A ";
+ content += "Max charge current: " + String(maxCurrentChargeFloat, 1) + " A ";
+ } else {
+ content += formatPowerValue("Max discharge power", datalayer.battery3.status.max_discharge_power_W, "", 1);
+ content += formatPowerValue("Max charge power", datalayer.battery3.status.max_charge_power_W, "", 1);
+ content +=
+ "Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A ";
+ content += "Max charge current: " + String(maxCurrentChargeFloat, 1) + " A ";
+ }
+
+ content += "Cell min/max: " + String(datalayer.battery3.status.cell_min_voltage_mV) + " mV / " +
+ String(datalayer.battery3.status.cell_max_voltage_mV) + " mV ";
+ if (cell_delta_mv > datalayer.battery3.info.max_cell_voltage_deviation_mV) {
+ content += "Cell delta: " + String(cell_delta_mv) + " mV ";
+ } else {
+ content += "Cell delta: " + String(cell_delta_mv) + " mV ";
+ }
+ content += "Temperature min/max: " + String(tempMinFloat, 1) + " °C / " + String(tempMaxFloat, 1) +
+ " °C ";
+ if (datalayer.battery.status.bms_status == ACTIVE) {
+ content += "System status: OK ";
+ } else if (datalayer.battery.status.bms_status == UPDATING) {
+ content += "System status: UPDATING ";
+ } else {
+ content += "System status: FAULT ";
+ }
+ if (datalayer.battery3.status.current_dA == 0) {
+ content += "Battery idle ";
+ } else if (datalayer.battery3.status.current_dA < 0) {
+ content += "Battery discharging! ";
+ } else { // > 0
+ content += "Battery charging! ";
+ }
+ content += "
";
+ content += "";
+ }
content += "";
}
}
@@ -1345,15 +1361,17 @@ String processor(const String& var) {
} else { //contactor_control_enabled TRUE
content += "
Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_engaged == 0) {
- content += "PRECHARGE ";
+ content += "OFF (DISCONNECTED) ";
} else if (datalayer.system.status.contactors_engaged == 1) {
content += "ON ";
} else if (datalayer.system.status.contactors_engaged == 2) {
- content += "OFF ";
+ content += "OFF (FAULT) ";
content += " [!] ";
content +=
"Emulator spent too much time in critical FAULT event. Investigate event "
"causing this via Events page. Reboot required to resume operation! ";
+ } else if (datalayer.system.status.contactors_engaged == 3) {
+ content += "PRECHARGE ";
}
content += " ";
if (contactor_control_enabled_double_battery && battery2) {
@@ -1448,7 +1466,7 @@ String processor(const String& var) {
content += "Reboot Emulator ";
if (webserver_auth)
content += "Logout ";
- if (!datalayer.system.settings.equipment_stop_active)
+ if (!datalayer.system.info.equipment_stop_active)
content +=
"::value || std::is_same::value || std::is_same::value) {
float convertedValue = static_cast(value);
- if (convertedValue >= 1000.0 || convertedValue <= -1000.0) {
- result += String(convertedValue / 1000.0, precision) + " kW";
+ if (convertedValue >= 1000.0f || convertedValue <= -1000.0f) {
+ result += String(convertedValue / 1000.0f, precision) + " kW";
} else {
result += String(convertedValue, 0) + " W";
}
diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h
index 6cbb956fb..f3f1973c5 100644
--- a/Software/src/devboard/webserver/webserver.h
+++ b/Software/src/devboard/webserver/webserver.h
@@ -20,6 +20,9 @@ extern float charger_stat_LVvol;
//LEAF charger
extern uint16_t OBC_Charge_Power;
+// OTA status
+extern bool ota_active;
+
/**
* @brief Initialization function for the webserver.
*
diff --git a/Software/src/devboard/wifi/wifi.cpp b/Software/src/devboard/wifi/wifi.cpp
index 64b31be08..89f9b77c6 100644
--- a/Software/src/devboard/wifi/wifi.cpp
+++ b/Software/src/devboard/wifi/wifi.cpp
@@ -1,11 +1,14 @@
#include "wifi.h"
-#include
#include "../utils/events.h"
#include "../utils/logging.h"
+#ifndef SMALL_FLASH_DEVICE
+#include
+#endif
bool wifi_enabled = true;
bool wifiap_enabled = true;
-bool mdns_enabled = true; //If true, allows battery monitor te be found by .local address
+bool mdns_enabled = true; //If true, allows battery monitor te be found by .local address
+bool espnow_enabled = true; //If true, allows battery emulator to send battery status by using ESPNow messages
uint16_t wifi_channel = 0;
std::string custom_hostname; //If not set, the default naming format 'esp32-XXXXXX' will be used
@@ -16,18 +19,18 @@ std::string passwordAP;
// Set your Static IP address. Only used incase Static address option is set
bool static_IP_enabled = false;
-uint16_t static_local_IP1 = 0;
-uint16_t static_local_IP2 = 0;
-uint16_t static_local_IP3 = 0;
-uint16_t static_local_IP4 = 0;
-uint16_t static_gateway1 = 0;
-uint16_t static_gateway2 = 0;
-uint16_t static_gateway3 = 0;
-uint16_t static_gateway4 = 0;
-uint16_t static_subnet1 = 0;
-uint16_t static_subnet2 = 0;
-uint16_t static_subnet3 = 0;
-uint16_t static_subnet4 = 0;
+uint8_t static_local_IP1 = 0;
+uint8_t static_local_IP2 = 0;
+uint8_t static_local_IP3 = 0;
+uint8_t static_local_IP4 = 0;
+uint8_t static_gateway1 = 0;
+uint8_t static_gateway2 = 0;
+uint8_t static_gateway3 = 0;
+uint8_t static_gateway4 = 0;
+uint8_t static_subnet1 = 0;
+uint8_t static_subnet2 = 0;
+uint8_t static_subnet3 = 0;
+uint8_t static_subnet4 = 0;
// Configuration Parameters
static const uint16_t WIFI_CHECK_INTERVAL = 2000; // 1 seconds normal check interval when last connected
@@ -220,8 +223,9 @@ void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) {
//normal reconnect retry start at first 2 seconds
}
-// Initialise mDNS
+// Initialise mDNS (Only available on devices with )
void init_mDNS() {
+#ifndef SMALL_FLASH_DEVICE
// Calulate the host name using the last two chars from the MAC address so each one is likely unique on a network.
// e.g batteryemulator8C.local where the mac address is 08:F9:E0:D1:06:8C
String mac = WiFi.macAddress();
@@ -238,6 +242,7 @@ void init_mDNS() {
// Advertise via bonjour the service so we can auto discover these battery emulators on the local network.
MDNS.addService(mdnsHost, "tcp", 80);
}
+#endif
}
void init_WiFi_AP() {
diff --git a/Software/src/devboard/wifi/wifi.h b/Software/src/devboard/wifi/wifi.h
index ed6a9a89d..6f2dcdd05 100644
--- a/Software/src/devboard/wifi/wifi.h
+++ b/Software/src/devboard/wifi/wifi.h
@@ -37,18 +37,19 @@ void init_mDNS();
extern bool wifi_enabled;
extern bool wifiap_enabled;
extern bool mdns_enabled;
+extern bool espnow_enabled;
extern bool static_IP_enabled;
-extern uint16_t static_local_IP1;
-extern uint16_t static_local_IP2;
-extern uint16_t static_local_IP3;
-extern uint16_t static_local_IP4;
-extern uint16_t static_gateway1;
-extern uint16_t static_gateway2;
-extern uint16_t static_gateway3;
-extern uint16_t static_gateway4;
-extern uint16_t static_subnet1;
-extern uint16_t static_subnet2;
-extern uint16_t static_subnet3;
-extern uint16_t static_subnet4;
+extern uint8_t static_local_IP1;
+extern uint8_t static_local_IP2;
+extern uint8_t static_local_IP3;
+extern uint8_t static_local_IP4;
+extern uint8_t static_gateway1;
+extern uint8_t static_gateway2;
+extern uint8_t static_gateway3;
+extern uint8_t static_gateway4;
+extern uint8_t static_subnet1;
+extern uint8_t static_subnet2;
+extern uint8_t static_subnet3;
+extern uint8_t static_subnet4;
#endif
diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp
index ade4756ba..71d270fb0 100644
--- a/Software/src/inverter/AFORE-CAN.cpp
+++ b/Software/src/inverter/AFORE-CAN.cpp
@@ -16,8 +16,8 @@ void AforeCanInverter::
AFORE_350.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
//Total battery current unit: 0.1A offset 5000; positive is charging,
//Negative means discharge; for example: 1A = (5010-5000)/10
- AFORE_350.data.u8[2] = ((datalayer.battery.status.current_dA + 5000) & 0x00FF);
- AFORE_350.data.u8[3] = ((datalayer.battery.status.current_dA + 5000) >> 8);
+ AFORE_350.data.u8[2] = ((datalayer.battery.status.reported_current_dA + 5000) & 0x00FF);
+ AFORE_350.data.u8[3] = ((datalayer.battery.status.reported_current_dA + 5000) >> 8);
//Battery temperature unit: 0.1C offset 1000; for example: 20C
//= (1200 -1000)/10; when all temperatures are greater than or equal to 0
AFORE_350.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
@@ -78,7 +78,7 @@ void AforeCanInverter::
15 InterlockOpen
AFORE_353.data.u8[0] = Fault H table & 0x00FF
AFORE_353.data.u8[1] = Fault H table >> 8);
- /* Fault L, bit, definitions
+ Fault L, bit, definitions
8 VoltageInterlockShortCircuit
9 SystemFailure
10 ErrorChargeReferenceOvervoltage
diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp
index 1310dc02f..1c0036cb2 100644
--- a/Software/src/inverter/BYD-CAN.cpp
+++ b/Software/src/inverter/BYD-CAN.cpp
@@ -78,8 +78,8 @@ void BydCanInverter::
BYD_1D0.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
BYD_1D0.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Current (ex 81.0A)
- BYD_1D0.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- BYD_1D0.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ BYD_1D0.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ BYD_1D0.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
//Temperature average
BYD_1D0.data.u8[4] = (temperature_average >> 8);
BYD_1D0.data.u8[5] = (temperature_average & 0x00FF);
@@ -113,8 +113,18 @@ void BydCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
inverterStartedUp = true;
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1;
- inverter_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) * 0.1;
+ inverter_current = (int16_t)((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
inverter_temperature = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1;
+ if (useAsShunt) {
+ datalayer.shunt.available = true;
+ datalayer.shunt.precharging = false;
+ datalayer.shunt.contactors_engaged = true;
+ datalayer.shunt.measured_voltage_dV = inverter_voltage;
+ datalayer.shunt.measured_voltage_mV = inverter_voltage * 100;
+ datalayer.shunt.measured_outvoltage_mV = inverter_voltage * 100;
+ datalayer.shunt.measured_amperage_dA = inverter_current / 10;
+ datalayer.shunt.measured_amperage_mA = inverter_current * 10;
+ }
break;
case 0x0D1:
inverterStartedUp = true;
@@ -180,3 +190,9 @@ void BydCanInverter::send_initial_data() {
BYD_3D0.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}; //VS
transmit_can_frame(&BYD_3D0);
}
+
+void BydCanInverter::enable_shunt() {
+ strncpy(datalayer.system.info.shunt_protocol, Name, 31);
+ datalayer.system.info.shunt_protocol[31] = '\0';
+ useAsShunt = true;
+}
diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h
index 09538ad3a..f7dbc29c0 100644
--- a/Software/src/inverter/BYD-CAN.h
+++ b/Software/src/inverter/BYD-CAN.h
@@ -10,6 +10,8 @@ class BydCanInverter : public CanInverterProtocol {
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void update_values();
+ bool provides_shunt() { return true; }
+ void enable_shunt();
static constexpr const char* Name = "BYD Battery-Box Premium HVS over CAN Bus";
private:
@@ -17,10 +19,20 @@ class BydCanInverter : public CanInverterProtocol {
unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
-
- static const int FW_MAJOR_VERSION = 0x03;
- static const int FW_MINOR_VERSION = 0x29;
- static const int VOLTAGE_OFFSET_DV = 20;
+ unsigned long inverter_timestamp = 0;
+ uint16_t remaining_capacity_ah = 0;
+ uint16_t fully_charged_capacity_ah = 0;
+ uint16_t inverter_voltage = 0;
+ uint16_t inverter_SOC = 0;
+ int16_t temperature_average = 0;
+ int16_t inverter_current = 0;
+ int16_t inverter_temperature = 0;
+ static const uint8_t FW_MAJOR_VERSION = 0x03;
+ static const uint8_t FW_MINOR_VERSION = 0x29;
+ static const uint8_t VOLTAGE_OFFSET_DV = 20;
+ bool initialDataSent = false;
+ bool inverterStartedUp = false;
+ bool useAsShunt = false;
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,
@@ -71,17 +83,6 @@ class BydCanInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
-
- int16_t temperature_average = 0;
- uint16_t inverter_voltage = 0;
- uint16_t inverter_SOC = 0;
- int16_t inverter_current = 0;
- int16_t inverter_temperature = 0;
- uint16_t remaining_capacity_ah = 0;
- uint16_t fully_charged_capacity_ah = 0;
- long inverter_timestamp = 0;
- bool initialDataSent = false;
- bool inverterStartedUp = false;
};
#endif
diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp
index 4351a1003..2407e2b30 100644
--- a/Software/src/inverter/BYD-MODBUS.cpp
+++ b/Software/src/inverter/BYD-MODBUS.cpp
@@ -48,18 +48,20 @@ void BydModbusInverter::handle_static_data() {
void BydModbusInverter::handle_update_data_modbusp201_byd() {
if (battery2) {
mbPV[202] = std::min(datalayer.battery.info.total_capacity_Wh + datalayer.battery2.info.total_capacity_Wh,
- static_cast(60000u)); //Cap to 60kWh
+ static_cast(57960u)); //Cap to 58kWh
} else {
- mbPV[202] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast(60000u)); //Cap to 60kWh
+ mbPV[202] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast(57960u)); //Cap to 58kWh
}
- mbPV[205] = (datalayer.battery.info.max_design_voltage_dV); // Max Voltage, if higher Gen24 forces discharge
+ mbPV[205] =
+ std::min(datalayer.battery.info.max_design_voltage_dV,
+ static_cast(4500u)); // Max Voltage, if higher Gen24 forces discharge, cap to 450.0V for Primo
mbPV[206] = (datalayer.battery.info.min_design_voltage_dV); // Min Voltage, if lower Gen24 disables battery
}
void BydModbusInverter::handle_update_data_modbusp301_byd() {
- if (datalayer.battery.status.current_dA == 0) {
+ if (datalayer.battery.status.reported_current_dA == 0) {
bms_char_dis_status = STANDBY;
- } else if (datalayer.battery.status.current_dA < 0) { //Negative value = Discharging
+ } else if (datalayer.battery.status.reported_current_dA < 0) { //Negative value = Discharging
bms_char_dis_status = DISCHARGING;
} else { //Positive value = Charging
bms_char_dis_status = CHARGING;
@@ -83,20 +85,24 @@ void BydModbusInverter::handle_update_data_modbusp301_byd() {
}
mbPV[300] = datalayer.battery.status.bms_status;
mbPV[302] = 128 + bms_char_dis_status;
- mbPV[303] = datalayer.battery.status.reported_soc;
+ if (datalayer.battery.status.reported_soc < 100) {
+ mbPV[303] = 100; //Force SOC to never go below 1% to avoid overdischarge
+ } else {
+ mbPV[303] = datalayer.battery.status.reported_soc;
+ }
if (battery2) {
mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh + datalayer.battery2.info.total_capacity_Wh,
- static_cast(60000u)); //Cap to 60kWh
+ static_cast(57960u)); //Cap to 58kWh
} else {
- mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast(60000u)); //Cap to 60kWh
+ mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast(57960u)); //Cap to 58kWh
}
if (battery2) {
mbPV[305] = std::min(datalayer.battery.status.reported_remaining_capacity_Wh +
datalayer.battery2.status.reported_remaining_capacity_Wh,
- static_cast(60000u)); //Cap to 60kWh
+ static_cast(57960u)); //Cap to 58kWh
} else {
mbPV[305] = std::min(datalayer.battery.status.reported_remaining_capacity_Wh,
- static_cast(60000u)); //Cap to 60kWh
+ static_cast(57960u)); //Cap to 58kWh
}
mbPV[306] = std::min(max_discharge_W, static_cast(30000u)); //Cap to 30000 if exceeding
mbPV[307] = std::min(max_charge_W, static_cast(30000u)); //Cap to 30000 if exceeding
diff --git a/Software/src/inverter/FERROAMP-CAN.cpp b/Software/src/inverter/FERROAMP-CAN.cpp
index 84498d62f..bc7311908 100644
--- a/Software/src/inverter/FERROAMP-CAN.cpp
+++ b/Software/src/inverter/FERROAMP-CAN.cpp
@@ -57,11 +57,11 @@ void FerroampCanInverter::
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
if (datalayer.battery.status.bms_status == FAULT) {
FERROAMP_4251.data.u8[0] = (0x00); // Sleep
- } else if (datalayer.battery.status.current_dA < 0) {
+ } else if (datalayer.battery.status.reported_current_dA < 0) {
FERROAMP_4251.data.u8[0] = (0x01); // Charge
- } else if (datalayer.battery.status.current_dA > 0) {
+ } else if (datalayer.battery.status.reported_current_dA > 0) {
FERROAMP_4251.data.u8[0] = (0x02); // Discharge
- } else if (datalayer.battery.status.current_dA == 0) {
+ } else if (datalayer.battery.status.reported_current_dA == 0) {
FERROAMP_4251.data.u8[0] = (0x03); // Idle
}
@@ -70,8 +70,8 @@ void FerroampCanInverter::
FERROAMP_4211.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
//Current (15.0)
- FERROAMP_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
- FERROAMP_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
+ FERROAMP_4211.data.u8[2] = ((datalayer.battery.status.reported_current_dA + 30000) & 0x00FF);
+ FERROAMP_4211.data.u8[3] = ((datalayer.battery.status.reported_current_dA + 30000) >> 8);
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
FERROAMP_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp
index 772120a6c..4ee76e087 100644
--- a/Software/src/inverter/FOXESS-CAN.cpp
+++ b/Software/src/inverter/FOXESS-CAN.cpp
@@ -13,9 +13,9 @@ below that you can customize, incase you use a lower voltage battery with this p
0b11111111 //0x1875 b2 contains status for operational packs (responding) in binary so 01111111 is pack 8 not operational, 11101101 is pack 5 & 2 not operational
#define NUMBER_OF_PACKS 8 //1-8
#define BATTERY_TYPE_MASTER 0x52 //0x52 is HV2600 V2 BMS master
-#define BATTERY_TYPE_SLAVE 0x82 //0x82 is HV2600 V1, 0x83 is ECS4100 v1, 0x84 is HV2600 V2
-#define FIRMWARE_VERSION_MASTER 0xFF
-#define FIRMWARE_VERSION_SLAVE 0x20
+#define BATTERY_TYPE_SLAVE 0x84 //0x82 is HV2600 V1, 0x83 is ECS4100 v1, 0x84 is HV2600 V2
+#define FIRMWARE_VERSION_MASTER 0x12
+#define FIRMWARE_VERSION_SLAVE 0x1F
//for the PACK_ID (b7 =10,20,30,40,50,60,70,80) then FIRMWARE_VERSION 0x1F = 0001 1111, version is v1.15, and if FIRMWARE_VERSION was 0x20 = 0010 0000 then = v2.0
#define MASTER 0
#define MAX_AC_VOLTAGE 2567 //256.7VAC max
@@ -29,6 +29,17 @@ void FoxessCanInverter::
//Calculate the required values
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
+ //Foxess only supports LFP batteries. We need to fake an LFP cell voltage range if the battery used is not LFP
+ if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
+ //Already LFP, pass thru value
+ cell_tweaked_max_voltage_mV = datalayer.battery.status.cell_max_voltage_mV;
+ cell_tweaked_min_voltage_mV = datalayer.battery.status.cell_min_voltage_mV;
+ } else { //linear interpolation to remap the value from the range [2500-4200] to [2500-3400]
+ cell_tweaked_max_voltage_mV =
+ (2500 + ((datalayer.battery.status.cell_max_voltage_mV - 2500) * (3400 - 2500)) / (4200 - 2500));
+ cell_tweaked_min_voltage_mV =
+ (2500 + ((datalayer.battery.status.cell_min_voltage_mV - 2500) * (3400 - 2500)) / (4200 - 2500));
+ }
//Put the values into the CAN messages
//BMS_Limits
@@ -44,8 +55,9 @@ void FoxessCanInverter::
//BMS_PackData
FOXESS_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK
FOXESS_1873.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
- FOXESS_1873.data.u8[2] = (int8_t)datalayer.battery.status.current_dA; // OK, Signed (Active current in Amps x 10)
- FOXESS_1873.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
+ FOXESS_1873.data.u8[2] =
+ (int8_t)datalayer.battery.status.reported_current_dA; // OK, Signed (Active current in Amps x 10)
+ FOXESS_1873.data.u8[3] = (datalayer.battery.status.reported_current_dA >> 8);
FOXESS_1873.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100); //SOC (0-100%)
FOXESS_1873.data.u8[5] = 0x00;
FOXESS_1873.data.u8[6] = (uint8_t)(datalayer.battery.status.reported_remaining_capacity_Wh / 10);
@@ -56,10 +68,10 @@ void FoxessCanInverter::
FOXESS_1874.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
FOXESS_1874.data.u8[2] = (int8_t)datalayer.battery.status.temperature_min_dC;
FOXESS_1874.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
- FOXESS_1874.data.u8[4] = (uint8_t)(3300); //cut_mv_max (Should we send a limit, or the actual mV?)
- FOXESS_1874.data.u8[5] = (3300 >> 8);
- FOXESS_1874.data.u8[6] = (uint8_t)(3300); //cut_mV_min (Should we send a limit, or the actual mV?)
- FOXESS_1874.data.u8[7] = (3300 >> 8);
+ FOXESS_1874.data.u8[4] = (uint8_t)(cell_tweaked_max_voltage_mV);
+ FOXESS_1874.data.u8[5] = (cell_tweaked_max_voltage_mV >> 8);
+ FOXESS_1874.data.u8[6] = (uint8_t)(cell_tweaked_min_voltage_mV);
+ FOXESS_1874.data.u8[7] = (cell_tweaked_min_voltage_mV >> 8);
//BMS_Status
FOXESS_1875.data.u8[0] = (uint8_t)temperature_average;
@@ -103,7 +115,6 @@ void FoxessCanInverter::
FOXESS_1877.data.u8[5] = (uint8_t)0; //Unused
if (current_pack_info == MASTER) {
FOXESS_1877.data.u8[4] = (uint8_t)BATTERY_TYPE_MASTER;
- FOXESS_1877.data.u8[5] = (uint8_t)0x22; //Unused?
FOXESS_1877.data.u8[6] = (uint8_t)FIRMWARE_VERSION_MASTER;
FOXESS_1877.data.u8[7] = (uint8_t)0x01;
} else { // 1-8
@@ -124,7 +135,7 @@ void FoxessCanInverter::
//Errorcodes and flags
FOXESS_1879.data.u8[0] = (uint8_t)0; // Error codes go here, still unsure of bitmasking
- if (datalayer.battery.status.current_dA > 0) {
+ if (datalayer.battery.status.reported_current_dA > 0) {
FOXESS_1879.data.u8[1] = 0x35; //Charging
} // Mappings taken from https://github.com/FozzieUK/FoxESS-Canbus-Protocol
else {
@@ -138,8 +149,8 @@ void FoxessCanInverter::
if (NUMBER_OF_PACKS > 0) { //div0 safeguard
//We calculate how much each emulated pack should show
- voltage_per_pack = (datalayer.battery.status.voltage_dV / NUMBER_OF_PACKS);
- current_per_pack = (datalayer.battery.status.current_dA / NUMBER_OF_PACKS);
+ voltage_per_pack = (datalayer.battery.status.voltage_dV / NUMBER_OF_PACKS) * 10;
+ current_per_pack = (datalayer.battery.status.reported_current_dA / NUMBER_OF_PACKS);
if (datalayer.battery.status.temperature_max_dC >= 0) {
temperature_max_per_pack = (uint8_t)((datalayer.battery.status.temperature_max_dC / 10) + 40);
} else { // negative values, cap to 0*C for now. Most LFPs are not allowed to go below 0*C.
@@ -160,8 +171,8 @@ void FoxessCanInverter::
FOXESS_0C05.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C05.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C05.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C05.data.u8[6] = 0xD0; //pack_1_volts (53.456V) //TODO, does hardcoded value work?
- FOXESS_0C05.data.u8[7] = 0xD0; //pack_1_volts (53.456V) //Or shall we put in 'voltage_per_pack'
+ FOXESS_0C05.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C05.data.u8[7] = (voltage_per_pack >> 8);
// Pack 2
FOXESS_0C06.data.u8[0] = (uint8_t)current_per_pack;
@@ -170,8 +181,8 @@ void FoxessCanInverter::
FOXESS_0C06.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C06.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C06.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C06.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C06.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C06.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C06.data.u8[7] = (voltage_per_pack >> 8);
// Pack 3
FOXESS_0C07.data.u8[0] = (uint8_t)current_per_pack;
@@ -180,8 +191,8 @@ void FoxessCanInverter::
FOXESS_0C07.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C07.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C07.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C07.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C07.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C07.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C07.data.u8[7] = (voltage_per_pack >> 8);
// Pack 4
FOXESS_0C08.data.u8[0] = (uint8_t)current_per_pack;
@@ -190,8 +201,8 @@ void FoxessCanInverter::
FOXESS_0C08.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C08.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C08.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C08.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C08.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C08.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C08.data.u8[7] = (voltage_per_pack >> 8);
// Pack 5
FOXESS_0C09.data.u8[0] = (uint8_t)current_per_pack;
@@ -200,8 +211,8 @@ void FoxessCanInverter::
FOXESS_0C09.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C09.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C09.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C09.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C09.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C09.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C09.data.u8[7] = (voltage_per_pack >> 8);
// Pack 6
FOXESS_0C0A.data.u8[0] = (uint8_t)current_per_pack;
@@ -210,8 +221,8 @@ void FoxessCanInverter::
FOXESS_0C0A.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C0A.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C0A.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C0A.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C0A.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C0A.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C0A.data.u8[7] = (voltage_per_pack >> 8);
// Pack 7
FOXESS_0C0B.data.u8[0] = (uint8_t)current_per_pack;
@@ -220,8 +231,8 @@ void FoxessCanInverter::
FOXESS_0C0B.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C0B.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C0B.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C0B.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C0B.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C0B.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C0B.data.u8[7] = (voltage_per_pack >> 8);
// Pack 8
FOXESS_0C0C.data.u8[0] = (uint8_t)current_per_pack;
@@ -230,8 +241,8 @@ void FoxessCanInverter::
FOXESS_0C0C.data.u8[3] = (uint8_t)temperature_min_per_pack;
FOXESS_0C0C.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
FOXESS_0C0C.data.u8[5] = 0x0A; //b5-7chg/dis?
- FOXESS_0C0C.data.u8[6] = 0xD0; //pack_1_volts (53.456V)
- FOXESS_0C0C.data.u8[7] = 0xD0; //pack_1_volts (53.456V)
+ FOXESS_0C0C.data.u8[6] = (uint8_t)voltage_per_pack;
+ FOXESS_0C0C.data.u8[7] = (voltage_per_pack >> 8);
//Cellvoltages
/*
diff --git a/Software/src/inverter/FOXESS-CAN.h b/Software/src/inverter/FOXESS-CAN.h
index cd756807f..eee864d6d 100644
--- a/Software/src/inverter/FOXESS-CAN.h
+++ b/Software/src/inverter/FOXESS-CAN.h
@@ -14,6 +14,8 @@ class FoxessCanInverter : public CanInverterProtocol {
private:
int16_t temperature_average = 0;
uint16_t voltage_per_pack = 0;
+ uint16_t cell_tweaked_max_voltage_mV = 3300;
+ uint16_t cell_tweaked_min_voltage_mV = 3300;
int16_t current_per_pack = 0;
uint8_t temperature_max_per_pack = 0;
uint8_t temperature_min_per_pack = 0;
@@ -81,17 +83,17 @@ class FoxessCanInverter : public CanInverterProtocol {
.ext_ID = true,
.DLC = 8,
.ID = 0x1881,
- .data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
+ .data = {0x00, '6', '0', 'E', 'P', '0', '0', '5'}};
CAN_frame FOXESS_1882 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1882,
- .data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
+ .data = {0x00, '0', '4', '7', 'M', 'A', '0', '5'}};
CAN_frame FOXESS_1883 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x1883,
- .data = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}};
+ .data = {0x00, '2', '\0', '\0', '\0', '\0', '\0', '\0'}};
CAN_frame FOXESS_0C05 = {.FD = false,
.ext_ID = true,
diff --git a/Software/src/inverter/GROWATT-HV-CAN.cpp b/Software/src/inverter/GROWATT-HV-CAN.cpp
index 0a4ab2e58..d1b06d0b2 100644
--- a/Software/src/inverter/GROWATT-HV-CAN.cpp
+++ b/Software/src/inverter/GROWATT-HV-CAN.cpp
@@ -3,10 +3,10 @@
#include "../datalayer/datalayer.h"
/* TODO:
-This protocol has not been tested with any inverter. Proceed with extreme caution.
-Search the file for "TODO" to see all the places that might require work /*
+This protocol has been tested with SPH10000THBL3-UP Inverter
+Search the file for "TODO" to see all the places that might require work
-/* Growatt BMS CAN-Bus-protocol High Voltage V1.10 2023-11-06
+Growatt BMS CAN-Bus-protocol High Voltage V1.10 2023-11-06
29-bit identifier
500kBit/sec
Big-endian
@@ -18,17 +18,50 @@ Module - A battery module composed of 16 strings of cells
Pack - A battery pack composed of the BMS and battery modules connected in parallel and series, which can work independently
FCC - Full charge capacity
RM - Remaining capacity
-BMS - Battery Information Collector*/
+BMS - Battery Information Collector */
void GrowattHvInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
+ // ---- Cell/module topology (Growatt frames 0x3150 and 0x3180) ----
+ // Previous revisions hard-coded TOTAL_NUMBER_OF_CELLS=300 and NUMBER_OF_MODULES_IN_SERIES=20.
+ // BMS that provides the actual series cell count in datalayer.battery.info.number_of_cells (e.g. 119).
+ // Use that when it is sane. Keep the hard-coded values as a fallback for integrations that do not populate
+ // number_of_cells (e.g. some Pylon paths set it to a placeholder value).
+ uint16_t total_cells = TOTAL_NUMBER_OF_CELLS;
+ if (datalayer.battery.info.number_of_cells >= 10) {
+ total_cells = (uint16_t)datalayer.battery.info.number_of_cells;
+ }
+ // If we are using a dynamic cell count, a safe default for "modules in series" is 1 unless your integration
+ // provides a more accurate module topology.
+ uint16_t modules_in_series = NUMBER_OF_MODULES_IN_SERIES;
+ if (total_cells != TOTAL_NUMBER_OF_CELLS) {
+ modules_in_series = 1;
+ }
+
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
- ampere_hours_remaining =
- ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) *
- 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
- ampere_hours_full = ((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) *
- 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
+ // 0x3140 expects capacity in 10mAh units.
+ // capacity_10mAh = Wh * 1000 / dV (because V = dV/10, and 10mAh units = Ah*100)
+ const uint16_t v_dV = datalayer.battery.status.voltage_dV;
+
+ uint32_t full_10mAh = (uint32_t)((uint64_t)datalayer.battery.info.total_capacity_Wh * 1000ULL / v_dV);
+
+ uint32_t rem_10mAh = 0;
+ if (datalayer.battery.status.remaining_capacity_Wh > 0) {
+ rem_10mAh = (uint32_t)((uint64_t)datalayer.battery.status.remaining_capacity_Wh * 1000ULL / v_dV);
+ } else {
+ // Fallback: derive remaining capacity from SOC if remaining Wh is not available.
+ const uint32_t soc_pct = (uint32_t)(datalayer.battery.status.reported_soc / 100); // 0..100
+ rem_10mAh = (uint32_t)((uint64_t)full_10mAh * soc_pct / 100ULL);
+ }
+
+ if (full_10mAh > 0xFFFF)
+ full_10mAh = 0xFFFF;
+ if (rem_10mAh > 0xFFFF)
+ rem_10mAh = 0xFFFF;
+
+ capacity_full_10mAh = (uint16_t)full_10mAh;
+ capacity_remaining_10mAh = (uint16_t)rem_10mAh;
}
//Map values to CAN messages
@@ -88,8 +121,8 @@ void GrowattHvInverter::
GROWATT_3130.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
GROWATT_3130.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Total current (0.1A -300 to 300A)
- GROWATT_3130.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- GROWATT_3130.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ GROWATT_3130.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ GROWATT_3130.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
//Cell max temperature (0.1C) [-40 to 120*C]
GROWATT_3130.data.u8[4] = (datalayer.battery.status.temperature_max_dC >> 8);
GROWATT_3130.data.u8[5] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
@@ -100,11 +133,11 @@ void GrowattHvInverter::
//Battery capacity information
//Remaining capacity (10 mAh) [0.0 ~ 500000.0 mAH]
- GROWATT_3140.data.u8[0] = ((ampere_hours_remaining * 100) >> 8);
- GROWATT_3140.data.u8[1] = ((ampere_hours_remaining * 100) & 0x00FF);
+ GROWATT_3140.data.u8[0] = (capacity_remaining_10mAh >> 8);
+ GROWATT_3140.data.u8[1] = (capacity_remaining_10mAh & 0x00FF);
//Fully charged capacity (10 mAh) [0.0 ~ 500000.0 mAH]
- GROWATT_3140.data.u8[2] = ((ampere_hours_full * 100) >> 8);
- GROWATT_3140.data.u8[3] = ((ampere_hours_full * 100) & 0x00FF);
+ GROWATT_3140.data.u8[2] = (capacity_full_10mAh >> 8);
+ GROWATT_3140.data.u8[3] = (capacity_full_10mAh & 0x00FF);
//Manufacturer code
GROWATT_3140.data.u8[4] = MANUFACTURER_ASCII_0;
GROWATT_3140.data.u8[5] = MANUFACTURER_ASCII_1;
@@ -126,11 +159,11 @@ void GrowattHvInverter::
GROWATT_3150.data.u8[2] = (datalayer.battery.status.temperature_max_dC >> 8);
GROWATT_3150.data.u8[3] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
//Total number of cells
- GROWATT_3150.data.u8[4] = (TOTAL_NUMBER_OF_CELLS >> 8);
- GROWATT_3150.data.u8[5] = (TOTAL_NUMBER_OF_CELLS & 0x00FF);
+ GROWATT_3150.data.u8[4] = (total_cells >> 8);
+ GROWATT_3150.data.u8[5] = (total_cells & 0x00FF);
//Number of modules in series
- GROWATT_3150.data.u8[6] = (NUMBER_OF_MODULES_IN_SERIES >> 8);
- GROWATT_3150.data.u8[7] = (NUMBER_OF_MODULES_IN_SERIES & 0x00FF);
+ GROWATT_3150.data.u8[6] = (modules_in_series >> 8);
+ GROWATT_3150.data.u8[7] = (modules_in_series & 0x00FF);
//Battery fault and voltage number information
//Fault flag bit
@@ -175,8 +208,8 @@ void GrowattHvInverter::
GROWATT_3180.data.u8[2] = (NUMBER_OF_PACKS_IN_PARALLEL >> 8);
GROWATT_3180.data.u8[3] = (NUMBER_OF_PACKS_IN_PARALLEL & 0x00FF);
//Total number of cells (1-65536)
- GROWATT_3180.data.u8[4] = (TOTAL_NUMBER_OF_CELLS >> 8);
- GROWATT_3180.data.u8[5] = (TOTAL_NUMBER_OF_CELLS & 0x00FF);
+ GROWATT_3180.data.u8[4] = (total_cells >> 8);
+ GROWATT_3180.data.u8[5] = (total_cells & 0x00FF);
//Pack number + BIC forward/reverse encoding number
// Bits 0-3: Pack number
// Bits 4-9: Max. number of BIC in forward BIC encoding in daisy- chain communication
diff --git a/Software/src/inverter/GROWATT-HV-CAN.h b/Software/src/inverter/GROWATT-HV-CAN.h
index 44b867456..02f657386 100644
--- a/Software/src/inverter/GROWATT-HV-CAN.h
+++ b/Software/src/inverter/GROWATT-HV-CAN.h
@@ -23,7 +23,27 @@ class GrowattHvInverter : public CanInverterProtocol {
static const int MANUFACTURER_ASCII_0 = 0x47; //G
static const int MANUFACTURER_ASCII_1 = 0x54; //T
- /* Do not change code below unless you are sure what you are doing */
+ unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
+ unsigned long previousMillisBatchSend = 0;
+ uint32_t unix_time = 0;
+ // 0x3140 expects capacity in 10 mAh units.
+ uint16_t capacity_remaining_10mAh = 0;
+ uint16_t capacity_full_10mAh = 0;
+ uint16_t send_times = 0; // Overflows every 18hours. Cumulative number, plus 1 for each transmission
+ uint8_t safety_specification = 0;
+ uint8_t charging_command = 0;
+ uint8_t discharging_command = 0;
+ uint8_t shielding_external_communication_failure = 0;
+ uint8_t clearing_battery_fault =
+ 0; //When PCS receives the forced charge Mark 1 and Cell under- voltage protection fault, it will send 0XAA
+ uint8_t ISO_detection_command = 0;
+ uint8_t sleep_wakeup_control = 0;
+ uint8_t PCS_working_status = 0; //00 standby, 01 operating
+ uint8_t serial_number_counter = 0; //0-1-2-0-1-2...
+ uint8_t can_message_batch_index = 0;
+ const uint8_t delay_between_batches_ms = 10;
+ bool inverter_alive = false;
+ bool time_to_send_1s_data = false;
//Actual content messages
CAN_frame GROWATT_3110 = {.FD = false,
@@ -126,27 +146,6 @@ class GrowattHvInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x3F00,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
-
- unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
- unsigned long previousMillisBatchSend = 0;
- uint32_t unix_time = 0;
- uint16_t ampere_hours_remaining = 0;
- uint16_t ampere_hours_full = 0;
- uint16_t send_times = 0; // Overflows every 18hours. Cumulative number, plus 1 for each transmission
- uint8_t safety_specification = 0;
- uint8_t charging_command = 0;
- uint8_t discharging_command = 0;
- uint8_t shielding_external_communication_failure = 0;
- uint8_t clearing_battery_fault =
- 0; //When PCS receives the forced charge Mark 1 and Cell under- voltage protection fault, it will send 0XAA
- uint8_t ISO_detection_command = 0;
- uint8_t sleep_wakeup_control = 0;
- uint8_t PCS_working_status = 0; //00 standby, 01 operating
- uint8_t serial_number_counter = 0; //0-1-2-0-1-2...
- uint8_t can_message_batch_index = 0;
- const uint8_t delay_between_batches_ms = 10;
- bool inverter_alive = false;
- bool time_to_send_1s_data = false;
};
#endif
diff --git a/Software/src/inverter/GROWATT-LV-CAN.cpp b/Software/src/inverter/GROWATT-LV-CAN.cpp
index 60899840c..055e82927 100644
--- a/Software/src/inverter/GROWATT-LV-CAN.cpp
+++ b/Software/src/inverter/GROWATT-LV-CAN.cpp
@@ -12,7 +12,7 @@ The inverter replies data every second (standard frame/decimal)0x301:*/
void GrowattLvInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
- cell_delta_mV = datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
+ cell_delta_mV = abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
ampere_hours_remaining =
@@ -58,8 +58,8 @@ void GrowattLvInverter::
GROWATT_313.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 8);
GROWATT_313.data.u8[1] = ((datalayer.battery.status.voltage_dV * 10) & 0x00FF);
//Module or system total current (0.1A Sint16)
- GROWATT_313.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- GROWATT_313.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ GROWATT_313.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ GROWATT_313.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
//Cell max temperature (0.1C)
GROWATT_313.data.u8[4] = (datalayer.battery.status.temperature_max_dC >> 8);
GROWATT_313.data.u8[5] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
diff --git a/Software/src/inverter/GROWATT-LV-CAN.h b/Software/src/inverter/GROWATT-LV-CAN.h
index a55a1ef94..06713e9d5 100644
--- a/Software/src/inverter/GROWATT-LV-CAN.h
+++ b/Software/src/inverter/GROWATT-LV-CAN.h
@@ -12,7 +12,14 @@ class GrowattLvInverter : public CanInverterProtocol {
static constexpr const char* Name = "Growatt Low Voltage (48V) protocol via CAN";
private:
- //Actual content messages
+ static const int VOLTAGE_OFFSET_DV = 40; //Offset in deciVolt from max charge voltage and min discharge voltage
+ static const int MAX_VOLTAGE_DV = 630;
+ static const int MIN_VOLTAGE_DV = 410;
+
+ uint16_t cell_delta_mV = 0;
+ uint16_t ampere_hours_remaining = 0;
+ uint16_t ampere_hours_full = 0;
+
CAN_frame GROWATT_311 = {.FD = false, //Voltage and charge limits and status
.ext_ID = false,
.DLC = 8,
@@ -70,14 +77,6 @@ class GrowattLvInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x318,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
-
- static const int VOLTAGE_OFFSET_DV = 40; //Offset in deciVolt from max charge voltage and min discharge voltage
- static const int MAX_VOLTAGE_DV = 630;
- static const int MIN_VOLTAGE_DV = 410;
-
- uint16_t cell_delta_mV = 0;
- uint16_t ampere_hours_remaining = 0;
- uint16_t ampere_hours_full = 0;
};
#endif
diff --git a/Software/src/inverter/GROWATT-WIT-CAN.cpp b/Software/src/inverter/GROWATT-WIT-CAN.cpp
index 4bb4b2dd6..e61744caa 100644
--- a/Software/src/inverter/GROWATT-WIT-CAN.cpp
+++ b/Software/src/inverter/GROWATT-WIT-CAN.cpp
@@ -2,132 +2,676 @@
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
-/* TODO:
-This protocol has not been tested with any inverter. Proceed with extreme caution.
-Search the file for "TODO" to see all the places that might require work
+/* GROWATT BATTERY BMS CAN COMMUNICATION PROTOCOL V1.1 2024.7.19
+ *
+ * Protocol specifications:
+ * - CAN 2.0B Extended Frame (29-bit identifier)
+ * - 500 kbps
+ * - Intel (Little Endian) byte order
+ * - Cycle tolerance: ยฑ5%
+ *
+ * This implementation emulates a Growatt-compatible battery BMS.
+ */
-Largest TODO: Map all CAN message .ID properly
+void GrowattWitInverter::update_values() {
-Example, how should 1AC6XXXX be transmitted? .ID = 0x1AC6, at the time being, but we need to add the targegt address and source address.
+ /* ============================================================
+ * 1AC3: Current/Voltage Limits (100ms cycle)
+ * Basic function - Required for power control
+ * ============================================================ */
-GROWATT BATTERY BMS CAN COMMUNICATION PROTOCOL V1.1 2024.7.19
-29-bit identifier
-500kBit/sec
-Big-endian
+ // Byte 0-1: Max Charge Current (0.1A, 0-10000 = 0-1000A)
+ // Little Endian: low byte first
+ GROWATT_1AC3.data.u8[0] = (datalayer.battery.status.max_charge_current_dA & 0xFF);
+ GROWATT_1AC3.data.u8[1] = (datalayer.battery.status.max_charge_current_dA >> 8);
-Internal CAN: The extended frame CAN_ID is 29 bits, and the 29-bit CAN_ID is divided into five parts:
-PRI, PG, DA, SA, and FSN.
-- PRI: indicates priority.
-- PG: indicates the page number. If the FSN is not enough, you can turn the page to increase the range of the FSN.
-- TA: indicates the target address.
-- SA: indicates the source address.
-- FSN: represents functional domain.
+ // Byte 2-3: Max Discharge Current (0.1A, 0-10000)
+ GROWATT_1AC3.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA & 0xFF);
+ GROWATT_1AC3.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA >> 8);
-(See the Wiki for full documentation explained)
+ // Byte 4-5: Max Charge Voltage (0.1V, 0-15000)
+ uint16_t max_charge_voltage_dV;
+ if (datalayer.battery.settings.user_set_voltage_limits_active) {
+ max_charge_voltage_dV = datalayer.battery.settings.max_user_set_charge_voltage_dV;
+ } else {
+ max_charge_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
+ }
+ GROWATT_1AC3.data.u8[4] = (max_charge_voltage_dV & 0xFF);
+ GROWATT_1AC3.data.u8[5] = (max_charge_voltage_dV >> 8);
-Example: 0x1AC0FFF3: PRI is 6, PG is 2, FSN is 0xC0, TA is 0xFF, SA is 0xF3
+ // Byte 6-7: Min Discharge Voltage (0.1V, 0-15000)
+ uint16_t min_discharge_voltage_dV;
+ if (datalayer.battery.settings.user_set_voltage_limits_active) {
+ min_discharge_voltage_dV = datalayer.battery.settings.max_user_set_discharge_voltage_dV;
+ } else {
+ min_discharge_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
+ }
+ GROWATT_1AC3.data.u8[6] = (min_discharge_voltage_dV & 0xFF);
+ GROWATT_1AC3.data.u8[7] = (min_discharge_voltage_dV >> 8);
-*/
+ /* ============================================================
+ * 1AC4: CV Voltage (100ms cycle)
+ * Constant voltage charging threshold
+ * ============================================================ */
-void GrowattWitInverter::update_values() {
+ // Byte 0-3: Reserved
+ GROWATT_1AC4.data.u8[0] = 0x00;
+ GROWATT_1AC4.data.u8[1] = 0x00;
+ GROWATT_1AC4.data.u8[2] = 0x00;
+ GROWATT_1AC4.data.u8[3] = 0x00;
+
+ // Byte 4-5: CV Voltage (0.1V)
+ // Set CV voltage slightly below max charge voltage (10V = 100 dV below)
+ uint16_t cv_voltage_dV = max_charge_voltage_dV > 100 ? max_charge_voltage_dV - 100 : max_charge_voltage_dV;
+ GROWATT_1AC4.data.u8[4] = (cv_voltage_dV & 0xFF);
+ GROWATT_1AC4.data.u8[5] = (cv_voltage_dV >> 8);
+
+ // Byte 6-7: Reserved
+ GROWATT_1AC4.data.u8[6] = 0x00;
+ GROWATT_1AC4.data.u8[7] = 0x00;
+
+ /* ============================================================
+ * 1AC5: Working Status (100ms cycle)
+ * BMS status and charge/discharge flags
+ * ============================================================ */
- //Maximum allowable charging current of the battery system, 0-10000 dA
- GROWATT_1AC3XXXX.data.u8[0] = (datalayer.battery.status.max_charge_current_dA >> 8);
- GROWATT_1AC3XXXX.data.u8[1] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
- //Maximum allowable discharge current of the battery system, 0-10000 dA
- GROWATT_1AC3XXXX.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA >> 8);
- GROWATT_1AC3XXXX.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
- //Maximum charging voltage of the battery system 0-15000 dV
- //Stop charging when the current battery output voltage is greater than or equal to this value
- if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
- //User specified charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
- GROWATT_1AC3XXXX.data.u8[4] = (datalayer.battery.settings.max_user_set_charge_voltage_dV >> 8);
- GROWATT_1AC3XXXX.data.u8[5] = (datalayer.battery.settings.max_user_set_charge_voltage_dV & 0x00FF);
- GROWATT_1AC3XXXX.data.u8[6] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV >> 8);
- GROWATT_1AC3XXXX.data.u8[7] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV & 0x00FF);
+ // Byte 0: BMS Working Status
+ // 0=Init, 1=Standby, 2=Charging, 3=Discharging, 4=Shutdown, 5=Fault, 6=Upgrade
+ if (datalayer.battery.status.bms_status == FAULT) {
+ GROWATT_1AC5.data.u8[0] = 5; // Fault
+ } else if (datalayer.battery.status.current_dA > 5) {
+ GROWATT_1AC5.data.u8[0] = 2; // Charging (current > 0.5A)
+ } else if (datalayer.battery.status.current_dA < -5) {
+ GROWATT_1AC5.data.u8[0] = 3; // Discharging (current < -0.5A)
} else {
- //Battery max voltage used as charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
- GROWATT_1AC3XXXX.data.u8[4] = (datalayer.battery.info.max_design_voltage_dV >> 8);
- GROWATT_1AC3XXXX.data.u8[5] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
- GROWATT_1AC3XXXX.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV >> 8);
- GROWATT_1AC3XXXX.data.u8[7] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
- }
-
- //CV voltage value
- //Total charge voltage of constant voltage (set to 10.0V below max voltage for now)
- //When the battery output voltage is greater than or equal to this value, enter constant voltage charging state.
- if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
- //User specified charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
- GROWATT_1AC4XXXX.data.u8[4] = ((datalayer.battery.settings.max_user_set_charge_voltage_dV - 100) >> 8);
- GROWATT_1AC4XXXX.data.u8[5] = ((datalayer.battery.settings.max_user_set_charge_voltage_dV - 100) & 0x00FF);
+ GROWATT_1AC5.data.u8[0] = 1; // Standby
+ }
+
+ // Byte 1: Charge Flag
+ // Bit 0: 0=Allow charge, 1=Prohibit charge
+ // Bit 1: 0=Force charge OFF, 1=Force charge ON
+ uint8_t charge_flag = 0x00;
+ if (datalayer.battery.status.max_charge_current_dA == 0 || datalayer.battery.status.reported_soc >= 10000 || // 100%
+ datalayer.battery.status.bms_status == FAULT) {
+ charge_flag |= 0x01; // Prohibit charge
+ }
+ GROWATT_1AC5.data.u8[1] = charge_flag;
+
+ // Byte 2: Discharge Flag
+ // Bit 0: 0=Allow discharge, 1=Prohibit discharge
+ // Bit 1: 0=Force discharge OFF, 1=Force discharge ON
+ // Bit 3: 0=Normal, 1=Soft starting
+ uint8_t discharge_flag = 0x00;
+ if (datalayer.battery.status.max_discharge_current_dA == 0 || datalayer.battery.status.reported_soc == 0 ||
+ datalayer.battery.status.bms_status == FAULT) {
+ discharge_flag |= 0x01; // Prohibit discharge
+ }
+ GROWATT_1AC5.data.u8[2] = discharge_flag;
+
+ // Byte 3-7: Reserved
+ GROWATT_1AC5.data.u8[3] = 0x00;
+ GROWATT_1AC5.data.u8[4] = 0x00;
+ GROWATT_1AC5.data.u8[5] = 0x00;
+ GROWATT_1AC5.data.u8[6] = 0x00;
+ GROWATT_1AC5.data.u8[7] = 0x00;
+
+ /* ============================================================
+ * 1AC6: SOC/SOH/Capacity (500ms cycle)
+ * ============================================================ */
+
+ // Byte 0: SOC (0-100%)
+ GROWATT_1AC6.data.u8[0] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
+
+ // Byte 1: SOH (0-100%, bit7 = scrap warning if 1)
+ uint8_t soh = (uint8_t)(datalayer.battery.status.soh_pptt / 100);
+ if (soh < 50) {
+ soh |= 0x80; // Set scrap warning bit if SOH < 50%
+ }
+ GROWATT_1AC6.data.u8[1] = soh;
+
+ // Byte 2-3: Rated Capacity (0.1Ah, 0-50000)
+ // Convert Wh to Ah: Ah = Wh / V
+ uint16_t rated_capacity_dAh = 0;
+ if (datalayer.battery.status.voltage_dV > 0) {
+ // total_capacity_Wh / (voltage_dV / 10) = capacity_Ah
+ // capacity_dAh = capacity_Ah * 10 = total_capacity_Wh * 100 / voltage_dV
+ uint32_t capacity_calc = (datalayer.battery.info.total_capacity_Wh * 100UL) / datalayer.battery.status.voltage_dV;
+ // Clamp to uint16_t max (50000 per protocol, but allow up to 65535)
+ rated_capacity_dAh = (capacity_calc > 50000) ? 50000 : (uint16_t)capacity_calc;
+ }
+ GROWATT_1AC6.data.u8[2] = (rated_capacity_dAh & 0xFF);
+ GROWATT_1AC6.data.u8[3] = (rated_capacity_dAh >> 8);
+
+ // Byte 4-7: Reserved
+ GROWATT_1AC6.data.u8[4] = 0x00;
+ GROWATT_1AC6.data.u8[5] = 0x00;
+ GROWATT_1AC6.data.u8[6] = 0x00;
+ GROWATT_1AC6.data.u8[7] = 0x00;
+
+ /* ============================================================
+ * 1AC7: Voltage/Current (100ms cycle)
+ * ============================================================ */
+
+ // Byte 0-1: Reserved
+ GROWATT_1AC7.data.u8[0] = 0x00;
+ GROWATT_1AC7.data.u8[1] = 0x00;
+
+ // Byte 2-3: Battery Voltage (0.1V, 0-15000)
+ GROWATT_1AC7.data.u8[2] = (datalayer.battery.status.voltage_dV & 0xFF);
+ GROWATT_1AC7.data.u8[3] = (datalayer.battery.status.voltage_dV >> 8);
+
+ // Byte 4-5: Battery Current (0.1A, offset -1000A)
+ // Raw = (Actual + 1000) * 10 = Actual_dA + 10000
+ int16_t current_dA = datalayer.battery.status.current_dA;
+ uint16_t current_raw = (uint16_t)(current_dA + GROWATT_CURRENT_OFFSET_DA);
+ GROWATT_1AC7.data.u8[4] = (current_raw & 0xFF);
+ GROWATT_1AC7.data.u8[5] = (current_raw >> 8);
+
+ // Byte 6-7: Reserved
+ GROWATT_1AC7.data.u8[6] = 0x00;
+ GROWATT_1AC7.data.u8[7] = 0x00;
+
+ /* ============================================================
+ * 1AC0: System Composition (2000ms cycle)
+ * ============================================================ */
+
+ // Byte 0: Stack Sum (1-10)
+ GROWATT_1AC0.data.u8[0] = 1;
+
+ // Byte 1: Cluster Sum (1-16)
+ GROWATT_1AC0.data.u8[1] = 1;
+
+ // Byte 2: Modules per Cluster (1-32)
+ GROWATT_1AC0.data.u8[2] = 1;
+
+ // Byte 3: Cells per Module (1-32, clamp to protocol limits)
+ uint8_t cells_per_module = datalayer.battery.info.number_of_cells;
+ if (cells_per_module == 0) {
+ cells_per_module = 16; // Default
+ } else if (cells_per_module > 32) {
+ cells_per_module = 32; // Protocol max
+ }
+ GROWATT_1AC0.data.u8[3] = cells_per_module;
+
+ // Byte 4-5: Module Rated Voltage (0.01V)
+ // Use pack voltage as module voltage for single module setup
+ // Clamp to prevent overflow: max 6553.5V (65535 cV)
+ uint32_t voltage_cV_calc = (uint32_t)datalayer.battery.status.voltage_dV * 10;
+ uint16_t module_voltage_cV = (voltage_cV_calc > 65535) ? 65535 : (uint16_t)voltage_cV_calc;
+ GROWATT_1AC0.data.u8[4] = (module_voltage_cV & 0xFF);
+ GROWATT_1AC0.data.u8[5] = (module_voltage_cV >> 8);
+
+ // Byte 6-7: Module Rated Capacity (0.1Ah)
+ GROWATT_1AC0.data.u8[6] = (rated_capacity_dAh & 0xFF);
+ GROWATT_1AC0.data.u8[7] = (rated_capacity_dAh >> 8);
+
+ /* ============================================================
+ * 1AC2: Product Version (Event - triggered by PCS)
+ * ============================================================ */
+
+ // Byte 0: Cell Model (1=LFP_50Ah, 2=NCM_50Ah, 3=LFP_100Ah, 4=LFP_280Ah)
+ GROWATT_1AC2.data.u8[0] = 3; // LFP_100Ah as default
+
+ // Byte 1: Cell Manufacturer (1=RIPO, 2=EVE, 3=BYD, 4=ATL, 5=CATL, 6=SUNWODA)
+ GROWATT_1AC2.data.u8[1] = 5; // CATL as default
+
+ // Byte 2: Pack Foundry (0=default)
+ GROWATT_1AC2.data.u8[2] = 0;
+
+ // Byte 3: Pack Solution Provider (0=default)
+ GROWATT_1AC2.data.u8[3] = 0;
+
+ // Byte 4: Pack Version (1=AL1, etc.)
+ GROWATT_1AC2.data.u8[4] = 1;
+
+ // Byte 5: BMS HW Version
+ GROWATT_1AC2.data.u8[5] = 1;
+
+ // Byte 6: BMS HW Mode (0=default)
+ GROWATT_1AC2.data.u8[6] = 0;
+
+ // Byte 7: Reserved
+ GROWATT_1AC2.data.u8[7] = 0;
+
+ /* ============================================================
+ * 1AC8: Software Version (500ms cycle)
+ * ============================================================ */
+
+ // Byte 0-3: Software Code (ASCII, e.g., "EMUL")
+ GROWATT_1AC8.data.u8[0] = 'E';
+ GROWATT_1AC8.data.u8[1] = 'M';
+ GROWATT_1AC8.data.u8[2] = 'U';
+ GROWATT_1AC8.data.u8[3] = 'L';
+
+ // Byte 4-5: Monitor Version
+ GROWATT_1AC8.data.u8[4] = 0x01;
+ GROWATT_1AC8.data.u8[5] = 0x00;
+
+ // Byte 6: BOOT Version
+ GROWATT_1AC8.data.u8[6] = 0x01;
+
+ // Byte 7: Software Subversion
+ GROWATT_1AC8.data.u8[7] = 0x00;
+
+ /* ============================================================
+ * 1AC9: Max Module Voltage (1000ms cycle)
+ * ============================================================ */
+
+ // Byte 0: Stack Number (1-10)
+ GROWATT_1AC9.data.u8[0] = 1;
+
+ // Byte 1: Cluster Number (1-10)
+ GROWATT_1AC9.data.u8[1] = 1;
+
+ // Byte 2: Module Number (1-100)
+ GROWATT_1AC9.data.u8[2] = 1;
+
+ // Byte 3: Reserved
+ GROWATT_1AC9.data.u8[3] = 0;
+
+ // Byte 4-5: Max Module Voltage (0.01V)
+ // For single module, use pack voltage
+ GROWATT_1AC9.data.u8[4] = (module_voltage_cV & 0xFF);
+ GROWATT_1AC9.data.u8[5] = (module_voltage_cV >> 8);
+
+ // Byte 6-7: Avg Module Voltage (0.01V)
+ GROWATT_1AC9.data.u8[6] = (module_voltage_cV & 0xFF);
+ GROWATT_1AC9.data.u8[7] = (module_voltage_cV >> 8);
+
+ /* ============================================================
+ * 1ACA: Min Module Voltage (1000ms cycle)
+ * ============================================================ */
+
+ // Byte 0-2: Stack/Cluster/Module Number
+ GROWATT_1ACA.data.u8[0] = 1;
+ GROWATT_1ACA.data.u8[1] = 1;
+ GROWATT_1ACA.data.u8[2] = 1;
+
+ // Byte 3: Reserved
+ GROWATT_1ACA.data.u8[3] = 0;
+
+ // Byte 4-5: Min Module Voltage (0.01V)
+ GROWATT_1ACA.data.u8[4] = (module_voltage_cV & 0xFF);
+ GROWATT_1ACA.data.u8[5] = (module_voltage_cV >> 8);
+
+ // Byte 6-7: Reserved
+ GROWATT_1ACA.data.u8[6] = 0;
+ GROWATT_1ACA.data.u8[7] = 0;
+
+ /* ============================================================
+ * 1ACC: Max Cell Temperature (1000ms cycle)
+ * ============================================================ */
+
+ // Byte 0-3: Stack/Cluster/Module/Cell Number
+ GROWATT_1ACC.data.u8[0] = 1;
+ GROWATT_1ACC.data.u8[1] = 1;
+ GROWATT_1ACC.data.u8[2] = 1;
+ GROWATT_1ACC.data.u8[3] = 1;
+
+ // Byte 4-5: Max Cell Temp (0.1ยฐC, offset -40ยฐC)
+ // Raw = (Actual + 40) * 10 = Actual_dC + 400
+ // Clamp to valid range: -40ยฐC to 125ยฐC (-400 to 1250 dC, raw 0 to 1650)
+ int16_t temp_max_dC = datalayer.battery.status.temperature_max_dC;
+ int32_t temp_max_calc = temp_max_dC + TEMP_OFFSET_DC;
+ uint16_t temp_max_raw = (temp_max_calc < 0) ? 0 : (temp_max_calc > 1650) ? 1650 : (uint16_t)temp_max_calc;
+ GROWATT_1ACC.data.u8[4] = (temp_max_raw & 0xFF);
+ GROWATT_1ACC.data.u8[5] = (temp_max_raw >> 8);
+
+ // Byte 6-7: Avg Cell Temp (0.1ยฐC, offset -40ยฐC)
+ // Use average of min and max
+ int16_t temp_avg_dC = (datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2;
+ int32_t temp_avg_calc = temp_avg_dC + TEMP_OFFSET_DC;
+ uint16_t temp_avg_raw = (temp_avg_calc < 0) ? 0 : (temp_avg_calc > 1650) ? 1650 : (uint16_t)temp_avg_calc;
+ GROWATT_1ACC.data.u8[6] = (temp_avg_raw & 0xFF);
+ GROWATT_1ACC.data.u8[7] = (temp_avg_raw >> 8);
+
+ /* ============================================================
+ * 1ACD: Min Cell Temperature (1000ms cycle)
+ * ============================================================ */
+
+ // Byte 0-3: Stack/Cluster/Module/Cell Number
+ GROWATT_1ACD.data.u8[0] = 1;
+ GROWATT_1ACD.data.u8[1] = 1;
+ GROWATT_1ACD.data.u8[2] = 1;
+ GROWATT_1ACD.data.u8[3] = 2;
+
+ // Byte 4-5: Min Cell Temp (0.1ยฐC, offset -40ยฐC)
+ // Clamp to valid range: -40ยฐC to 125ยฐC
+ int16_t temp_min_dC = datalayer.battery.status.temperature_min_dC;
+ int32_t temp_min_calc = temp_min_dC + TEMP_OFFSET_DC;
+ uint16_t temp_min_raw = (temp_min_calc < 0) ? 0 : (temp_min_calc > 1650) ? 1650 : (uint16_t)temp_min_calc;
+ GROWATT_1ACD.data.u8[4] = (temp_min_raw & 0xFF);
+ GROWATT_1ACD.data.u8[5] = (temp_min_raw >> 8);
+
+ // Byte 6-7: Environment Temp (use min temp as approximation)
+ GROWATT_1ACD.data.u8[6] = (temp_min_raw & 0xFF);
+ GROWATT_1ACD.data.u8[7] = (temp_min_raw >> 8);
+
+ /* ============================================================
+ * 1ACE: Max Cell Voltage (100ms cycle)
+ * ============================================================ */
+
+ // Byte 0-3: Stack/Cluster/Module/Cell Number
+ GROWATT_1ACE.data.u8[0] = 1;
+ GROWATT_1ACE.data.u8[1] = 1;
+ GROWATT_1ACE.data.u8[2] = 1;
+ GROWATT_1ACE.data.u8[3] = 1;
+
+ // Byte 4-5: Max Cell Voltage (1mV, 0-5000)
+ uint16_t cell_max_mV = datalayer.battery.status.cell_max_voltage_mV;
+ GROWATT_1ACE.data.u8[4] = (cell_max_mV & 0xFF);
+ GROWATT_1ACE.data.u8[5] = (cell_max_mV >> 8);
+
+ // Byte 6-7: Avg Cell Voltage (1mV, 0-5000)
+ uint16_t cell_avg_mV =
+ (datalayer.battery.status.cell_max_voltage_mV + datalayer.battery.status.cell_min_voltage_mV) / 2;
+ GROWATT_1ACE.data.u8[6] = (cell_avg_mV & 0xFF);
+ GROWATT_1ACE.data.u8[7] = (cell_avg_mV >> 8);
+
+ /* ============================================================
+ * 1ACF: Min Cell Voltage (100ms cycle)
+ * ============================================================ */
+
+ // Byte 0-3: Stack/Cluster/Module/Cell Number
+ GROWATT_1ACF.data.u8[0] = 1;
+ GROWATT_1ACF.data.u8[1] = 1;
+ GROWATT_1ACF.data.u8[2] = 1;
+ GROWATT_1ACF.data.u8[3] = 2;
+
+ // Byte 4-5: Min Cell Voltage (1mV, 0-5000)
+ uint16_t cell_min_mV = datalayer.battery.status.cell_min_voltage_mV;
+ GROWATT_1ACF.data.u8[4] = (cell_min_mV & 0xFF);
+ GROWATT_1ACF.data.u8[5] = (cell_min_mV >> 8);
+
+ // Byte 6-7: Reserved
+ GROWATT_1ACF.data.u8[6] = 0;
+ GROWATT_1ACF.data.u8[7] = 0;
+
+ /* ============================================================
+ * 1AD0: Cluster SOC Info (1000ms cycle)
+ * ============================================================ */
+
+ uint8_t soc_percent = (uint8_t)(datalayer.battery.status.reported_soc / 100);
+
+ // Byte 0-1: Max SOC Stack/Cluster
+ GROWATT_1AD0.data.u8[0] = 1;
+ GROWATT_1AD0.data.u8[1] = 1;
+
+ // Byte 2-3: Min SOC Stack/Cluster
+ GROWATT_1AD0.data.u8[2] = 1;
+ GROWATT_1AD0.data.u8[3] = 1;
+
+ // Byte 4: Max SOC (0-100%)
+ GROWATT_1AD0.data.u8[4] = soc_percent;
+
+ // Byte 5: Min SOC (0-100%)
+ GROWATT_1AD0.data.u8[5] = soc_percent;
+
+ // Byte 6: Avg SOC (0-100%)
+ GROWATT_1AD0.data.u8[6] = soc_percent;
+
+ // Byte 7: Reserved
+ GROWATT_1AD0.data.u8[7] = 0;
+
+ /* ============================================================
+ * 1AD1: Cumulative Energy (1000ms cycle)
+ * ============================================================ */
+
+ // Byte 0-2: Accumulated Charge (0.1kWh, U24)
+ // Convert Wh to 0.1kWh: value = Wh / 100
+ uint32_t acc_charge_01kWh = 0;
+ if (datalayer.battery.status.total_charged_battery_Wh > 0) {
+ acc_charge_01kWh = (uint32_t)(datalayer.battery.status.total_charged_battery_Wh / 100);
+ }
+ GROWATT_1AD1.data.u8[0] = (acc_charge_01kWh & 0xFF);
+ GROWATT_1AD1.data.u8[1] = ((acc_charge_01kWh >> 8) & 0xFF);
+ GROWATT_1AD1.data.u8[2] = ((acc_charge_01kWh >> 16) & 0xFF);
+
+ // Byte 3-5: Accumulated Discharge (0.1kWh, U24)
+ uint32_t acc_discharge_01kWh = 0;
+ if (datalayer.battery.status.total_discharged_battery_Wh > 0) {
+ acc_discharge_01kWh = (uint32_t)(datalayer.battery.status.total_discharged_battery_Wh / 100);
+ }
+ GROWATT_1AD1.data.u8[3] = (acc_discharge_01kWh & 0xFF);
+ GROWATT_1AD1.data.u8[4] = ((acc_discharge_01kWh >> 8) & 0xFF);
+ GROWATT_1AD1.data.u8[5] = ((acc_discharge_01kWh >> 16) & 0xFF);
+
+ // Byte 6-7: Charge/Discharge Power (1W, offset -32000W)
+ // Raw = Power_W + 32000, valid range 0-65535 (represents -32000W to +33535W)
+ int32_t power_W = datalayer.battery.status.active_power_W;
+ int32_t power_calc = power_W + 32000;
+ uint16_t power_raw;
+ if (power_calc < 0) {
+ power_raw = 0; // Clamp to min (-32000W)
+ } else if (power_calc > 65535) {
+ power_raw = 65535; // Clamp to max (+33535W)
} else {
- //Battery max voltage used as charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 0, MAX 1000V)
- GROWATT_1AC4XXXX.data.u8[4] = ((datalayer.battery.info.max_design_voltage_dV - 100) >> 8);
- GROWATT_1AC4XXXX.data.u8[5] = ((datalayer.battery.info.max_design_voltage_dV - 100) & 0x00FF);
+ power_raw = (uint16_t)power_calc;
+ }
+ GROWATT_1AD1.data.u8[6] = (power_raw & 0xFF);
+ GROWATT_1AD1.data.u8[7] = (power_raw >> 8);
+
+ /* ============================================================
+ * 1AD8: Fault Information 3 (100ms cycle)
+ * Charge/Discharge Alarms and Protections
+ * ============================================================ */
+
+ uint16_t charge_alarm = 0;
+ uint16_t discharge_alarm = 0;
+ uint16_t charge_protection = 0;
+ uint16_t discharge_protection = 0;
+
+ // Check for fault conditions and set appropriate bits
+ if (datalayer.battery.status.bms_status == FAULT) {
+ // Set general fault indicators
+ charge_protection |= 0x01; // Cell overvoltage protection
+ discharge_protection |= 0x01; // Cell undervoltage protection
+ }
+
+ // Check cell voltage limits
+ if (datalayer.battery.status.cell_max_voltage_mV >= datalayer.battery.info.max_cell_voltage_mV) {
+ charge_alarm |= 0x01; // Cell overvoltage alarm
+ charge_protection |= 0x01; // Cell overvoltage protection
+ }
+ if (datalayer.battery.status.cell_min_voltage_mV <= datalayer.battery.info.min_cell_voltage_mV) {
+ discharge_alarm |= 0x01; // Cell undervoltage alarm
+ discharge_protection |= 0x01; // Cell undervoltage protection
}
- //System BMS working status
- if (datalayer.battery.status.current_dA == 0) {
- GROWATT_1AC5XXXX.data.u8[0] = 1; //Standby
- } else if (datalayer.battery.status.current_dA < 0) { //Negative value = Discharging
- GROWATT_1AC5XXXX.data.u8[0] = 3; //Discharging
- } else { //Positive value = Charging
- GROWATT_1AC5XXXX.data.u8[0] = 2; //Charging
+ // Check temperature limits (assuming reasonable limits)
+ if (datalayer.battery.status.temperature_max_dC > 550) { // > 55ยฐC
+ charge_alarm |= 0x10; // Charging temp too high
+ discharge_alarm |= 0x10; // Discharge temp too high
+ charge_protection |= 0x10;
+ discharge_protection |= 0x10;
+ }
+ if (datalayer.battery.status.temperature_min_dC < 0) { // < 0ยฐC
+ charge_alarm |= 0x08; // Charging temp too low
+ charge_protection |= 0x08; // Low charging temp protection
}
+ // Check SOC limits
+ if (soc_percent <= 5) {
+ discharge_alarm |= 0x40; // SOC too low
+ discharge_protection |= 0x40; // SOC too low protection
+ }
+
+ // Byte 0-1: Charging Alarm
+ GROWATT_1AD8.data.u8[0] = (charge_alarm & 0xFF);
+ GROWATT_1AD8.data.u8[1] = (charge_alarm >> 8);
+
+ // Byte 2-3: Discharge Alarm
+ GROWATT_1AD8.data.u8[2] = (discharge_alarm & 0xFF);
+ GROWATT_1AD8.data.u8[3] = (discharge_alarm >> 8);
+
+ // Byte 4-5: Charging Protection
+ GROWATT_1AD8.data.u8[4] = (charge_protection & 0xFF);
+ GROWATT_1AD8.data.u8[5] = (charge_protection >> 8);
+
+ // Byte 6-7: Discharge Protection
+ GROWATT_1AD8.data.u8[6] = (discharge_protection & 0xFF);
+ GROWATT_1AD8.data.u8[7] = (discharge_protection >> 8);
+
+ /* ============================================================
+ * 1AD9: Fault Information 4 (100ms cycle)
+ * Fault codes and generic alarms/protections
+ * ============================================================ */
+
+ uint32_t fault_code = 0;
+ uint16_t generic_alarm = 0;
+ uint16_t generic_protection = 0;
+
+ // Set fault code if BMS is in fault state
if (datalayer.battery.status.bms_status == FAULT) {
- GROWATT_1AC5XXXX.data.u8[0] = 5; //FAULT, Stop using battery!
- }
-
- //SOC 0-100 %
- GROWATT_1AC6XXXX.data.u8[0] = (datalayer.battery.status.reported_soc / 100);
- //SOH (%) (Bit 0~ Bit6 SOH Counters) Bit7 low SOH flag (Indicates that battery is in unsafe use)
- GROWATT_1AC6XXXX.data.u8[1] = (datalayer.battery.status.soh_pptt / 100);
- //Rated battery capacity when new (0-50000) dAH
- GROWATT_1AC6XXXX.data.u8[2]; //TODO
- GROWATT_1AC6XXXX.data.u8[3]; //TODO
-
- //Battery voltage, 0-15000 dV
- GROWATT_1AC7XXXX.data.u8[2] = (datalayer.battery.status.voltage_dV >> 8);
- GROWATT_1AC7XXXX.data.u8[3] = (datalayer.battery.status.voltage_dV & 0x00FF);
- //Battery current, 0-20000dA (offset -1000A)
- // Apply the -1000 offset (add 1000 to the dA value)
- uint32_t current_value = (int32_t)datalayer.battery.status.current_dA + 1000;
-
- // Clamp to uint16_t range (0 to 65535)
- if (current_value < 0) {
- current_value = 0;
- } else if (current_value > 65535) {
- current_value = 65535;
- }
- GROWATT_1AC7XXXX.data.u8[4] = (current_value >> 8);
- GROWATT_1AC7XXXX.data.u8[5] = (current_value & 0x00FF);
+ fault_code |= 0x01; // Cell fault
+ }
+
+ // Check cell voltage deviation
+ uint16_t cell_deviation = datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
+ if (cell_deviation > datalayer.battery.info.max_cell_voltage_deviation_mV) {
+ generic_alarm |= 0x01; // Cell voltage difference excessive
+ generic_protection |= 0x01; // Cell voltage difference protection
+ }
+
+ // Check temperature difference
+ int16_t temp_diff = datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC;
+ if (temp_diff > 150) { // > 15ยฐC difference
+ generic_alarm |= 0x40; // Cell temp difference too high
+ }
+
+ // Byte 0-3: Fault code (U32)
+ GROWATT_1AD9.data.u8[0] = (fault_code & 0xFF);
+ GROWATT_1AD9.data.u8[1] = ((fault_code >> 8) & 0xFF);
+ GROWATT_1AD9.data.u8[2] = ((fault_code >> 16) & 0xFF);
+ GROWATT_1AD9.data.u8[3] = ((fault_code >> 24) & 0xFF);
+
+ // Byte 4-5: Generic Alarm
+ GROWATT_1AD9.data.u8[4] = (generic_alarm & 0xFF);
+ GROWATT_1AD9.data.u8[5] = (generic_alarm >> 8);
+
+ // Byte 6-7: Generic Protection
+ GROWATT_1AD9.data.u8[6] = (generic_protection & 0xFF);
+ GROWATT_1AD9.data.u8[7] = (generic_protection >> 8);
+
+ /* ============================================================
+ * 1A80: BMS Software Version (Event)
+ * ============================================================ */
+
+ // Byte 0-3: BMS Software Code (ASCII)
+ GROWATT_1A80.data.u8[0] = 'B';
+ GROWATT_1A80.data.u8[1] = 'E';
+ GROWATT_1A80.data.u8[2] = 'M';
+ GROWATT_1A80.data.u8[3] = 'U';
+
+ // Byte 4-5: BMS Version
+ GROWATT_1A80.data.u8[4] = 0x01;
+ GROWATT_1A80.data.u8[5] = 0x00;
+
+ // Byte 6-7: DTC (Battery Type) - 13000 = third-party battery
+ uint16_t dtc = 13000;
+ GROWATT_1A80.data.u8[6] = (dtc & 0xFF);
+ GROWATT_1A80.data.u8[7] = (dtc >> 8);
+
+ /* ============================================================
+ * 1A82: Battery Serial Number (Event - multi-frame)
+ * ============================================================ */
+ // Will be sent in transmit_can when triggered
}
void GrowattWitInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
+ // Validate extended frame (29-bit ID required for Growatt WIT protocol)
+ if (!rx_frame.ext_ID) {
+ return; // Ignore standard 11-bit frames
+ }
+
+ // Validate minimum DLC for message parsing
+ if (rx_frame.DLC < 2) {
+ return; // Need at least 2 bytes for most messages
+ }
- uint32_t first4bytes = ((rx_frame.ID & 0xFFFF0000) >> 4);
- //1AB5XXXX becomes 1AB5. Most likely not needed if all PCS messages come from XXXXDFF1
+ // Extract FSN from 29-bit CAN ID
+ uint8_t fsn = get_fsn_from_id(rx_frame.ID);
- switch (first4bytes) {
- case 0x1AB5: // Heartbeat command, 1000ms
+ switch (fsn) {
+ case FSN_HEARTBEAT: // 1AB5: Heartbeat from PCS (1000ms)
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ inverter_alive = true;
+ // Byte 0-1: Frame count (increments each send)
+ pcs_frame_count = rx_frame.data.u8[0] | ((uint16_t)rx_frame.data.u8[1] << 8);
break;
- case 0x1AB6: // Time and date, 1000ms
+
+ case FSN_DATETIME: // 1AB6: Date/Time from PCS (1000ms)
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ inverter_alive = true;
+ // Could extract date/time here if needed
+ // Byte 0: Year (20-99, actual = 2000 + value)
+ // Byte 1: Month (1-12)
+ // Byte 2: Day (1-31)
+ // Byte 3: Hour (0-24)
+ // Byte 4: Minute (0-59)
+ // Byte 5: Second (0-59)
break;
- case 0x1AB7: // PCS status information, 1000ms
+
+ case FSN_PCS_STATUS: // 1AB7: PCS Status (1000ms)
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ inverter_alive = true;
+ // Byte 0: PCS Working Status (0=Init, 1=Standby, 2=Charging, 3=Discharging, 4=Shutdown, 5=Fault, 6=Upgrade)
+ pcs_working_status = rx_frame.data.u8[0];
+ // Byte 1: PCS Fault Status (0=Init, 1=Fault, 2=Alarm)
+ pcs_fault_status = rx_frame.data.u8[1];
break;
- case 0x1AB8: // Bus voltage setting 1, 50ms
+
+ case FSN_BUS_VOLTAGE: // 1AB8: Bus Voltage Setting (50ms)
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ inverter_alive = true;
+ // Byte 0-1: Bus Voltage Setting (0.1V, 3400-10000 = 340-1000V)
+ pcs_bus_voltage_dV = rx_frame.data.u8[0] | ((uint16_t)rx_frame.data.u8[1] << 8);
break;
- case 0x1ABE: // PCS product information, Non-periodic
+
+ case FSN_PCS_PRODUCT: // 1ABE: PCS Product Info (Event)
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- transmit_can_frame(&GROWATT_1AC2XXXX);
- transmit_can_frame(&GROWATT_1A80XXXX);
- transmit_can_frame(&GROWATT_1A82XXXX);
+ inverter_alive = true;
+ // This message triggers BMS to send event messages (1AC2, 1A80, 1A82)
+ transmit_can_frame(&GROWATT_1AC2);
+ transmit_can_frame(&GROWATT_1A80);
+
+ // Send multi-frame serial number (3 frames)
+ for (sn_frame_counter = 0; sn_frame_counter < 3; sn_frame_counter++) {
+ GROWATT_1A82.data.u8[0] = sn_frame_counter; // Frame number
+ // SN characters (placeholder - "BATTERY-EMULATOR")
+ switch (sn_frame_counter) {
+ case 0:
+ GROWATT_1A82.data.u8[1] = 'B';
+ GROWATT_1A82.data.u8[2] = 'A';
+ GROWATT_1A82.data.u8[3] = 'T';
+ GROWATT_1A82.data.u8[4] = 'T';
+ GROWATT_1A82.data.u8[5] = 'E';
+ GROWATT_1A82.data.u8[6] = 'R';
+ GROWATT_1A82.data.u8[7] = 'Y';
+ break;
+ case 1:
+ GROWATT_1A82.data.u8[1] = '-';
+ GROWATT_1A82.data.u8[2] = 'E';
+ GROWATT_1A82.data.u8[3] = 'M';
+ GROWATT_1A82.data.u8[4] = 'U';
+ GROWATT_1A82.data.u8[5] = 'L';
+ GROWATT_1A82.data.u8[6] = 'A';
+ GROWATT_1A82.data.u8[7] = 'T';
+ break;
+ case 2:
+ GROWATT_1A82.data.u8[1] = 'O';
+ GROWATT_1A82.data.u8[2] = 'R';
+ GROWATT_1A82.data.u8[3] = '0';
+ GROWATT_1A82.data.u8[4] = '0';
+ GROWATT_1A82.data.u8[5] = '1';
+ GROWATT_1A82.data.u8[6] = 0;
+ GROWATT_1A82.data.u8[7] = 0;
+ break;
+ }
+ transmit_can_frame(&GROWATT_1A82);
+ }
break;
+
default:
break;
}
@@ -135,20 +679,67 @@ void GrowattWitInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
void GrowattWitInverter::transmit_can(unsigned long currentMillis) {
- //Send 100ms message
+ // Don't send until inverter is alive (has sent heartbeat)
+ if (!inverter_alive) {
+ return;
+ }
+
+ /* ============================================================
+ * 100ms messages (Basic functions - Required)
+ * ============================================================ */
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
previousMillis100ms = currentMillis;
- transmit_can_frame(&GROWATT_1AC3XXXX);
- transmit_can_frame(&GROWATT_1AC4XXXX);
- transmit_can_frame(&GROWATT_1AC5XXXX);
- transmit_can_frame(&GROWATT_1AC7XXXX);
+ // Basic required messages
+ transmit_can_frame(&GROWATT_1AC3); // Current/Voltage limits
+ transmit_can_frame(&GROWATT_1AC4); // CV voltage
+ transmit_can_frame(&GROWATT_1AC5); // Working status
+ transmit_can_frame(&GROWATT_1AC7); // Voltage/Current
+
+ // Optional monitoring - Cell voltages
+ transmit_can_frame(&GROWATT_1ACE); // Max cell voltage
+ transmit_can_frame(&GROWATT_1ACF); // Min cell voltage
+
+ // Optional monitoring - Fault information
+ transmit_can_frame(&GROWATT_1AD8); // Fault info 3
+ transmit_can_frame(&GROWATT_1AD9); // Fault info 4
}
- //Send 500ms message
+ /* ============================================================
+ * 500ms messages
+ * ============================================================ */
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
previousMillis500ms = currentMillis;
- transmit_can_frame(&GROWATT_1AC6XXXX);
+ transmit_can_frame(&GROWATT_1AC6); // SOC/SOH/Capacity
+ transmit_can_frame(&GROWATT_1AC8); // Software version
+ }
+
+ /* ============================================================
+ * 1000ms messages
+ * ============================================================ */
+ if (currentMillis - previousMillis1000ms >= INTERVAL_1_S) {
+ previousMillis1000ms = currentMillis;
+
+ // Module voltage info
+ transmit_can_frame(&GROWATT_1AC9); // Max module voltage
+ transmit_can_frame(&GROWATT_1ACA); // Min module voltage
+
+ // Temperature info
+ transmit_can_frame(&GROWATT_1ACC); // Max cell temperature
+ transmit_can_frame(&GROWATT_1ACD); // Min cell temperature
+
+ // Cluster and energy info
+ transmit_can_frame(&GROWATT_1AD0); // Cluster SOC info
+ transmit_can_frame(&GROWATT_1AD1); // Cumulative energy
+ }
+
+ /* ============================================================
+ * 2000ms messages
+ * ============================================================ */
+ if (currentMillis - previousMillis2000ms >= INTERVAL_2_S) {
+ previousMillis2000ms = currentMillis;
+
+ transmit_can_frame(&GROWATT_1AC0); // System composition
}
}
diff --git a/Software/src/inverter/GROWATT-WIT-CAN.h b/Software/src/inverter/GROWATT-WIT-CAN.h
index fceef3d52..d2447ea93 100644
--- a/Software/src/inverter/GROWATT-WIT-CAN.h
+++ b/Software/src/inverter/GROWATT-WIT-CAN.h
@@ -1,8 +1,38 @@
#ifndef GROWATT_WIT_CAN_H
#define GROWATT_WIT_CAN_H
+#include
#include "CanInverterProtocol.h"
+/* GROWATT BATTERY BMS CAN COMMUNICATION PROTOCOL V1.1 2024.7.19
+ * CAN 2.0B Extended Frame (29-bit identifier)
+ * 500 kbps
+ * Intel (Little Endian) byte order
+ *
+ * CAN ID Structure (29-bit):
+ * | PRI (28-26) | PG (25-24) | FSN (23-16) | TA (15-8) | SA (7-0) |
+ *
+ * Address definitions:
+ * - 0xF1: Inverter (PCS)
+ * - 0xF3: Battery monitoring (BMS)
+ * - 0xFF: Broadcast to all nodes
+ *
+ * Example: 0x1AC3FFF3 = PRI:6, PG:2, FSN:0xC3, TA:0xFF, SA:0xF3
+ */
+
+// PCS -> BMS message FSN codes (for matching incoming messages)
+#define FSN_HEARTBEAT 0xB5 // 1AB5: Heartbeat from PCS
+#define FSN_DATETIME 0xB6 // 1AB6: Date/Time from PCS
+#define FSN_PCS_STATUS 0xB7 // 1AB7: PCS status
+#define FSN_BUS_VOLTAGE 0xB8 // 1AB8: Bus voltage setting
+#define FSN_PCS_PRODUCT 0xBE // 1ABE: PCS product info (triggers event messages)
+
+// Temperature offset: Actual = Raw * 0.1 - 40.0
+#define TEMP_OFFSET_DC 400 // 40.0ยฐC in deciCelsius
+
+// Current offset: Actual = Raw * 0.1 - 1000.0
+#define GROWATT_CURRENT_OFFSET_DA 10000 // 1000.0A in deciAmpere
+
class GrowattWitInverter : public CanInverterProtocol {
public:
const char* name() override { return Name; }
@@ -12,111 +42,166 @@ class GrowattWitInverter : public CanInverterProtocol {
static constexpr const char* Name = "Growatt WIT compatible battery via CAN";
private:
- /* Do not change code below unless you are sure what you are doing */
-
- //Actual content messages
- CAN_frame GROWATT_1AC3XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC3, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC4XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC4, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC5XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC5, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC6XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC6, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC7XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC7, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC0XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC0, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC2XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC2, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC8XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC8, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AC9XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AC9, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1ACAXXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1ACA, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1ACCXXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1ACC, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1ACDXXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1ACD, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1ACEXXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1ACE, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1ACFXXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1ACF, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AD0XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AD0, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AD1XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AD1, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AD8XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AD8, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1AD9XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1AD9, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1A80XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1A80, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame GROWATT_1A82XXXX = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x1A82, //TODO
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // Helper to extract FSN from 29-bit CAN ID
+ uint8_t get_fsn_from_id(uint32_t can_id) { return (can_id >> 16) & 0xFF; }
+
+ // Timing variables
unsigned long previousMillis100ms = 0;
unsigned long previousMillis500ms = 0;
+ unsigned long previousMillis1000ms = 0;
+ unsigned long previousMillis2000ms = 0;
+
+ uint16_t pcs_frame_count = 0;
+ uint16_t pcs_bus_voltage_dV = 0;
+ uint8_t pcs_working_status = 0;
+ uint8_t pcs_fault_status = 0;
+ uint8_t sn_frame_counter = 0; // For multi-frame SN transmission
+
+ bool inverter_alive = false;
+
+ /* BMS -> PCS Messages (Basic Functions - Required) */
+
+ // 1AC3: Current/Voltage Limits (100ms)
+ CAN_frame GROWATT_1AC3 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC3FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC4: CV Voltage (100ms)
+ CAN_frame GROWATT_1AC4 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC4FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC5: Working Status (100ms)
+ CAN_frame GROWATT_1AC5 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC5FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC6: SOC/SOH/Capacity (500ms)
+ CAN_frame GROWATT_1AC6 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC6FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC7: Voltage/Current (100ms)
+ CAN_frame GROWATT_1AC7 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC7FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ /* BMS -> PCS Messages (Data Monitoring - Optional but recommended) */
+
+ // 1AC0: System Composition (2000ms)
+ CAN_frame GROWATT_1AC0 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC0FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC2: Product Version (Event - triggered by PCS 1ABE)
+ CAN_frame GROWATT_1AC2 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC2FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC8: Software Version (500ms)
+ CAN_frame GROWATT_1AC8 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC8FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AC9: Max Module Voltage (1000ms)
+ CAN_frame GROWATT_1AC9 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AC9FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1ACA: Min Module Voltage (1000ms)
+ CAN_frame GROWATT_1ACA = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1ACAFFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1ACC: Max Cell Temperature (1000ms)
+ CAN_frame GROWATT_1ACC = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1ACCFFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1ACD: Min Cell Temperature (1000ms)
+ CAN_frame GROWATT_1ACD = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1ACDFFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1ACE: Max Cell Voltage (100ms)
+ CAN_frame GROWATT_1ACE = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1ACEFFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1ACF: Min Cell Voltage (100ms)
+ CAN_frame GROWATT_1ACF = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1ACFFFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AD0: Cluster SOC Info (1000ms)
+ CAN_frame GROWATT_1AD0 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AD0FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AD1: Cumulative Energy (1000ms)
+ CAN_frame GROWATT_1AD1 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AD1FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AD8: Fault Information 3 (100ms)
+ CAN_frame GROWATT_1AD8 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AD8FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1AD9: Fault Information 4 (100ms)
+ CAN_frame GROWATT_1AD9 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1AD9FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1A80: BMS Software Version (Event)
+ CAN_frame GROWATT_1A80 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1A80FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+ // 1A82: Battery Serial Number (Event - multi-frame)
+ CAN_frame GROWATT_1A82 = {.FD = false,
+ .ext_ID = true,
+ .DLC = 8,
+ .ID = 0x1A82FFF3,
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif
diff --git a/Software/src/inverter/INVERTERS.cpp b/Software/src/inverter/INVERTERS.cpp
index eab145080..c595b2f10 100644
--- a/Software/src/inverter/INVERTERS.cpp
+++ b/Software/src/inverter/INVERTERS.cpp
@@ -14,6 +14,7 @@ uint16_t user_selected_inverter_cells_per_module = 0;
uint16_t user_selected_inverter_voltage_level = 0;
uint16_t user_selected_inverter_ah_capacity = 0;
uint16_t user_selected_inverter_battery_type = 0;
+uint16_t user_selected_inverter_sungrow_type = 0;
bool user_selected_inverter_ignore_contactors = false;
bool user_selected_pylon_30koffset = false;
bool user_selected_pylon_invert_byteorder = false;
@@ -73,14 +74,11 @@ extern const char* name_for_inverter_type(InverterProtocolType type) {
case InverterProtocolType::SmaBydH:
return SmaBydHInverter::Name;
- case InverterProtocolType::SmaBydHvs:
- return SmaBydHvsInverter::Name;
-
case InverterProtocolType::SmaLv:
return SmaLvInverter::Name;
- case InverterProtocolType::SmaTripower:
- return SmaTripowerInverter::Name;
+ case InverterProtocolType::SmaBydHvs:
+ return SmaBydHvsInverter::Name;
case InverterProtocolType::Sofar:
return SofarInverter::Name;
@@ -96,6 +94,12 @@ extern const char* name_for_inverter_type(InverterProtocolType type) {
case InverterProtocolType::Sungrow:
return SungrowInverter::Name;
+
+ case InverterProtocolType::VCU:
+ return VCUInverter::Name;
+
+ case InverterProtocolType::Highest:
+ return "None";
}
return nullptr;
}
@@ -158,16 +162,12 @@ bool setup_inverter() {
inverter = new SmaBydHInverter();
break;
- case InverterProtocolType::SmaBydHvs:
- inverter = new SmaBydHvsInverter();
- break;
-
case InverterProtocolType::SmaLv:
inverter = new SmaLvInverter();
break;
- case InverterProtocolType::SmaTripower:
- inverter = new SmaTripowerInverter();
+ case InverterProtocolType::SmaBydHvs:
+ inverter = new SmaBydHvsInverter();
break;
case InverterProtocolType::Sofar:
@@ -190,6 +190,10 @@ bool setup_inverter() {
inverter = new SungrowInverter();
break;
+ case InverterProtocolType::VCU:
+ inverter = new VCUInverter();
+ break;
+
case InverterProtocolType::None:
return true;
case InverterProtocolType::Highest:
diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h
index cffcdb736..82783dba0 100644
--- a/Software/src/inverter/INVERTERS.h
+++ b/Software/src/inverter/INVERTERS.h
@@ -19,12 +19,12 @@ extern InverterProtocol* inverter;
#include "SMA-BYD-H-CAN.h"
#include "SMA-BYD-HVS-CAN.h"
#include "SMA-LV-CAN.h"
-#include "SMA-TRIPOWER-CAN.h"
#include "SOFAR-CAN.h"
#include "SOL-ARK-LV-CAN.h"
#include "SOLAX-CAN.h"
#include "SOLXPOW-CAN.h"
#include "SUNGROW-CAN.h"
+#include "VCU-CAN.h"
// Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected.
bool setup_inverter();
@@ -36,6 +36,7 @@ extern uint16_t user_selected_inverter_cells_per_module;
extern uint16_t user_selected_inverter_voltage_level;
extern uint16_t user_selected_inverter_ah_capacity;
extern uint16_t user_selected_inverter_battery_type;
+extern uint16_t user_selected_inverter_sungrow_type;
extern bool user_selected_inverter_ignore_contactors;
extern bool user_selected_pylon_30koffset;
extern bool user_selected_pylon_invert_byteorder;
diff --git a/Software/src/inverter/InverterProtocol.h b/Software/src/inverter/InverterProtocol.h
index 7b9a3f5fb..23f5eddae 100644
--- a/Software/src/inverter/InverterProtocol.h
+++ b/Software/src/inverter/InverterProtocol.h
@@ -17,15 +17,15 @@ enum class InverterProtocolType {
Pylon = 10,
PylonLv = 11,
Schneider = 12,
- SmaBydH = 13,
- SmaBydHvs = 14,
+ SmaBydH = 14,
SmaLv = 15,
- SmaTripower = 16,
+ SmaBydHvs = 16,
Sofar = 17,
Solax = 18,
Solxpow = 19,
SolArkLv = 20,
Sungrow = 21,
+ VCU = 22,
Highest
};
@@ -53,6 +53,9 @@ class InverterProtocol {
virtual bool allows_contactor_closing() { return false; }
virtual bool supports_battery_id() { return false; }
+
+ virtual bool provides_shunt() { return false; }
+ virtual void enable_shunt() {}
};
extern InverterProtocol* inverter;
diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp
index 425e9d292..c7ac53e77 100644
--- a/Software/src/inverter/KOSTAL-RS485.cpp
+++ b/Software/src/inverter/KOSTAL-RS485.cpp
@@ -125,7 +125,7 @@ void KostalInverterProtocol::update_values() {
float2frame(CYCLIC_DATA, (float)average_temperature_dC / 10, 14);
//Only perform this operation when Shunt is in used and set to BMW SBOX
- if (shunt || (user_selected_shunt_type == ShuntType::BmwSbox)) {
+ if (user_selected_shunt_type == ShuntType::BmwSbox) {
float2frame(CYCLIC_DATA, (float)(datalayer.shunt.measured_amperage_mA / 100) / 10, 18);
float2frame(CYCLIC_DATA, (float)(datalayer.shunt.measured_avg1S_amperage_mA / 100) / 10, 22);
@@ -147,8 +147,9 @@ void KostalInverterProtocol::update_values() {
float2frame(CYCLIC_DATA, 0.0, 34);
}
} else {
- float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 18); // Last current
- float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s)
+ float2frame(CYCLIC_DATA, (float)datalayer.battery.status.reported_current_dA / 10, 18); // Last current
+ float2frame(CYCLIC_DATA, (float)datalayer.battery.status.reported_current_dA / 10,
+ 22); // Should be Avg current(1s)
// Close contactors after 7 battery info frames requested
if (f2_startup_count > 7) {
@@ -212,7 +213,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
if (datalayer.system.status.battery_allows_contactor_closing & !contactorMillis) {
contactorMillis = currentMillis;
}
- if (currentMillis - contactorMillis >= INTERVAL_2_S & !RX_allow) {
+ if ((currentMillis - contactorMillis >= INTERVAL_2_S) && !RX_allow) {
dbg_message("RX_allow -> true");
RX_allow = true;
}
diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h
index d67b74380..f86ef58ca 100644
--- a/Software/src/inverter/KOSTAL-RS485.h
+++ b/Software/src/inverter/KOSTAL-RS485.h
@@ -15,8 +15,8 @@ class KostalInverterProtocol : public Rs485InverterProtocol {
int baud_rate() { return 57600; }
void float2frame(uint8_t* arr, float value, uint8_t framepointer);
bool check_kostal_frame_crc(int len);
- // How many value updates we can go without inverter gets reported as missing \
- // e.g. value set to 12, 12*5sec=60seconds without comm before event is raised
+ /* How many value updates we can go without inverter gets reported as missing
+ e.g. value set to 12, 12*5sec=60seconds without comm before event is raised */
const int RS485_HEALTHY = 12;
const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29};
diff --git a/Software/src/inverter/ModbusInverterProtocol.cpp b/Software/src/inverter/ModbusInverterProtocol.cpp
index 244ebab37..e4d22a948 100644
--- a/Software/src/inverter/ModbusInverterProtocol.cpp
+++ b/Software/src/inverter/ModbusInverterProtocol.cpp
@@ -2,8 +2,13 @@
#include "../devboard/utils/logging.h"
#include "../lib/eModbus-eModbus/ModbusServerRTU.h"
+// Fill up the RS485 DE pin with -1 if not available
+// The library ignores it if its defibe to -1
+#ifndef RS485_DE_PIN
+#define RS485_DE_PIN -1
+#endif
// Creates a ModbusRTU server instance with 2000ms timeout
-ModbusInverterProtocol::ModbusInverterProtocol(int serverId) : MBserver(2000) {
+ModbusInverterProtocol::ModbusInverterProtocol(int serverId) : MBserver(2000, RS485_DE_PIN) {
_serverId = serverId;
MBserver.registerWorker(_serverId, READ_HOLD_REGISTER,
diff --git a/Software/src/inverter/PYLON-CAN.cpp b/Software/src/inverter/PYLON-CAN.cpp
index b4a3f955d..e15f64b62 100644
--- a/Software/src/inverter/PYLON-CAN.cpp
+++ b/Software/src/inverter/PYLON-CAN.cpp
@@ -54,8 +54,8 @@ void PylonInverter::
PYLON_421X.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
PYLON_421X.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Current (15.0)
- PYLON_421X.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- PYLON_421X.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ PYLON_421X.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ PYLON_421X.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
PYLON_421X.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
PYLON_421X.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
@@ -123,11 +123,11 @@ void PylonInverter::
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
if (datalayer.battery.status.bms_status == FAULT) {
PYLON_425X.data.u8[0] = (0x00); // Sleep
- } else if (datalayer.battery.status.current_dA < 0) {
+ } else if (datalayer.battery.status.reported_current_dA < 0) {
PYLON_425X.data.u8[0] = (0x01); // Charge
- } else if (datalayer.battery.status.current_dA > 0) {
+ } else if (datalayer.battery.status.reported_current_dA > 0) {
PYLON_425X.data.u8[0] = (0x02); // Discharge
- } else if (datalayer.battery.status.current_dA == 0) {
+ } else if (datalayer.battery.status.reported_current_dA == 0) {
PYLON_425X.data.u8[0] = (0x03); // Idle
}
}
diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp
index 324196c4f..f3f80b9f0 100644
--- a/Software/src/inverter/PYLON-LV-CAN.cpp
+++ b/Software/src/inverter/PYLON-LV-CAN.cpp
@@ -35,8 +35,8 @@ void PylonLvInverter::update_values() {
int16_t temperature = (datalayer.battery.status.temperature_min_dC + datalayer.battery.status.temperature_max_dC) / 2;
PYLON_356.data.u8[0] = voltage_cV & 0xff;
PYLON_356.data.u8[1] = voltage_cV >> 8;
- PYLON_356.data.u8[2] = datalayer.battery.status.current_dA & 0xff;
- PYLON_356.data.u8[3] = datalayer.battery.status.current_dA >> 8;
+ PYLON_356.data.u8[2] = datalayer.battery.status.reported_current_dA & 0xff;
+ PYLON_356.data.u8[3] = datalayer.battery.status.reported_current_dA >> 8;
PYLON_356.data.u8[4] = temperature & 0xff;
PYLON_356.data.u8[5] = temperature >> 8;
@@ -50,7 +50,7 @@ void PylonLvInverter::update_values() {
PYLON_359.data.u8[6] = 0x4E; //N
// ERRORS
- if (datalayer.battery.status.current_dA >= (datalayer.battery.status.max_discharge_current_dA + 10))
+ if (datalayer.battery.status.reported_current_dA >= (datalayer.battery.status.max_discharge_current_dA + 10))
PYLON_359.data.u8[0] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE)
PYLON_359.data.u8[0] |= 0x10;
@@ -60,11 +60,12 @@ void PylonLvInverter::update_values() {
PYLON_359.data.u8[0] |= 0x04;
if (datalayer.battery.status.bms_status == FAULT)
PYLON_359.data.u8[1] |= 0x80;
- if (datalayer.battery.status.current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
+ if (datalayer.battery.status.reported_current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
PYLON_359.data.u8[1] |= 0x01;
// WARNINGS (using same rules as errors but reporting earlier)
- if (datalayer.battery.status.current_dA >= datalayer.battery.status.max_discharge_current_dA * WARNINGS_PERCENT / 100)
+ if (datalayer.battery.status.reported_current_dA >=
+ datalayer.battery.status.max_discharge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <=
warning_threshold_of_min(BATTERY_MINTEMPERATURE, BATTERY_MAXTEMPERATURE))
@@ -75,24 +76,39 @@ void PylonLvInverter::update_values() {
datalayer.battery.info.max_design_voltage_dV))
PYLON_359.data.u8[2] |= 0x04;
// we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal"
- if (datalayer.battery.status.current_dA <=
+ if (datalayer.battery.status.reported_current_dA <=
-1 * datalayer.battery.status.max_charge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[3] |= 0x01;
PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging
if (datalayer.battery.status.bms_status == FAULT)
PYLON_35C.data.u8[0] = 0x00; // disable all
+ else if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV)
+ PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
+ else if (datalayer.battery.status.voltage_dV >= datalayer.battery.info.max_design_voltage_dV)
+ PYLON_35C.data.u8[0] = 0x40; // only allow discharging
else if (datalayer.battery.settings.user_set_voltage_limits_active &&
- datalayer.battery.status.voltage_dV > datalayer.battery.settings.max_user_set_charge_voltage_dV)
+ datalayer.battery.status.voltage_dV >= datalayer.battery.settings.max_user_set_charge_voltage_dV)
PYLON_35C.data.u8[0] = 0x40; // only allow discharging
else if (datalayer.battery.settings.user_set_voltage_limits_active &&
datalayer.battery.status.voltage_dV < datalayer.battery.settings.max_user_set_discharge_voltage_dV)
- PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
+ PYLON_35C.data.u8[0] = 0x80; // enable charing
else if (datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage)
- PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
+ PYLON_35C.data.u8[0] = 0x80; // enable charing
else if (datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage)
PYLON_35C.data.u8[0] = 0x40; // enable discharging only
+ if ((PYLON_35C.data.u8[0] & 0x80) == 0) {
+ // set max charge current to 0 when charging is disabled
+ PYLON_351.data.u8[2] = 0;
+ PYLON_351.data.u8[3] = 0;
+ }
+ if ((PYLON_35C.data.u8[0] & 0x40) == 0) {
+ // set max discharge current to 0 when discharging is disabled
+ PYLON_351.data.u8[4] = 0;
+ PYLON_351.data.u8[5] = 0;
+ }
+
// PYLON_35E is pre-filled with the manufacturer name
}
diff --git a/Software/src/inverter/SCHNEIDER-CAN.cpp b/Software/src/inverter/SCHNEIDER-CAN.cpp
index 31c302ced..4c20f5e68 100644
--- a/Software/src/inverter/SCHNEIDER-CAN.cpp
+++ b/Software/src/inverter/SCHNEIDER-CAN.cpp
@@ -78,10 +78,10 @@ void SchneiderInverter::
SE_323.data.u8[2] = (((datalayer.battery.status.voltage_dV * 10) & 0x0000FF00) >> 8);
SE_323.data.u8[3] = ((datalayer.battery.status.voltage_dV * 10) & 0x000000FF);
//Current (ex 81.00A = 8100) TODO: Note s32 bit, which direction?
- SE_323.data.u8[4] = ((datalayer.battery.status.current_dA * 10) >> 24);
- SE_323.data.u8[5] = (((datalayer.battery.status.current_dA * 10) & 0x00FF0000) >> 16);
- SE_323.data.u8[6] = (((datalayer.battery.status.current_dA * 10) & 0x0000FF00) >> 8);
- SE_323.data.u8[7] = ((datalayer.battery.status.current_dA * 10) & 0x000000FF);
+ SE_323.data.u8[4] = ((datalayer.battery.status.reported_current_dA * 10) >> 24);
+ SE_323.data.u8[5] = (((datalayer.battery.status.reported_current_dA * 10) & 0x00FF0000) >> 16);
+ SE_323.data.u8[6] = (((datalayer.battery.status.reported_current_dA * 10) & 0x0000FF00) >> 8);
+ SE_323.data.u8[7] = ((datalayer.battery.status.reported_current_dA * 10) & 0x000000FF);
//Temperature average
SE_324.data.u8[0] = (temperature_average >> 8);
diff --git a/Software/src/inverter/SMA-BYD-H-CAN.cpp b/Software/src/inverter/SMA-BYD-H-CAN.cpp
index 0b0184e5a..d5705e337 100644
--- a/Software/src/inverter/SMA-BYD-H-CAN.cpp
+++ b/Software/src/inverter/SMA-BYD-H-CAN.cpp
@@ -25,8 +25,7 @@ void SmaBydHInverter::
SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long)
- SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >>
- 8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
+ SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A)
SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
@@ -49,18 +48,31 @@ void SmaBydHInverter::
SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Current (TODO: signed OK?)
- SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ SMA_4D8.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ SMA_4D8.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
//Temperature average
SMA_4D8.data.u8[4] = (temperature_average >> 8);
SMA_4D8.data.u8[5] = (temperature_average & 0x00FF);
//Battery ready
- if (datalayer.battery.status.bms_status == ACTIVE) {
- SMA_4D8.data.u8[6] = READY_STATE;
- } else {
+ if (datalayer.battery.status.bms_status == FAULT) {
SMA_4D8.data.u8[6] = STOP_STATE;
+ } else {
+ SMA_4D8.data.u8[6] = READY_STATE;
}
+ //Highest battery temperature
+ SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
+ SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
+ //Lowest battery temperature
+ SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
+ SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
+ //Sum of all cellvoltages
+ SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
+ SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
+ //Cell min/max voltage (mV / 25)
+ SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
+ SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
+
//Lifetime charged energy amount
SMA_458.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0xFF000000) >> 24;
SMA_458.data.u8[1] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF0000) >> 16;
@@ -78,18 +90,6 @@ void SmaBydHInverter::
} else {
SMA_158.data.u8[2] = 0x6A;
}
- //Highest battery temperature
- SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
- SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- //Lowest battery temperature
- SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
- SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- //Sum of all cellvoltages
- SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
- SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
- //Cell min/max voltage (mV / 25)
- SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
- SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
control_contactor_led();
@@ -162,86 +162,141 @@ void SmaBydHInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1];
inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
break;
+
case 0x3E0: //Message originating from SMA inverter - ?
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x420: //Message originating from SMA inverter - Timestamp
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- //Frame0-3 Timestamp
- /*
- transmit_can_frame(&SMA_158);
- transmit_can_frame(&SMA_358);
- transmit_can_frame(&SMA_3D8);
- transmit_can_frame(&SMA_458);
- transmit_can_frame(&SMA_518);
- transmit_can_frame(&SMA_4D8);
- */
inverter_time =
(rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
break;
+
case 0x560: //Message originating from SMA inverter - Init
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x561:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x562:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x563:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x564:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x565:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x566:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x567:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E0: //Message originating from SMA inverter - String
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
//Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA
break;
+
case 0x5E1:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E2:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E3:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E4:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E5:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E6:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x5E7: //Message originating from SMA inverter - Pairing request
+ /* FALLTHROUGH */
case 0x660: //Message originating from SMA inverter - Pairing request
logging.println("Received SMA pairing request");
pairing_events++;
set_event(EVENT_SMA_PAIRING, pairing_events);
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- transmit_can_init();
+ transmit_can_init = true;
break;
+
case 0x62C:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
default:
break;
}
}
void SmaBydHInverter::transmit_can(unsigned long currentMillis) {
+
+ if (transmit_can_init) {
+
+ // Check if enough time has passed since the last batch
+ if (currentMillis - previousMillisBatch >= delay_between_batches_ms) {
+ previousMillisBatch = currentMillis; // Update the time of the last message batch
+
+ // Send a subset of messages per iteration to avoid overloading the CAN bus / transmit buffer
+ switch (batch_send_index) {
+ case 0:
+ transmit_can_frame(&SMA_558);
+ transmit_can_frame(&SMA_598);
+ transmit_can_frame(&SMA_5D8);
+ break;
+ case 1:
+ transmit_can_frame(&SMA_618_1);
+ transmit_can_frame(&SMA_618_2);
+ transmit_can_frame(&SMA_618_3);
+ break;
+ case 2:
+ transmit_can_frame(&SMA_158);
+ transmit_can_frame(&SMA_358);
+ transmit_can_frame(&SMA_3D8);
+ break;
+ case 3:
+ transmit_can_frame(&SMA_458);
+ transmit_can_frame(&SMA_518);
+ transmit_can_frame(&SMA_4D8);
+ transmit_can_init = false;
+ break;
+ default:
+ break;
+ }
+
+ // Increment message index and wrap around if needed
+ batch_send_index++;
+
+ if (transmit_can_init == false) { //We completed sending the batches
+ batch_send_index = 0;
+ }
+ }
+ }
+
// Send CAN Message every 100ms if inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
@@ -260,18 +315,3 @@ void SmaBydHInverter::transmit_can(unsigned long currentMillis) {
}
}
}
-
-void SmaBydHInverter::transmit_can_init() {
- transmit_can_frame(&SMA_558);
- transmit_can_frame(&SMA_598);
- transmit_can_frame(&SMA_5D8);
- transmit_can_frame(&SMA_618_1);
- transmit_can_frame(&SMA_618_2);
- transmit_can_frame(&SMA_618_3);
- transmit_can_frame(&SMA_158);
- transmit_can_frame(&SMA_358);
- transmit_can_frame(&SMA_3D8);
- transmit_can_frame(&SMA_458);
- transmit_can_frame(&SMA_518);
- transmit_can_frame(&SMA_4D8);
-}
diff --git a/Software/src/inverter/SMA-BYD-H-CAN.h b/Software/src/inverter/SMA-BYD-H-CAN.h
index 8365127a9..fdeac33aa 100644
--- a/Software/src/inverter/SMA-BYD-H-CAN.h
+++ b/Software/src/inverter/SMA-BYD-H-CAN.h
@@ -10,87 +10,103 @@ class SmaBydHInverter : public SmaInverterBase {
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
- static constexpr const char* Name = "SMA compatible BYD H";
+ static constexpr const char* Name = "SMA compatible BYD Battery-Box H";
virtual bool controls_contactor() { return true; }
private:
static const int READY_STATE = 0x03;
static const int STOP_STATE = 0x02;
-
- void transmit_can_init();
-
- unsigned long previousMillis100ms = 0;
+ static const int THIRTY_MINUTES = 1200;
unsigned long previousMillis60s = 0;
+ unsigned long previousMillis100ms = 0;
+ unsigned long previousMillisBatch = 0;
+ uint8_t batch_send_index = 0;
+ const uint8_t delay_between_batches_ms =
+ 7; //TODO, tweak to as low as possible before performance issues/crashes appear
+ bool transmit_can_init = false;
+
uint8_t pairing_events = 0;
uint32_t inverter_time = 0;
uint16_t inverter_voltage = 0;
int16_t inverter_current = 0;
uint16_t timeWithoutInverterAllowsContactorClosing = 0;
- static const int THIRTY_MINUTES = 1200;
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
.ext_ID = false,
.DLC = 8,
- .ID = 0x158,
- .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
+ .ID = 0x158, // All 0xAA, no faults active
+ .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
- .data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x11, 0xA0, 0x07, 0x00, 0x01, 0x5E, 0x00, 0xC8}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
- .data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}};
+ .data = {0x13, 0x2E, 0x27, 0x10, 0x00, 0x45, 0xF9, 0x00}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
- .data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}};
+ .data = {0x00, 0x00, 0x11, 0xC8, 0x00, 0x00, 0x0E, 0xF4}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
- .data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}};
+ .data = {0x10, 0x62, 0x00, 0x16, 0x01, 0x68, 0x03, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
- .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}};
+ .data = {0x01, 0x4A, 0x01, 0x25, 0x10, 0x10, 0xFF, 0xFF}};
+
+ // Pairing/Battery setup information
+
CAN_frame SMA_558 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x558,
- .data = {0x03, 0x12, 0x00, 0x04, 0x00, 0x59, 0x07, 0x07}}; //7x BYD modules, Vendor ID 7 BYD
+ .data = {0x03, 0x13, 0x00, 0x03, 0x00, 0x66, 0x04, 0x07}};
+ // 0x03 |
+ // 0x13 |
+ // 0x00 |
+ // 0x03 | Module count: a 3-module stack
+ // 0x00 |
+ // 0x66 |
+ // 0x04 |
+ // 0x07 |
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
- .data = {0x00, 0x00, 0x12, 0x34, 0x5A, 0xDE, 0x07, 0x4F}}; //B0-4 Serial, rest unknown
+ .data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}}; //B0-4 Serial, rest unknown
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
- .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
+ .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //(0) B Y D (0) (0) (0) (0)
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
- .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
+ .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //(0) B A T T E R Y
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
- .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x39}}; //1 - B O X H
+ .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x31}}; //(1) - B o x H 1
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
- .data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0
+ .data = {0x02, 0x30, 0x2E, 0x32, 0x00, 0x00, 0x00, 0x00}}; //(2) 0 . 2 (0) (0) (0) (0)
+ int16_t discharge_current = 0;
+ int16_t charge_current = 0;
int16_t temperature_average = 0;
uint16_t ampere_hours_remaining = 0;
};
diff --git a/Software/src/inverter/SMA-BYD-HVS-CAN.cpp b/Software/src/inverter/SMA-BYD-HVS-CAN.cpp
index 1c4727aa0..e23c8b9fc 100644
--- a/Software/src/inverter/SMA-BYD-HVS-CAN.cpp
+++ b/Software/src/inverter/SMA-BYD-HVS-CAN.cpp
@@ -3,9 +3,13 @@
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
-/* TODO: Map error bits in 0x158 */
-
-/* Do not change code below unless you are sure what you are doing */
+/* TODO:
+- Figure out the manufacturer info needed in transmit_can_init() CAN messages
+ - CAN logs from real system might be needed
+- Figure out how cellvoltages need to be displayed
+- Figure out if sending transmit_can_init() like we do now is OK
+- Figure out how to send the non-cyclic messages when needed
+*/
void SmaBydHvsInverter::
update_values() { //This function maps all the values fetched from battery CAN to the inverter CAN
@@ -48,16 +52,16 @@ void SmaBydHvsInverter::
SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Current (TODO: signed OK?)
- SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ SMA_4D8.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ SMA_4D8.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
//Temperature average
SMA_4D8.data.u8[4] = (temperature_average >> 8);
SMA_4D8.data.u8[5] = (temperature_average & 0x00FF);
//Battery ready
- if (datalayer.battery.status.bms_status == ACTIVE) {
- SMA_4D8.data.u8[6] = READY_STATE;
- } else {
+ if (datalayer.battery.status.bms_status == FAULT) {
SMA_4D8.data.u8[6] = STOP_STATE;
+ } else {
+ SMA_4D8.data.u8[6] = READY_STATE;
}
//Highest battery temperature
@@ -84,13 +88,6 @@ void SmaBydHvsInverter::
SMA_458.data.u8[6] = (datalayer.battery.status.total_discharged_battery_Wh & 0x0000FF00) >> 8;
SMA_458.data.u8[7] = (datalayer.battery.status.total_discharged_battery_Wh & 0x000000FF);
- //Error bits
- if (datalayer.system.status.battery_allows_contactor_closing) {
- SMA_158.data.u8[2] = 0xAA;
- } else {
- SMA_158.data.u8[2] = 0x6A;
- }
-
control_contactor_led();
// Check if Enable line is working. If we go too long without any input, raise an event
@@ -103,56 +100,6 @@ void SmaBydHvsInverter::
} else {
timeWithoutInverterAllowsContactorClosing = 0;
}
-
- /*
- //SMA_158.data.u8[0] = //bit12 Fault high temperature, bit34Battery cellundervoltage, bit56 Battery cell overvoltage, bit78 batterysystemdefect
- //TODO: add all error bits. Sending message with all 0xAA until that.
-
- 0x158 can be used to send error messages or warnings.
-
- Each message is defined of two bits:
- 01=message triggered
- 10=no message triggered
- 0xA9=10101001, triggers first message
- 0xA6=10100110, triggers second message
- 0x9A=10011010, triggers third message
- 0x6A=01101010, triggers forth message
- bX defines the byte
-
- b0 A9 Battery system defect
- b0 A6 Battery cell overvoltage fault
- b0 9A Battery cell undervoltage fault
- b0 6A Battery high temperature fault
- b1 A9 Battery low temperature fault
- b1 A6 Battery high temperature fault
- b1 9A Battery low temperature fault
- b1 6A Overload (reboot required)
- b2 A9 Overload (reboot required)
- b2 A6 Incorrect switch position for the battery disconnection point
- b2 9A Battery system short circuit
- b2 6A Internal battery hardware fault
- b3 A9 Battery imbalancing fault
- b3 A6 Battery service life expiry
- b3 9A Battery system thermal management defective
- b3 6A Internal battery hardware fault
- b4 A9 Battery system defect (warning)
- b4 A6 Battery cell overvoltage fault (warning)
- b4 9A Battery cell undervoltage fault (warning)
- b4 6A Battery high temperature fault (warning)
- b5 A9 Battery low temperature fault (warning)
- b5 A6 Battery high temperature fault (warning)
- b5 9A Battery low temperature fault (warning)
- b5 6A Self-diagnosis (warning)
- b6 A9 Self-diagnosis (warning)
- b6 A6 Incorrect switch position for the battery disconnection point (warning)
- b6 9A Battery system short circuit (warning)
- b6 6A Internal battery hardware fault (warning)
- b7 A9 Battery imbalancing fault (warning)
- b7 A6 Battery service life expiry (warning)
- b7 9A Battery system thermal management defective (warning)
- b7 6A Internal battery hardware fault (warning)
-
-*/
}
void SmaBydHvsInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
@@ -162,135 +109,111 @@ void SmaBydHvsInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1];
inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
break;
+
case 0x3E0: //Message originating from SMA inverter - ?
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
+
case 0x420: //Message originating from SMA inverter - Timestamp
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
inverter_time =
(rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
break;
+
case 0x560: //Message originating from SMA inverter - Init
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
- case 0x561:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x562:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x563:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x564:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x565:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x566:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x567:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
+
case 0x5E0: //Message originating from SMA inverter - String
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
//Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA
break;
- case 0x5E1:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x5E2:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x5E3:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x5E4:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x5E5:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x5E6:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
+
case 0x5E7: //Message originating from SMA inverter - Pairing request
+ /* FALLTHROUGH */
case 0x660: //Message originating from SMA inverter - Pairing request
logging.println("Received SMA pairing request");
pairing_events++;
set_event(EVENT_SMA_PAIRING, pairing_events);
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- transmit_can_init = true;
- break;
- case 0x62C:
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ transmit_can_init();
break;
+
default:
break;
}
}
-void SmaBydHvsInverter::transmit_can(unsigned long currentMillis) {
-
- if (transmit_can_init) {
-
- // Check if enough time has passed since the last batch
- if (currentMillis - previousMillisBatch >= delay_between_batches_ms) {
- previousMillisBatch = currentMillis; // Update the time of the last message batch
+void SmaBydHvsInverter::pushFrame(CAN_frame* frame, std::function callback) {
+ if (listLength >= 20) {
+ return; //TODO: scream.
+ }
+ framesToSend[listLength] = {
+ .frame = frame,
+ .callback = callback,
+ };
+ listLength++;
+}
- // Send a subset of messages per iteration to avoid overloading the CAN bus / transmit buffer
- switch (batch_send_index) {
- case 0:
- transmit_can_frame(&SMA_558);
- transmit_can_frame(&SMA_598);
- transmit_can_frame(&SMA_5D8);
- break;
- case 1:
- transmit_can_frame(&SMA_618_1);
- transmit_can_frame(&SMA_618_2);
- transmit_can_frame(&SMA_618_3);
- break;
- case 2:
- transmit_can_frame(&SMA_158);
- transmit_can_frame(&SMA_358);
- transmit_can_frame(&SMA_3D8);
- break;
- case 3:
- transmit_can_frame(&SMA_458);
- transmit_can_frame(&SMA_518);
- transmit_can_frame(&SMA_4D8);
- transmit_can_init = false;
- break;
- default:
- break;
- }
+void SmaBydHvsInverter::transmit_can(unsigned long currentMillis) {
- // Increment message index and wrap around if needed
- batch_send_index++;
+ // Send CAN Message only if we're enabled by inverter
+ if (!datalayer.system.status.inverter_allows_contactor_closing) {
+ return;
+ }
- if (transmit_can_init == false) { //We completed sending the batches
- batch_send_index = 0;
- }
+ if (listLength > 0 && currentMillis - previousMillis250ms >= INTERVAL_250_MS) {
+ previousMillis250ms = currentMillis;
+ // Send next frame.
+ Frame frame = framesToSend[0];
+ transmit_can_frame(frame.frame);
+ frame.callback();
+ for (int i = 0; i < listLength - 1; i++) {
+ framesToSend[i] = framesToSend[i + 1];
}
+ listLength--;
}
- // Send CAN Message every 100ms if inverter allows contactor closing
- if (datalayer.system.status.inverter_allows_contactor_closing) {
- if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
- previousMillis100ms = currentMillis;
- transmit_can_frame(&SMA_158);
- transmit_can_frame(&SMA_358);
- transmit_can_frame(&SMA_3D8);
- transmit_can_frame(&SMA_458);
- transmit_can_frame(&SMA_518);
- transmit_can_frame(&SMA_4D8);
- }
- // Send CAN Message every 60s (potentially SMA_458 is not required for stable operation)
- if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
- previousMillis60s = currentMillis;
- transmit_can_frame(&SMA_458);
- }
+ if (!pairing_completed) {
+ return;
}
+
+ // Send CAN Message every 2s
+ if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
+ previousMillis2s = currentMillis;
+ pushFrame(&SMA_358);
+ }
+ // Send CAN Message every 10s
+ if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
+ previousMillis10s = currentMillis;
+ pushFrame(&SMA_518);
+ pushFrame(&SMA_4D8);
+ pushFrame(&SMA_3D8);
+ }
+ // Send CAN Message every 60s (potentially SMA_458 is not required for stable operation)
+ if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
+ previousMillis60s = currentMillis;
+ pushFrame(&SMA_458);
+ }
+}
+
+void SmaBydHvsInverter::completePairing() {
+ pairing_completed = true;
+}
+
+void SmaBydHvsInverter::transmit_can_init() {
+ listLength = 0; // clear all frames
+
+ pushFrame(&SMA_558); //Pairing start - Vendor
+ pushFrame(&SMA_598); //Serial
+ pushFrame(&SMA_5D8); //BYD
+ pushFrame(&SMA_618_0); //BATTERY
+ pushFrame(&SMA_618_1); //-Box Pr
+ pushFrame(&SMA_618_2); //emium H
+ pushFrame(&SMA_618_3); //VS
+ pushFrame(&SMA_358);
+ pushFrame(&SMA_3D8);
+ pushFrame(&SMA_458);
+ pushFrame(&SMA_4D8);
+ pushFrame(&SMA_518, [this]() { this->completePairing(); });
}
diff --git a/Software/src/inverter/SMA-BYD-HVS-CAN.h b/Software/src/inverter/SMA-BYD-HVS-CAN.h
index 1e03b8c47..8c9a44ee9 100644
--- a/Software/src/inverter/SMA-BYD-HVS-CAN.h
+++ b/Software/src/inverter/SMA-BYD-HVS-CAN.h
@@ -4,6 +4,8 @@
#include "../devboard/hal/hal.h"
#include "SmaInverterBase.h"
+#include
+
class SmaBydHvsInverter : public SmaInverterBase {
public:
const char* name() override { return Name; }
@@ -15,92 +17,106 @@ class SmaBydHvsInverter : public SmaInverterBase {
virtual bool controls_contactor() { return true; }
private:
- static const int READY_STATE = 0x03;
- static const int STOP_STATE = 0x02;
- static const int THIRTY_MINUTES = 1200;
- unsigned long previousMillis60s = 0;
- unsigned long previousMillis100ms = 0;
- unsigned long previousMillisBatch = 0;
- uint8_t batch_send_index = 0;
- const uint8_t delay_between_batches_ms =
- 7; //TODO, tweak to as low as possible before performance issues/crashes appear
- bool transmit_can_init = false;
+ const int READY_STATE = 0x03;
+ const int STOP_STATE = 0x02;
+ const int THIRTY_MINUTES = 1200;
+
+ void transmit_can_init();
+ void pushFrame(CAN_frame* frame, std::function callback = []() {});
+ void completePairing();
+
+ unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send
+ unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
+ unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
+ unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
+ unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
+
+ typedef struct {
+ CAN_frame* frame;
+ std::function callback;
+ } Frame;
+
+ unsigned short listLength = 0;
+ Frame framesToSend[20];
- uint8_t pairing_events = 0;
uint32_t inverter_time = 0;
uint16_t inverter_voltage = 0;
int16_t inverter_current = 0;
+ uint8_t pairing_events = 0;
+ bool pairing_completed = false;
+ int16_t temperature_average = 0;
+ uint16_t ampere_hours_remaining = 0;
uint16_t timeWithoutInverterAllowsContactorClosing = 0;
//Actual content messages
- CAN_frame SMA_158 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x158, // All 0xAA, no faults active
- .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame SMA_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
- .data = {0x11, 0xA0, 0x07, 0x00, 0x01, 0x5E, 0x00, 0xC8}};
+ .data = {0x12, 0x40, 0x0C, 0x80, 0x01, 0x00, 0x01, 0x00}};
CAN_frame SMA_3D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D8,
- .data = {0x13, 0x2E, 0x27, 0x10, 0x00, 0x45, 0xF9, 0x00}};
+ .data = {0x04, 0x06, 0x27, 0x10, 0x00, 0x19, 0x00, 0xFA}};
CAN_frame SMA_458 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x458,
- .data = {0x00, 0x00, 0x11, 0xC8, 0x00, 0x00, 0x0E, 0xF4}};
+ .data = {0x00, 0x00, 0x73, 0xAE, 0x00, 0x00, 0x64, 0x64}};
CAN_frame SMA_4D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x4D8,
- .data = {0x10, 0x62, 0x00, 0x16, 0x01, 0x68, 0x03, 0x08}};
+ .data = {0x10, 0x62, 0x00, 0x00, 0x00, 0x78, 0x02, 0x08}};
CAN_frame SMA_518 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x518,
- .data = {0x01, 0x4A, 0x01, 0x25, 0x10, 0x10, 0xFF, 0xFF}};
-
- // Pairing/Battery setup information
-
- CAN_frame SMA_558 = {.FD = false,
+ .data = {0x00, 0x96, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SMA_558 = {.FD = false, //Pairing first message
.ext_ID = false,
.DLC = 8,
- .ID = 0x558,
- .data = {0x03, 0x13, 0x00, 0x03, 0x00, 0x66, 0x04, 0x07}}; //4x BYD modules, Vendor ID 7 BYD
+ .ID = 0x558, // BYD HVS 10.2 kWh (0x66 might be kWh)
+ .data = {0x03, 0x24, 0x00, 0x04, 0x00, 0x66, 0x04, 0x09}};
+ // 0x03 |
+ // 0x24 |
+ // 0x00 |
+ // 0x04 | Module count: a 4-module stack
+ // 0x00 |
+ // 0x66 |
+ // 0x04 |
+ // 0x09 |
CAN_frame SMA_598 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x598,
- .data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}}; //B0-4 Serial, rest unknown
+ .data = {0x12, 0xD6, 0x43, 0xA4, 0x00, 0x00, 0x00, 0x00}}; //B0-4 Serial 301100932
CAN_frame SMA_5D8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5D8,
- .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
+ .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //(0) B Y D (0) (0) (0) (0)
+ CAN_frame SMA_618_0 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x618,
+ .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //(0) B A T T E R Y
CAN_frame SMA_618_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
- .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //0 B A T T E R Y
+ .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //(1) - B o x P r
CAN_frame SMA_618_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
- .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x48, 0x31}}; //- B o x H 1
+ .data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //(2) e m i u m H
CAN_frame SMA_618_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x618,
- .data = {0x02, 0x30, 0x2E, 0x32, 0x00, 0x00, 0x00, 0x00}}; // 0 . 2
-
- int16_t discharge_current = 0;
- int16_t charge_current = 0;
- int16_t temperature_average = 0;
- uint16_t ampere_hours_remaining = 0;
+ .data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //(3) V S (0) (0) (0) (0) (0)
};
#endif
diff --git a/Software/src/inverter/SMA-LV-CAN.cpp b/Software/src/inverter/SMA-LV-CAN.cpp
index 84aae8665..a736eddf8 100644
--- a/Software/src/inverter/SMA-LV-CAN.cpp
+++ b/Software/src/inverter/SMA-LV-CAN.cpp
@@ -54,8 +54,8 @@ void SmaLvInverter::
SMA_356.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 8);
SMA_356.data.u8[1] = ((datalayer.battery.status.voltage_dV * 10) & 0x00FF);
//Current (S16 dA)
- SMA_356.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SMA_356.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ SMA_356.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ SMA_356.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
//Temperature (s16 degC)
SMA_356.data.u8[4] = (temperature_average >> 8);
SMA_356.data.u8[5] = (temperature_average & 0x00FF);
diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp
deleted file mode 100644
index 172cd4c09..000000000
--- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-#include "SMA-TRIPOWER-CAN.h"
-#include "../communication/can/comm_can.h"
-#include "../datalayer/datalayer.h"
-#include "../devboard/utils/events.h"
-
-/* TODO:
-- Figure out the manufacturer info needed in transmit_can_init() CAN messages
- - CAN logs from real system might be needed
-- Figure out how cellvoltages need to be displayed
-- Figure out if sending transmit_can_init() like we do now is OK
-- Figure out how to send the non-cyclic messages when needed
-*/
-
-void SmaTripowerInverter::
- update_values() { //This function maps all the values fetched from battery CAN to the inverter CAN
- // Update values
- 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.reported_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)
- SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
- SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
- //Minvoltage (eg 300.0V = 3000 , 16bits long)
- SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
- SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
- //Discharge limited current, 500 = 50A, (0.1, A)
- SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
- SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
- //Charge limited current, 125 =12.5A (0.1, A)
- SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
- SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
-
- //SOC (100.00%)
- SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
- SMA_3D8.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF);
- //StateOfHealth (100.00%)
- SMA_3D8.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8);
- SMA_3D8.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF);
- //State of charge (AH, 0.1)
- SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8);
- SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF);
-
- //Voltage (370.0)
- SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
- SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
- //Current (TODO: signed OK?)
- SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
- //Temperature average
- SMA_4D8.data.u8[4] = (temperature_average >> 8);
- SMA_4D8.data.u8[5] = (temperature_average & 0x00FF);
- //Battery ready
- if (datalayer.battery.status.bms_status == FAULT) {
- SMA_4D8.data.u8[6] = STOP_STATE;
- } else {
- SMA_4D8.data.u8[6] = READY_STATE;
- }
-
- //Highest battery temperature
- SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
- SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- //Lowest battery temperature
- SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
- SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- //Sum of all cellvoltages
- SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
- SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
- //Cell min/max voltage (mV / 25)
- SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
- SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
-
- //Lifetime charged energy amount
- SMA_458.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0xFF000000) >> 24;
- SMA_458.data.u8[1] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF0000) >> 16;
- SMA_458.data.u8[2] = (datalayer.battery.status.total_charged_battery_Wh & 0x0000FF00) >> 8;
- SMA_458.data.u8[3] = (datalayer.battery.status.total_charged_battery_Wh & 0x000000FF);
- //Lifetime discharged energy amount
- SMA_458.data.u8[4] = (datalayer.battery.status.total_discharged_battery_Wh & 0xFF000000) >> 24;
- SMA_458.data.u8[5] = (datalayer.battery.status.total_discharged_battery_Wh & 0x00FF0000) >> 16;
- SMA_458.data.u8[6] = (datalayer.battery.status.total_discharged_battery_Wh & 0x0000FF00) >> 8;
- SMA_458.data.u8[7] = (datalayer.battery.status.total_discharged_battery_Wh & 0x000000FF);
-
- control_contactor_led();
-
- // Check if Enable line is working. If we go too long without any input, raise an event
- if (!datalayer.system.status.inverter_allows_contactor_closing) {
- timeWithoutInverterAllowsContactorClosing++;
-
- if (timeWithoutInverterAllowsContactorClosing > THIRTY_MINUTES) {
- set_event(EVENT_NO_ENABLE_DETECTED, 0);
- }
- } else {
- timeWithoutInverterAllowsContactorClosing = 0;
- }
-}
-
-void SmaTripowerInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
- switch (rx_frame.ID) {
- case 0x360: //Message originating from SMA inverter - Voltage and current
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1];
- inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
- break;
- case 0x3E0: //Message originating from SMA inverter - ?
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x420: //Message originating from SMA inverter - Timestamp
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- inverter_time =
- (rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
- break;
- case 0x560: //Message originating from SMA inverter - Init
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x5E0: //Message originating from SMA inverter - String
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- //Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA
- break;
- case 0x5E7: //Message originating from SMA inverter - Pairing request
- case 0x660: //Message originating from SMA inverter - Pairing request
- logging.println("Received SMA pairing request");
- pairing_events++;
- set_event(EVENT_SMA_PAIRING, pairing_events);
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- transmit_can_init();
- break;
- default:
- break;
- }
-}
-
-void SmaTripowerInverter::pushFrame(CAN_frame* frame, std::function callback) {
- if (listLength >= 20) {
- return; //TODO: scream.
- }
- framesToSend[listLength] = {
- .frame = frame,
- .callback = callback,
- };
- listLength++;
-}
-
-void SmaTripowerInverter::transmit_can(unsigned long currentMillis) {
-
- // Send CAN Message only if we're enabled by inverter
- if (!datalayer.system.status.inverter_allows_contactor_closing) {
- return;
- }
-
- if (listLength > 0 && currentMillis - previousMillis250ms >= INTERVAL_250_MS) {
- previousMillis250ms = currentMillis;
- // Send next frame.
- Frame frame = framesToSend[0];
- transmit_can_frame(frame.frame);
- frame.callback();
- for (int i = 0; i < listLength - 1; i++) {
- framesToSend[i] = framesToSend[i + 1];
- }
- listLength--;
- }
-
- if (!pairing_completed) {
- return;
- }
-
- // Send CAN Message every 2s
- if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
- previousMillis2s = currentMillis;
- pushFrame(&SMA_358);
- }
- // Send CAN Message every 10s
- if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
- previousMillis10s = currentMillis;
- pushFrame(&SMA_518);
- pushFrame(&SMA_4D8);
- pushFrame(&SMA_3D8);
- }
- // Send CAN Message every 60s (potentially SMA_458 is not required for stable operation)
- if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
- previousMillis60s = currentMillis;
- pushFrame(&SMA_458);
- }
-}
-
-void SmaTripowerInverter::completePairing() {
- pairing_completed = true;
-}
-
-void SmaTripowerInverter::transmit_can_init() {
- listLength = 0; // clear all frames
-
- pushFrame(&SMA_558); //Pairing start - Vendor
- pushFrame(&SMA_598); //Serial
- pushFrame(&SMA_5D8); //BYD
- pushFrame(&SMA_618_0); //BATTERY
- pushFrame(&SMA_618_1); //-Box Pr
- pushFrame(&SMA_618_2); //emium H
- pushFrame(&SMA_618_3); //VS
- pushFrame(&SMA_358);
- pushFrame(&SMA_3D8);
- pushFrame(&SMA_458);
- pushFrame(&SMA_4D8);
- pushFrame(&SMA_518, [this]() { this->completePairing(); });
-}
diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.h b/Software/src/inverter/SMA-TRIPOWER-CAN.h
deleted file mode 100644
index 15a660cc5..000000000
--- a/Software/src/inverter/SMA-TRIPOWER-CAN.h
+++ /dev/null
@@ -1,114 +0,0 @@
-#ifndef SMA_CAN_TRIPOWER_H
-#define SMA_CAN_TRIPOWER_H
-
-#include "../devboard/hal/hal.h"
-#include "SmaInverterBase.h"
-
-#include
-
-class SmaTripowerInverter : public SmaInverterBase {
- public:
- const char* name() override { return Name; }
- void update_values();
- void transmit_can(unsigned long currentMillis);
- void map_can_frame_to_variable(CAN_frame rx_frame);
- static constexpr const char* Name = "SMA Tripower CAN";
-
- virtual bool controls_contactor() { return true; }
-
- private:
- const int READY_STATE = 0x03;
- const int STOP_STATE = 0x02;
- const int THIRTY_MINUTES = 1200;
-
- void transmit_can_init();
- void pushFrame(CAN_frame* frame, std::function callback = []() {});
- void completePairing();
-
- unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send
- unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
- unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
- unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
- unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
-
- typedef struct {
- CAN_frame* frame;
- std::function callback;
- } Frame;
-
- unsigned short listLength = 0;
- Frame framesToSend[20];
-
- uint32_t inverter_time = 0;
- uint16_t inverter_voltage = 0;
- int16_t inverter_current = 0;
- uint8_t pairing_events = 0;
- bool pairing_completed = false;
- int16_t temperature_average = 0;
- uint16_t ampere_hours_remaining = 0;
- uint16_t timeWithoutInverterAllowsContactorClosing = 0;
-
- //Actual content messages
- CAN_frame SMA_358 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x358,
- .data = {0x12, 0x40, 0x0C, 0x80, 0x01, 0x00, 0x01, 0x00}};
- CAN_frame SMA_3D8 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x3D8,
- .data = {0x04, 0x06, 0x27, 0x10, 0x00, 0x19, 0x00, 0xFA}};
- CAN_frame SMA_458 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x458,
- .data = {0x00, 0x00, 0x73, 0xAE, 0x00, 0x00, 0x64, 0x64}};
- CAN_frame SMA_4D8 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x4D8,
- .data = {0x10, 0x62, 0x00, 0x00, 0x00, 0x78, 0x02, 0x08}};
- CAN_frame SMA_518 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x518,
- .data = {0x00, 0x96, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame SMA_558 = {.FD = false, //Pairing first message
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x558, // BYD HVS 10.2 kWh (0x66 might be kWh)
- .data = {0x03, 0x24, 0x00, 0x04, 0x00, 0x66, 0x04, 0x09}}; //Amount of modules? Vendor ID?
- CAN_frame SMA_598 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x598,
- .data = {0x12, 0xD6, 0x43, 0xA4, 0x00, 0x00, 0x00, 0x00}}; //B0-4 Serial 301100932
- CAN_frame SMA_5D8 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x5D8,
- .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D
- CAN_frame SMA_618_0 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x618,
- .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //BATTERY
- CAN_frame SMA_618_1 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x618,
- .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
- CAN_frame SMA_618_2 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x618,
- .data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
- CAN_frame SMA_618_3 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x618,
- .data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
-};
-
-#endif
diff --git a/Software/src/inverter/SOFAR-CAN.cpp b/Software/src/inverter/SOFAR-CAN.cpp
index d724e6c06..95c87ee9b 100644
--- a/Software/src/inverter/SOFAR-CAN.cpp
+++ b/Software/src/inverter/SOFAR-CAN.cpp
@@ -32,8 +32,8 @@ void SofarInverter::
// Voltage (e.g. 370.0V -> 3700 dV), Current in dA, Temperature in dC
SOFAR_356.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
SOFAR_356.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
- SOFAR_356.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
- SOFAR_356.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
+ SOFAR_356.data.u8[2] = (datalayer.battery.status.reported_current_dA & 0x00FF);
+ SOFAR_356.data.u8[3] = (datalayer.battery.status.reported_current_dA >> 8);
SOFAR_356.data.u8[4] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
SOFAR_356.data.u8[5] = (datalayer.battery.status.temperature_max_dC >> 8);
diff --git a/Software/src/inverter/SOL-ARK-LV-CAN.cpp b/Software/src/inverter/SOL-ARK-LV-CAN.cpp
index c8c8fb55e..8c55a96c8 100644
--- a/Software/src/inverter/SOL-ARK-LV-CAN.cpp
+++ b/Software/src/inverter/SOL-ARK-LV-CAN.cpp
@@ -38,8 +38,8 @@ void SolArkLvInverter::update_values() {
(datalayer.battery.status.temperature_min_dC + datalayer.battery.status.temperature_max_dC) / 2;
SOLARK_356.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
SOLARK_356.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
- SOLARK_356.data.u8[2] = datalayer.battery.status.current_dA & 0xff;
- SOLARK_356.data.u8[3] = datalayer.battery.status.current_dA >> 8;
+ SOLARK_356.data.u8[2] = datalayer.battery.status.reported_current_dA & 0xff;
+ SOLARK_356.data.u8[3] = datalayer.battery.status.reported_current_dA >> 8;
SOLARK_356.data.u8[4] = average_temperature & 0xff;
SOLARK_356.data.u8[5] = average_temperature >> 8;
@@ -54,7 +54,7 @@ void SolArkLvInverter::update_values() {
SOLARK_359.data.u8[7] = 0x00; //Unused, should be 00
// Protection Byte 1 Bitfield: (If a bit is set, one of these caused batt self-protection mode)
- if (datalayer.battery.status.current_dA >= (datalayer.battery.status.max_discharge_current_dA + 50))
+ if (datalayer.battery.status.reported_current_dA >= (datalayer.battery.status.max_discharge_current_dA + 50))
SOLARK_359.data.u8[0] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE)
SOLARK_359.data.u8[0] |= 0x10;
@@ -64,7 +64,7 @@ void SolArkLvInverter::update_values() {
SOLARK_359.data.u8[0] |= 0x04;
if (datalayer.battery.status.bms_status == FAULT)
SOLARK_359.data.u8[1] |= 0x80;
- if (datalayer.battery.status.current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
+ if (datalayer.battery.status.reported_current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
SOLARK_359.data.u8[1] |= 0x01;
// WARNINGS (using same rules as errors but reporting earlier)
diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp
index 4117a2a90..f447e4664 100644
--- a/Software/src/inverter/SOLAX-CAN.cpp
+++ b/Software/src/inverter/SOLAX-CAN.cpp
@@ -14,7 +14,7 @@
void SolaxInverter::
update_values() { //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 && !configured_ignore_contactors) {
+ if (millis() - LastFrameTime >= INTERVAL_2_S && !configured_ignore_contactors) {
datalayer.system.status.inverter_allows_contactor_closing = false;
STATE = BATTERY_ANNOUNCE;
}
@@ -49,8 +49,9 @@ void SolaxInverter::
//BMS_PackData
SOLAX_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK
SOLAX_1873.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
- SOLAX_1873.data.u8[2] = (int8_t)datalayer.battery.status.current_dA; // OK, Signed (Active current in Amps x 10)
- SOLAX_1873.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
+ SOLAX_1873.data.u8[2] =
+ (int8_t)datalayer.battery.status.reported_current_dA; // OK, Signed (Active current in Amps x 10)
+ SOLAX_1873.data.u8[3] = (datalayer.battery.status.reported_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 / 10);
@@ -120,71 +121,14 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
if (rx_frame.ID == 0x1871) {
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- }
- if (rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x01) ||
- rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
- LastFrameTime = millis();
-
- if (configured_ignore_contactors) {
- // Skip the state machine since we're not going to open/close contactors,
- // and the Solax would otherwise wait forever for us to do so.
-
- datalayer.system.status.inverter_allows_contactor_closing = true;
- SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on.
- transmit_can_frame(&SOLAX_187E);
- transmit_can_frame(&SOLAX_187A);
- transmit_can_frame(&SOLAX_1872);
- transmit_can_frame(&SOLAX_1873);
- transmit_can_frame(&SOLAX_1874);
- transmit_can_frame(&SOLAX_1875);
- transmit_can_frame(&SOLAX_1876);
- transmit_can_frame(&SOLAX_1877);
- transmit_can_frame(&SOLAX_1878);
- transmit_can_frame(&SOLAX_100A001);
- return;
- }
+ if ((rx_frame.data.u8[0] == (0x01)) || (rx_frame.data.u8[0] == (0x02))) {
+ LastFrameTime = millis();
- switch (STATE) {
- case (BATTERY_ANNOUNCE):
- logging.println("Solax Battery State: Announce");
- datalayer.system.status.inverter_allows_contactor_closing = false;
- SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
- for (uint8_t i = 0; i < number_of_batteries; i++) {
- transmit_can_frame(&SOLAX_187E);
- transmit_can_frame(&SOLAX_187A);
- transmit_can_frame(&SOLAX_1872);
- transmit_can_frame(&SOLAX_1873);
- transmit_can_frame(&SOLAX_1874);
- transmit_can_frame(&SOLAX_1875);
- transmit_can_frame(&SOLAX_1876);
- transmit_can_frame(&SOLAX_1877);
- transmit_can_frame(&SOLAX_1878);
- }
- transmit_can_frame(&SOLAX_100A001); //BMS Announce
- // Message from the inverter to proceed to contactor closing
- // Byte 4 changes from 0 to 1
- if (rx_frame.data.u64 == Contactor_Close_Payload)
- STATE = WAITING_FOR_CONTACTOR;
- break;
-
- case (WAITING_FOR_CONTACTOR):
- SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
- transmit_can_frame(&SOLAX_187E);
- transmit_can_frame(&SOLAX_187A);
- transmit_can_frame(&SOLAX_1872);
- transmit_can_frame(&SOLAX_1873);
- transmit_can_frame(&SOLAX_1874);
- transmit_can_frame(&SOLAX_1875);
- transmit_can_frame(&SOLAX_1876);
- transmit_can_frame(&SOLAX_1877);
- transmit_can_frame(&SOLAX_1878);
- transmit_can_frame(&SOLAX_1801); // Announce that the battery will be connected
- STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State
- logging.println("Solax Battery State: Contactor Closed");
- break;
+ if (configured_ignore_contactors) {
+ // Skip the state machine since we're not going to open/close contactors,
+ // and the Solax would otherwise wait forever for us to do so.
- case (CONTACTOR_CLOSED):
datalayer.system.status.inverter_allows_contactor_closing = true;
SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on.
transmit_can_frame(&SOLAX_187E);
@@ -196,13 +140,69 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
transmit_can_frame(&SOLAX_1876);
transmit_can_frame(&SOLAX_1877);
transmit_can_frame(&SOLAX_1878);
- // Message from the inverter to open contactor
- // Byte 4 changes from 1 to 0
- if (rx_frame.data.u64 == Contactor_Open_Payload) {
- set_event(EVENT_INVERTER_OPEN_CONTACTOR, 0);
- STATE = BATTERY_ANNOUNCE;
- }
- break;
+ transmit_can_frame(&SOLAX_100A001);
+ return;
+ }
+
+ switch (STATE) {
+ case (BATTERY_ANNOUNCE):
+ logging.println("Solax Battery State: Announce");
+ datalayer.system.status.inverter_allows_contactor_closing = false;
+ SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
+ for (uint8_t i = 0; i < number_of_batteries; i++) {
+ transmit_can_frame(&SOLAX_187E);
+ transmit_can_frame(&SOLAX_187A);
+ transmit_can_frame(&SOLAX_1872);
+ transmit_can_frame(&SOLAX_1873);
+ transmit_can_frame(&SOLAX_1874);
+ transmit_can_frame(&SOLAX_1875);
+ transmit_can_frame(&SOLAX_1876);
+ transmit_can_frame(&SOLAX_1877);
+ transmit_can_frame(&SOLAX_1878);
+ }
+ transmit_can_frame(&SOLAX_100A001); //BMS Announce
+ // Message from the inverter to proceed to contactor closing
+ // Byte 4 changes from 0 to 1
+ if (rx_frame.data.u64 == Contactor_Close_Payload)
+ STATE = WAITING_FOR_CONTACTOR;
+ break;
+
+ case (WAITING_FOR_CONTACTOR):
+ SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
+ transmit_can_frame(&SOLAX_187E);
+ transmit_can_frame(&SOLAX_187A);
+ transmit_can_frame(&SOLAX_1872);
+ transmit_can_frame(&SOLAX_1873);
+ transmit_can_frame(&SOLAX_1874);
+ transmit_can_frame(&SOLAX_1875);
+ transmit_can_frame(&SOLAX_1876);
+ transmit_can_frame(&SOLAX_1877);
+ transmit_can_frame(&SOLAX_1878);
+ transmit_can_frame(&SOLAX_1801); // Announce that the battery will be connected
+ STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State
+ logging.println("Solax Battery State: Contactor Closed");
+ break;
+
+ case (CONTACTOR_CLOSED):
+ datalayer.system.status.inverter_allows_contactor_closing = true;
+ SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on.
+ transmit_can_frame(&SOLAX_187E);
+ transmit_can_frame(&SOLAX_187A);
+ transmit_can_frame(&SOLAX_1872);
+ transmit_can_frame(&SOLAX_1873);
+ transmit_can_frame(&SOLAX_1874);
+ transmit_can_frame(&SOLAX_1875);
+ transmit_can_frame(&SOLAX_1876);
+ transmit_can_frame(&SOLAX_1877);
+ transmit_can_frame(&SOLAX_1878);
+ // Message from the inverter to open contactor
+ // Byte 4 changes from 1 to 0
+ if (rx_frame.data.u64 == Contactor_Open_Payload) {
+ set_event(EVENT_INVERTER_OPEN_CONTACTOR, 0);
+ STATE = BATTERY_ANNOUNCE;
+ }
+ break;
+ }
}
}
@@ -221,13 +221,13 @@ bool SolaxInverter::setup(void) { // Performs one time setup at startup
if (user_selected_inverter_modules > 0) {
configured_number_of_modules = user_selected_inverter_modules;
} else {
- configured_number_of_modules = NUMBER_OF_MODULES;
+ configured_number_of_modules = DEFAULT_NUMBER_OF_MODULES;
}
if (user_selected_inverter_battery_type > 0) {
configured_battery_type = user_selected_inverter_battery_type;
} else {
- configured_battery_type = BATTERY_TYPE;
+ configured_battery_type = DEFAULT_BATTERY_TYPE;
}
configured_ignore_contactors = user_selected_inverter_ignore_contactors;
diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h
index 2599133c0..b82fd4199 100644
--- a/Software/src/inverter/SOLAX-CAN.h
+++ b/Software/src/inverter/SOLAX-CAN.h
@@ -13,14 +13,8 @@ class SolaxInverter : public CanInverterProtocol {
static constexpr const char* Name = "SolaX Triple Power LFP over CAN bus";
private:
- static const int NUMBER_OF_MODULES = 0;
- static const int BATTERY_TYPE = 0x50;
- // If you are having BattVoltFault issues, configure the above values according to wiki page
- // https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters
-
- // Timeout in milliseconds
- static const int SolaxTimeout = 2000;
-
+ static const uint8_t DEFAULT_NUMBER_OF_MODULES = 0;
+ static const uint8_t DEFAULT_BATTERY_TYPE = 0x50;
//SOLAX BMS States Definition
static const int BATTERY_ANNOUNCE = 0;
static const int WAITING_FOR_CONTACTOR = 1;
@@ -35,8 +29,8 @@ class SolaxInverter : public CanInverterProtocol {
uint16_t capped_capacity_Wh;
uint16_t capped_remaining_capacity_Wh;
- int configured_number_of_modules = 0;
- int configured_battery_type = 0;
+ uint16_t configured_number_of_modules = 0;
+ uint16_t configured_battery_type = 0;
// If true, the integration will ignore the inverter's requests to open the
// battery contactors. Useful for batteries that can't open contactors on
// request.
diff --git a/Software/src/inverter/SOLXPOW-CAN.cpp b/Software/src/inverter/SOLXPOW-CAN.cpp
index e54c0dfee..02a0c134d 100644
--- a/Software/src/inverter/SOLXPOW-CAN.cpp
+++ b/Software/src/inverter/SOLXPOW-CAN.cpp
@@ -3,10 +3,7 @@
#include "../datalayer/datalayer.h"
#include "../inverter/INVERTERS.h"
-#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_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 INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes
//#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
void SolxpowInverter::
@@ -22,278 +19,193 @@ void SolxpowInverter::
}
//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
//Charge / Discharge allowed flags
if (datalayer.battery.status.max_charge_current_dA == 0) {
SOLXPOW_4280.data.u8[0] = 0xAA; //Charge forbidden
- SOLXPOW_4281.data.u8[0] = 0xAA;
} else {
SOLXPOW_4280.data.u8[0] = 0; //Charge allowed
- SOLXPOW_4281.data.u8[0] = 0;
}
if (datalayer.battery.status.max_discharge_current_dA == 0) {
SOLXPOW_4280.data.u8[1] = 0xAA; //Discharge forbidden
- SOLXPOW_4281.data.u8[1] = 0xAA;
} else {
SOLXPOW_4280.data.u8[1] = 0; //Discharge allowed
- SOLXPOW_4281.data.u8[1] = 0;
}
//In case run into a FAULT state, let inverter know to stop any charge/discharge
if (datalayer.battery.status.bms_status == FAULT) {
SOLXPOW_4280.data.u8[0] = 0xAA; //Charge forbidden
SOLXPOW_4280.data.u8[1] = 0xAA; //Discharge forbidden
- SOLXPOW_4281.data.u8[0] = 0xAA; //Charge forbidden
- SOLXPOW_4281.data.u8[1] = 0xAA; //Discharge forbidden
}
//Voltage (370.0)
SOLXPOW_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
SOLXPOW_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
- SOLXPOW_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
- SOLXPOW_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
//Current (15.0)
- SOLXPOW_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SOLXPOW_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
- SOLXPOW_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SOLXPOW_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
+ SOLXPOW_4210.data.u8[2] = (datalayer.battery.status.reported_current_dA >> 8);
+ SOLXPOW_4210.data.u8[3] = (datalayer.battery.status.reported_current_dA & 0x00FF);
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
SOLXPOW_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
SOLXPOW_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
- SOLXPOW_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
- SOLXPOW_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
#else // Not INVERT_LOW_HIGH_BYTES
SOLXPOW_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
SOLXPOW_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
- SOLXPOW_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
- SOLXPOW_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
#endif // INVERT_LOW_HIGH_BYTES
//SOC (100.00%)
SOLXPOW_4210.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
- SOLXPOW_4211.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
//StateOfHealth (100.00%)
SOLXPOW_4210.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
- SOLXPOW_4211.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
if (datalayer.battery.status.bms_status == FAULT) {
SOLXPOW_4250.data.u8[0] = (0x00); // Sleep
- SOLXPOW_4251.data.u8[0] = (0x00); // Sleep
- } else if (datalayer.battery.status.current_dA < 0) {
+ } else if (datalayer.battery.status.reported_current_dA < 0) {
SOLXPOW_4250.data.u8[0] = (0x01); // Charge
- SOLXPOW_4251.data.u8[0] = (0x01); // Charge
- } else if (datalayer.battery.status.current_dA > 0) {
+ } else if (datalayer.battery.status.reported_current_dA > 0) {
SOLXPOW_4250.data.u8[0] = (0x02); // Discharge
- SOLXPOW_4251.data.u8[0] = (0x02); // Discharge
- } else if (datalayer.battery.status.current_dA == 0) {
+ } else if (datalayer.battery.status.reported_current_dA == 0) {
SOLXPOW_4250.data.u8[0] = (0x03); // Idle
- SOLXPOW_4251.data.u8[0] = (0x03); // Idle
}
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
//Voltage (370.0)
SOLXPOW_4210.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
SOLXPOW_4210.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
- SOLXPOW_4211.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
- SOLXPOW_4211.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
#ifdef SET_30K_OFFSET
//Current (15.0)
SOLXPOW_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
SOLXPOW_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
- SOLXPOW_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
- SOLXPOW_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
#else // Not SET_30K_OFFSET
- SOLXPOW_4210.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
- SOLXPOW_4210.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
- SOLXPOW_4211.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
- SOLXPOW_4211.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
+ SOLXPOW_4210.data.u8[2] = (datalayer.battery.status.reported_current_dA & 0x00FF);
+ SOLXPOW_4210.data.u8[3] = (datalayer.battery.status.reported_current_dA >> 8);
#endif //SET_30K_OFFSET
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
SOLXPOW_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
SOLXPOW_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
- SOLXPOW_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
- SOLXPOW_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
SOLXPOW_4220.data.u8[0] = (charge_cutoff_voltage_dV & 0x00FF);
SOLXPOW_4220.data.u8[1] = (charge_cutoff_voltage_dV >> 8);
- SOLXPOW_4221.data.u8[0] = (charge_cutoff_voltage_dV & 0x00FF);
- SOLXPOW_4221.data.u8[1] = (charge_cutoff_voltage_dV >> 8);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage
SOLXPOW_4220.data.u8[2] = (discharge_cutoff_voltage_dV & 0x00FF);
SOLXPOW_4220.data.u8[3] = (discharge_cutoff_voltage_dV >> 8);
- SOLXPOW_4221.data.u8[2] = (discharge_cutoff_voltage_dV & 0x00FF);
- SOLXPOW_4221.data.u8[3] = (discharge_cutoff_voltage_dV >> 8);
#ifdef SET_30K_OFFSET
//Max ChargeCurrent
SOLXPOW_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
SOLXPOW_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
- SOLXPOW_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
- SOLXPOW_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
//Max DischargeCurrent
SOLXPOW_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
SOLXPOW_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
- SOLXPOW_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
- SOLXPOW_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
#else // Not SET_30K_OFFSET
//Max ChargeCurrent
SOLXPOW_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
SOLXPOW_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
- SOLXPOW_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
- SOLXPOW_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
//Max DishargeCurrent
SOLXPOW_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
SOLXPOW_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
- SOLXPOW_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
- SOLXPOW_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
#endif // SET_30K_OFFSET
//Max cell voltage
SOLXPOW_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
SOLXPOW_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
- SOLXPOW_4231.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
- SOLXPOW_4231.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
//Min cell voltage
SOLXPOW_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
SOLXPOW_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
- SOLXPOW_4231.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
- SOLXPOW_4231.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
//Max temperature per cell
SOLXPOW_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
SOLXPOW_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
- SOLXPOW_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- SOLXPOW_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
//Max/Min temperature per cell
SOLXPOW_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
SOLXPOW_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
- SOLXPOW_4241.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- SOLXPOW_4241.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
//Max temperature per module
SOLXPOW_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
SOLXPOW_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
- SOLXPOW_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- SOLXPOW_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
//Min temperature per module
SOLXPOW_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
SOLXPOW_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
- SOLXPOW_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- SOLXPOW_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
#else // Not INVERT_LOW_HIGH_BYTES
//Voltage (370.0)
SOLXPOW_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
SOLXPOW_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
- SOLXPOW_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
- SOLXPOW_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
#ifdef SET_30K_OFFSET
//Current (15.0)
SOLXPOW_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
SOLXPOW_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
- SOLXPOW_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
- SOLXPOW_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
#else // Not SET_30K_OFFSET
SOLXPOW_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
SOLXPOW_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
- SOLXPOW_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
- SOLXPOW_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
#endif //SET_30K_OFFSET
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
SOLXPOW_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
SOLXPOW_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
- SOLXPOW_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
- SOLXPOW_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
SOLXPOW_4220.data.u8[0] = (charge_cutoff_voltage_dV >> 8);
SOLXPOW_4220.data.u8[1] = (charge_cutoff_voltage_dV & 0x00FF);
- SOLXPOW_4221.data.u8[0] = (charge_cutoff_voltage_dV >> 8);
- SOLXPOW_4221.data.u8[1] = (charge_cutoff_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage
SOLXPOW_4220.data.u8[2] = (discharge_cutoff_voltage_dV >> 8);
SOLXPOW_4220.data.u8[3] = (discharge_cutoff_voltage_dV & 0x00FF);
- SOLXPOW_4221.data.u8[2] = (discharge_cutoff_voltage_dV >> 8);
- SOLXPOW_4221.data.u8[3] = (discharge_cutoff_voltage_dV & 0x00FF);
#ifdef SET_30K_OFFSET
//Max ChargeCurrent
SOLXPOW_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
SOLXPOW_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
- SOLXPOW_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
- SOLXPOW_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
//Max DischargeCurrent
SOLXPOW_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
SOLXPOW_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
- SOLXPOW_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
- SOLXPOW_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
#else // Not SET_30K_OFFSET
//Max ChargeCurrent
SOLXPOW_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8);
SOLXPOW_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
- SOLXPOW_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8);
- SOLXPOW_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//Max DishargeCurrent
SOLXPOW_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SOLXPOW_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
- SOLXPOW_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current >> 8);
- SOLXPOW_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
#endif //SET_30K_OFFSET
//Max cell voltage
SOLXPOW_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
SOLXPOW_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
- SOLXPOW_4231.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
- SOLXPOW_4231.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
//Min cell voltage
SOLXPOW_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
SOLXPOW_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
- SOLXPOW_4231.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
- SOLXPOW_4231.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
//Max temperature per cell
SOLXPOW_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
SOLXPOW_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- SOLXPOW_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
- SOLXPOW_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
//Max/Min temperature per cell
SOLXPOW_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
SOLXPOW_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- SOLXPOW_4241.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
- SOLXPOW_4241.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
//Max temperature per module
SOLXPOW_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
SOLXPOW_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- SOLXPOW_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
- SOLXPOW_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
//Min temperature per module
SOLXPOW_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
SOLXPOW_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- SOLXPOW_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
- SOLXPOW_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
#endif // Not INVERT_LOW_HIGH_BYTES
}
@@ -318,20 +230,13 @@ void SolxpowInverter::transmit_can(unsigned long currentMillis) {
}
void SolxpowInverter::send_setup_info() { //Ensemble information
-#ifdef SEND_0
transmit_can_frame(&SOLXPOW_7310);
transmit_can_frame(&SOLXPOW_7320);
transmit_can_frame(&SOLXPOW_7330);
transmit_can_frame(&SOLXPOW_7340);
-#endif
-#ifdef SEND_1
- transmit_can_frame(&SOLXPOW_7311);
- transmit_can_frame(&SOLXPOW_7321);
-#endif
}
void SolxpowInverter::send_system_data() { //System equipment information
-#ifdef SEND_0
transmit_can_frame(&SOLXPOW_4210);
transmit_can_frame(&SOLXPOW_4220);
transmit_can_frame(&SOLXPOW_4230);
@@ -341,50 +246,30 @@ void SolxpowInverter::send_system_data() { //System equipment information
transmit_can_frame(&SOLXPOW_4270);
transmit_can_frame(&SOLXPOW_4280);
transmit_can_frame(&SOLXPOW_4290);
-#endif
-#ifdef SEND_1
- transmit_can_frame(&SOLXPOW_4211);
- transmit_can_frame(&SOLXPOW_4221);
- transmit_can_frame(&SOLXPOW_4231);
- transmit_can_frame(&SOLXPOW_4241);
- transmit_can_frame(&SOLXPOW_4251);
- transmit_can_frame(&SOLXPOW_4261);
- transmit_can_frame(&SOLXPOW_4271);
- transmit_can_frame(&SOLXPOW_4281);
- transmit_can_frame(&SOLXPOW_4291);
-#endif
}
bool SolxpowInverter::setup() {
if (user_selected_inverter_cells > 0) {
SOLXPOW_7320.data.u8[0] = user_selected_inverter_cells & 0xff;
SOLXPOW_7320.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
- SOLXPOW_7321.data.u8[0] = user_selected_inverter_cells & 0xff;
- SOLXPOW_7321.data.u8[1] = (uint8_t)(user_selected_inverter_cells >> 8);
}
if (user_selected_inverter_modules > 0) {
SOLXPOW_7320.data.u8[2] = user_selected_inverter_modules;
- SOLXPOW_7321.data.u8[2] = user_selected_inverter_modules;
}
if (user_selected_inverter_cells_per_module > 0) {
SOLXPOW_7320.data.u8[3] = user_selected_inverter_cells_per_module;
- SOLXPOW_7321.data.u8[3] = user_selected_inverter_cells_per_module;
}
if (user_selected_inverter_voltage_level > 0) {
SOLXPOW_7320.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
SOLXPOW_7320.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
- SOLXPOW_7321.data.u8[4] = user_selected_inverter_voltage_level & 0xff;
- SOLXPOW_7321.data.u8[5] = (uint8_t)(user_selected_inverter_voltage_level >> 8);
}
if (user_selected_inverter_ah_capacity > 0) {
SOLXPOW_7320.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
SOLXPOW_7320.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
- SOLXPOW_7321.data.u8[6] = user_selected_inverter_ah_capacity & 0xff;
- SOLXPOW_7321.data.u8[7] = (uint8_t)(user_selected_inverter_ah_capacity >> 8);
}
return true;
diff --git a/Software/src/inverter/SOLXPOW-CAN.h b/Software/src/inverter/SOLXPOW-CAN.h
index 8010c944f..63d954d7e 100644
--- a/Software/src/inverter/SOLXPOW-CAN.h
+++ b/Software/src/inverter/SOLXPOW-CAN.h
@@ -41,11 +41,6 @@ class SolxpowInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x7310,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
- CAN_frame SOLXPOW_7311 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x7311,
- .data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame SOLXPOW_7320 = {.FD = false,
.ext_ID = true,
.DLC = 8,
@@ -53,13 +48,6 @@ class SolxpowInverter : public CanInverterProtocol {
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
(uint8_t)(AH_CAPACITY & 0x00FF), (uint8_t)(AH_CAPACITY >> 8)}};
- CAN_frame SOLXPOW_7321 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x7321,
- .data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
- CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
- (uint8_t)(AH_CAPACITY & 0x00FF), (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame SOLXPOW_4210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
@@ -105,51 +93,6 @@ class SolxpowInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x4290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame SOLXPOW_4211 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4211,
- .data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
- CAN_frame SOLXPOW_4221 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4221,
- .data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
- CAN_frame SOLXPOW_4231 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4231,
- .data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
- CAN_frame SOLXPOW_4241 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4241,
- .data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
- CAN_frame SOLXPOW_4251 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4251,
- .data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame SOLXPOW_4261 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4261,
- .data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
- CAN_frame SOLXPOW_4271 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4271,
- .data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
- CAN_frame SOLXPOW_4281 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4281,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame SOLXPOW_4291 = {.FD = false,
- .ext_ID = true,
- .DLC = 8,
- .ID = 0x4291,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint16_t discharge_cutoff_voltage_dV = 0;
uint16_t charge_cutoff_voltage_dV = 0;
diff --git a/Software/src/inverter/SUNGROW-CAN.cpp b/Software/src/inverter/SUNGROW-CAN.cpp
index c75740061..9a7927118 100644
--- a/Software/src/inverter/SUNGROW-CAN.cpp
+++ b/Software/src/inverter/SUNGROW-CAN.cpp
@@ -1,9 +1,14 @@
#include "SUNGROW-CAN.h"
+
+#include
+
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
+#include "INVERTERS.h"
/* TODO:
-This protocol is still under development. It can not be used yet for Sungrow inverters,
+This protocol is still under development and should be considered beta quality.
+It can be used with caution.
see the Wiki for more info on how to use your Sungrow inverter */
/*
@@ -18,8 +23,115 @@ S/N of Module 2: EM032D2310123462DF
S/N of Module 3: EM032D2310123463DF
*/
+// ---- Internal Modbus helper namespace for SBRXXX threshold block ----
+namespace {
+
+// Modbus RTU constants for the SBRXXX battery on CAN ID 0x1E0.
+
+// SOC thresholds in percent * 100 (pptt).
+constexpr uint16_t MODBUS_SOC_CUTOFF_PPTT = 500; // 5.00 %
+constexpr uint16_t MODBUS_SOC_EMERGENCY_PPTT = 300; // 3.00 %
+constexpr uint16_t MODBUS_SOC_IDLE_TRIGGER_PPTT = 200; // 2.00 %
+
+// Modbus function codes we care about.
+constexpr uint8_t MODBUS_FUNC_READ_HOLDING_REGS = 0x03;
+constexpr uint8_t MODBUS_FUNC_READ_INPUT_REGS = 0x04;
+
+// Backing store for 6 registers starting at 0x4DE2:
+// Reg0: cutoff SOC of discharge (pptt)
+// Reg1: reserved
+// Reg2: emergency charging SOC (pptt)
+// Reg3: reserved
+// Reg4: trigger SOC of idle mode (pptt)
+// Reg5: reserved
+constexpr uint16_t MODBUS_REGISTER_VALUES[SungrowInverter::MODBUS_REGISTER_QTY] = {
+ MODBUS_SOC_CUTOFF_PPTT, 0, MODBUS_SOC_EMERGENCY_PPTT, 0, MODBUS_SOC_IDLE_TRIGGER_PPTT, 0};
+
+// Maximum number of Modbus data bytes based on the virtual register window.
+constexpr uint8_t MODBUS_MAX_DATA_BYTES = static_cast(SungrowInverter::MODBUS_REGISTER_QTY * 2);
+
+// Minimal Modbus RTU CRC16 (poly 0xA001, little-endian output).
+uint16_t modbus_crc(const uint8_t* data, uint8_t length) {
+ uint16_t crc = 0xFFFF;
+ for (uint8_t i = 0; i < length; ++i) {
+ crc ^= data[i];
+ for (uint8_t bit = 0; bit < 8; ++bit) {
+ if (crc & 0x0001) {
+ crc = (crc >> 1) ^ 0xA001;
+ } else {
+ crc >>= 1;
+ }
+ }
+ }
+ return crc;
+}
+
+} // namespace
+
+bool SungrowInverter::setup() {
+ // Stored value is model (0-6), get_config_for_model handles default
+ battery_config = get_config_for_model(user_selected_inverter_sungrow_type);
+
+ // Cell type for each active module
+ // 0x42 = IEC
+ // 0x44 = Non-IEC
+ for (uint8_t i = 0; i < battery_config.module_count && i < 8; i++) {
+ SUNGROW_71A.data.u8[i] = 0x44;
+ }
+
+ // Production date for active modules
+ // 0x71B: modules 1+2, 0x71C: modules 3+4, 0x71D: modules 5+6, 0x71E: modules 7+8
+ const uint8_t prod_date[4] = {0x3F, 0x7A, 0x6F, 0x01};
+ CAN_frame* prod_date_frames[4] = {&SUNGROW_71B, &SUNGROW_71C, &SUNGROW_71D, &SUNGROW_71E};
+ for (uint8_t m = 0; m < battery_config.module_count && m < 8; m++) {
+ uint8_t frame_idx = m / 2; // 0-1->0, 2-3->1, 4-5->2, 6-7->3
+ uint8_t offset = (m % 2) * 4; // even->0, odd->4
+ memcpy(&prod_date_frames[frame_idx]->data.u8[offset], prod_date, 4);
+ }
+
+ // Serial numbers: base serial with incrementing module number
+ // Format: "EM03XY" + "231012" + "346NDF" where X=cell type, N=module number
+ // Fragment 1 byte 7 = cell type (same as 0x71A value)
+ CAN_frame* serial_frames[8][3] = {{&SUNGROW_71F_01_01, &SUNGROW_71F_01_02, &SUNGROW_71F_01_03},
+ {&SUNGROW_71F_02_01, &SUNGROW_71F_02_02, &SUNGROW_71F_02_03},
+ {&SUNGROW_71F_03_01, &SUNGROW_71F_03_02, &SUNGROW_71F_03_03},
+ {&SUNGROW_71F_04_01, &SUNGROW_71F_04_02, &SUNGROW_71F_04_03},
+ {&SUNGROW_71F_05_01, &SUNGROW_71F_05_02, &SUNGROW_71F_05_03},
+ {&SUNGROW_71F_06_01, &SUNGROW_71F_06_02, &SUNGROW_71F_06_03},
+ {&SUNGROW_71F_07_01, &SUNGROW_71F_07_02, &SUNGROW_71F_07_03},
+ {&SUNGROW_71F_08_01, &SUNGROW_71F_08_02, &SUNGROW_71F_08_03}};
+
+ for (uint8_t m = 0; m < battery_config.module_count && m < 8; m++) {
+ // Fragment 1: "EM032" + cell_type (byte 7 special)
+ serial_frames[m][0]->data.u8[2] = 0x45; // 'E'
+ serial_frames[m][0]->data.u8[3] = 0x4D; // 'M'
+ serial_frames[m][0]->data.u8[4] = 0x30; // '0'
+ serial_frames[m][0]->data.u8[5] = 0x33; // '3'
+ serial_frames[m][0]->data.u8[6] = 0x32; // '2'
+ serial_frames[m][0]->data.u8[7] = SUNGROW_71A.data.u8[m]; // cell type
+
+ // Fragment 2: "231012"
+ serial_frames[m][1]->data.u8[2] = 0x32; // '2'
+ serial_frames[m][1]->data.u8[3] = 0x33; // '3'
+ serial_frames[m][1]->data.u8[4] = 0x31; // '1'
+ serial_frames[m][1]->data.u8[5] = 0x30; // '0'
+ serial_frames[m][1]->data.u8[6] = 0x31; // '1'
+ serial_frames[m][1]->data.u8[7] = 0x32; // '2'
+
+ // Fragment 3: "346NDF" where N = module number ('1'-'8')
+ serial_frames[m][2]->data.u8[2] = 0x33; // '3'
+ serial_frames[m][2]->data.u8[3] = 0x34; // '4'
+ serial_frames[m][2]->data.u8[4] = 0x36; // '6'
+ serial_frames[m][2]->data.u8[5] = '1' + m; // '1', '2', '3', ... '8'
+ serial_frames[m][2]->data.u8[6] = 0x44; // 'D'
+ serial_frames[m][2]->data.u8[7] = 0x46; // 'F'
+ }
+
+ return true;
+}
+
void SungrowInverter::update_values() {
- current_dA = datalayer.battery.status.current_dA;
+ current_dA = datalayer.battery.status.reported_current_dA;
// Actual SoC
SUNGROW_400.data.u8[1] = (datalayer.battery.status.real_soc & 0x00FF);
@@ -30,15 +142,15 @@ void SungrowInverter::update_values() {
// BMS init message
SUNGROW_500.data.u8[0] = 0x01; // Magic number
SUNGROW_500.data.u8[1] = 0x01; // Magic number
- SUNGROW_500.data.u8[2] = 0x03; // Number of modules?
+ SUNGROW_500.data.u8[2] = 0x03; // Magic number
SUNGROW_500.data.u8[3] = 0xFF; // Magic number
SUNGROW_500.data.u8[5] = 0x01; // Magic number
SUNGROW_500.data.u8[7] = (datalayer.battery.status.reported_soc / 100); // SoC as a int
- //Maxvoltage (eg 400.0V = 4000 , 16bits long)
+ // Max voltage (eg 400.0V = 4000 , 16bits long)
SUNGROW_701.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
SUNGROW_701.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
- //Minvoltage (eg 300.0V = 3000 , 16bits long)
+ // Min voltage (eg 300.0V = 3000 , 16bits long)
SUNGROW_701.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
SUNGROW_701.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
// Max Charging Current
@@ -48,24 +160,24 @@ void SungrowInverter::update_values() {
SUNGROW_701.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
SUNGROW_701.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
- //SOC (100.0%)
+ // SOC (100.0%)
SUNGROW_702.data.u8[0] = (datalayer.battery.status.reported_soc & 0x00FF);
SUNGROW_702.data.u8[1] = (datalayer.battery.status.reported_soc >> 8);
- //SOH (100.00%)
+ // SOH (100.00%)
SUNGROW_702.data.u8[2] = (datalayer.battery.status.soh_pptt & 0x00FF);
SUNGROW_702.data.u8[3] = (datalayer.battery.status.soh_pptt >> 8);
// Energy Remaining (Wh), clamped to 16-bit to avoid overflow on large packs
remaining_wh = datalayer.battery.status.reported_remaining_capacity_Wh;
if (remaining_wh > 0xFFFFu)
remaining_wh = 0xFFFFu;
- SUNGROW_702.data.u8[4] = static_cast(remaining_wh & 0x00FF);
- SUNGROW_702.data.u8[5] = static_cast((remaining_wh >> 8) & 0x00FF);
+ SUNGROW_702.data.u8[4] = (remaining_wh & 0x00FF);
+ SUNGROW_702.data.u8[5] = (remaining_wh >> 8);
// Capacity max (Wh), clamped to 16-bit to avoid overflow on large packs
capacity_wh = datalayer.battery.info.reported_total_capacity_Wh;
if (capacity_wh > 0xFFFFu)
capacity_wh = 0xFFFFu;
- SUNGROW_702.data.u8[6] = static_cast(capacity_wh & 0x00FF);
- SUNGROW_702.data.u8[7] = static_cast((capacity_wh >> 8) & 0x00FF);
+ SUNGROW_702.data.u8[6] = (capacity_wh & 0x00FF);
+ SUNGROW_702.data.u8[7] = (capacity_wh >> 8);
// Energy total charged (Wh)
SUNGROW_703.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF);
@@ -82,49 +194,50 @@ void SungrowInverter::update_values() {
SUNGROW_704.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
SUNGROW_704.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
// Current
- SUNGROW_704.data.u8[2] = static_cast(current_dA & 0xFF);
- SUNGROW_704.data.u8[3] = static_cast((current_dA >> 8) & 0xFF);
+ SUNGROW_704.data.u8[2] = (current_dA & 0xFF);
+ SUNGROW_704.data.u8[3] = ((current_dA >> 8) & 0xFF);
// Another voltage. Different but similar
SUNGROW_704.data.u8[4] = (datalayer.battery.status.voltage_dV & 0x00FF);
SUNGROW_704.data.u8[5] = (datalayer.battery.status.voltage_dV >> 8);
- //Temperature //TODO: Signed correctly? Also should be put AVG here?
- SUNGROW_704.data.u8[6] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- SUNGROW_704.data.u8[7] = (datalayer.battery.status.temperature_max_dC >> 8);
-
- //Status bytes?
- SUNGROW_705.data.u8[0] = 0x02; // Magic number
- SUNGROW_705.data.u8[1] = 0x00; // Magic number
- SUNGROW_705.data.u8[2] = 0x01; // Magic number
- SUNGROW_705.data.u8[3] = 0xE7; // Magic number
- SUNGROW_705.data.u8[4] = 0x20; // Magic number
+ // Temperature (signed int16_t in 0.1ยฐC units)
+ SUNGROW_704.data.u8[6] = (datalayer.battery.status.temperature_max_dC & 0xFF);
+ SUNGROW_704.data.u8[7] = ((datalayer.battery.status.temperature_max_dC >> 8) & 0xFF);
+
+ // Battery status: 0=Unplugged, 1=Standby, 2=Run
+ SUNGROW_705.data.u8[0] = 0x02; // Always "Run"
+ SUNGROW_705.data.u8[1] = 0x00; // Magic number
+ SUNGROW_705.data.u8[2] = 0x01; // Magic number
+ uint16_t battery_model_id = 8420 + battery_config.module_count; // SBR064=8422, SBR096=8423, etc.
+ SUNGROW_705.data.u8[3] = (battery_model_id & 0xFF);
+ SUNGROW_705.data.u8[4] = (battery_model_id >> 8);
//Vbat, again (eg 400.0V = 4000 , 16bits long)
SUNGROW_705.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
SUNGROW_705.data.u8[6] = (datalayer.battery.status.voltage_dV >> 8);
// Padding?
SUNGROW_705.data.u8[7] = 0x00; // Magic number
- //Temperature Max //TODO: Signed correctly?
- SUNGROW_706.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
- SUNGROW_706.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
- //Temperature Min //TODO: Signed correctly?
- SUNGROW_706.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
- SUNGROW_706.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
- //Cell voltage max
+ // Temperature Max (signed int16_t in 0.1ยฐC units)
+ SUNGROW_706.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0xFF);
+ SUNGROW_706.data.u8[1] = ((datalayer.battery.status.temperature_max_dC >> 8) & 0xFF);
+ // Temperature Min (signed int16_t in 0.1ยฐC units)
+ SUNGROW_706.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0xFF);
+ SUNGROW_706.data.u8[3] = ((datalayer.battery.status.temperature_min_dC >> 8) & 0xFF);
+ // Cell voltage max
SUNGROW_706.data.u8[4] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
SUNGROW_706.data.u8[5] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
- //Cell voltage min
+ // Cell voltage min
SUNGROW_706.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
SUNGROW_706.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
// Battery Configuration
- SUNGROW_707.data.u8[0] = 0x26; // Magic number
- SUNGROW_707.data.u8[1] = 0x00; // Magic number
- SUNGROW_707.data.u8[2] = 0x00; // Magic number
- SUNGROW_707.data.u8[3] = 0x01; // Magic number. Num of stacks?
- SUNGROW_707.data.u8[4] = (NAMEPLATE_WH & 0x00FF); // Nameplate capacity
- SUNGROW_707.data.u8[5] = (NAMEPLATE_WH >> 8); // Nameplate capacity
- SUNGROW_707.data.u8[6] = 0x03; // Magic number. Num of modules?
- SUNGROW_707.data.u8[7] = 0x00; // Padding?
+ SUNGROW_707.data.u8[0] = 0x26; // Magic number
+ SUNGROW_707.data.u8[1] = 0x00; // Magic number
+ SUNGROW_707.data.u8[2] = 0x00; // Magic number
+ SUNGROW_707.data.u8[3] = 0x01; // Magic number. Num of stacks?
+ SUNGROW_707.data.u8[4] = (battery_config.nameplate_wh & 0x00FF); // Nameplate capacity
+ SUNGROW_707.data.u8[5] = (battery_config.nameplate_wh >> 8); // Nameplate capacity
+ SUNGROW_707.data.u8[6] = battery_config.module_count; // Num of modules
+ SUNGROW_707.data.u8[7] = 0x00; // Padding?
// ---- 0x70F: two muxes (b0 = 0x00..0x07, b1 = 0x00) ----
SUNGROW_70F_00.data.u8[2] = 0x88; // Magic number
@@ -133,151 +246,130 @@ void SungrowInverter::update_values() {
SUNGROW_70F_00.data.u8[5] = 0x16; // Magic number
SUNGROW_70F_00.data.u8[6] = 0x80; // Magic number
SUNGROW_70F_00.data.u8[7] = 0x16; // Magic number
-
// Charge counter??
SUNGROW_70F_02.data.u8[2] = 0x70; // Magic number
SUNGROW_70F_02.data.u8[3] = 0x20; // Magic number
// Unknown
SUNGROW_70F_02.data.u8[6] = 0x92; // Magic number
SUNGROW_70F_02.data.u8[7] = 0x09; // Magic number
-
// Discharge counter??
SUNGROW_70F_03.data.u8[2] = 0xFD;
SUNGROW_70F_03.data.u8[3] = 0x1D;
// Unknown
SUNGROW_70F_03.data.u8[6] = 0xCE;
SUNGROW_70F_03.data.u8[7] = 0x26;
-
// Unknown
SUNGROW_70F_04.data.u8[2] = 0x0C;
SUNGROW_70F_04.data.u8[3] = 0x06;
- // Module 1 SoC
- SUNGROW_70F_05.data.u8[2] = (datalayer.battery.status.real_soc & 0xFF);
- SUNGROW_70F_05.data.u8[3] = (datalayer.battery.status.real_soc >> 8);
- // Module 2 SoC
- SUNGROW_70F_05.data.u8[4] = (datalayer.battery.status.real_soc & 0xFF);
- SUNGROW_70F_05.data.u8[5] = (datalayer.battery.status.real_soc >> 8);
- // Module 3 SoC
- SUNGROW_70F_05.data.u8[6] = (datalayer.battery.status.real_soc & 0xFF);
- SUNGROW_70F_05.data.u8[7] = (datalayer.battery.status.real_soc >> 8);
-
- //Status bytes?
- SUNGROW_713.data.u8[0] = 0x02; // Magic number
- SUNGROW_713.data.u8[1] = 0x01; // Magic number
- SUNGROW_713.data.u8[2] = 0x77; // Magic number
- SUNGROW_713.data.u8[3] = 0x00; // Magic number
- SUNGROW_713.data.u8[4] = 0x01; // Magic number
- SUNGROW_713.data.u8[5] = 0x02; // Magic number
- SUNGROW_713.data.u8[6] = 0x89; // Magic number
- SUNGROW_713.data.u8[7] = 0x00; // Magic number
-
- // Overview - Stack overview?
- SUNGROW_714.data.u8[0] = 0x05; // Enum??
- SUNGROW_714.data.u8[1] = 0x01; // Magic number
- // Cell Voltage
+ // Populate 0x70F_05/06/07 with module SOC based on module_count
+ // 0x70F_05: modules 1-3, 0x70F_06: modules 4-6, 0x70F_07: modules 7-8
+ {
+ CAN_frame* soc_frames[3] = {&SUNGROW_70F_05, &SUNGROW_70F_06, &SUNGROW_70F_07};
+ for (uint8_t m = 0; m < battery_config.module_count && m < 8; m++) {
+ uint8_t frame_idx = m / 3; // 0-2->0, 3-5->1, 6-7->2
+ uint8_t offset = (m % 3) * 2 + 2; // 0->2, 1->4, 2->6
+ soc_frames[frame_idx]->data.u8[offset] = (datalayer.battery.status.real_soc & 0xFF);
+ soc_frames[frame_idx]->data.u8[offset + 1] = (datalayer.battery.status.real_soc >> 8);
+ }
+ }
+
+ // 0x713 - Battery Cell Temperature Overview
+ // Cell position with minimum temperature (cell/module addressing)
+ SUNGROW_713.data.u8[0] = 0x02; // Cell location with minimum temperature
+ SUNGROW_713.data.u8[1] = 0x01; // Module location with minimum temperature
+ // Minimum cell temperature (signed int16_t in 0.1ยฐC units)
+ SUNGROW_713.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0xFF);
+ SUNGROW_713.data.u8[3] = ((datalayer.battery.status.temperature_min_dC >> 8) & 0xFF);
+ // Cell position with maximum temperature (cell/module addressing)
+ SUNGROW_713.data.u8[4] = 0x01; // Cell location with maximum temperature
+ SUNGROW_713.data.u8[5] = 0x02; // Module location with maximum temperature
+ // Maximum cell temperature (signed int16_t in 0.1ยฐC units)
+ SUNGROW_713.data.u8[6] = (datalayer.battery.status.temperature_max_dC & 0xFF);
+ SUNGROW_713.data.u8[7] = ((datalayer.battery.status.temperature_max_dC >> 8) & 0xFF);
+
+ // 0x714 - Battery Cell Voltage Overview
+ // Cell position with maximum voltage (cell/module addressing)
+ SUNGROW_714.data.u8[0] = 0x05; // Cell location with minimum voltage
+ SUNGROW_714.data.u8[1] = 0x01; // Module location with minimum voltage
+ // Maximum cell voltage (0.1 mV units = cell_max_voltage_mV * 10)
SUNGROW_714.data.u8[2] = ((datalayer.battery.status.cell_max_voltage_mV * 10) & 0x00FF);
SUNGROW_714.data.u8[3] = ((datalayer.battery.status.cell_max_voltage_mV * 10) >> 8);
- SUNGROW_714.data.u8[4] = 0x11; // Enum???
- SUNGROW_714.data.u8[5] = 0x02; // Magic number
- // Cell Voltage
+ // Cell position with minimum voltage (cell/module addressing)
+ SUNGROW_714.data.u8[4] = 0x11; // Cell location with maximum voltage
+ SUNGROW_714.data.u8[5] = 0x02; // Module location with maximum voltage
+ // Minimum cell voltage (0.1 mV units = cell_min_voltage_mV * 10)
SUNGROW_714.data.u8[6] = ((datalayer.battery.status.cell_min_voltage_mV * 10) & 0x00FF);
SUNGROW_714.data.u8[7] = ((datalayer.battery.status.cell_min_voltage_mV * 10) >> 8);
- // Module 1 Min Cell voltage
- SUNGROW_715.data.u8[0] = ((datalayer.battery.status.cell_min_voltage_mV * 10) & 0x00FF);
- SUNGROW_715.data.u8[1] = ((datalayer.battery.status.cell_min_voltage_mV * 10) >> 8);
- // Module 1 Max Cell voltage
- SUNGROW_715.data.u8[2] = ((datalayer.battery.status.cell_max_voltage_mV * 10) & 0x00FF);
- SUNGROW_715.data.u8[3] = ((datalayer.battery.status.cell_max_voltage_mV * 10) >> 8);
- // Module 2 Min Cell voltage
- SUNGROW_715.data.u8[4] = ((datalayer.battery.status.cell_min_voltage_mV * 10) & 0x00FF);
- SUNGROW_715.data.u8[5] = ((datalayer.battery.status.cell_min_voltage_mV * 10) >> 8);
- // Module 2 Max Cell voltage
- SUNGROW_715.data.u8[6] = ((datalayer.battery.status.cell_max_voltage_mV * 10) & 0x00FF);
- SUNGROW_715.data.u8[7] = ((datalayer.battery.status.cell_max_voltage_mV * 10) >> 8);
-
- // Module 3 Min Cell voltage
- SUNGROW_716.data.u8[0] = ((datalayer.battery.status.cell_min_voltage_mV * 10) & 0x00FF);
- SUNGROW_716.data.u8[1] = ((datalayer.battery.status.cell_min_voltage_mV * 10) >> 8);
- // Module 3 Max Cell voltage
- SUNGROW_716.data.u8[2] = ((datalayer.battery.status.cell_max_voltage_mV * 10) & 0x00FF);
- SUNGROW_716.data.u8[3] = ((datalayer.battery.status.cell_max_voltage_mV * 10) >> 8);
-
- SUNGROW_717.data.u8[0] = 0x00;
-
- // Status flags?
- SUNGROW_719.data.u8[0] = 0x02;
+ // 0x715-0x718 - Module Cell Voltage Overview (min/max per module)
+ // Each frame holds 2 modules: [min1, max1, min2, max2] in 0.1mV units
+ // 0x715: modules 1+2, 0x716: modules 3+4, 0x717: modules 5+6, 0x718: modules 7+8
+ {
+ const uint16_t cell_min_01mV = datalayer.battery.status.cell_min_voltage_mV * 10;
+ const uint16_t cell_max_01mV = datalayer.battery.status.cell_max_voltage_mV * 10;
+ CAN_frame* voltage_frames[4] = {&SUNGROW_715, &SUNGROW_716, &SUNGROW_717, &SUNGROW_718};
+
+ for (uint8_t m = 0; m < battery_config.module_count && m < 8; m++) {
+ uint8_t frame_idx = m / 2; // 0-1->0, 2-3->1, 4-5->2, 6-7->3
+ uint8_t offset = (m % 2) * 4; // even->0, odd->4
+ voltage_frames[frame_idx]->data.u8[offset + 0] = (cell_min_01mV & 0xFF);
+ voltage_frames[frame_idx]->data.u8[offset + 1] = (cell_min_01mV >> 8);
+ voltage_frames[frame_idx]->data.u8[offset + 2] = (cell_max_01mV & 0xFF);
+ voltage_frames[frame_idx]->data.u8[offset + 3] = (cell_max_01mV >> 8);
+ }
+ }
- // Battery flags?
- SUNGROW_71A.data.u8[0] = 0x44; // Magic number
- SUNGROW_71A.data.u8[1] = 0x44; // Magic number
- SUNGROW_71A.data.u8[2] = 0x44; // Magic number
-
- // Module 1 + 2 data?
- SUNGROW_71B.data.u8[0] = 0x3F; // Magic number
- SUNGROW_71B.data.u8[1] = 0x7A; // Magic number
- SUNGROW_71B.data.u8[2] = 0x6F; // Magic number
- SUNGROW_71B.data.u8[3] = 0x01; // Magic number
- SUNGROW_71B.data.u8[4] = 0x3F; // Magic number
- SUNGROW_71B.data.u8[5] = 0x7A; // Magic number
- SUNGROW_71B.data.u8[6] = 0x6F; // Magic number
- SUNGROW_71B.data.u8[7] = 0x01; // Magic number
-
- // Module 3 + 4 data?
- SUNGROW_71C.data.u8[0] = 0x3F; // Magic number
- SUNGROW_71C.data.u8[1] = 0x7A; // Magic number
- SUNGROW_71C.data.u8[2] = 0x6F; // Magic number
- SUNGROW_71C.data.u8[3] = 0x01; // Magic number
+ // 0x719 - Status and Module Fault???
+ // Possibly relates to Modbus register 3_10789 and 3_10790
+ SUNGROW_719.data.u8[0] = 0x02;
//Copy 7## content to 0## messages
- for (int i = 0; i < 8; i++) {
- // SUNGROW_000 all bytes 0x00
- SUNGROW_001.data.u8[i] = SUNGROW_701.data.u8[i];
- SUNGROW_002.data.u8[i] = SUNGROW_702.data.u8[i];
- SUNGROW_003.data.u8[i] = SUNGROW_703.data.u8[i];
- SUNGROW_004.data.u8[i] = SUNGROW_704.data.u8[i];
- SUNGROW_005.data.u8[i] = SUNGROW_705.data.u8[i];
- SUNGROW_006.data.u8[i] = SUNGROW_706.data.u8[i];
- SUNGROW_007.data.u8[i] = SUNGROW_707.data.u8[i];
- SUNGROW_008_00.data.u8[i] = SUNGROW_708_00.data.u8[i];
- SUNGROW_008_01.data.u8[i] = SUNGROW_708_01.data.u8[i];
- // SUNGROW_009 all bytes 0x00
- SUNGROW_00A_00.data.u8[i] = SUNGROW_70A_00.data.u8[i];
- SUNGROW_00A_01.data.u8[i] = SUNGROW_70A_01.data.u8[i];
- SUNGROW_00B.data.u8[i] = SUNGROW_70B.data.u8[i];
- SUNGROW_00D.data.u8[i] = SUNGROW_70D.data.u8[i];
- SUNGROW_00E.data.u8[i] = SUNGROW_70E.data.u8[i];
- SUNGROW_013.data.u8[i] = SUNGROW_713.data.u8[i];
- SUNGROW_014.data.u8[i] = SUNGROW_714.data.u8[i];
- SUNGROW_015.data.u8[i] = SUNGROW_715.data.u8[i];
- SUNGROW_016.data.u8[i] = SUNGROW_716.data.u8[i];
- SUNGROW_017.data.u8[i] = SUNGROW_717.data.u8[i];
- SUNGROW_018.data.u8[i] = SUNGROW_718.data.u8[i];
- SUNGROW_019.data.u8[i] = SUNGROW_719.data.u8[i];
- SUNGROW_01A.data.u8[i] = SUNGROW_71A.data.u8[i];
- SUNGROW_01B.data.u8[i] = SUNGROW_71B.data.u8[i];
- SUNGROW_01C.data.u8[i] = SUNGROW_71C.data.u8[i];
- SUNGROW_01D.data.u8[i] = SUNGROW_71D.data.u8[i];
- SUNGROW_01E.data.u8[i] = SUNGROW_71E.data.u8[i];
- }
+ // SUNGROW_000 all bytes 0x00
+ memcpy(SUNGROW_001.data.u8, SUNGROW_701.data.u8, 8);
+ memcpy(SUNGROW_002.data.u8, SUNGROW_702.data.u8, 8);
+ memcpy(SUNGROW_003.data.u8, SUNGROW_703.data.u8, 8);
+ memcpy(SUNGROW_004.data.u8, SUNGROW_704.data.u8, 8);
+ memcpy(SUNGROW_005.data.u8, SUNGROW_705.data.u8, 8);
+ memcpy(SUNGROW_006.data.u8, SUNGROW_706.data.u8, 8);
+ memcpy(SUNGROW_007.data.u8, SUNGROW_707.data.u8, 8);
+ memcpy(SUNGROW_008_00.data.u8, SUNGROW_708_00.data.u8, 8);
+ memcpy(SUNGROW_008_01.data.u8, SUNGROW_708_01.data.u8, 8);
+ // SUNGROW_009 all bytes 0x00
+ memcpy(SUNGROW_00A_00.data.u8, SUNGROW_70A_00.data.u8, 8);
+ memcpy(SUNGROW_00A_01.data.u8, SUNGROW_70A_01.data.u8, 8);
+ memcpy(SUNGROW_00B.data.u8, SUNGROW_70B.data.u8, 8);
+ memcpy(SUNGROW_00D.data.u8, SUNGROW_70D.data.u8, 8);
+ memcpy(SUNGROW_00E.data.u8, SUNGROW_70E.data.u8, 8);
+ memcpy(SUNGROW_013.data.u8, SUNGROW_713.data.u8, 8);
+ memcpy(SUNGROW_014.data.u8, SUNGROW_714.data.u8, 8);
+ memcpy(SUNGROW_015.data.u8, SUNGROW_715.data.u8, 8);
+ memcpy(SUNGROW_016.data.u8, SUNGROW_716.data.u8, 8);
+ memcpy(SUNGROW_017.data.u8, SUNGROW_717.data.u8, 8);
+ memcpy(SUNGROW_018.data.u8, SUNGROW_718.data.u8, 8);
+ memcpy(SUNGROW_019.data.u8, SUNGROW_719.data.u8, 8);
+ memcpy(SUNGROW_01A.data.u8, SUNGROW_71A.data.u8, 8);
+ memcpy(SUNGROW_01B.data.u8, SUNGROW_71B.data.u8, 8);
+ memcpy(SUNGROW_01C.data.u8, SUNGROW_71C.data.u8, 8);
+ memcpy(SUNGROW_01D.data.u8, SUNGROW_71D.data.u8, 8);
+ memcpy(SUNGROW_01E.data.u8, SUNGROW_71E.data.u8, 8);
//Copy 7## content to 5## messages
- for (int i = 0; i < 8; i++) {
- SUNGROW_501.data.u8[i] = SUNGROW_701.data.u8[i];
- SUNGROW_502.data.u8[i] = SUNGROW_702.data.u8[i];
- SUNGROW_503.data.u8[i] = SUNGROW_703.data.u8[i];
- SUNGROW_504.data.u8[i] = SUNGROW_704.data.u8[i];
- SUNGROW_505.data.u8[i] = SUNGROW_705.data.u8[i];
- SUNGROW_506.data.u8[i] = SUNGROW_706.data.u8[i];
- }
+ memcpy(SUNGROW_501.data.u8, SUNGROW_701.data.u8, 8);
+ memcpy(SUNGROW_502.data.u8, SUNGROW_702.data.u8, 8);
+ memcpy(SUNGROW_503.data.u8, SUNGROW_703.data.u8, 8);
+ memcpy(SUNGROW_504.data.u8, SUNGROW_704.data.u8, 8);
+ memcpy(SUNGROW_505.data.u8, SUNGROW_705.data.u8, 8);
+ memcpy(SUNGROW_506.data.u8, SUNGROW_706.data.u8, 8);
+
// 0x504 cannot be a straight copy: current must be the opposite sign
int32_t flipped = -(static_cast(current_dA));
int16_t current_dA_flipped = clamp_i32_to_i16(flipped);
- SUNGROW_504.data.u8[2] = static_cast(current_dA_flipped & 0xFF);
- SUNGROW_504.data.u8[3] = static_cast((current_dA_flipped >> 8) & 0xFF);
+ SUNGROW_504.data.u8[2] = (current_dA_flipped & 0xFF);
+ SUNGROW_504.data.u8[3] = ((current_dA_flipped >> 8) & 0xFF);
+// TODO: This needs to do something useful
#ifdef DEBUG_VIA_USB
if (inverter_sends_000) {
Serial.println("Inverter sends 0x000");
@@ -421,10 +513,10 @@ void SungrowInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
transmit_can_init = true; // We only see 0x191 when the inverter is searching for a battery.
break;
- case 0x1E0:
- // Modbus RTU over CAN??
+ case 0x1E0: {
+ // Modbus RTU over CAN from inverter to battery.
- // Inverter
+ // Inverter Request
// [8] 01 04 4D E2 00 02 C6 91
// Slave Addr: 0x01
// Function: 0x04
@@ -440,17 +532,98 @@ void SungrowInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
// Data: 0x01F4, 0x0000 => Decimal 500, 0
// CRC16: 0x8ABB
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- //transmit_can_frame(&SUNGROW_1E0_00);
- //transmit_can_frame(&SUNGROW_1E0_01);
- break;
- case 0x00004200: //Only sent by SH15T (Inverter trying to use Pylon CAN)
- // Seen on AU SH10RS @ 500k. Incorrect bitrate?
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
- case 0x02007F00: //Only sent by SH15T (Inverter trying to use Pylon CAN)
- // Seen on AU SH10RS @ 500k. Incorrect bitrate?
- datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
- break;
+
+ // We only handle 8-byte Modbus request frames here.
+ if (rx_frame.DLC != 8) {
+ break;
+ }
+
+ const uint8_t* p = rx_frame.data.u8;
+
+ // Validate Modbus RTU CRC16 (little-endian in bytes 6..7) over the first 6 bytes.
+ const uint16_t req_crc = static_cast(p[6]) | (static_cast(p[7]) << 8);
+ const uint16_t calc_crc = modbus_crc(p, 6);
+ if (req_crc != calc_crc) {
+ break;
+ }
+
+ const uint8_t addr = p[0];
+ const uint8_t func = p[1];
+ const uint16_t start_addr = (static_cast(p[2]) << 8) | p[3];
+ const uint16_t quantity = (static_cast(p[4]) << 8) | p[5];
+
+ if (addr != SungrowInverter::MODBUS_SLAVE_ADDR || start_addr != SungrowInverter::MODBUS_REGISTER_BASE_ADDR) {
+ break;
+ }
+
+ // Helper to construct a Modbus RTU response (addr/func/byteCount/data),
+ // append CRC, split into CAN frames of up to 8 bytes, and send them.
+ auto send_modbus_response = [this, &rx_frame](const uint8_t* payload, uint8_t payload_len) {
+ constexpr uint8_t MAX_PAYLOAD = 3 + MODBUS_MAX_DATA_BYTES; // addr + func + byteCount + data bytes
+ constexpr uint8_t MAX_TOTAL = MAX_PAYLOAD + 2; // + CRC16
+
+ if (payload_len > MAX_PAYLOAD) {
+ return; // Defensive: ignore impossible lengths.
+ }
+
+ uint8_t frame_bytes[MAX_TOTAL];
+
+ // Copy payload (addr, func, byteCount, data...)
+ for (uint8_t i = 0; i < payload_len; ++i) {
+ frame_bytes[i] = payload[i];
+ }
+
+ // Append CRC16 (little-endian)
+ const uint16_t crc = modbus_crc(payload, payload_len);
+ const uint8_t crc_lo = static_cast(crc & 0xFF);
+ const uint8_t crc_hi = static_cast((crc >> 8) & 0xFF);
+
+ frame_bytes[payload_len] = crc_lo;
+ frame_bytes[payload_len + 1] = crc_hi;
+
+ uint8_t total_len = payload_len + 2; // payload + CRC
+ uint8_t offset = 0;
+
+ while (total_len > 0) {
+ CAN_frame f = rx_frame;
+ const uint8_t chunk = (total_len > 8) ? 8 : total_len;
+ f.DLC = chunk;
+ for (uint8_t i = 0; i < chunk; ++i) {
+ f.data.u8[i] = frame_bytes[offset + i];
+ }
+ transmit_can_frame(&f);
+ offset += chunk;
+ total_len -= chunk;
+ }
+ };
+
+ auto send_modbus_registers = [send_modbus_response, addr, func](uint16_t quantity) {
+ if (quantity == 0 || quantity > SungrowInverter::MODBUS_REGISTER_QTY) {
+ return;
+ }
+
+ const uint8_t byte_count = static_cast(quantity * 2);
+ uint8_t payload[3 + MODBUS_MAX_DATA_BYTES]; // addr, func, byteCount, data bytes
+
+ payload[0] = addr;
+ payload[1] = func;
+ payload[2] = byte_count;
+
+ uint8_t idx = 3;
+ for (uint16_t reg = 0; reg < quantity; ++reg) {
+ const uint16_t value = MODBUS_REGISTER_VALUES[reg];
+ payload[idx++] = static_cast((value >> 8) & 0xFF);
+ payload[idx++] = static_cast(value & 0xFF);
+ }
+
+ send_modbus_response(payload, idx);
+ };
+
+ // For now we only support 0x03/0x04 reads against this register window.
+ if (func == MODBUS_FUNC_READ_INPUT_REGS || func == MODBUS_FUNC_READ_HOLDING_REGS) {
+ send_modbus_registers(quantity);
+ }
+ } break;
default:
break;
}
@@ -557,12 +730,14 @@ void SungrowInverter::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&SUNGROW_70F_02);
transmit_can_frame(&SUNGROW_70F_03);
transmit_can_frame(&SUNGROW_70F_04);
- transmit_can_frame(&SUNGROW_70F_05);
- transmit_can_frame(&SUNGROW_70F_06);
- transmit_can_frame(&SUNGROW_70F_07);
+ transmit_can_frame(&SUNGROW_70F_05); // Modules 1-3 SOC (zeros if unpopulated)
+ transmit_can_frame(&SUNGROW_70F_06); // Modules 4-6 SOC (zeros if unpopulated)
+ transmit_can_frame(&SUNGROW_70F_07); // Modules 7-8 SOC (zeros if unpopulated)
transmit_can_frame(&SUNGROW_71A);
- transmit_can_frame(&SUNGROW_71B);
- transmit_can_frame(&SUNGROW_71C);
+ transmit_can_frame(&SUNGROW_71B); // Modules 1+2 production date (zeros if unpopulated)
+ transmit_can_frame(&SUNGROW_71C); // Modules 3+4 production date (zeros if unpopulated)
+ transmit_can_frame(&SUNGROW_71D); // Modules 5+6 production date (zeros if unpopulated)
+ transmit_can_frame(&SUNGROW_71E); // Modules 7+8 production date (zeros if unpopulated)
break;
case 4:
@@ -611,8 +786,35 @@ void SungrowInverter::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&SUNGROW_71F_02_01);
transmit_can_frame(&SUNGROW_71F_02_02);
transmit_can_frame(&SUNGROW_71F_02_03);
- transmit_can_frame(&SUNGROW_71F_03_01);
- transmit_can_frame(&SUNGROW_71F_03_02);
- transmit_can_frame(&SUNGROW_71F_03_03);
+ if (battery_config.module_count >= 3) {
+ transmit_can_frame(&SUNGROW_71F_03_01);
+ transmit_can_frame(&SUNGROW_71F_03_02);
+ transmit_can_frame(&SUNGROW_71F_03_03);
+ }
+ if (battery_config.module_count >= 4) {
+ transmit_can_frame(&SUNGROW_71F_04_01);
+ transmit_can_frame(&SUNGROW_71F_04_02);
+ transmit_can_frame(&SUNGROW_71F_04_03);
+ }
+ if (battery_config.module_count >= 5) {
+ transmit_can_frame(&SUNGROW_71F_05_01);
+ transmit_can_frame(&SUNGROW_71F_05_02);
+ transmit_can_frame(&SUNGROW_71F_05_03);
+ }
+ if (battery_config.module_count >= 6) {
+ transmit_can_frame(&SUNGROW_71F_06_01);
+ transmit_can_frame(&SUNGROW_71F_06_02);
+ transmit_can_frame(&SUNGROW_71F_06_03);
+ }
+ if (battery_config.module_count >= 7) {
+ transmit_can_frame(&SUNGROW_71F_07_01);
+ transmit_can_frame(&SUNGROW_71F_07_02);
+ transmit_can_frame(&SUNGROW_71F_07_03);
+ }
+ if (battery_config.module_count >= 8) {
+ transmit_can_frame(&SUNGROW_71F_08_01);
+ transmit_can_frame(&SUNGROW_71F_08_02);
+ transmit_can_frame(&SUNGROW_71F_08_03);
+ }
}
}
diff --git a/Software/src/inverter/SUNGROW-CAN.h b/Software/src/inverter/SUNGROW-CAN.h
index 6bacd5e52..925a892a2 100644
--- a/Software/src/inverter/SUNGROW-CAN.h
+++ b/Software/src/inverter/SUNGROW-CAN.h
@@ -3,15 +3,25 @@
#include "CanInverterProtocol.h"
+// Sungrow battery configuration
+struct SungrowBatteryConfig {
+ uint16_t nameplate_wh;
+ uint8_t module_count;
+};
+
class SungrowInverter : public CanInverterProtocol {
public:
const char* name() override { return Name; }
// Constructor: request 250 kbps on the inverter CAN interface
SungrowInverter() : CanInverterProtocol(CAN_Speed::CAN_SPEED_250KBPS) {}
+ bool setup() override;
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
static constexpr const char* Name = "Sungrow SBRXXX emulation over CAN bus";
+ static constexpr uint8_t MODBUS_SLAVE_ADDR = 0x01;
+ static constexpr uint16_t MODBUS_REGISTER_BASE_ADDR = 0x4DE2;
+ static constexpr uint16_t MODBUS_REGISTER_QTY = 0x0006;
private:
unsigned long previousMillisBatch = 0;
@@ -28,7 +38,31 @@ class SungrowInverter : public CanInverterProtocol {
uint32_t remaining_wh = 0;
uint32_t capacity_wh = 0;
uint8_t batch_send_index = 0;
- static constexpr uint16_t NAMEPLATE_WH = 9600;
+
+ // Battery configuration (set via user_selected_inverter_sungrow_type = model 0-6)
+ SungrowBatteryConfig battery_config = {9600, 3}; // Default: SBR096
+
+ // Returns config based on battery model (0=SBR064 through 6=SBR256)
+ // Model + 2 = module count, each module adds 3200Wh of capacity
+ static constexpr SungrowBatteryConfig get_config_for_model(uint16_t model) {
+ switch (model) {
+ case 0:
+ return {6400, 2}; // SBR064
+ case 2:
+ return {12800, 4}; // SBR128
+ case 3:
+ return {16000, 5}; // SBR160
+ case 4:
+ return {19200, 6}; // SBR192
+ case 5:
+ return {22400, 7}; // SBR224
+ case 6:
+ return {25600, 8}; // SBR256
+ case 1:
+ default:
+ return {9600, 3}; // SBR096
+ }
+ }
// Cached signed current in deci-amps (range: int16_t)
int16_t current_dA = 0;
@@ -179,12 +213,6 @@ class SungrowInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x01E,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
- CAN_frame SUNGROW_1E0_00 = {.FD = false,
- .ext_ID = false,
- .DLC = 8,
- .ID = 0x1E0,
- .data = {0x01, 0x04, 0x04, 0x01, 0xF4, 0x00, 0x00, 0xBB}};
- CAN_frame SUNGROW_1E0_01 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x1E0, .data = {0x8A}};
CAN_frame SUNGROW_400 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@@ -235,86 +263,121 @@ class SungrowInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x700 - All Zero
CAN_frame SUNGROW_700 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x700,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x701 - Battery Operational Parameters
CAN_frame SUNGROW_701 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x701,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Battery MAX voltage
+ 0x00, 0x00, // Battery MIN voltage
+ 0x00, 0x00, // Battery MAX charge current
+ 0x00, 0x00}}; // Battery MIN charge current
+ // 0x702 - Battery Charge Level Status
CAN_frame SUNGROW_702 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x702,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Battery SoC %
+ 0x00, 0x00, // Battery SoH %
+ 0x00, 0x00, // Battery remaining Wh
+ 0x00, 0x00}}; // Battery total Wh
+ // 0x703 - Battery Energy Status
CAN_frame SUNGROW_703 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x703,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, 0x00, 0x00, // Energy charged
+ 0x00, 0x00, 0x00, 0x00}}; // Energy discharged
+ // 0x704 - Battery Status
CAN_frame SUNGROW_704 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x704,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Battery voltage
+ 0x00, 0x00, // Battery current
+ 0x00, 0x00, // Another related battery voltage
+ 0x00, 0x00}}; // Battery temperature
+ // 0x705 - TODO
CAN_frame SUNGROW_705 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x705,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Status??
+ 0x00, 0x00, 0x00, // ????
+ 0x00, 0x00, // Yet another battery voltage
+ 0x00}}; // Padding??
+ // 0x706 - Battery Cell Status
CAN_frame SUNGROW_706 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x706,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Cell MAX temperature
+ 0x00, 0x00, // Cell MIN temperature
+ 0x00, 0x00, // Cell MIN voltage
+ 0x00, 0x00}}; // Cell MAX voltage
+ // 0x707 - TODO
CAN_frame SUNGROW_707 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x707,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x708 - Battery Serial Number (MUX)
CAN_frame SUNGROW_708_00 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x708,
- .data = {0x00, 0x53, 0x32, 0x33, 0x31, 0x30, 0x31, 0x32}}; // "S231012"
+ .data = {0x00, // Fragment #
+ 0x53, 0x32, 0x33, 0x31, 0x30, 0x31, 0x32}}; // "S231012"
CAN_frame SUNGROW_708_01 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x708,
- .data = {0x01, 0x33, 0x34, 0x35, 0x36, 0x00, 0x00, 0x53}}; // "3456" S suffix
+ .data = {0x01, // Fragment #
+ 0x33, 0x34, 0x35, 0x36, 0x00, 0x00, 0x53}}; // "3456" S suffix
+ // 0x709 - All Zero
CAN_frame SUNGROW_709 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x709,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x70A - Battery Type (MUX)
CAN_frame SUNGROW_70A_00 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x70A,
- .data = {0x00, 0x53, 0x55, 0x4E, 0x47, 0x52, 0x4F, 0x57}};
+ .data = {0x00, // Mux
+ 0x53, 0x55, 0x4E, 0x47, 0x52, 0x4F, 0x57}}; // "SUNGROW"
CAN_frame SUNGROW_70A_01 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x70A,
- .data = {0x01, 0x00, 0x42, 0x52, 0x58, 0x58, 0x58, 0x00}};
+ .data = {0x01, // Mux
+ 0x00, 0x42, 0x52, 0x58, 0x58, 0x58, 0x00}}; // "\0BRXXX\0"
+ // 0x70B - Battery Family
CAN_frame SUNGROW_70B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x70B,
- .data = {0x00, 0x53, 0x42, 0x52, 0x58, 0x58, 0x58, 0x00}};
+ .data = {0x00, 0x53, 0x42, 0x52, 0x58, 0x58, 0x58, 0x00}}; // "\0SBRXXX\0"
+ // 0x70D - TODO Modbus 3_10701
CAN_frame SUNGROW_70D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x70D,
.data = {0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00}};
+ // 0x70E - TODO Modbus 3_10705
CAN_frame SUNGROW_70E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x70E,
.data = {0x07, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x70F (MUX)
CAN_frame SUNGROW_70F_00 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@@ -355,111 +418,232 @@ class SungrowInverter : public CanInverterProtocol {
.DLC = 8,
.ID = 0x70F,
.data = {0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x713 - Battery Cell Temperature Overview
CAN_frame SUNGROW_713 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x713,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, // Cell location with minimum temperature
+ 0x00, // Module location with minimum temperature
+ 0x00, 0x00, // Cell MIN temperature
+ 0x00, // Cell location with maximum temperature
+ 0x00, // Module location with maximum temperature
+ 0x00, 0x00}}; // Cell MAX temperature
+ // 0x714 - Battery Cell Voltage Overview
CAN_frame SUNGROW_714 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x714,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, // Cell location with minimum voltage
+ 0x00, // Module location with minimum voltage
+ 0x00, 0x00, // Cell MIN voltage
+ 0x00, // Cell location with maximum voltage
+ 0x00, // Module location with maximum voltage
+ 0x00, 0x00}}; // Cell MAX voltage
+ // 0x715 - Module 1+2 Cell Voltage Overview
CAN_frame SUNGROW_715 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x715,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Module 1 Cell MIN voltage
+ 0x00, 0x00, // Module 1 Cell MAX voltage
+ 0x00, 0x00, // Module 2 Cell MIN voltage
+ 0x00, 0x00}}; // Module 2 Cell MAX voltage
+ // 0x716 - Module 2+3 Cell Voltage Overview
CAN_frame SUNGROW_716 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x716,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Module 3 Cell MIN voltage
+ 0x00, 0x00, // Module 3 Cell MAX voltage
+ 0x00, 0x00, // Module 4 Cell MIN voltage
+ 0x00, 0x00}}; // Module 4 Cell MAX voltage
+ // 0x717 - Module 5+6 Cell Voltage Overview
CAN_frame SUNGROW_717 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x717,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Module 5 Cell MIN voltage
+ 0x00, 0x00, // Module 5 Cell MAX voltage
+ 0x00, 0x00, // Module 6 Cell MIN voltage
+ 0x00, 0x00}}; // Module 6 Cell MAX voltage
+ // 0x718 - Module 7+8 Cell Voltage Overview
CAN_frame SUNGROW_718 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x718,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, // Module 7 Cell MIN voltage
+ 0x00, 0x00, // Module 7 Cell MAX voltage
+ 0x00, 0x00, // Module 8 Cell MIN voltage
+ 0x00, 0x00}}; // Module 8 Cell MAX voltage
+ // 0x719 - POSSIBLY Status (3_10789) and Module fault (3_10790)
CAN_frame SUNGROW_719 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x719,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ // 0x71A - Module Cell Type (set in setup)
CAN_frame SUNGROW_71A = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71A,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, // Module 1 Cell type (set in setup)
+ 0x00, // Module 2 Cell type (set in setup)
+ 0x00, // Module 3 Cell type (set in setup)
+ 0x00, // Module 4 Cell type (set in setup)
+ 0x00, // Module 5 Cell type (set in setup)
+ 0x00, // Module 6 Cell type (set in setup)
+ 0x00, // Module 7 Cell type (set in setup)
+ 0x00}}; // Module 8 Cell type (set in setup)
+ // 0x71B - Module 1+2 Production Date (set in setup)
CAN_frame SUNGROW_71B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71B,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, 0x00, 0x00, // Module 1 production date
+ 0x00, 0x00, 0x00, 0x00}}; // Module 2 production date
+ // 0x71C - Module 3+4 Production Date (set in setup)
CAN_frame SUNGROW_71C = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71C,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, 0x00, 0x00, // Module 3 production date
+ 0x00, 0x00, 0x00, 0x00}}; // Module 4 production date
+ // 0x71D - Module 5+6 Production Date (set in setup)
CAN_frame SUNGROW_71D = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71D,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, 0x00, 0x00, // Module 5 production date
+ 0x00, 0x00, 0x00, 0x00}}; // Module 6 production date
+ // 0x71E - Module 7+8 Production Date (set in setup)
CAN_frame SUNGROW_71E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71E,
- .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ .data = {0x00, 0x00, 0x00, 0x00, // Module 7 production date
+ 0x00, 0x00, 0x00, 0x00}}; // Module 8 production date
+ // 0x71F - Module Serial Number (MUX) - set in setup()
CAN_frame SUNGROW_71F_01_01 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x01, 0x01, 0x45, 0x4D, 0x30, 0x33, 0x32, 0x44}}; // "EM032D"
+ .data = {0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_01_02 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x01, 0x02, 0x32, 0x33, 0x31, 0x30, 0x31, 0x32}}; // "231012"
+ .data = {0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_01_03 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x01, 0x03, 0x33, 0x34, 0x36, 0x31, 0x44, 0x46}}; // "3461DF"
+ .data = {0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_02_01 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x02, 0x01, 0x45, 0x4D, 0x30, 0x33, 0x32, 0x44}}; // "EM032D"
+ .data = {0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_02_02 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x02, 0x02, 0x32, 0x33, 0x31, 0x30, 0x31, 0x32}}; // "231012"
+ .data = {0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_02_03 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x02, 0x03, 0x33, 0x34, 0x36, 0x32, 0x44, 0x46}}; // "3462DF"
+ .data = {0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_03_01 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x03, 0x01, 0x45, 0x4D, 0x30, 0x33, 0x32, 0x44}}; // "EM032D"
+ .data = {0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_03_02 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x03, 0x02, 0x32, 0x33, 0x31, 0x30, 0x31, 0x32}}; // "231012"
+ .data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SUNGROW_71F_03_03 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x71F,
- .data = {0x03, 0x03, 0x33, 0x34, 0x36, 0x33, 0x44, 0x46}}; // "3463DF"
+ .data = {0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_04_01 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_04_02 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_04_03 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_05_01 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_05_02 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_05_03 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x05, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_06_01 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_06_02 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_06_03 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_07_01 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x07, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_07_02 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x07, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_07_03 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_08_01 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_08_02 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CAN_frame SUNGROW_71F_08_03 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x71F,
+ .data = {0x08, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
};
#endif
diff --git a/Software/src/inverter/VCU-CAN.cpp b/Software/src/inverter/VCU-CAN.cpp
new file mode 100644
index 000000000..b2eb1fa2c
--- /dev/null
+++ b/Software/src/inverter/VCU-CAN.cpp
@@ -0,0 +1,104 @@
+#include "VCU-CAN.h"
+#include "../communication/can/comm_can.h"
+#include "../datalayer/datalayer.h"
+#include "../devboard/utils/common_functions.h" //For CRC table
+#include "../inverter/INVERTERS.h"
+
+/*TODO once testing starts:
+- Map more optional content into all message
+- Add OBD2 responses for 79B /7BB polls
+-*/
+
+static uint8_t calculate_CRC_Nissan(CAN_frame* frame) {
+ uint8_t crc = 0;
+ for (uint8_t j = 0; j < 7; j++) {
+ crc = crctable_nissan_leaf[(crc ^ static_cast(frame->data.u8[j])) % 256];
+ }
+ return crc;
+}
+
+void VCUInverter::update_values() { //Called every 1s
+
+ LEAF_1DC.data.u8[0] = ((datalayer.battery.status.max_discharge_power_W / 4) << 2);
+ LEAF_1DC.data.u8[1] = ((datalayer.battery.status.max_discharge_power_W / 4) << 6);
+ LEAF_1DC.data.u8[1] = (LEAF_1DC.data.u8[1] || (((datalayer.battery.status.max_charge_power_W / 4) >> 4) & 0x3F));
+ LEAF_1DC.data.u8[2] = ((datalayer.battery.status.max_charge_power_W / 4) << 4);
+
+ LEAF_55B.data.u8[0] = ((datalayer.battery.status.real_soc / 10) << 2);
+ LEAF_55B.data.u8[1] = ((datalayer.battery.status.real_soc / 10) << 6);
+
+ LEAF_1DB.data.u8[0] =
+ ((datalayer.battery.status.current_dA / 10) / 2) >> 3; //TODO: This is most likely handled wrong
+ LEAF_1DB.data.u8[1] = ((((datalayer.battery.status.current_dA / 10) / 2) & 0x07) << 5);
+ LEAF_1DB.data.u8[2] = ((datalayer.battery.status.voltage_dV / 10) / 2) >> 2;
+ LEAF_1DB.data.u8[3] = (((datalayer.battery.status.voltage_dV / 10) / 2) << 6) | 0x2B; //Lots of status flags here
+
+ remining_gids = (datalayer.battery.status.real_soc / 10000.0) * 281; //0-281 for 24kWh
+ LEAF_5BC.data.u8[0] = remining_gids << 2;
+ LEAF_5BC.data.u8[1] = remining_gids << 6;
+ LEAF_5BC.data.u8[4] = (datalayer.battery.status.soh_pptt / 100) << 1;
+}
+
+void VCUInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
+ switch (rx_frame.ID) {
+ case 0x1F2:
+ datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x1D4:
+ datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x50B:
+ datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ break;
+ case 0x50C:
+ datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
+ break;
+ default:
+ break;
+ }
+}
+
+void VCUInverter::transmit_can(unsigned long currentMillis) {
+ // Send 10ms CAN Message
+ if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
+ previousMillis10ms = currentMillis;
+
+ mprun10 = (mprun10 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1...
+ LEAF_1DC.data.u8[6] = mprun10;
+ LEAF_1DC.data.u8[7] = calculate_CRC_Nissan(&LEAF_1DC);
+ transmit_can_frame(&LEAF_1DC);
+ LEAF_1DB.data.u8[6] = mprun10;
+ LEAF_1DB.data.u8[7] = calculate_CRC_Nissan(&LEAF_1DB);
+ transmit_can_frame(&LEAF_1DB);
+ }
+ // Send 100ms CAN Messages
+ if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
+ previousMillis100ms = currentMillis;
+
+ mprun100 = (mprun100 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1...
+ counter_55B = (counter_55B + 1) % 10;
+ if (counter_55B < 5) {
+ LEAF_55B.data.u8[2] = 0xAA;
+ } else {
+ LEAF_55B.data.u8[2] = 0x55;
+ }
+ LEAF_55B.data.u8[6] = (1 << 4) | mprun100; //1 = RefuseToSleep 2 = ReadyToSleep
+ LEAF_55B.data.u8[7] = calculate_CRC_Nissan(&LEAF_55B);
+ transmit_can_frame(&LEAF_55B);
+ transmit_can_frame(&LEAF_5BC);
+ }
+ // Send 100ms CAN Messages
+ if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
+ previousMillis500ms = currentMillis;
+
+ transmit_can_frame(&LEAF_59E);
+ if (LEAF_5C0.data.u8[0] == 0x40) {
+ LEAF_5C0.data.u8[0] = 0x80;
+ } else if (LEAF_5C0.data.u8[0] == 0x80) {
+ LEAF_5C0.data.u8[0] = 0xC0;
+ } else if (LEAF_5C0.data.u8[0] == 0xC0) {
+ LEAF_5C0.data.u8[0] = 0x40;
+ }
+ transmit_can_frame(&LEAF_5C0);
+ }
+}
diff --git a/Software/src/inverter/VCU-CAN.h b/Software/src/inverter/VCU-CAN.h
new file mode 100644
index 000000000..2940fa720
--- /dev/null
+++ b/Software/src/inverter/VCU-CAN.h
@@ -0,0 +1,54 @@
+#ifndef VCU_CAN_H
+#define VCU_CAN_H
+
+#include "CanInverterProtocol.h"
+
+class VCUInverter : public CanInverterProtocol {
+ public:
+ const char* name() override { return Name; }
+ void update_values();
+ void transmit_can(unsigned long currentMillis);
+ void map_can_frame_to_variable(CAN_frame rx_frame);
+ static constexpr const char* Name = "VCU mode: Nissan LEAF battery";
+
+ private:
+ unsigned long previousMillis10ms = 0;
+ unsigned long previousMillis100ms = 0;
+ unsigned long previousMillis500ms = 0;
+ uint16_t remining_gids = 281;
+ uint8_t mprun10 = 0;
+ uint8_t mprun100 = 0;
+ uint8_t counter_55B = 0;
+ CAN_frame LEAF_1DC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x1DC,
+ .data = {0x6E, 0x0C, 0x2F, 0xFD, 0x0C, 0x00, 0x00, 0xD8}};
+ CAN_frame LEAF_1DB = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x1DB,
+ .data = {0xFF, 0xC0, 0xB1, 0xAB, 0x08, 0x00, 0x02, 0xB5}};
+ CAN_frame LEAF_55B = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x55B,
+ .data = {0x27, 0x00, 0xAA, 0x00, 0xE1, 0x00, 0x10, 0x79}};
+ CAN_frame LEAF_5BC = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x5BC,
+ .data = {0x16, 0x00, 0x14, 0x50, 0xC8, 0x02, 0xA1, 0x68}};
+ CAN_frame LEAF_59E = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x59E,
+ .data = {0x00, 0x00, 0x0E, 0x60, 0x88, 0x00, 0x00, 0x00}};
+ CAN_frame LEAF_5C0 = {.FD = false,
+ .ext_ID = false,
+ .DLC = 8,
+ .ID = 0x5C0,
+ .data = {0x80, 0x60, 0x60, 0x00, 0xC7, 0xB4, 0x04, 0x0C}};
+};
+
+#endif
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.json b/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.json
index 4d13d28ef..407df1d94 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.json
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.json
@@ -1,6 +1,6 @@
{
"name": "ESPAsyncWebServer",
- "version": "3.8.0",
+ "version": "3.8.1",
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
"keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@@ -25,7 +25,7 @@
{
"owner": "ESP32Async",
"name": "AsyncTCP",
- "version": "^3.4.7",
+ "version": "^3.4.8",
"platforms": [
"espressif32",
"libretiny"
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.properties b/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.properties
index 774972b39..664aa70fa 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.properties
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/library.properties
@@ -1,6 +1,6 @@
name=ESP Async WebServer
includes=ESPAsyncWebServer.h
-version=3.8.0
+version=3.8.1
author=ESP32Async
maintainer=ESP32Async
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncEventSource.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncEventSource.cpp
deleted file mode 100644
index 2ebfa2d30..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncEventSource.cpp
+++ /dev/null
@@ -1,507 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#include "Arduino.h"
-#if defined(ESP32)
-#include
-#endif
-#include "AsyncEventSource.h"
-
-#define ASYNC_SSE_NEW_LINE_CHAR (char)0xa
-
-using namespace asyncsrv;
-
-static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
- String str;
- size_t len{0};
- if (message) {
- len += strlen(message);
- }
-
- if (event) {
- len += strlen(event);
- }
-
- len += 42; // give it some overhead
-
- if (!str.reserve(len)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- return emptyString;
- }
-
- if (reconnect) {
- str += T_retry_;
- str += reconnect;
- str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
- }
-
- if (id) {
- str += T_id__;
- str += id;
- str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
- }
-
- if (event != NULL) {
- str += T_event_;
- str += event;
- str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
- }
-
- if (!message) {
- return str;
- }
-
- size_t messageLen = strlen(message);
- char *lineStart = (char *)message;
- char *lineEnd;
- do {
- char *nextN = strchr(lineStart, '\n');
- char *nextR = strchr(lineStart, '\r');
- if (nextN == NULL && nextR == NULL) {
- // a message is a single-line string
- str += T_data_;
- str += message;
- str += T_nn;
- return str;
- }
-
- // a message is a multi-line string
- char *nextLine = NULL;
- if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
- if (nextR + 1 == nextN) {
- // normal \r\n sequence
- lineEnd = nextR;
- nextLine = nextN + 1;
- } else {
- // some abnormal \n \r mixed sequence
- lineEnd = std::min(nextR, nextN);
- nextLine = lineEnd + 1;
- }
- } else if (nextN != NULL) { // Unix/Mac OS X LF
- lineEnd = nextN;
- nextLine = nextN + 1;
- } else { // some ancient garbage
- lineEnd = nextR;
- nextLine = nextR + 1;
- }
-
- str += T_data_;
- str.concat(lineStart, lineEnd - lineStart);
- str += ASYNC_SSE_NEW_LINE_CHAR; // \n
-
- lineStart = nextLine;
- } while (lineStart < ((char *)message + messageLen));
-
- // append another \n to terminate message
- str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
-
- return str;
-}
-
-// Message
-
-size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t time) {
- // If the whole message is now acked...
- if (_acked + len > _data->length()) {
- // Return the number of extra bytes acked (they will be carried on to the next message)
- const size_t extra = _acked + len - _data->length();
- _acked = _data->length();
- return extra;
- }
- // Return that no extra bytes left.
- _acked += len;
- return 0;
-}
-
-size_t AsyncEventSourceMessage::write(AsyncClient *client) {
- if (!client) {
- return 0;
- }
-
- if (_sent >= _data->length() || !client->canSend()) {
- return 0;
- }
-
- size_t len = std::min(_data->length() - _sent, client->space());
- /*
- add() would call lwip's tcp_write() under the AsyncTCP hood with apiflags argument.
- By default apiflags=ASYNC_WRITE_FLAG_COPY
- we could have used apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
- but looks like it does not work for Arduino's lwip in ESP32/IDF
- it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
- if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
- https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
-
- So let's just keep it enforced ASYNC_WRITE_FLAG_COPY and keep in mind that there is no zero-copy
- */
- size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
- _sent += written;
- return written;
-}
-
-size_t AsyncEventSourceMessage::send(AsyncClient *client) {
- size_t sent = write(client);
- return sent && client->send() ? sent : 0;
-}
-
-// Client
-
-AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->client()), _server(server) {
-
- if (request->hasHeader(T_Last_Event_ID)) {
- _lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str());
- }
-
- _client->setRxTimeout(0);
- _client->onError(NULL, NULL);
- _client->onAck(
- [](void *r, AsyncClient *c, size_t len, uint32_t time) {
- (void)c;
- static_cast(r)->_onAck(len, time);
- },
- this
- );
- _client->onPoll(
- [](void *r, AsyncClient *c) {
- (void)c;
- static_cast(r)->_onPoll();
- },
- this
- );
- _client->onData(NULL, NULL);
- _client->onTimeout(
- [this](void *r, AsyncClient *c __attribute__((unused)), uint32_t time) {
- static_cast(r)->_onTimeout(time);
- },
- this
- );
- _client->onDisconnect(
- [this](void *r, AsyncClient *c) {
- static_cast(r)->_onDisconnect();
- delete c;
- },
- this
- );
-
- _server->_addClient(this);
- delete request;
-
- _client->setNoDelay(true);
-}
-
-AsyncEventSourceClient::~AsyncEventSourceClient() {
-#ifdef ESP32
- std::lock_guard lock(_lockmq);
-#endif
- _messageQueue.clear();
- close();
-}
-
-bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
- if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
-#ifdef ESP8266
- ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
-#elif defined(ESP32)
- log_e("Event message queue overflow: discard message");
-#endif
- return false;
- }
-
-#ifdef ESP32
- // length() is not thread-safe, thus acquiring the lock before this call..
- std::lock_guard lock(_lockmq);
-#endif
-
- _messageQueue.emplace_back(message, len);
-
- /*
- throttle queue run
- if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
- forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
- the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
- */
- if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
- _runQueue();
- }
-
- return true;
-}
-
-bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
- if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
-#ifdef ESP8266
- ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
-#elif defined(ESP32)
- log_e("Event message queue overflow: discard message");
-#endif
- return false;
- }
-
-#ifdef ESP32
- // length() is not thread-safe, thus acquiring the lock before this call..
- std::lock_guard lock(_lockmq);
-#endif
-
- _messageQueue.emplace_back(std::move(msg));
-
- /*
- throttle queue run
- if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
- forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
- the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
- */
- if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
- _runQueue();
- }
- return true;
-}
-
-void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
-#ifdef ESP32
- // Same here, acquiring the lock early
- std::lock_guard lock(_lockmq);
-#endif
-
- // adjust in-flight len
- if (len < _inflight) {
- _inflight -= len;
- } else {
- _inflight = 0;
- }
-
- // acknowledge as much messages's data as we got confirmed len from a AsyncTCP
- while (len && _messageQueue.size()) {
- len = _messageQueue.front().ack(len);
- if (_messageQueue.front().finished()) {
- // now we could release full ack'ed messages, we were keeping it unless send confirmed from AsyncTCP
- _messageQueue.pop_front();
- }
- }
-
- // try to send another batch of data
- if (_messageQueue.size()) {
- _runQueue();
- }
-}
-
-void AsyncEventSourceClient::_onPoll() {
- if (_messageQueue.size()) {
-#ifdef ESP32
- // Same here, acquiring the lock early
- std::lock_guard lock(_lockmq);
-#endif
- _runQueue();
- }
-}
-
-void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
- if (_client) {
- _client->close(true);
- }
-}
-
-void AsyncEventSourceClient::_onDisconnect() {
- if (!_client) {
- return;
- }
- _client = nullptr;
- _server->_handleDisconnect(this);
-}
-
-void AsyncEventSourceClient::close() {
- if (_client) {
- _client->close();
- }
-}
-
-bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
- if (!connected()) {
- return false;
- }
- return _queueMessage(std::make_shared(generateEventMessage(message, event, id, reconnect)));
-}
-
-void AsyncEventSourceClient::_runQueue() {
- if (!_client) {
- return;
- }
-
- // there is no need to lock the mutex here, 'cause all the calls to this method must be already lock'ed
- size_t total_bytes_written = 0;
- for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
- if (!i->sent()) {
- const size_t bytes_written = i->write(_client);
- total_bytes_written += bytes_written;
- _inflight += bytes_written;
- if (bytes_written == 0 || _inflight > _max_inflight) {
- // Serial.print("_");
- break;
- }
- }
- }
-
- // flush socket
- if (total_bytes_written) {
- _client->send();
- }
-}
-
-void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
- if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) {
- _max_inflight = value;
- }
-}
-
-/* AsyncEventSource */
-
-void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
- AsyncAuthorizationMiddleware *m = new AsyncAuthorizationMiddleware(401, cb);
- m->_freeOnRemoval = true;
- addMiddleware(m);
-}
-
-void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
- if (!client) {
- return;
- }
-#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
-#endif
- _clients.emplace_back(client);
- if (_connectcb) {
- _connectcb(client);
- }
-
- _adjust_inflight_window();
-}
-
-void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
- if (_disconnectcb) {
- _disconnectcb(client);
- }
-#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
-#endif
- for (auto i = _clients.begin(); i != _clients.end(); ++i) {
- if (i->get() == client) {
- _clients.erase(i);
- break;
- }
- }
- _adjust_inflight_window();
-}
-
-void AsyncEventSource::close() {
- // While the whole loop is not done, the linked list is locked and so the
- // iterator should remain valid even when AsyncEventSource::_handleDisconnect()
- // is called very early
-#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
-#endif
- for (const auto &c : _clients) {
- if (c->connected()) {
- /**
- * @brief: Fix self-deadlock by using recursive_mutex instead.
- * Due to c->close() shall call the callback function _onDisconnect()
- * The calling flow _onDisconnect() --> _handleDisconnect() --> deadlock
- */
- c->close();
- }
- }
-}
-
-// pmb fix
-size_t AsyncEventSource::avgPacketsWaiting() const {
- size_t aql = 0;
- uint32_t nConnectedClients = 0;
-#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
-#endif
- if (!_clients.size()) {
- return 0;
- }
-
- for (const auto &c : _clients) {
- if (c->connected()) {
- aql += c->packetsWaiting();
- ++nConnectedClients;
- }
- }
- return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
-}
-
-AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
- AsyncEvent_SharedData_t shared_msg = std::make_shared(generateEventMessage(message, event, id, reconnect));
-#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
-#endif
- size_t hits = 0;
- size_t miss = 0;
- for (const auto &c : _clients) {
- if (c->write(shared_msg)) {
- ++hits;
- } else {
- ++miss;
- }
- }
- return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
-}
-
-size_t AsyncEventSource::count() const {
-#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
-#endif
- size_t n_clients{0};
- for (const auto &i : _clients) {
- if (i->connected()) {
- ++n_clients;
- }
- }
-
- return n_clients;
-}
-
-bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) const {
- return request->isSSE() && request->url().equals(_url);
-}
-
-void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
- request->send(new AsyncEventSourceResponse(this));
-}
-
-void AsyncEventSource::_adjust_inflight_window() {
- if (_clients.size()) {
- size_t inflight = SSE_MAX_INFLIGH / _clients.size();
- for (const auto &c : _clients) {
- c->set_max_inflight_bytes(inflight);
- }
- // Serial.printf("adjusted inflight to: %u\n", inflight);
- }
-}
-
-/* Response */
-
-AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) {
- _server = server;
- _code = 200;
- _contentType = T_text_event_stream;
- _sendContentLength = false;
- addHeader(T_Cache_Control, T_no_cache);
- addHeader(T_Connection, T_keep_alive);
-}
-
-void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
- String out;
- _assembleHead(out, request->version());
- request->client()->write(out.c_str(), _headLength);
- _state = RESPONSE_WAIT_ACK;
-}
-
-size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) {
- if (len) {
- new AsyncEventSourceClient(request, _server);
- }
- return 0;
-}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncEventSource.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncEventSource.h
deleted file mode 100644
index abd0338cf..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncEventSource.h
+++ /dev/null
@@ -1,325 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#ifndef ASYNCEVENTSOURCE_H_
-#define ASYNCEVENTSOURCE_H_
-
-#include
-
-#if defined(ESP32) || defined(LIBRETINY)
-#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
-#ifdef LIBRETINY
-#ifdef round
-#undef round
-#endif
-#endif
-#include
-#ifndef SSE_MAX_QUEUED_MESSAGES
-#define SSE_MAX_QUEUED_MESSAGES 32
-#endif
-#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
-#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
-#elif defined(ESP8266)
-#include
-#ifndef SSE_MAX_QUEUED_MESSAGES
-#define SSE_MAX_QUEUED_MESSAGES 8
-#endif
-#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
-#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
-#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
-#include
-#ifndef SSE_MAX_QUEUED_MESSAGES
-#define SSE_MAX_QUEUED_MESSAGES 32
-#endif
-#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
-#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
-#endif
-
-#include "ESPAsyncWebServer.h"
-
-#ifdef ESP8266
-#include
-#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
-#include <../src/Hash.h>
-#endif
-#endif
-
-class AsyncEventSource;
-class AsyncEventSourceResponse;
-class AsyncEventSourceClient;
-using ArEventHandlerFunction = std::function;
-using ArAuthorizeConnectHandler = ArAuthorizeFunction;
-// shared message object container
-using AsyncEvent_SharedData_t = std::shared_ptr;
-
-/**
- * @brief Async Event Message container with shared message content data
- *
- */
-class AsyncEventSourceMessage {
-
-private:
- const AsyncEvent_SharedData_t _data;
- size_t _sent{0}; // num of bytes already sent
- size_t _acked{0}; // num of bytes acked
-
-public:
- AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
-#if defined(ESP32)
- AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared(data, len)){};
-#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
- AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared()) {
- if (data && len > 0) {
- _data->concat(data, len);
- }
- };
-#else
- // esp8266's String does not have constructor with data/length arguments. Use a concat method here
- AsyncEventSourceMessage(const char *data, size_t len) {
- _data->concat(data, len);
- };
-#endif
-
- /**
- * @brief acknowledge sending len bytes of data
- * @note if num of bytes to ack is larger then the unacknowledged message length the number of carried over bytes are returned
- *
- * @param len bytes to acknowledge
- * @param time
- * @return size_t number of extra bytes carried over
- */
- size_t ack(size_t len, uint32_t time = 0);
-
- /**
- * @brief write message data to client's buffer
- * @note this method does NOT call client's send
- *
- * @param client
- * @return size_t number of bytes written
- */
- size_t write(AsyncClient *client);
-
- /**
- * @brief writes message data to client's buffer and calls client's send method
- *
- * @param client
- * @return size_t returns num of bytes the clien was able to send()
- */
- size_t send(AsyncClient *client);
-
- // returns true if full message's length were acked
- bool finished() {
- return _acked == _data->length();
- }
-
- /**
- * @brief returns true if all data has been sent already
- *
- */
- bool sent() {
- return _sent == _data->length();
- }
-};
-
-/**
- * @brief class holds a sse messages queue for a particular client's connection
- *
- */
-class AsyncEventSourceClient {
-private:
- AsyncClient *_client;
- AsyncEventSource *_server;
- uint32_t _lastId{0};
- size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
- size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
- std::list _messageQueue;
-#ifdef ESP32
- mutable std::recursive_mutex _lockmq;
-#endif
- bool _queueMessage(const char *message, size_t len);
- bool _queueMessage(AsyncEvent_SharedData_t &&msg);
- void _runQueue();
-
-public:
- AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
- ~AsyncEventSourceClient();
-
- /**
- * @brief Send an SSE message to client
- * it will craft an SSE message and place it to client's message queue
- *
- * @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
- * @param event body string, a sinle line string
- * @param id sequence id
- * @param reconnect client's reconnect timeout
- * @return true if message was placed in a queue
- * @return false if queue is full
- */
- bool send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
- bool send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
- return send(message.c_str(), event.c_str(), id, reconnect);
- }
- bool send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
- return send(message.c_str(), event, id, reconnect);
- }
-
- /**
- * @brief place supplied preformatted SSE message to the message queue
- * @note message must a properly formatted SSE string according to https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
- *
- * @param message data
- * @return true on success
- * @return false on queue overflow or no client connected
- */
- bool write(AsyncEvent_SharedData_t message) {
- return connected() && _queueMessage(std::move(message));
- };
-
- [[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
- bool write(const char *message, size_t len) {
- return connected() && _queueMessage(message, len);
- };
-
- // close client's connection
- void close();
-
- // getters
-
- AsyncClient *client() {
- return _client;
- }
- bool connected() const {
- return _client && _client->connected();
- }
- uint32_t lastId() const {
- return _lastId;
- }
- size_t packetsWaiting() const {
- return _messageQueue.size();
- };
-
- /**
- * @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
- * used to throttle message delivery length to tradeoff memory consumption
- * @note actual amount of data written could possible be a bit larger but no more than available socket buff space
- *
- * @param value
- */
- void set_max_inflight_bytes(size_t value);
-
- /**
- * @brief Get current max inflight bytes value
- *
- * @return size_t
- */
- size_t get_max_inflight_bytes() const {
- return _max_inflight;
- }
-
- // system callbacks (do not call if from user code!)
- void _onAck(size_t len, uint32_t time);
- void _onPoll();
- void _onTimeout(uint32_t time);
- void _onDisconnect();
-};
-
-/**
- * @brief a class that maintains all connected HTTP clients subscribed to SSE delivery
- * dispatches supplied messages to the client's queues
- *
- */
-class AsyncEventSource : public AsyncWebHandler {
-private:
- String _url;
- std::list> _clients;
-#ifdef ESP32
- // Same as for individual messages, protect mutations of _clients list
- // since simultaneous access from different tasks is possible
- mutable std::recursive_mutex _client_queue_lock;
-#endif
- ArEventHandlerFunction _connectcb = nullptr;
- ArEventHandlerFunction _disconnectcb = nullptr;
-
- // this method manipulates in-fligh data size for connected client depending on number of active connections
- void _adjust_inflight_window();
-
-public:
- typedef enum {
- DISCARDED = 0,
- ENQUEUED = 1,
- PARTIALLY_ENQUEUED = 2,
- } SendStatus;
-
- AsyncEventSource(const char *url) : _url(url){};
- AsyncEventSource(const String &url) : _url(url){};
- ~AsyncEventSource() {
- close();
- };
-
- const char *url() const {
- return _url.c_str();
- }
- // close all connected clients
- void close();
-
- /**
- * @brief set on-connect callback for the client
- * used to deliver messages to client on first connect
- *
- * @param cb
- */
- void onConnect(ArEventHandlerFunction cb) {
- _connectcb = cb;
- }
-
- /**
- * @brief Send an SSE message to client
- * it will craft an SSE message and place it to all connected client's message queues
- *
- * @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
- * @param event body string, a sinle line string
- * @param id sequence id
- * @param reconnect client's reconnect timeout
- * @return SendStatus if message was placed in any/all/part of the client's queues
- */
- SendStatus send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
- SendStatus send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
- return send(message.c_str(), event.c_str(), id, reconnect);
- }
- SendStatus send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
- return send(message.c_str(), event, id, reconnect);
- }
-
- // The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
- void onDisconnect(ArEventHandlerFunction cb) {
- _disconnectcb = cb;
- }
- void authorizeConnect(ArAuthorizeConnectHandler cb);
-
- // returns number of connected clients
- size_t count() const;
-
- // returns average number of messages pending in all client's queues
- size_t avgPacketsWaiting() const;
-
- // system callbacks (do not call from user code!)
- void _addClient(AsyncEventSourceClient *client);
- void _handleDisconnect(AsyncEventSourceClient *client);
- bool canHandle(AsyncWebServerRequest *request) const override final;
- void handleRequest(AsyncWebServerRequest *request) override final;
-};
-
-class AsyncEventSourceResponse : public AsyncWebServerResponse {
-private:
- AsyncEventSource *_server;
-
-public:
- AsyncEventSourceResponse(AsyncEventSource *server);
- void _respond(AsyncWebServerRequest *request);
- size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
- bool _sourceValid() const {
- return true;
- }
-};
-
-#endif /* ASYNCEVENTSOURCE_H_ */
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncJson.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncJson.cpp
deleted file mode 100644
index 8381f75ed..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncJson.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#include "AsyncJson.h"
-
-#if ASYNC_JSON_SUPPORT == 1
-
-#if ARDUINOJSON_VERSION_MAJOR == 5
-AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
- _code = 200;
- _contentType = asyncsrv::T_application_json;
- if (isArray) {
- _root = _jsonBuffer.createArray();
- } else {
- _root = _jsonBuffer.createObject();
- }
-}
-#elif ARDUINOJSON_VERSION_MAJOR == 6
-AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
- _code = 200;
- _contentType = asyncsrv::T_application_json;
- if (isArray) {
- _root = _jsonBuffer.createNestedArray();
- } else {
- _root = _jsonBuffer.createNestedObject();
- }
-}
-#else
-AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
- _code = 200;
- _contentType = asyncsrv::T_application_json;
- if (isArray) {
- _root = _jsonBuffer.add();
- } else {
- _root = _jsonBuffer.add();
- }
-}
-#endif
-
-size_t AsyncJsonResponse::setLength() {
-#if ARDUINOJSON_VERSION_MAJOR == 5
- _contentLength = _root.measureLength();
-#else
- _contentLength = measureJson(_root);
-#endif
- if (_contentLength) {
- _isValid = true;
- }
- return _contentLength;
-}
-
-size_t AsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
- ChunkPrint dest(data, _sentLength, len);
-#if ARDUINOJSON_VERSION_MAJOR == 5
- _root.printTo(dest);
-#else
- serializeJson(_root, dest);
-#endif
- return len;
-}
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
-PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
-#else
-PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
-#endif
-
-size_t PrettyAsyncJsonResponse::setLength() {
-#if ARDUINOJSON_VERSION_MAJOR == 5
- _contentLength = _root.measurePrettyLength();
-#else
- _contentLength = measureJsonPretty(_root);
-#endif
- if (_contentLength) {
- _isValid = true;
- }
- return _contentLength;
-}
-
-size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
- ChunkPrint dest(data, _sentLength, len);
-#if ARDUINOJSON_VERSION_MAJOR == 5
- _root.prettyPrintTo(dest);
-#else
- serializeJsonPretty(_root, dest);
-#endif
- return len;
-}
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
-AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
- : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
-#else
-AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
- : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
-#endif
-
-bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
- if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
- return false;
- }
-
- if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
- return false;
- }
-
- if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) {
- return false;
- }
-
- return true;
-}
-
-void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
- if (_onRequest) {
- // GET request:
- if (request->method() == HTTP_GET) {
- JsonVariant json;
- _onRequest(request, json);
- return;
- }
-
- // POST / PUT / ... requests:
- // check if JSON body is too large, if it is, don't deserialize
- if (request->contentLength() > _maxContentLength) {
-#ifdef ESP32
- log_e("Content length exceeds maximum allowed");
-#endif
- request->send(413);
- return;
- }
-
- if (request->_tempObject == NULL) {
- // there is no body
- request->send(400);
- return;
- }
-
-#if ARDUINOJSON_VERSION_MAJOR == 5
- DynamicJsonBuffer jsonBuffer;
- JsonVariant json = jsonBuffer.parse((const char *)request->_tempObject);
- if (json.success()) {
-#elif ARDUINOJSON_VERSION_MAJOR == 6
- DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
- DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
- if (!error) {
- JsonVariant json = jsonBuffer.as();
-#else
- JsonDocument jsonBuffer;
- DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
- if (!error) {
- JsonVariant json = jsonBuffer.as();
-#endif
-
- _onRequest(request, json);
- } else {
- // error parsing the body
- request->send(400);
- }
- }
-}
-
-void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
- if (_onRequest) {
- // ignore callback if size is larger than maxContentLength
- if (total > _maxContentLength) {
- return;
- }
-
- if (index == 0) {
- // this check allows request->_tempObject to be initialized from a middleware
- if (request->_tempObject == NULL) {
- request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // null-terminated string
- if (request->_tempObject == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- request->abort();
- return;
- }
- }
- }
-
- if (request->_tempObject != NULL) {
- uint8_t *buffer = (uint8_t *)request->_tempObject;
- memcpy(buffer + index, data, len);
- }
- }
-}
-
-#endif // ASYNC_JSON_SUPPORT
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncJson.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncJson.h
deleted file mode 100644
index 2194069d1..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncJson.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#ifndef ASYNC_JSON_H_
-#define ASYNC_JSON_H_
-
-#if __has_include("ArduinoJson.h")
-#include
-#if ARDUINOJSON_VERSION_MAJOR >= 5
-#define ASYNC_JSON_SUPPORT 1
-#else
-#define ASYNC_JSON_SUPPORT 0
-#endif // ARDUINOJSON_VERSION_MAJOR >= 5
-#endif // __has_include("ArduinoJson.h")
-
-#if ASYNC_JSON_SUPPORT == 1
-#include
-
-#include "ChunkPrint.h"
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
-#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
-#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
-#endif
-#endif
-
-class AsyncJsonResponse : public AsyncAbstractResponse {
-protected:
-#if ARDUINOJSON_VERSION_MAJOR == 5
- DynamicJsonBuffer _jsonBuffer;
-#elif ARDUINOJSON_VERSION_MAJOR == 6
- DynamicJsonDocument _jsonBuffer;
-#else
- JsonDocument _jsonBuffer;
-#endif
-
- JsonVariant _root;
- bool _isValid;
-
-public:
-#if ARDUINOJSON_VERSION_MAJOR == 6
- AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
-#else
- AsyncJsonResponse(bool isArray = false);
-#endif
- JsonVariant &getRoot() {
- return _root;
- }
- bool _sourceValid() const {
- return _isValid;
- }
- size_t setLength();
- size_t getSize() const {
- return _jsonBuffer.size();
- }
- size_t _fillBuffer(uint8_t *data, size_t len);
-#if ARDUINOJSON_VERSION_MAJOR >= 6
- bool overflowed() const {
- return _jsonBuffer.overflowed();
- }
-#endif
-};
-
-class PrettyAsyncJsonResponse : public AsyncJsonResponse {
-public:
-#if ARDUINOJSON_VERSION_MAJOR == 6
- PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
-#else
- PrettyAsyncJsonResponse(bool isArray = false);
-#endif
- size_t setLength();
- size_t _fillBuffer(uint8_t *data, size_t len);
-};
-
-typedef std::function ArJsonRequestHandlerFunction;
-
-class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
-protected:
- String _uri;
- WebRequestMethodComposite _method;
- ArJsonRequestHandlerFunction _onRequest;
-#if ARDUINOJSON_VERSION_MAJOR == 6
- size_t maxJsonBufferSize;
-#endif
- size_t _maxContentLength;
-
-public:
-#if ARDUINOJSON_VERSION_MAJOR == 6
- AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
-#else
- AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
-#endif
-
- void setMethod(WebRequestMethodComposite method) {
- _method = method;
- }
- void setMaxContentLength(int maxContentLength) {
- _maxContentLength = maxContentLength;
- }
- void onRequest(ArJsonRequestHandlerFunction fn) {
- _onRequest = fn;
- }
-
- bool canHandle(AsyncWebServerRequest *request) const override final;
- void handleRequest(AsyncWebServerRequest *request) override final;
- void handleUpload(
- __unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
- __unused bool final
- ) override final {}
- void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
- bool isRequestHandlerTrivial() const override final {
- return !_onRequest;
- }
-};
-
-#endif // ASYNC_JSON_SUPPORT == 1
-
-#endif // ASYNC_JSON_H_
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncMessagePack.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncMessagePack.cpp
deleted file mode 100644
index 0c6faa107..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncMessagePack.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#include "AsyncMessagePack.h"
-
-#if ASYNC_MSG_PACK_SUPPORT == 1
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
-AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
- _code = 200;
- _contentType = asyncsrv::T_application_msgpack;
- if (isArray) {
- _root = _jsonBuffer.createNestedArray();
- } else {
- _root = _jsonBuffer.createNestedObject();
- }
-}
-#else
-AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
- _code = 200;
- _contentType = asyncsrv::T_application_msgpack;
- if (isArray) {
- _root = _jsonBuffer.add();
- } else {
- _root = _jsonBuffer.add();
- }
-}
-#endif
-
-size_t AsyncMessagePackResponse::setLength() {
- _contentLength = measureMsgPack(_root);
- if (_contentLength) {
- _isValid = true;
- }
- return _contentLength;
-}
-
-size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
- ChunkPrint dest(data, _sentLength, len);
- serializeMsgPack(_root, dest);
- return len;
-}
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
-AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
- const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
-)
- : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
-#else
-AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
- : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
-#endif
-
-bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
- if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
- return false;
- }
-
- if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
- return false;
- }
-
- if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) {
- return false;
- }
-
- return true;
-}
-
-void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest *request) {
- if (_onRequest) {
- if (request->method() == HTTP_GET) {
- JsonVariant json;
- _onRequest(request, json);
- return;
- } else if (request->_tempObject != NULL) {
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
- DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
- DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
- if (!error) {
- JsonVariant json = jsonBuffer.as();
-#else
- JsonDocument jsonBuffer;
- DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
- if (!error) {
- JsonVariant json = jsonBuffer.as();
-#endif
-
- _onRequest(request, json);
- return;
- }
- }
- request->send(_contentLength > _maxContentLength ? 413 : 400);
- } else {
- request->send(500);
- }
-}
-
-void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
- if (_onRequest) {
- _contentLength = total;
- if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
- request->_tempObject = malloc(total);
- if (request->_tempObject == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- request->abort();
- return;
- }
- }
- if (request->_tempObject != NULL) {
- memcpy((uint8_t *)(request->_tempObject) + index, data, len);
- }
- }
-}
-
-#endif // ASYNC_MSG_PACK_SUPPORT
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncMessagePack.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncMessagePack.h
deleted file mode 100644
index 7488b5cba..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncMessagePack.h
+++ /dev/null
@@ -1,126 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#pragma once
-
-/*
- server.on("/msg_pack", HTTP_ANY, [](AsyncWebServerRequest * request) {
- AsyncMessagePackResponse * response = new AsyncMessagePackResponse();
- JsonObject& root = response->getRoot();
- root["key1"] = "key number one";
- JsonObject& nested = root.createNestedObject("nested");
- nested["key1"] = "key number one";
- response->setLength();
- request->send(response);
- });
-
- --------------------
-
- AsyncCallbackMessagePackWebHandler* handler = new AsyncCallbackMessagePackWebHandler("/msg_pack/endpoint");
- handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
- JsonObject jsonObj = json.as();
- // ...
- });
- server.addHandler(handler);
-*/
-
-#if __has_include("ArduinoJson.h")
-#include
-#if ARDUINOJSON_VERSION_MAJOR >= 6
-#define ASYNC_MSG_PACK_SUPPORT 1
-#else
-#define ASYNC_MSG_PACK_SUPPORT 0
-#endif // ARDUINOJSON_VERSION_MAJOR >= 6
-#endif // __has_include("ArduinoJson.h")
-
-#if ASYNC_MSG_PACK_SUPPORT == 1
-#include
-
-#include "ChunkPrint.h"
-
-#if ARDUINOJSON_VERSION_MAJOR == 6
-#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
-#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
-#endif
-#endif
-
-class AsyncMessagePackResponse : public AsyncAbstractResponse {
-protected:
-#if ARDUINOJSON_VERSION_MAJOR == 6
- DynamicJsonDocument _jsonBuffer;
-#else
- JsonDocument _jsonBuffer;
-#endif
-
- JsonVariant _root;
- bool _isValid;
-
-public:
-#if ARDUINOJSON_VERSION_MAJOR == 6
- AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
-#else
- AsyncMessagePackResponse(bool isArray = false);
-#endif
- JsonVariant &getRoot() {
- return _root;
- }
- bool _sourceValid() const {
- return _isValid;
- }
- size_t setLength();
- size_t getSize() const {
- return _jsonBuffer.size();
- }
- size_t _fillBuffer(uint8_t *data, size_t len);
-#if ARDUINOJSON_VERSION_MAJOR >= 6
- bool overflowed() const {
- return _jsonBuffer.overflowed();
- }
-#endif
-};
-
-typedef std::function ArMessagePackRequestHandlerFunction;
-
-class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
-protected:
- String _uri;
- WebRequestMethodComposite _method;
- ArMessagePackRequestHandlerFunction _onRequest;
- size_t _contentLength;
-#if ARDUINOJSON_VERSION_MAJOR == 6
- size_t maxJsonBufferSize;
-#endif
- size_t _maxContentLength;
-
-public:
-#if ARDUINOJSON_VERSION_MAJOR == 6
- AsyncCallbackMessagePackWebHandler(
- const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
- );
-#else
- AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
-#endif
-
- void setMethod(WebRequestMethodComposite method) {
- _method = method;
- }
- void setMaxContentLength(int maxContentLength) {
- _maxContentLength = maxContentLength;
- }
- void onRequest(ArMessagePackRequestHandlerFunction fn) {
- _onRequest = fn;
- }
-
- bool canHandle(AsyncWebServerRequest *request) const override final;
- void handleRequest(AsyncWebServerRequest *request) override final;
- void handleUpload(
- __unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
- __unused bool final
- ) override final {}
- void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
- bool isRequestHandlerTrivial() const override final {
- return !_onRequest;
- }
-};
-
-#endif // ASYNC_MSG_PACK_SUPPORT == 1
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerRequest.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerRequest.cpp
index b15de6094..6190ac92e 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerRequest.cpp
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerRequest.cpp
@@ -29,20 +29,16 @@ void AsyncWebServerRequest::send(FS &fs, const String &path, const char *content
const String gzPath = path + asyncsrv::T__gz;
File gzFile = fs.open(gzPath, fs::FileOpenMode::read);
- // Compressed file not found or invalid
- if (!gzFile.seek(gzFile.size() - 8)) {
- send(404);
- gzFile.close();
- return;
- }
-
// ETag validation
if (this->hasHeader(asyncsrv::T_INM)) {
// Generate server ETag from CRC in gzip trailer
- uint8_t crcInTrailer[4];
- gzFile.read(crcInTrailer, 4);
char serverETag[9];
- _getEtag(crcInTrailer, serverETag);
+ if (!_getEtag(gzFile, serverETag)) {
+ // Compressed file not found or invalid
+ send(404);
+ gzFile.close();
+ return;
+ }
// Compare with client's ETag
const AsyncWebHeader *inmHeader = this->getHeader(asyncsrv::T_INM);
@@ -59,27 +55,36 @@ void AsyncWebServerRequest::send(FS &fs, const String &path, const char *content
}
/**
- * @brief Generates an ETag string from a 4-byte trailer
+ * @brief Generates an ETag string from the CRC32 trailer of a GZIP file.
+ *
+ * This function reads the CRC32 checksum (4 bytes) located at the end of a GZIP-compressed file
+ * and converts it into an 8-character hexadecimal ETag string (null-terminated).
*
- * This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes.
+ * @param gzFile Opened file handle pointing to the GZIP file.
+ * @param eTag Output buffer to store the generated ETag.
+ * Must be pre-allocated with at least 9 bytes (8 for hex digits + 1 for null terminator).
*
- * @param trailer[4] Input array of 4 bytes to convert to hexadecimal
- * @param serverETag Output buffer to store the ETag
- * Must be pre-allocated with minimum 9 bytes (8 hex + 1 null terminator)
+ * @return true if the ETag was successfully generated, false otherwise (e.g., file too short or seek failed).
*/
-void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char *serverETag) {
+bool AsyncWebServerRequest::_getEtag(File gzFile, char *etag) {
static constexpr char hexChars[] = "0123456789ABCDEF";
- uint32_t data;
- memcpy(&data, trailer, 4);
+ if (!gzFile.seek(gzFile.size() - 8)) {
+ return false;
+ }
+
+ uint32_t crc;
+ gzFile.read(reinterpret_cast(&crc), sizeof(crc));
+
+ etag[0] = hexChars[(crc >> 4) & 0x0F];
+ etag[1] = hexChars[crc & 0x0F];
+ etag[2] = hexChars[(crc >> 12) & 0x0F];
+ etag[3] = hexChars[(crc >> 8) & 0x0F];
+ etag[4] = hexChars[(crc >> 20) & 0x0F];
+ etag[5] = hexChars[(crc >> 16) & 0x0F];
+ etag[6] = hexChars[(crc >> 28)];
+ etag[7] = hexChars[(crc >> 24) & 0x0F];
+ etag[8] = '\0';
- serverETag[0] = hexChars[(data >> 4) & 0x0F];
- serverETag[1] = hexChars[data & 0x0F];
- serverETag[2] = hexChars[(data >> 12) & 0x0F];
- serverETag[3] = hexChars[(data >> 8) & 0x0F];
- serverETag[4] = hexChars[(data >> 20) & 0x0F];
- serverETag[5] = hexChars[(data >> 16) & 0x0F];
- serverETag[6] = hexChars[(data >> 28)];
- serverETag[7] = hexChars[(data >> 24) & 0x0F];
- serverETag[8] = '\0';
+ return true;
}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerVersion.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerVersion.h
index 3a7a34ce3..75928f16e 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerVersion.h
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebServerVersion.h
@@ -12,7 +12,7 @@ extern "C" {
/** Minor version number (x.X.x) */
#define ASYNCWEBSERVER_VERSION_MINOR 8
/** Patch version number (x.x.X) */
-#define ASYNCWEBSERVER_VERSION_PATCH 0
+#define ASYNCWEBSERVER_VERSION_PATCH 1
/**
* Macro to convert version number into an integer
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebSocket.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebSocket.cpp
deleted file mode 100644
index 0cd17493b..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebSocket.cpp
+++ /dev/null
@@ -1,1321 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#include "AsyncWebSocket.h"
-#include "Arduino.h"
-
-#include
-
-#include
-
-#if defined(ESP32)
-#if ESP_IDF_VERSION_MAJOR < 5
-#include "BackPort_SHA1Builder.h"
-#else
-#include
-#endif
-#include
-#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
-#include
-#elif defined(LIBRETINY)
-#include
-#endif
-
-using namespace asyncsrv;
-
-size_t webSocketSendFrameWindow(AsyncClient *client) {
- if (!client || !client->canSend()) {
- return 0;
- }
- size_t space = client->space();
- if (space < 9) {
- return 0;
- }
- return space - 8;
-}
-
-size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len) {
- if (!client || !client->canSend()) {
- // Serial.println("SF 1");
- return 0;
- }
- size_t space = client->space();
- if (space < 2) {
- // Serial.println("SF 2");
- return 0;
- }
- uint8_t mbuf[4] = {0, 0, 0, 0};
- uint8_t headLen = 2;
- if (len && mask) {
- headLen += 4;
- mbuf[0] = rand() % 0xFF;
- mbuf[1] = rand() % 0xFF;
- mbuf[2] = rand() % 0xFF;
- mbuf[3] = rand() % 0xFF;
- }
- if (len > 125) {
- headLen += 2;
- }
- if (space < headLen) {
- // Serial.println("SF 2");
- return 0;
- }
- space -= headLen;
-
- if (len > space) {
- len = space;
- }
-
- uint8_t *buf = (uint8_t *)malloc(headLen);
- if (buf == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- client->abort();
- return 0;
- }
-
- buf[0] = opcode & 0x0F;
- if (final) {
- buf[0] |= 0x80;
- }
- if (len < 126) {
- buf[1] = len & 0x7F;
- } else {
- buf[1] = 126;
- buf[2] = (uint8_t)((len >> 8) & 0xFF);
- buf[3] = (uint8_t)(len & 0xFF);
- }
- if (len && mask) {
- buf[1] |= 0x80;
- memcpy(buf + (headLen - 4), mbuf, 4);
- }
- if (client->add((const char *)buf, headLen) != headLen) {
- // os_printf("error adding %lu header bytes\n", headLen);
- free(buf);
- // Serial.println("SF 4");
- return 0;
- }
- free(buf);
-
- if (len) {
- if (len && mask) {
- size_t i;
- for (i = 0; i < len; i++) {
- data[i] = data[i] ^ mbuf[i % 4];
- }
- }
- if (client->add((const char *)data, len) != len) {
- // os_printf("error adding %lu data bytes\n", len);
- // Serial.println("SF 5");
- return 0;
- }
- }
- if (!client->send()) {
- // os_printf("error sending frame: %lu\n", headLen+len);
- // Serial.println("SF 6");
- return 0;
- }
- // Serial.println("SF");
- return len;
-}
-
-size_t AsyncWebSocketControl::send(AsyncClient *client) {
- _finished = true;
- return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
-}
-
-/*
- * AsyncWebSocketMessageBuffer
- */
-
-AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size) : _buffer(std::make_shared>(size)) {
- if (_buffer->capacity() < size) {
- _buffer->reserve(size);
- } else {
- std::memcpy(_buffer->data(), data, size);
- }
-}
-
-AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) : _buffer(std::make_shared>(size)) {
- if (_buffer->capacity() < size) {
- _buffer->reserve(size);
- }
-}
-
-bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
- if (_buffer->capacity() >= size) {
- return true;
- }
- _buffer->reserve(size);
- return _buffer->capacity() >= size;
-}
-
-/*
- * AsyncWebSocketMessage Message
- */
-
-AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask)
- : _WSbuffer{buffer}, _opcode(opcode & 0x07), _mask{mask}, _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {}
-
-void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
- (void)time;
- _acked += len;
- if (_sent >= _WSbuffer->size() && _acked >= _ack) {
- _status = WS_MSG_SENT;
- }
- // ets_printf("A: %u\n", len);
-}
-
-size_t AsyncWebSocketMessage::send(AsyncClient *client) {
- if (!client) {
- return 0;
- }
-
- if (_status != WS_MSG_SENDING) {
- return 0;
- }
- if (_acked < _ack) {
- return 0;
- }
- if (_sent == _WSbuffer->size()) {
- if (_acked == _ack) {
- _status = WS_MSG_SENT;
- }
- return 0;
- }
- if (_sent > _WSbuffer->size()) {
- _status = WS_MSG_ERROR;
- // ets_printf("E: %u > %u\n", _sent, _WSbuffer->length());
- return 0;
- }
-
- size_t toSend = _WSbuffer->size() - _sent;
- size_t window = webSocketSendFrameWindow(client);
-
- if (window < toSend) {
- toSend = window;
- }
-
- _sent += toSend;
- _ack += toSend + ((toSend < 126) ? 2 : 4) + (_mask * 4);
-
- // ets_printf("W: %u %u\n", _sent - toSend, toSend);
-
- bool final = (_sent == _WSbuffer->size());
- uint8_t *dPtr = (uint8_t *)(_WSbuffer->data() + (_sent - toSend));
- uint8_t opCode = (toSend && _sent == toSend) ? _opcode : (uint8_t)WS_CONTINUATION;
-
- size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
- _status = WS_MSG_SENDING;
- if (toSend && sent != toSend) {
- // ets_printf("E: %u != %u\n", toSend, sent);
- _sent -= (toSend - sent);
- _ack -= (toSend - sent);
- }
- // ets_printf("S: %u %u\n", _sent, sent);
- return sent;
-}
-
-/*
- * Async WebSocket Client
- */
-const char *AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
-const size_t AWSC_PING_PAYLOAD_LEN = 22;
-
-AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _tempObject(NULL) {
- _client = request->client();
- _server = server;
- _clientId = _server->_getNextId();
- _status = WS_CONNECTED;
- _pstate = 0;
- _lastMessageTime = millis();
- _keepAlivePeriod = 0;
- _client->setRxTimeout(0);
- _client->onError(
- [](void *r, AsyncClient *c, int8_t error) {
- (void)c;
- ((AsyncWebSocketClient *)(r))->_onError(error);
- },
- this
- );
- _client->onAck(
- [](void *r, AsyncClient *c, size_t len, uint32_t time) {
- (void)c;
- ((AsyncWebSocketClient *)(r))->_onAck(len, time);
- },
- this
- );
- _client->onDisconnect(
- [](void *r, AsyncClient *c) {
- ((AsyncWebSocketClient *)(r))->_onDisconnect();
- delete c;
- },
- this
- );
- _client->onTimeout(
- [](void *r, AsyncClient *c, uint32_t time) {
- (void)c;
- ((AsyncWebSocketClient *)(r))->_onTimeout(time);
- },
- this
- );
- _client->onData(
- [](void *r, AsyncClient *c, void *buf, size_t len) {
- (void)c;
- ((AsyncWebSocketClient *)(r))->_onData(buf, len);
- },
- this
- );
- _client->onPoll(
- [](void *r, AsyncClient *c) {
- (void)c;
- ((AsyncWebSocketClient *)(r))->_onPoll();
- },
- this
- );
- delete request;
- memset(&_pinfo, 0, sizeof(_pinfo));
-}
-
-AsyncWebSocketClient::~AsyncWebSocketClient() {
- {
-#ifdef ESP32
- std::lock_guard lock(_lock);
-#endif
- _messageQueue.clear();
- _controlQueue.clear();
- }
- _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
-}
-
-void AsyncWebSocketClient::_clearQueue() {
- while (!_messageQueue.empty() && _messageQueue.front().finished()) {
- _messageQueue.pop_front();
- }
-}
-
-void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
- _lastMessageTime = millis();
-
-#ifdef ESP32
- std::unique_lock lock(_lock);
-#endif
-
- if (!_controlQueue.empty()) {
- auto &head = _controlQueue.front();
- if (head.finished()) {
- len -= head.len();
- if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
- _controlQueue.pop_front();
- _status = WS_DISCONNECTED;
- if (_client) {
-#ifdef ESP32
- /*
- Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
- Due to _client->close(true) shall call the callback function _onDisconnect()
- The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
- */
- lock.unlock();
-#endif
- _client->close(true);
- }
- return;
- }
- _controlQueue.pop_front();
- }
- }
-
- if (len && !_messageQueue.empty()) {
- _messageQueue.front().ack(len, time);
- }
-
- _clearQueue();
-
- _runQueue();
-}
-
-void AsyncWebSocketClient::_onPoll() {
- if (!_client) {
- return;
- }
-
-#ifdef ESP32
- std::unique_lock lock(_lock);
-#endif
- if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
- _runQueue();
- } else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) {
-#ifdef ESP32
- lock.unlock();
-#endif
- ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
- }
-}
-
-void AsyncWebSocketClient::_runQueue() {
- // all calls to this method MUST be protected by a mutex lock!
- if (!_client) {
- return;
- }
-
- _clearQueue();
-
- if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames())
- && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
- _controlQueue.front().send(_client);
- } else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) {
- _messageQueue.front().send(_client);
- }
-}
-
-bool AsyncWebSocketClient::queueIsFull() const {
-#ifdef ESP32
- std::lock_guard lock(_lock);
-#endif
- return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
-}
-
-size_t AsyncWebSocketClient::queueLen() const {
-#ifdef ESP32
- std::lock_guard lock(_lock);
-#endif
- return _messageQueue.size();
-}
-
-bool AsyncWebSocketClient::canSend() const {
-#ifdef ESP32
- std::lock_guard lock(_lock);
-#endif
- return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
-}
-
-bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) {
- if (!_client) {
- return false;
- }
-
-#ifdef ESP32
- std::lock_guard lock(_lock);
-#endif
-
- _controlQueue.emplace_back(opcode, data, len, mask);
-
- if (_client && _client->canSend()) {
- _runQueue();
- }
-
- return true;
-}
-
-bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) {
- if (!_client || buffer->size() == 0 || _status != WS_CONNECTED) {
- return false;
- }
-
-#ifdef ESP32
- std::unique_lock lock(_lock);
-#endif
-
- if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
- if (closeWhenFull) {
- _status = WS_DISCONNECTED;
-
- if (_client) {
-#ifdef ESP32
- /*
- Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
- Due to _client->close(true) shall call the callback function _onDisconnect()
- The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
- */
- lock.unlock();
-#endif
- _client->close(true);
- }
-
-#ifdef ESP8266
- ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
-#elif defined(ESP32)
- log_e("Too many messages queued: closing connection");
-#endif
-
- } else {
-#ifdef ESP8266
- ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n");
-#elif defined(ESP32)
- log_e("Too many messages queued: discarding new message");
-#endif
- }
-
- return false;
- }
-
- _messageQueue.emplace_back(buffer, opcode, mask);
-
- if (_client && _client->canSend()) {
- _runQueue();
- }
-
- return true;
-}
-
-void AsyncWebSocketClient::close(uint16_t code, const char *message) {
- if (_status != WS_CONNECTED) {
- return;
- }
-
- _status = WS_DISCONNECTING;
-
- if (code) {
- uint8_t packetLen = 2;
- if (message != NULL) {
- size_t mlen = strlen(message);
- if (mlen > 123) {
- mlen = 123;
- }
- packetLen += mlen;
- }
- char *buf = (char *)malloc(packetLen);
- if (buf != NULL) {
- buf[0] = (uint8_t)(code >> 8);
- buf[1] = (uint8_t)(code & 0xFF);
- if (message != NULL) {
- memcpy(buf + 2, message, packetLen - 2);
- }
- _queueControl(WS_DISCONNECT, (uint8_t *)buf, packetLen);
- free(buf);
- return;
- } else {
-#ifdef ESP32
- log_e("Failed to allocate");
- _client->abort();
-#endif
- }
- }
- _queueControl(WS_DISCONNECT);
-}
-
-bool AsyncWebSocketClient::ping(const uint8_t *data, size_t len) {
- return _status == WS_CONNECTED && _queueControl(WS_PING, data, len);
-}
-
-void AsyncWebSocketClient::_onError(int8_t) {
- // Serial.println("onErr");
-}
-
-void AsyncWebSocketClient::_onTimeout(uint32_t time) {
- if (!_client) {
- return;
- }
- // Serial.println("onTime");
- (void)time;
- _client->close(true);
-}
-
-void AsyncWebSocketClient::_onDisconnect() {
- // Serial.println("onDis");
- _client = nullptr;
- _server->_handleDisconnect(this);
-}
-
-void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
- _lastMessageTime = millis();
- uint8_t *data = (uint8_t *)pbuf;
- while (plen > 0) {
- if (!_pstate) {
- const uint8_t *fdata = data;
-
- _pinfo.index = 0;
- _pinfo.final = (fdata[0] & 0x80) != 0;
- _pinfo.opcode = fdata[0] & 0x0F;
- _pinfo.masked = (fdata[1] & 0x80) != 0;
- _pinfo.len = fdata[1] & 0x7F;
-
- // log_d("WS[%" PRIu32 "]: _onData: %" PRIu32, _clientId, plen);
- // log_d("WS[%" PRIu32 "]: _status = %" PRIu32, _clientId, _status);
- // log_d("WS[%" PRIu32 "]: _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index, _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len);
-
- data += 2;
- plen -= 2;
-
- if (_pinfo.len == 126 && plen >= 2) {
- _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
- data += 2;
- plen -= 2;
-
- } else if (_pinfo.len == 127 && plen >= 8) {
- _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32
- | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
- data += 8;
- plen -= 8;
- }
-
- if (_pinfo.masked
- && plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
- memcpy(_pinfo.mask, data, 4);
- data += 4;
- plen -= 4;
- }
- }
-
- const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);
- const auto datalast = data[datalen];
-
- if (_pinfo.masked) {
- for (size_t i = 0; i < datalen; i++) {
- data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4];
- }
- }
-
- if ((datalen + _pinfo.index) < _pinfo.len) {
- _pstate = 1;
-
- if (_pinfo.index == 0) {
- if (_pinfo.opcode) {
- _pinfo.message_opcode = _pinfo.opcode;
- _pinfo.num = 0;
- }
- }
- if (datalen > 0) {
- _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
- }
-
- _pinfo.index += datalen;
- } else if ((datalen + _pinfo.index) == _pinfo.len) {
- _pstate = 0;
- if (_pinfo.opcode == WS_DISCONNECT) {
- if (datalen) {
- uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1];
- char *reasonString = (char *)(data + 2);
- if (reasonCode > 1001) {
- _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t *)reasonString, strlen(reasonString));
- }
- }
- if (_status == WS_DISCONNECTING) {
- _status = WS_DISCONNECTED;
- if (_client) {
- _client->close(true);
- }
- } else {
- _status = WS_DISCONNECTING;
- if (_client) {
- _client->ackLater();
- }
- _queueControl(WS_DISCONNECT, data, datalen);
- }
- } else if (_pinfo.opcode == WS_PING) {
- _server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0);
- _queueControl(WS_PONG, data, datalen);
- } else if (_pinfo.opcode == WS_PONG) {
- if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) {
- _server->_handleEvent(this, WS_EVT_PONG, NULL, NULL, 0);
- }
- } else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
- _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
- if (_pinfo.final) {
- _pinfo.num = 0;
- } else {
- _pinfo.num += 1;
- }
- }
- } else {
- // os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
- // what should we do?
- break;
- }
-
- // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
- if (datalen) {
- data[datalen] = datalast;
- }
-
- data += datalen;
- plen -= datalen;
- }
-}
-
-size_t AsyncWebSocketClient::printf(const char *format, ...) {
- va_list arg;
- va_start(arg, format);
- size_t len = vsnprintf(nullptr, 0, format, arg);
- va_end(arg);
-
- if (len == 0) {
- return 0;
- }
-
- char *buffer = new char[len + 1];
-
- if (!buffer) {
- return 0;
- }
-
- va_start(arg, format);
- len = vsnprintf(buffer, len + 1, format, arg);
- va_end(arg);
-
- bool enqueued = text(buffer, len);
- delete[] buffer;
- return enqueued ? len : 0;
-}
-
-#ifdef ESP8266
-size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
- va_list arg;
- va_start(arg, formatP);
- size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
- va_end(arg);
-
- if (len == 0) {
- return 0;
- }
-
- char *buffer = new char[len + 1];
-
- if (!buffer) {
- return 0;
- }
-
- va_start(arg, formatP);
- len = vsnprintf_P(buffer, len + 1, formatP, arg);
- va_end(arg);
-
- bool enqueued = text(buffer, len);
- delete[] buffer;
- return enqueued ? len : 0;
-}
-#endif
-
-namespace {
-AsyncWebSocketSharedBuffer makeSharedBuffer(const uint8_t *message, size_t len) {
- auto buffer = std::make_shared>(len);
- std::memcpy(buffer->data(), message, len);
- return buffer;
-}
-} // namespace
-
-bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer *buffer) {
- bool enqueued = false;
- if (buffer) {
- enqueued = text(std::move(buffer->_buffer));
- delete buffer;
- }
- return enqueued;
-}
-
-bool AsyncWebSocketClient::text(AsyncWebSocketSharedBuffer buffer) {
- return _queueMessage(buffer);
-}
-
-bool AsyncWebSocketClient::text(const uint8_t *message, size_t len) {
- return text(makeSharedBuffer(message, len));
-}
-
-bool AsyncWebSocketClient::text(const char *message, size_t len) {
- return text((const uint8_t *)message, len);
-}
-
-bool AsyncWebSocketClient::text(const char *message) {
- return text(message, strlen(message));
-}
-
-bool AsyncWebSocketClient::text(const String &message) {
- return text(message.c_str(), message.length());
-}
-
-#ifdef ESP8266
-bool AsyncWebSocketClient::text(const __FlashStringHelper *data) {
- PGM_P p = reinterpret_cast(data);
-
- size_t n = 0;
- while (1) {
- if (pgm_read_byte(p + n) == 0) {
- break;
- }
- n += 1;
- }
-
- char *message = (char *)malloc(n + 1);
- bool enqueued = false;
- if (message) {
- memcpy_P(message, p, n);
- message[n] = 0;
- enqueued = text(message, n);
- free(message);
- }
- return enqueued;
-}
-#endif // ESP8266
-
-bool AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer *buffer) {
- bool enqueued = false;
- if (buffer) {
- enqueued = binary(std::move(buffer->_buffer));
- delete buffer;
- }
- return enqueued;
-}
-
-bool AsyncWebSocketClient::binary(AsyncWebSocketSharedBuffer buffer) {
- return _queueMessage(buffer, WS_BINARY);
-}
-
-bool AsyncWebSocketClient::binary(const uint8_t *message, size_t len) {
- return binary(makeSharedBuffer(message, len));
-}
-
-bool AsyncWebSocketClient::binary(const char *message, size_t len) {
- return binary((const uint8_t *)message, len);
-}
-
-bool AsyncWebSocketClient::binary(const char *message) {
- return binary(message, strlen(message));
-}
-
-bool AsyncWebSocketClient::binary(const String &message) {
- return binary(message.c_str(), message.length());
-}
-
-#ifdef ESP8266
-bool AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) {
- PGM_P p = reinterpret_cast(data);
- char *message = (char *)malloc(len);
- bool enqueued = false;
- if (message) {
- memcpy_P(message, p, len);
- enqueued = binary(message, len);
- free(message);
- }
- return enqueued;
-}
-#endif
-
-IPAddress AsyncWebSocketClient::remoteIP() const {
- if (!_client) {
- return IPAddress((uint32_t)0U);
- }
-
- return _client->remoteIP();
-}
-
-uint16_t AsyncWebSocketClient::remotePort() const {
- if (!_client) {
- return 0;
- }
-
- return _client->remotePort();
-}
-
-/*
- * Async Web Socket - Each separate socket location
- */
-
-void AsyncWebSocket::_handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
- if (_eventHandler != NULL) {
- _eventHandler(this, client, type, arg, data, len);
- }
-}
-
-AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) {
- _clients.emplace_back(request, this);
- _handleEvent(&_clients.back(), WS_EVT_CONNECT, request, NULL, 0);
- return &_clients.back();
-}
-
-void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) {
- const auto client_id = client->id();
- const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [client_id](const AsyncWebSocketClient &c) {
- return c.id() == client_id;
- });
- if (iter != std::end(_clients)) {
- _clients.erase(iter);
- }
-}
-
-bool AsyncWebSocket::availableForWriteAll() {
- return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
- return c.queueIsFull();
- });
-}
-
-bool AsyncWebSocket::availableForWrite(uint32_t id) {
- const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient &c) {
- return c.id() == id;
- });
- if (iter == std::end(_clients)) {
- return true;
- }
- return !iter->queueIsFull();
-}
-
-size_t AsyncWebSocket::count() const {
- return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
- return c.status() == WS_CONNECTED;
- });
-}
-
-AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) {
- const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient &c) {
- return c.id() == id && c.status() == WS_CONNECTED;
- });
- if (iter == std::end(_clients)) {
- return nullptr;
- }
-
- return &(*iter);
-}
-
-void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) {
- if (AsyncWebSocketClient *c = client(id)) {
- c->close(code, message);
- }
-}
-
-void AsyncWebSocket::closeAll(uint16_t code, const char *message) {
- for (auto &c : _clients) {
- if (c.status() == WS_CONNECTED) {
- c.close(code, message);
- }
- }
-}
-
-void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
- if (count() > maxClients) {
- _clients.front().close();
- }
-
- for (auto i = _clients.begin(); i != _clients.end(); ++i) {
- if (i->shouldBeDeleted()) {
- _clients.erase(i);
- break;
- }
- }
-}
-
-bool AsyncWebSocket::ping(uint32_t id, const uint8_t *data, size_t len) {
- AsyncWebSocketClient *c = client(id);
- return c && c->ping(data, len);
-}
-
-AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) {
- size_t hit = 0;
- size_t miss = 0;
- for (auto &c : _clients) {
- if (c.status() == WS_CONNECTED && c.ping(data, len)) {
- hit++;
- } else {
- miss++;
- }
- }
- return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
-}
-
-bool AsyncWebSocket::text(uint32_t id, const uint8_t *message, size_t len) {
- AsyncWebSocketClient *c = client(id);
- return c && c->text(makeSharedBuffer(message, len));
-}
-bool AsyncWebSocket::text(uint32_t id, const char *message, size_t len) {
- return text(id, (const uint8_t *)message, len);
-}
-bool AsyncWebSocket::text(uint32_t id, const char *message) {
- return text(id, message, strlen(message));
-}
-bool AsyncWebSocket::text(uint32_t id, const String &message) {
- return text(id, message.c_str(), message.length());
-}
-
-#ifdef ESP8266
-bool AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *data) {
- PGM_P p = reinterpret_cast(data);
-
- size_t n = 0;
- while (true) {
- if (pgm_read_byte(p + n) == 0) {
- break;
- }
- n += 1;
- }
-
- char *message = (char *)malloc(n + 1);
- bool enqueued = false;
- if (message) {
- memcpy_P(message, p, n);
- message[n] = 0;
- enqueued = text(id, message, n);
- free(message);
- }
- return enqueued;
-}
-#endif // ESP8266
-
-bool AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer *buffer) {
- bool enqueued = false;
- if (buffer) {
- enqueued = text(id, std::move(buffer->_buffer));
- delete buffer;
- }
- return enqueued;
-}
-bool AsyncWebSocket::text(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
- AsyncWebSocketClient *c = client(id);
- return c && c->text(buffer);
-}
-
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const uint8_t *message, size_t len) {
- return textAll(makeSharedBuffer(message, len));
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const char *message, size_t len) {
- return textAll((const uint8_t *)message, len);
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const char *message) {
- return textAll(message, strlen(message));
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const String &message) {
- return textAll(message.c_str(), message.length());
-}
-#ifdef ESP8266
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const __FlashStringHelper *data) {
- PGM_P p = reinterpret_cast(data);
-
- size_t n = 0;
- while (1) {
- if (pgm_read_byte(p + n) == 0) {
- break;
- }
- n += 1;
- }
-
- char *message = (char *)malloc(n + 1);
- AsyncWebSocket::SendStatus status = DISCARDED;
- if (message) {
- memcpy_P(message, p, n);
- message[n] = 0;
- status = textAll(message, n);
- free(message);
- }
- return status;
-}
-#endif // ESP8266
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer *buffer) {
- AsyncWebSocket::SendStatus status = DISCARDED;
- if (buffer) {
- status = textAll(std::move(buffer->_buffer));
- delete buffer;
- }
- return status;
-}
-
-AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) {
- size_t hit = 0;
- size_t miss = 0;
- for (auto &c : _clients) {
- if (c.status() == WS_CONNECTED && c.text(buffer)) {
- hit++;
- } else {
- miss++;
- }
- }
- return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
-}
-
-bool AsyncWebSocket::binary(uint32_t id, const uint8_t *message, size_t len) {
- AsyncWebSocketClient *c = client(id);
- return c && c->binary(makeSharedBuffer(message, len));
-}
-bool AsyncWebSocket::binary(uint32_t id, const char *message, size_t len) {
- return binary(id, (const uint8_t *)message, len);
-}
-bool AsyncWebSocket::binary(uint32_t id, const char *message) {
- return binary(id, message, strlen(message));
-}
-bool AsyncWebSocket::binary(uint32_t id, const String &message) {
- return binary(id, message.c_str(), message.length());
-}
-
-#ifdef ESP8266
-bool AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *data, size_t len) {
- PGM_P p = reinterpret_cast(data);
- char *message = (char *)malloc(len);
- bool enqueued = false;
- if (message) {
- memcpy_P(message, p, len);
- enqueued = binary(id, message, len);
- free(message);
- }
- return enqueued;
-}
-#endif // ESP8266
-
-bool AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer) {
- bool enqueued = false;
- if (buffer) {
- enqueued = binary(id, std::move(buffer->_buffer));
- delete buffer;
- }
- return enqueued;
-}
-bool AsyncWebSocket::binary(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
- AsyncWebSocketClient *c = client(id);
- return c && c->binary(buffer);
-}
-
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const uint8_t *message, size_t len) {
- return binaryAll(makeSharedBuffer(message, len));
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const char *message, size_t len) {
- return binaryAll((const uint8_t *)message, len);
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const char *message) {
- return binaryAll(message, strlen(message));
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const String &message) {
- return binaryAll(message.c_str(), message.length());
-}
-
-#ifdef ESP8266
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const __FlashStringHelper *data, size_t len) {
- PGM_P p = reinterpret_cast(data);
- char *message = (char *)malloc(len);
- AsyncWebSocket::SendStatus status = DISCARDED;
- if (message) {
- memcpy_P(message, p, len);
- status = binaryAll(message, len);
- free(message);
- }
- return status;
-}
-#endif // ESP8266
-
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer *buffer) {
- AsyncWebSocket::SendStatus status = DISCARDED;
- if (buffer) {
- status = binaryAll(std::move(buffer->_buffer));
- delete buffer;
- }
- return status;
-}
-AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) {
- size_t hit = 0;
- size_t miss = 0;
- for (auto &c : _clients) {
- if (c.status() == WS_CONNECTED && c.binary(buffer)) {
- hit++;
- } else {
- miss++;
- }
- }
- return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
-}
-
-size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...) {
- AsyncWebSocketClient *c = client(id);
- if (c) {
- va_list arg;
- va_start(arg, format);
- size_t len = c->printf(format, arg);
- va_end(arg);
- return len;
- }
- return 0;
-}
-
-size_t AsyncWebSocket::printfAll(const char *format, ...) {
- va_list arg;
- va_start(arg, format);
- size_t len = vsnprintf(nullptr, 0, format, arg);
- va_end(arg);
-
- if (len == 0) {
- return 0;
- }
-
- char *buffer = new char[len + 1];
-
- if (!buffer) {
- return 0;
- }
-
- va_start(arg, format);
- len = vsnprintf(buffer, len + 1, format, arg);
- va_end(arg);
-
- AsyncWebSocket::SendStatus status = textAll(buffer, len);
- delete[] buffer;
- return status == DISCARDED ? 0 : len;
-}
-
-#ifdef ESP8266
-size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...) {
- AsyncWebSocketClient *c = client(id);
- if (c != NULL) {
- va_list arg;
- va_start(arg, formatP);
- size_t len = c->printf_P(formatP, arg);
- va_end(arg);
- return len;
- }
- return 0;
-}
-
-size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
- va_list arg;
- va_start(arg, formatP);
- size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
- va_end(arg);
-
- if (len == 0) {
- return 0;
- }
-
- char *buffer = new char[len + 1];
-
- if (!buffer) {
- return 0;
- }
-
- va_start(arg, formatP);
- len = vsnprintf_P(buffer, len + 1, formatP, arg);
- va_end(arg);
-
- AsyncWebSocket::SendStatus status = textAll(buffer, len);
- delete[] buffer;
- return status == DISCARDED ? 0 : len;
-}
-#endif
-
-const char __WS_STR_CONNECTION[] PROGMEM = {"Connection"};
-const char __WS_STR_UPGRADE[] PROGMEM = {"Upgrade"};
-const char __WS_STR_ORIGIN[] PROGMEM = {"Origin"};
-const char __WS_STR_COOKIE[] PROGMEM = {"Cookie"};
-const char __WS_STR_VERSION[] PROGMEM = {"Sec-WebSocket-Version"};
-const char __WS_STR_KEY[] PROGMEM = {"Sec-WebSocket-Key"};
-const char __WS_STR_PROTOCOL[] PROGMEM = {"Sec-WebSocket-Protocol"};
-const char __WS_STR_ACCEPT[] PROGMEM = {"Sec-WebSocket-Accept"};
-const char __WS_STR_UUID[] PROGMEM = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"};
-
-#define WS_STR_UUID_LEN 36
-
-#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION)
-#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE)
-#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN)
-#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE)
-#define WS_STR_VERSION FPSTR(__WS_STR_VERSION)
-#define WS_STR_KEY FPSTR(__WS_STR_KEY)
-#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL)
-#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT)
-#define WS_STR_UUID FPSTR(__WS_STR_UUID)
-
-bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request) const {
- return _enabled && request->isWebSocketUpgrade() && request->url().equals(_url);
-}
-
-void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request) {
- if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) {
- request->send(400);
- return;
- }
- if (_handshakeHandler != nullptr) {
- if (!_handshakeHandler(request)) {
- request->send(401);
- return;
- }
- }
- const AsyncWebHeader *version = request->getHeader(WS_STR_VERSION);
- if (version->value().toInt() != 13) {
- AsyncWebServerResponse *response = request->beginResponse(400);
- response->addHeader(WS_STR_VERSION, T_13);
- request->send(response);
- return;
- }
- const AsyncWebHeader *key = request->getHeader(WS_STR_KEY);
- AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
- if (response == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- request->abort();
- return;
- }
- if (request->hasHeader(WS_STR_PROTOCOL)) {
- const AsyncWebHeader *protocol = request->getHeader(WS_STR_PROTOCOL);
- // ToDo: check protocol
- response->addHeader(WS_STR_PROTOCOL, protocol->value());
- }
- request->send(response);
-}
-
-AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(size_t size) {
- return new AsyncWebSocketMessageBuffer(size);
-}
-
-AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(const uint8_t *data, size_t size) {
- return new AsyncWebSocketMessageBuffer(data, size);
-}
-
-/*
- * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server
- * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480
- */
-
-AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket *server) {
- _server = server;
- _code = 101;
- _sendContentLength = false;
-
- uint8_t hash[20];
- char buffer[33];
-
-#if defined(ESP8266) || defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(TARGET_RP2350)
- sha1(key + WS_STR_UUID, hash);
-#else
- String k;
- if (!k.reserve(key.length() + WS_STR_UUID_LEN)) {
- log_e("Failed to allocate");
- return;
- }
- k.concat(key);
- k.concat(WS_STR_UUID);
-#ifdef LIBRETINY
- mbedtls_sha1_context ctx;
- mbedtls_sha1_init(&ctx);
- mbedtls_sha1_starts(&ctx);
- mbedtls_sha1_update(&ctx, (const uint8_t *)k.c_str(), k.length());
- mbedtls_sha1_finish(&ctx, hash);
- mbedtls_sha1_free(&ctx);
-#else
- SHA1Builder sha1;
- sha1.begin();
- sha1.add((const uint8_t *)k.c_str(), k.length());
- sha1.calculate();
- sha1.getBytes(hash);
-#endif
-#endif
- base64_encodestate _state;
- base64_init_encodestate(&_state);
- int len = base64_encode_block((const char *)hash, 20, buffer, &_state);
- len = base64_encode_blockend((buffer + len), &_state);
- addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE);
- addHeader(WS_STR_UPGRADE, T_WS);
- addHeader(WS_STR_ACCEPT, buffer);
-}
-
-void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request) {
- if (_state == RESPONSE_FAILED) {
- request->client()->close(true);
- return;
- }
- String out;
- _assembleHead(out, request->version());
- request->client()->write(out.c_str(), _headLength);
- _state = RESPONSE_WAIT_ACK;
-}
-
-size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
- (void)time;
-
- if (len) {
- _server->_newClient(request);
- }
-
- return 0;
-}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebSocket.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebSocket.h
deleted file mode 100644
index 0c00b4020..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/AsyncWebSocket.h
+++ /dev/null
@@ -1,560 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#ifndef ASYNCWEBSOCKET_H_
-#define ASYNCWEBSOCKET_H_
-
-#include
-
-#if defined(ESP32) || defined(LIBRETINY)
-#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
-#ifdef LIBRETINY
-#ifdef round
-#undef round
-#endif
-#endif
-#include
-#ifndef WS_MAX_QUEUED_MESSAGES
-#define WS_MAX_QUEUED_MESSAGES 32
-#endif
-#elif defined(ESP8266)
-#include
-#ifndef WS_MAX_QUEUED_MESSAGES
-#define WS_MAX_QUEUED_MESSAGES 8
-#endif
-#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
-#include
-#ifndef WS_MAX_QUEUED_MESSAGES
-#define WS_MAX_QUEUED_MESSAGES 32
-#endif
-#endif
-
-#include "ESPAsyncWebServer.h"
-
-#include
-
-#ifdef ESP8266
-#include
-#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
-#include <../src/Hash.h>
-#endif
-#endif
-
-#ifndef DEFAULT_MAX_WS_CLIENTS
-#ifdef ESP32
-#define DEFAULT_MAX_WS_CLIENTS 8
-#else
-#define DEFAULT_MAX_WS_CLIENTS 4
-#endif
-#endif
-
-using AsyncWebSocketSharedBuffer = std::shared_ptr>;
-
-class AsyncWebSocket;
-class AsyncWebSocketResponse;
-class AsyncWebSocketClient;
-
-/*
- * Control Frame
- */
-
-class AsyncWebSocketControl {
-private:
- uint8_t _opcode;
- uint8_t *_data;
- size_t _len;
- bool _mask;
- bool _finished;
-
-public:
- AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false)
- : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
- if (data == NULL) {
- _len = 0;
- }
- if (_len) {
- if (_len > 125) {
- _len = 125;
- }
-
- _data = (uint8_t *)malloc(_len);
-
- if (_data == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- _len = 0;
- } else {
- memcpy(_data, data, len);
- }
- } else {
- _data = NULL;
- }
- }
-
- ~AsyncWebSocketControl() {
- if (_data != NULL) {
- free(_data);
- }
- }
-
- bool finished() const {
- return _finished;
- }
- uint8_t opcode() {
- return _opcode;
- }
- uint8_t len() {
- return _len + 2;
- }
- size_t send(AsyncClient *client);
-};
-
-typedef struct {
- /** Message type as defined by enum AwsFrameType.
- * Note: Applications will only see WS_TEXT and WS_BINARY.
- * All other types are handled by the library. */
- uint8_t message_opcode;
- /** Frame number of a fragmented message. */
- uint32_t num;
- /** Is this the last frame in a fragmented message ?*/
- uint8_t final;
- /** Is this frame masked? */
- uint8_t masked;
- /** Message type as defined by enum AwsFrameType.
- * This value is the same as message_opcode for non-fragmented
- * messages, but may also be WS_CONTINUATION in a fragmented message. */
- uint8_t opcode;
- /** Length of the current frame.
- * This equals the total length of the message if num == 0 && final == true */
- uint64_t len;
- /** Mask key */
- uint8_t mask[4];
- /** Offset of the data inside the current frame. */
- uint64_t index;
-} AwsFrameInfo;
-
-typedef enum {
- WS_DISCONNECTED,
- WS_CONNECTED,
- WS_DISCONNECTING
-} AwsClientStatus;
-typedef enum {
- WS_CONTINUATION,
- WS_TEXT,
- WS_BINARY,
- WS_DISCONNECT = 0x08,
- WS_PING,
- WS_PONG
-} AwsFrameType;
-typedef enum {
- WS_MSG_SENDING,
- WS_MSG_SENT,
- WS_MSG_ERROR
-} AwsMessageStatus;
-typedef enum {
- WS_EVT_CONNECT,
- WS_EVT_DISCONNECT,
- WS_EVT_PING,
- WS_EVT_PONG,
- WS_EVT_ERROR,
- WS_EVT_DATA
-} AwsEventType;
-
-class AsyncWebSocketMessageBuffer {
- friend AsyncWebSocket;
- friend AsyncWebSocketClient;
-
-private:
- AsyncWebSocketSharedBuffer _buffer;
-
-public:
- AsyncWebSocketMessageBuffer() {}
- explicit AsyncWebSocketMessageBuffer(size_t size);
- AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
- //~AsyncWebSocketMessageBuffer();
- bool reserve(size_t size);
- uint8_t *get() {
- return _buffer->data();
- }
- size_t length() const {
- return _buffer->size();
- }
-};
-
-class AsyncWebSocketMessage {
-private:
- AsyncWebSocketSharedBuffer _WSbuffer;
- uint8_t _opcode{WS_TEXT};
- bool _mask{false};
- AwsMessageStatus _status{WS_MSG_ERROR};
- size_t _sent{};
- size_t _ack{};
- size_t _acked{};
-
-public:
- AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
-
- bool finished() const {
- return _status != WS_MSG_SENDING;
- }
- bool betweenFrames() const {
- return _acked == _ack;
- }
-
- void ack(size_t len, uint32_t time);
- size_t send(AsyncClient *client);
-};
-
-class AsyncWebSocketClient {
-private:
- AsyncClient *_client;
- AsyncWebSocket *_server;
- uint32_t _clientId;
- AwsClientStatus _status;
-#ifdef ESP32
- mutable std::recursive_mutex _lock;
-#endif
- std::deque _controlQueue;
- std::deque _messageQueue;
- bool closeWhenFull = true;
-
- uint8_t _pstate;
- AwsFrameInfo _pinfo;
-
- uint32_t _lastMessageTime;
- uint32_t _keepAlivePeriod;
-
- bool _queueControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false);
- bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
- void _runQueue();
- void _clearQueue();
-
-public:
- void *_tempObject;
-
- AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
- ~AsyncWebSocketClient();
-
- // client id increments for the given server
- uint32_t id() const {
- return _clientId;
- }
- AwsClientStatus status() const {
- return _status;
- }
- AsyncClient *client() {
- return _client;
- }
- const AsyncClient *client() const {
- return _client;
- }
- AsyncWebSocket *server() {
- return _server;
- }
- const AsyncWebSocket *server() const {
- return _server;
- }
- AwsFrameInfo const &pinfo() const {
- return _pinfo;
- }
-
- // - If "true" (default), the connection will be closed if the message queue is full.
- // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
- // The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
- // and so on, causing a resource exhaustion.
- //
- // - If "false", the incoming message will be discarded if the queue is full.
- // This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
- // This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
- //
- // - In any case, when the queue is full, a message is logged.
- // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
- //
- // Usage:
- // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
- //
- // Use cases:,
- // - if using websocket to send logging messages, maybe some loss is acceptable.
- // - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
- void setCloseClientOnQueueFull(bool close) {
- closeWhenFull = close;
- }
- bool willCloseClientOnQueueFull() const {
- return closeWhenFull;
- }
-
- IPAddress remoteIP() const;
- uint16_t remotePort() const;
-
- bool shouldBeDeleted() const {
- return !_client;
- }
-
- // control frames
- void close(uint16_t code = 0, const char *message = NULL);
- bool ping(const uint8_t *data = NULL, size_t len = 0);
-
- // set auto-ping period in seconds. disabled if zero (default)
- void keepAlivePeriod(uint16_t seconds) {
- _keepAlivePeriod = seconds * 1000;
- }
- uint16_t keepAlivePeriod() {
- return (uint16_t)(_keepAlivePeriod / 1000);
- }
-
- // data packets
- void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
- _queueMessage(buffer, opcode, mask);
- }
- bool queueIsFull() const;
- size_t queueLen() const;
-
- size_t printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
-
- bool text(AsyncWebSocketSharedBuffer buffer);
- bool text(const uint8_t *message, size_t len);
- bool text(const char *message, size_t len);
- bool text(const char *message);
- bool text(const String &message);
- bool text(AsyncWebSocketMessageBuffer *buffer);
-
- bool binary(AsyncWebSocketSharedBuffer buffer);
- bool binary(const uint8_t *message, size_t len);
- bool binary(const char *message, size_t len);
- bool binary(const char *message);
- bool binary(const String &message);
- bool binary(AsyncWebSocketMessageBuffer *buffer);
-
- bool canSend() const;
-
- // system callbacks (do not call)
- void _onAck(size_t len, uint32_t time);
- void _onError(int8_t);
- void _onPoll();
- void _onTimeout(uint32_t time);
- void _onDisconnect();
- void _onData(void *pbuf, size_t plen);
-
-#ifdef ESP8266
- size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
- bool text(const __FlashStringHelper *message);
- bool binary(const __FlashStringHelper *message, size_t len);
-#endif
-};
-
-using AwsHandshakeHandler = std::function;
-using AwsEventHandler = std::function;
-
-// WebServer Handler implementation that plays the role of a socket server
-class AsyncWebSocket : public AsyncWebHandler {
-private:
- String _url;
- std::list _clients;
- uint32_t _cNextId;
- AwsEventHandler _eventHandler;
- AwsHandshakeHandler _handshakeHandler;
- bool _enabled;
-#ifdef ESP32
- mutable std::mutex _lock;
-#endif
-
-public:
- typedef enum {
- DISCARDED = 0,
- ENQUEUED = 1,
- PARTIALLY_ENQUEUED = 2,
- } SendStatus;
-
- explicit AsyncWebSocket(const char *url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
- AsyncWebSocket(const String &url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
- ~AsyncWebSocket(){};
- const char *url() const {
- return _url.c_str();
- }
- void enable(bool e) {
- _enabled = e;
- }
- bool enabled() const {
- return _enabled;
- }
- bool availableForWriteAll();
- bool availableForWrite(uint32_t id);
-
- size_t count() const;
- AsyncWebSocketClient *client(uint32_t id);
- bool hasClient(uint32_t id) {
- return client(id) != nullptr;
- }
-
- void close(uint32_t id, uint16_t code = 0, const char *message = NULL);
- void closeAll(uint16_t code = 0, const char *message = NULL);
- void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
-
- bool ping(uint32_t id, const uint8_t *data = NULL, size_t len = 0);
- SendStatus pingAll(const uint8_t *data = NULL, size_t len = 0); // done
-
- bool text(uint32_t id, const uint8_t *message, size_t len);
- bool text(uint32_t id, const char *message, size_t len);
- bool text(uint32_t id, const char *message);
- bool text(uint32_t id, const String &message);
- bool text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
- bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
-
- SendStatus textAll(const uint8_t *message, size_t len);
- SendStatus textAll(const char *message, size_t len);
- SendStatus textAll(const char *message);
- SendStatus textAll(const String &message);
- SendStatus textAll(AsyncWebSocketMessageBuffer *buffer);
- SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
-
- bool binary(uint32_t id, const uint8_t *message, size_t len);
- bool binary(uint32_t id, const char *message, size_t len);
- bool binary(uint32_t id, const char *message);
- bool binary(uint32_t id, const String &message);
- bool binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
- bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
-
- SendStatus binaryAll(const uint8_t *message, size_t len);
- SendStatus binaryAll(const char *message, size_t len);
- SendStatus binaryAll(const char *message);
- SendStatus binaryAll(const String &message);
- SendStatus binaryAll(AsyncWebSocketMessageBuffer *buffer);
- SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
-
- size_t printf(uint32_t id, const char *format, ...) __attribute__((format(printf, 3, 4)));
- size_t printfAll(const char *format, ...) __attribute__((format(printf, 2, 3)));
-
-#ifdef ESP8266
- bool text(uint32_t id, const __FlashStringHelper *message);
- SendStatus textAll(const __FlashStringHelper *message);
- bool binary(uint32_t id, const __FlashStringHelper *message, size_t len);
- SendStatus binaryAll(const __FlashStringHelper *message, size_t len);
- size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
- size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
-#endif
-
- void onEvent(AwsEventHandler handler) {
- _eventHandler = handler;
- }
- void handleHandshake(AwsHandshakeHandler handler) {
- _handshakeHandler = handler;
- }
-
- // system callbacks (do not call)
- uint32_t _getNextId() {
- return _cNextId++;
- }
- AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
- void _handleDisconnect(AsyncWebSocketClient *client);
- void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
- bool canHandle(AsyncWebServerRequest *request) const override final;
- void handleRequest(AsyncWebServerRequest *request) override final;
-
- // messagebuffer functions/objects.
- AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
- AsyncWebSocketMessageBuffer *makeBuffer(const uint8_t *data, size_t size);
-
- std::list &getClients() {
- return _clients;
- }
-};
-
-// WebServer response to authenticate the socket and detach the tcp client from the web server request
-class AsyncWebSocketResponse : public AsyncWebServerResponse {
-private:
- String _content;
- AsyncWebSocket *_server;
-
-public:
- AsyncWebSocketResponse(const String &key, AsyncWebSocket *server);
- void _respond(AsyncWebServerRequest *request);
- size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
- bool _sourceValid() const {
- return true;
- }
-};
-
-class AsyncWebSocketMessageHandler {
-public:
- AwsEventHandler eventHandler() const {
- return _handler;
- }
-
- void onConnect(std::function onConnect) {
- _onConnect = onConnect;
- }
-
- void onDisconnect(std::function onDisconnect) {
- _onDisconnect = onDisconnect;
- }
-
- /**
- * Error callback
- * @param reason null-terminated string
- * @param len length of the string
- */
- void onError(std::function onError) {
- _onError = onError;
- }
-
- /**
- * Complete message callback
- * @param data pointer to the data (binary or null-terminated string). This handler expects the user to know which data type he uses.
- */
- void onMessage(std::function onMessage) {
- _onMessage = onMessage;
- }
-
- /**
- * Fragmented message callback
- * @param data pointer to the data (binary or null-terminated string), will be null-terminated. This handler expects the user to know which data type he uses.
- */
- // clang-format off
- void onFragment(std::function onFragment) {
- _onFragment = onFragment;
- }
- // clang-format on
-
-private:
- // clang-format off
- std::function _onConnect;
- std::function _onError;
- std::function _onMessage;
- std::function _onFragment;
- std::function _onDisconnect;
- // clang-format on
-
- // this handler is meant to only support 1-frame messages (== unfragmented messages)
- AwsEventHandler _handler = [this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
- if (type == WS_EVT_CONNECT) {
- if (_onConnect) {
- _onConnect(server, client);
- }
- } else if (type == WS_EVT_DISCONNECT) {
- if (_onDisconnect) {
- _onDisconnect(server, client->id());
- }
- } else if (type == WS_EVT_ERROR) {
- if (_onError) {
- _onError(server, client, *((uint16_t *)arg), (const char *)data, len);
- }
- } else if (type == WS_EVT_DATA) {
- AwsFrameInfo *info = (AwsFrameInfo *)arg;
- if (info->opcode == WS_TEXT) {
- data[len] = 0;
- }
- if (info->final && info->index == 0 && info->len == len) {
- if (_onMessage) {
- _onMessage(server, client, data, len);
- }
- } else {
- if (_onFragment) {
- _onFragment(server, client, info, data, len);
- }
- }
- }
- };
-};
-
-#endif /* ASYNCWEBSOCKET_H_ */
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/BackPort_SHA1Builder.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/BackPort_SHA1Builder.cpp
deleted file mode 100644
index 06a73a5b8..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/BackPort_SHA1Builder.cpp
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * FIPS-180-1 compliant SHA-1 implementation
- *
- * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
- * SPDX-License-Identifier: Apache-2.0
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This file is part of mbed TLS (https://tls.mbed.org)
- * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
- */
-
-#include
-#if ESP_IDF_VERSION_MAJOR < 5
-
-#include "BackPort_SHA1Builder.h"
-
-// 32-bit integer manipulation macros (big endian)
-
-#ifndef GET_UINT32_BE
-#define GET_UINT32_BE(n, b, i) \
- { (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
-#endif
-
-#ifndef PUT_UINT32_BE
-#define PUT_UINT32_BE(n, b, i) \
- { \
- (b)[(i)] = (uint8_t)((n) >> 24); \
- (b)[(i) + 1] = (uint8_t)((n) >> 16); \
- (b)[(i) + 2] = (uint8_t)((n) >> 8); \
- (b)[(i) + 3] = (uint8_t)((n)); \
- }
-#endif
-
-// Constants
-
-static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
-// Private methods
-
-void SHA1Builder::process(const uint8_t *data) {
- uint32_t temp, W[16], A, B, C, D, E;
-
- GET_UINT32_BE(W[0], data, 0);
- GET_UINT32_BE(W[1], data, 4);
- GET_UINT32_BE(W[2], data, 8);
- GET_UINT32_BE(W[3], data, 12);
- GET_UINT32_BE(W[4], data, 16);
- GET_UINT32_BE(W[5], data, 20);
- GET_UINT32_BE(W[6], data, 24);
- GET_UINT32_BE(W[7], data, 28);
- GET_UINT32_BE(W[8], data, 32);
- GET_UINT32_BE(W[9], data, 36);
- GET_UINT32_BE(W[10], data, 40);
- GET_UINT32_BE(W[11], data, 44);
- GET_UINT32_BE(W[12], data, 48);
- GET_UINT32_BE(W[13], data, 52);
- GET_UINT32_BE(W[14], data, 56);
- GET_UINT32_BE(W[15], data, 60);
-
-#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
-
-#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
-
-#define sha1_P(a, b, c, d, e, x) \
- { \
- e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
- b = sha1_S(b, 30); \
- }
-
- A = state[0];
- B = state[1];
- C = state[2];
- D = state[3];
- E = state[4];
-
-#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
-#define sha1_K 0x5A827999
-
- sha1_P(A, B, C, D, E, W[0]);
- sha1_P(E, A, B, C, D, W[1]);
- sha1_P(D, E, A, B, C, W[2]);
- sha1_P(C, D, E, A, B, W[3]);
- sha1_P(B, C, D, E, A, W[4]);
- sha1_P(A, B, C, D, E, W[5]);
- sha1_P(E, A, B, C, D, W[6]);
- sha1_P(D, E, A, B, C, W[7]);
- sha1_P(C, D, E, A, B, W[8]);
- sha1_P(B, C, D, E, A, W[9]);
- sha1_P(A, B, C, D, E, W[10]);
- sha1_P(E, A, B, C, D, W[11]);
- sha1_P(D, E, A, B, C, W[12]);
- sha1_P(C, D, E, A, B, W[13]);
- sha1_P(B, C, D, E, A, W[14]);
- sha1_P(A, B, C, D, E, W[15]);
- sha1_P(E, A, B, C, D, sha1_R(16));
- sha1_P(D, E, A, B, C, sha1_R(17));
- sha1_P(C, D, E, A, B, sha1_R(18));
- sha1_P(B, C, D, E, A, sha1_R(19));
-
-#undef sha1_K
-#undef sha1_F
-
-#define sha1_F(x, y, z) (x ^ y ^ z)
-#define sha1_K 0x6ED9EBA1
-
- sha1_P(A, B, C, D, E, sha1_R(20));
- sha1_P(E, A, B, C, D, sha1_R(21));
- sha1_P(D, E, A, B, C, sha1_R(22));
- sha1_P(C, D, E, A, B, sha1_R(23));
- sha1_P(B, C, D, E, A, sha1_R(24));
- sha1_P(A, B, C, D, E, sha1_R(25));
- sha1_P(E, A, B, C, D, sha1_R(26));
- sha1_P(D, E, A, B, C, sha1_R(27));
- sha1_P(C, D, E, A, B, sha1_R(28));
- sha1_P(B, C, D, E, A, sha1_R(29));
- sha1_P(A, B, C, D, E, sha1_R(30));
- sha1_P(E, A, B, C, D, sha1_R(31));
- sha1_P(D, E, A, B, C, sha1_R(32));
- sha1_P(C, D, E, A, B, sha1_R(33));
- sha1_P(B, C, D, E, A, sha1_R(34));
- sha1_P(A, B, C, D, E, sha1_R(35));
- sha1_P(E, A, B, C, D, sha1_R(36));
- sha1_P(D, E, A, B, C, sha1_R(37));
- sha1_P(C, D, E, A, B, sha1_R(38));
- sha1_P(B, C, D, E, A, sha1_R(39));
-
-#undef sha1_K
-#undef sha1_F
-
-#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
-#define sha1_K 0x8F1BBCDC
-
- sha1_P(A, B, C, D, E, sha1_R(40));
- sha1_P(E, A, B, C, D, sha1_R(41));
- sha1_P(D, E, A, B, C, sha1_R(42));
- sha1_P(C, D, E, A, B, sha1_R(43));
- sha1_P(B, C, D, E, A, sha1_R(44));
- sha1_P(A, B, C, D, E, sha1_R(45));
- sha1_P(E, A, B, C, D, sha1_R(46));
- sha1_P(D, E, A, B, C, sha1_R(47));
- sha1_P(C, D, E, A, B, sha1_R(48));
- sha1_P(B, C, D, E, A, sha1_R(49));
- sha1_P(A, B, C, D, E, sha1_R(50));
- sha1_P(E, A, B, C, D, sha1_R(51));
- sha1_P(D, E, A, B, C, sha1_R(52));
- sha1_P(C, D, E, A, B, sha1_R(53));
- sha1_P(B, C, D, E, A, sha1_R(54));
- sha1_P(A, B, C, D, E, sha1_R(55));
- sha1_P(E, A, B, C, D, sha1_R(56));
- sha1_P(D, E, A, B, C, sha1_R(57));
- sha1_P(C, D, E, A, B, sha1_R(58));
- sha1_P(B, C, D, E, A, sha1_R(59));
-
-#undef sha1_K
-#undef sha1_F
-
-#define sha1_F(x, y, z) (x ^ y ^ z)
-#define sha1_K 0xCA62C1D6
-
- sha1_P(A, B, C, D, E, sha1_R(60));
- sha1_P(E, A, B, C, D, sha1_R(61));
- sha1_P(D, E, A, B, C, sha1_R(62));
- sha1_P(C, D, E, A, B, sha1_R(63));
- sha1_P(B, C, D, E, A, sha1_R(64));
- sha1_P(A, B, C, D, E, sha1_R(65));
- sha1_P(E, A, B, C, D, sha1_R(66));
- sha1_P(D, E, A, B, C, sha1_R(67));
- sha1_P(C, D, E, A, B, sha1_R(68));
- sha1_P(B, C, D, E, A, sha1_R(69));
- sha1_P(A, B, C, D, E, sha1_R(70));
- sha1_P(E, A, B, C, D, sha1_R(71));
- sha1_P(D, E, A, B, C, sha1_R(72));
- sha1_P(C, D, E, A, B, sha1_R(73));
- sha1_P(B, C, D, E, A, sha1_R(74));
- sha1_P(A, B, C, D, E, sha1_R(75));
- sha1_P(E, A, B, C, D, sha1_R(76));
- sha1_P(D, E, A, B, C, sha1_R(77));
- sha1_P(C, D, E, A, B, sha1_R(78));
- sha1_P(B, C, D, E, A, sha1_R(79));
-
-#undef sha1_K
-#undef sha1_F
-
- state[0] += A;
- state[1] += B;
- state[2] += C;
- state[3] += D;
- state[4] += E;
-}
-
-// Public methods
-
-void SHA1Builder::begin() {
- total[0] = 0;
- total[1] = 0;
-
- state[0] = 0x67452301;
- state[1] = 0xEFCDAB89;
- state[2] = 0x98BADCFE;
- state[3] = 0x10325476;
- state[4] = 0xC3D2E1F0;
-
- memset(buffer, 0x00, sizeof(buffer));
- memset(hash, 0x00, sizeof(hash));
-}
-
-void SHA1Builder::add(const uint8_t *data, size_t len) {
- size_t fill;
- uint32_t left;
-
- if (len == 0) {
- return;
- }
-
- left = total[0] & 0x3F;
- fill = 64 - left;
-
- total[0] += (uint32_t)len;
- total[0] &= 0xFFFFFFFF;
-
- if (total[0] < (uint32_t)len) {
- total[1]++;
- }
-
- if (left && len >= fill) {
- memcpy((void *)(buffer + left), data, fill);
- process(buffer);
- data += fill;
- len -= fill;
- left = 0;
- }
-
- while (len >= 64) {
- process(data);
- data += 64;
- len -= 64;
- }
-
- if (len > 0) {
- memcpy((void *)(buffer + left), data, len);
- }
-}
-
-void SHA1Builder::calculate(void) {
- uint32_t last, padn;
- uint32_t high, low;
- uint8_t msglen[8];
-
- high = (total[0] >> 29) | (total[1] << 3);
- low = (total[0] << 3);
-
- PUT_UINT32_BE(high, msglen, 0);
- PUT_UINT32_BE(low, msglen, 4);
-
- last = total[0] & 0x3F;
- padn = (last < 56) ? (56 - last) : (120 - last);
-
- add((uint8_t *)sha1_padding, padn);
- add(msglen, 8);
-
- PUT_UINT32_BE(state[0], hash, 0);
- PUT_UINT32_BE(state[1], hash, 4);
- PUT_UINT32_BE(state[2], hash, 8);
- PUT_UINT32_BE(state[3], hash, 12);
- PUT_UINT32_BE(state[4], hash, 16);
-}
-
-void SHA1Builder::getBytes(uint8_t *output) {
- memcpy(output, hash, SHA1_HASH_SIZE);
-}
-
-#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/BackPort_SHA1Builder.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/BackPort_SHA1Builder.h
deleted file mode 100644
index e7eafbef9..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/BackPort_SHA1Builder.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include
-#if ESP_IDF_VERSION_MAJOR < 5
-
-#ifndef SHA1Builder_h
-#define SHA1Builder_h
-
-#include
-#include
-
-#define SHA1_HASH_SIZE 20
-
-class SHA1Builder {
-private:
- uint32_t total[2]; /* number of bytes processed */
- uint32_t state[5]; /* intermediate digest state */
- unsigned char buffer[64]; /* data block being processed */
- uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
-
- void process(const uint8_t *data);
-
-public:
- void begin();
- void add(const uint8_t *data, size_t len);
- void calculate();
- void getBytes(uint8_t *output);
-};
-
-#endif // SHA1Builder_h
-
-#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ChunkPrint.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ChunkPrint.cpp
deleted file mode 100644
index de5a1cf29..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ChunkPrint.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#include "ChunkPrint.h"
-
-ChunkPrint::ChunkPrint(uint8_t *destination, size_t from, size_t len) : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
-
-size_t ChunkPrint::write(uint8_t c) {
- if (_to_skip > 0) {
- _to_skip--;
- return 1;
- } else if (_to_write > 0) {
- _to_write--;
- _destination[_pos++] = c;
- return 1;
- }
- return 0;
-}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ChunkPrint.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ChunkPrint.h
deleted file mode 100644
index 04938b3f2..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ChunkPrint.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#ifndef CHUNKPRINT_H
-#define CHUNKPRINT_H
-
-#include
-
-class ChunkPrint : public Print {
-private:
- uint8_t *_destination;
- size_t _to_skip;
- size_t _to_write;
- size_t _pos;
-
-public:
- ChunkPrint(uint8_t *destination, size_t from, size_t len);
- size_t write(uint8_t c);
- size_t write(const uint8_t *buffer, size_t size) {
- return this->Print::write(buffer, size);
- }
-};
-#endif
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h
index 09703a91b..6679401f5 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h
@@ -15,17 +15,7 @@
#include
#include
-#if defined(ESP32) || defined(LIBRETINY)
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
-#elif defined(ESP8266)
-#include
-#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
-#include
-#include
-#include
-#else
-#error Platform not supported
-#endif
#include "literals.h"
@@ -269,7 +259,7 @@ class AsyncWebServerRequest {
void _send();
void _runMiddlewareChain();
- static void _getEtag(uint8_t trailer[4], char *serverETag);
+ static bool _getEtag(File gzFile, char *eTag);
public:
File _tempFile;
@@ -1220,8 +1210,6 @@ class DefaultHeaders {
}
};
-#include "AsyncEventSource.h"
-#include "AsyncWebSocket.h"
#include "WebHandlerImpl.h"
#include "WebResponseImpl.h"
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/Middleware.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/Middleware.cpp
index f174f7bd9..a85cdf94e 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/Middleware.cpp
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/Middleware.cpp
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-#include "WebAuthentication.h"
#include "ESPAsyncWebServer.h"
AsyncMiddlewareChain::~AsyncMiddlewareChain() {
@@ -68,56 +67,6 @@ void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewar
return next();
}
-void AsyncAuthenticationMiddleware::setUsername(const char *username) {
- _username = username;
- _hasCreds = _username.length() && _credentials.length();
-}
-
-void AsyncAuthenticationMiddleware::setPassword(const char *password) {
- _credentials = password;
- _hash = false;
- _hasCreds = _username.length() && _credentials.length();
-}
-
-void AsyncAuthenticationMiddleware::setPasswordHash(const char *hash) {
- _credentials = hash;
- _hash = _credentials.length();
- _hasCreds = _username.length() && _credentials.length();
-}
-
-bool AsyncAuthenticationMiddleware::generateHash() {
- // ensure we have all the necessary data
- if (!_hasCreds) {
- return false;
- }
-
- // if we already have a hash, do nothing
- if (_hash) {
- return false;
- }
-
- switch (_authMethod) {
- case AsyncAuthType::AUTH_DIGEST:
- _credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
- if (_credentials.length()) {
- _hash = true;
- return true;
- } else {
- return false;
- }
-
- case AsyncAuthType::AUTH_BASIC:
- _credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
- if (_credentials.length()) {
- _hash = true;
- return true;
- } else {
- return false;
- }
-
- default: return false;
- }
-}
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE) {
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebAuthentication.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebAuthentication.cpp
deleted file mode 100644
index 7ed78142f..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebAuthentication.cpp
+++ /dev/null
@@ -1,247 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#include "WebAuthentication.h"
-#include
-#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
-#include
-#else
-#include "md5.h"
-#endif
-#include "literals.h"
-
-using namespace asyncsrv;
-
-// Basic Auth hash = base64("username:password")
-
-bool checkBasicAuthentication(const char *hash, const char *username, const char *password) {
- if (username == NULL || password == NULL || hash == NULL) {
- return false;
- }
- return generateBasicHash(username, password).equalsIgnoreCase(hash);
-}
-
-String generateBasicHash(const char *username, const char *password) {
- if (username == NULL || password == NULL) {
- return emptyString;
- }
-
- size_t toencodeLen = strlen(username) + strlen(password) + 1;
-
- char *toencode = new char[toencodeLen + 1];
- if (toencode == NULL) {
- return emptyString;
- }
- char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
- if (encoded == NULL) {
- delete[] toencode;
- return emptyString;
- }
- sprintf_P(toencode, PSTR("%s:%s"), username, password);
- if (base64_encode_chars(toencode, toencodeLen, encoded) > 0) {
- String res = String(encoded);
- delete[] toencode;
- delete[] encoded;
- return res;
- }
- delete[] toencode;
- delete[] encoded;
- return emptyString;
-}
-
-static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more
-#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
- MD5Builder md5;
- md5.begin();
- md5.add(data, len);
- md5.calculate();
- md5.getChars(output);
-#else
- md5_context_t _ctx;
-
- uint8_t *_buf = (uint8_t *)malloc(16);
- if (_buf == NULL) {
- return false;
- }
- memset(_buf, 0x00, 16);
-
- MD5Init(&_ctx);
- MD5Update(&_ctx, data, len);
- MD5Final(_buf, &_ctx);
-
- for (uint8_t i = 0; i < 16; i++) {
- sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
- }
-
- free(_buf);
-#endif
- return true;
-}
-
-String genRandomMD5() {
-#ifdef ESP8266
- uint32_t r = RANDOM_REG32;
-#else
- uint32_t r = rand();
-#endif
- char *out = (char *)malloc(33);
- if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- return emptyString;
- }
- String res = String(out);
- free(out);
- return res;
-}
-
-static String stringMD5(const String &in) {
- char *out = (char *)malloc(33);
- if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- return emptyString;
- }
- String res = String(out);
- free(out);
- return res;
-}
-
-String generateDigestHash(const char *username, const char *password, const char *realm) {
- if (username == NULL || password == NULL || realm == NULL) {
- return emptyString;
- }
- char *out = (char *)malloc(33);
- if (out == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- return emptyString;
- }
-
- String in;
- if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- free(out);
- return emptyString;
- }
-
- in.concat(username);
- in.concat(':');
- in.concat(realm);
- in.concat(':');
- in.concat(password);
-
- if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- free(out);
- return emptyString;
- }
-
- in = String(out);
- free(out);
- return in;
-}
-
-bool checkDigestAuthentication(
- const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
- const char *opaque, const char *uri
-) {
- if (username == NULL || password == NULL || header == NULL || method == NULL) {
- // os_printf("AUTH FAIL: missing required fields\n");
- return false;
- }
-
- String myHeader(header);
- int nextBreak = myHeader.indexOf(',');
- if (nextBreak < 0) {
- // os_printf("AUTH FAIL: no variables\n");
- return false;
- }
-
- String myUsername;
- String myRealm;
- String myNonce;
- String myUri;
- String myResponse;
- String myQop;
- String myNc;
- String myCnonce;
-
- myHeader += (char)0x2c; // ','
- myHeader += (char)0x20; // ' '
- do {
- String avLine(myHeader.substring(0, nextBreak));
- avLine.trim();
- myHeader = myHeader.substring(nextBreak + 1);
- nextBreak = myHeader.indexOf(',');
-
- int eqSign = avLine.indexOf('=');
- if (eqSign < 0) {
- // os_printf("AUTH FAIL: no = sign\n");
- return false;
- }
- String varName(avLine.substring(0, eqSign));
- avLine = avLine.substring(eqSign + 1);
- if (avLine.startsWith(String('"'))) {
- avLine = avLine.substring(1, avLine.length() - 1);
- }
-
- if (varName.equals(T_username)) {
- if (!avLine.equals(username)) {
- // os_printf("AUTH FAIL: username\n");
- return false;
- }
- myUsername = avLine;
- } else if (varName.equals(T_realm)) {
- if (realm != NULL && !avLine.equals(realm)) {
- // os_printf("AUTH FAIL: realm\n");
- return false;
- }
- myRealm = avLine;
- } else if (varName.equals(T_nonce)) {
- if (nonce != NULL && !avLine.equals(nonce)) {
- // os_printf("AUTH FAIL: nonce\n");
- return false;
- }
- myNonce = avLine;
- } else if (varName.equals(T_opaque)) {
- if (opaque != NULL && !avLine.equals(opaque)) {
- // os_printf("AUTH FAIL: opaque\n");
- return false;
- }
- } else if (varName.equals(T_uri)) {
- if (uri != NULL && !avLine.equals(uri)) {
- // os_printf("AUTH FAIL: uri\n");
- return false;
- }
- myUri = avLine;
- } else if (varName.equals(T_response)) {
- myResponse = avLine;
- } else if (varName.equals(T_qop)) {
- myQop = avLine;
- } else if (varName.equals(T_nc)) {
- myNc = avLine;
- } else if (varName.equals(T_cnonce)) {
- myCnonce = avLine;
- }
- } while (nextBreak > 0);
-
- String ha1 = passwordIsHash ? password : stringMD5(myUsername + ':' + myRealm + ':' + password).c_str();
- String ha2 = stringMD5(String(method) + ':' + myUri);
- String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + ha2;
-
- if (myResponse.equals(stringMD5(response))) {
- // os_printf("AUTH SUCCESS\n");
- return true;
- }
-
- // os_printf("AUTH FAIL: password\n");
- return false;
-}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebAuthentication.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebAuthentication.h
deleted file mode 100644
index 1711821f9..000000000
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebAuthentication.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
-
-#ifndef WEB_AUTHENTICATION_H_
-#define WEB_AUTHENTICATION_H_
-
-#include "Arduino.h"
-
-bool checkBasicAuthentication(const char *header, const char *username, const char *password);
-
-bool checkDigestAuthentication(
- const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
- const char *opaque, const char *uri
-);
-
-// for storing hashed versions on the device that can be authenticated against
-String generateDigestHash(const char *username, const char *password, const char *realm);
-
-String generateBasicHash(const char *username, const char *password);
-
-String genRandomMD5();
-
-#endif
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebHandlers.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebHandlers.cpp
index 21dba006c..6db71b93b 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebHandlers.cpp
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebHandlers.cpp
@@ -173,9 +173,6 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const St
size_t pathLen = path.length();
char *_tempPath = (char *)malloc(pathLen + 1);
if (_tempPath == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
request->abort();
request->_tempFile.close();
return false;
@@ -242,9 +239,6 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
}
if (!response) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
request->abort();
return;
}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebRequest.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebRequest.cpp
index ff4cf4cb7..d4fd02317 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebRequest.cpp
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebRequest.cpp
@@ -2,12 +2,13 @@
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "ESPAsyncWebServer.h"
-#include "WebAuthentication.h"
#include "WebResponseImpl.h"
#include "literals.h"
#include
-#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
+static inline bool isParamChar(char c) {
+ return ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='));
+}
static void doNotDelete(AsyncWebServerRequest *) {}
@@ -29,7 +30,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
c->onError(
[](void *r, AsyncClient *c, int8_t error) {
(void)c;
- // log_e("AsyncWebServerRequest::_onError");
+ // async_ws_log_e("AsyncWebServerRequest::_onError");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onError(error);
},
@@ -38,7 +39,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
c->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
- // log_e("AsyncWebServerRequest::_onAck");
+ // async_ws_log_e("AsyncWebServerRequest::_onAck");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onAck(len, time);
},
@@ -46,7 +47,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
);
c->onDisconnect(
[](void *r, AsyncClient *c) {
- // log_e("AsyncWebServerRequest::_onDisconnect");
+ // async_ws_log_e("AsyncWebServerRequest::_onDisconnect");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onDisconnect();
delete c;
@@ -56,7 +57,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
c->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
- // log_e("AsyncWebServerRequest::_onTimeout");
+ // async_ws_log_e("AsyncWebServerRequest::_onTimeout");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onTimeout(time);
},
@@ -65,7 +66,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
c->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
- // log_e("AsyncWebServerRequest::_onData");
+ // async_ws_log_e("AsyncWebServerRequest::_onData");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onData(buf, len);
},
@@ -74,7 +75,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
c->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
- // log_e("AsyncWebServerRequest::_onPoll");
+ // async_ws_log_e("AsyncWebServerRequest::_onPoll");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onPoll();
},
@@ -83,7 +84,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
}
AsyncWebServerRequest::~AsyncWebServerRequest() {
- // log_e("AsyncWebServerRequest::~AsyncWebServerRequest");
+ // async_ws_log_e("AsyncWebServerRequest::~AsyncWebServerRequest");
_this.reset();
@@ -112,9 +113,6 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) {
// SSL/TLS handshake detection
#ifndef ASYNC_TCP_SSL_ENABLED
if (_parseState == PARSE_REQ_START && len && ((uint8_t *)buf)[0] == 0x16) { // 0x16 indicates a Handshake message (SSL/TLS).
-#ifdef ESP32
- log_d("SSL/TLS handshake detected: resetting connection");
-#endif
_parseState = PARSE_REQ_FAIL;
abort();
return;
@@ -142,9 +140,6 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) {
char ch = str[len - 1];
str[len - 1] = 0;
if (!_temp.reserve(_temp.length() + len)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
_parseState = PARSE_REQ_FAIL;
abort();
return;
@@ -183,9 +178,12 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) {
if (_parsedLength == 0) {
if (_contentType.startsWith(T_app_xform_urlencoded)) {
_isPlainPost = true;
- } else if (_contentType == T_text_plain && __is_param_char(((char *)buf)[0])) {
+ } else if (_contentType == T_text_plain && isParamChar(((char *)buf)[0])) {
size_t i = 0;
- while (i < len && __is_param_char(((char *)buf)[i++]));
+ char ch;
+ do {
+ ch = ((char *)buf)[i];
+ } while (i++ < len && isParamChar(ch));
if (i < len && ((char *)buf)[i - 1] == '=') {
_isPlainPost = true;
}
@@ -419,12 +417,7 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
_params.emplace_back(name, urlDecode(value), true);
}
-#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
- // Ancient PRI core does not have String::clear() method 8-()
- _temp = emptyString;
-#else
_temp.clear();
-#endif
}
}
@@ -540,9 +533,6 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
}
_itemBuffer = (uint8_t *)malloc(RESPONSE_STREAM_BUFFER_SIZE);
if (_itemBuffer == NULL) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
_multiParseState = PARSE_ERROR;
abort();
return;
@@ -707,7 +697,7 @@ void AsyncWebServerRequest::_runMiddlewareChain() {
void AsyncWebServerRequest::_send() {
if (!_sent && !_paused) {
- // log_d("AsyncWebServerRequest::_send()");
+ // async_ws_log_d("AsyncWebServerRequest::_send()");
// user did not create a response ?
if (!_response) {
@@ -743,7 +733,7 @@ void AsyncWebServerRequest::abort() {
_sent = true;
_paused = false;
_this.reset();
- // log_e("AsyncWebServerRequest::abort");
+ // async_ws_log_e("AsyncWebServerRequest::abort");
_client->abort();
}
}
@@ -761,12 +751,6 @@ bool AsyncWebServerRequest::hasHeader(const char *name) const {
return false;
}
-#ifdef ESP8266
-bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper *data) const {
- return hasHeader(String(data));
-}
-#endif
-
const AsyncWebHeader *AsyncWebServerRequest::getHeader(const char *name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
@@ -774,22 +758,6 @@ const AsyncWebHeader *AsyncWebServerRequest::getHeader(const char *name) const {
return (iter == std::end(_headers)) ? nullptr : &(*iter);
}
-#ifdef ESP8266
-const AsyncWebHeader *AsyncWebServerRequest::getHeader(const __FlashStringHelper *data) const {
- PGM_P p = reinterpret_cast(data);
- size_t n = strlen_P(p);
- char *name = (char *)malloc(n + 1);
- if (name) {
- strcpy_P(name, p);
- const AsyncWebHeader *result = getHeader(String(name));
- free(name);
- return result;
- } else {
- return nullptr;
- }
-}
-#endif
-
const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const {
if (num >= _headers.size()) {
return nullptr;
@@ -797,21 +765,6 @@ const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const {
return &(*std::next(_headers.cbegin(), num));
}
-size_t AsyncWebServerRequest::getHeaderNames(std::vector &names) const {
- const size_t size = names.size();
- for (const auto &h : _headers) {
- names.push_back(h.name().c_str());
- }
- return names.size() - size;
-}
-
-bool AsyncWebServerRequest::removeHeader(const char *name) {
- const size_t size = _headers.size();
- _headers.remove_if([name](const AsyncWebHeader &header) {
- return header.name().equalsIgnoreCase(name);
- });
- return size != _headers.size();
-}
size_t AsyncWebServerRequest::params() const {
return _params.size();
@@ -835,12 +788,6 @@ const AsyncWebParameter *AsyncWebServerRequest::getParam(const char *name, bool
return nullptr;
}
-#ifdef ESP8266
-const AsyncWebParameter *AsyncWebServerRequest::getParam(const __FlashStringHelper *data, bool post, bool file) const {
- return getParam(String(data), post, file);
-}
-#endif
-
const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const {
if (num >= _params.size()) {
return nullptr;
@@ -848,26 +795,6 @@ const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const {
return &(*std::next(_params.cbegin(), num));
}
-const String &AsyncWebServerRequest::getAttribute(const char *name, const String &defaultValue) const {
- auto it = _attributes.find(name);
- return it != _attributes.end() ? it->second : defaultValue;
-}
-bool AsyncWebServerRequest::getAttribute(const char *name, bool defaultValue) const {
- auto it = _attributes.find(name);
- return it != _attributes.end() ? it->second == "1" : defaultValue;
-}
-long AsyncWebServerRequest::getAttribute(const char *name, long defaultValue) const {
- auto it = _attributes.find(name);
- return it != _attributes.end() ? it->second.toInt() : defaultValue;
-}
-float AsyncWebServerRequest::getAttribute(const char *name, float defaultValue) const {
- auto it = _attributes.find(name);
- return it != _attributes.end() ? it->second.toFloat() : defaultValue;
-}
-double AsyncWebServerRequest::getAttribute(const char *name, double defaultValue) const {
- auto it = _attributes.find(name);
- return it != _attributes.end() ? it->second.toDouble() : defaultValue;
-}
AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(int code, const char *contentType, const char *content, AwsTemplateProcessor callback) {
if (callback) {
@@ -948,16 +875,7 @@ void AsyncWebServerRequest::redirect(const char *url, int code) {
}
bool AsyncWebServerRequest::authenticate(const char *username, const char *password, const char *realm, bool passwordIsHash) const {
- if (_authorization.length()) {
- if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
- return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
- } else if (!passwordIsHash) {
- return checkBasicAuthentication(_authorization.c_str(), username, password);
- } else {
- return _authorization.equals(password);
- }
- }
- return false;
+ return true;
}
bool AsyncWebServerRequest::authenticate(const char *hash) const {
@@ -979,7 +897,7 @@ bool AsyncWebServerRequest::authenticate(const char *hash) const {
}
String realm = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
- return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
+ return false;
}
// Basic Auth, Bearer Auth, or other
@@ -993,124 +911,9 @@ void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const ch
AsyncWebServerResponse *r = _authFailMsg ? beginResponse(401, T_text_html, _authFailMsg) : beginResponse(401);
- switch (method) {
- case AsyncAuthType::AUTH_BASIC:
- {
- String header;
- if (header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1)) {
- header.concat(T_BASIC_REALM);
- header.concat(realm);
- header.concat('"');
- r->addHeader(T_WWW_AUTH, header.c_str());
- } else {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- abort();
- }
-
- break;
- }
- case AsyncAuthType::AUTH_DIGEST:
- {
- size_t len = strlen(T_DIGEST_) + strlen(T_realm__) + strlen(T_auth_nonce) + 32 + strlen(T__opaque) + 32 + 1;
- String header;
- if (header.reserve(len + strlen(realm))) {
- const String nonce = genRandomMD5();
- const String opaque = genRandomMD5();
- if (nonce.length() && opaque.length()) {
- header.concat(T_DIGEST_);
- header.concat(T_realm__);
- header.concat(realm);
- header.concat(T_auth_nonce);
- header.concat(nonce);
- header.concat(T__opaque);
- header.concat(opaque);
- header.concat((char)0x22); // '"'
- r->addHeader(T_WWW_AUTH, header.c_str());
- } else {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- abort();
- }
- }
- break;
- }
- default: break;
- }
-
send(r);
}
-bool AsyncWebServerRequest::hasArg(const char *name) const {
- for (const auto &arg : _params) {
- if (arg.name() == name) {
- return true;
- }
- }
- return false;
-}
-
-#ifdef ESP8266
-bool AsyncWebServerRequest::hasArg(const __FlashStringHelper *data) const {
- return hasArg(String(data).c_str());
-}
-#endif
-
-const String &AsyncWebServerRequest::arg(const char *name) const {
- for (const auto &arg : _params) {
- if (arg.name() == name) {
- return arg.value();
- }
- }
- return emptyString;
-}
-
-#ifdef ESP8266
-const String &AsyncWebServerRequest::arg(const __FlashStringHelper *data) const {
- return arg(String(data).c_str());
-}
-#endif
-
-const String &AsyncWebServerRequest::arg(size_t i) const {
- return getParam(i)->value();
-}
-
-const String &AsyncWebServerRequest::argName(size_t i) const {
- return getParam(i)->name();
-}
-
-const String &AsyncWebServerRequest::pathArg(size_t i) const {
- if (i >= _pathParams.size()) {
- return emptyString;
- }
- auto it = _pathParams.begin();
- std::advance(it, i);
- return *it;
-}
-
-const String &AsyncWebServerRequest::header(const char *name) const {
- const AsyncWebHeader *h = getHeader(name);
- return h ? h->value() : emptyString;
-}
-
-#ifdef ESP8266
-const String &AsyncWebServerRequest::header(const __FlashStringHelper *data) const {
- return header(String(data).c_str());
-};
-#endif
-
-const String &AsyncWebServerRequest::header(size_t i) const {
- const AsyncWebHeader *h = getHeader(i);
- return h ? h->value() : emptyString;
-}
-
-const String &AsyncWebServerRequest::headerName(size_t i) const {
- const AsyncWebHeader *h = getHeader(i);
- return h ? h->name() : emptyString;
-}
-
String AsyncWebServerRequest::urlDecode(const String &text) const {
char temp[] = "0x00";
unsigned int len = text.length();
@@ -1118,9 +921,6 @@ String AsyncWebServerRequest::urlDecode(const String &text) const {
String decoded;
// Allocate the string internal buffer - never longer from source text
if (!decoded.reserve(len)) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
return emptyString;
}
while (i < len) {
@@ -1140,45 +940,6 @@ String AsyncWebServerRequest::urlDecode(const String &text) const {
return decoded;
}
-const char *AsyncWebServerRequest::methodToString() const {
- if (_method == HTTP_ANY) {
- return T_ANY;
- }
- if (_method & HTTP_GET) {
- return T_GET;
- }
- if (_method & HTTP_POST) {
- return T_POST;
- }
- if (_method & HTTP_DELETE) {
- return T_DELETE;
- }
- if (_method & HTTP_PUT) {
- return T_PUT;
- }
- if (_method & HTTP_PATCH) {
- return T_PATCH;
- }
- if (_method & HTTP_HEAD) {
- return T_HEAD;
- }
- if (_method & HTTP_OPTIONS) {
- return T_OPTIONS;
- }
- return T_UNKNOWN;
-}
-
-const char *AsyncWebServerRequest::requestedConnTypeToString() const {
- switch (_reqconntype) {
- case RCT_NOT_USED: return T_RCT_NOT_USED;
- case RCT_DEFAULT: return T_RCT_DEFAULT;
- case RCT_HTTP: return T_RCT_HTTP;
- case RCT_WS: return T_RCT_WS;
- case RCT_EVENT: return T_RCT_EVENT;
- default: return T_ERROR;
- }
-}
-
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) const {
return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) || ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype))
|| ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebResponses.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebResponses.cpp
index 2878a2c1d..d8a7959e7 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebResponses.cpp
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebResponses.cpp
@@ -66,11 +66,7 @@ AsyncWebServerResponse::AsyncWebServerResponse()
}
}
-void AsyncWebServerResponse::setCode(int code) {
- if (_state == RESPONSE_SETUP) {
- _code = code;
- }
-}
+
void AsyncWebServerResponse::setContentLength(size_t len) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) {
@@ -358,14 +354,6 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
++_in_flight_credit;
}
- // for chunked responses ignore acks if there are no _in_flight_credits left
- if (_chunked && !_in_flight_credit) {
-#ifdef ESP32
- log_d("(chunk) out of in-flight credits");
-#endif
- return 0;
- }
-
_in_flight -= (_in_flight > len) ? len : _in_flight;
// get the size of available sock space
#endif
@@ -397,7 +385,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
// Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space.
// That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q
if (_in_flight > space) {
- // log_d("defer user call %u/%u", _in_flight, space);
+ // async_ws_log_d("defer user call %u/%u", _in_flight, space);
// take the credit back since we are ignoring this ack and rely on other inflight data
if (len) {
--_in_flight_credit;
@@ -421,9 +409,6 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
if (!buf) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
request->abort();
return 0;
}
@@ -599,6 +584,10 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
len = std::min(len + roomTaken, originalLen);
}
+ // Battery Emulator Fix: update pTemplateStart to point after inserted
+ // parameter value, so that % characters in the inserted value aren't
+ // parsed again
+ pTemplateStart += numBytesCopied;
}
} // while(pTemplateStart)
return len;
@@ -619,14 +608,6 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
* @note The method modifies the internal _contentType member variable
*/
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
-#if HAVE_EXTERN_GET_Content_Type_FUNCTION
-#ifndef ESP8266
- extern const char *getContentType(const String &path);
-#else
- extern const __FlashStringHelper *getContentType(const String &path);
-#endif
- _contentType = getContentType(path);
-#else
const char *cpath = path.c_str();
const char *dot = strrchr(cpath, '.');
@@ -639,8 +620,8 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
_contentType = T_text_html;
} else if (strcmp(dot, T__css) == 0) {
_contentType = T_text_css;
- } else if (strcmp(dot, T__js) == 0) {
- _contentType = T_application_javascript;
+ } else if (strcmp(dot, T__js) == 0 || strcmp(dot, T__mjs) == 0) {
+ _contentType = T_text_javascript;
} else if (strcmp(dot, T__json) == 0) {
_contentType = T_application_json;
} else if (strcmp(dot, T__png) == 0) {
@@ -659,26 +640,15 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
_contentType = T_image_gif;
} else if (strcmp(dot, T__woff2) == 0) {
_contentType = T_font_woff2;
- } else if (strcmp(dot, T__woff) == 0) {
- _contentType = T_font_woff;
} else if (strcmp(dot, T__ttf) == 0) {
_contentType = T_font_ttf;
} else if (strcmp(dot, T__xml) == 0) {
_contentType = T_text_xml;
- } else if (strcmp(dot, T__pdf) == 0) {
- _contentType = T_application_pdf;
- } else if (strcmp(dot, T__mp4) == 0) {
- _contentType = T_video_mp4;
- } else if (strcmp(dot, T__opus) == 0) {
- _contentType = T_audio_opus;
- } else if (strcmp(dot, T__webm) == 0) {
- _contentType = T_video_webm;
} else if (strcmp(dot, T__txt) == 0) {
_contentType = T_text_plain;
} else {
_contentType = T_application_octet_stream;
}
-#endif
}
/**
@@ -699,29 +669,23 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
// Try to open the uncompressed version first
_content = fs.open(path, fs::FileOpenMode::read);
- if (_content.available()) {
- _contentLength = _content.size();
- } else {
- // Try to open the compressed version (.gz)
+ if (!_content.available()) {
+ // If not available try to open the compressed version (.gz)
String gzPath;
uint16_t pathLen = path.length();
gzPath.reserve(pathLen + 3);
gzPath.concat(path);
gzPath.concat(asyncsrv::T__gz);
_content = fs.open(gzPath, fs::FileOpenMode::read);
- _contentLength = _content.size();
- if (_content.seek(_contentLength - 8)) {
+ char serverETag[9];
+ if (AsyncWebServerRequest::_getEtag(_content, serverETag)) {
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
// Add ETag and cache headers
- uint8_t crcInTrailer[4];
- _content.read(crcInTrailer, sizeof(crcInTrailer));
- char serverETag[9];
- AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag);
addHeader(T_ETag, serverETag, true);
addHeader(T_Cache_Control, T_no_cache, true);
@@ -733,6 +697,8 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
}
}
+ _contentLength = _content.size();
+
if (*contentType == '\0') {
_setContentTypeFromPath(path);
} else {
@@ -742,9 +708,12 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
if (download) {
// Extract filename from path and set as download attachment
int filenameStart = path.lastIndexOf('/') + 1;
- char buf[26 + path.length() - filenameStart];
- char *filename = (char *)path.c_str() + filenameStart;
- snprintf(buf, sizeof(buf), T_attachment, filename);
+ const char *filename = path.c_str() + filenameStart;
+ String buf;
+ buf.reserve(strlen(T_attachment) + strlen(filename) + 2);
+ buf = T_attachment;
+ buf += filename;
+ buf += "\"";
addHeader(T_Content_Disposition, buf, false);
} else {
// Serve file inline (display in browser)
@@ -768,22 +737,26 @@ AsyncFileResponse::AsyncFileResponse(File content, const String &path, const cha
_content = content;
_contentLength = _content.size();
- if (strlen(contentType) == 0) {
+ if (*contentType == '\0') {
_setContentTypeFromPath(path);
} else {
_contentType = contentType;
}
- int filenameStart = path.lastIndexOf('/') + 1;
- char buf[26 + path.length() - filenameStart];
- char *filename = (char *)path.c_str() + filenameStart;
-
if (download) {
- snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
+ // Extract filename from path and set as download attachment
+ int filenameStart = path.lastIndexOf('/') + 1;
+ const char *filename = path.c_str() + filenameStart;
+ String buf;
+ buf.reserve(strlen(T_attachment) + strlen(filename) + 2);
+ buf = T_attachment;
+ buf += filename;
+ buf += "\"";
+ addHeader(T_Content_Disposition, buf, false);
} else {
- snprintf_P(buf, sizeof(buf), PSTR("inline"));
+ // Serve file inline (display in browser)
+ addHeader(T_Content_Disposition, T_inline, false);
}
- addHeader(T_Content_Disposition, buf, false);
}
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len) {
@@ -893,11 +866,6 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
_contentType = contentType;
// internal buffer will be null on allocation failure
_content = std::unique_ptr(new cbuf(bufferSize));
- if (bufferSize && _content->size() < bufferSize) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- }
}
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
@@ -911,14 +879,6 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (len > _content->room()) {
size_t needed = len - _content->room();
_content->resizeAdd(needed);
- // log a warning if allocation failed, but do not return: keep writing the bytes we can
- // with _content->write: if len is more than the available size in the buffer, only
- // the available size will be written
- if (len > _content->room()) {
-#ifdef ESP32
- log_e("Failed to allocate");
-#endif
- }
}
size_t written = _content->write((const char *)data, len);
_contentLength += written;
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebServer.cpp b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebServer.cpp
index c3c3ee73f..db3716d27 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebServer.cpp
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/WebServer.cpp
@@ -3,38 +3,21 @@
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
-
-#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
#include
-#elif defined(ESP8266)
-#include
-#else
-#error Platform not supported
-#endif
using namespace asyncsrv;
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
-#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
return WiFi.localIP() == request->client()->localIP();
-#else
- return false;
-#endif
}
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
-#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
return WiFi.localIP() != request->client()->localIP();
-#else
- return false;
-#endif
}
-#ifndef HAVE_FS_FILE_OPEN_MODE
const char *fs::FileOpenMode::read = "r";
const char *fs::FileOpenMode::write = "w";
const char *fs::FileOpenMode::append = "a";
-#endif
AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
_catchAllHandler = new AsyncCallbackWebHandler();
@@ -114,16 +97,6 @@ void AsyncWebServer::end() {
_server.end();
}
-#if ASYNC_TCP_SSL_ENABLED
-void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void *arg) {
- _server.onSslFileRequest(cb, arg);
-}
-
-void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) {
- _server.beginSecure(cert, key, password);
-}
-#endif
-
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
delete request;
}
diff --git a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/literals.h b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/literals.h
index fc0fff13c..db292bfe6 100644
--- a/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/literals.h
+++ b/Software/src/lib/ESP32Async-ESPAsyncWebServer/src/literals.h
@@ -12,7 +12,7 @@ static constexpr const char *T_100_CONTINUE = "100-continue";
static constexpr const char *T_13 = "13";
static constexpr const char *T_ACCEPT = "Accept";
static constexpr const char *T_Accept_Ranges = "Accept-Ranges";
-static constexpr const char *T_attachment = "attachment; filename=\"%s\"";
+static constexpr const char *T_attachment = "attachment; filename=\"";
static constexpr const char *T_AUTH = "Authorization";
static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
static constexpr const char *T_BASIC = "Basic";
@@ -114,27 +114,20 @@ static constexpr const char *T__ico = ".ico"; // ICO: Favicons, system icon
static constexpr const char *T__jpg = ".jpg"; // JPEG/JPG: Photos. Legacy support
static constexpr const char *T__js = ".js"; // JavaScript: Interactive functionality
static constexpr const char *T__json = ".json"; // JSON: Data exchange format
-static constexpr const char *T__mp4 = ".mp4"; // MP4: Proprietary format. Worse compression than WEBM.
-static constexpr const char *T__opus = ".opus"; // OPUS: High compression audio format
-static constexpr const char *T__pdf = ".pdf"; // PDF: Universal document format
+static constexpr const char *T__mjs = ".mjs"; // MJS: JavaScript module format
static constexpr const char *T__png = ".png"; // PNG: Icons, logos, transparency. Legacy support
static constexpr const char *T__svg = ".svg"; // SVG: Vector graphics, icons (scalable, tiny file sizes)
static constexpr const char *T__ttf = ".ttf"; // TTF: Font file. Legacy support
static constexpr const char *T__txt = ".txt"; // TXT: Plain text files
-static constexpr const char *T__webm = ".webm"; // WebM: Video. Open source, optimized for web. Compatible with all modern browsers.
static constexpr const char *T__webp = ".webp"; // WebP: Highly compressed images. Compatible with all modern browsers.
-static constexpr const char *T__woff = ".woff"; // WOFF: Font file. Legacy support
static constexpr const char *T__woff2 = ".woff2"; // WOFF2: Better compression. Compatible with all modern browsers.
static constexpr const char *T__xml = ".xml"; // XML: Configuration and data files
static constexpr const char *T_application_javascript = "application/javascript"; // Obsolete type for JavaScript
static constexpr const char *T_application_json = "application/json";
static constexpr const char *T_application_msgpack = "application/msgpack";
static constexpr const char *T_application_octet_stream = "application/octet-stream";
-static constexpr const char *T_application_pdf = "application/pdf";
static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
-static constexpr const char *T_audio_opus = "audio/opus";
static constexpr const char *T_font_ttf = "font/ttf";
-static constexpr const char *T_font_woff = "font/woff";
static constexpr const char *T_font_woff2 = "font/woff2";
static constexpr const char *T_image_avif = "image/avif";
static constexpr const char *T_image_gif = "image/gif";
@@ -150,8 +143,6 @@ static constexpr const char *T_text_html = "text/html";
static constexpr const char *T_text_javascript = "text/javascript";
static constexpr const char *T_text_plain = "text/plain";
static constexpr const char *T_text_xml = "text/xml";
-static constexpr const char *T_video_mp4 = "video/mp4";
-static constexpr const char *T_video_webm = "video/webm";
// Response codes
static constexpr const char *T_HTTP_CODE_100 = "Continue";
diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/keywords.txt b/Software/src/lib/ayushsharma82-ElegantOTA/keywords.txt
deleted file mode 100644
index d9eeee7ec..000000000
--- a/Software/src/lib/ayushsharma82-ElegantOTA/keywords.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-ElegantOTA KEYWORD1
-begin KEYWORD2
-loop KEYWORD2
-setAuth KEYWORD2
-clearAuth KEYWORD2
-setAutoReboot KEYWORD2
-onStart KEYWORD2
-onEnd KEYWORD2
-onProgress KEYWORD2
diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/platformio_upload.py b/Software/src/lib/ayushsharma82-ElegantOTA/platformio_upload.py
deleted file mode 100644
index 467f21951..000000000
--- a/Software/src/lib/ayushsharma82-ElegantOTA/platformio_upload.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# Allows PlatformIO to upload directly to ElegantOTA
-#
-# To use:
-# - copy this script into the same folder as your platformio.ini
-# - set the following for your project in platformio.ini:
-#
-# extra_scripts = platformio_upload.py
-# upload_protocol = custom
-# custom_upload_url =
-#
-# An example of an upload URL:
-# custom_upload_url = http://192.168.1.123/update
-# also possible: custom_upload_url = http://domainname/update
-
-import sys
-import requests
-import hashlib
-from urllib.parse import urlparse
-import time
-from requests.auth import HTTPDigestAuth
-Import("env")
-
-try:
- from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
- from tqdm import tqdm
-except ImportError:
- env.Execute("$PYTHONEXE -m pip install requests_toolbelt")
- env.Execute("$PYTHONEXE -m pip install tqdm")
- from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
- from tqdm import tqdm
-
-def on_upload(source, target, env):
- firmware_path = str(source[0])
-
- auth = None
- upload_url_compatibility = env.GetProjectOption('custom_upload_url')
- upload_url = upload_url_compatibility.replace("/update", "")
-
- with open(firmware_path, 'rb') as firmware:
- md5 = hashlib.md5(firmware.read()).hexdigest()
-
- parsed_url = urlparse(upload_url)
- host_ip = parsed_url.netloc
-
- # Fรผhre die GET-Anfrage aus
- start_url = f"{upload_url}/ota/start?mode=fr&hash={md5}"
-
- start_headers = {
- 'Host': host_ip,
- 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0',
- 'Accept': '*/*',
- 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
- 'Accept-Encoding': 'gzip, deflate',
- 'Referer': f'{upload_url}/update',
- 'Connection': 'keep-alive'
- }
-
- try:
- checkAuthResponse = requests.get(f"{upload_url_compatibility}/update")
- except Exception as e:
- return 'Error checking auth: ' + repr(e)
-
- if checkAuthResponse.status_code == 401:
- try:
- username = env.GetProjectOption('custom_username')
- password = env.GetProjectOption('custom_password')
- except:
- username = None
- password = None
- print("No authentication values specified.")
- print('Please, add some Options in your .ini file like: \n\ncustom_username=username\ncustom_password=password\n')
- if username is None or password is None:
- return "Authentication required, but no credentials provided."
- print("Serverconfiguration: authentication needed.")
- auth = HTTPDigestAuth(username, password)
- try:
- doUpdateAuth = requests.get(start_url, headers=start_headers, auth=auth)
- except Exception as e:
- return 'Error while authenticating: ' + repr(e)
-
- if doUpdateAuth.status_code != 200:
- return "Authentication failed " + str(doUpdateAuth.status_code)
- print("Authentication successful")
- else:
- auth = None
- print("Serverconfiguration: authentication not needed.")
- try:
- doUpdate = requests.get(start_url, headers=start_headers)
- except Exception as e:
- return 'Error while starting upload: ' + repr(e)
-
- if doUpdate.status_code != 200:
- return "Start request failed " + str(doUpdate.status_code)
-
- firmware.seek(0)
- encoder = MultipartEncoder(fields={
- 'MD5': md5,
- 'firmware': ('firmware', firmware, 'application/octet-stream')}
- )
-
- bar = tqdm(desc='Upload Progress',
- total=encoder.len,
- dynamic_ncols=True,
- unit='B',
- unit_scale=True,
- unit_divisor=1024
- )
-
- monitor = MultipartEncoderMonitor(encoder, lambda monitor: bar.update(monitor.bytes_read - bar.n))
-
- post_headers = {
- 'Host': host_ip,
- 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0',
- 'Accept': '*/*',
- 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
- 'Accept-Encoding': 'gzip, deflate',
- 'Referer': f'{upload_url}/update',
- 'Connection': 'keep-alive',
- 'Content-Type': monitor.content_type,
- 'Content-Length': str(monitor.len),
- 'Origin': f'{upload_url}'
- }
-
- try:
- response = requests.post(f"{upload_url}/ota/upload", data=monitor, headers=post_headers, auth=auth)
- except Exception as e:
- return 'Error while uploading: ' + repr(e)
-
- bar.close()
- time.sleep(0.1)
-
- if response.status_code != 200:
- message = "\nUpload failed.\nServer response: " + response.text
- tqdm.write(message)
- else:
- message = "\nUpload successful.\nServer response: " + response.text
- tqdm.write(message)
-
-
-env.Replace(UPLOADCMD=on_upload)
\ No newline at end of file
diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.cpp b/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.cpp
index acf22d708..b5e213a7d 100644
--- a/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.cpp
+++ b/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.cpp
@@ -2,46 +2,16 @@
ElegantOTAClass::ElegantOTAClass(){}
-void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username, const char * password){
+void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server){
_server = server;
- setAuth(username, password);
-
- #if defined(TARGET_RP2040)
- if (!__isPicoW) {
- ELEGANTOTA_DEBUG_MSG("RP2040: Not a Pico W, skipping OTA setup\n");
- return;
- }
- #endif
-
- #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
_server->on("/update", HTTP_GET, [&](AsyncWebServerRequest *request){
- if(_authenticate && !request->authenticate(_username.c_str(), _password.c_str())){
- return request->requestAuthentication();
- }
- #if defined(ASYNCWEBSERVER_VERSION) && ASYNCWEBSERVER_VERSION_MAJOR > 2 // This means we are using recommended fork of AsyncWebServer
- AsyncWebServerResponse *response = request->beginResponse(200, "text/html", ELEGANT_HTML, sizeof(ELEGANT_HTML));
- #else
- AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", ELEGANT_HTML, sizeof(ELEGANT_HTML));
- #endif
+ AsyncWebServerResponse *response = request->beginResponse(200, "text/html", ELEGANT_HTML, sizeof(ELEGANT_HTML));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
- #else
- _server->on("/update", HTTP_GET, [&](){
- if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
- return _server->requestAuthentication();
- }
- _server->sendHeader("Content-Encoding", "gzip");
- _server->send_P(200, "text/html", (const char*)ELEGANT_HTML, sizeof(ELEGANT_HTML));
- });
- #endif
- #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
_server->on("/ota/start", HTTP_GET, [&](AsyncWebServerRequest *request) {
- if (_authenticate && !request->authenticate(_username.c_str(), _password.c_str())) {
- return request->requestAuthentication();
- }
// Get header x-ota-mode value, if present
OTA_Mode mode = OTA_MODE_FIRMWARE;
@@ -49,10 +19,8 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
if (request->hasParam("mode")) {
String argValue = request->getParam("mode")->value();
if (argValue == "fs") {
- ELEGANTOTA_DEBUG_MSG("OTA Mode: Filesystem\n");
mode = OTA_MODE_FILESYSTEM;
} else {
- ELEGANTOTA_DEBUG_MSG("OTA Mode: Firmware\n");
mode = OTA_MODE_FIRMWARE;
}
}
@@ -60,144 +28,27 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
// Get file MD5 hash from arg
if (request->hasParam("hash")) {
String hash = request->getParam("hash")->value();
- ELEGANTOTA_DEBUG_MSG(String("MD5: "+hash+"\n").c_str());
if (!Update.setMD5(hash.c_str())) {
- ELEGANTOTA_DEBUG_MSG("ERROR: MD5 hash not valid\n");
- return request->send(400, "text/plain", "MD5 parameter invalid");
+ return request->send(400, "text/plain", "MD5 param invalid");
}
}
- #if UPDATE_DEBUG == 1
- // Serial output must be active to see the callback serial prints
- Serial.setDebugOutput(true);
- #endif
-
// Pre-OTA update callback
if (preUpdateCallback != NULL) preUpdateCallback();
// Start update process
- #if defined(ESP8266)
- uint32_t update_size = mode == OTA_MODE_FILESYSTEM ? ((size_t)FS_end - (size_t)FS_start) : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
- if (mode == OTA_MODE_FILESYSTEM) {
- close_all_fs();
- }
- Update.runAsync(true);
- if (!Update.begin(update_size, mode == OTA_MODE_FILESYSTEM ? U_FS : U_FLASH)) {
- ELEGANTOTA_DEBUG_MSG("Failed to start update process\n");
- // Save error to string
- StreamString str;
- Update.printError(str);
- _update_error_str = str.c_str();
- _update_error_str.concat("\n");
- ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
- }
- #elif defined(ESP32)
if (!Update.begin(UPDATE_SIZE_UNKNOWN, mode == OTA_MODE_FILESYSTEM ? U_SPIFFS : U_FLASH)) {
- ELEGANTOTA_DEBUG_MSG("Failed to start update process\n");
// Save error to string
StreamString str;
Update.printError(str);
_update_error_str = str.c_str();
_update_error_str.concat("\n");
- ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
}
- #endif
return request->send((Update.hasError()) ? 400 : 200, "text/plain", (Update.hasError()) ? _update_error_str.c_str() : "OK");
});
- #else
- _server->on("/ota/start", HTTP_GET, [&]() {
- if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
- return _server->requestAuthentication();
- }
-
- // Get header x-ota-mode value, if present
- OTA_Mode mode = OTA_MODE_FIRMWARE;
- // Get mode from arg
- if (_server->hasArg("mode")) {
- String argValue = _server->arg("mode");
- if (argValue == "fs") {
- ELEGANTOTA_DEBUG_MSG("OTA Mode: Filesystem\n");
- mode = OTA_MODE_FILESYSTEM;
- } else {
- ELEGANTOTA_DEBUG_MSG("OTA Mode: Firmware\n");
- mode = OTA_MODE_FIRMWARE;
- }
- }
-
- // Get file MD5 hash from arg
- if (_server->hasArg("hash")) {
- String hash = _server->arg("hash");
- ELEGANTOTA_DEBUG_MSG(String("MD5: "+hash+"\n").c_str());
- if (!Update.setMD5(hash.c_str())) {
- ELEGANTOTA_DEBUG_MSG("ERROR: MD5 hash not valid\n");
- return _server->send(400, "text/plain", "MD5 parameter invalid");
- }
- }
-
- #if UPDATE_DEBUG == 1
- // Serial output must be active to see the callback serial prints
- Serial.setDebugOutput(true);
- #endif
-
- // Pre-OTA update callback
- if (preUpdateCallback != NULL) preUpdateCallback();
-
- // Start update process
- #if defined(ESP8266)
- uint32_t update_size = mode == OTA_MODE_FILESYSTEM ? ((size_t)FS_end - (size_t)FS_start) : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
- if (mode == OTA_MODE_FILESYSTEM) {
- close_all_fs();
- }
- Update.runAsync(true);
- if (!Update.begin(update_size, mode == OTA_MODE_FILESYSTEM ? U_FS : U_FLASH)) {
- ELEGANTOTA_DEBUG_MSG("Failed to start update process\n");
- // Save error to string
- StreamString str;
- Update.printError(str);
- _update_error_str = str.c_str();
- _update_error_str.concat("\n");
- ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
- }
- #elif defined(ESP32)
- if (!Update.begin(UPDATE_SIZE_UNKNOWN, mode == OTA_MODE_FILESYSTEM ? U_SPIFFS : U_FLASH)) {
- ELEGANTOTA_DEBUG_MSG("Failed to start update process\n");
- // Save error to string
- StreamString str;
- Update.printError(str);
- _update_error_str = str.c_str();
- _update_error_str.concat("\n");
- ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
- }
- #elif defined(TARGET_RP2040)
- uint32_t update_size = 0;
- // Gather FS Size
- if (mode == OTA_MODE_FILESYSTEM) {
- update_size = ((size_t)&_FS_end - (size_t)&_FS_start);
- LittleFS.end();
- } else {
- FSInfo i;
- LittleFS.begin();
- LittleFS.info(i);
- update_size = i.totalBytes - i.usedBytes;
- }
- // Start update process
- if (!Update.begin(update_size, mode == OTA_MODE_FILESYSTEM ? U_FS : U_FLASH)) {
- ELEGANTOTA_DEBUG_MSG("Failed to start update process because there is not enough space\n");
- _update_error_str = "Not enough space";
- return _server->send(400, "text/plain", _update_error_str.c_str());
- }
- #endif
-
- return _server->send((Update.hasError()) ? 400 : 200, "text/plain", (Update.hasError()) ? _update_error_str.c_str() : "OK");
- });
- #endif
- #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
_server->on("/ota/upload", HTTP_POST, [&](AsyncWebServerRequest *request) {
- if(_authenticate && !request->authenticate(_username.c_str(), _password.c_str())){
- return request->requestAuthentication();
- }
// Post-OTA update callback
if (postUpdateCallback != NULL) postUpdateCallback(!Update.hasError());
AsyncWebServerResponse *response = request->beginResponse((Update.hasError()) ? 400 : 200, "text/plain", (Update.hasError()) ? _update_error_str.c_str() : "OK");
@@ -206,19 +57,11 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
request->send(response);
// Set reboot flag
if (!Update.hasError()) {
- if (_auto_reboot) {
_reboot_request_millis = millis();
_reboot = true;
- }
}
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
//Upload handler chunks in data
- if(_authenticate){
- if(!request->authenticate(_username.c_str(), _password.c_str())){
- return request->requestAuthentication();
- }
- }
-
if (!index) {
// Reset progress size on first frame
_current_progress_size = 0;
@@ -227,7 +70,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
// Write chunked data to the free sketch space
if(len){
if (Update.write(data, len) != len) {
- return request->send(400, "text/plain", "Failed to write chunked data to free space");
+ return request->send(400, "text/plain", "Fail write chunk data");
}
_current_progress_size += len;
// Progress update callback
@@ -241,95 +84,18 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
Update.printError(str);
_update_error_str = str.c_str();
_update_error_str.concat("\n");
- ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
}
}else{
return;
}
});
- #else
- _server->on("/ota/upload", HTTP_POST, [&](){
- if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
- return _server->requestAuthentication();
- }
- // Post-OTA update callback
- if (postUpdateCallback != NULL) postUpdateCallback(!Update.hasError());
- _server->sendHeader("Connection", "close");
- _server->send((Update.hasError()) ? 400 : 200, "text/plain", (Update.hasError()) ? _update_error_str.c_str() : "OK");
- // Set reboot flag
- if (!Update.hasError()) {
- if (_auto_reboot) {
- _reboot_request_millis = millis();
- _reboot = true;
- }
- }
- }, [&](){
- // Actual OTA Download
- HTTPUpload& upload = _server->upload();
- if (upload.status == UPLOAD_FILE_START) {
- // Check authentication
- if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
- ELEGANTOTA_DEBUG_MSG("Authentication Failed on UPLOAD_FILE_START\n");
- return;
- }
- Serial.printf("Update Received: %s\n", upload.filename.c_str());
- _current_progress_size = 0;
- } else if (upload.status == UPLOAD_FILE_WRITE) {
- if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
- #if UPDATE_DEBUG == 1
- Update.printError(Serial);
- #endif
- }
-
- _current_progress_size += upload.currentSize;
- // Progress update callback
- if (progressUpdateCallback != NULL) progressUpdateCallback(_current_progress_size, upload.totalSize);
- } else if (upload.status == UPLOAD_FILE_END) {
- if (Update.end(true)) {
- ELEGANTOTA_DEBUG_MSG(String("Update Success: "+String(upload.totalSize)+"\n").c_str());
- } else {
- ELEGANTOTA_DEBUG_MSG("[!] Update Failed\n");
- // Store error to string
- StreamString str;
- Update.printError(str);
- _update_error_str = str.c_str();
- _update_error_str.concat("\n");
- ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
- }
-
- #if UPDATE_DEBUG == 1
- Serial.setDebugOutput(false);
- #endif
- } else {
- ELEGANTOTA_DEBUG_MSG(String("Update Failed Unexpectedly (likely broken connection): status="+String(upload.status)+"\n").c_str());
- }
- });
- #endif
-}
-
-void ElegantOTAClass::setAuth(const char * username, const char * password){
- _username = username;
- _password = password;
- _authenticate = _username.length() && _password.length();
-}
-
-void ElegantOTAClass::clearAuth(){
- _authenticate = false;
-}
-void ElegantOTAClass::setAutoReboot(bool enable){
- _auto_reboot = enable;
}
void ElegantOTAClass::loop() {
// Check if 2 seconds have passed since _reboot_request_millis was set
if (_reboot && millis() - _reboot_request_millis > 2000) {
- ELEGANTOTA_DEBUG_MSG("Rebooting...\n");
- #if defined(ESP8266) || defined(ESP32)
- ESP.restart();
- #elif defined(TARGET_RP2040)
- rp2040.reboot();
- #endif
+ ESP.restart();
_reboot = false;
}
}
diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h b/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h
index 566aa7d90..f75affbf4 100644
--- a/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h
+++ b/Software/src/lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h
@@ -20,80 +20,12 @@ _____ _ _ ___ _____ _
#define ElegantOTA_h
#include "Arduino.h"
-#include "stdlib_noniso.h"
#include "elop.h"
-#ifndef ELEGANTOTA_USE_ASYNC_WEBSERVER
- #define ELEGANTOTA_USE_ASYNC_WEBSERVER 1
-#endif
-
-#ifndef ELEGANTOTA_DEBUG
- #define ELEGANTOTA_DEBUG 0
-#endif
-
-#ifndef UPDATE_DEBUG
- #define UPDATE_DEBUG 0
-#endif
-
-#if ELEGANTOTA_DEBUG
- #define ELEGANTOTA_DEBUG_MSG(x) Serial.printf("%s %s", "[ElegantOTA] ", x)
-#else
- #define ELEGANTOTA_DEBUG_MSG(x)
-#endif
-
-#if defined(ESP8266)
- #include
- #include "FS.h"
- #include "LittleFS.h"
- #include "Updater.h"
- #include "StreamString.h"
- #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
- #include "ESPAsyncTCP.h"
- #include "ESPAsyncWebServer.h"
- #define ELEGANTOTA_WEBSERVER AsyncWebServer
- #else
- #include "ESP8266WiFi.h"
- #include "WiFiClient.h"
- #include "ESP8266WebServer.h"
- #define ELEGANTOTA_WEBSERVER ESP8266WebServer
- #endif
- #define HARDWARE "ESP8266"
-#elif defined(ESP32)
- #include
- #include "FS.h"
- #include "Update.h"
- #include "StreamString.h"
- #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
- #include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
- #include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
- #define ELEGANTOTA_WEBSERVER AsyncWebServer
- #else
- #include "WiFi.h"
- #include "WiFiClient.h"
- #include "WebServer.h"
- #define ELEGANTOTA_WEBSERVER WebServer
- #endif
- #define HARDWARE "ESP32"
-#elif defined(TARGET_RP2040)
- #include
- #include "Arduino.h"
- #include "FS.h"
- #include "LittleFS.h"
- #include "WiFiClient.h"
- #include "WiFiServer.h"
- #include "WebServer.h"
- #include "WiFiUdp.h"
- #include "StreamString.h"
- #include "Updater.h"
- #define HARDWARE "RP2040"
- #define ELEGANTOTA_WEBSERVER WebServer
- // Throw an error if async mode is enabled
- #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
- #error "Async mode is not supported on RP2040. Please set ELEGANTOTA_USE_ASYNC_WEBSERVER to 0."
- #endif
- extern uint8_t _FS_start;
- extern uint8_t _FS_end;
-#endif
+#include "Update.h"
+#include "StreamString.h"
+#include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
+#define ELEGANTOTA_WEBSERVER AsyncWebServer
enum OTA_Mode {
OTA_MODE_FIRMWARE = 0,
@@ -104,11 +36,8 @@ class ElegantOTAClass{
public:
ElegantOTAClass();
- void begin(ELEGANTOTA_WEBSERVER *server, const char * username = "", const char * password = "");
+ void begin(ELEGANTOTA_WEBSERVER *server);
- void setAuth(const char * username, const char * password);
- void clearAuth();
- void setAutoReboot(bool enable);
void loop();
void onStart(std::function callable);
@@ -117,12 +46,6 @@ class ElegantOTAClass{
private:
ELEGANTOTA_WEBSERVER *_server;
-
- bool _authenticate;
- String _username;
- String _password;
-
- bool _auto_reboot = true;
bool _reboot = false;
unsigned long _reboot_request_millis = 0;
diff --git a/Software/src/lib/eModbus-eModbus/CoilData.cpp b/Software/src/lib/eModbus-eModbus/CoilData.cpp
index 42b7839a8..1b599e4d6 100644
--- a/Software/src/lib/eModbus-eModbus/CoilData.cpp
+++ b/Software/src/lib/eModbus-eModbus/CoilData.cpp
@@ -4,8 +4,6 @@
// =================================================================================================
#include "CoilData.h"
-#undef LOCAL_LOG_LEVEL
-#include "Logging.h"
// Constructor: optional size in bits, optional initial value for all bits
// Maximum size is 2000 coils (=250 bytes)
@@ -516,41 +514,3 @@ uint16_t CoilData::coilsSetON() const {
uint16_t CoilData::coilsSetOFF() const {
return CDsize - coilsSetON();
}
-
-#if !IS_LINUX
-// Not for Linux for the Print reference!
-
-// Print out a coil storage in readable form to ease debugging
-void CoilData::print(const char *label, Print& s) {
- uint8_t bitptr = 0;
- uint8_t labellen = strlen(label);
- uint8_t pos = labellen;
-
- // Put out the label
- s.print(label);
-
- // Print out all coils as "1" or "0"
- for (uint16_t i = 0; i < CDsize; ++i) {
- s.print((CDbuffer[byteIndex(i)] & (1 << bitptr)) ? "1" : "0");
- pos++;
- // Have a blank after every group of 4
- if (i % 4 == 3) {
- // Have a line break if > 80 characters, including the last group of 4
- if (pos >= 80) {
- s.println("");
- pos = 0;
- // Leave a nice empty space below the label
- while (pos++ < labellen) {
- s.print(" ");
- }
- } else {
- s.print(" ");
- pos++;
- }
- }
- bitptr++;
- bitptr &= 0x07;
- }
- s.println("");
-}
-#endif
diff --git a/Software/src/lib/eModbus-eModbus/CoilData.h b/Software/src/lib/eModbus-eModbus/CoilData.h
index fe01618a9..88d3f26f3 100644
--- a/Software/src/lib/eModbus-eModbus/CoilData.h
+++ b/Software/src/lib/eModbus-eModbus/CoilData.h
@@ -98,11 +98,6 @@ class CoilData {
// Return number of coils set to 0 (or OFF)
uint16_t coilsSetOFF() const;
-#if !LINUX
- // Helper function to dump out coils in logical order
- void print(const char *label, Print& s);
-#endif
-
protected:
// bit masks for bits left of a bit index in a byte
const uint8_t CDfilter[8] = { 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
diff --git a/Software/src/lib/eModbus-eModbus/Logging.cpp b/Software/src/lib/eModbus-eModbus/Logging.cpp
deleted file mode 100644
index 1991cfad1..000000000
--- a/Software/src/lib/eModbus-eModbus/Logging.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-// =================================================================================================
-// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
-// MIT license - see license.md for details
-// =================================================================================================
-#include "Logging.h"
-#include
-
-int MBUlogLvl = LOG_LEVEL;
-#if IS_LINUX
-#define PrintOut printf
-
-void logHexDump(const char *letter, const char *label, const uint8_t *data, const size_t length) {
-#else
-Print *LOGDEVICE = &Serial;
-#define PrintOut output->printf
-
-void logHexDump(Print *output, const char *letter, const char *label, const uint8_t *data, const size_t length) {
-#endif
- size_t cnt = 0;
- size_t step = 0;
- char limiter = '|';
- // Use line buffer to speed up output
- const uint16_t BUFLEN(80);
- const uint16_t ascOffset(61);
- char linebuf[BUFLEN];
- char *cp = linebuf;
- const char HEXDIGIT[] = "0123456789ABCDEF";
-
- // Print out header
- PrintOut ("[%s] %s: @%" PRIXPTR "/%" PRIu32 ":\n", letter, label, (uintptr_t)data, (uint32_t)(length & 0xFFFFFFFF));
-
- // loop over data in steps of 16
- for (cnt = 0; cnt < length; ++cnt) {
- step = cnt % 16;
- // New line?
- if (step == 0) {
- // Yes. Clear line and print address header
- memset(linebuf, ' ', BUFLEN);
- linebuf[60] = limiter;
- linebuf[77] = limiter;
- linebuf[78] = '\n';
- linebuf[BUFLEN - 1] = 0;
- snprintf(linebuf, BUFLEN, " %c %04X: ", limiter, (uint16_t)(cnt & 0xFFFF));
- cp = linebuf + strlen(linebuf);
- // No, but first block of 8 done?
- } else if (step == 8) {
- // Yes, put out additional space
- cp++;
- }
- // Print data byte
- uint8_t c = data[cnt];
- *cp++ = HEXDIGIT[(c >> 4) & 0x0F];
- *cp++ = HEXDIGIT[c & 0x0F];
- *cp++ = ' ';
- if (c >= 32 && c <= 127) linebuf[ascOffset + step] = c;
- else linebuf[ascOffset + step] = '.';
- // Line end?
- if (step == 15) {
- // Yes, print line
- PrintOut ("%s", linebuf);
- }
- }
- // Unfinished line?
- if (length && step != 15) {
- // Yes, print line
- PrintOut ("%s", linebuf);
- }
-}
diff --git a/Software/src/lib/eModbus-eModbus/Logging.h b/Software/src/lib/eModbus-eModbus/Logging.h
deleted file mode 100644
index 88b1cb8b2..000000000
--- a/Software/src/lib/eModbus-eModbus/Logging.h
+++ /dev/null
@@ -1,181 +0,0 @@
-// =================================================================================================
-// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
-// MIT license - see license.md for details
-// =================================================================================================
-
-#ifndef LOG_LEVEL
-#define LOG_LEVEL LOG_LEVEL_ERROR
-#endif
-
-#ifndef LOCAL_LOG_LEVEL
-#define LOCAL_LOG_LEVEL LOG_LEVEL
-#endif
-
-// The following needs to be defined only once
-#ifndef _MODBUS_LOGGING
-#define _MODBUS_LOGGING
-#include "options.h"
-
-#define LOG_LEVEL_NONE (0)
-#define LOG_LEVEL_CRITICAL (1)
-#define LOG_LEVEL_ERROR (2)
-#define LOG_LEVEL_WARNING (3)
-#define LOG_LEVEL_INFO (4)
-#define LOG_LEVEL_DEBUG (5)
-#define LOG_LEVEL_VERBOSE (6)
-
-#define LL_RED "\e[1;31m"
-#define LL_GREEN "\e[32m"
-#define LL_YELLOW "\e[1;33m"
-#define LL_BLUE "\e[34m"
-#define LL_MAGENTA "\e[35m"
-#define LL_CYAN "\e[36m"
-#define LL_NORM "\e[0m"
-
-#define LOG_HEADER(x) "[" #x "] %lu| %-20s [%4d] %s: "
-
-constexpr const char* str_end(const char *str) {
- return *str ? str_end(str + 1) : str;
-}
-
-constexpr bool str_slant(const char *str) {
- return ((*str == '/') || (*str == '\\')) ? true : (*str ? str_slant(str + 1) : false);
-}
-
-constexpr const char* r_slant(const char* str) {
- return ((*str == '/') || (*str == '\\')) ? (str + 1) : r_slant(str - 1);
-}
-constexpr const char* file_name(const char* str) {
- return str_slant(str) ? r_slant(str_end(str)) : str;
-}
-
-#if IS_LINUX
-void logHexDump(const char *letter, const char *label, const uint8_t *data, const size_t length);
-#else
-extern Print *LOGDEVICE;
-void logHexDump(Print *output, const char *letter, const char *label, const uint8_t *data, const size_t length);
-#endif
-extern int MBUlogLvl;
-#endif // _MODBUS_LOGGING
-
-// The remainder may need to be redefined if LOCAL_LOG_LEVEL was set differently before
-#ifdef LOG_LINE_T
-#undef LOG_LINE_C
-#undef LOG_LINE_E
-#undef LOG_LINE_T
-#undef LOG_RAW_C
-#undef LOG_RAW_E
-#undef LOG_RAW_T
-#undef HEX_DUMP_T
-#undef LOG_N
-#undef LOG_C
-#undef LOG_E
-#undef LOG_W
-#undef LOG_I
-#undef LOG_D
-#undef LOG_V
-#undef LOGRAW_N
-#undef LOGRAW_C
-#undef LOGRAW_E
-#undef LOGRAW_W
-#undef LOGRAW_I
-#undef LOGRAW_D
-#undef LOGRAW_V
-#undef HEXDUMP_N
-#undef HEXDUMP_C
-#undef HEXDUMP_E
-#undef HEXDUMP_W
-#undef HEXDUMP_I
-#undef HEXDUMP_D
-#undef HEXDUMP_V
-#endif
-
-// Now we can define the macros based on LOCAL_LOG_LEVEL
-#if IS_LINUX
-#define LOG_LINE_C(level, x, format, ...) if (MBUlogLvl >= level) printf(LL_RED LOG_HEADER(x) format LL_NORM, millis(), file_name(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
-#define LOG_LINE_E(level, x, format, ...) if (MBUlogLvl >= level) printf(LL_YELLOW LOG_HEADER(x) format LL_NORM, millis(), file_name(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
-#define LOG_LINE_T(level, x, format, ...) if (MBUlogLvl >= level) printf(LOG_HEADER(x) format, millis(), file_name(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
-#define LOG_RAW_C(level, x, format, ...) if (MBUlogLvl >= level) printf(LL_RED format LL_NORM, ##__VA_ARGS__)
-#define LOG_RAW_E(level, x, format, ...) if (MBUlogLvl >= level) printf(LL_YELLOW format LL_NORM, ##__VA_ARGS__)
-#define LOG_RAW_T(level, x, format, ...) if (MBUlogLvl >= level) printf(format, ##__VA_ARGS__)
-#define HEX_DUMP_T(x, level, label, address, length) if (MBUlogLvl >= level) logHexDump(#x, label, address, length)
-#else
-#define LOG_LINE_C(level, x, format, ...) if (MBUlogLvl >= level) LOGDEVICE->printf(LL_RED LOG_HEADER(x) format LL_NORM, millis(), file_name(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
-#define LOG_LINE_E(level, x, format, ...) if (MBUlogLvl >= level) LOGDEVICE->printf(LL_YELLOW LOG_HEADER(x) format LL_NORM, millis(), file_name(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
-#define LOG_LINE_T(level, x, format, ...) if (MBUlogLvl >= level) LOGDEVICE->printf(LOG_HEADER(x) format, millis(), file_name(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
-#define LOG_RAW_C(level, x, format, ...) if (MBUlogLvl >= level) LOGDEVICE->printf(LL_RED format LL_NORM, ##__VA_ARGS__)
-#define LOG_RAW_E(level, x, format, ...) if (MBUlogLvl >= level) LOGDEVICE->printf(LL_YELLOW format LL_NORM, ##__VA_ARGS__)
-#define LOG_RAW_T(level, x, format, ...) if (MBUlogLvl >= level) LOGDEVICE->printf(format, ##__VA_ARGS__)
-#define HEX_DUMP_T(x, level, label, address, length) if (MBUlogLvl >= level) logHexDump(LOGDEVICE, #x, label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_NONE
-#define LOG_N(format, ...) LOG_LINE_T(LOG_LEVEL_NONE, N, format, ##__VA_ARGS__)
-#define LOGRAW_N(format, ...) LOG_RAW_T(LOG_LEVEL_NONE, N, format, ##__VA_ARGS__)
-#define HEXDUMP_N(label, address, length) HEX_DUMP_T(N, LOG_LEVEL_NONE, label, address, length)
-#else
-#define LOG_N(format, ...)
-#define LOGRAW_N(format, ...)
-#define HEXDUMP_N(label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_CRITICAL
-#define LOG_C(format, ...) LOG_LINE_C(LOG_LEVEL_CRITICAL, C, format, ##__VA_ARGS__)
-#define LOGRAW_C(format, ...) LOG_RAW_C(LOG_LEVEL_CRITICAL, C, format, ##__VA_ARGS__)
-#define HEXDUMP_C(label, address, length) HEX_DUMP_T(C, LOG_LEVEL_CRITICAL, label, address, length)
-#else
-#define LOG_C(format, ...)
-#define LOGRAW_C(format, ...)
-#define HEXDUMP_C(label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_ERROR
-#define LOG_E(format, ...) LOG_LINE_E(LOG_LEVEL_ERROR, E, format, ##__VA_ARGS__)
-#define LOGRAW_E(format, ...) LOG_RAW_E(LOG_LEVEL_ERROR, E, format, ##__VA_ARGS__)
-#define HEXDUMP_E(label, address, length) HEX_DUMP_T(E, LOG_LEVEL_ERROR, label, address, length)
-#else
-#define LOG_E(format, ...)
-#define LOGRAW_E(format, ...)
-#define HEXDUMP_E(label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_WARNING
-#define LOG_W(format, ...) LOG_LINE_T(LOG_LEVEL_WARNING, W, format, ##__VA_ARGS__)
-#define LOGRAW_W(format, ...) LOG_RAW_T(LOG_LEVEL_WARNING, W, format, ##__VA_ARGS__)
-#define HEXDUMP_W(label, address, length) HEX_DUMP_T(W, LOG_LEVEL_WARNING, label, address, length)
-#else
-#define LOG_W(format, ...)
-#define LOGRAW_W(format, ...)
-#define HEXDUMP_W(label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_INFO
-#define LOG_I(format, ...) LOG_LINE_T(LOG_LEVEL_INFO, I, format, ##__VA_ARGS__)
-#define LOGRAW_I(format, ...) LOG_RAW_T(LOG_LEVEL_INFO, I, format, ##__VA_ARGS__)
-#define HEXDUMP_I(label, address, length) HEX_DUMP_T(I, LOG_LEVEL_INFO, label, address, length)
-#else
-#define LOG_I(format, ...)
-#define LOGRAW_I(format, ...)
-#define HEXDUMP_I(label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_DEBUG
-#define LOG_D(format, ...) LOG_LINE_T(LOG_LEVEL_DEBUG, D, format, ##__VA_ARGS__)
-#define LOGRAW_D(format, ...) LOG_RAW_T(LOG_LEVEL_DEBUG, D, format, ##__VA_ARGS__)
-#define HEXDUMP_D(label, address, length) HEX_DUMP_T(D, LOG_LEVEL_DEBUG, label, address, length)
-#else
-#define LOG_D(format, ...)
-#define LOGRAW_D(format, ...)
-#define HEXDUMP_D(label, address, length)
-#endif
-
-#if LOCAL_LOG_LEVEL >= LOG_LEVEL_VERBOSE
-#define LOG_V(format, ...) LOG_LINE_T(LOG_LEVEL_VERBOSE, V, format, ##__VA_ARGS__)
-#define LOGRAW_V(format, ...) LOG_RAW_T(LOG_LEVEL_VERBOSE, V, format, ##__VA_ARGS__)
-#define HEXDUMP_V(label, address, length) HEX_DUMP_T(V, LOG_LEVEL_VERBOSE, label, address, length)
-#else
-#define LOG_V(format, ...)
-#define LOGRAW_V(format, ...)
-#define HEXDUMP_V(label, address, length)
-#endif
-
diff --git a/Software/src/lib/eModbus-eModbus/ModbusError.h b/Software/src/lib/eModbus-eModbus/ModbusError.h
index 8f4c3a1a9..44a793766 100644
--- a/Software/src/lib/eModbus-eModbus/ModbusError.h
+++ b/Software/src/lib/eModbus-eModbus/ModbusError.h
@@ -28,111 +28,8 @@ class ModbusError {
inline explicit operator Error() { return err; }
inline operator int() { return static_cast(err); }
-#ifndef MINIMAL
- inline explicit operator const char *() { return getText(err); }
-#endif
-
private:
Error err; // The error code
-
-#ifndef MINIMAL
-// Return error as static text
- inline static const char *getText(Error err) {
- switch (err) {
- case SUCCESS : // 0x00,
- return "Success";
- break;
- case ILLEGAL_FUNCTION : // 0x01,
- return "Illegal function code";
- break;
- case ILLEGAL_DATA_ADDRESS : // 0x02,
- return "Illegal data address";
- break;
- case ILLEGAL_DATA_VALUE : // 0x03,
- return "Illegal data value";
- break;
- case SERVER_DEVICE_FAILURE : // 0x04,
- return "Server device failure";
- break;
- case ACKNOWLEDGE : // 0x05,
- return "Acknowledge";
- break;
- case SERVER_DEVICE_BUSY : // 0x06,
- return "Server device busy";
- break;
- case NEGATIVE_ACKNOWLEDGE : // 0x07,
- return "Negative acknowledge";
- break;
- case MEMORY_PARITY_ERROR : // 0x08,
- return "Memory parity error";
- break;
- case GATEWAY_PATH_UNAVAIL : // 0x0A,
- return "Gateway path unavailable";
- break;
- case GATEWAY_TARGET_NO_RESP: // 0x0B,
- return "Gateway target not responding";
- break;
- case TIMEOUT : // 0xE0,
- return "Timeout";
- break;
- case INVALID_SERVER : // 0xE1,
- return "Invalid server";
- break;
- case CRC_ERROR : // 0xE2, // only for Modbus-RTU
- return "CRC check error";
- break;
- case FC_MISMATCH : // 0xE3,
- return "Function code mismatch";
- break;
- case SERVER_ID_MISMATCH : // 0xE4,
- return "Server ID mismatch";
- break;
- case PACKET_LENGTH_ERROR : // 0xE5,
- return "Packet length error";
- break;
- case PARAMETER_COUNT_ERROR : // 0xE6,
- return "Wrong # of parameters";
- break;
- case PARAMETER_LIMIT_ERROR : // 0xE7,
- return "Parameter out of bounds";
- break;
- case REQUEST_QUEUE_FULL : // 0xE8,
- return "Request queue full";
- break;
- case ILLEGAL_IP_OR_PORT : // 0xE9,
- return "Illegal IP or port";
- break;
- case IP_CONNECTION_FAILED : // 0xEA,
- return "IP connection failed";
- break;
- case TCP_HEAD_MISMATCH : // 0xEB,
- return "TCP header mismatch";
- break;
- case EMPTY_MESSAGE : // 0xEC,
- return "Incomplete request";
- break;
- case ASCII_FRAME_ERR : // 0xED,
- return "Invalid ASCII frame";
- break;
- case ASCII_CRC_ERR : // 0xEE,
- return "Invalid ASCII CRC";
- break;
- case ASCII_INVALID_CHAR : // 0xEF,
- return "Invalid ASCII character";
- break;
- case BROADCAST_ERROR : // 0xF0,
- return "Broadcast data invalid";
- break;
- case UNDEFINED_ERROR : // 0xFF // otherwise uncovered communication error
- default:
- return "Unspecified error";
- break;
- }
- return "What?";
- }
-
-#endif
-
};
#endif
diff --git a/Software/src/lib/eModbus-eModbus/ModbusMessage.cpp b/Software/src/lib/eModbus-eModbus/ModbusMessage.cpp
index 7e63bdb95..9da0a30e6 100644
--- a/Software/src/lib/eModbus-eModbus/ModbusMessage.cpp
+++ b/Software/src/lib/eModbus-eModbus/ModbusMessage.cpp
@@ -3,9 +3,6 @@
// MIT license - see license.md for details
// =================================================================================================
#include "ModbusMessage.h"
-#undef LOCAL_LOG_LEVEL
-// #define LOCAL_LOG_LEVEL LOG_LEVEL_ERROR
-#include "Logging.h"
#include
// Default Constructor - takes optional size of MM_data to allocate memory
@@ -87,7 +84,6 @@ uint8_t ModbusMessage::operator[](uint16_t index) const {
if (index < MM_data.size()) {
return MM_data[index];
}
- LOG_W("Index %d out of bounds (>=%d).\n", index, MM_data.size());
return 0;
}
// Resize internal MM_data
@@ -173,7 +169,6 @@ uint8_t ModbusMessage::determineFloatOrder() {
// This will only work for 32bit floats, so check that
if (floatSize != 4) {
// OOPS! we cannot proceed.
- LOG_E("Oops. float seems to be %d bytes wide instead of 4.\n", floatSize);
return 0;
}
@@ -198,11 +193,9 @@ uint8_t ModbusMessage::determineFloatOrder() {
// All bytes found?
if (matches != floatSize) {
// No! There is something fishy...
- LOG_E("Unable to determine float byte order (matched=%d of %d)\n", matches, floatSize);
floatOrder[0] = 0xFF;
return 0;
} else {
- HEXDUMP_V("floatOrder", floatOrder, floatSize);
}
}
return floatSize;
@@ -217,7 +210,6 @@ uint8_t ModbusMessage::determineDoubleOrder() {
// This will only work for 64bit doubles, so check that
if (doubleSize != 8) {
// OOPS! we cannot proceed.
- LOG_E("Oops. double seems to be %d bytes wide instead of 8.\n", doubleSize);
return 0;
}
@@ -242,11 +234,9 @@ uint8_t ModbusMessage::determineDoubleOrder() {
// All bytes found?
if (matches != doubleSize) {
// No! There is something fishy...
- LOG_E("Unable to determine double byte order (matched=%d of %d)\n", matches, doubleSize);
doubleOrder[0] = 0xFF;
return 0;
} else {
- HEXDUMP_V("doubleOrder", doubleOrder, doubleSize);
}
}
return doubleSize;
@@ -255,7 +245,6 @@ uint8_t ModbusMessage::determineDoubleOrder() {
// swapFloat() and swapDouble() will re-order the bytes of a float or double value
// according a user-given pattern
float ModbusMessage::swapFloat(float& f, int swapRule) {
- LOG_V("swap float, swapRule=%02X\n", swapRule);
// Make a byte pointer to the given float
uint8_t *src = (uint8_t *)&f;
// Define a "work bench" float and byte pointer to it
@@ -265,7 +254,6 @@ float ModbusMessage::swapFloat(float& f, int swapRule) {
for (uint8_t i = 0; i < sizeof(float); ++i) {
// Get i-th byte from the spot the swap table tells
// (only the first 4 tables are valid for floats)
- LOG_V("dst[%d] = src[%d]\n", i, swapTables[swapRule & 0x03][i]);
dst[i] = src[swapTables[swapRule & 0x03][i]];
// Does the swar rule require nibble swaps?
if (swapRule & 0x08) {
@@ -280,7 +268,6 @@ float ModbusMessage::swapFloat(float& f, int swapRule) {
}
double ModbusMessage::swapDouble(double& f, int swapRule) {
- LOG_V("swap double, swapRule=%02X\n", swapRule);
// Make a byte pointer to the given double
uint8_t *src = (uint8_t *)&f;
// Define a "work bench" double and byte pointer to it
@@ -289,7 +276,6 @@ double ModbusMessage::swapDouble(double& f, int swapRule) {
// Loop over all bytes of a double
for (uint8_t i = 0; i < sizeof(double); ++i) {
// Get i-th byte from the spot the swap table tells
- LOG_V("dst[%d] = src[%d]\n", i, swapTables[swapRule & 0x07][i]);
dst[i] = src[swapTables[swapRule & 0x07][i]];
// Does the swar rule require nibble swaps?
if (swapRule & 0x08) {
@@ -312,8 +298,6 @@ uint16_t ModbusMessage::add(vector v) {
// values will be added in IEEE754 byte sequence (MSB first)
uint16_t ModbusMessage::add(float v, int swapRule) {
// First check if we need to determine byte order
- LOG_V("add float, swapRule=%02X\n", swapRule);
- HEXDUMP_V("float", (uint8_t *)&v, sizeof(float));
if (determineFloatOrder()) {
// If we get here, the floatOrder is known
float interim = 0;
@@ -323,13 +307,11 @@ uint16_t ModbusMessage::add(float v, int swapRule) {
for (uint8_t i = 0; i < sizeof(float); ++i) {
dst[i] = src[floatOrder[i]];
}
- HEXDUMP_V("normalized float", (uint8_t *)&interim, sizeof(float));
// Do we need to apply a swap rule?
if (swapRule & 0x0B) {
// Yes, so do it.
swapFloat(interim, swapRule & 0x0B);
}
- HEXDUMP_V("swapped float", (uint8_t *)&interim, sizeof(float));
// Put out the bytes of v in normalized (and swapped) sequence
for (uint8_t i = 0; i < sizeof(float); ++i) {
MM_data.push_back(dst[i]);
@@ -341,8 +323,6 @@ uint16_t ModbusMessage::add(float v, int swapRule) {
uint16_t ModbusMessage::add(double v, int swapRule) {
// First check if we need to determine byte order
- LOG_V("add double, swapRule=%02X\n", swapRule);
- HEXDUMP_V("double", (uint8_t *)&v, sizeof(double));
if (determineDoubleOrder()) {
// If we get here, the doubleOrder is known
double interim = 0;
@@ -352,13 +332,11 @@ uint16_t ModbusMessage::add(double v, int swapRule) {
for (uint8_t i = 0; i < sizeof(double); ++i) {
dst[i] = src[doubleOrder[i]];
}
- HEXDUMP_V("normalized double", (uint8_t *)&interim, sizeof(double));
// Do we need to apply a swap rule?
if (swapRule & 0x0F) {
// Yes, so do it.
swapDouble(interim, swapRule & 0x0F);
}
- HEXDUMP_V("swapped double", (uint8_t *)&interim, sizeof(double));
// Put out the bytes of v in normalized (and swapped) sequence
for (uint8_t i = 0; i < sizeof(double); ++i) {
MM_data.push_back(dst[i]);
@@ -381,13 +359,11 @@ uint16_t ModbusMessage::get(uint16_t index, float& v, int swapRule) const {
for (uint8_t i = 0; i < sizeof(float); ++i) {
bytes[i] = MM_data[index + floatOrder[i]];
}
- HEXDUMP_V("got float", (uint8_t *)&v, sizeof(float));
// Do we need to apply a swap rule?
if (swapRule & 0x0B) {
// Yes, so do it.
swapFloat(v, swapRule & 0x0B);
}
- HEXDUMP_V("got float swapped", (uint8_t *)&v, sizeof(float));
index += sizeof(float);
}
}
@@ -406,13 +382,11 @@ uint16_t ModbusMessage::get(uint16_t index, double& v, int swapRule) const {
for (uint8_t i = 0; i < sizeof(double); ++i) {
bytes[i] = MM_data[index + doubleOrder[i]];
}
- HEXDUMP_V("got double", (uint8_t *)&v, sizeof(double));
// Do we need to apply a swap rule?
if (swapRule & 0x0F) {
// Yes, so do it.
swapDouble(v, swapRule & 0x0F);
}
- HEXDUMP_V("got double swapped", (uint8_t *)&v, sizeof(double));
index += sizeof(double);
}
}
@@ -442,7 +416,6 @@ Error ModbusMessage::checkServerFC(uint8_t serverID, uint8_t functionCode) {
// 1. no additional parameter (FCs 0x07, 0x0b, 0x0c, 0x11)
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode) {
- LOG_V("Check data #1\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -456,7 +429,6 @@ Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode) {
// 2. one uint16_t parameter (FC 0x18)
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t p1) {
- LOG_V("Check data #2\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -470,7 +442,6 @@ Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t
// 3. two uint16_t parameters (FC 0x01, 0x02, 0x03, 0x04, 0x05, 0x06)
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t p1, uint16_t p2) {
- LOG_V("Check data #3\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -498,7 +469,6 @@ Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t
// 4. three uint16_t parameters (FC 0x16)
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t p1, uint16_t p2, uint16_t p3) {
- LOG_V("Check data #4\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -512,7 +482,6 @@ Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t
// 5. two uint16_t parameters, a uint8_t length byte and a uint16_t* pointer to array of words (FC 0x10)
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t p1, uint16_t p2, uint8_t count, uint16_t *arrayOfWords) {
- LOG_V("Check data #5\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -529,7 +498,6 @@ Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t
// 6. two uint16_t parameters, a uint8_t length byte and a uint16_t* pointer to array of bytes (FC 0x0f)
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t p1, uint16_t p2, uint8_t count, uint8_t *arrayOfBytes) {
- LOG_V("Check data #6\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -546,7 +514,6 @@ Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t
// 7. generic constructor for preformatted data ==> count is counting bytes!
Error ModbusMessage::checkData(uint8_t serverID, uint8_t functionCode, uint16_t count, uint8_t *arrayOfBytes) {
- LOG_V("Check data #7\n");
Error returnCode = checkServerFC(serverID, functionCode);
if (returnCode == SUCCESS)
{
@@ -694,7 +661,6 @@ Error ModbusMessage::setError(uint8_t serverID, uint8_t functionCode, Error erro
// Error output in case a message constructor will fail
void ModbusMessage::printError(const char *file, int lineNo, Error e, uint8_t serverID, uint8_t functionCode) {
- LOG_E("(%s, line %d) Error in constructor: %02X - %s (%02X/%02X)\n", file_name(file), lineNo, e, (const char *)(ModbusError(e)), serverID, functionCode);
}
uint8_t ModbusMessage::floatOrder[] = { 0xFF };
diff --git a/Software/src/lib/eModbus-eModbus/ModbusServer.cpp b/Software/src/lib/eModbus-eModbus/ModbusServer.cpp
index 90cdcda49..9826b3644 100644
--- a/Software/src/lib/eModbus-eModbus/ModbusServer.cpp
+++ b/Software/src/lib/eModbus-eModbus/ModbusServer.cpp
@@ -5,21 +5,15 @@
#include
#include "ModbusServer.h"
-#undef LOCAL_LOG_LEVEL
-// #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
-#include "Logging.h"
-
// registerWorker: register a worker function for a certain serverID/FC combination
// If there is one already, it will be overwritten!
void ModbusServer::registerWorker(uint8_t serverID, uint8_t functionCode, MBSworker worker) {
workerMap[serverID][functionCode] = worker;
- LOG_D("Registered worker for %02X/%02X\n", serverID, functionCode);
}
// getWorker: if a worker function is registered, return its address, nullptr otherwise
MBSworker ModbusServer::getWorker(uint8_t serverID, uint8_t functionCode) {
bool serverFound = false;
- LOG_D("Need worker for %02X-%02X : ", serverID, functionCode);
// Search the FC map associated with the serverID
auto svmap = workerMap.find(serverID);
// Is there one?
@@ -52,12 +46,10 @@ MBSworker ModbusServer::getWorker(uint8_t serverID, uint8_t functionCode) {
}
if (functionCodeFound) {
// Yes. Return the function pointer for it.
- LOGRAW_D("Worker found for %02X/%02X\n", serverID, functionCode);
return fcmap->second;
}
}
// No matching function pointer found
- LOGRAW_D("No matching worker found\n");
return nullptr;
}
@@ -80,7 +72,6 @@ bool ModbusServer::unregisterWorker(uint8_t serverID, uint8_t functionCode) {
numEntries = workerMap.erase(serverID);
}
}
- LOG_D("Removed %d worker entries for %d/%d\n", numEntries, serverID, functionCode);
return (numEntries ? true : false);
}
@@ -136,18 +127,13 @@ ModbusMessage ModbusServer::localRequest(ModbusMessage msg) {
ModbusMessage m;
uint8_t serverID = msg.getServerID();
uint8_t functionCode = msg.getFunctionCode();
- LOG_D("Local request for %02X/%02X\n", serverID, functionCode);
- HEXDUMP_V("Request", msg.data(), msg.size());
messageCount++;
// Try to get a worker for the request
MBSworker worker = getWorker(serverID, functionCode);
// Did we get one?
if (worker != nullptr) {
// Yes. call it and return the response
- LOG_D("Call worker\n");
m = worker(msg);
- LOG_D("Worker responded\n");
- HEXDUMP_V("Worker response", m.data(), m.size());
// Process Response. Is it one of the predefined types?
if (m[0] == 0xFF && (m[1] == 0xF0 || m[1] == 0xF1)) {
// Yes. Check it
@@ -163,13 +149,11 @@ ModbusMessage ModbusServer::localRequest(ModbusMessage msg) {
break;
}
}
- HEXDUMP_V("Response", m.data(), m.size());
if (m.getError() != SUCCESS) {
errorCount++;
}
return m;
} else {
- LOG_D("No worker found. Error response.\n");
// No. Is there at least one worker for the serverID?
if (isServerFor(serverID)) {
// Yes. Respond with "illegal function code"
@@ -182,7 +166,6 @@ ModbusMessage ModbusServer::localRequest(ModbusMessage msg) {
return m;
}
// We should never get here...
- LOG_C("Internal problem: should not get here!\n");
m.setError(serverID, functionCode, UNDEFINED_ERROR);
errorCount++;
return m;
@@ -200,10 +183,7 @@ ModbusServer::~ModbusServer() {
// listServer: Print out all mapped server/FC combinations
void ModbusServer::listServer() {
for (auto it = workerMap.begin(); it != workerMap.end(); ++it) {
- LOG_N("Server %3d: ", it->first);
for (auto it2 = it->second.begin(); it2 != it->second.end(); it2++) {
- LOGRAW_N(" %02X", it2->first);
}
- LOGRAW_N("\n");
}
}
diff --git a/Software/src/lib/eModbus-eModbus/ModbusServerEthernet.h b/Software/src/lib/eModbus-eModbus/ModbusServerEthernet.h
deleted file mode 100644
index c520c9f77..000000000
--- a/Software/src/lib/eModbus-eModbus/ModbusServerEthernet.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// =================================================================================================
-// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
-// MIT license - see license.md for details
-// =================================================================================================
-#ifndef _MODBUS_SERVER_ETHERNET_H
-#define _MODBUS_SERVER_ETHERNET_H
-#include "options.h"
-#if HAS_ETHERNET == 1
-#include
-#include
-
-#undef SERVER_END
-#define SERVER_END // NIL for Ethernet
-
-// Create own non-virtual EthernetServer class
-class EthernetServerEM : public EthernetServer {
-public:
- explicit EthernetServerEM(uint16_t port) : EthernetServer(port) { }
- void begin(uint16_t port = 0) override { }
-};
-
-#include "ModbusServerTCPtemp.h"
-using ModbusServerEthernet = ModbusServerTCP;
-#endif
-
-#endif
diff --git a/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp b/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp
index f7e19eae2..082c2b706 100644
--- a/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp
+++ b/Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp
@@ -6,9 +6,6 @@
#if HAS_FREERTOS
-#undef LOG_LEVEL_LOCAL
-#include "Logging.h"
-
// Init number of created ModbusServerRTU objects
uint8_t ModbusServerRTU::instanceCounter = 0;
@@ -96,14 +93,12 @@ void ModbusServerRTU::doBegin(uint32_t baudRate, int coreID, uint32_t userInterv
// Start task to handle the client
xTaskCreatePinnedToCore((TaskFunction_t)&serve, taskName, SERVER_TASK_STACK, this, 8, &serverTask, coreID >= 0 ? coreID : tskNO_AFFINITY);
- LOG_D("Server task %d started. Interval=%d\n", (uint32_t)serverTask, MSRinterval);
}
// end: kill server task
void ModbusServerRTU::end() {
if (serverTask != nullptr) {
vTaskDelete(serverTask);
- LOG_D("Server task %d stopped.\n", (uint32_t)serverTask);
serverTask = nullptr;
}
}
@@ -112,13 +107,11 @@ void ModbusServerRTU::end() {
void ModbusServerRTU::useModbusASCII(unsigned long timeout) {
MSRuseASCII = true;
serverTimeout = timeout; // Set timeout to ASCII's value
- LOG_D("Protocol mode: ASCII\n");
}
// Toggle protocol to ModbusRTU
void ModbusServerRTU::useModbusRTU() {
MSRuseASCII = false;
- LOG_D("Protocol mode: RTU\n");
}
// Inquire protocol mode
@@ -135,14 +128,12 @@ void ModbusServerRTU::setModbusTimeout(unsigned long timeout)
// Toggle skipping of leading 0x00 byte
void ModbusServerRTU::skipLeading0x00(bool onOff) {
MSRskipLeadingZeroByte = onOff;
- LOG_D("Skip leading 0x00 mode = %s\n", onOff ? "ON" : "OFF");
}
// Special case: worker to react on broadcast requests
void ModbusServerRTU::registerBroadcastWorker(MSRlistener worker) {
// If there is one already, it will be overwritten!
listener = worker;
- LOG_D("Registered worker for broadcast requests\n");
}
// Even more special: register a sniffer worker
@@ -151,7 +142,6 @@ void ModbusServerRTU::registerSniffer(MSRlistener worker) {
// This holds true for the broadcast worker as well,
// so a sniffer never will do else but to sniff on broadcast requests!
sniffer = worker;
- LOG_D("Registered sniffer\n");
}
// serve: loop until killed and receive messages from the RTU interface
@@ -181,7 +171,6 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
// Request longer than 1 byte (that will signal an error in receive())?
if (request.size() > 1) {
- LOG_D("Request received.\n");
// Yes.
// Do we have a sniffer listening?
@@ -191,12 +180,10 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
}
// Is it a broadcast?
if (request[0] == 0) {
- LOG_D("Broadcast!\n");
// Yes. Do we have a listener?
if (myServer->listener) {
// Yes. call it
myServer->listener(request);
- LOG_D("Broadcast served.\n");
}
// else we simply ignore it
} else {
@@ -204,16 +191,13 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
// Do we have a callback function registered for it?
MBSworker callBack = myServer->getWorker(request[0], request[1]);
if (callBack) {
- LOG_D("Callback found.\n");
// Yes, we do. Count the message
{
LOCK_GUARD(cntLock, myServer->m);
myServer->messageCount++;
}
// Get the user's response
- LOG_D("Callback called.\n");
m = callBack(request);
- HEXDUMP_V("Callback response", m.data(), m.size());
// Process Response. Is it one of the predefined types?
if (m[0] == 0xFF && (m[1] == 0xF0 || m[1] == 0xF1)) {
@@ -248,7 +232,6 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
if (response.size() >= 3) {
// Yes. send it back.
RTUutils::send(*(myServer->MSRserial), myServer->MSRlastMicros, myServer->MSRinterval, myServer->MRTSrts, response, myServer->MSRuseASCII);
- LOG_D("Response sent.\n");
// Count it, in case we had an error response
if (response.getError() != SUCCESS) {
LOCK_GUARD(errorCntLock, myServer->m);
@@ -262,7 +245,6 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
if (request[0] != TIMEOUT) {
// Any other error could be important for debugging, so print it
ModbusError me((Error)request[0]);
- LOG_E("RTU receive: %02X - %s\n", (int)me, (const char *)me);
}
}
// Give scheduler room to breathe
diff --git a/Software/src/lib/eModbus-eModbus/RTUutils.cpp b/Software/src/lib/eModbus-eModbus/RTUutils.cpp
index fc4eaadf7..e13ab2d24 100644
--- a/Software/src/lib/eModbus-eModbus/RTUutils.cpp
+++ b/Software/src/lib/eModbus-eModbus/RTUutils.cpp
@@ -6,55 +6,52 @@
#if HAS_FREERTOS
#include "ModbusMessage.h"
#include "RTUutils.h"
-#undef LOCAL_LOG_LEVEL
-// #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
-#include "Logging.h"
-// calcCRC: calculate Modbus CRC16 on a given array of bytes
-uint16_t RTUutils::calcCRC(const uint8_t *data, uint16_t len) {
// CRC16 pre-calculated tables
- const uint8_t crcHiTable[] = {
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
- 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
- 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
- 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
- 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
- 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
- 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
- 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
- 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
- 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
- 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
- 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
- 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
- 0x40
- };
-
- const uint8_t crcLoTable[] = {
- 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
- 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
- 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
- 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
- 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
- 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
- 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
- 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
- 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
- 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
- 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
- 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
- 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
- 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
- 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
- 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
- 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
- 0x40
- };
+static constexpr uint8_t crcHiTable[] = {
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
+ 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
+ 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
+ 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
+ 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
+ 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
+ 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
+ 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
+ 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
+ 0x40
+};
+
+static constexpr uint8_t crcLoTable[] = {
+ 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
+ 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
+ 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
+ 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
+ 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
+ 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
+ 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
+ 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
+ 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
+ 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
+ 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
+ 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
+ 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
+ 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
+ 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
+ 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
+ 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
+ 0x40
+};
+// calcCRC: calculate Modbus CRC16 on a given array of bytes
+uint16_t RTUutils::calcCRC(const uint8_t *data, uint16_t len) {
uint8_t crcHi = 0xFF;
uint8_t crcLo = 0xFF;
@@ -108,7 +105,6 @@ uint32_t RTUutils::calculateInterval(uint32_t baudRate) {
// silent interval is at least 3.5x character time
interval = 35000000UL / baudRate; // 3.5 * 10 bits * 1000 ยตs * 1000 ms / baud
if (interval < 1750) interval = 1750; // lower limit according to Modbus RTU standard
- LOG_V("Calc interval(%u)=%u\n", baudRate, interval);
return interval;
}
@@ -169,8 +165,6 @@ void RTUutils::send(Stream& serial, unsigned long& lastMicros, uint32_t interval
rts(LOW);
}
- HEXDUMP_D("Sent packet", data, len);
-
// Mark end-of-message time for next interval
lastMicros = micros();
}
@@ -258,7 +252,6 @@ ModbusMessage RTUutils::receive(uint8_t caller, Stream& serial, uint32_t timeout
// Are we past the interval gap?
if (micros() - lastMicros >= interval) {
// Yes, terminate reading
- LOG_V("%c/%ldus without data after %u\n", (const char)caller, micros() - lastMicros, bufferPtr);
state = DATA_READ;
break;
}
@@ -268,8 +261,6 @@ ModbusMessage RTUutils::receive(uint8_t caller, Stream& serial, uint32_t timeout
// DATA_READ: successfully gathered some data. Prepare return object.
case DATA_READ:
// Did we get a sensible buffer length?
- LOG_V("%c/", (const char)caller);
- HEXDUMP_V("Raw buffer received", buffer, bufferPtr);
if (bufferPtr >= 4)
{
// Yes. Check CRC
@@ -389,8 +380,6 @@ ModbusMessage RTUutils::receive(uint8_t caller, Stream& serial, uint32_t timeout
case A_WAIT_LEAD_OUT:
if (b == 0xF2) {
// Lead-out byte 2 received. Transfer buffer to returned message
- LOG_V("%c/", (const char)caller);
- HEXDUMP_V("Raw buffer received", buffer, bufferPtr);
// Did we get a sensible buffer length?
if (bufferPtr >= 3)
{
@@ -433,9 +422,6 @@ ModbusMessage RTUutils::receive(uint8_t caller, Stream& serial, uint32_t timeout
// Deallocate buffer
delete[] buffer;
- LOG_D("%c/", (const char)caller);
- HEXDUMP_D("Received packet", rv.data(), rv.size());
-
return rv;
}
diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp
index e13fa7649..13acec0cc 100644
--- a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp
+++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp
@@ -59,7 +59,6 @@ static uint8_t _readBuffer[MAX_PAYLOAD_SIZE];
static bool _start_asyncsock_task(void)
{
if (!_asyncsock_service_task_handle) {
- log_i("Creating asyncTcpSock task running in core %d (-1 for any available core)...", CONFIG_ASYNC_TCP_RUNNING_CORE);
xTaskCreateUniversal(
_asynctcpsock_task,
CONFIG_ASYNC_TCP_TASK_NAME,
@@ -133,20 +132,10 @@ void _asynctcpsock_task(void *)
}
}
for (it = sockList.begin(); it != sockList.end(); it++) {
-#if CONFIG_ASYNC_TCP_USE_WDT
- if (esp_task_wdt_add(NULL) != ESP_OK) {
- log_e("Failed to add async task to WDT");
- }
-#endif
if ((*it)->_sockIsWriteable()) {
(*it)->_sock_lastactivity = millis();
nActive++;
}
-#if CONFIG_ASYNC_TCP_USE_WDT
- if (esp_task_wdt_delete(NULL) != ESP_OK) {
- log_e("Failed to remove loop task from WDT");
- }
-#endif
}
sockList.clear();
@@ -159,19 +148,9 @@ void _asynctcpsock_task(void *)
}
}
for (it = sockList.begin(); it != sockList.end(); it++) {
-#if CONFIG_ASYNC_TCP_USE_WDT
- if (esp_task_wdt_add(NULL) != ESP_OK) {
- log_e("Failed to add async task to WDT");
- }
-#endif
(*it)->_sock_lastactivity = millis();
(*it)->_sockIsReadable();
nActive++;
-#if CONFIG_ASYNC_TCP_USE_WDT
- if (esp_task_wdt_delete(NULL) != ESP_OK) {
- log_e("Failed to remove loop task from WDT");
- }
-#endif
}
sockList.clear();
}
@@ -184,18 +163,8 @@ void _asynctcpsock_task(void *)
}
}
for (it = sockList.begin(); it != sockList.end(); it++) {
-#if CONFIG_ASYNC_TCP_USE_WDT
- if(esp_task_wdt_add(NULL) != ESP_OK){
- log_e("Failed to add async task to WDT");
- }
-#endif
(*it)->_isdnsfinished = false;
(*it)->_sockDelayedConnect();
-#if CONFIG_ASYNC_TCP_USE_WDT
- if(esp_task_wdt_delete(NULL) != ESP_OK){
- log_e("Failed to remove loop task from WDT");
- }
-#endif
}
sockList.clear();
@@ -213,17 +182,7 @@ void _asynctcpsock_task(void *)
// Run activity poll on all pollable sockets
for (it = sockList.begin(); it != sockList.end(); it++) {
-#if CONFIG_ASYNC_TCP_USE_WDT
- if(esp_task_wdt_add(NULL) != ESP_OK){
- log_e("Failed to add async task to WDT");
- }
-#endif
(*it)->_sockPoll();
-#if CONFIG_ASYNC_TCP_USE_WDT
- if(esp_task_wdt_delete(NULL) != ESP_OK){
- log_e("Failed to remove loop task from WDT");
- }
-#endif
}
sockList.clear();
@@ -282,19 +241,6 @@ AsyncClient::AsyncClient(int sockfd)
, _rx_since_timeout(0)
, _ack_timeout(ASYNC_MAX_ACK_TIME)
, _connect_port(0)
-#if ASYNC_TCP_SSL_ENABLED
-, _root_ca_len(0)
-, _root_ca(NULL)
-, _cli_cert_len(0)
-, _cli_cert(NULL)
-, _cli_key_len(0)
-, _cli_key(NULL)
-, _secure(false)
-, _handshake_done(true)
-, _psk_ident(0)
-, _psk(0)
-, _sslctx(NULL)
-#endif // ASYNC_TCP_SSL_ENABLED
, _writeSpaceRemaining(TCP_SND_BUF)
, _conn_state(0)
{
@@ -343,22 +289,9 @@ void AsyncClient::setNoDelay(bool nodelay){
int flag = nodelay;
int res = setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
if(res < 0) {
- log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
}
}
-bool AsyncClient::getNoDelay(){
- if (_socket == -1) return false;
-
- int flag = 0;
- socklen_t size = sizeof(int);
- int res = getsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, &size);
- if(res < 0) {
- log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno));
- }
- return flag;
-}
-
/*
* Callback Setters
* */
@@ -480,31 +413,18 @@ uint16_t AsyncClient::localPort() {
return getLocalPort();
}
-
-#if ASYNC_TCP_SSL_ENABLED
-bool AsyncClient::connect(IPAddress ip, uint16_t port, bool secure)
-#else
bool AsyncClient::connect(IPAddress ip, uint16_t port)
-#endif // ASYNC_TCP_SSL_ENABLED
{
if (_socket != -1) {
- log_w("already connected, state %d", _conn_state);
return false;
}
if(!_start_asyncsock_task()){
- log_e("failed to start task");
return false;
}
-#if ASYNC_TCP_SSL_ENABLED
- _secure = secure;
- _handshake_done = !secure;
-#endif // ASYNC_TCP_SSL_ENABLED
-
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
- log_e("socket: %d", errno);
return false;
}
int r = fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK );
@@ -516,18 +436,8 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port)
memcpy(&(serveraddr.sin_addr.s_addr), &ip_addr, 4);
serveraddr.sin_port = htons(port);
-#ifdef EINPROGRESS
- #if EINPROGRESS != 119
- #error EINPROGRESS invalid
- #endif
-#endif
-
- //Serial.printf("DEBUG: connect to %08x port %d using IP... ", ip_addr, port);
errno = 0; r = ::connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
- //Serial.printf("r=%d errno=%d\r\n", r, errno);
if (r < 0 && errno != EINPROGRESS) {
- //Serial.println("\t(connect failed)");
- log_e("connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno));
::close(sockfd);
return false;
}
@@ -540,45 +450,26 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port)
xSemaphoreGiveRecursive(_asyncsock_mutex);
// Socket is now connecting. Should become writable in asyncTcpSock task
- //Serial.printf("\twaiting for connect finished on socket: %d\r\n", _socket);
return true;
}
void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
-#if ASYNC_TCP_SSL_ENABLED
-bool AsyncClient::connect(const char* host, uint16_t port, bool secure){
-#else
+
bool AsyncClient::connect(const char* host, uint16_t port){
-#endif // ASYNC_TCP_SSL_ENABLED
ip_addr_t addr;
if(!_start_asyncsock_task()){
- log_e("failed to start task");
return false;
}
- log_v("connect to %s port %d using DNS...", host, port);
err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcpsock_dns_found, this);
if(err == ERR_OK) {
- log_v("\taddr resolved as %08x, connecting...", addr.u_addr.ip4.addr);
-#if ASYNC_TCP_SSL_ENABLED
- _hostname = host;
- return connect(IPAddress(addr.u_addr.ip4.addr), port, secure);
-#else
return connect(IPAddress(addr.u_addr.ip4.addr), port);
-#endif // ASYNC_TCP_SSL_ENABLED
} else if(err == ERR_INPROGRESS) {
- log_v("\twaiting for DNS resolution");
_conn_state = 1;
_connect_port = port;
-#if ASYNC_TCP_SSL_ENABLED
- _hostname = host;
- _secure = secure;
- _handshake_done = !secure;
-#endif // ASYNC_TCP_SSL_ENABLED
return true;
}
- log_e("error: %d", err);
return false;
}
@@ -604,11 +495,7 @@ void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg)
void AsyncClient::_sockDelayedConnect(void)
{
if (_connect_addr.u_addr.ip4.addr) {
-#if ASYNC_TCP_SSL_ENABLED
- connect(IPAddress(_connect_addr.u_addr.ip4.addr), _connect_port, _secure);
-#else
connect(IPAddress(_connect_addr.u_addr.ip4.addr), _connect_port);
-#endif
} else {
_conn_state = 0;
if(_error_cb) {
@@ -620,32 +507,6 @@ void AsyncClient::_sockDelayedConnect(void)
}
}
-#if ASYNC_TCP_SSL_ENABLED
-int AsyncClient::_runSSLHandshakeLoop(void)
-{
- int res = 0;
-
- while (!_handshake_done) {
- res = _sslctx->runSSLHandshake();
- if (res == 0) {
- // Handshake successful
- _handshake_done = true;
- } else if (ASYNCTCP_TLS_CAN_RETRY(res)) {
- // Ran out of readable data or writable space on socket, must continue later
- break;
- } else {
- // SSL handshake for AsyncTCP does not inform SSL errors
- log_e("TLS setup failed with error %d, closing socket...", res);
- _close();
- // _sslctx should be NULL after this
- break;
- }
- }
-
- return res;
-}
-#endif
-
bool AsyncClient::_sockIsWriteable(void)
{
int res;
@@ -668,46 +529,6 @@ bool AsyncClient::_sockIsWriteable(void)
} else if (sockerr != 0) {
_error(sockerr);
} else {
-#if ASYNC_TCP_SSL_ENABLED
- if (_secure) {
- int res = 0;
-
- if (_sslctx == NULL) {
- String remIP_str = remoteIP().toString();
- const char * host_or_ip = _hostname.isEmpty()
- ? remIP_str.c_str()
- : _hostname.c_str();
-
- _sslctx = new AsyncTCP_TLS_Context();
- if (_root_ca != NULL) {
- res = _sslctx->startSSLClient(_socket, host_or_ip,
- (const unsigned char *)_root_ca, _root_ca_len,
- (const unsigned char *)_cli_cert, _cli_cert_len,
- (const unsigned char *)_cli_key, _cli_key_len);
- } else if (_psk_ident != NULL) {
- res = _sslctx->startSSLClient(_socket, host_or_ip,
- _psk_ident, _psk);
- } else {
- res = _sslctx->startSSLClientInsecure(_socket, host_or_ip);
- }
-
- if (res != 0) {
- // SSL setup for AsyncTCP does not inform SSL errors
- log_e("TLS setup failed with error %d, closing socket...", res);
- _close();
- // _sslctx should be NULL after this
- }
- }
-
- // _handshake_done is set to FALSE on connect() if encrypted connection
- if (_sslctx != NULL && res == 0) res = _runSSLHandshakeLoop();
-
- if (!_handshake_done) return ASYNCTCP_TLS_CAN_RETRY(res);
-
- // Fallthrough to ordinary successful connection
- }
-#endif
-
// Socket is now fully connected
_conn_state = 4;
activity = true;
@@ -756,26 +577,7 @@ bool AsyncClient::_flushWriteQueue(void)
size_t n = it->length - it->written;
errno = 0;
ssize_t r;
-
-#if ASYNC_TCP_SSL_ENABLED
- if (_sslctx != NULL) {
- r = _sslctx->write(p, n);
- if (ASYNCTCP_TLS_CAN_RETRY(r)) {
- r = -1;
- errno = EAGAIN;
- } else if (ASYNCTCP_TLS_EOF(r)) {
- r = -1;
- errno = EPIPE;
- } else if (r < 0) {
- if (errno == 0) errno = EIO;
- }
- } else {
-#endif
- r = lwip_write(_socket, p, n);
-#if ASYNC_TCP_SSL_ENABLED
- }
-#endif
-
+ r = lwip_write(_socket, p, n);
if (r >= 0) {
// Written some data into the socket
it->written += r;
@@ -787,14 +589,13 @@ bool AsyncClient::_flushWriteQueue(void)
if (it->owned) ::free(it->data);
it->data = NULL;
}
- } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ } else if (errno == EAGAIN) {
// Socket is full, could not write anything
keep_writing = false;
} else {
// A write error happened that should be reported
it->write_errno = errno;
keep_writing = false;
- log_e("socket %d lwip_write() failed errno=%d", _socket, it->write_errno);
}
} while (keep_writing && it->written < it->length);
}
@@ -851,34 +652,7 @@ void AsyncClient::_sockIsReadable(void)
errno = 0;
ssize_t r;
-#if ASYNC_TCP_SSL_ENABLED
- if (_sslctx != NULL) {
- if (!_handshake_done) {
- // Handshake process has stopped for want of data, must be
- // continued here for connection to complete.
- _runSSLHandshakeLoop();
-
- // If handshake was successful, this will be recognized when the socket
- // next becomes writable. No other read operation should be done here.
- return;
- } else {
- r = _sslctx->read(_readBuffer, MAX_PAYLOAD_SIZE);
- if (ASYNCTCP_TLS_CAN_RETRY(r)) {
- r = -1;
- errno = EAGAIN;
- } else if (ASYNCTCP_TLS_EOF(r)) {
- // Simulate "successful" end-of-stream condition
- r = 0;
- } else if (r < 0) {
- if (errno == 0) errno = EIO;
- }
- }
- } else {
-#endif
- r = lwip_read(_socket, _readBuffer, MAX_PAYLOAD_SIZE);
-#if ASYNC_TCP_SSL_ENABLED
- }
-#endif
+ r = lwip_read(_socket, _readBuffer, MAX_PAYLOAD_SIZE);
if (r > 0) {
if(_recv_cb) {
@@ -888,7 +662,7 @@ void AsyncClient::_sockIsReadable(void)
// A successful read of 0 bytes indicates remote side closed connection
_close();
} else if (r < 0) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ if (errno == EAGAIN) {
// Do nothing, will try later
} else {
_error(errno);
@@ -925,7 +699,6 @@ void AsyncClient::_sockPoll(void)
uint32_t sent_delay = now - _writeQueue.front().queued_at;
if (sent_delay >= _ack_timeout && _writeQueue.front().written_at == 0) {
_ack_timeout_signaled = true;
- //log_w("ack timeout %d", pcb->state);
xSemaphoreGive(_write_mutex);
if(_timeout_cb)
_timeout_cb(_timeout_cb_arg, this, sent_delay);
@@ -950,7 +723,6 @@ void AsyncClient::_sockPoll(void)
// RX Timeout
if (_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)) {
- //log_w("rx timeout %d", pcb->state);
_close();
return;
}
@@ -980,17 +752,10 @@ void AsyncClient::_removeAllCallbacks(void)
void AsyncClient::_close(void)
{
- //Serial.print("AsyncClient::_close: "); Serial.println(_socket);
xSemaphoreTakeRecursive(_asyncsock_mutex, (TickType_t)portMAX_DELAY);
_conn_state = 0;
::close(_socket);
_socket = -1;
-#if ASYNC_TCP_SSL_ENABLED
- if (_sslctx != NULL) {
- delete _sslctx;
- _sslctx = NULL;
- }
-#endif
xSemaphoreGiveRecursive(_asyncsock_mutex);
_clearWriteQueue();
@@ -1003,12 +768,6 @@ void AsyncClient::_error(int8_t err)
_conn_state = 0;
::close(_socket);
_socket = -1;
-#if ASYNC_TCP_SSL_ENABLED
- if (_sslctx != NULL) {
- delete _sslctx;
- _sslctx = NULL;
- }
-#endif
xSemaphoreGiveRecursive(_asyncsock_mutex);
_clearWriteQueue();
@@ -1138,75 +897,6 @@ int8_t AsyncClient::abort(){
return ERR_ABRT;
}
-#if ASYNC_TCP_SSL_ENABLED
-void AsyncClient::setRootCa(const char* rootca, const size_t len) {
- _root_ca = (char*)rootca;
- _root_ca_len = len;
-}
-
-void AsyncClient::setClientCert(const char* cli_cert, const size_t len) {
- _cli_cert = (char*)cli_cert;
- _cli_cert_len = len;
-}
-
-void AsyncClient::setClientKey(const char* cli_key, const size_t len) {
- _cli_key = (char*)cli_key;
- _cli_key_len = len;
-}
-
-void AsyncClient::setPsk(const char* psk_ident, const char* psk) {
- _psk_ident = psk_ident;
- _psk = psk;
-}
-#endif // ASYNC_TCP_SSL_ENABLED
-
-const char * AsyncClient::errorToString(int8_t error){
- switch(error){
- case ERR_OK: return "OK";
- case ERR_MEM: return "Out of memory error";
- case ERR_BUF: return "Buffer error";
- case ERR_TIMEOUT: return "Timeout";
- case ERR_RTE: return "Routing problem";
- case ERR_INPROGRESS: return "Operation in progress";
- case ERR_VAL: return "Illegal value";
- case ERR_WOULDBLOCK: return "Operation would block";
- case ERR_USE: return "Address in use";
- case ERR_ALREADY: return "Already connected";
- case ERR_CONN: return "Not connected";
- case ERR_IF: return "Low-level netif error";
- case ERR_ABRT: return "Connection aborted";
- case ERR_RST: return "Connection reset";
- case ERR_CLSD: return "Connection closed";
- case ERR_ARG: return "Illegal argument";
- case -55: return "DNS failed";
- default: return "UNKNOWN";
- }
-}
-/*
-const char * AsyncClient::stateToString(){
- switch(state()){
- case 0: return "Closed";
- case 1: return "Listen";
- case 2: return "SYN Sent";
- case 3: return "SYN Received";
- case 4: return "Established";
- case 5: return "FIN Wait 1";
- case 6: return "FIN Wait 2";
- case 7: return "Close Wait";
- case 8: return "Closing";
- case 9: return "Last ACK";
- case 10: return "Time Wait";
- default: return "UNKNOWN";
- }
-}
-*/
-
-
-
-/*
- Async TCP Server
- */
-
AsyncServer::AsyncServer(IPAddress addr, uint16_t port)
: _port(port)
, _addr(addr)
@@ -1237,7 +927,6 @@ void AsyncServer::begin()
if (_socket != -1) return;
if (!_start_asyncsock_task()) {
- log_e("failed to start task");
return;
}
@@ -1250,14 +939,12 @@ void AsyncServer::begin()
server.sin_port = htons(_port);
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) {
::close(sockfd);
- log_e("bind error: %d - %s", errno, strerror(errno));
return;
}
static uint8_t backlog = 5;
if (listen(sockfd , backlog) < 0) {
::close(sockfd);
- log_e("listen error: %d - %s", errno, strerror(errno));
return;
}
fcntl(sockfd, F_SETFL, O_NONBLOCK);
@@ -1279,15 +966,12 @@ void AsyncServer::end()
void AsyncServer::_sockIsReadable(void)
{
- //Serial.print("AsyncServer::_sockIsReadable: "); Serial.println(_socket);
if (_connect_cb) {
struct sockaddr_in client;
size_t cs = sizeof(struct sockaddr_in);
errno = 0; int accepted_sockfd = ::accept(_socket, (struct sockaddr *)&client, (socklen_t*)&cs);
- //Serial.printf("\t new sockfd=%d errno=%d\r\n", accepted_sockfd, errno);
if (accepted_sockfd < 0) {
- log_e("accept error: %d - %s", errno, strerror(errno));
return;
}
diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h
index 96d02c96a..b9673bf13 100644
--- a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h
+++ b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h
@@ -32,22 +32,12 @@
#include
#include
#include
-#if ASYNC_TCP_SSL_ENABLED
-#include
-#include "AsyncTCP_TLS_Context.h"
-#endif
extern "C" {
#include "lwip/err.h"
#include "lwip/sockets.h"
}
-#define ASYNCTCP_VERSION "1.0.2-dev"
-#define ASYNCTCP_VERSION_MAJOR 1
-#define ASYNCTCP_VERSION_MINOR 2
-#define ASYNCTCP_VERSION_REVISION 2
-#define ASYNCTCP_FORK_mathieucarbou
-
//If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE
@@ -71,21 +61,15 @@ extern "C" {
class AsyncClient;
-#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
-#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
-#endif
#ifndef ASYNC_MAX_ACK_TIME
-#define ASYNC_MAX_ACK_TIME CONFIG_ASYNC_TCP_MAX_ACK_TIME
+#define ASYNC_MAX_ACK_TIME 5000
#endif
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
-#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
-#define SSL_HANDSHAKE_TIMEOUT 5000 // timeout to complete SSL handshake
typedef std::function AcConnectHandler;
typedef std::function AcAckHandler;
typedef std::function AcErrorHandler;
typedef std::function AcDataHandler;
-//typedef std::function AcPacketHandler;
typedef std::function AcTimeoutHandler;
class AsyncSocketBase
@@ -120,17 +104,8 @@ class AsyncClient : public AsyncSocketBase
AsyncClient(int sockfd = -1);
~AsyncClient();
-#if ASYNC_TCP_SSL_ENABLED
- bool connect(IPAddress ip, uint16_t port, bool secure = false);
- bool connect(const char* host, uint16_t port, bool secure = false);
- void setRootCa(const char* rootca, const size_t len);
- void setClientCert(const char* cli_cert, const size_t len);
- void setClientKey(const char* cli_key, const size_t len);
- void setPsk(const char* psk_ident, const char* psk);
-#else
bool connect(IPAddress ip, uint16_t port);
bool connect(const char* host, uint16_t port);
-#endif // ASYNC_TCP_SSL_ENABLED
void close(bool now = false);
int8_t abort();
@@ -155,7 +130,6 @@ class AsyncClient : public AsyncSocketBase
uint32_t getRxTimeout();
void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds
void setNoDelay(bool nodelay);
- bool getNoDelay();
uint32_t getRemoteAddress();
uint16_t getRemotePort();
@@ -215,24 +189,6 @@ class AsyncClient : public AsyncSocketBase
// from the LWIP thread itself.
struct ip_addr _connect_addr;
uint16_t _connect_port = 0;
- //const char * _connect_dnsname = NULL;
-
-#if ASYNC_TCP_SSL_ENABLED
- size_t _root_ca_len;
- char* _root_ca;
- size_t _cli_cert_len;
- char* _cli_cert;
- size_t _cli_key_len;
- char* _cli_key;
- bool _secure;
- bool _handshake_done;
- const char* _psk_ident;
- const char* _psk;
-
- String _hostname;
- AsyncTCP_TLS_Context * _sslctx;
-#endif // ASYNC_TCP_SSL_ENABLED
-
// The following private struct represents a buffer enqueued with the add()
// method. Each of these buffers are flushed whenever the socket becomes
// writable
@@ -271,18 +227,9 @@ class AsyncClient : public AsyncSocketBase
void _clearWriteQueue(void);
void _collectNotifyWrittenBuffers(std::deque &, int &);
void _notifyWrittenBuffers(std::deque &, int);
-
-#if ASYNC_TCP_SSL_ENABLED
- int _runSSLHandshakeLoop(void);
-#endif
-
friend void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
};
-#if ASYNC_TCP_SSL_ENABLED
-typedef std::function AcSSlFileHandler;
-#endif
-
class AsyncServer : public AsyncSocketBase
{
public:
@@ -290,16 +237,10 @@ class AsyncServer : public AsyncSocketBase
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void* arg);
-#if ASYNC_TCP_SSL_ENABLED
- // Dummy, so it compiles with ESP Async WebServer library enabled.
- void onSslFileRequest(AcSSlFileHandler cb, void* arg) {};
- void beginSecure(const char *cert, const char *private_key_file, const char *password) {};
-#endif
void begin();
void end();
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
- bool getNoDelay() { return _noDelay; }
uint8_t status() const;
protected:
diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h
deleted file mode 100644
index fc70073df..000000000
--- a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef ASYNCTCP_SSL_H_
-#define ASYNCTCP_SSL_H_
-
-#include "AsyncTCP_SSL.hpp"
-
-#endif /* ASYNCTCP_SSL_H_ */
\ No newline at end of file
diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp
deleted file mode 100644
index 8f088d0ba..000000000
--- a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_SSL.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef ASYNCTCP_SSL_HPP
-#define ASYNCTCP_SSL_HPP
-
-#ifdef ASYNC_TCP_SSL_ENABLED
-
-#include
-
-#define AsyncSSLClient AsyncClient
-#define AsyncSSLServer AsyncServer
-
-#define ASYNC_TCP_SSL_VERSION "AsyncTCPSock SSL shim v0.0.1"
-
-#else
-#error Compatibility shim requires ASYNC_TCP_SSL_ENABLED to be defined!
-#endif
-
-#endif
\ No newline at end of file
diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp
deleted file mode 100644
index 677146a7e..000000000
--- a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.cpp
+++ /dev/null
@@ -1,346 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-
-#include "AsyncTCP_TLS_Context.h"
-
-#if ASYNC_TCP_SSL_ENABLED
-#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
-# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher"
-#else
-
-static const char *pers = "esp32-tls";
-
-static int _handle_error(int err, const char * function, int line)
-{
- if(err == -30848){
- return err;
- }
-#ifdef MBEDTLS_ERROR_C
- char error_buf[100];
- mbedtls_strerror(err, error_buf, 100);
- log_e("[%s():%d]: (%d) %s", function, line, err, error_buf);
-#else
- log_e("[%s():%d]: code %d", function, line, err);
-#endif
- return err;
-}
-
-#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__)
-
-AsyncTCP_TLS_Context::AsyncTCP_TLS_Context(void)
-{
- mbedtls_ssl_init(&ssl_ctx);
- mbedtls_ssl_config_init(&ssl_conf);
- mbedtls_ctr_drbg_init(&drbg_ctx);
- _socket = -1;
- _have_ca_cert = false;
- _have_client_cert = false;
- _have_client_key = false;
- handshake_timeout = 120000;
-}
-
-int AsyncTCP_TLS_Context::startSSLClientInsecure(int sck, const char * host_or_ip)
-{
- return _startSSLClient(sck, host_or_ip,
- NULL, 0,
- NULL, 0,
- NULL, 0,
- NULL, NULL,
- true);
-}
-
-int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
- const char *pskIdent, const char *psKey)
-{
- return _startSSLClient(sck, host_or_ip,
- NULL, 0,
- NULL, 0,
- NULL, 0,
- pskIdent, psKey,
- false);
-}
-
-int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
- const char *rootCABuff,
- const char *cli_cert,
- const char *cli_key)
-{
- return startSSLClient(sck, host_or_ip,
- (const unsigned char *)rootCABuff, (rootCABuff != NULL) ? strlen(rootCABuff) + 1 : 0,
- (const unsigned char *)cli_cert, (cli_cert != NULL) ? strlen(cli_cert) + 1 : 0,
- (const unsigned char *)cli_key, (cli_key != NULL) ? strlen(cli_key) + 1 : 0);
-}
-
-int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
- const unsigned char *rootCABuff, const size_t rootCABuff_len,
- const unsigned char *cli_cert, const size_t cli_cert_len,
- const unsigned char *cli_key, const size_t cli_key_len)
-{
- return _startSSLClient(sck, host_or_ip,
- rootCABuff, rootCABuff_len,
- cli_cert, cli_cert_len,
- cli_key, cli_key_len,
- NULL, NULL,
- false);
-}
-
-int AsyncTCP_TLS_Context::_startSSLClient(int sck, const char * host_or_ip,
- const unsigned char *rootCABuff, const size_t rootCABuff_len,
- const unsigned char *cli_cert, const size_t cli_cert_len,
- const unsigned char *cli_key, const size_t cli_key_len,
- const char *pskIdent, const char *psKey,
- bool insecure)
-{
- int ret;
- int enable = 1;
-
- // The insecure flag will skip server certificate validation. Otherwise some
- // certificate is required.
- if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) {
- return -1;
- }
-
-#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }}
-// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO");
-// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO");
-
- ROE(lwip_setsockopt(sck, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY");
- ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE");
-
- log_v("Seeding the random number generator");
- mbedtls_entropy_init(&entropy_ctx);
-
- ret = mbedtls_ctr_drbg_seed(&drbg_ctx, mbedtls_entropy_func,
- &entropy_ctx, (const unsigned char *) pers, strlen(pers));
- if (ret < 0) {
- return handle_error(ret);
- }
-
- log_v("Setting up the SSL/TLS structure...");
-
- if ((ret = mbedtls_ssl_config_defaults(&ssl_conf,
- MBEDTLS_SSL_IS_CLIENT,
- MBEDTLS_SSL_TRANSPORT_STREAM,
- MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
- return handle_error(ret);
- }
-
- if (insecure) {
- mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE);
- log_i("WARNING: Skipping SSL Verification. INSECURE!");
- } else if (rootCABuff != NULL) {
- log_v("Loading CA cert");
- mbedtls_x509_crt_init(&ca_cert);
- mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
- ret = mbedtls_x509_crt_parse(&ca_cert, rootCABuff, rootCABuff_len);
- _have_ca_cert = true;
- mbedtls_ssl_conf_ca_chain(&ssl_conf, &ca_cert, NULL);
- if (ret < 0) {
- // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash.
- _deleteHandshakeCerts();
- return handle_error(ret);
- }
- } else if (pskIdent != NULL && psKey != NULL) {
- log_v("Setting up PSK");
- // convert PSK from hex to binary
- if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) {
- log_e("pre-shared key not valid hex or too long");
- return -1;
- }
- unsigned char psk[MBEDTLS_PSK_MAX_LEN];
- size_t psk_len = strlen(psKey)/2;
- for (int j=0; j= '0' && c <= '9') c -= '0';
- else if (c >= 'A' && c <= 'F') c -= 'A' - 10;
- else if (c >= 'a' && c <= 'f') c -= 'a' - 10;
- else return -1;
- psk[j/2] = c<<4;
- c = psKey[j+1];
- if (c >= '0' && c <= '9') c -= '0';
- else if (c >= 'A' && c <= 'F') c -= 'A' - 10;
- else if (c >= 'a' && c <= 'f') c -= 'a' - 10;
- else return -1;
- psk[j/2] |= c;
- }
- // set mbedtls config
- ret = mbedtls_ssl_conf_psk(&ssl_conf, psk, psk_len,
- (const unsigned char *)pskIdent, strlen(pskIdent));
- if (ret != 0) {
- log_e("mbedtls_ssl_conf_psk returned %d", ret);
- return handle_error(ret);
- }
- } else {
- return -1;
- }
-
- if (!insecure && cli_cert != NULL && cli_key != NULL) {
- mbedtls_x509_crt_init(&client_cert);
- mbedtls_pk_init(&client_key);
-
- log_v("Loading CRT cert");
-
- ret = mbedtls_x509_crt_parse(&client_cert, cli_cert, cli_cert_len);
- _have_client_cert = true;
- if (ret < 0) {
- // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash.
- _deleteHandshakeCerts();
- return handle_error(ret);
- }
-
- log_v("Loading private key");
-#if MBEDTLS_VERSION_NUMBER < 0x03000000
- ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0);
-#else
- ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0, mbedtls_ctr_drbg_random, &drbg_ctx);
-#endif
- _have_client_key = true;
-
- if (ret != 0) {
- _deleteHandshakeCerts();
- return handle_error(ret);
- }
-
- mbedtls_ssl_conf_own_cert(&ssl_conf, &client_cert, &client_key);
- }
-
- log_v("Setting hostname for TLS session...");
-
- // Hostname set here should match CN in server certificate
- if ((ret = mbedtls_ssl_set_hostname(&ssl_ctx, host_or_ip)) != 0){
- _deleteHandshakeCerts();
- return handle_error(ret);
- }
-
- mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &drbg_ctx);
-
- if ((ret = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf)) != 0) {
- _deleteHandshakeCerts();
- return handle_error(ret);
- }
-
- _socket = sck;
- mbedtls_ssl_set_bio(&ssl_ctx, &_socket, mbedtls_net_send, mbedtls_net_recv, NULL );
- handshake_start_time = 0;
-
- return 0;
-}
-
-int AsyncTCP_TLS_Context::runSSLHandshake(void)
-{
- int ret, flags;
- if (_socket < 0) return -1;
-
- if (handshake_start_time == 0) handshake_start_time = millis();
- ret = mbedtls_ssl_handshake(&ssl_ctx);
- if (ret != 0) {
- // Something happened before SSL handshake could be completed
-
- // Negotiation error, other than socket not readable/writable when required
- if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
- return handle_error(ret);
- }
-
- // Handshake is taking too long
- if ((millis()-handshake_start_time) > handshake_timeout)
- return -1;
-
- // Either MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE
- return ret;
- }
-
- // Handshake completed, validate remote side if required...
-
- if (_have_client_cert && _have_client_key) {
- log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_ctx));
- if ((ret = mbedtls_ssl_get_record_expansion(&ssl_ctx)) >= 0) {
- log_d("Record expansion is %d", ret);
- } else {
- log_w("Record expansion is unknown (compression)");
- }
- }
-
- log_v("Verifying peer X.509 certificate...");
-
- if ((flags = mbedtls_ssl_get_verify_result(&ssl_ctx)) != 0) {
- char buf[512];
- memset(buf, 0, sizeof(buf));
- mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags);
- log_e("Failed to verify peer certificate! verification info: %s", buf);
- _deleteHandshakeCerts();
- return handle_error(ret);
- } else {
- log_v("Certificate verified.");
- }
-
- _deleteHandshakeCerts();
-
- log_v("Free internal heap after TLS %u", ESP.getFreeHeap());
-
- return 0;
-}
-
-int AsyncTCP_TLS_Context::write(const uint8_t *data, size_t len)
-{
- if (_socket < 0) return -1;
-
- log_v("Writing packet, %d bytes unencrypted...", len);
- int ret = mbedtls_ssl_write(&ssl_ctx, data, len);
- if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
- log_v("Handling error %d", ret); //for low level debug
- return handle_error(ret);
- }
- return ret;
-}
-
-int AsyncTCP_TLS_Context::read(uint8_t * data, size_t len)
-{
- int ret = mbedtls_ssl_read(&ssl_ctx, data, len);
- if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
- log_v("Handling error %d", ret); //for low level debug
- return handle_error(ret);
- }
- if (ret > 0) log_v("Read packet, %d out of %d requested bytes...", ret, len);
- return ret;
-}
-
-void AsyncTCP_TLS_Context::_deleteHandshakeCerts(void)
-{
- if (_have_ca_cert) {
- log_v("Cleaning CA certificate.");
- mbedtls_x509_crt_free(&ca_cert);
- _have_ca_cert = false;
- }
- if (_have_client_cert) {
- log_v("Cleaning client certificate.");
- mbedtls_x509_crt_free(&client_cert);
- _have_client_cert = false;
- }
- if (_have_client_key) {
- log_v("Cleaning client certificate key.");
- mbedtls_pk_free(&client_key);
- _have_client_key = false;
- }
-}
-
-AsyncTCP_TLS_Context::~AsyncTCP_TLS_Context()
-{
- _deleteHandshakeCerts();
-
- log_v("Cleaning SSL connection.");
-
- mbedtls_ssl_free(&ssl_ctx);
- mbedtls_ssl_config_free(&ssl_conf);
- mbedtls_ctr_drbg_free(&drbg_ctx);
- mbedtls_entropy_free(&entropy_ctx); // <-- Is this OK to free if mbedtls_entropy_init() has not been called on it?
-}
-
-#endif
-#endif // ASYNC_TCP_SSL_ENABLED
\ No newline at end of file
diff --git a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h b/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h
deleted file mode 100644
index 4ef033273..000000000
--- a/Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP_TLS_Context.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#pragma once
-
-#if ASYNC_TCP_SSL_ENABLED
-
-#include "mbedtls/version.h"
-#include "mbedtls/platform.h"
-#if MBEDTLS_VERSION_NUMBER < 0x03000000
-#include "mbedtls/net.h"
-#else
-#include "mbedtls/net_sockets.h"
-#endif
-#include "mbedtls/debug.h"
-#include "mbedtls/ssl.h"
-#include "mbedtls/entropy.h"
-#include "mbedtls/ctr_drbg.h"
-#include "mbedtls/error.h"
-
-#define ASYNCTCP_TLS_CAN_RETRY(r) (((r) == MBEDTLS_ERR_SSL_WANT_READ) || ((r) == MBEDTLS_ERR_SSL_WANT_WRITE))
-#define ASYNCTCP_TLS_EOF(r) (((r) == MBEDTLS_ERR_SSL_CONN_EOF) || ((r) == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY))
-
-class AsyncTCP_TLS_Context
-{
-private:
- // These fields must persist for the life of the encrypted connection, destroyed on
- // object destructor.
- mbedtls_ssl_context ssl_ctx;
- mbedtls_ssl_config ssl_conf;
- mbedtls_ctr_drbg_context drbg_ctx;
- mbedtls_entropy_context entropy_ctx;
-
- // These allocate memory during handshake but must be freed on either success or failure
- mbedtls_x509_crt ca_cert;
- mbedtls_x509_crt client_cert;
- mbedtls_pk_context client_key;
- bool _have_ca_cert;
- bool _have_client_cert;
- bool _have_client_key;
-
- unsigned long handshake_timeout;
- unsigned long handshake_start_time;
-
- int _socket;
-
- int _startSSLClient(int sck, const char * host_or_ip,
- const unsigned char *rootCABuff, const size_t rootCABuff_len,
- const unsigned char *cli_cert, const size_t cli_cert_len,
- const unsigned char *cli_key, const size_t cli_key_len,
- const char *pskIdent, const char *psKey,
- bool insecure);
-
- // Delete certificates used in handshake
- void _deleteHandshakeCerts(void);
-public:
- AsyncTCP_TLS_Context(void);
- virtual ~AsyncTCP_TLS_Context();
-
- int startSSLClientInsecure(int sck, const char * host_or_ip);
-
- int startSSLClient(int sck, const char * host_or_ip,
- const char *pskIdent, const char *psKey);
-
- int startSSLClient(int sck, const char * host_or_ip,
- const char *rootCABuff,
- const char *cli_cert,
- const char *cli_key);
-
- int startSSLClient(int sck, const char * host_or_ip,
- const unsigned char *rootCABuff, const size_t rootCABuff_len,
- const unsigned char *cli_cert, const size_t cli_cert_len,
- const unsigned char *cli_key, const size_t cli_key_len);
-
- int runSSLHandshake(void);
-
- int write(const uint8_t *data, size_t len);
-
- int read(uint8_t * data, size_t len);
-};
-
-#endif // ASYNC_TCP_SSL_ENABLED
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index 422c8e264..b862c4b30 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -11,14 +11,33 @@
[platformio]
src_dir = ./Software
-[env:esp32dev]
-platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30/platform-espressif32.zip
+[env:compiler_warning_check]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.32/platform-espressif32.zip
board = esp32dev
monitor_speed = 115200
monitor_filters = default, time, log2file, esp32_exception_decoder
-;board_build.partitions = min_spiffs.csv
+board_build.flash_mode = qio
+board_build.f_flash = 80000000
+board_build.arduino.memory_type = qio_qspi
+board_build.partitions = min_spiffs.csv
framework = arduino
-build_flags = -I include -DHW_DEVKIT -Wimplicit-fallthrough -Wextra
+build_flags = -I include -DHW_LILYGO -DSMALL_FLASH_DEVICE -Wimplicit-fallthrough -Wextra -Wall -Werror
+ -fno-exceptions -ffunction-sections -fdata-sections -Wl,--gc-sections
+; -fno-rtti ; was avoided as we receive some warnings from gcc because the flags seems to be common for gcc and g++ in platformio (unfortunately)
+lib_deps =
+
+[env:esp32devkit_330]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
+board = esp32dev
+monitor_speed = 115200
+monitor_filters = default, time, log2file, esp32_exception_decoder
+board_build.flash_mode = qio
+board_build.f_flash = 80000000
+board_build.arduino.memory_type = qio_qspi
+board_build.partitions = min_spiffs.csv
+framework = arduino
+build_flags = -I include -DHW_DEVKIT -DSMALL_FLASH_DEVICE -Wimplicit-fallthrough -Wextra -Wall
+ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections
lib_deps =
[env:esp32s3box]
@@ -34,12 +53,12 @@ build_flags =
-std=gnu++17
-I include
-DHW_DEVKIT
- -Wimplicit-fallthrough
- -Wextra
+ -DSMALL_FLASH_DEVICE
+ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections
lib_deps =
[env:lilygo_330]
-platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30/platform-espressif32.zip
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
board = esp32dev
monitor_speed = 115200
monitor_filters = default, time, log2file, esp32_exception_decoder
@@ -48,24 +67,26 @@ board_build.f_flash = 80000000
board_build.arduino.memory_type = qio_qspi
board_build.partitions = min_spiffs.csv
framework = arduino
-build_flags = -I include -DHW_LILYGO -Wimplicit-fallthrough -Wextra
+build_flags = -I include -DHW_LILYGO -DSMALL_FLASH_DEVICE -Wimplicit-fallthrough -Wextra -Wall
+ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections
lib_deps =
[env:stark_330]
-platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30/platform-espressif32.zip
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
board = esp32dev
monitor_speed = 115200
monitor_filters = default, time, log2file, esp32_exception_decoder
board_build.flash_mode = qio
board_build.f_flash = 80000000
board_build.arduino.memory_type = qio_qspi
-board_build.partitions = min_spiffs.csv
+board_build.partitions = default_8MB.csv
framework = arduino
-build_flags = -I include -DHW_STARK -Wimplicit-fallthrough -Wextra
+build_flags = -I include -DHW_STARK -Wimplicit-fallthrough -Wextra -Wall
+ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections
lib_deps =
[env:lilygo_2CAN_330]
-platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30/platform-espressif32.zip
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
board = esp32s3_flash_16MB
monitor_speed = 115200
monitor_filters = default, time, log2file, esp32_exception_decoder
@@ -79,10 +100,34 @@ build_flags =
-I include
-Wimplicit-fallthrough
-Wextra
+ -Wall
-D HW_LILYGO2CAN
-D BOARD_HAS_PSRAM
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1 ;1 is to use the USB port as a serial port
-D ARDUINO_RUNNING_CORE=1 ; Arduino Runs On Core (setup, loop)
-D ARDUINO_EVENT_RUNNING_CORE=1 ; Events Run On Core
+ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections
+lib_deps =
+
+[env:BECom_330]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
+board = esp32s3_flash_16MB
+monitor_speed = 115200
+upload_speed = 921600
+monitor_filters = default, time, log2file, esp32_exception_decoder
+board_build.arduino.partitions = default_16MB.csv
+board_build.arduino.memory_type = qio_qspi
+framework = arduino
+build_flags =
+ -I include
+ -Wimplicit-fallthrough
+ -Wextra
+ -Wall
+ -D HW_BECOM
+ -D ARDUINO_USB_MODE=1
+ -D ARDUINO_USB_CDC_ON_BOOT=1 ;1 is to use the USB port as a serial port
+ -D ARDUINO_RUNNING_CORE=1 ; Arduino Runs On Core (setup, loop)
+ -D ARDUINO_EVENT_RUNNING_CORE=1 ; Events Run On Core
+ -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections
lib_deps =
\ No newline at end of file
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index e00fd2b9c..df7499c6e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -69,6 +69,7 @@ add_compile_definitions(ESP32 HW_LILYGO COMMON_IMAGE)
add_executable(tests
tests.cpp
safety_tests.cpp
+ bms_reset_tests.cpp
battery/NissanLeafTest.cpp
battery/still_alive_tests.cpp
can_log_based/canlog_safety_tests.cpp
@@ -78,16 +79,16 @@ add_executable(tests
../Software/src/communication/rs485/comm_rs485.cpp
../Software/src/devboard/safety/safety.cpp
../Software/src/devboard/hal/hal.cpp
+ ../Software/src/devboard/utils/types.cpp
../Software/src/devboard/utils/events.cpp
+ ../Software/src/devboard/utils/common_functions.cpp
../Software/src/datalayer/datalayer.cpp
../Software/src/datalayer/datalayer_extended.cpp
- ../Software/src/lib/eModbus-eModbus/Logging.cpp
../Software/src/lib/eModbus-eModbus/ModbusMessage.cpp
../Software/src/lib/eModbus-eModbus/ModbusServer.cpp
../Software/src/lib/eModbus-eModbus/ModbusServerRTU.cpp
../Software/src/lib/eModbus-eModbus/ModbusTypeDefs.cpp
../Software/src/lib/eModbus-eModbus/RTUutils.cpp
-# ../Software/src/lib/eModbus-eModbus/scripts/mbServerFCs.cpp
../Software/src/battery/BATTERIES.cpp
../Software/src/battery/Battery.cpp
../Software/src/battery/BMW-I3-BATTERY.cpp
@@ -103,10 +104,13 @@ add_executable(tests
../Software/src/battery/CHADEMO-BATTERY.cpp
../Software/src/battery/CHADEMO-SHUNTS.cpp
../Software/src/battery/CMFA-EV-BATTERY.cpp
+ ../Software/src/battery/CMP-SMART-CAR-BATTERY.cpp
../Software/src/battery/DALY-BMS.cpp
../Software/src/battery/ECMP-BATTERY.cpp
+ ../Software/src/battery/FORD-MACH-E-BATTERY.cpp
../Software/src/battery/FOXESS-BATTERY.cpp
../Software/src/battery/GEELY-GEOMETRY-C-BATTERY.cpp
+ ../Software/src/battery/GROWATT-HV-ARK-BATTERY.cpp
../Software/src/battery/HYUNDAI-IONIQ-28-BATTERY-HTML.cpp
../Software/src/battery/HYUNDAI-IONIQ-28-BATTERY.cpp
../Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp
@@ -136,7 +140,10 @@ add_executable(tests
../Software/src/battery/SIMPBMS-BATTERY.cpp
../Software/src/battery/SONO-BATTERY.cpp
../Software/src/battery/TESLA-BATTERY.cpp
+ ../Software/src/battery/TESLA-LEGACY-BATTERY.cpp
../Software/src/battery/TEST-FAKE-BATTERY.cpp
+ ../Software/src/battery/THINK-BATTERY.cpp
+ ../Software/src/battery/GEELY-SEA-BATTERY.cpp
../Software/src/battery/VOLVO-SPA-BATTERY.cpp
../Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.cpp
../Software/src/inverter/AFORE-CAN.cpp
@@ -156,12 +163,12 @@ add_executable(tests
../Software/src/inverter/SMA-BYD-H-CAN.cpp
../Software/src/inverter/SMA-BYD-HVS-CAN.cpp
../Software/src/inverter/SMA-LV-CAN.cpp
- ../Software/src/inverter/SMA-TRIPOWER-CAN.cpp
../Software/src/inverter/SOFAR-CAN.cpp
../Software/src/inverter/SOL-ARK-LV-CAN.cpp
../Software/src/inverter/SOLAX-CAN.cpp
../Software/src/inverter/SOLXPOW-CAN.cpp
../Software/src/inverter/SUNGROW-CAN.cpp
+ ../Software/src/inverter/VCU-CAN.cpp
../Software/src/charger/CHARGERS.cpp
../Software/src/charger/CHEVY-VOLT-CHARGER.cpp
../Software/src/charger/NISSAN-LEAF-CHARGER.cpp
@@ -177,4 +184,9 @@ target_link_libraries(tests
libgmock
)
+# Define the path for tests
+target_compile_definitions(tests PRIVATE
+ TEST_CAN_LOG_DIR="${CMAKE_SOURCE_DIR}/can_log_based/can_logs"
+)
+
gtest_discover_tests(tests)
diff --git a/test/bms_reset_tests.cpp b/test/bms_reset_tests.cpp
new file mode 100644
index 000000000..31aed378a
--- /dev/null
+++ b/test/bms_reset_tests.cpp
@@ -0,0 +1,157 @@
+#include
+
+#include "../Software/src/communication/contactorcontrol/comm_contactorcontrol.h"
+#include "../Software/src/datalayer/datalayer.h"
+#include "../Software/src/devboard/safety/safety.h"
+#include "../Software/src/devboard/utils/events.h"
+
+#include "Arduino.h"
+
+const unsigned long bmsWarmupDuration = 3000;
+
+// Test a BMS reqest sequence from end to end. This is for the case where the
+// contactors are powered directly by BE, so the reset doesn't need to wait for
+// zero current before cutting the BMS power.
+TEST(BmsResetTests, BmsResetSequenceDirectSuccess) {
+ set_millis64(0xffffffffffffffff - 10); // Test overflow handling
+ remote_bms_reset = true;
+ contactor_control_enabled = true;
+ datalayer.battery.settings.user_set_bms_reset_duration_ms = 30000; // 30 seconds
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Nothing has happened yet, should still be idle.
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_IDLE);
+ start_bms_reset();
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should have gone straight to powered-off
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERED_OFF);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be powered off
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERED_OFF);
+
+ set_millis64(20000); // Simulate 20 seconds passing
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be powered off
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERED_OFF);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ set_millis64(31000); // Simulate 31 seconds passing
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should now be powering on
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERING_ON);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be powering on
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERING_ON);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ set_millis64(31000 + bmsWarmupDuration + 1000); // Simulate warmup duration passing
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should now be idle again
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_IDLE);
+ EXPECT_EQ(emulator_pause_request_ON, false);
+}
+
+// Test a BMS reqest sequence from end to end. This is for the case where the
+// contactors are powered by the BMS, so the reset needs to wait for zero
+// current before cutting power to avoid arcing.
+TEST(BmsResetTests, BmsResetSequenceWaitSuccess) {
+ set_millis64(0xffffffffffffffff - 10);
+ remote_bms_reset = true;
+ contactor_control_enabled = false;
+ datalayer.battery.settings.user_set_bms_reset_duration_ms = 30000; // 30 seconds
+ datalayer.battery.status.current_dA = -50; // Simulate battery under load
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Nothing has happened yet, should still be idle.
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_IDLE);
+ start_bms_reset();
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should be waiting for current to drop to zero
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_WAITING_FOR_PAUSE);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ datalayer.battery.status.current_dA = 5; // Reduce to a tiny load
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be waiting for current to drop to zero
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_WAITING_FOR_PAUSE);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ set_millis64(6000); // Simulate 6 seconds passing
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should now be powered off (past 5s low-current threshold)
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERED_OFF);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be powered off
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERED_OFF);
+
+ set_millis64(26000);
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be powered off
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERED_OFF);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ set_millis64(37000);
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should now be powering on
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERING_ON);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // Should still be powering on
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_POWERING_ON);
+ EXPECT_EQ(emulator_pause_request_ON, true);
+
+ set_millis64(37000 + bmsWarmupDuration + 1000); // Simulate warmup duration passing
+
+ for (int i = 0; i < 10; i++)
+ handle_BMSpower();
+
+ // BMS should now be idle again
+ EXPECT_EQ(datalayer.system.status.bms_reset_status, BMS_RESET_IDLE);
+ EXPECT_EQ(emulator_pause_request_ON, false);
+}
diff --git a/test/can_log_based/can_logs/29_RenaultZoe2_cv88.txt b/test/can_log_based/can_logs/29_RenaultZoe2_cv88.txt
new file mode 100644
index 000000000..89017f422
--- /dev/null
+++ b/test/can_log_based/can_logs/29_RenaultZoe2_cv88.txt
@@ -0,0 +1,4 @@
+# Update cell voltage 88 via a faked poll response
+# (this is synthetic, please update with a real log!)
+
+(84215.921) RX0 18DAF1DB [8] 00 00 90 7B 0C 7E AA AA
diff --git a/test/can_log_based/can_logs/4_BoltAmpera_cv88.txt b/test/can_log_based/can_logs/4_BoltAmpera_cv88.txt
new file mode 100644
index 000000000..4165a0f80
--- /dev/null
+++ b/test/can_log_based/can_logs/4_BoltAmpera_cv88.txt
@@ -0,0 +1,3 @@
+# Actually comes out as 3122mV
+# (this is synthetic, please replace with real logs)
+(123.893) RX0 206 [8] 00 00 13 84 00 00 a0 00
diff --git a/test/can_log_based/canlog_safety_tests.cpp b/test/can_log_based/canlog_safety_tests.cpp
index 17a76ce0d..9825eed5a 100644
--- a/test/can_log_based/canlog_safety_tests.cpp
+++ b/test/can_log_based/canlog_safety_tests.cpp
@@ -148,6 +148,22 @@ class CellUnderVoltageTest : public CanLogTestFixture {
}
};
+// Check that the parsed logs set a particular cell voltage value (3123mV)
+class CellVoltageTest : public CanLogTestFixture {
+ public:
+ explicit CellVoltageTest(fs::path path, int cellnum) : CanLogTestFixture(path), cellnum_(cellnum) {}
+ void TestBody() override {
+ ProcessLog();
+
+ // Target is 3123mV, but some integrations round strangely so need a margin
+ EXPECT_GT(datalayer.battery.status.cell_voltages_mV[cellnum_], 3121);
+ EXPECT_LT(datalayer.battery.status.cell_voltages_mV[cellnum_], 3125);
+ }
+
+ private:
+ int cellnum_;
+};
+
void RegisterCanLogTests() {
// The logs should be named as follows:
//
@@ -160,8 +176,9 @@ void RegisterCanLogTests() {
// ov: test that an overvoltage event is triggered
// cov: test that normal and critical cell overvoltage events are triggered
// cuv: test that normal and critical cell undervoltage events are triggered
+ // cv88: test that cell 88 (or another) voltage is correctly set (to 3123mV)
- std::string directoryPath = "../can_log_based/can_logs";
+ std::string directoryPath = TEST_CAN_LOG_DIR;
for (const auto& entry : fs::directory_iterator(directoryPath)) {
if (!entry.is_regular_file() || entry.path().extension().string() != ".txt") {
@@ -170,7 +187,18 @@ void RegisterCanLogTests() {
auto bits = split(entry.path().stem(), '_');
auto has_flag = [&bits](const std::string& flag) -> bool {
- return std::find(bits.begin() + 2, bits.end(), flag) != bits.end();
+ // return true if any of the bits start with the supplied flag string
+ return std::any_of(bits.begin() + 2, bits.end(),
+ [&flag](const std::string& bit) { return bit.rfind(flag, 0) == 0; });
+ };
+ auto get_int_flag = [&bits](const std::string& prefix) -> int {
+ // parse anything after a matching flag prefix as an integer, or -1 if not found
+ for (const auto& bit : bits) {
+ if (bit.rfind(prefix, 0) == 0) {
+ return std::stoi(bit.substr(prefix.size()));
+ }
+ }
+ return -1;
};
if (has_flag("base")) {
@@ -197,5 +225,13 @@ void RegisterCanLogTests() {
nullptr, nullptr, __FILE__, __LINE__,
[=]() -> CanLogTestFixture* { return new CellUnderVoltageTest(entry.path()); });
}
+
+ if (has_flag("cv")) {
+ int cellnum = get_int_flag("cv");
+ testing::RegisterTest("CanLogSafetyTests",
+ ("TestCellVoltage" + snake_case_to_camel_case(entry.path().stem().string())).c_str(),
+ nullptr, nullptr, __FILE__, __LINE__,
+ [=]() -> CanLogTestFixture* { return new CellVoltageTest(entry.path(), cellnum); });
+ }
}
}
diff --git a/test/emul/Arduino.h b/test/emul/Arduino.h
index 257d9ac7a..c3cc23b91 100644
--- a/test/emul/Arduino.h
+++ b/test/emul/Arduino.h
@@ -116,6 +116,7 @@ unsigned long micros();
// Can be previously declared as a macro in stupid eModbus
#undef millis
unsigned long millis();
+void set_millis64(uint64_t time);
void delay(unsigned long ms);
void delayMicroseconds(unsigned long us);
diff --git a/test/emul/time.cpp b/test/emul/time.cpp
index f8f1283cf..e366163d0 100644
--- a/test/emul/time.cpp
+++ b/test/emul/time.cpp
@@ -1,7 +1,9 @@
#include
+uint64_t current_time = 0;
+
unsigned long millis() {
- return 0;
+ return static_cast(current_time);
}
uint64_t get_timestamp(unsigned long millis) {
@@ -9,5 +11,9 @@ uint64_t get_timestamp(unsigned long millis) {
}
uint64_t millis64(void) {
- return 0;
+ return current_time;
+}
+
+void set_millis64(uint64_t time) {
+ current_time = time;
}