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. -image +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 + +image + +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 + +image + +After selecting the hardware you need, click the "**Upload Artifact**", and there will be a download link. Download the file! -image +image -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! -image 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 += ""; + content += ""; + content += ""; + 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 += + ""; + content += ""; + content += ""; + content += ""; + } + + content += ""; + content += "
DTC CodeStatusDescription
" + + String(dtcStr) + "" + statusStr + "" + + description + "
"; + 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) + + "
"; content += " "; content += " "; #ifdef LOG_CAN_TO_SD content += " "; #endif content += ""; + 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( + + + )rawliteral" +#else +#define GPIOOPT1_SETTING "" +#endif + +#ifdef HW_LILYGO +#define GPIOOPT2_SETTING \ + R"rawliteral( + + + )rawliteral" +#else +#define GPIOOPT2_SETTING "" +#endif + +#ifdef HW_LILYGO +#define GPIOOPT3_SETTING \ + R"rawliteral( + + + )rawliteral" +#else +#define GPIOOPT3_SETTING "" +#endif + +#ifdef HW_LILYGO +#define GPIOOPT4_SETTING \ + R"rawliteral( + + + )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(

Settings saved. Reboot to take the new settings into use.

Network config

Battery config

Inverter config

Optional components config

Hardware config

Connectivity settings

Local IP:
. . .
Gateway:
. . .
Subnet:
. . .

Debug options

Settings saved. Reboot to take the new settings into use.

Battery interface: %BATTERYINTF%

Battery interface: %BATTERY2INTF%

Inverter interface: %INVINTF%

Shunt interface: %SHUNTINTF%

Battery capacity: %BATTERY_WH_MAX% Wh

Rescale SOC: %SOC_SCALING%

SOC max percentage: %SOC_MAX_PERCENTAGE%

SOC min percentage: %SOC_MIN_PERCENTAGE%

Max charge speed: %MAX_CHARGE_SPEED% A

Max discharge speed: %MAX_DISCHARGE_SPEED% A

Manual charge voltage limits: %VOLTAGE_LIMITS%

Target charge voltage: %CHARGE_VOLTAGE% V

Target discharge voltage: %DISCHARGE_VOLTAGE% V

Periodic BMS reset off time: %BMS_RESET_DURATION% s

Fake battery voltage: %BATTERY_VOLTAGE% V

Manual LFP balancing: %MANUAL_BALANCING%

Balancing max time: %BAL_MAX_TIME% Minutes

Balancing float power: %BAL_POWER% W

Max battery voltage: %BAL_MAX_PACK_VOLTAGE% V

Max cell voltage: %BAL_MAX_CELL_VOLTAGE% mV

Max cell voltage deviation: %BAL_MAX_DEV_CELL_VOLTAGE% mV

Charger HVDC Enabled: %CHG_HV%

Charger Aux12VDC Enabled: %CHG_AUX12V%

Charger Voltage Setpoint: %CHG_VOLTAGE_SETPOINT% V

Charger Current Setpoint: %CHG_CURRENT_SETPOINT% A

)rawliteral" + R"rawliteral( + + + +
+
+ +
+

Settings saved. Reboot to take the new settings into use.

+

+ +
+

Network config

+
+ + + + + +
+
+ +
+

Battery config

+
+ + + + +
+ + +
+ +
+ + + + + + + + +
+ +
+ + + + + +
+ +
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ + + + + + + + + + + +
+ + + + +
+ + + + + + +
+ + +
+ +
+ +
+
+ +
+

Inverter config

+
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + + + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ +
+

Optional components config

+
+ + + +
+ +
+ + + +
+ +
+ +
+
+ +
+

Hardware config

+
+ + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + + +
+ + + + + +
+ +
+ + + + + + + +
+ + + + + + + + +
+ + + + )rawliteral" GPIOOPT1_SETTING R"rawliteral( + )rawliteral" GPIOOPT2_SETTING R"rawliteral( + )rawliteral" GPIOOPT3_SETTING R"rawliteral( + )rawliteral" GPIOOPT4_SETTING R"rawliteral( + +
+
+ +
+

Connectivity settings

+
+ + + + + + + + + + + + + + + + + + + +
+
+
Local IP:
+ . + . + . + +
+ +
+
Gateway:
+ . + . + . + +
+ +
+
Subnet:
+ . + . + . + +
+
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + +
+ +
+
+ +
+

Debug options

+
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+

Settings saved. Reboot to take the new settings into use.

+

+ +
+
+ + +

Battery interface: %BATTERYINTF%

+ +

Battery interface: %BATTERY2INTF%

+ +

Inverter interface: %INVINTF%

+ +

Shunt interface: %SHUNTINTF%

+ + + +
+ +

Battery capacity: %BATTERY_WH_MAX% Wh

+ +

Rescale SOC: %SOC_SCALING% +

+ +

SOC max percentage: %SOC_MAX_PERCENTAGE%

+ +

SOC min percentage: %SOC_MIN_PERCENTAGE%

+ +

Max charge speed: %MAX_CHARGE_SPEED% A

+ +

Max discharge speed: %MAX_DISCHARGE_SPEED% A

+ +

Manual charge voltage limits: + %VOLTAGE_LIMITS% +

+ +

Target charge voltage: %CHARGE_VOLTAGE% V

+ +

Target discharge voltage: %DISCHARGE_VOLTAGE% V

+ +

Periodic BMS reset off time: %BMS_RESET_DURATION% s

+ +

Undercharged emergency recovery mode:

+ +
+ +
+

Fake battery voltage: %BATTERY_VOLTAGE% V

+
+ + + +
+ +

Manual LFP balancing: %MANUAL_BALANCING% +

+ +

Balancing max time: %BAL_MAX_TIME% Minutes

+ +

Balancing float power: %BAL_POWER% W

+ +

Max battery voltage: %BAL_MAX_PACK_VOLTAGE% V

+ +

Max cell voltage: %BAL_MAX_CELL_VOLTAGE% mV

+ +

Max cell voltage deviation: %BAL_MAX_DEV_CELL_VOLTAGE% mV

+ +
+ +
+ +

+ Charger HVDC Enabled: %CHG_HV% + +

+ +

+ Charger Aux12VDC Enabled: %CHG_AUX12V% + +

+ +

Charger Voltage Setpoint: %CHG_VOLTAGE_SETPOINT% V

+ +

Charger Current Setpoint: %CHG_CURRENT_SETPOINT% A

+ +
+ + + +)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 += ""; if (webserver_auth) content += ""; - if (!datalayer.system.settings.equipment_stop_active) + if (!datalayer.system.info.equipment_stop_active) content += "