diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 22f260e78..71921d60c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -23,6 +23,6 @@ - Inverter communication protocol: `` - Hardware used for Battery-Emulator: `HW_LILYGO, HW_STARK, Custom` - CONTACTOR_CONTROL: `yes/no` -- DUAL_CAN: `yes/no` +- CAN_ADDON: `yes/no` - WEBSERVER: `yes/no` - MQTT: `yes/no` diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 287b75f24..ae6bafb6b 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -9,12 +9,28 @@ on: - pull_request # This is the list of jobs that will be run concurrently. -# Since we use a build matrix, the actual number of jobs -# started depends on how many configurations the matrix -# will produce. jobs: - # This is the name of the job + # 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"]' + + # Since we use a build matrix, the actual number of jobs + # started depends on how many configurations the matrix + # will produce. build-batteries: + needs: skip-duplicate-actions + if: needs.skip-duplicate-actions.outputs.should_skip != 'true' # Here we tell GitHub that the jobs must be determined # dynamically depending on a matrix configuration. @@ -40,9 +56,10 @@ jobs: - CHADEMO_BATTERY - IMIEV_CZERO_ION_BATTERY - JAGUAR_IPACE_BATTERY - - KIA_HYUNDAI_64_BATTERY - KIA_E_GMP_BATTERY + - KIA_HYUNDAI_64_BATTERY - KIA_HYUNDAI_HYBRID_BATTERY + - MEB_BATTERY - MG_5_BATTERY - NISSAN_LEAF_BATTERY - PYLON_BATTERY @@ -53,13 +70,19 @@ jobs: - RENAULT_ZOE_GEN1_BATTERY - RENAULT_ZOE_GEN2_BATTERY - SANTA_FE_PHEV_BATTERY + - STELLANTIS_ECMP_BATTERY - TESLA_MODEL_3Y_BATTERY + - TESLA_MODEL_SX_BATTERY - VOLVO_SPA_BATTERY - TEST_FAKE_BATTERY - SERIAL_LINK_RECEIVER # These are the emulated inverter communication protocols for which the code will be compiled. inverter: - BYD_CAN + # These are the supported hardware platforms for which the code will be compiled. + hardware: + - HW_LILYGO + # This is the platform GitHub will use to run our workflow. runs-on: ubuntu-latest @@ -68,6 +91,10 @@ jobs: # First we clone the repo using the `checkout` action. - name: Checkout uses: actions/checkout@v4 + + # Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + - name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h # We use the `arduino/setup-arduino-cli` action to install and # configure the Arduino CLI on the system. @@ -84,4 +111,4 @@ jobs: # in the build matrix, and using build flags to define the # battery and inverter set in the build matrix. - name: Compile Sketch - run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software + run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software diff --git a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml new file mode 100644 index 000000000..2926e091a --- /dev/null +++ b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml @@ -0,0 +1,119 @@ +# This is the name of the workflow, visible on GitHub UI. +name: Compile All Combinations + +# Here we tell GitHub when to run the workflow. +on: + # This allows you to run this workflow manually from the + # GitHub Actions tab. + workflow_dispatch: + # The workflow is run upon creating, editing, + # pre-releasing, releasing and publishing a release + release: + types: [created, edited, prereleased, released, published] + +# 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"]' + + # Since we use a build matrix, the actual number of jobs + # started depends on how many configurations the matrix + # will produce. + + # This is the name of the job. + build-matrix: + needs: skip-duplicate-actions + if: needs.skip-duplicate-actions.outputs.should_skip != 'true' + + # Here we tell GitHub that the jobs must be determined + # dynamically depending on a matrix configuration. + strategy: + # The matrix will produce one job for each combination of parameters. + matrix: + # This is the development board hardware for which the code will be compiled. + # FBQN stands for "fully qualified board name", and is used by Arduino to define the hardware to compile for. + fqbn: + - esp32:esp32:esp32 + # further ESP32 chips + #- esp32:esp32:esp32c3 + #- esp32:esp32:esp32c2 + #- esp32:esp32:esp32c6 + #- esp32:esp32:esp32h2 + #- esp32:esp32:esp32s3 + # These are the batteries for which the code will be compiled. + battery: + - BMW_I3_BATTERY + - BMW_IX_BATTERY + - BYD_ATTO_3_BATTERY + - CELLPOWER_BMS + - CHADEMO_BATTERY + - IMIEV_CZERO_ION_BATTERY + - JAGUAR_IPACE_BATTERY + - KIA_E_GMP_BATTERY + - KIA_HYUNDAI_64_BATTERY + - KIA_HYUNDAI_HYBRID_BATTERY + - MEB_BATTERY + - MG_5_BATTERY + # These are the emulated inverter communication protocols for which the code will be compiled. + inverter: + - AFORE_CAN + - BYD_CAN + - BYD_KOSTAL_RS485 + - BYD_MODBUS + - FOXESS_CAN + - GROWATT_LV_CAN + - PYLON_CAN + - PYLON_LV_CAN + - SCHNEIDER_CAN + - SMA_BYD_H_CAN + - SMA_BYD_HVS_CAN + - SMA_LV_CAN + - SMA_TRIPOWER_CAN + - SOFAR_CAN + - SOLAX_CAN + - SERIAL_LINK_TRANSMITTER + # These are the supported hardware platforms for which the code will be compiled. + hardware: + - HW_LILYGO + + # This is the platform GitHub will use to run our workflow. + runs-on: ubuntu-latest + + # This is the list of steps this job will run. + steps: + # First we clone the repo using the `checkout` action. + - name: Checkout + uses: actions/checkout@v4 + + # Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + - name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h + + # We use the `arduino/setup-arduino-cli` action to install and + # configure the Arduino CLI on the system. + - name: Setup Arduino CLI + uses: arduino/setup-arduino-cli@v2 + + # We then install the platform. + - name: Install platform + run: | + arduino-cli core update-index + arduino-cli core install esp32:esp32 + + # Finally, we compile the sketch, using the FQBN that was set + # in the build matrix, and using build flags to define the + # battery and inverter set in the build matrix. + - name: Compile Sketch + run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software diff --git a/.github/workflows/compile-all-combinations.yml b/.github/workflows/compile-all-combinations-part2-batteries-N-to-Z.yml similarity index 63% rename from .github/workflows/compile-all-combinations.yml rename to .github/workflows/compile-all-combinations-part2-batteries-N-to-Z.yml index c194e97a3..c1233dd04 100644 --- a/.github/workflows/compile-all-combinations.yml +++ b/.github/workflows/compile-all-combinations-part2-batteries-N-to-Z.yml @@ -12,12 +12,30 @@ on: types: [created, edited, prereleased, released, published] # This is the list of jobs that will be run concurrently. -# Since we use a build matrix, the actual number of jobs -# started depends on how many configurations the matrix -# will produce. 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"]' + + # Since we use a build matrix, the actual number of jobs + # started depends on how many configurations the matrix + # will produce. + # This is the name of the job. build-matrix: + needs: skip-duplicate-actions + if: needs.skip-duplicate-actions.outputs.should_skip != 'true' # Here we tell GitHub that the jobs must be determined # dynamically depending on a matrix configuration. @@ -36,34 +54,41 @@ jobs: #- esp32:esp32:esp32s3 # These are the batteries for which the code will be compiled. battery: - - BMW_I3_BATTERY - - BYD_ATTO_3_BATTERY - - CHADEMO_BATTERY - - IMIEV_CZERO_ION_BATTERY - - KIA_HYUNDAI_64_BATTERY - - KIA_HYUNDAI_HYBRID_BATTERY - NISSAN_LEAF_BATTERY - PYLON_BATTERY - RJXZS_BMS + - RANGE_ROVER_PHEV_BATTERY - RENAULT_KANGOO_BATTERY + - RENAULT_TWIZY_BATTERY - RENAULT_ZOE_GEN1_BATTERY - RENAULT_ZOE_GEN2_BATTERY + - SANTA_FE_PHEV_BATTERY + - STELLANTIS_ECMP_BATTERY - TESLA_MODEL_3Y_BATTERY + - TESLA_MODEL_SX_BATTERY - VOLVO_SPA_BATTERY - TEST_FAKE_BATTERY # These are the emulated inverter communication protocols for which the code will be compiled. inverter: + - AFORE_CAN - BYD_CAN - BYD_KOSTAL_RS485 - - BYD_SMA - BYD_MODBUS - FOXESS_CAN + - GROWATT_LV_CAN - PYLON_CAN - - SMA_CAN + - PYLON_LV_CAN + - SCHNEIDER_CAN + - SMA_BYD_H_CAN + - SMA_BYD_HVS_CAN + - SMA_LV_CAN - SMA_TRIPOWER_CAN - SOFAR_CAN - SOLAX_CAN - SERIAL_LINK_TRANSMITTER + # These are the supported hardware platforms for which the code will be compiled. + hardware: + - HW_LILYGO # This is the platform GitHub will use to run our workflow. runs-on: ubuntu-latest @@ -73,6 +98,10 @@ jobs: # First we clone the repo using the `checkout` action. - name: Checkout uses: actions/checkout@v4 + + # Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + - name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h # We use the `arduino/setup-arduino-cli` action to install and # configure the Arduino CLI on the system. @@ -89,4 +118,4 @@ jobs: # in the build matrix, and using build flags to define the # battery and inverter set in the build matrix. - name: Compile Sketch - run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software + run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software diff --git a/.github/workflows/compile-all-hardware.yml b/.github/workflows/compile-all-hardware.yml new file mode 100644 index 000000000..473e0fb2f --- /dev/null +++ b/.github/workflows/compile-all-hardware.yml @@ -0,0 +1,93 @@ +# This is the name of the workflow, visible on GitHub UI. +name: Compile All Hardware + +# Here we tell GitHub when to run the workflow. +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"]' + + # Since we use a build matrix, the actual number of jobs + # started depends on how many configurations the matrix + # will produce. + + # This is the name of the job. + build-hardware: + needs: skip-duplicate-actions + if: needs.skip-duplicate-actions.outputs.should_skip != 'true' + + # Here we tell GitHub that the jobs must be determined + # dynamically depending on a matrix configuration. + strategy: + # The matrix will produce one job for each combination of parameters. + matrix: + # This is the development board hardware for which the code will be compiled. + # FBQN stands for "fully qualified board name", and is used by Arduino to define the hardware to compile for. + fqbn: + - esp32:esp32:esp32 + # further ESP32 chips + #- esp32:esp32:esp32c3 + #- esp32:esp32:esp32c2 + #- esp32:esp32:esp32c6 + #- esp32:esp32:esp32h2 + #- esp32:esp32:esp32s3 + # These are the batteries for which the code will be compiled. + battery: + - NISSAN_LEAF_BATTERY + # These are the emulated inverter communication protocols for which the code will be compiled. + inverter: + - BYD_CAN + # These are the supported hardware platforms for which the code will be compiled. + hardware: + - HW_LILYGO + - HW_STARK + - HW_3LB + - HW_DEVKIT + + # This is the platform GitHub will use to run our workflow. + runs-on: ubuntu-latest + + # This is the list of steps this job will run. + steps: + # First we clone the repo using the `checkout` action. + - name: Checkout + uses: actions/checkout@v4 + + # Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + - name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h + + # We use the `arduino/setup-arduino-cli` action to install and + # configure the Arduino CLI on the system. + - name: Setup Arduino CLI + uses: arduino/setup-arduino-cli@v2 + + # We then install the platform. + - name: Install platform + run: | + arduino-cli core update-index + arduino-cli core install esp32:esp32 + + # Finally, we compile the sketch, using the FQBN that was set + # in the build matrix, and using build flags to define the + # battery and inverter set in the build matrix. + - name: Compile Sketch + run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index af2ce3794..24b9f8469 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -9,12 +9,30 @@ on: - pull_request # This is the list of jobs that will be run concurrently. -# Since we use a build matrix, the actual number of jobs -# started depends on how many configurations the matrix -# will produce. 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"]' + + # Since we use a build matrix, the actual number of jobs + # started depends on how many configurations the matrix + # will produce. + # This is the name of the job. build-inverters: + needs: skip-duplicate-actions + if: needs.skip-duplicate-actions.outputs.should_skip != 'true' # Here we tell GitHub that the jobs must be determined # dynamically depending on a matrix configuration. @@ -33,30 +51,30 @@ jobs: #- esp32:esp32:esp32s3 # These are the batteries for which the code will be compiled. battery: -# - BMW_I3_BATTERY -# - CHADEMO_BATTERY -# - IMIEV_CZERO_ION_BATTERY -# - KIA_HYUNDAI_64_BATTERY - NISSAN_LEAF_BATTERY -# - RENAULT_ZOE_BATTERY -# - TESLA_MODEL_3Y_BATTERY # These are the emulated inverter communication protocols for which the code will be compiled. inverter: - AFORE_CAN - BYD_CAN - BYD_KOSTAL_RS485 - - BYD_SMA - BYD_MODBUS - FOXESS_CAN - - PYLON_LV_CAN + - GROWATT_LV_CAN - PYLON_CAN + - PYLON_LV_CAN - SCHNEIDER_CAN - - SMA_CAN + - SMA_BYD_H_CAN + - SMA_BYD_HVS_CAN + - SMA_LV_CAN - SMA_TRIPOWER_CAN - SOFAR_CAN - SOLAX_CAN - SERIAL_LINK_TRANSMITTER - - NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if charger compiles + - NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if the charger compiles + # These are the supported hardware platforms for which the code will be compiled. + hardware: + - HW_LILYGO + # This is the platform GitHub will use to run our workflow. runs-on: ubuntu-latest @@ -65,6 +83,10 @@ jobs: # First we clone the repo using the `checkout` action. - name: Checkout uses: actions/checkout@v4 + + # Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + - name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h + run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h # We use the `arduino/setup-arduino-cli` action to install and # configure the Arduino CLI on the system. @@ -81,4 +103,4 @@ jobs: # in the build matrix, and using build flags to define the # battery and inverter set in the build matrix. - name: Compile Sketch - run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software + run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software diff --git a/.gitignore b/.gitignore index f4dd4582f..f0e530d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ compile.bat # Ignore binary files *.bin + +# Ignore secret file +USER_SECRETS.h \ No newline at end of file diff --git a/README.md b/README.md index f5ad6aa52..1c4e676ec 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This software enables EV battery packs to be used for stationary storage. It ach ## Hardware requirements ๐Ÿ“œ This code fits on the LilyGo ESP32 T-CAN485 devboard , see https://github.com/Xinyuan-LilyGO/T-CAN485 -You will also need a complete EV battery. [See the battery compability list on which are supported.](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki#supported-batteries-list) +You will also need a complete EV battery. [See the battery compatibility list on which are supported.](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki#supported-batteries-list) Finally, you will need a [compatible hybrid solar inverter](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki#supported-inverters-list), for example the "Fronius Gen24" or "GoodWe ET" @@ -36,7 +36,7 @@ Here's how to wire up the communication between the components. Here's how to connect the high voltage lines ![HighVoltageWiring](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/f70e6262-d630-4148-9a39-dad32e79b3d6) -For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki/Nissan-LEAF-battery#wiring-diagram) +For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200) ## How to compile the software ๐Ÿ’ป 1. Download the Arduino IDE: https://www.arduino.cc/en/software @@ -44,17 +44,19 @@ For more examples showing wiring, see each battery types own Wiki page. For inst 3. Click `File` menu -> `Preferences` -> `Additional Development` -> `Additional Board Manager URLs` -> Enter the URL in the input box: `https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json` and click OK. 4. Click `Tools` menu -> `Board: "...."` -> `Boards Manager...`, install the `esp32` package by `Espressif Systems` (not `Arduino ESP32 Boards`), then press `Close`. -**NOTE: The version depends on which release of Battery-Emulator you are running!** +**NOTE: The ESP32 version depends on which release of Battery-Emulator you are running!** - โš ๏ธ Make sure to use a 2.x.x version if you are on a release **older** than 6.0.0 (For instance ESP32 v2.0.11 when using Battery-Emulator v5.4.0) -- โš ๏ธ Make sure to use a 3.x.x version if you are on a release **newer** than 6.0.0 (For instance ESP32 v3.0.0 when using Battery-Emulator v6.0.0) - +- โš ๏ธ Make sure to use a 3.0.x version if you are on a release **newer** than 6.0.0 (For instance ESP32 v3.0.0 when using Battery-Emulator v6.0.0) +- โš ๏ธ Make sure to use a 3.1.x version if you are on a release **newer** than 8.0.0 (For instance ESP32 v3.1.0 when using Battery-Emulator v8.0.0) + ![bild](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/6a2414b1-f2ca-4746-8e8d-9afd78bd9252) 5. The Arduino board should be set to `ESP32 Dev Module` (under `Tools` -> `Board` -> `ESP32 Arduino`) with the following settings: ![alt text](https://github.com/Xinyuan-LilyGO/T-CAN485/blob/main/img/arduino_setting.png) 6. Select which battery type you will use, along with other optional settings. This is done in the `USER_SETTINGS.h` file. -7. Press `Verify` and `Upload` to send the sketch to the board. +7. Copy the `USER_SECRETS.TEMPLATE.h` file to `USER_SECRETS.h` and update connectivity settings inside this file. +8. Press `Verify` and `Upload` to send the sketch to the board. NOTE: In some cases, the LilyGo must be powered through the main power connector instead of USB-C when performing the initial firmware upload. NOTE: On Mac, the following USB driver may need to be installed: https://github.com/WCHSoftGroup/ch34xser_macos diff --git a/Software/Software.ino b/Software/Software.ino index 2074f1919..257878000 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -1,20 +1,28 @@ /* Do not change any code below this line unless you are sure what you are doing */ /* Only change battery specific settings in "USER_SETTINGS.h" */ - -#include "src/include.h" - #include "HardwareSerial.h" +#include "USER_SECRETS.h" #include "USER_SETTINGS.h" #include "esp_system.h" #include "esp_task_wdt.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "src/charger/CHARGERS.h" + +#include "src/communication/can/comm_can.h" +#include "src/communication/contactorcontrol/comm_contactorcontrol.h" +#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h" +#include "src/communication/nvm/comm_nvm.h" +#include "src/communication/rs485/comm_rs485.h" +#include "src/communication/seriallink/comm_seriallink.h" #include "src/datalayer/datalayer.h" +#include "src/devboard/sdcard/sdcard.h" #include "src/devboard/utils/events.h" #include "src/devboard/utils/led_handler.h" +#include "src/devboard/utils/logging.h" +#include "src/devboard/utils/timer.h" #include "src/devboard/utils/value_mapping.h" +#include "src/include.h" #include "src/lib/YiannisBourkelis-Uptime-Library/src/uptime.h" #include "src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h" #include "src/lib/bblanchon-ArduinoJson/ArduinoJson.h" @@ -23,7 +31,10 @@ #include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h" #include "src/lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" - +#ifndef AP_PASSWORD +#error \ + "Initial setup not completed, USER_SECRETS.h is missing. Please rename the file USER_SECRETS.TEMPLATE.h to USER_SECRETS.h and fill in the required credentials. This file is ignored by version control to keep sensitive information private." +#endif #ifdef WIFI #include "src/devboard/wifi/wifi.h" #ifdef WEBSERVER @@ -41,52 +52,14 @@ #endif // MQTT #endif // WIFI -#ifndef CONTACTOR_CONTROL -#ifdef PWM_CONTACTOR_CONTROL -#error CONTACTOR_CONTROL needs to be enabled for PWM_CONTACTOR_CONTROL -#endif -#endif - -#ifdef EQUIPMENT_STOP_BUTTON -#include "src/devboard/utils/debounce_button.h" -#endif - -Preferences settings; // Store user settings // The current software version, shown on webserver -const char* version_number = "7.8.dev"; +const char* version_number = "8.2.dev"; // Interval settings uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers unsigned long previousMillis10ms = 0; unsigned long previousMillisUpdateVal = 0; -// CAN parameters -CAN_device_t CAN_cfg; // CAN Config -const int rx_queue_size = 10; // Receive Queue size -volatile bool send_ok = 0; - -#ifdef DUAL_CAN -#include "src/lib/pierremolinaro-acan2515/ACAN2515.h" -static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h -ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT); -static ACAN2515_Buffer16 gBuffer; -#endif //DUAL_CAN -#ifdef CAN_FD -#include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" -ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT); -#endif //CAN_FD - -// ModbusRTU parameters -#ifdef MODBUS_INVERTER_SELECTED -#define MB_RTU_NUM_VALUES 30000 -uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory -// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout -ModbusServerRTU MBserver(Serial2, 2000); -#endif -#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) -#define SERIAL_LINK_BAUDRATE 112500 -#endif - // Common charger parameters volatile float charger_setpoint_HV_VDC = 0.0f; volatile float charger_setpoint_HV_IDC = 0.0f; @@ -109,73 +82,26 @@ MyTimer core_task_timer_10s(INTERVAL_10_S); int64_t connectivity_task_time_us; MyTimer connectivity_task_timer_10s(INTERVAL_10_S); +int64_t logging_task_time_us; +MyTimer logging_task_timer_10s(INTERVAL_10_S); + MyTimer loop_task_timer_10s(INTERVAL_10_S); MyTimer check_pause_2s(INTERVAL_2_S); -// Contactor parameters -#ifdef CONTACTOR_CONTROL -enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED }; -State contactorStatus = DISCONNECTED; - -#define ON 1 -#define OFF 0 - -#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic -#undef ON -#define ON 0 -#undef OFF -#define OFF 1 -#endif - -#define MAX_ALLOWED_FAULT_TICKS 1000 -/* NOTE: modify the precharge time constant below to account for the resistance and capacitance of the target system. - * t=3RC at minimum, t=5RC ideally - */ -#define PRECHARGE_TIME_MS 160 -#define NEGATIVE_CONTACTOR_TIME_MS 1000 -#define POSITIVE_CONTACTOR_TIME_MS 2000 -#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range -#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100% -#define PWM_HOLD_DUTY 250 -#define PWM_OFF_DUTY 0 -#define PWM_ON_DUTY 1023 -#define POSITIVE_PWM_Ch 0 -#define NEGATIVE_PWM_Ch 1 -unsigned long prechargeStartTime = 0; -unsigned long negativeStartTime = 0; -unsigned long timeSpentInFaultedMode = 0; -#endif - -void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFFFFFFFF) { -#ifdef PWM_CONTACTOR_CONTROL - if (pwm_freq != 0xFFFFFFFFFF) { - ledcWrite(pin, pwm_freq); - return; - } -#endif - if (direction == 1) { - digitalWrite(pin, HIGH); - } else { // 0 - digitalWrite(pin, LOW); - } -} - -#ifdef EQUIPMENT_STOP_BUTTON -const unsigned long equipment_button_long_press_duration = - 15000; // 15 seconds for long press in case of MOMENTARY_SWITCH -const unsigned long equipment_button_debounce_duration = 200; // 250ms for debouncing the button -unsigned long timeSincePress = 0; // Variable to store the time since the last press -DebouncedButton equipment_stop_button; // Debounced button object -#endif - TaskHandle_t main_loop_task; TaskHandle_t connectivity_loop_task; +TaskHandle_t logging_loop_task; + +Logging logging; // Initialization void setup() { init_serial(); + // We print this after setting up serial, such that is also printed to serial with DEBUG_VIA_USB set. + logging.printf("Battery emulator %s build " __DATE__ " " __TIME__ "\n", version_number); + init_stored_settings(); #ifdef WIFI @@ -183,6 +109,11 @@ void setup() { TASK_CONNECTIVITY_PRIO, &connectivity_loop_task, WIFI_CORE); #endif +#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) + xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, &logging_task_time_us, + TASK_CONNECTIVITY_PRIO, &logging_loop_task, WIFI_CORE); +#endif + init_events(); init_CAN(); @@ -198,6 +129,9 @@ void setup() { setup_battery(); #ifdef EQUIPMENT_STOP_BUTTON init_equipment_stop_button(); +#endif +#ifdef CAN_SHUNT_SELECTED + setup_can_shunt(); #endif // BOOT button at runtime is used as an input for various things pinMode(0, INPUT_PULLUP); @@ -222,6 +156,23 @@ void loop() { #endif } +#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) +void logging_loop(void* task_time_us) { + + init_logging_buffers(); + init_sdcard(); + + while (true) { +#ifdef LOG_TO_SD + write_log_to_sdcard(); +#endif +#ifdef LOG_CAN_TO_SD + write_can_frame_to_sdcard(); +#endif + } +} +#endif + #ifdef WIFI void connectivity_loop(void* task_time_us) { @@ -276,25 +227,19 @@ void core_loop(void* task_time_us) { #endif // Input, Runs as fast as possible - receive_can_native(); // Receive CAN messages from native CAN port -#ifdef CAN_FD - receive_canfd(); // Receive CAN-FD messages. -#endif -#ifdef DUAL_CAN - receive_can_addonMCP2515(); // Receive CAN messages on add-on MCP2515 chip -#endif + receive_can(); // Receive CAN messages #ifdef RS485_INVERTER_SELECTED receive_RS485(); // Process serial2 RS485 interface -#endif +#endif // RS485_INVERTER_SELECTED #if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) - runSerialDataLink(); -#endif + run_serialDataLink(); +#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us); #ifdef WEBSERVER START_TIME_MEASUREMENT(ota); ElegantOTA.loop(); END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us); -#endif +#endif // WEBSERVER START_TIME_MEASUREMENT(time_10ms); // Process @@ -312,26 +257,22 @@ void core_loop(void* task_time_us) { #ifdef DOUBLE_BATTERY update_values_battery2(); check_interconnect_available(); -#endif +#endif // DOUBLE_BATTERY update_calculated_values(); #ifndef SERIAL_LINK_RECEIVER update_machineryprotection(); // Check safeties (Not on serial link reciever board) -#endif - update_values_inverter(); // Update values heading towards inverter - if (DUMMY_EVENT_ENABLED) { - set_event(EVENT_DUMMY_ERROR, (uint8_t)millis()); - } +#endif // SERIAL_LINK_RECEIVER + update_values_inverter(); // Update values heading towards inverter } END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us); START_TIME_MEASUREMENT(cantx); // Output - send_can(); // Send CAN messages to all components + transmit_can(); // Send CAN messages to all components END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us); END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us); #ifdef FUNCTION_TIME_MEASUREMENT - if (datalayer.system.status.core_task_10s_max_us > datalayer.system.status.core_task_max_us) { // Update worst case total time datalayer.system.status.core_task_max_us = datalayer.system.status.core_task_10s_max_us; @@ -353,10 +294,9 @@ void core_loop(void* task_time_us) { datalayer.system.status.time_cantx_us = 0; datalayer.system.status.core_task_10s_max_us = 0; } - -#endif +#endif // FUNCTION_TIME_MEASUREMENT if (check_pause_2s.elapsed()) { - emulator_pause_state_send_CAN_battery(); + emulator_pause_state_transmit_can_battery(); } vTaskDelayUntil(&xLastWakeTime, xFrequency); @@ -370,352 +310,9 @@ void init_serial() { while (!Serial) {} #ifdef DEBUG_VIA_USB Serial.println("__ OK __"); -#endif -} - -void init_stored_settings() { - static uint32_t temp = 0; - 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) { - set_event(EVENT_EQUIPMENT_STOP, 1); - } - -#ifndef LOAD_SAVED_SETTINGS_ON_BOOT - settings.clear(); // If this clear function is executed, no settings will be read from storage - - //always save the equipment stop status - settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active); - -#endif - -#ifdef WIFI - - char tempSSIDstring[63]; // Allocate buffer with sufficient size - size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring)); - if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID! - ssid = tempSSIDstring; - } else { // Reading from settings failed. Do nothing with SSID. Raise event? - } - char tempPasswordString[63]; // Allocate buffer with sufficient size - size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString)); - if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password! - password = tempPasswordString; - } else { // Reading from settings failed. Do nothing with SSID. Raise event? - } -#endif - - temp = settings.getUInt("BATTERY_WH_MAX", false); - if (temp != 0) { - datalayer.battery.info.total_capacity_Wh = temp; - } - temp = settings.getUInt("MAXPERCENTAGE", false); - if (temp != 0) { - datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility - } - temp = settings.getUInt("MINPERCENTAGE", false); - if (temp != 0) { - datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility - } - temp = settings.getUInt("MAXCHARGEAMP", false); - if (temp != 0) { - datalayer.battery.settings.max_user_set_charge_dA = temp; - } - temp = settings.getUInt("MAXDISCHARGEAMP", false); - if (temp != 0) { - datalayer.battery.settings.max_user_set_discharge_dA = temp; - temp = settings.getBool("USE_SCALED_SOC", false); - datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block - } // No way to know if it wasnt reset otherwise - - settings.end(); -} - -void init_CAN() { -// CAN pins -#ifdef CAN_SE_PIN - pinMode(CAN_SE_PIN, OUTPUT); - digitalWrite(CAN_SE_PIN, LOW); -#endif - CAN_cfg.speed = CAN_SPEED_500KBPS; -#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed - CAN_cfg.speed = CAN_SPEED_250KBPS; -#endif - CAN_cfg.tx_pin_id = CAN_TX_PIN; - CAN_cfg.rx_pin_id = CAN_RX_PIN; - CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t)); - // Init CAN Module - ESP32Can.CANInit(); - -#ifdef DUAL_CAN -#ifdef DEBUG_VIA_USB - Serial.println("Dual CAN Bus (ESP32+MCP2515) selected"); -#endif - gBuffer.initWithSize(25); - SPI.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI); - ACAN2515Settings settings(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s - settings.mRequestedMode = ACAN2515Settings::NormalMode; - const uint16_t errorCodeMCP = can.begin(settings, [] { can.isr(); }); - if (errorCodeMCP == 0) { -#ifdef DEBUG_VIA_USB - Serial.println("Can ok"); -#endif - } else { -#ifdef DEBUG_VIA_USB - Serial.print("Error Can: 0x"); - Serial.println(errorCodeMCP, HEX); -#endif - set_event(EVENT_CANMCP_INIT_FAILURE, (uint8_t)errorCodeMCP); - } -#endif - -#ifdef CAN_FD -#ifdef DEBUG_VIA_USB - Serial.println("CAN FD add-on (ESP32+MCP2517) selected"); -#endif - SPI.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI); - ACAN2517FDSettings settings(CAN_FD_CRYSTAL_FREQUENCY_MHZ, 500 * 1000, - DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s -#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN - settings.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD -#else - settings.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD -#endif - const uint32_t errorCode = canfd.begin(settings, [] { canfd.isr(); }); - canfd.poll(); - if (errorCode == 0) { -#ifdef DEBUG_VIA_USB - Serial.print("Bit Rate prescaler: "); - Serial.println(settings.mBitRatePrescaler); - Serial.print("Arbitration Phase segment 1: "); - Serial.println(settings.mArbitrationPhaseSegment1); - Serial.print("Arbitration Phase segment 2: "); - Serial.println(settings.mArbitrationPhaseSegment2); - Serial.print("Arbitration SJW:"); - Serial.println(settings.mArbitrationSJW); - Serial.print("Actual Arbitration Bit Rate: "); - Serial.print(settings.actualArbitrationBitRate()); - Serial.println(" bit/s"); - Serial.print("Exact Arbitration Bit Rate ? "); - Serial.println(settings.exactArbitrationBitRate() ? "yes" : "no"); - Serial.print("Arbitration Sample point: "); - Serial.print(settings.arbitrationSamplePointFromBitStart()); - Serial.println("%"); -#endif - } else { -#ifdef DEBUG_VIA_USB - Serial.print("CAN-FD Configuration error 0x"); - Serial.println(errorCode, HEX); -#endif - set_event(EVENT_CANFD_INIT_FAILURE, (uint8_t)errorCode); - } -#endif -} - -void init_contactors() { - // Init contactor pins -#ifdef CONTACTOR_CONTROL -#ifdef PWM_CONTACTOR_CONTROL - ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, - POSITIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution - ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, - NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Positive PWM to 0% - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Negative PWM to 0% -#else //Normal CONTACTOR_CONTROL - pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); - set(POSITIVE_CONTACTOR_PIN, OFF); - pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); - set(NEGATIVE_CONTACTOR_PIN, OFF); -#endif - pinMode(PRECHARGE_PIN, OUTPUT); - set(PRECHARGE_PIN, OFF); -#endif //CONTACTOR_CONTROL -#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY - - pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT); - set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); - pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT); - set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); -#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY -// Init BMS contactor -#ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor - pinMode(BMS_POWER, OUTPUT); - digitalWrite(BMS_POWER, HIGH); -#endif //HW_STARK -} - -void init_rs485() { -// Set up Modbus RTU Server -#ifdef RS485_EN_PIN - pinMode(RS485_EN_PIN, OUTPUT); - digitalWrite(RS485_EN_PIN, HIGH); -#endif -#ifdef RS485_SE_PIN - pinMode(RS485_SE_PIN, OUTPUT); - digitalWrite(RS485_SE_PIN, HIGH); -#endif -#ifdef PIN_5V_EN - pinMode(PIN_5V_EN, OUTPUT); - digitalWrite(PIN_5V_EN, HIGH); -#endif -#ifdef RS485_INVERTER_SELECTED - Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); -#endif -#ifdef MODBUS_INVERTER_SELECTED -#ifdef BYD_MODBUS - // Init Static data to the RTU Modbus - handle_static_data_modbus_byd(); -#endif - - // Init Serial2 connected to the RTU Modbus - RTUutils::prepareHardwareSerial(Serial2); - Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); - // Register served function code worker for server - MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03); - MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06); - MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16); - MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23); - // Start ModbusRTU background task - MBserver.begin(Serial2, MODBUS_CORE); -#endif +#endif // DEBUG_VIA_USB } -#ifdef EQUIPMENT_STOP_BUTTON - -void monitor_equipment_stop_button() { - - ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress); - - if (equipment_stop_behavior == LATCHING_SWITCH) { - if (changed_state == PRESSED) { - // Changed to ON โ€“ initiating equipment stop. - setBatteryPause(true, false, true); - } else if (changed_state == RELEASED) { - // Changed to OFF โ€“ ending equipment stop. - setBatteryPause(false, false, false); - } - } else if (equipment_stop_behavior == MOMENTARY_SWITCH) { - if (changed_state == RELEASED) { // button is released - - if (timeSincePress < equipment_button_long_press_duration) { - // Short press detected, trigger equipment stop - setBatteryPause(true, false, true); - } else { - // Long press detected, reset equipment stop state - setBatteryPause(false, false, false); - } - } - } -} - -void init_equipment_stop_button() { - //using external pullup resistors NC - pinMode(EQUIPMENT_STOP_PIN, INPUT); - // Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time - initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration); -} - -#endif - -enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1 -void print_can_frame(CAN_frame frame, frameDirection msgDir); -void print_can_frame(CAN_frame frame, frameDirection msgDir) { - uint8_t i = 0; - Serial.print(millis()); - Serial.print(" "); - (msgDir == 0) ? Serial.print("RX ") : Serial.print("TX "); - Serial.print(frame.ID, HEX); - Serial.print(" "); - Serial.print(frame.DLC); - Serial.print(" "); - for (i = 0; i < frame.DLC; i++) { - Serial.print(frame.data.u8[i] < 16 ? "0" : ""); - Serial.print(frame.data.u8[i], HEX); - Serial.print(" "); - } - Serial.println(" "); -} - -#ifdef CAN_FD -// Functions -void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming - CANFDMessage frame; - if (canfd.available()) { - canfd.receive(frame); - - CAN_frame rx_frame; - rx_frame.ID = frame.id; - rx_frame.ext_ID = frame.ext; - rx_frame.DLC = frame.len; - for (uint8_t i = 0; i < rx_frame.DLC && i < 64; i++) { - rx_frame.data.u8[i] = frame.data[i]; - } - //message incoming, pass it on to the handler - receive_can(&rx_frame, CAN_ADDON_FD_MCP2518); - receive_can(&rx_frame, CANFD_NATIVE); - } -} -#endif - -void receive_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port - CAN_frame_t rx_frame_native; - if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame_native, 0) == pdTRUE) { - CAN_frame rx_frame; - rx_frame.ID = rx_frame_native.MsgID; - if (rx_frame_native.FIR.B.FF == CAN_frame_std) { - rx_frame.ext_ID = false; - } else { //CAN_frame_ext == 1 - rx_frame.ext_ID = true; - } - rx_frame.DLC = rx_frame_native.FIR.B.DLC; - for (uint8_t i = 0; i < rx_frame.DLC && i < 8; i++) { - rx_frame.data.u8[i] = rx_frame_native.data.u8[i]; - } - //message incoming, pass it on to the handler - receive_can(&rx_frame, CAN_NATIVE); - } -} - -void send_can() { - if (!allowed_to_send_CAN) { - return; - } - - send_can_battery(); - -#ifdef CAN_INVERTER_SELECTED - send_can_inverter(); -#endif // CAN_INVERTER_SELECTED - -#ifdef CHARGER_SELECTED - send_can_charger(); -#endif // CHARGER_SELECTED -} - -#ifdef DUAL_CAN -void receive_can_addonMCP2515() { // This section checks if we have a complete CAN message incoming on add-on CAN port - CAN_frame rx_frame; // Struct with our CAN format - CANMessage MCP2515Frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library - - if (can.available()) { - can.receive(MCP2515Frame); - - rx_frame.ID = MCP2515Frame.id; - rx_frame.ext_ID = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std; - rx_frame.DLC = MCP2515Frame.len; - for (uint8_t i = 0; i < MCP2515Frame.len && i < 8; i++) { - rx_frame.data.u8[i] = MCP2515Frame.data[i]; - } - - //message incoming, pass it on to the handler - receive_can(&rx_frame, CAN_ADDON_MCP2515); - } -} -#endif // DUAL_CAN - #ifdef DOUBLE_BATTERY void check_interconnect_available() { if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) { @@ -736,113 +333,7 @@ void check_interconnect_available() { set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10)); } } -#endif //DOUBLE_BATTERY - -void handle_contactors() { -#ifdef BYD_SMA - datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN); -#endif - -#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY - handle_contactors_battery2(); -#endif - -#ifdef CONTACTOR_CONTROL - // First check if we have any active errors, incase we do, turn off the battery - if (datalayer.battery.status.bms_status == FAULT) { - timeSpentInFaultedMode++; - } else { - timeSpentInFaultedMode = 0; - } - - //handle contactor control SHUTDOWN_REQUESTED - if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) { - contactorStatus = SHUTDOWN_REQUESTED; - } - - if (contactorStatus == SHUTDOWN_REQUESTED) { - set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); - set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); - set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); - datalayer.system.status.contactors_engaged = false; - return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) - } - - // After that, check if we are OK to start turning on the battery - if (contactorStatus == DISCONNECTED) { - set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); - set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); - - if (datalayer.system.status.battery_allows_contactor_closing && - datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) { - contactorStatus = PRECHARGE; - } - } - - // 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) { - contactorStatus = DISCONNECTED; - } - // Skip running the state machine below if it has already completed - return; - } - - unsigned long currentTime = millis(); - // Handle actual state machine. This first turns on Precharge, then Negative, then Positive, and finally turns OFF precharge - switch (contactorStatus) { - case PRECHARGE: - set(PRECHARGE_PIN, ON); - prechargeStartTime = currentTime; - contactorStatus = NEGATIVE; - break; - - case NEGATIVE: - if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) { - set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); - negativeStartTime = currentTime; - contactorStatus = POSITIVE; - } - break; - - case POSITIVE: - if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) { - set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); - contactorStatus = PRECHARGE_OFF; - } - break; - - case PRECHARGE_OFF: - if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) { - set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); - set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); - contactorStatus = COMPLETED; - datalayer.system.status.contactors_engaged = true; - } - break; - default: - break; - } -#endif // CONTACTOR_CONTROL -} - -#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY -void handle_contactors_battery2() { - if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) { - set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); - set(SECOND_POSITIVE_CONTACTOR_PIN, ON); - datalayer.system.status.contactors_battery2_engaged = true; - } else { // Closing contactors on secondary battery not allowed - set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); - set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); - datalayer.system.status.contactors_battery2_engaged = false; - } -} -#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY +#endif // DOUBLE_BATTERY void update_calculated_values() { /* Calculate allowed charge/discharge currents*/ @@ -901,8 +392,13 @@ void update_calculated_values() { calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc); calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000; // remove % capacity reserved in min_percentage to total_capacity_Wh - datalayer.battery.status.reported_remaining_capacity_Wh = - datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity; + if (datalayer.battery.status.remaining_capacity_Wh > calc_reserved_capacity) { + datalayer.battery.status.reported_remaining_capacity_Wh = + datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity; + } else { + datalayer.battery.status.reported_remaining_capacity_Wh = 0; + } + } else { datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } @@ -918,12 +414,16 @@ void update_calculated_values() { (datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc); calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000; // remove % capacity reserved in min_percentage to total_capacity_Wh - datalayer.battery2.status.reported_remaining_capacity_Wh = - datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity; + if (datalayer.battery2.status.remaining_capacity_Wh > calc_reserved_capacity) { + datalayer.battery2.status.reported_remaining_capacity_Wh = + datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity; + } else { + datalayer.battery2.status.reported_remaining_capacity_Wh = 0; + } } else { datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; } -#endif +#endif // DOUBLE_BATTERY } else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real. datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; @@ -952,65 +452,19 @@ void update_calculated_values() { datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; } -#endif //DOUBLE_BATTERY +#endif // DOUBLE_BATTERY } void update_values_inverter() { #ifdef CAN_INVERTER_SELECTED update_values_can_inverter(); -#endif +#endif // CAN_INVERTER_SELECTED #ifdef MODBUS_INVERTER_SELECTED update_modbus_registers_inverter(); -#endif +#endif // CAN_INVERTER_SELECTED #ifdef RS485_INVERTER_SELECTED update_RS485_registers_inverter(); -#endif -} - -#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) -void runSerialDataLink() { - static unsigned long updateTime = 0; - unsigned long currentMillis = millis(); - - if ((currentMillis - updateTime) > 1) { //Every 2ms - updateTime = currentMillis; -#ifdef SERIAL_LINK_RECEIVER - manageSerialLinkReceiver(); -#endif -#ifdef SERIAL_LINK_TRANSMITTER - manageSerialLinkTransmitter(); -#endif - } -} -#endif - -void init_serialDataLink() { -#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) - Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); -#endif -} - -void store_settings_equipment_stop() { - settings.begin("batterySettings", false); - settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active); - settings.end(); -} - -void storeSettings() { - settings.begin("batterySettings", false); -#ifdef WIFI - settings.putString("SSID", String(ssid.c_str())); - settings.putString("PASSWORD", String(password.c_str())); -#endif - settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh); - settings.putUInt("MAXPERCENTAGE", - datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility - settings.putUInt("MINPERCENTAGE", - datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility - settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA); - settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA); - settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active); - settings.end(); +#endif // CAN_INVERTER_SELECTED } /** Reset reason numbering and description @@ -1089,91 +543,3 @@ void check_reset_reason() { break; } } - -void transmit_can(CAN_frame* tx_frame, int interface) { - if (!allowed_to_send_CAN) { - return; - } -#ifdef DEBUG_CAN_DATA - print_can_frame(*tx_frame, frameDirection(MSG_TX)); -#endif //DEBUG_CAN_DATA - - switch (interface) { - case CAN_NATIVE: - CAN_frame_t frame; - frame.MsgID = tx_frame->ID; - frame.FIR.B.FF = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; - frame.FIR.B.DLC = tx_frame->DLC; - frame.FIR.B.RTR = CAN_no_RTR; - for (uint8_t i = 0; i < tx_frame->DLC; i++) { - frame.data.u8[i] = tx_frame->data.u8[i]; - } - ESP32Can.CANWriteFrame(&frame); - break; - case CAN_ADDON_MCP2515: { -#ifdef DUAL_CAN - //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 - CANMessage MCP2515Frame; - MCP2515Frame.id = tx_frame->ID; - MCP2515Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; - MCP2515Frame.len = tx_frame->DLC; - MCP2515Frame.rtr = false; - for (uint8_t i = 0; i < MCP2515Frame.len; i++) { - MCP2515Frame.data[i] = tx_frame->data.u8[i]; - } - can.tryToSend(MCP2515Frame); -#else // Interface not compiled, and settings try to use it - set_event(EVENT_INTERFACE_MISSING, interface); -#endif //DUAL_CAN - } break; - case CANFD_NATIVE: - case CAN_ADDON_FD_MCP2518: { -#ifdef CAN_FD - CANFDMessage MCP2518Frame; - MCP2518Frame.id = tx_frame->ID; - MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; - MCP2518Frame.len = tx_frame->DLC; - for (uint8_t i = 0; i < MCP2518Frame.len; i++) { - MCP2518Frame.data[i] = tx_frame->data.u8[i]; - } - send_ok = canfd.tryToSend(MCP2518Frame); - if (!send_ok) { - set_event(EVENT_CANFD_BUFFER_FULL, interface); - } -#else // Interface not compiled, and settings try to use it - set_event(EVENT_INTERFACE_MISSING, interface); -#endif //CAN_FD - } break; - default: - // Invalid interface sent with function call. TODO: Raise event that coders messed up - break; - } -} -void receive_can(CAN_frame* rx_frame, int interface) { - -#ifdef DEBUG_CAN_DATA - print_can_frame(*rx_frame, frameDirection(MSG_RX)); -#endif //DEBUG_CAN_DATA - - if (interface == can_config.battery) { - receive_can_battery(*rx_frame); -#ifdef CHADEMO_BATTERY - ISA_handleFrame(rx_frame); -#endif - } - if (interface == can_config.inverter) { -#ifdef CAN_INVERTER_SELECTED - receive_can_inverter(*rx_frame); -#endif - } - if (interface == can_config.battery_double) { -#ifdef DOUBLE_BATTERY - receive_can_battery2(*rx_frame); -#endif - } - if (interface == can_config.charger) { -#ifdef CHARGER_SELECTED - receive_can_charger(*rx_frame); -#endif - } -} diff --git a/Software/USER_SECRETS.TEMPLATE.h b/Software/USER_SECRETS.TEMPLATE.h new file mode 100644 index 000000000..1cd013b9a --- /dev/null +++ b/Software/USER_SECRETS.TEMPLATE.h @@ -0,0 +1,20 @@ +/* This file should be renamed to USER_SECRETS.h to be able to use the software! +It contains all the credentials that should never be made public */ + +//Password to the access point generated by the Battery-Emulator +#define AP_PASSWORD "123456789" // Minimum of 8 characters; set to blank if you want the access point to be open + +//Name and password of Wifi network you want the emulator to connect to +#define WIFI_SSID "REPLACE_WITH_YOUR_SSID" // Maximum of 63 characters +#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // Minimum of 8 characters + +//Set WEBSERVER_AUTH_REQUIRED to true to require a password when accessing the webserver homepage. Improves cybersecurity. +#define WEBSERVER_AUTH_REQUIRED false +#define HTTP_USERNAME "admin" // Username for webserver authentication +#define HTTP_PASSWORD "admin" // Password for webserver authentication + +//MQTT credentials +#define MQTT_SERVER "192.168.xxx.yyy" // MQTT server address +#define MQTT_PORT 1883 // MQTT server port +#define MQTT_USER NULL // MQTT username, leave blank for no authentication +#define MQTT_PASSWORD NULL // MQTT password, leave blank for no authentication diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 7e916e68e..3026ac801 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -1,5 +1,6 @@ #include "USER_SETTINGS.h" #include +#include "USER_SECRETS.h" #include "src/devboard/hal/hal.h" /* This file contains all the battery settings and limits */ @@ -9,42 +10,36 @@ CAN_NATIVE = Native CAN port on the LilyGo & Stark hardware CANFD_NATIVE = Native CANFD port on the Stark CMR hardware CAN_ADDON_MCP2515 = Add-on CAN MCP2515 connected to GPIO pins -CAN_ADDON_FD_MCP2518 = Add-on CAN-FD MCP2518 connected to GPIO pins +CANFD_ADDON_MCP2518 = Add-on CAN-FD MCP2518 connected to GPIO pins */ volatile CAN_Configuration can_config = { .battery = CAN_NATIVE, // Which CAN is your battery connected to? .inverter = CAN_NATIVE, // Which CAN is your inverter connected to? (No need to configure incase you use RS485) .battery_double = CAN_ADDON_MCP2515, // (OPTIONAL) Which CAN is your second battery connected to? - .charger = CAN_NATIVE // (OPTIONAL) Which CAN is your charger connected to? + .charger = CAN_NATIVE, // (OPTIONAL) Which CAN is your charger connected to? + .shunt = CAN_NATIVE // (OPTIONAL) Which CAN is your shunt connected to? }; -#ifdef WIFI - -volatile uint8_t AccessPointEnabled = true; //Set to either true/false to enable direct wifi access point -std::string ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 characters -std::string password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters +std::string ssid = WIFI_SSID; // Set in USER_SECRETS.h +std::string password = WIFI_PASSWORD; // Set in USER_SECRETS.h const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters, also used for device name on web interface -const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open -const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection +const char* passwordAP = AP_PASSWORD; // Set in USER_SECRETS.h +const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection -#ifdef WIFICONFIG -// Set your Static IP address +#ifdef WEBSERVER +const char* http_username = HTTP_USERNAME; // Set in USER_SECRETS.h +const char* http_password = HTTP_PASSWORD; // Set in USER_SECRETS.h +// Set your Static IP address. Only used incase WIFICONFIG is set in USER_SETTINGS.h IPAddress local_IP(192, 168, 10, 150); -// Set your Gateway IP address IPAddress gateway(192, 168, 10, 1); -// Set your Subnet IP address IPAddress subnet(255, 255, 255, 0); -#endif -#ifdef WEBSERVER -const char* http_username = "admin"; // username to webserver authentication; -const char* http_password = "admin"; // password to webserver authentication; - #endif // WEBSERVER + // MQTT #ifdef MQTT -const char* mqtt_user = "REDACTED"; // Set NULL for no username -const char* mqtt_password = "REDACTED"; // Set NULL for no password +const char* mqtt_user = MQTT_USER; // Set in USER_SECRETS.h +const char* mqtt_password = MQTT_PASSWORD; // Set in USER_SECRETS.h #ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME const char* mqtt_topic_name = "BE"; // Custom MQTT topic name. Previously, the name was automatically set to "battery-emulator_esp32-XXXXXX" @@ -52,9 +47,10 @@ const char* mqtt_object_id_prefix = "be_"; // Custom prefix for MQTT object ID. Previously, the prefix was automatically set to "esp32-XXXXXX_" const char* mqtt_device_name = "Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX" -#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME -#endif // USE_MQTT -#endif // WIFI +const char* ha_device_id = + "battery-emulator"; // Custom device ID in Home Assistant. Previously, the ID was always "battery-emulator" +#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME +#endif // USE_MQTT #ifdef EQUIPMENT_STOP_BUTTON // Equipment stop button behavior. Use NC button for safety reasons. diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 3c6c6763a..0e5843344 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -11,14 +11,16 @@ /* Select battery used */ //#define BMW_I3_BATTERY //#define BMW_IX_BATTERY +//#define BOLT_AMPERA_BATTERY //#define BYD_ATTO_3_BATTERY //#define CELLPOWER_BMS //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define IMIEV_CZERO_ION_BATTERY //#define JAGUAR_IPACE_BATTERY -//#define KIA_HYUNDAI_64_BATTERY //#define KIA_E_GMP_BATTERY +//#define KIA_HYUNDAI_64_BATTERY //#define KIA_HYUNDAI_HYBRID_BATTERY +//#define MEB_BATTERY //#define MG_5_BATTERY //#define NISSAN_LEAF_BATTERY //#define PYLON_BATTERY @@ -28,69 +30,80 @@ //#define RENAULT_TWIZY_BATTERY //#define RENAULT_ZOE_GEN1_BATTERY //#define RENAULT_ZOE_GEN2_BATTERY +//#define SONO_BATTERY //#define SANTA_FE_PHEV_BATTERY -//#define TESLA_MODEL_SX_BATTERY +//#define STELLANTIS_ECMP_BATTERY //#define TESLA_MODEL_3Y_BATTERY +//#define TESLA_MODEL_SX_BATTERY //#define VOLVO_SPA_BATTERY //#define TEST_FAKE_BATTERY -//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup) +//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires CAN_ADDON setup) /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ -//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus -//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus -//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU -//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 +//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus +//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus +//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485 +//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus +//#define GROWATT_LV_CAN //Enable this line to emulate a "48V Growatt Low Voltage battery" over CAN bus //#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus //#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus //#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus -//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus +//#define SMA_BYD_H_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" (SMA compatible) over CAN bus +//#define SMA_BYD_HVS_CAN //Enable this line to emulate a "BYD Battery-Box HVS 10.2KW battery" (SMA compatible) over CAN bus +//#define SMA_LV_CAN //Enable this line to emulate a "SMA Sunny Island 48V battery" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus //#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus /* Select hardware used for Battery-Emulator */ -#define HW_LILYGO +//#define HW_LILYGO //#define HW_STARK //#define HW_3LB +//#define HW_DEVKIT /* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */ +#define PRECHARGE_TIME_MS 500 //Precharge time in milliseconds. Modify to suit your inverter (See wiki for more info) //#define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins) //#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins) //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled. //#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting! +//#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF +//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF + +/* Shunt/Contactor settings */ +//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement /* Other options */ -//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) -//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production) -//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting -//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) -#define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency? -//#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board -#ifdef CAN_FD // CAN_FD additional options if enabled -#define CAN_FD_CRYSTAL_FREQUENCY_MHZ \ - ACAN2517FDSettings:: \ - OSC_40MHz //CAN_FD option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz) +//#define LOG_TO_SD //Enable this line to log diagnostic data to SD card +//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) +//#define DEBUG_VIA_WEB //Enable this line to log diagnostic data while program runs, which can be viewed via webpage (WARNING, slightly raises CPU load, do not use for production) +#if defined(DEBUG_VIA_USB) || defined(DEBUG_VIA_WEB) || defined(LOG_TO_SD) +#define DEBUG_LOG #endif + +//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production) +//#define LOG_CAN_TO_SD //Enable this line to log incoming/outgoing CAN & CAN-FD messages to SD card +//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting +//#define CAN_ADDON //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) +#define CRYSTAL_FREQUENCY_MHZ 8 //CAN_ADDON option, what is your MCP2515 add-on boards crystal frequency? +//#define CANFD_ADDON //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board +#define CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ \ + ACAN2517FDSettings::OSC_40MHz //CANFD_ADDON option, what is your MCP2518 add-on boards crystal frequency? //#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) #define WIFI //#define WIFICONFIG //Enable this line to set a static IP address / gateway /subnet mask for the device. see USER_SETTINGS.cpp for the settings #define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. -#define WEBSERVER_AUTH_REQUIRED \ - false //Set this line to true to activate webserver authentication (this line must not be commented). Refer to USER_SETTINGS.cpp for setting the credentials. -#define WIFIAP //Disable this line to permanently disable WIFI AP mode (make sure to hardcode ssid and password of you home wifi network). When enabled WIFI AP can still be disabled by a setting in the future. +#define WIFIAP //When enabled, the emulator will broadcast its own access point Wifi. Can be used at the same time as a normal Wifi connection to a router. #define MDNSRESPONDER //Enable this line to enable MDNS, allows battery monitor te be found by .local address. Requires WEBSERVER to be enabled. -#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot (overrides Wifi/battery settings set below) +#define LOAD_SAVED_SETTINGS_ON_BOOT // Enable this line to read settings stored via the webserver on boot (overrides Wifi credentials set here) //#define FUNCTION_TIME_MEASUREMENT // Enable this to record execution times and present them in the web UI (WARNING, raises CPU load, do not use for production) //#define EQUIPMENT_STOP_BUTTON // Enable this to allow an equipment stop button connected to the Battery-Emulator to disengage the battery /* MQTT options */ // #define MQTT // Enable this line to enable MQTT -#define MQTT_SERVER "192.168.xxx.yyy" -#define MQTT_PORT 1883 #define MQTT_MANUAL_TOPIC_OBJECT_NAME // Enable this to use custom MQTT topic, object ID prefix, and device name. \ // WARNING: If this is not defined, the previous default naming format \ // 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used. \ @@ -102,9 +115,6 @@ /* Home Assistant options */ #define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required -/* Event options*/ -#define DUMMY_EVENT_ENABLED false //Enable this line to have a dummy event that gets logged to test the interface - /* Select charger used (Optional) */ //#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function. //#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging @@ -122,19 +132,26 @@ #define BATTERY_MAXTEMPERATURE 500 // -250 = -25.0 ยฐC , Min temperature (Will produce a battery frozen event if below) #define BATTERY_MINTEMPERATURE -250 -// 300 = 30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited) +// 300 = 30.0A , Max charge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_CHARGE_AMP 300 -// 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited) +// 300 = 30.0A , Max discharge in Amp (Some inverters needs to be limited) #define BATTERY_MAX_DISCHARGE_AMP 300 +// Enable this to manually set voltage limits on how much battery can be discharged/charged. Normally not used. +#define BATTERY_USE_VOLTAGE_LIMITS false +// 5000 = 500.0V , Target charge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true +#define BATTERY_MAX_CHARGE_VOLTAGE 5000 +// 3000 = 300.0V, Target discharge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true +#define BATTERY_MAX_DISCHARGE_VOLTAGE 3000 -/* Do not change any code below this line unless you are sure what you are doing */ -/* Only change battery specific settings in "USER_SETTINGS.h" */ -typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CAN_ADDON_FD_MCP2518 = 3 } CAN_Interface; +/* Do not change any code below this line */ +/* Only change battery specific settings above and in "USER_SETTINGS.cpp" */ +typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CANFD_ADDON_MCP2518 = 3 } CAN_Interface; typedef struct { CAN_Interface battery; CAN_Interface inverter; CAN_Interface battery_double; CAN_Interface charger; + CAN_Interface shunt; } CAN_Configuration; extern volatile CAN_Configuration can_config; extern volatile uint8_t AccessPointEnabled; @@ -158,9 +175,7 @@ extern volatile STOP_BUTTON_BEHAVIOR equipment_stop_behavior; #ifdef WIFICONFIG extern IPAddress local_IP; -// Set your Gateway IP address extern IPAddress gateway; -// Set your Subnet IP address extern IPAddress subnet; #endif diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index e17d1c065..af62c8e91 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -2,6 +2,13 @@ #define BATTERIES_H #include "../../USER_SETTINGS.h" +#ifdef BMW_SBOX +#include "BMW-SBOX.h" +void handle_incoming_can_frame_shunt(CAN_frame rx_frame); +void transmit_can_shunt(); +void setup_can_shunt(); +#endif + #ifdef BMW_I3_BATTERY #include "BMW-I3-BATTERY.h" #endif @@ -10,6 +17,10 @@ #include "BMW-IX-BATTERY.h" #endif +#ifdef BOLT_AMPERA_BATTERY +#include "BOLT-AMPERA-BATTERY.h" +#endif + #ifdef BYD_ATTO_3_BATTERY #include "BYD-ATTO-3-BATTERY.h" #endif @@ -23,6 +34,14 @@ #include "CHADEMO-SHUNTS.h" #endif +#ifdef SONO_BATTERY +#include "SONO-BATTERY.h" +#endif + +#ifdef STELLANTIS_ECMP_BATTERY +#include "ECMP-BATTERY.h" +#endif + #ifdef IMIEV_CZERO_ION_BATTERY #include "IMIEV-CZERO-ION-BATTERY.h" #endif @@ -43,6 +62,10 @@ #include "KIA-HYUNDAI-HYBRID-BATTERY.h" #endif +#ifdef MEB_BATTERY +#include "MEB-BATTERY.h" +#endif + #ifdef MG_5_BATTERY #include "MG-5-BATTERY.h" #endif @@ -100,14 +123,14 @@ #include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" #endif -void receive_can_battery(CAN_frame rx_frame); +void handle_incoming_can_frame_battery(CAN_frame rx_frame); void update_values_battery(); -void send_can_battery(); +void transmit_can_battery(); void setup_battery(void); #ifdef DOUBLE_BATTERY void update_values_battery2(); -void receive_can_battery2(CAN_frame rx_frame); +void handle_incoming_can_frame_battery2(CAN_frame rx_frame); #endif #endif diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index e43eaa5ae..55bc2b975 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -19,6 +19,7 @@ static unsigned long previousMillis10000 = 0; // will store last time a 10000ms enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH }; static BatterySize detectedBattery = BATTERY_60AH; +static BatterySize detectedBattery2 = BATTERY_60AH; // For double battery setups enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST }; @@ -269,7 +270,6 @@ static uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection static uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0; static uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0; static uint8_t battery_ID2 = 0; -static uint8_t battery_cellvoltage_mux = 0; static uint8_t battery_soh = 99; static uint32_t battery2_serial_number = 0; @@ -337,7 +337,6 @@ static uint8_t battery2_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection static uint8_t battery2_status_diagnosis_powertrain_maximum_multiplexer = 0; static uint8_t battery2_status_diagnosis_powertrain_immediate_multiplexer = 0; static uint8_t battery2_ID2 = 0; -static uint8_t battery2_cellvoltage_mux = 0; static uint8_t battery2_soh = 99; static uint8_t message_data[50]; @@ -392,12 +391,12 @@ void update_values_battery2() { //This function maps all the values fetched via if (battery2_info_available) { // Start checking safeties. First up, cellvoltages! - if (detectedBattery == BATTERY_60AH) { + if (detectedBattery2 == BATTERY_60AH) { datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH; datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH; datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH; datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH; - } else if (detectedBattery == BATTERY_94AH) { + } else if (detectedBattery2 == BATTERY_94AH) { datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH; datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH; datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH; @@ -425,9 +424,15 @@ void update_values_battery2() { //This function maps all the values fetched via void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer if (datalayer.system.settings.equipment_stop_active == true) { - digitalWrite(WUP_PIN, LOW); // Turn off WUP_PIN + digitalWrite(WUP_PIN1, LOW); // Turn off WUP_PIN1 +#if defined(WUP_PIN2) && defined(DOUBLE_BATTERY) + digitalWrite(WUP_PIN2, LOW); // Turn off WUP_PIN2 +#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY) } else { - digitalWrite(WUP_PIN, HIGH); // Wake up the battery + digitalWrite(WUP_PIN1, HIGH); // Wake up the battery +#if defined(WUP_PIN2) && defined(DOUBLE_BATTERY) + digitalWrite(WUP_PIN2, HIGH); // Wake up the battery2 +#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY) } if (!battery_awake) { @@ -502,7 +507,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bmwi3.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve; } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2 battery_awake = true; @@ -632,7 +637,7 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x607: //BMS - responses to message requests on 0x615 if ((cmdState == CELL_VOLTAGE_CELLNO || cmdState == CELL_VOLTAGE_CELLNO_LAST) && (rx_frame.data.u8[0] == 0xF4)) { if (rx_frame.DLC == 6) { - transmit_can(&BMW_6F4_CELL_CONTINUE, can_config.battery); // tell battery to send the cellvoltage + transmit_can_frame(&BMW_6F4_CELL_CONTINUE, can_config.battery); // tell battery to send the cellvoltage } if (rx_frame.DLC == 8) { // We have the full value, map it datalayer.battery.status.cell_voltages_mV[current_cell_polled - 1] = @@ -645,7 +650,7 @@ void receive_can_battery(CAN_frame rx_frame) { while (count < rx_frame.DLC && next_data < 49) { message_data[next_data++] = rx_frame.data.u8[count++]; } - transmit_can(&BMW_6F1_CONTINUE, can_config.battery); // tell battery to send additional messages + transmit_can_frame(&BMW_6F1_CONTINUE, can_config.battery); // tell battery to send additional messages } else if (rx_frame.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 && ((rx_frame.data.u8[1] & 0xF0) == 0x20)) { @@ -681,7 +686,7 @@ void receive_can_battery(CAN_frame rx_frame) { break; } } -void receive_can_battery2(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2 battery2_awake = true; @@ -776,18 +781,6 @@ void receive_can_battery2(CAN_frame rx_frame) { case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2 battery2_status_cooling_HV = (rx_frame.data.u8[1] & 0x03); break; - case 0x426: // TODO: Figure out how to trigger sending of this. Does the SME require some CAN command? - battery2_cellvoltage_mux = rx_frame.data.u8[0]; - if (battery2_cellvoltage_mux == 0) { - datalayer.battery2.status.cell_voltages_mV[0] = ((rx_frame.data.u8[1] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[2] = ((rx_frame.data.u8[3] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800); - datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800); - } - break; case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2 battery2_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); battery2_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); @@ -801,6 +794,13 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); battery2_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4]; battery2_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50; + if (battery2_energy_content_maximum_kWh > 33) { + detectedBattery2 = BATTERY_120AH; + } else if (battery2_energy_content_maximum_kWh > 20) { + detectedBattery2 = BATTERY_94AH; + } else { + detectedBattery2 = BATTERY_60AH; + } break; case 0x432: //BMS [200ms] SOC% info battery2_request_operating_mode = (rx_frame.data.u8[0] & 0x03); @@ -815,12 +815,23 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_ID2 = rx_frame.data.u8[0]; break; case 0x607: //BMS - responses to message requests on 0x615 + if ((cmdState == CELL_VOLTAGE_CELLNO || cmdState == CELL_VOLTAGE_CELLNO_LAST) && (rx_frame.data.u8[0] == 0xF4)) { + if (rx_frame.DLC == 6) { + transmit_can_frame(&BMW_6F4_CELL_CONTINUE, + can_config.battery_double); // tell battery to send the cellvoltage + } + if (rx_frame.DLC == 8) { // We have the full value, map it + datalayer.battery2.status.cell_voltages_mV[current_cell_polled - 1] = + (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]); + } + } + if (rx_frame.DLC > 6 && next_data == 0 && rx_frame.data.u8[0] == 0xf1) { uint8_t count2 = 6; while (count2 < rx_frame.DLC && next_data < 49) { message_data[next_data++] = rx_frame.data.u8[count2++]; } - transmit_can(&BMW_6F1_CONTINUE, can_config.battery_double); + transmit_can_frame(&BMW_6F1_CONTINUE, can_config.battery_double); } else if (rx_frame.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 && ((rx_frame.data.u8[1] & 0xF0) == 0x20)) { @@ -856,7 +867,7 @@ void receive_can_battery2(CAN_frame rx_frame) { break; } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); if (battery_awake) { @@ -887,12 +898,12 @@ void send_can_battery() { if (datalayer.battery.status.bms_status == FAULT) { } //If battery is not in Fault mode, allow contactor to close by sending 10B else { - transmit_can(&BMW_10B, can_config.battery); + transmit_can_frame(&BMW_10B, can_config.battery); } #ifdef DOUBLE_BATTERY //If second battery is allowed to join in, also send 10B if (datalayer.system.status.battery2_allows_contactor_closing == true) { - transmit_can(&BMW_10B, can_config.battery_double); + transmit_can_frame(&BMW_10B, can_config.battery_double); } #endif } @@ -905,9 +916,9 @@ void send_can_battery() { alive_counter_100ms = increment_alive_counter(alive_counter_100ms); - transmit_can(&BMW_12F, can_config.battery); + transmit_can_frame(&BMW_12F, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_12F, can_config.battery_double); + transmit_can_frame(&BMW_12F, can_config.battery_double); #endif } // Send 200ms CAN Message @@ -919,9 +930,9 @@ void send_can_battery() { alive_counter_200ms = increment_alive_counter(alive_counter_200ms); - transmit_can(&BMW_19B, can_config.battery); + transmit_can_frame(&BMW_19B, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_19B, can_config.battery_double); + transmit_can_frame(&BMW_19B, can_config.battery_double); #endif } // Send 500ms CAN Message @@ -933,20 +944,20 @@ void send_can_battery() { alive_counter_500ms = increment_alive_counter(alive_counter_500ms); - transmit_can(&BMW_30B, can_config.battery); + transmit_can_frame(&BMW_30B, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_30B, can_config.battery_double); + transmit_can_frame(&BMW_30B, can_config.battery_double); #endif } // Send 640ms CAN Message if (currentMillis - previousMillis640 >= INTERVAL_640_MS) { previousMillis640 = currentMillis; - transmit_can(&BMW_512, can_config.battery); // Keep BMS alive - transmit_can(&BMW_5F8, can_config.battery); + transmit_can_frame(&BMW_512, can_config.battery); // Keep BMS alive + transmit_can_frame(&BMW_5F8, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_512, can_config.battery_double); - transmit_can(&BMW_5F8, can_config.battery_double); + transmit_can_frame(&BMW_512, can_config.battery_double); + transmit_can_frame(&BMW_5F8, can_config.battery_double); #endif } // Send 1000ms CAN Message @@ -973,39 +984,39 @@ void send_can_battery() { alive_counter_1000ms = increment_alive_counter(alive_counter_1000ms); - transmit_can(&BMW_3E8, can_config.battery); //Order comes from CAN logs - transmit_can(&BMW_328, can_config.battery); - transmit_can(&BMW_3F9, can_config.battery); - transmit_can(&BMW_2E2, can_config.battery); - transmit_can(&BMW_41D, can_config.battery); - transmit_can(&BMW_3D0, can_config.battery); - transmit_can(&BMW_3CA, can_config.battery); - transmit_can(&BMW_3A7, can_config.battery); - transmit_can(&BMW_2CA, can_config.battery); - transmit_can(&BMW_3FB, can_config.battery); - transmit_can(&BMW_418, can_config.battery); - transmit_can(&BMW_1D0, can_config.battery); - transmit_can(&BMW_3EC, can_config.battery); - transmit_can(&BMW_192, can_config.battery); - transmit_can(&BMW_13E, can_config.battery); - transmit_can(&BMW_433, can_config.battery); + transmit_can_frame(&BMW_3E8, can_config.battery); //Order comes from CAN logs + transmit_can_frame(&BMW_328, can_config.battery); + transmit_can_frame(&BMW_3F9, can_config.battery); + transmit_can_frame(&BMW_2E2, can_config.battery); + transmit_can_frame(&BMW_41D, can_config.battery); + transmit_can_frame(&BMW_3D0, can_config.battery); + transmit_can_frame(&BMW_3CA, can_config.battery); + transmit_can_frame(&BMW_3A7, can_config.battery); + transmit_can_frame(&BMW_2CA, can_config.battery); + transmit_can_frame(&BMW_3FB, can_config.battery); + transmit_can_frame(&BMW_418, can_config.battery); + transmit_can_frame(&BMW_1D0, can_config.battery); + transmit_can_frame(&BMW_3EC, can_config.battery); + transmit_can_frame(&BMW_192, can_config.battery); + transmit_can_frame(&BMW_13E, can_config.battery); + transmit_can_frame(&BMW_433, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_3E8, can_config.battery_double); - transmit_can(&BMW_328, can_config.battery_double); - transmit_can(&BMW_3F9, can_config.battery_double); - transmit_can(&BMW_2E2, can_config.battery_double); - transmit_can(&BMW_41D, can_config.battery_double); - transmit_can(&BMW_3D0, can_config.battery_double); - transmit_can(&BMW_3CA, can_config.battery_double); - transmit_can(&BMW_3A7, can_config.battery_double); - transmit_can(&BMW_2CA, can_config.battery_double); - transmit_can(&BMW_3FB, can_config.battery_double); - transmit_can(&BMW_418, can_config.battery_double); - transmit_can(&BMW_1D0, can_config.battery_double); - transmit_can(&BMW_3EC, can_config.battery_double); - transmit_can(&BMW_192, can_config.battery_double); - transmit_can(&BMW_13E, can_config.battery_double); - transmit_can(&BMW_433, can_config.battery_double); + transmit_can_frame(&BMW_3E8, can_config.battery_double); + transmit_can_frame(&BMW_328, can_config.battery_double); + transmit_can_frame(&BMW_3F9, can_config.battery_double); + transmit_can_frame(&BMW_2E2, can_config.battery_double); + transmit_can_frame(&BMW_41D, can_config.battery_double); + transmit_can_frame(&BMW_3D0, can_config.battery_double); + transmit_can_frame(&BMW_3CA, can_config.battery_double); + transmit_can_frame(&BMW_3A7, can_config.battery_double); + transmit_can_frame(&BMW_2CA, can_config.battery_double); + transmit_can_frame(&BMW_3FB, can_config.battery_double); + transmit_can_frame(&BMW_418, can_config.battery_double); + transmit_can_frame(&BMW_1D0, can_config.battery_double); + transmit_can_frame(&BMW_3EC, can_config.battery_double); + transmit_can_frame(&BMW_192, can_config.battery_double); + transmit_can_frame(&BMW_13E, can_config.battery_double); + transmit_can_frame(&BMW_433, can_config.battery_double); #endif BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this @@ -1014,23 +1025,23 @@ void send_can_battery() { next_data = 0; switch (cmdState) { case SOC: - transmit_can(&BMW_6F1_CELL, can_config.battery); + transmit_can_frame(&BMW_6F1_CELL, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_6F1_CELL, can_config.battery_double); + transmit_can_frame(&BMW_6F1_CELL, can_config.battery_double); #endif cmdState = CELL_VOLTAGE_MINMAX; break; case CELL_VOLTAGE_MINMAX: - transmit_can(&BMW_6F1_SOH, can_config.battery); + transmit_can_frame(&BMW_6F1_SOH, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_6F1_SOH, can_config.battery_double); + transmit_can_frame(&BMW_6F1_SOH, can_config.battery_double); #endif cmdState = SOH; break; case SOH: - transmit_can(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery); + transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery_double); + transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery_double); #endif cmdState = CELL_VOLTAGE_CELLNO; current_cell_polled = 0; @@ -1045,16 +1056,16 @@ void send_can_battery() { cmdState = CELL_VOLTAGE_CELLNO; BMW_6F4_CELL_VOLTAGE_CELLNO.data.u8[6] = current_cell_polled; - transmit_can(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery); + transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery_double); + transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery_double); #endif } break; case CELL_VOLTAGE_CELLNO_LAST: - transmit_can(&BMW_6F1_SOC, can_config.battery); + transmit_can_frame(&BMW_6F1_SOC, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_6F1_SOC, can_config.battery_double); + transmit_can_frame(&BMW_6F1_SOC, can_config.battery_double); #endif cmdState = SOC; break; @@ -1067,25 +1078,25 @@ void send_can_battery() { BMW_3FC.data.u8[1] = ((BMW_3FC.data.u8[1] & 0xF0) + alive_counter_5000ms); BMW_3C5.data.u8[0] = ((BMW_3C5.data.u8[0] & 0xF0) + alive_counter_5000ms); - transmit_can(&BMW_3FC, can_config.battery); //Order comes from CAN logs - transmit_can(&BMW_3C5, can_config.battery); - transmit_can(&BMW_3A0, can_config.battery); - transmit_can(&BMW_592_0, can_config.battery); - transmit_can(&BMW_592_1, can_config.battery); + transmit_can_frame(&BMW_3FC, can_config.battery); //Order comes from CAN logs + transmit_can_frame(&BMW_3C5, can_config.battery); + transmit_can_frame(&BMW_3A0, can_config.battery); + transmit_can_frame(&BMW_592_0, can_config.battery); + transmit_can_frame(&BMW_592_1, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_3FC, can_config.battery_double); - transmit_can(&BMW_3C5, can_config.battery_double); - transmit_can(&BMW_3A0, can_config.battery_double); - transmit_can(&BMW_592_0, can_config.battery_double); - transmit_can(&BMW_592_1, can_config.battery_double); + transmit_can_frame(&BMW_3FC, can_config.battery_double); + transmit_can_frame(&BMW_3C5, can_config.battery_double); + transmit_can_frame(&BMW_3A0, can_config.battery_double); + transmit_can_frame(&BMW_592_0, can_config.battery_double); + transmit_can_frame(&BMW_592_1, can_config.battery_double); #endif alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms); if (BMW_380_counter < 3) { - transmit_can(&BMW_380, can_config.battery); // This message stops after 3 times on startup + transmit_can_frame(&BMW_380, can_config.battery); // This message stops after 3 times on startup #ifdef DOUBLE_BATTERY - transmit_can(&BMW_380, can_config.battery_double); + transmit_can_frame(&BMW_380, can_config.battery_double); #endif BMW_380_counter++; } @@ -1094,13 +1105,13 @@ void send_can_battery() { if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { previousMillis10000 = currentMillis; - transmit_can(&BMW_3E5, can_config.battery); //Order comes from CAN logs - transmit_can(&BMW_3E4, can_config.battery); - transmit_can(&BMW_37B, can_config.battery); + transmit_can_frame(&BMW_3E5, can_config.battery); //Order comes from CAN logs + transmit_can_frame(&BMW_3E4, can_config.battery); + transmit_can_frame(&BMW_37B, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&BMW_3E5, can_config.battery_double); - transmit_can(&BMW_3E4, can_config.battery_double); - transmit_can(&BMW_37B, can_config.battery_double); + transmit_can_frame(&BMW_3E5, can_config.battery_double); + transmit_can_frame(&BMW_3E4, can_config.battery_double); + transmit_can_frame(&BMW_37B, can_config.battery_double); #endif BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this @@ -1134,8 +1145,12 @@ void setup_battery(void) { // Performs one time setup at startup datalayer.battery2.status.voltage_dV = 0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding #endif - pinMode(WUP_PIN, OUTPUT); - digitalWrite(WUP_PIN, HIGH); // Wake up the battery + pinMode(WUP_PIN1, OUTPUT); + digitalWrite(WUP_PIN1, HIGH); // Wake up the battery +#if defined(DOUBLE_BATTERY) && defined(WUP_PIN2) + pinMode(WUP_PIN2, OUTPUT); + digitalWrite(WUP_PIN2, HIGH); // Wake up the battery +#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY) } #endif diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index 63228ea8a..ca5e95532 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -5,7 +5,6 @@ #define BATTERY_SELECTED -#define WUP_PIN 25 #define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value #define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value @@ -20,6 +19,6 @@ #define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value #define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/BMW-IX-BATTERY.cpp b/Software/src/battery/BMW-IX-BATTERY.cpp index bcd229836..4296793fd 100644 --- a/Software/src/battery/BMW-IX-BATTERY.cpp +++ b/Software/src/battery/BMW-IX-BATTERY.cpp @@ -516,7 +516,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; } } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { battery_awake = true; switch (rx_frame.ID) { case 0x112: @@ -540,7 +540,7 @@ void receive_can_battery(CAN_frame rx_frame) { } //Frame has continued data - so request it - transmit_can(&BMWiX_6F4_CONTINUE_DATA, can_config.battery); + transmit_can_frame(&BMWiX_6F4_CONTINUE_DATA, can_config.battery); } if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 && @@ -670,11 +670,11 @@ void receive_can_battery(CAN_frame rx_frame) { if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 || (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot -#ifdef DEBUG_VIA_USB - Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); +#ifdef DEBUG_LOG + logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); #endif //set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type - transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); + transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); } else { //Only ingest values if they are not the 10V Error state min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]); max_cell_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]); @@ -724,7 +724,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); //if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms @@ -734,10 +734,10 @@ void send_can_battery() { //Loop through and send a different UDS request each cycle uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter); - transmit_can(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); + transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery); //Send SME Keep alive values 100ms - transmit_can(&BMWiX_510, can_config.battery); + transmit_can_frame(&BMWiX_510, can_config.battery); } // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { @@ -745,16 +745,16 @@ void send_can_battery() { //Send SME Keep alive values 200ms BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1 - transmit_can(&BMWiX_0C0, can_config.battery); + transmit_can_frame(&BMWiX_0C0, can_config.battery); } // Send 1000ms CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; //Send SME Keep alive values 1000ms - //Don't believe this is needed: transmit_can(&BMWiX_06D, can_config.battery); - //Don't believe this is needed: transmit_can(&BMWiX_2F1, can_config.battery); - //Don't believe this is needed: transmit_can(&BMWiX_439, can_config.battery); + //Don't believe this is needed: transmit_can_frame(&BMWiX_06D, can_config.battery); + //Don't believe this is needed: transmit_can_frame(&BMWiX_2F1, can_config.battery); + //Don't believe this is needed: transmit_can_frame(&BMWiX_439, can_config.battery); } // Send 5000ms CAN Message if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index a53367781..e04a0d878 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -5,7 +5,6 @@ #define BATTERY_SELECTED -//#define WUP_PIN 25 //Not used #define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V #define MIN_PACK_VOLTAGE_DV 3000 #define MAX_CELL_DEVIATION_MV 250 @@ -18,6 +17,6 @@ #define STALE_PERIOD_CONFIG \ 300000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 300 seconds void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/BMW-SBOX.cpp b/Software/src/battery/BMW-SBOX.cpp new file mode 100644 index 000000000..ff0139b45 --- /dev/null +++ b/Software/src/battery/BMW-SBOX.cpp @@ -0,0 +1,218 @@ +#include "../include.h" +#ifdef BMW_SBOX +#include "../datalayer/datalayer.h" +#include "BMW-SBOX.h" + +#define MAX_ALLOWED_FAULT_TICKS 1000 + +enum SboxState { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED }; +SboxState contactorStatus = DISCONNECTED; + +unsigned long prechargeStartTime = 0; +unsigned long negativeStartTime = 0; +unsigned long positiveStartTime = 0; +unsigned long timeSpentInFaultedMode = 0; +unsigned long LastMsgTime = 0; // will store last time a 20ms CAN Message was send +unsigned long LastAvgTime = 0; // Last current storage time +unsigned long ShuntLastSeen = 0; + +uint32_t avg_mA_array[10]; +uint32_t avg_sum; + +uint8_t k; //avg array pointer + +uint8_t CAN100_cnt = 0; + +CAN_frame SBOX_100 = {.FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x100, + .data = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00}}; // Byte 0: relay control, Byte 1: counter 0-E, Byte 4: CRC + +CAN_frame SBOX_300 = {.FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x300, + .data = {0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}}; // Static frame + +uint8_t reverse_bits(uint8_t byte) { + uint8_t reversed = 0; + for (int i = 0; i < 8; i++) { + reversed = (reversed << 1) | (byte & 1); + byte >>= 1; + } + return reversed; +} + +/** CRC8, both inverted, poly 0x31 **/ +uint8_t calculateCRC(CAN_frame CAN) { + uint8_t crc = 0; + for (size_t i = 0; i < CAN.DLC; i++) { + uint8_t reversed_byte = reverse_bits(CAN.data.u8[i]); + crc ^= reversed_byte; + for (int j = 0; j < 8; j++) { + if (crc & 0x80) { + crc = (crc << 1) ^ 0x31; + } else { + crc <<= 1; + } + crc &= 0xFF; + } + } + crc = reverse_bits(crc); + return crc; +} + +void handle_incoming_can_frame_shunt(CAN_frame rx_frame) { + unsigned long currentTime = millis(); + if (rx_frame.ID == 0x200) { + ShuntLastSeen = currentTime; + datalayer.shunt.measured_amperage_mA = + ((rx_frame.data.u8[2] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[0] << 8)) / 256; + datalayer.shunt.measured_amperage_dA = datalayer.shunt.measured_amperage_mA / 100; + + /** Calculate 1S avg current **/ + if (LastAvgTime + 100 < currentTime) { + LastAvgTime = currentTime; + if (k > 9) { + k = 0; + } + avg_mA_array[k] = datalayer.shunt.measured_amperage_mA; + k++; + avg_sum = 0; + for (uint8_t i = 0; i < 10; i++) { + avg_sum = avg_sum + avg_mA_array[i]; + } + datalayer.shunt.measured_avg1S_amperage_mA = avg_sum / 10; + } + } else if (rx_frame.ID == 0x210) //SBOX input (battery side) voltage + { + ShuntLastSeen = currentTime; + datalayer.shunt.measured_voltage_mV = + ((rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | (rx_frame.data.u8[0])); + } else if (rx_frame.ID == 0x220) //SBOX output voltage + { + ShuntLastSeen = currentTime; + datalayer.shunt.measured_outvoltage_mV = + ((rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) | (rx_frame.data.u8[0])); + datalayer.shunt.available = true; + } +} + +void transmit_can_shunt() { + unsigned long currentTime = millis(); + + /** Shunt can frames seen? **/ + if (ShuntLastSeen + 1000 < currentTime) { + datalayer.shunt.available = false; + } else { + datalayer.shunt.available = true; + } + // Send 20ms CAN Message + if (currentTime - LastMsgTime >= INTERVAL_20_MS) { + LastMsgTime = currentTime; + // First check if we have any active errors, incase we do, turn off the battery + if (datalayer.battery.status.bms_status == FAULT) { + timeSpentInFaultedMode++; + } else { + timeSpentInFaultedMode = 0; + } + + //handle contactor control SHUTDOWN_REQUESTED + if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) { + contactorStatus = SHUTDOWN_REQUESTED; + SBOX_100.data.u8[0] = 0x55; // All open + } + + if (contactorStatus == SHUTDOWN_REQUESTED) { + datalayer.shunt.contactors_engaged = false; + return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) + } + + // After that, check if we are OK to start turning on the contactors + if (contactorStatus == DISCONNECTED) { + datalayer.shunt.contactors_engaged = false; + 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.shunt.measured_voltage_mV > MINIMUM_INPUT_VOLTAGE * 1000)) { + contactorStatus = PRECHARGE; + } + } + // 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) { + contactorStatus = DISCONNECTED; + } + } + // Handle actual state machine. This first turns on Precharge, then Negative, then Positive, and finally turns OFF precharge + switch (contactorStatus) { + case PRECHARGE: + SBOX_100.data.u8[0] = 0x86; // Precharge relay only + prechargeStartTime = currentTime; + contactorStatus = NEGATIVE; +#ifdef DEBUG_VIA_USB + Serial.println("S-BOX Precharge relay engaged"); +#endif + break; + case NEGATIVE: + if (currentTime - prechargeStartTime >= CONTACTOR_CONTROL_T1) { + SBOX_100.data.u8[0] = 0xA6; // Precharge + Negative + negativeStartTime = currentTime; + contactorStatus = POSITIVE; + datalayer.shunt.precharging = true; +#ifdef DEBUG_VIA_USB + Serial.println("S-BOX Negative relay engaged"); +#endif + } + break; + case POSITIVE: + if (currentTime - negativeStartTime >= CONTACTOR_CONTROL_T2 && + (datalayer.shunt.measured_voltage_mV * MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT < + datalayer.shunt.measured_outvoltage_mV)) { + SBOX_100.data.u8[0] = 0xAA; // Precharge + Negative + Positive + positiveStartTime = currentTime; + contactorStatus = PRECHARGE_OFF; + datalayer.shunt.precharging = false; +#ifdef DEBUG_VIA_USB + Serial.println("S-BOX Positive relay engaged"); +#endif + } + break; + case PRECHARGE_OFF: + if (currentTime - positiveStartTime >= CONTACTOR_CONTROL_T3) { + SBOX_100.data.u8[0] = 0x6A; // Negative + Positive + contactorStatus = COMPLETED; +#ifdef DEBUG_VIA_USB + Serial.println("S-BOX Precharge relay released"); +#endif + datalayer.shunt.contactors_engaged = true; + } + break; + case COMPLETED: + SBOX_100.data.u8[0] = 0x6A; // Negative + Positive + default: + break; + } + CAN100_cnt++; + if (CAN100_cnt > 0x0E) { + CAN100_cnt = 0; + } + SBOX_100.data.u8[1] = CAN100_cnt << 4 | 0x01; + SBOX_100.data.u8[3] = 0x00; + SBOX_100.data.u8[3] = calculateCRC(SBOX_100); + transmit_can_frame(&SBOX_100, can_config.shunt); + transmit_can_frame(&SBOX_300, can_config.shunt); + } +} + +void setup_can_shunt() { + strncpy(datalayer.system.info.shunt_protocol, "BMW SBOX", 63); + datalayer.system.info.shunt_protocol[63] = '\0'; +} +#endif diff --git a/Software/src/battery/BMW-SBOX.h b/Software/src/battery/BMW-SBOX.h new file mode 100644 index 000000000..131fa3a0e --- /dev/null +++ b/Software/src/battery/BMW-SBOX.h @@ -0,0 +1,22 @@ +#ifndef BMW_SBOX_CONTROL_H +#define BMW_SBOX_CONTROL_H +#include "../include.h" +#define CAN_SHUNT_SELECTED +void transmit_can(CAN_frame* tx_frame, int interface); + +/** Minimum input voltage required to enable relay control **/ +#define MINIMUM_INPUT_VOLTAGE 250 + +/** Minimum required percentage of input voltage at the output port to engage the positive relay. **/ +/** Prevents engagement of the positive contactor if the precharge resistor is faulty. **/ +#define MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT 0.99 + +/* NOTE: modify the T2 time constant below to account for the resistance and capacitance of the target system. + * t=3RC at minimum, t=5RC ideally + */ + +#define CONTACTOR_CONTROL_T1 5000 // Time before negative contactor engages and precharging starts +#define CONTACTOR_CONTROL_T2 5000 // Precharge time before precharge resistor is bypassed by positive contactor +#define CONTACTOR_CONTROL_T3 2000 // Precharge relay lead time after positive contactor has been engaged + +#endif diff --git a/Software/src/battery/BOLT-AMPERA-BATTERY.cpp b/Software/src/battery/BOLT-AMPERA-BATTERY.cpp new file mode 100644 index 000000000..e7c2e0f68 --- /dev/null +++ b/Software/src/battery/BOLT-AMPERA-BATTERY.cpp @@ -0,0 +1,789 @@ +#include "../include.h" +#ifdef BOLT_AMPERA_BATTERY +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "../devboard/utils/events.h" +#include "BOLT-AMPERA-BATTERY.h" + +/* +TODOs left for this implementation +- The battery has 3 CAN ports. One of them is responsible for the 7E4 polls, the other for the 7E7 polls +- Current implementation only seems to get the 7E7 polls working. +- Could on of the CAN channels be GMLAN? + +- The values missing for a working implementation is: +- SOC% missing! This is absolutely mandatory to fix before starting to use this! +- Capacity (kWh) (can be estimated) +- Charge max power (can be estimated) +- Discharge max power (can be estimated) +- SOH% (low prio)) +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis120ms = 0; // will store last time a 120ms CAN Message was send + +CAN_frame BOLT_778 = {.FD = false, // Unsure of what this message is, added only as example + .ext_ID = false, + .DLC = 7, + .ID = 0x778, + .data = {0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame BOLT_POLL_7E4 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x7E4, + .data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame BOLT_ACK_7E4 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x7E4, + .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame BOLT_POLL_7E7 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x7E7, + .data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame BOLT_ACK_7E7 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x7E7, + .data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +// 7E4 Battery , reply 000007EC +// 7E7 Battery (Cell voltages), reply 000007EF + +static uint16_t battery_cell_voltages[96]; //array with all the cellvoltages +static uint16_t battery_capacity_my17_18 = 0; +static uint16_t battery_capacity_my19plus = 0; +static uint16_t battery_SOC_display = 0; +static uint16_t battery_SOC_raw_highprec = 0; +static uint16_t battery_max_temperature = 0; +static uint16_t battery_min_temperature = 0; +static uint16_t battery_min_cell_voltage = 0; +static uint16_t battery_max_cell_voltage = 0; +static uint16_t battery_internal_resistance = 0; +static uint16_t battery_lowest_cell = 0; +static uint16_t battery_highest_cell = 0; +static uint16_t battery_voltage_polled = 0; +static uint16_t battery_voltage_periodic = 0; +static uint16_t battery_vehicle_isolation = 0; +static uint16_t battery_isolation_kohm = 0; +static uint16_t battery_HV_locked = 0; +static uint16_t battery_crash_event = 0; +static uint16_t battery_HVIL = 0; +static uint16_t battery_HVIL_status = 0; +static uint16_t battery_5V_ref = 0; +static int16_t battery_current_7E4 = 0; +static int16_t battery_module_temp_1 = 0; +static int16_t battery_module_temp_2 = 0; +static int16_t battery_module_temp_3 = 0; +static int16_t battery_module_temp_4 = 0; +static int16_t battery_module_temp_5 = 0; +static int16_t battery_module_temp_6 = 0; +static uint16_t battery_cell_average_voltage = 0; +static uint16_t battery_cell_average_voltage_2 = 0; +static uint16_t battery_terminal_voltage = 0; +static uint16_t battery_ignition_power_mode = 0; +static int16_t battery_current_7E7 = 0; +static int16_t temperature_1 = 0; +static int16_t temperature_2 = 0; +static int16_t temperature_3 = 0; +static int16_t temperature_4 = 0; +static int16_t temperature_5 = 0; +static int16_t temperature_6 = 0; +static int16_t temperature_highest = 0; +static int16_t temperature_lowest = 0; +static uint8_t mux = 0; +static uint8_t poll_index_7E4 = 0; +static uint16_t currentpoll_7E4 = POLL_7E4_CAPACITY_EST_GEN1; +static uint16_t reply_poll_7E4 = 0; +static uint8_t poll_index_7E7 = 0; +static uint16_t currentpoll_7E7 = POLL_7E7_CURRENT; +static uint16_t reply_poll_7E7 = 0; + +const uint16_t poll_commands_7E4[19] = {POLL_7E4_CAPACITY_EST_GEN1, + POLL_7E4_CAPACITY_EST_GEN2, + POLL_7E4_SOC_DISPLAY, + POLL_7E4_SOC_RAW_HIGHPREC, + POLL_7E4_MAX_TEMPERATURE, + POLL_7E4_MIN_TEMPERATURE, + POLL_7E4_MIN_CELL_V, + POLL_7E4_MAX_CELL_V, + POLL_7E4_INTERNAL_RES, + POLL_7E4_LOWEST_CELL_NUMBER, + POLL_7E4_HIGHEST_CELL_NUMBER, + POLL_7E4_VOLTAGE, + POLL_7E4_VEHICLE_ISOLATION, + POLL_7E4_ISOLATION_TEST_KOHM, + POLL_7E4_HV_LOCKED_OUT, + POLL_7E4_CRASH_EVENT, + POLL_7E4_HVIL, + POLL_7E4_HVIL_STATUS, + POLL_7E4_CURRENT}; + +const uint16_t poll_commands_7E7[108] = {POLL_7E7_CURRENT, POLL_7E7_5V_REF, + POLL_7E7_MODULE_TEMP_1, POLL_7E7_MODULE_TEMP_2, + POLL_7E7_MODULE_TEMP_3, POLL_7E7_MODULE_TEMP_4, + POLL_7E7_MODULE_TEMP_5, POLL_7E7_MODULE_TEMP_6, + POLL_7E7_CELL_AVG_VOLTAGE, POLL_7E7_CELL_AVG_VOLTAGE_2, + POLL_7E7_TERMINAL_VOLTAGE, POLL_7E7_IGNITION_POWER_MODE, + POLL_7E7_CELL_01, POLL_7E7_CELL_02, + POLL_7E7_CELL_03, POLL_7E7_CELL_04, + POLL_7E7_CELL_05, POLL_7E7_CELL_06, + POLL_7E7_CELL_07, POLL_7E7_CELL_08, + POLL_7E7_CELL_09, POLL_7E7_CELL_10, + POLL_7E7_CELL_11, POLL_7E7_CELL_12, + POLL_7E7_CELL_13, POLL_7E7_CELL_14, + POLL_7E7_CELL_15, POLL_7E7_CELL_16, + POLL_7E7_CELL_17, POLL_7E7_CELL_18, + POLL_7E7_CELL_19, POLL_7E7_CELL_20, + POLL_7E7_CELL_21, POLL_7E7_CELL_22, + POLL_7E7_CELL_23, POLL_7E7_CELL_24, + POLL_7E7_CELL_25, POLL_7E7_CELL_26, + POLL_7E7_CELL_27, POLL_7E7_CELL_28, + POLL_7E7_CELL_29, POLL_7E7_CELL_30, + POLL_7E7_CELL_31, POLL_7E7_CELL_32, + POLL_7E7_CELL_33, POLL_7E7_CELL_34, + POLL_7E7_CELL_35, POLL_7E7_CELL_36, + POLL_7E7_CELL_37, POLL_7E7_CELL_38, + POLL_7E7_CELL_39, POLL_7E7_CELL_40, + POLL_7E7_CELL_41, POLL_7E7_CELL_42, + POLL_7E7_CELL_43, POLL_7E7_CELL_44, + POLL_7E7_CELL_45, POLL_7E7_CELL_46, + POLL_7E7_CELL_47, POLL_7E7_CELL_48, + POLL_7E7_CELL_49, POLL_7E7_CELL_50, + POLL_7E7_CELL_51, POLL_7E7_CELL_52, + POLL_7E7_CELL_53, POLL_7E7_CELL_54, + POLL_7E7_CELL_55, POLL_7E7_CELL_56, + POLL_7E7_CELL_57, POLL_7E7_CELL_58, + POLL_7E7_CELL_59, POLL_7E7_CELL_60, + POLL_7E7_CELL_61, POLL_7E7_CELL_62, + POLL_7E7_CELL_63, POLL_7E7_CELL_64, + POLL_7E7_CELL_65, POLL_7E7_CELL_66, + POLL_7E7_CELL_67, POLL_7E7_CELL_68, + POLL_7E7_CELL_69, POLL_7E7_CELL_70, + POLL_7E7_CELL_71, POLL_7E7_CELL_72, + POLL_7E7_CELL_73, POLL_7E7_CELL_74, + POLL_7E7_CELL_75, POLL_7E7_CELL_76, + POLL_7E7_CELL_77, POLL_7E7_CELL_78, + POLL_7E7_CELL_79, POLL_7E7_CELL_80, + POLL_7E7_CELL_81, POLL_7E7_CELL_82, + POLL_7E7_CELL_83, POLL_7E7_CELL_84, + POLL_7E7_CELL_85, POLL_7E7_CELL_86, + POLL_7E7_CELL_87, POLL_7E7_CELL_88, + POLL_7E7_CELL_89, POLL_7E7_CELL_90, + POLL_7E7_CELL_91, POLL_7E7_CELL_92, + POLL_7E7_CELL_93, POLL_7E7_CELL_94, + POLL_7E7_CELL_95, POLL_7E7_CELL_96}; + +void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer + + datalayer.battery.status.real_soc = battery_SOC_display; + + //datalayer.battery.status.voltage_dV = battery_voltage * 0.52; + datalayer.battery.status.voltage_dV = (battery_voltage_periodic / 8) * 10; + + datalayer.battery.status.current_dA = battery_current_7E7; + + datalayer.battery.info.total_capacity_Wh; + + datalayer.battery.status.remaining_capacity_Wh; + + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.max_discharge_power_W; + + datalayer.battery.status.max_charge_power_W; + + // Store temperatures in an array + int16_t temperatures[] = {temperature_1, temperature_2, temperature_3, temperature_4, temperature_5, temperature_6}; + + // Initialize highest and lowest to the first element + temperature_highest = temperatures[0]; + temperature_lowest = temperatures[0]; + + // Iterate through the array to find the highest and lowest values + for (uint8_t i = 1; i < 6; ++i) { + if (temperatures[i] > temperature_highest) { + temperature_highest = temperatures[i]; + } + if (temperatures[i] < temperature_lowest) { + temperature_lowest = temperatures[i]; + } + } + + datalayer.battery.status.temperature_min_dC = temperature_lowest * 10; + + datalayer.battery.status.temperature_max_dC = temperature_highest * 10; + + //Map all cell voltages to the global array + memcpy(datalayer.battery.status.cell_voltages_mV, battery_cell_voltages, 96 * sizeof(uint16_t)); + + // Update webserver datalayer + datalayer_extended.boltampera.battery_5V_ref = battery_5V_ref; + datalayer_extended.boltampera.battery_module_temp_1 = battery_module_temp_1; + datalayer_extended.boltampera.battery_module_temp_2 = battery_module_temp_2; + datalayer_extended.boltampera.battery_module_temp_3 = battery_module_temp_3; + datalayer_extended.boltampera.battery_module_temp_4 = battery_module_temp_4; + datalayer_extended.boltampera.battery_module_temp_5 = battery_module_temp_5; + datalayer_extended.boltampera.battery_module_temp_6 = battery_module_temp_6; + datalayer_extended.boltampera.battery_cell_average_voltage = battery_cell_average_voltage; + datalayer_extended.boltampera.battery_cell_average_voltage_2 = battery_cell_average_voltage_2; + datalayer_extended.boltampera.battery_terminal_voltage = battery_terminal_voltage; + datalayer_extended.boltampera.battery_ignition_power_mode = battery_ignition_power_mode; + datalayer_extended.boltampera.battery_current_7E7 = battery_current_7E7; + datalayer_extended.boltampera.battery_capacity_my17_18 = battery_capacity_my17_18; + datalayer_extended.boltampera.battery_capacity_my19plus = battery_capacity_my19plus; + datalayer_extended.boltampera.battery_SOC_display = battery_SOC_display; + datalayer_extended.boltampera.battery_SOC_raw_highprec = battery_SOC_raw_highprec; + datalayer_extended.boltampera.battery_max_temperature = battery_max_temperature; + datalayer_extended.boltampera.battery_min_temperature = battery_min_temperature; + datalayer_extended.boltampera.battery_min_cell_voltage = battery_min_cell_voltage; + datalayer_extended.boltampera.battery_max_cell_voltage = battery_max_cell_voltage; + datalayer_extended.boltampera.battery_lowest_cell = battery_lowest_cell; + datalayer_extended.boltampera.battery_highest_cell = battery_highest_cell; + datalayer_extended.boltampera.battery_internal_resistance = battery_internal_resistance; + datalayer_extended.boltampera.battery_voltage_polled = battery_voltage_polled; + datalayer_extended.boltampera.battery_vehicle_isolation = battery_vehicle_isolation; + datalayer_extended.boltampera.battery_isolation_kohm = battery_isolation_kohm; + datalayer_extended.boltampera.battery_HV_locked = battery_HV_locked; + datalayer_extended.boltampera.battery_crash_event = battery_crash_event; + datalayer_extended.boltampera.battery_HVIL = battery_HVIL; + datalayer_extended.boltampera.battery_HVIL_status = battery_HVIL_status; + datalayer_extended.boltampera.battery_current_7E4 = battery_current_7E4; +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x200: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7 + break; + case 0x202: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7 + break; + case 0x204: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7 + break; + case 0x206: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7 + break; + case 0x208: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7 + break; + case 0x20C: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x216: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x2C7: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_voltage_periodic = (rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4); + break; + case 0x260: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x270: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x272: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x274: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x302: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + temperature_1 = ((rx_frame.data.u8[1] / 2) - 40); //Module 1 Temperature + temperature_2 = ((rx_frame.data.u8[2] / 2) - 40); //Module 2 Temperature + temperature_3 = ((rx_frame.data.u8[3] / 2) - 40); //Module 3 Temperature + temperature_4 = ((rx_frame.data.u8[4] / 2) - 40); //Module 4 Temperature + temperature_5 = ((rx_frame.data.u8[5] / 2) - 40); //Module 5 Temperature + temperature_6 = ((rx_frame.data.u8[6] / 2) - 40); //Module 6 Temperature + break; + case 0x3E3: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x460: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x5EF: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x7EC: //When polling 7E4 BMS replies with 7EC ?? + + if (rx_frame.data.u8[0] == 0x10) { //"PID Header" + transmit_can_frame(&BOLT_ACK_7E4, can_config.battery); + } + + //Frame 2 & 3 contains reply + reply_poll_7E4 = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + + switch (reply_poll_7E4) { + case POLL_7E4_CAPACITY_EST_GEN1: + battery_capacity_my17_18 = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case POLL_7E4_CAPACITY_EST_GEN2: + battery_capacity_my19plus = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case POLL_7E4_SOC_DISPLAY: + battery_SOC_display = ((rx_frame.data.u8[4] * 100) / 255); + break; + case POLL_7E4_SOC_RAW_HIGHPREC: + battery_SOC_raw_highprec = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 100) / 65535); + break; + case POLL_7E4_MAX_TEMPERATURE: + battery_max_temperature = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E4_MIN_TEMPERATURE: + battery_min_temperature = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E4_MIN_CELL_V: + battery_min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 1666; + break; + case POLL_7E4_MAX_CELL_V: + battery_max_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 1666; + break; + case POLL_7E4_INTERNAL_RES: + battery_internal_resistance = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 2; + break; + case POLL_7E4_LOWEST_CELL_NUMBER: + battery_lowest_cell = rx_frame.data.u8[4]; + break; + case POLL_7E4_HIGHEST_CELL_NUMBER: + battery_highest_cell = rx_frame.data.u8[4]; + break; + case POLL_7E4_VOLTAGE: + battery_voltage_polled = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.52); + break; + case POLL_7E4_VEHICLE_ISOLATION: + battery_vehicle_isolation = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case POLL_7E4_ISOLATION_TEST_KOHM: + battery_isolation_kohm = (rx_frame.data.u8[4] * 25); + break; + case POLL_7E4_HV_LOCKED_OUT: + battery_HV_locked = rx_frame.data.u8[4]; + break; + case POLL_7E4_CRASH_EVENT: + battery_crash_event = rx_frame.data.u8[4]; + break; + case POLL_7E4_HVIL: + battery_HVIL = rx_frame.data.u8[4]; + break; + case POLL_7E4_HVIL_STATUS: + battery_HVIL_status = rx_frame.data.u8[4]; + break; + case POLL_7E4_CURRENT: + battery_current_7E4 = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / (-6.675)); + break; + default: + break; + } + + break; + case 0x7EF: //When polling 7E7 BMS replies with 7EF + + if (rx_frame.data.u8[0] == 0x10) { //"PID Header" + transmit_can_frame(&BOLT_ACK_7E7, can_config.battery); + } + + //Frame 2 & 3 contains reply + reply_poll_7E7 = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + + switch (reply_poll_7E7) { + case POLL_7E7_CURRENT: + battery_current_7E7 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case POLL_7E7_5V_REF: + battery_5V_ref = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5) / 65535); + break; + case POLL_7E7_MODULE_TEMP_1: + battery_module_temp_1 = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E7_MODULE_TEMP_2: + battery_module_temp_2 = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E7_MODULE_TEMP_3: + battery_module_temp_3 = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E7_MODULE_TEMP_4: + battery_module_temp_4 = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E7_MODULE_TEMP_5: + battery_module_temp_5 = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E7_MODULE_TEMP_6: + battery_module_temp_6 = (rx_frame.data.u8[4] - 40); + break; + case POLL_7E7_CELL_AVG_VOLTAGE: + battery_cell_average_voltage = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535); + break; + case POLL_7E7_CELL_AVG_VOLTAGE_2: + battery_cell_average_voltage_2 = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8000) * 1000); + break; + case POLL_7E7_TERMINAL_VOLTAGE: + battery_terminal_voltage = rx_frame.data.u8[4] * 2; + break; + 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: + break; + } + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + + //Send 20ms message + if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis20ms >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20ms)); + } else { + clear_event(EVENT_CAN_OVERRUN); + } + previousMillis20ms = currentMillis; + transmit_can_frame(&BOLT_778, can_config.battery); + } + + //Send 100ms message + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + // Update current poll from the 7E7 array + currentpoll_7E7 = poll_commands_7E7[poll_index_7E7]; + poll_index_7E7 = (poll_index_7E7 + 1) % 108; + + BOLT_POLL_7E7.data.u8[2] = (uint8_t)((currentpoll_7E7 & 0xFF00) >> 8); + BOLT_POLL_7E7.data.u8[3] = (uint8_t)(currentpoll_7E7 & 0x00FF); + + transmit_can_frame(&BOLT_POLL_7E7, can_config.battery); + } + + //Send 120ms message + if (currentMillis - previousMillis120ms >= 120) { + previousMillis120ms = currentMillis; + + // Update current poll from the 7E4 array + currentpoll_7E4 = poll_commands_7E4[poll_index_7E4]; + poll_index_7E4 = (poll_index_7E4 + 1) % 19; + + BOLT_POLL_7E4.data.u8[2] = (uint8_t)((currentpoll_7E4 & 0xFF00) >> 8); + BOLT_POLL_7E4.data.u8[3] = (uint8_t)(currentpoll_7E4 & 0x00FF); + + transmit_can_frame(&BOLT_POLL_7E4, can_config.battery); + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "Chevrolet Bolt EV/Opel Ampera-e", 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; + 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; +} + +#endif diff --git a/Software/src/battery/BOLT-AMPERA-BATTERY.h b/Software/src/battery/BOLT-AMPERA-BATTERY.h new file mode 100644 index 000000000..56a515fd8 --- /dev/null +++ b/Software/src/battery/BOLT-AMPERA-BATTERY.h @@ -0,0 +1,146 @@ +#ifndef BOLT_AMPERA_BATTERY_H +#define BOLT_AMPERA_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED + +#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 2500 +#define MAX_CELL_DEVIATION_MV 500 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +#define POLL_7E4_CAPACITY_EST_GEN1 0x41A3 +#define POLL_7E4_CAPACITY_EST_GEN2 0x45F9 +#define POLL_7E4_SOC_DISPLAY 0x8334 +#define POLL_7E4_SOC_RAW_HIGHPREC 0x43AF +#define POLL_7E4_MAX_TEMPERATURE 0x4349 +#define POLL_7E4_MIN_TEMPERATURE 0x434A +#define POLL_7E4_MIN_CELL_V 0x4329 +#define POLL_7E4_MAX_CELL_V 0x432B +#define POLL_7E4_INTERNAL_RES 0x40E9 +#define POLL_7E4_LOWEST_CELL_NUMBER 0x433B +#define POLL_7E4_HIGHEST_CELL_NUMBER 0x433C +#define POLL_7E4_VOLTAGE 0x432D +#define POLL_7E4_VEHICLE_ISOLATION 0x41EC +#define POLL_7E4_ISOLATION_TEST_KOHM 0x43A6 +#define POLL_7E4_HV_LOCKED_OUT 0x44F8 +#define POLL_7E4_CRASH_EVENT 0x4522 +#define POLL_7E4_HVIL 0x4310 +#define POLL_7E4_HVIL_STATUS 0x4311 +#define POLL_7E4_CURRENT 0x4356 + +#define POLL_7E7_CURRENT 0x40D4 +#define POLL_7E7_5V_REF 0x40D3 +#define POLL_7E7_MODULE_TEMP_1 0x40D7 +#define POLL_7E7_MODULE_TEMP_2 0x40D9 +#define POLL_7E7_MODULE_TEMP_3 0x40DB +#define POLL_7E7_MODULE_TEMP_4 0x40DD +#define POLL_7E7_MODULE_TEMP_5 0x40DF +#define POLL_7E7_MODULE_TEMP_6 0x40E1 +#define POLL_7E7_CELL_AVG_VOLTAGE 0xC218 +#define POLL_7E7_CELL_AVG_VOLTAGE_2 0x44B9 +#define POLL_7E7_TERMINAL_VOLTAGE 0x82A3 +#define POLL_7E7_IGNITION_POWER_MODE 0x8002 +#define POLL_7E7_CELL_01 0x4181 +#define POLL_7E7_CELL_02 0x4182 +#define POLL_7E7_CELL_03 0x4183 +#define POLL_7E7_CELL_04 0x4184 +#define POLL_7E7_CELL_05 0x4185 +#define POLL_7E7_CELL_06 0x4186 +#define POLL_7E7_CELL_07 0x4187 +#define POLL_7E7_CELL_08 0x4188 +#define POLL_7E7_CELL_09 0x4189 +#define POLL_7E7_CELL_10 0x418A +#define POLL_7E7_CELL_11 0x418B +#define POLL_7E7_CELL_12 0x418C +#define POLL_7E7_CELL_13 0x418D +#define POLL_7E7_CELL_14 0x418E +#define POLL_7E7_CELL_15 0x418F +#define POLL_7E7_CELL_16 0x4190 +#define POLL_7E7_CELL_17 0x4191 +#define POLL_7E7_CELL_18 0x4192 +#define POLL_7E7_CELL_19 0x4193 +#define POLL_7E7_CELL_20 0x4194 +#define POLL_7E7_CELL_21 0x4195 +#define POLL_7E7_CELL_22 0x4196 +#define POLL_7E7_CELL_23 0x4197 +#define POLL_7E7_CELL_24 0x4198 +#define POLL_7E7_CELL_25 0x4199 +#define POLL_7E7_CELL_26 0x419A +#define POLL_7E7_CELL_27 0x419B +#define POLL_7E7_CELL_28 0x419C +#define POLL_7E7_CELL_29 0x419D +#define POLL_7E7_CELL_30 0x419E +#define POLL_7E7_CELL_31 0x419F +#define POLL_7E7_CELL_32 0x4200 +#define POLL_7E7_CELL_33 0x4201 +#define POLL_7E7_CELL_34 0x4202 +#define POLL_7E7_CELL_35 0x4203 +#define POLL_7E7_CELL_36 0x4204 +#define POLL_7E7_CELL_37 0x4205 +#define POLL_7E7_CELL_38 0x4206 +#define POLL_7E7_CELL_39 0x4207 +#define POLL_7E7_CELL_40 0x4208 +#define POLL_7E7_CELL_41 0x4209 +#define POLL_7E7_CELL_42 0x420A +#define POLL_7E7_CELL_43 0x420B +#define POLL_7E7_CELL_44 0x420C +#define POLL_7E7_CELL_45 0x420D +#define POLL_7E7_CELL_46 0x420E +#define POLL_7E7_CELL_47 0x420F +#define POLL_7E7_CELL_48 0x4210 +#define POLL_7E7_CELL_49 0x4211 +#define POLL_7E7_CELL_50 0x4212 +#define POLL_7E7_CELL_51 0x4213 +#define POLL_7E7_CELL_52 0x4214 +#define POLL_7E7_CELL_53 0x4215 +#define POLL_7E7_CELL_54 0x4216 +#define POLL_7E7_CELL_55 0x4217 +#define POLL_7E7_CELL_56 0x4218 +#define POLL_7E7_CELL_57 0x4219 +#define POLL_7E7_CELL_58 0x421A +#define POLL_7E7_CELL_59 0x421B +#define POLL_7E7_CELL_60 0x421C +#define POLL_7E7_CELL_61 0x421D +#define POLL_7E7_CELL_62 0x421E +#define POLL_7E7_CELL_63 0x421F +#define POLL_7E7_CELL_64 0x4220 +#define POLL_7E7_CELL_65 0x4221 +#define POLL_7E7_CELL_66 0x4222 +#define POLL_7E7_CELL_67 0x4223 +#define POLL_7E7_CELL_68 0x4224 +#define POLL_7E7_CELL_69 0x4225 +#define POLL_7E7_CELL_70 0x4226 +#define POLL_7E7_CELL_71 0x4227 +#define POLL_7E7_CELL_72 0x4228 +#define POLL_7E7_CELL_73 0x4229 +#define POLL_7E7_CELL_74 0x422A +#define POLL_7E7_CELL_75 0x422B +#define POLL_7E7_CELL_76 0x422C +#define POLL_7E7_CELL_77 0x422D +#define POLL_7E7_CELL_78 0x422E +#define POLL_7E7_CELL_79 0x422F +#define POLL_7E7_CELL_80 0x4230 +#define POLL_7E7_CELL_81 0x4231 +#define POLL_7E7_CELL_82 0x4232 +#define POLL_7E7_CELL_83 0x4233 +#define POLL_7E7_CELL_84 0x4234 +#define POLL_7E7_CELL_85 0x4235 +#define POLL_7E7_CELL_86 0x4236 +#define POLL_7E7_CELL_87 0x4237 +#define POLL_7E7_CELL_88 0x4238 +#define POLL_7E7_CELL_89 0x4239 +#define POLL_7E7_CELL_90 0x423A +#define POLL_7E7_CELL_91 0x423B +#define POLL_7E7_CELL_92 0x423C +#define POLL_7E7_CELL_93 0x423D +#define POLL_7E7_CELL_94 0x423E +#define POLL_7E7_CELL_95 0x423F +#define POLL_7E7_CELL_96 0x4240 + +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index 5ad4220fb..892767a48 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -118,7 +118,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV); SOC_method = ESTIMATED; #else // Pack is not crashed, we can use periodically transmitted SOC - datalayer.battery.status.real_soc = battery_highprecision_SOC * 100; + datalayer.battery.status.real_soc = battery_highprecision_SOC * 10; SOC_method = MEASURED; #endif @@ -162,7 +162,7 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.bydAtto3.voltage_polled = BMS_voltage; } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { //Log values taken with 422V from battery case 0x244: //00,00,00,04,41,0F,20,8B - Static, values never changes between logs break; @@ -292,7 +292,7 @@ void receive_can_battery(CAN_frame rx_frame) { break; } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); //Send 50ms message if (currentMillis - previousMillis50 >= INTERVAL_50_MS) { @@ -334,9 +334,9 @@ void send_can_battery() { ATTO_3_12D.data.u8[6] = (0x0F | (frame6_counter << 4)); ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4)); - transmit_can(&ATTO_3_12D, can_config.battery); + transmit_can_frame(&ATTO_3_12D, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&ATTO_3_12D, can_config.battery_double); + transmit_can_frame(&ATTO_3_12D, can_config.battery_double); #endif //DOUBLE_BATTERY } // Send 100ms CAN Message @@ -355,9 +355,9 @@ void send_can_battery() { ATTO_3_441.data.u8[7] = 0xF5; } - transmit_can(&ATTO_3_441, can_config.battery); + transmit_can_frame(&ATTO_3_441, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&ATTO_3_441, can_config.battery_double); + transmit_can_frame(&ATTO_3_441, can_config.battery_double); #endif //DOUBLE_BATTERY } // Send 500ms CAN Message @@ -402,9 +402,9 @@ void send_can_battery() { break; } - transmit_can(&ATTO_3_7E7_POLL, can_config.battery); + transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&ATTO_3_7E7_POLL, can_config.battery_double); + transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery_double); #endif //DOUBLE_BATTERY } } @@ -419,6 +419,9 @@ void setup_battery(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; + //Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V + //We now init the value to 380.1V to avoid false positive events. + datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1; #ifdef DOUBLE_BATTERY datalayer.battery2.info.number_of_cells = 126; datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP; @@ -474,7 +477,7 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.temperature_max_dC = battery2_calc_max_temperature * 10; } -void receive_can_battery2(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { switch (rx_frame.ID) { //Log values taken with 422V from battery2 case 0x244: //00,00,00,04,41,0F,20,8B - Static, values never changes between logs break; diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index 1829ec6b0..57f943ff5 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -17,6 +17,6 @@ #define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/CELLPOWER-BMS.cpp b/Software/src/battery/CELLPOWER-BMS.cpp index 80c981c64..9a73ed407 100644 --- a/Software/src/battery/CELLPOWER-BMS.cpp +++ b/Software/src/battery/CELLPOWER-BMS.cpp @@ -213,7 +213,7 @@ void update_values_battery() { //TODO, shall we react on this? } } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x1A4: //PDO1_TX - 200ms @@ -316,7 +316,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 1s CAN Message if (currentMillis - previousMillis1s >= INTERVAL_1_S) { @@ -324,10 +324,10 @@ void send_can_battery() { previousMillis1s = currentMillis; /* - transmit_can(&CELLPOWER_18FF50E9, can_config.battery); - transmit_can(&CELLPOWER_18FF50E8, can_config.battery); - transmit_can(&CELLPOWER_18FF50E7, can_config.battery); - transmit_can(&CELLPOWER_18FF50E5, can_config.battery); + transmit_can_frame(&CELLPOWER_18FF50E9, can_config.battery); + transmit_can_frame(&CELLPOWER_18FF50E8, can_config.battery); + transmit_can_frame(&CELLPOWER_18FF50E7, can_config.battery); + transmit_can_frame(&CELLPOWER_18FF50E5, can_config.battery); */ } } diff --git a/Software/src/battery/CELLPOWER-BMS.h b/Software/src/battery/CELLPOWER-BMS.h index e1956b744..c5da0ecea 100644 --- a/Software/src/battery/CELLPOWER-BMS.h +++ b/Software/src/battery/CELLPOWER-BMS.h @@ -14,6 +14,6 @@ #define NATIVECAN_250KBPS void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 14720065c..f99d61ed7 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -197,13 +197,13 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) { x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest; x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage; -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG //Note on p131 uint8_t chargingrate = 0; if (x100_chg_lim.ConstantOfChargingRateIndication > 0) { chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100; - Serial.print("Charge Rate (kW): "); - Serial.println(chargingrate); + logging.print("Charge Rate (kW): "); + logging.println(chargingrate); } #endif @@ -217,40 +217,40 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) { */ if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) || (x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) { -#ifdef DEBUG_VIA_USB - Serial.println("Inconsistent charge/discharge state."); +#ifdef DEBUG_LOG + logging.println("Inconsistent charge/discharge state."); #endif CHADEMO_Status = CHADEMO_FAULT; return; } if (x102_chg_session.f.fault.FaultBatteryOverVoltage) { -#ifdef DEBUG_VIA_USB - Serial.println("Vehicle indicates fault, battery over voltage."); +#ifdef DEBUG_LOG + logging.println("Vehicle indicates fault, battery over voltage."); #endif CHADEMO_Status = CHADEMO_STOP; return; } if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) { -#ifdef DEBUG_VIA_USB - Serial.println("Vehicle indicates fault, battery under voltage."); +#ifdef DEBUG_LOG + logging.println("Vehicle indicates fault, battery under voltage."); #endif CHADEMO_Status = CHADEMO_STOP; return; } if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) { -#ifdef DEBUG_VIA_USB - Serial.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?"); +#ifdef DEBUG_LOG + logging.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?"); #endif CHADEMO_Status = CHADEMO_STOP; return; } if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) { -#ifdef DEBUG_VIA_USB - Serial.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?"); +#ifdef DEBUG_LOG + logging.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?"); #endif CHADEMO_Status = CHADEMO_STOP; return; @@ -264,8 +264,8 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) { //FIXME condition nesting or more stanzas needed here for clear determination of cessation reason if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) { -#ifdef DEBUG_VIA_USB - Serial.println("State of charge ceiling reached or charging interrupted, stop charging"); +#ifdef DEBUG_LOG + logging.println("State of charge ceiling reached or charging interrupted, stop charging"); #endif CHADEMO_Status = CHADEMO_STOP; return; @@ -273,8 +273,8 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) { if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) { CHADEMO_Status = CHADEMO_EV_ALLOWED; -#ifdef DEBUG_VIA_USB - Serial.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()"); +#ifdef DEBUG_LOG + logging.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()"); #endif return; } @@ -284,22 +284,22 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) { // consider relocating if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 && newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) { -#ifdef DEBUG_VIA_USB - Serial.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()"); +#ifdef DEBUG_LOG + logging.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()"); #endif CHADEMO_Status = CHADEMO_EVSE_START; return; } if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) { -#ifdef DEBUG_VIA_USB - Serial.println("updating vehicle request in process_vehicle_charging_session()"); +#ifdef DEBUG_LOG + logging.println("updating vehicle request in process_vehicle_charging_session()"); #endif return; } -#ifdef DEBUG_VIA_USB - Serial.println("UNHANDLED STATE IN process_vehicle_charging_session()"); +#ifdef DEBUG_LOG + logging.println("UNHANDLED STATE IN process_vehicle_charging_session()"); #endif return; } @@ -312,20 +312,20 @@ inline void process_vehicle_charging_limits(CAN_frame rx_frame) { x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6]; x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7]; -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG /* unsigned long currentMillis = millis(); if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { previousMillis5000 = currentMillis; - Serial.println("x200 Max remaining capacity for charging/discharging:"); + logging.println("x200 Max remaining capacity for charging/discharging:"); // initially this is set to 0, which is represented as 0xFF - Serial.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging); + logging.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging); } */ #endif if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) { -#ifdef DEBUG_VIA_USB - Serial.println("x200 minimum discharge voltage met or exceeded, stopping."); +#ifdef DEBUG_LOG + logging.println("x200 minimum discharge voltage met or exceeded, stopping."); #endif CHADEMO_Status = CHADEMO_STOP; } @@ -341,13 +341,13 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) { x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]); -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { previousMillis5000 = currentMillis; - Serial.print("x201 availabile vehicle energy, completion time: "); - Serial.println(x201_discharge_estimate.AvailableVehicleEnergy); - Serial.print("x201 approx vehicle completion time: "); - Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime); + logging.print("x201 availabile vehicle energy, completion time: "); + logging.println(x201_discharge_estimate.AvailableVehicleEnergy); + logging.print("x201 approx vehicle completion time: "); + logging.println(x201_discharge_estimate.ApproxDischargeCompletionTime); } #endif } @@ -367,19 +367,19 @@ inline void process_vehicle_vendor_ID(CAN_frame rx_frame) { ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); //Actually more bytes, but not needed for our purpose } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { #ifdef CH_CAN_DEBUG - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); + 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) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); + logging.print(rx_frame.data.u8[i], HEX); + logging.print(" "); } - Serial.println(""); + logging.println(""); #endif // CHADEMO coexists with a CAN-based shunt. Only process CHADEMO-specific IDs @@ -657,7 +657,7 @@ void update_evse_discharge_capabilities(CAN_frame& f) { CHADEMO_208.data.u8[7] = highByte(x208_evse_dischg_cap.lower_threshold_voltage); } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); @@ -693,8 +693,8 @@ void send_can_battery() { * that is the limiting factor. Therefore, we * can generally send as is without tweaks here. */ - transmit_can(&CHADEMO_108, can_config.battery); - transmit_can(&CHADEMO_109, can_config.battery); + transmit_can_frame(&CHADEMO_108, can_config.battery); + transmit_can_frame(&CHADEMO_109, can_config.battery); /* TODO for dynamic control: can send x118 with byte 6 bit 0 set to 0 for 1s (before flipping back to 1) as a way of giving vehicle a chance to update 101.1 and 101.2 * within 6 seconds of x118 toggle. @@ -703,9 +703,9 @@ void send_can_battery() { */ if (EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL) { - transmit_can(&CHADEMO_208, can_config.battery); + transmit_can_frame(&CHADEMO_208, can_config.battery); if (x201_received) { - transmit_can(&CHADEMO_209, can_config.battery); + transmit_can_frame(&CHADEMO_209, can_config.battery); x209_sent = true; } } @@ -713,11 +713,11 @@ void send_can_battery() { // TODO need an update_evse_dynamic_control(..) function above before we send 118 // 110.0.0 if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles? -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG //FIXME REMOVE - Serial.println("REMOVE: proto 2.0"); + logging.println("REMOVE: proto 2.0"); #endif - transmit_can(&CHADEMO_118, can_config.battery); + transmit_can_frame(&CHADEMO_118, can_config.battery); } } } @@ -753,15 +753,15 @@ void handle_chademo_sequence() { /* ------------------- State override conditions checks ------------------- */ /* ------------------------------------------------------------------------------ */ if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) { -#ifdef DEBUG_VIA_USB - Serial.println("Vehicle is not parked, abort."); +#ifdef DEBUG_LOG + logging.println("Vehicle is not parked, abort."); #endif CHADEMO_Status = CHADEMO_STOP; } if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) { -#ifdef DEBUG_VIA_USB - Serial.println("Vehicle charge/discharge permission ended, stop."); +#ifdef DEBUG_LOG + logging.println("Vehicle charge/discharge permission ended, stop."); #endif CHADEMO_Status = CHADEMO_STOP; } @@ -775,24 +775,24 @@ void handle_chademo_sequence() { plug_inserted = digitalRead(CHADEMO_PIN_7); if (!plug_inserted) { -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug -// Serial.println("CHADEMO plug is not inserted."); +// logging.println("CHADEMO plug is not inserted."); #endif return; } CHADEMO_Status = CHADEMO_CONNECTED; -#ifdef DEBUG_VIA_USB - Serial.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization."); +#ifdef DEBUG_LOG + logging.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization."); #endif break; case CHADEMO_CONNECTED: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - //Serial.println("CHADEMO_CONNECTED State"); + //logging.println("CHADEMO_CONNECTED State"); #endif /* plug_inserted is .. essentially a volatile of sorts, so verify */ if (plug_inserted) { @@ -810,8 +810,8 @@ void handle_chademo_sequence() { * with timers to have higher confidence of certain conditions hitting * a steady state */ -#ifdef DEBUG_VIA_USB - Serial.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization."); +#ifdef DEBUG_LOG + logging.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization."); #endif CHADEMO_Status = CHADEMO_IDLE; } @@ -819,10 +819,10 @@ void handle_chademo_sequence() { case CHADEMO_INIT: /* Transient state while awaiting CAN from Vehicle. * Used for triggers/error handling elsewhere; - * State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..) + * State change to CHADEMO_NEGOTIATE occurs in handle_incoming_can_frame_battery(..) */ -#ifdef DEBUG_VIA_USB -// Serial.println("Awaiting initial vehicle CAN to trigger negotiation"); +#ifdef DEBUG_LOG +// logging.println("Awaiting initial vehicle CAN to trigger negotiation"); #endif evse_init(); break; @@ -830,16 +830,16 @@ void handle_chademo_sequence() { /* Vehicle and EVSE dance */ //TODO if pin 4 / j goes high, -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug -// Serial.println("CHADEMO_NEGOTIATE State"); +// logging.println("CHADEMO_NEGOTIATE State"); #endif x109_evse_state.s.status.ChgDischStopControl = 1; break; case CHADEMO_EV_ALLOWED: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_EV_ALLOWED State"); + logging.println("CHADEMO_EV_ALLOWED State"); #endif // If we are in this state, vehicle_permission was already set to true...but re-verify // that pin 4 (j) reads high @@ -855,9 +855,9 @@ void handle_chademo_sequence() { } break; case CHADEMO_EVSE_PREPARE: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_EVSE_PREPARE State"); + logging.println("CHADEMO_EVSE_PREPARE State"); #endif /* TODO voltage check of output < 20v * insulation test hypothetically happens here before triggering PIN 10 high @@ -878,7 +878,7 @@ void handle_chademo_sequence() { digitalWrite(CHADEMO_PIN_10, HIGH); evse_permission = true; } else { - Serial.println("Insulation check measures > 20v "); + logging.println("Insulation check measures > 20v "); } // likely unnecessary but just to be sure. consider removal @@ -891,9 +891,9 @@ void handle_chademo_sequence() { //state changes to CHADEMO_EVSE_START only upon receipt of charging session request break; case CHADEMO_EVSE_START: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_EVSE_START State"); + logging.println("CHADEMO_EVSE_START State"); #endif datalayer.system.status.battery_allows_contactor_closing = true; x109_evse_state.s.status.ChgDischStopControl = 1; @@ -901,8 +901,8 @@ void handle_chademo_sequence() { CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED; -#ifdef DEBUG_VIA_USB - Serial.println("Initiating contactors"); +#ifdef DEBUG_LOG + logging.println("Initiating contactors"); #endif /* break rather than fall through because contactors are not instantaneous; @@ -911,17 +911,17 @@ void handle_chademo_sequence() { break; case CHADEMO_EVSE_CONTACTORS_ENABLED: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_EVSE_CONTACTORS State"); + logging.println("CHADEMO_EVSE_CONTACTORS State"); #endif /* check whether contactors ready, because externally dependent upon inverter allow during discharge */ if (contactors_ready) { -#ifdef DEBUG_VIA_USB - Serial.println("Contactors ready"); - Serial.print("Voltage: "); - Serial.println(get_measured_voltage()); +#ifdef DEBUG_LOG + logging.println("Contactors ready"); + logging.print("Voltage: "); + logging.println(get_measured_voltage()); #endif /* transition to POWERFLOW state if discharge compatible on both sides */ if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible && @@ -941,9 +941,9 @@ void handle_chademo_sequence() { /* break or fall through ? TODO */ break; case CHADEMO_POWERFLOW: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_POWERFLOW State"); + logging.println("CHADEMO_POWERFLOW State"); #endif /* POWERFLOW for charging, discharging, and bidirectional */ /* Interpretation */ @@ -961,8 +961,8 @@ void handle_chademo_sequence() { } if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage) { -#ifdef DEBUG_VIA_USB - Serial.println("x200 minimum discharge voltage met or exceeded, stopping."); +#ifdef DEBUG_LOG + logging.println("x200 minimum discharge voltage met or exceeded, stopping."); #endif CHADEMO_Status = CHADEMO_STOP; } @@ -972,9 +972,9 @@ void handle_chademo_sequence() { x109_evse_state.s.status.EVSE_status = 1; break; case CHADEMO_STOP: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_STOP State"); + logging.println("CHADEMO_STOP State"); #endif /* back to CHADEMO_IDLE after teardown */ x109_evse_state.s.status.ChgDischStopControl = 1; @@ -1000,16 +1000,16 @@ void handle_chademo_sequence() { break; case CHADEMO_FAULT: -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG // Commented unless needed for debug - Serial.println("CHADEMO_FAULT State"); + logging.println("CHADEMO_FAULT State"); #endif /* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */ x109_evse_state.s.status.EVSE_error = 1; x109_evse_state.s.status.ChgDischError = 1; x109_evse_state.s.status.ChgDischStopControl = 1; -#ifdef DEBUG_VIA_USB - Serial.println("CHADEMO fault encountered, tearing down to make safe"); +#ifdef DEBUG_LOG + logging.println("CHADEMO fault encountered, tearing down to make safe"); #endif digitalWrite(CHADEMO_PIN_10, LOW); digitalWrite(CHADEMO_PIN_2, LOW); @@ -1020,8 +1020,8 @@ void handle_chademo_sequence() { break; default: -#ifdef DEBUG_VIA_USB - Serial.println("UNHANDLED CHADEMO_STATE, setting FAULT"); +#ifdef DEBUG_LOG + logging.println("UNHANDLED CHADEMO_STATE, setting FAULT"); #endif CHADEMO_Status = CHADEMO_FAULT; break; diff --git a/Software/src/battery/CHADEMO-BATTERY.h b/Software/src/battery/CHADEMO-BATTERY.h index fa6545a99..eacc2fd13 100644 --- a/Software/src/battery/CHADEMO-BATTERY.h +++ b/Software/src/battery/CHADEMO-BATTERY.h @@ -13,6 +13,6 @@ #define ISA_SHUNT void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/CHADEMO-SHUNTS.cpp b/Software/src/battery/CHADEMO-SHUNTS.cpp index 6ca55ca73..61ca170b7 100644 --- a/Software/src/battery/CHADEMO-SHUNTS.cpp +++ b/Software/src/battery/CHADEMO-SHUNTS.cpp @@ -91,17 +91,17 @@ void ISA_handleFrame(CAN_frame* frame) { case 0x510: case 0x511: - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(frame->ID, HEX); - Serial.print(" "); - Serial.print(frame->DLC); - Serial.print(" "); + logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D + logging.print(" "); + logging.print(frame->ID, HEX); + logging.print(" "); + logging.print(frame->DLC); + logging.print(" "); for (int i = 0; i < frame->DLC; ++i) { - Serial.print(frame->data.u8[i], HEX); - Serial.print(" "); + logging.print(frame->data.u8[i], HEX); + logging.print(" "); } - Serial.println(""); + logging.println(""); break; case 0x521: @@ -245,8 +245,8 @@ void ISA_initialize() { ISA_STOP(); delay(500); for (int i = 0; i < 8; i++) { - Serial.print("ISA Initialization "); - Serial.println(i); + logging.print("ISA Initialization "); + logging.println(i); outframe.data.u8[0] = (0x20 + i); outframe.data.u8[1] = 0x02; @@ -257,7 +257,7 @@ void ISA_initialize() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); delay(500); } @@ -271,7 +271,7 @@ void ISA_initialize() { } void ISA_STOP() { - Serial.println("ISA STOP"); + logging.println("ISA STOP"); outframe.data.u8[0] = 0x34; outframe.data.u8[1] = 0x00; @@ -282,11 +282,11 @@ void ISA_STOP() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } void ISA_sendSTORE() { - Serial.println("ISA send STORE"); + logging.println("ISA send STORE"); outframe.data.u8[0] = 0x32; outframe.data.u8[1] = 0x00; @@ -297,11 +297,11 @@ void ISA_sendSTORE() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } void ISA_START() { - Serial.println("ISA START"); + logging.println("ISA START"); outframe.data.u8[0] = 0x34; outframe.data.u8[1] = 0x01; @@ -312,12 +312,12 @@ void ISA_START() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } void ISA_RESTART() { //Has the effect of zeroing AH and KWH - Serial.println("ISA RESTART"); + logging.println("ISA RESTART"); outframe.data.u8[0] = 0x3F; outframe.data.u8[1] = 0x00; @@ -328,7 +328,7 @@ void ISA_RESTART() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } void ISA_deFAULT() { @@ -336,7 +336,7 @@ void ISA_deFAULT() { ISA_STOP(); delay(500); - Serial.println("ISA RESTART to default"); + logging.println("ISA RESTART to default"); outframe.data.u8[0] = 0x3D; outframe.data.u8[1] = 0x00; @@ -347,7 +347,7 @@ void ISA_deFAULT() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); delay(500); ISA_START(); @@ -358,7 +358,7 @@ void ISA_initCurrent() { ISA_STOP(); delay(500); - Serial.println("ISA Initialization Current"); + logging.println("ISA Initialization Current"); outframe.data.u8[0] = 0x21; outframe.data.u8[1] = 0x02; @@ -369,7 +369,7 @@ void ISA_initCurrent() { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); delay(500); ISA_sendSTORE(); @@ -382,8 +382,8 @@ void ISA_initCurrent() { } void ISA_getCONFIG(uint8_t i) { - Serial.print("ISA Get Config "); - Serial.println(i); + logging.print("ISA Get Config "); + logging.println(i); outframe.data.u8[0] = (0x60 + i); outframe.data.u8[1] = 0x00; @@ -394,12 +394,12 @@ void ISA_getCONFIG(uint8_t i) { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } void ISA_getCAN_ID(uint8_t i) { - Serial.print("ISA Get CAN ID "); - Serial.println(i); + logging.print("ISA Get CAN ID "); + logging.println(i); outframe.data.u8[0] = (0x50 + i); if (i == 8) @@ -414,12 +414,12 @@ void ISA_getCAN_ID(uint8_t i) { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } void ISA_getINFO(uint8_t i) { - Serial.print("ISA Get INFO "); - Serial.println(i, HEX); + logging.print("ISA Get INFO "); + logging.println(i, HEX); outframe.data.u8[0] = (0x70 + i); outframe.data.u8[1] = 0x00; @@ -430,6 +430,6 @@ void ISA_getINFO(uint8_t i) { outframe.data.u8[6] = 0x00; outframe.data.u8[7] = 0x00; - transmit_can(&outframe, can_config.battery); + transmit_can_frame(&outframe, can_config.battery); } #endif diff --git a/Software/src/battery/CHADEMO-SHUNTS.h b/Software/src/battery/CHADEMO-SHUNTS.h index b4ebe3cbc..9a40e53cf 100644 --- a/Software/src/battery/CHADEMO-SHUNTS.h +++ b/Software/src/battery/CHADEMO-SHUNTS.h @@ -25,6 +25,6 @@ void ISA_getCONFIG(uint8_t i); void ISA_getCAN_ID(uint8_t i); void ISA_getINFO(uint8_t i); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/ECMP-BATTERY.cpp b/Software/src/battery/ECMP-BATTERY.cpp new file mode 100644 index 000000000..9bea886ad --- /dev/null +++ b/Software/src/battery/ECMP-BATTERY.cpp @@ -0,0 +1,308 @@ +#include "../include.h" +#ifdef STELLANTIS_ECMP_BATTERY +#include // For std::min and std::max +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +#include "ECMP-BATTERY.h" + +/* TODO: +This integration is still ongoing. Here is what still needs to be done in order to use this battery type +- Find SOC% +- Find battery voltage +- Find current value +- Find/estimate charge/discharge limits +- Find temperature +- Figure out contactor closing + - Which CAN messages need to be sent towards the battery? +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent + +//Actual content messages +CAN_frame ECMP_XXX = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x301, + .data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +static uint16_t battery_voltage = 37000; +static uint16_t battery_soc = 0; +static uint16_t cellvoltages[108]; + +void update_values_battery() { + + datalayer.battery.status.real_soc = battery_soc * 100; + + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.voltage_dV = (battery_voltage / 10); + + datalayer.battery.status.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.status.max_charge_power_W; + + datalayer.battery.status.max_discharge_power_W; + + datalayer.battery.status.temperature_min_dC; + + datalayer.battery.status.temperature_max_dC; + + // Initialize min and max, lets find which cells are min and max! + uint16_t min_cell_mv_value = std::numeric_limits::max(); + uint16_t max_cell_mv_value = 0; + // Loop to find the min and max while ignoring zero values + for (uint8_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) { + uint16_t voltage_mV = datalayer.battery.status.cell_voltages_mV[i]; + if (voltage_mV != 0) { // Skip unread values (0) + min_cell_mv_value = std::min(min_cell_mv_value, voltage_mV); + max_cell_mv_value = std::max(max_cell_mv_value, voltage_mV); + } + } + // If all array values are 0, reset min/max to 3700 + if (min_cell_mv_value == std::numeric_limits::max()) { + min_cell_mv_value = 3700; + max_cell_mv_value = 3700; + } + + datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value; + datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value; +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + switch (rx_frame.ID) { + case 0x125: + break; + case 0x127: + break; + case 0x129: + break; + case 0x31B: + break; + case 0x358: + break; + case 0x359: + break; + case 0x361: + battery_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + break; + case 0x362: + break; + case 0x454: + break; + case 0x494: + break; + case 0x594: + break; + case 0x6D0: + battery_soc = (100 - rx_frame.data.u8[0]); + break; + case 0x6D1: + break; + case 0x6D2: + break; + case 0x6D3: + cellvoltages[0] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[1] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[2] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[3] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6D4: + cellvoltages[4] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[5] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[6] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[7] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6E0: + break; + case 0x6E1: + cellvoltages[8] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[9] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[10] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[11] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6E2: + cellvoltages[12] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[13] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[14] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[15] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6E3: + break; + case 0x6E4: + break; + case 0x6E5: + break; + case 0x6E6: + break; + case 0x6E7: + cellvoltages[16] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[17] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[18] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[19] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6E8: + cellvoltages[20] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[21] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[22] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[23] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6E9: + cellvoltages[24] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[25] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[26] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[27] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6EB: + cellvoltages[28] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[29] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[30] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[31] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6EC: + //Not available on e-C4 + break; + case 0x6ED: + cellvoltages[32] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[33] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[34] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[35] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6EE: + cellvoltages[36] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[37] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[38] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[39] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6EF: + cellvoltages[40] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[41] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[42] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[43] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F0: + cellvoltages[44] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[45] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[46] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[47] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F1: + cellvoltages[48] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[49] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[50] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[51] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F2: + cellvoltages[52] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[53] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[54] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[55] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F3: + cellvoltages[56] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[57] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[58] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[59] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F4: + cellvoltages[60] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[61] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[62] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[63] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F5: + cellvoltages[64] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[65] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[66] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[67] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F6: + cellvoltages[68] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[69] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[70] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[71] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F7: + cellvoltages[72] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[73] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[74] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[75] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F8: + cellvoltages[76] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[77] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[78] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[79] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6F9: + cellvoltages[80] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[81] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[82] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[83] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6FA: + cellvoltages[84] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[85] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[86] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[87] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6FB: + cellvoltages[88] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[89] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[90] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[91] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6FC: + cellvoltages[92] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[93] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[94] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[95] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6FD: + cellvoltages[96] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[97] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[98] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[99] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6FE: + cellvoltages[100] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[101] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[102] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[103] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + break; + case 0x6FF: + cellvoltages[104] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + cellvoltages[105] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; + cellvoltages[106] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + cellvoltages[107] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]; + memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages, 108 * sizeof(uint16_t)); + break; + case 0x794: + break; + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + // Send 1s CAN Message + if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { + previousMillis1000 = currentMillis; + } +} + +void setup_battery(void) { // Performs one time setup at startup +#ifdef DEBUG_VIA_USB + Serial.println("ECMP battery selected"); +#endif + datalayer.battery.info.number_of_cells = 108; + datalayer.battery.info.max_design_voltage_dV = 4546; // 454.6V, charging over this is not possible + datalayer.battery.info.min_design_voltage_dV = 3210; // 321.0V, under this, discharging further is disabled +} + +#endif diff --git a/Software/src/battery/ECMP-BATTERY.h b/Software/src/battery/ECMP-BATTERY.h new file mode 100644 index 000000000..a637ad8c7 --- /dev/null +++ b/Software/src/battery/ECMP-BATTERY.h @@ -0,0 +1,12 @@ +#ifndef STELLANTIS_ECMP_BATTERY_H +#define STELLANTIS_ECMP_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED +#define MAX_CELL_DEVIATION_MV 250 + +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index 05e6e7c69..ddc638146 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -103,33 +103,33 @@ void update_values_battery() { //This function maps all the values fetched via } if (!BMU_Detected) { -#ifdef DEBUG_VIA_USB - Serial.println("BMU not detected, check wiring!"); +#ifdef DEBUG_LOG + logging.println("BMU not detected, check wiring!"); #endif } -#ifdef DEBUG_VIA_USB - Serial.println("Battery Values"); - Serial.print("BMU SOC: "); - Serial.print(BMU_SOC); - Serial.print(" BMU Current: "); - Serial.print(BMU_Current); - Serial.print(" BMU Battery Voltage: "); - Serial.print(BMU_PackVoltage); - Serial.print(" BMU_Power: "); - Serial.print(BMU_Power); - Serial.print(" Cell max voltage: "); - Serial.print(max_volt_cel); - Serial.print(" Cell min voltage: "); - Serial.print(min_volt_cel); - Serial.print(" Cell max temp: "); - Serial.print(max_temp_cel); - Serial.print(" Cell min temp: "); - Serial.println(min_temp_cel); +#ifdef DEBUG_LOG + logging.println("Battery Values"); + logging.print("BMU SOC: "); + logging.print(BMU_SOC); + logging.print(" BMU Current: "); + logging.print(BMU_Current); + logging.print(" BMU Battery Voltage: "); + logging.print(BMU_PackVoltage); + logging.print(" BMU_Power: "); + logging.print(BMU_Power); + logging.print(" Cell max voltage: "); + logging.print(max_volt_cel); + logging.print(" Cell min voltage: "); + logging.print(min_volt_cel); + logging.print(" Cell max temp: "); + logging.print(max_temp_cel); + logging.print(" Cell min temp: "); + logging.println(min_temp_cel); #endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x374: //BMU message, 10ms - SOC datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; @@ -207,7 +207,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h index 70bbf9fbc..4191ab678 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h @@ -11,6 +11,6 @@ #define MIN_CELL_VOLTAGE_MV 2750 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp index eb13017c1..618b32c8d 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp @@ -57,9 +57,9 @@ CAN_frame ipace_keep_alive = {.FD = false, .data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/ void print_units(char* header, int value, char* units) { - Serial.print(header); - Serial.print(value); - Serial.print(units); + logging.print(header); + logging.print(value); + logging.print(units); } void update_values_battery() { @@ -104,8 +104,8 @@ void update_values_battery() { } /*Finally print out values to serial if configured to do so*/ -#ifdef DEBUG_VIA_USB - Serial.println("Values going to inverter"); +#ifdef DEBUG_LOG + logging.println("Values going to inverter"); print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% "); print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% "); print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V "); @@ -115,11 +115,11 @@ void update_values_battery() { print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "ยฐC "); print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV "); print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV "); - Serial.println(""); + logging.println(""); #endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { // Do not log noisy startup messages - there are many ! if (rx_frame.ID == 0 && rx_frame.DLC == 8 && rx_frame.data.u8[0] == 0 && rx_frame.data.u8[1] == 0 && @@ -229,26 +229,26 @@ void receive_can_battery(CAN_frame rx_frame) { } // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); + 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) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); + logging.print(rx_frame.data.u8[i], HEX); + logging.print(" "); } - Serial.println(""); + logging.println(""); } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); /* Send keep-alive every 200ms */ if (currentMillis - previousMillisKeepAlive >= INTERVAL_200_MS) { previousMillisKeepAlive = currentMillis; - transmit_can(&ipace_keep_alive, can_config.battery); + transmit_can_frame(&ipace_keep_alive, can_config.battery); return; } } diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.h b/Software/src/battery/JAGUAR-IPACE-BATTERY.h index 6ff7827fe..adf120a1a 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.h +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.h @@ -10,6 +10,6 @@ #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index a0715a26a..70f9fd459 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -690,67 +690,67 @@ void update_values_battery() { //This function maps all the values fetched via /* Safeties verified. Perform USB serial printout if configured to do so */ -#ifdef DEBUG_VIA_USB - Serial.println(); //sepatator - Serial.println("Values from battery: "); - Serial.print("SOC BMS: "); - Serial.print((uint16_t)SOC_BMS / 10.0, 1); - Serial.print("% | SOC Display: "); - Serial.print((uint16_t)SOC_Display / 10.0, 1); - Serial.print("% | SOH "); - Serial.print((uint16_t)batterySOH / 10.0, 1); - Serial.println("%"); - Serial.print((int16_t)batteryAmps / 10.0, 1); - Serial.print(" Amps | "); - Serial.print((uint16_t)batteryVoltage / 10.0, 1); - Serial.print(" Volts | "); - Serial.print((int16_t)datalayer.battery.status.active_power_W); - Serial.println(" Watts"); - Serial.print("Allowed Charge "); - Serial.print((uint16_t)allowedChargePower * 10); - Serial.print(" W | Allowed Discharge "); - Serial.print((uint16_t)allowedDischargePower * 10); - Serial.println(" W"); - Serial.print("MaxCellVolt "); - Serial.print(CellVoltMax_mV); - Serial.print(" mV No "); - Serial.print(CellVmaxNo); - Serial.print(" | MinCellVolt "); - Serial.print(CellVoltMin_mV); - Serial.print(" mV No "); - Serial.println(CellVminNo); - Serial.print("TempHi "); - Serial.print((int16_t)temperatureMax); - Serial.print("ยฐC TempLo "); - Serial.print((int16_t)temperatureMin); - Serial.print("ยฐC WaterInlet "); - Serial.print((int8_t)temperature_water_inlet); - Serial.print("ยฐC PowerRelay "); - Serial.print((int8_t)powerRelayTemperature * 2); - Serial.println("ยฐC"); - Serial.print("Aux12volt: "); - Serial.print((int16_t)leadAcidBatteryVoltage / 10.0, 1); - Serial.println("V | "); - Serial.print("BmsManagementMode "); - Serial.print((uint8_t)batteryManagementMode, BIN); +#ifdef DEBUG_LOG + logging.println(); //sepatator + logging.println("Values from battery: "); + logging.print("SOC BMS: "); + logging.print((uint16_t)SOC_BMS / 10.0, 1); + logging.print("% | SOC Display: "); + logging.print((uint16_t)SOC_Display / 10.0, 1); + logging.print("% | SOH "); + logging.print((uint16_t)batterySOH / 10.0, 1); + logging.println("%"); + logging.print((int16_t)batteryAmps / 10.0, 1); + logging.print(" Amps | "); + logging.print((uint16_t)batteryVoltage / 10.0, 1); + logging.print(" Volts | "); + logging.print((int16_t)datalayer.battery.status.active_power_W); + logging.println(" Watts"); + logging.print("Allowed Charge "); + logging.print((uint16_t)allowedChargePower * 10); + logging.print(" W | Allowed Discharge "); + logging.print((uint16_t)allowedDischargePower * 10); + logging.println(" W"); + logging.print("MaxCellVolt "); + logging.print(CellVoltMax_mV); + logging.print(" mV No "); + logging.print(CellVmaxNo); + logging.print(" | MinCellVolt "); + logging.print(CellVoltMin_mV); + logging.print(" mV No "); + logging.println(CellVminNo); + logging.print("TempHi "); + logging.print((int16_t)temperatureMax); + logging.print("ยฐC TempLo "); + logging.print((int16_t)temperatureMin); + logging.print("ยฐC WaterInlet "); + logging.print((int8_t)temperature_water_inlet); + logging.print("ยฐC PowerRelay "); + logging.print((int8_t)powerRelayTemperature * 2); + logging.println("ยฐC"); + logging.print("Aux12volt: "); + logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1); + logging.println("V | "); + logging.print("BmsManagementMode "); + logging.print((uint8_t)batteryManagementMode, BIN); if (bitRead((uint8_t)BMS_ign, 2) == 1) { - Serial.print(" | BmsIgnition ON"); + logging.print(" | BmsIgnition ON"); } else { - Serial.print(" | BmsIgnition OFF"); + logging.print(" | BmsIgnition OFF"); } if (bitRead((uint8_t)batteryRelay, 0) == 1) { - Serial.print(" | PowerRelay ON"); + logging.print(" | PowerRelay ON"); } else { - Serial.print(" | PowerRelay OFF"); + logging.print(" | PowerRelay OFF"); } - Serial.print(" | Inverter "); - Serial.print(inverterVoltage); - Serial.println(" Volts"); + logging.print(" | Inverter "); + logging.print(inverterVoltage); + logging.println(" Volts"); #endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { startedUp = true; switch (rx_frame.ID) { case 0x055: @@ -808,10 +808,10 @@ void receive_can_battery(CAN_frame rx_frame) { // print_canfd_frame(frame); switch (rx_frame.data.u8[0]) { case 0x10: //"PID Header" - // Serial.println ("Send ack"); + // logging.println ("Send ack"); poll_data_pid = rx_frame.data.u8[4]; // if (rx_frame.data.u8[4] == poll_data_pid) { - transmit_can(&EGMP_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled + transmit_can_frame(&EGMP_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled // } break; case 0x21: //First frame in PID group @@ -982,7 +982,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); if (startedUp) { //Send Contactor closing message loop @@ -993,7 +993,7 @@ void send_can_battery() { if (currentMillis - startMillis >= messageDelays[messageIndex]) { // Transmit the current message - transmit_can(messages[messageIndex], can_config.battery); + transmit_can_frame(messages[messageIndex], can_config.battery); // Move to the next message messageIndex++; @@ -1019,7 +1019,7 @@ void send_can_battery() { EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER; if (ok_start_polling_battery) { - transmit_can(&EGMP_7E4, can_config.battery); + transmit_can_frame(&EGMP_7E4, can_config.battery); } KIA_7E4_COUNTER++; diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.h b/Software/src/battery/KIA-E-GMP-BATTERY.h index 8c9f0db7f..b0f3dccc2 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.h +++ b/Software/src/battery/KIA-E-GMP-BATTERY.h @@ -18,6 +18,6 @@ extern ACAN2517FD canfd; #define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 3a6663c65..4555cd455 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -1,6 +1,7 @@ #include "../include.h" #ifdef KIA_HYUNDAI_64_BATTERY #include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" #include "../devboard/utils/events.h" #include "KIA-HYUNDAI-64-BATTERY.h" @@ -25,6 +26,7 @@ static int16_t batteryAmps = 0; static int16_t temperatureMax = 0; static int16_t temperatureMin = 0; static int16_t poll_data_pid = 0; +static bool holdPidCounter = false; static uint8_t CellVmaxNo = 0; static uint8_t CellVminNo = 0; static uint8_t batteryManagementMode = 0; @@ -140,65 +142,74 @@ void update_values_battery() { //This function maps all the values fetched via set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); } - /* Safeties verified. Perform USB serial printout if configured to do so */ - -#ifdef DEBUG_VIA_USB - Serial.println(); //sepatator - Serial.println("Values from battery: "); - Serial.print("SOC BMS: "); - Serial.print((uint16_t)SOC_BMS / 10.0, 1); - Serial.print("% | SOC Display: "); - Serial.print((uint16_t)SOC_Display / 10.0, 1); - Serial.print("% | SOH "); - Serial.print((uint16_t)batterySOH / 10.0, 1); - Serial.println("%"); - Serial.print((int16_t)batteryAmps / 10.0, 1); - Serial.print(" Amps | "); - Serial.print((uint16_t)batteryVoltage / 10.0, 1); - Serial.print(" Volts | "); - Serial.print((int16_t)datalayer.battery.status.active_power_W); - Serial.println(" Watts"); - Serial.print("Allowed Charge "); - Serial.print((uint16_t)allowedChargePower * 10); - Serial.print(" W | Allowed Discharge "); - Serial.print((uint16_t)allowedDischargePower * 10); - Serial.println(" W"); - Serial.print("MaxCellVolt "); - Serial.print(CellVoltMax_mV); - Serial.print(" mV No "); - Serial.print(CellVmaxNo); - Serial.print(" | MinCellVolt "); - Serial.print(CellVoltMin_mV); - Serial.print(" mV No "); - Serial.println(CellVminNo); - Serial.print("TempHi "); - Serial.print((int16_t)temperatureMax); - Serial.print("ยฐC TempLo "); - Serial.print((int16_t)temperatureMin); - Serial.print("ยฐC WaterInlet "); - Serial.print((int8_t)temperature_water_inlet); - Serial.print("ยฐC PowerRelay "); - Serial.print((int8_t)powerRelayTemperature * 2); - Serial.println("ยฐC"); - Serial.print("Aux12volt: "); - Serial.print((int16_t)leadAcidBatteryVoltage / 10.0, 1); - Serial.println("V | "); - Serial.print("BmsManagementMode "); - Serial.print((uint8_t)batteryManagementMode, BIN); + // Update webserver datalayer + datalayer_extended.KiaHyundai64.total_cell_count = datalayer.battery.info.number_of_cells; + datalayer_extended.KiaHyundai64.battery_12V = leadAcidBatteryVoltage; + datalayer_extended.KiaHyundai64.waterleakageSensor = waterleakageSensor; + datalayer_extended.KiaHyundai64.temperature_water_inlet = temperature_water_inlet; + datalayer_extended.KiaHyundai64.powerRelayTemperature = powerRelayTemperature * 2; + datalayer_extended.KiaHyundai64.batteryManagementMode = batteryManagementMode; + datalayer_extended.KiaHyundai64.BMS_ign = BMS_ign; + datalayer_extended.KiaHyundai64.batteryRelay = batteryRelay; + + //Perform logging if configured to do so +#ifdef DEBUG_LOG + logging.println(); //sepatator + logging.println("Values from battery: "); + logging.print("SOC BMS: "); + logging.print((uint16_t)SOC_BMS / 10.0, 1); + logging.print("% | SOC Display: "); + logging.print((uint16_t)SOC_Display / 10.0, 1); + logging.print("% | SOH "); + logging.print((uint16_t)batterySOH / 10.0, 1); + logging.println("%"); + logging.print((int16_t)batteryAmps / 10.0, 1); + logging.print(" Amps | "); + logging.print((uint16_t)batteryVoltage / 10.0, 1); + logging.print(" Volts | "); + logging.print((int16_t)datalayer.battery.status.active_power_W); + logging.println(" Watts"); + logging.print("Allowed Charge "); + logging.print((uint16_t)allowedChargePower * 10); + logging.print(" W | Allowed Discharge "); + logging.print((uint16_t)allowedDischargePower * 10); + logging.println(" W"); + logging.print("MaxCellVolt "); + logging.print(CellVoltMax_mV); + logging.print(" mV No "); + logging.print(CellVmaxNo); + logging.print(" | MinCellVolt "); + logging.print(CellVoltMin_mV); + logging.print(" mV No "); + logging.println(CellVminNo); + logging.print("TempHi "); + logging.print((int16_t)temperatureMax); + logging.print("ยฐC TempLo "); + logging.print((int16_t)temperatureMin); + logging.print("ยฐC WaterInlet "); + logging.print((int8_t)temperature_water_inlet); + logging.print("ยฐC PowerRelay "); + logging.print((int8_t)powerRelayTemperature * 2); + logging.println("ยฐC"); + logging.print("Aux12volt: "); + logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1); + logging.println("V | "); + logging.print("BmsManagementMode "); + logging.print((uint8_t)batteryManagementMode, BIN); if (bitRead((uint8_t)BMS_ign, 2) == 1) { - Serial.print(" | BmsIgnition ON"); + logging.print(" | BmsIgnition ON"); } else { - Serial.print(" | BmsIgnition OFF"); + logging.print(" | BmsIgnition OFF"); } if (bitRead((uint8_t)batteryRelay, 0) == 1) { - Serial.print(" | PowerRelay ON"); + logging.print(" | PowerRelay ON"); } else { - Serial.print(" | PowerRelay OFF"); + logging.print(" | PowerRelay OFF"); } - Serial.print(" | Inverter "); - Serial.print(inverterVoltage); - Serial.println(" Volts"); + logging.print(" | Inverter "); + logging.print(inverterVoltage); + logging.println(" Volts"); #endif } @@ -220,7 +231,7 @@ void update_number_of_cells() { } } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x4DE: startedUp = true; @@ -262,34 +273,36 @@ void receive_can_battery(CAN_frame rx_frame) { case 0x5D8: startedUp = true; - //PID data is polled after last message sent from battery: - if (poll_data_pid >= 10) { //polling one of ten PIDs at 100ms, resolution = 1s - poll_data_pid = 0; - } - poll_data_pid++; - if (poll_data_pid == 1) { - transmit_can(&KIA64_7E4_id1, can_config.battery); - } else if (poll_data_pid == 2) { - transmit_can(&KIA64_7E4_id2, can_config.battery); - } else if (poll_data_pid == 3) { - transmit_can(&KIA64_7E4_id3, can_config.battery); - } else if (poll_data_pid == 4) { - transmit_can(&KIA64_7E4_id4, can_config.battery); - } else if (poll_data_pid == 5) { - transmit_can(&KIA64_7E4_id5, can_config.battery); - } else if (poll_data_pid == 6) { - transmit_can(&KIA64_7E4_id6, can_config.battery); - } else if (poll_data_pid == 7) { - } else if (poll_data_pid == 8) { - } else if (poll_data_pid == 9) { - } else if (poll_data_pid == 10) { + //PID data is polled after last message sent from battery every other time: + if (holdPidCounter == true) { + holdPidCounter = false; + } else { + holdPidCounter = true; + if (poll_data_pid >= 6) { //polling one of six PIDs at 100ms*2, resolution = 1200ms + poll_data_pid = 0; + } + poll_data_pid++; + if (poll_data_pid == 1) { + transmit_can_frame(&KIA64_7E4_id1, can_config.battery); + } else if (poll_data_pid == 2) { + transmit_can_frame(&KIA64_7E4_id2, can_config.battery); + } else if (poll_data_pid == 3) { + transmit_can_frame(&KIA64_7E4_id3, can_config.battery); + } else if (poll_data_pid == 4) { + transmit_can_frame(&KIA64_7E4_id4, can_config.battery); + } else if (poll_data_pid == 5) { + transmit_can_frame(&KIA64_7E4_id5, can_config.battery); + } else if (poll_data_pid == 6) { + transmit_can_frame(&KIA64_7E4_id6, can_config.battery); + } } break; case 0x7EC: //Data From polled PID group, BigEndian switch (rx_frame.data.u8[0]) { case 0x10: //"PID Header" if (rx_frame.data.u8[4] == poll_data_pid) { - transmit_can(&KIA64_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled + transmit_can_frame(&KIA64_7E4_ack, + can_config.battery); //Send ack to BMS if the same frame is sent as polled } break; case 0x21: //First frame in PID group @@ -407,7 +420,9 @@ void receive_can_battery(CAN_frame rx_frame) { cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20); cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20); cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20); - cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20); + if (rx_frame.data.u8[7] > 4) { // Data only valid on 98S + cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20); // Perform extra checks + } } else if (poll_data_pid == 5) { batterySOH = ((rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]); } @@ -425,18 +440,38 @@ void receive_can_battery(CAN_frame rx_frame) { cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20); cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20); cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20); - } else if (poll_data_pid == 4) { - cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20); - cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20); - cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20); - cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20); - cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20); - } else if (poll_data_pid == 5) { - cellvoltages_mv[96] = (rx_frame.data.u8[4] * 20); - cellvoltages_mv[97] = (rx_frame.data.u8[5] * 20); + } else if (poll_data_pid == 4) { // Data only valid on 98S + if (rx_frame.data.u8[1] > 4) { // Perform extra checks + cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20); + } + if (rx_frame.data.u8[2] > 4) { // Perform extra checks + cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20); + } + if (rx_frame.data.u8[3] > 4) { // Perform extra checks + cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20); + } + if (rx_frame.data.u8[4] > 4) { // Perform extra checks + cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20); + } + if (rx_frame.data.u8[5] > 4) { // Perform extra checks + cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20); + } + } else if (poll_data_pid == 5) { // Data only valid on 98S + if (rx_frame.data.u8[4] > 4) { // Perform extra checks + cellvoltages_mv[96] = (rx_frame.data.u8[4] * 20); + } + if (rx_frame.data.u8[5] > 4) { // Perform extra checks + cellvoltages_mv[97] = (rx_frame.data.u8[5] * 20); + } } break; case 0x26: //Sixth datarow in PID group + //We have read all cells, check that content is valid: + for (uint8_t i = 85; i < 97; ++i) { + if (cellvoltages_mv[i] < 300) { // Zero the value if it's below 300 + cellvoltages_mv[i] = 0; // Some packs incorrectly report the last unpopulated cells as 20-60mV + } + } //Map all cell voltages to the global array memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t)); //Update number of cells @@ -460,7 +495,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); if (!startedUp) { @@ -471,9 +506,9 @@ void send_can_battery() { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; - transmit_can(&KIA64_553, can_config.battery); - transmit_can(&KIA64_57F, can_config.battery); - transmit_can(&KIA64_2A1, can_config.battery); + transmit_can_frame(&KIA64_553, can_config.battery); + transmit_can_frame(&KIA64_57F, can_config.battery); + transmit_can_frame(&KIA64_2A1, can_config.battery); } // Send 10ms CAN Message if (currentMillis - previousMillis10 >= INTERVAL_10_MS) { @@ -525,11 +560,11 @@ void send_can_battery() { break; } - transmit_can(&KIA_HYUNDAI_200, can_config.battery); + transmit_can_frame(&KIA_HYUNDAI_200, can_config.battery); - transmit_can(&KIA_HYUNDAI_523, can_config.battery); + transmit_can_frame(&KIA_HYUNDAI_523, can_config.battery); - transmit_can(&KIA_HYUNDAI_524, can_config.battery); + transmit_can_frame(&KIA_HYUNDAI_524, can_config.battery); } } diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h index 8b1b39351..ff9725081 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h @@ -14,6 +14,6 @@ void setup_battery(void); void update_number_of_cells(); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp index c37fcb15b..71c4c88fd 100644 --- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp @@ -86,7 +86,7 @@ void update_values_battery() { //This function maps all the values fetched via } } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { case 0x5F1: @@ -108,7 +108,7 @@ void receive_can_battery(CAN_frame rx_frame) { switch (rx_frame.data.u8[0]) { case 0x10: //"PID Header" if (rx_frame.data.u8[3] == poll_data_pid) { - transmit_can(&KIA_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled + transmit_can_frame(&KIA_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled } break; case 0x21: //First frame in PID group @@ -230,7 +230,7 @@ void receive_can_battery(CAN_frame rx_frame) { break; } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 1000ms CAN Message @@ -243,15 +243,15 @@ void send_can_battery() { } poll_data_pid++; if (poll_data_pid == 1) { - transmit_can(&KIA_7E4_id1, can_config.battery); + transmit_can_frame(&KIA_7E4_id1, can_config.battery); } else if (poll_data_pid == 2) { - transmit_can(&KIA_7E4_id2, can_config.battery); + transmit_can_frame(&KIA_7E4_id2, can_config.battery); } else if (poll_data_pid == 3) { - transmit_can(&KIA_7E4_id3, can_config.battery); + transmit_can_frame(&KIA_7E4_id3, can_config.battery); } else if (poll_data_pid == 4) { } else if (poll_data_pid == 5) { - transmit_can(&KIA_7E4_id5, can_config.battery); + transmit_can_frame(&KIA_7E4_id5, can_config.battery); } } } diff --git a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h index 26b06d6fa..5808723eb 100644 --- a/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h +++ b/Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h @@ -11,6 +11,6 @@ #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/MEB-BATTERY.cpp b/Software/src/battery/MEB-BATTERY.cpp new file mode 100644 index 000000000..c73a6f51e --- /dev/null +++ b/Software/src/battery/MEB-BATTERY.cpp @@ -0,0 +1,2132 @@ +#include "../include.h" +#ifdef MEB_BATTERY +#include // For std::min and std::max +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage +#include "../devboard/utils/events.h" +#include "MEB-BATTERY.h" + +/* +TODO list +- Check value mappings on the PID polls +- Check all TODO:s in the code +- 0x1B000044 & 1B00008F seems to be missing from logs? (Classic CAN) +- Scaled remaining capacity, should take already scaled total capacity into account, or we +should undo the scaling on the total capacity (which is calculated from the ah value now, +which is scaled already). +- Investigate why opening and then closing contactors from webpage does not always work +- Invertigate why contactors don't close when lilygo and battery are powered on simultaneously -> timeout on can msgs triggers to late, reset when open contactors is executed +- Find out how to get the battery in balancing mode +*/ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send +static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis40ms = 0; // will store last time a 40ms CAN Message was send +static unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was send +static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500ms = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send + +static bool toggle = false; +static uint8_t counter_1000ms = 0; +static uint8_t counter_200ms = 0; +static uint8_t counter_100ms = 0; +static uint8_t counter_50ms = 0; +static uint8_t counter_40ms = 0; +static uint8_t counter_20ms = 0; +static uint8_t counter_10ms = 0; +static uint8_t counter_040 = 0; +static uint8_t counter_0F7 = 0; +static uint8_t counter_3b5 = 0; + +static uint32_t poll_pid = PID_CELLVOLTAGE_CELL_85; // We start here to quickly determine the cell size of the pack. +static bool nof_cells_determined = false; +static uint32_t pid_reply = 0; +static uint16_t battery_soc_polled = 0; +static uint16_t battery_voltage_polled = 1480; +static int16_t battery_current_polled = 0; +static int16_t battery_max_temp = 600; +static int16_t battery_min_temp = 600; +static uint16_t battery_max_charge_voltage = 0; +static uint16_t battery_min_discharge_voltage = 0; +static uint16_t battery_allowed_charge_power = 0; +static uint16_t battery_allowed_discharge_power = 0; +static uint16_t cellvoltages_polled[108]; +static uint16_t tempval = 0; +static uint8_t BMS_5A2_CRC = 0; +static uint8_t BMS_5CA_CRC = 0; +static uint8_t BMS_0CF_CRC = 0; +static uint8_t BMS_578_CRC = 0; +static uint8_t BMS_0C0_CRC = 0; +static uint8_t BMS_16A954A6_CRC = 0; +static uint8_t BMS_5A2_counter = 0; +static uint8_t BMS_5CA_counter = 0; +static uint8_t BMS_0CF_counter = 0; +static uint8_t BMS_0C0_counter = 0; +static uint8_t BMS_578_counter = 0; +static uint8_t BMS_16A954A6_counter = 0; +static bool BMS_ext_limits_active = + false; //The current current limits of the HV battery are expanded to start the combustion engine / confirmation of the request +static uint8_t BMS_mode = + 0x07; //0: standby; Gates open; Communication active 1: Main contactor closed / HV network activated / normal driving operation +//2: assigned depending on the project (e.g. balancing, extended DC fast charging) //3: external charging +static uint8_t BMS_HVIL_status = 0; //0 init, 1 seated, 2 open, 3 fault +static bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure) +static uint16_t BMS_current = 16300; +static 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 +static uint32_t BMS_voltage_intermediate = 0; +static uint32_t BMS_voltage = 1480; +static uint8_t BMS_status_voltage_free = + 0; //0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error +static bool BMS_OBD_MIL = false; +static uint8_t BMS_error_status = + 0x7; //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 +static uint16_t BMS_capacity_ah = 0; +static bool BMS_error_lamp_req = false; +static bool BMS_warning_lamp_req = false; +static uint8_t BMS_Kl30c_Status = 0; // 0 init, 1 closed, 2 open, 3 fault +static bool service_disconnect_switch_missing = false; +static bool pilotline_open = false; +static bool balancing_request = false; +static uint8_t battery_diagnostic = 0; +static uint16_t battery_Wh_left = 0; +static uint16_t battery_Wh_max = 1000; +static uint8_t battery_potential_status = 0; +static uint8_t battery_temperature_warning = 0; +static uint16_t max_discharge_power_watt = 0; +static uint16_t max_discharge_current_amp = 0; +static uint16_t max_charge_power_watt = 0; +static uint16_t max_charge_current_amp = 0; +static uint16_t battery_SOC = 1; +static uint16_t usable_energy_amount_Wh = 0; +static uint8_t status_HV_line = 0; //0 init, 1 No open HV line, 2 open HV line detected, 3 fault +static uint8_t warning_support = 0; +static bool battery_heating_active = false; +static uint16_t power_discharge_percentage = 0; +static uint16_t power_charge_percentage = 0; +static uint16_t actual_battery_voltage = 0; +static uint16_t regen_battery = 0; +static uint16_t energy_extracted_from_battery = 0; +static uint16_t max_fastcharging_current_amp = 0; //maximum DC charging current allowed +static uint8_t BMS_Status_DCLS = + 0; //Status of the voltage monitoring on the DC charging interface. 0 inactive, 1 I_O , 2 N_I_O , 3 active +static uint16_t DC_voltage_DCLS = + 0; //Factor 1, DC voltage of the charging station. Measurement between the DC HV lines. +static uint16_t DC_voltage_chargeport = + 0; //Factor 0.5, Current voltage at the HV battery DC charging terminals; Outlet to the DC charging plug. +static uint8_t BMS_welded_contactors_status = + 0; //0: Init no diagnostic result, 1: no contactor welded, 2: at least 1 contactor welded, 3: Protection status detection error +static 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 +static 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 +static uint16_t power_battery_heating_watt = 0; +static uint16_t power_battery_heating_req_watt = 0; +static uint8_t cooling_request = + 0; //0 = No cooling, 1 = Light cooling, cabin prio, 2= higher cooling, 3 = immediate cooling, 4 = emergency cooling +static uint8_t heating_request = 0; //0 = init, 1= maintain temp, 2=higher demand, 3 = immediate heat demand +static uint8_t balancing_active = false; //0 = init, 1 active, 2 not active +static bool charging_active = false; +static uint16_t max_energy_Wh = 0; +static uint16_t max_charge_percent = 0; +static uint16_t min_charge_percent = 0; +static uint16_t isolation_resistance_kOhm = 0; +static bool battery_heating_installed = false; +static bool error_NT_circuit = false; +static uint8_t pump_1_control = 0; //0x0D not installed, 0x0E init, 0x0F fault +static uint8_t pump_2_control = 0; //0x0D not installed, 0x0E init, 0x0F fault +static uint8_t target_flow_temperature_C = 0; //*0,5 -40 +static uint8_t return_temperature_C = 0; //*0,5 -40 +static uint8_t status_valve_1 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault +static uint8_t status_valve_2 = 0; //0 not active, 1 active, 5 not installed, 6 init, 7 fault +static uint8_t battery_temperature = 0; +static uint8_t temperature_request = + 0; //0 high cooling, 1 medium cooling, 2 low cooling, 3 no temp requirement init, 4 low heating , 5 medium heating, 6 high heating, 7 circulation +static uint16_t performance_index_discharge_peak_temperature_percentage = 0; +static uint16_t performance_index_charge_peak_temperature_percentage = 0; +static uint8_t temperature_status_discharge = + 0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault +static uint8_t temperature_status_charge = + 0; //0 init, 1 temp under optimal, 2 temp optimal, 3 temp over optimal, 7 fault +static uint8_t isolation_fault = + 0; //0 init, 1 no iso fault, 2 iso fault threshold1, 3 iso fault threshold2, 4 IWU defective +static uint8_t isolation_status = + 0; // 0 init, 1 = larger threshold1, 2 = smaller threshold1 total, 3 = smaller threshold1 intern, 4 = smaller threshold2 total, 5 = smaller threshold2 intern, 6 = no measurement, 7 = measurement active +static uint8_t actual_temperature_highest_C = 0; //*0,5 -40 +static uint8_t actual_temperature_lowest_C = 0; //*0,5 -40 +static uint16_t actual_cellvoltage_highest_mV = 0; //bias 1000 +static uint16_t actual_cellvoltage_lowest_mV = 0; //bias 1000 +static uint16_t predicted_power_dyn_standard_watt = 0; +static uint8_t predicted_time_dyn_standard_minutes = 0; +static uint8_t mux = 0; +static int8_t celltemperature[56] = {0}; //Temperatures 1-56. Value is 0xFD if sensor not present +static uint16_t cellvoltages[160] = {0}; +static uint16_t duration_discharge_power_watt = 0; +static uint16_t duration_charge_power_watt = 0; +static uint16_t maximum_voltage = 0; +static uint16_t minimum_voltage = 0; +static uint8_t battery_serialnumber[26]; +static uint8_t realtime_overcurrent_monitor = 0; +static uint8_t realtime_CAN_communication_fault = 0; +static uint8_t realtime_overcharge_warning = 0; +static uint8_t realtime_SOC_too_high = 0; +static uint8_t realtime_SOC_too_low = 0; +static uint8_t realtime_SOC_jumping_warning = 0; +static uint8_t realtime_temperature_difference_warning = 0; +static uint8_t realtime_cell_overtemperature_warning = 0; +static uint8_t realtime_cell_undertemperature_warning = 0; +static uint8_t realtime_battery_overvoltage_warning = 0; +static uint8_t realtime_battery_undervoltage_warning = 0; +static uint8_t realtime_cell_overvoltage_warning = 0; +static uint8_t realtime_cell_undervoltage_warning = 0; +static uint8_t realtime_cell_imbalance_warning = 0; +static uint8_t realtime_warning_battery_unathorized = 0; +static bool component_protection_active = false; +static bool shutdown_active = false; +static bool transportation_mode_active = false; +static uint8_t KL15_mode = 0; +static uint8_t bus_knockout_timer = 0; +static uint8_t hybrid_wakeup_reason = 0; +static uint8_t wakeup_type = 0; +static bool instrumentation_cluster_request = false; +static uint8_t seconds = 0; +static uint32_t first_can_msg = 0; +static uint32_t last_can_msg_timestamp = 0; + +#define TIME_YEAR 2024 +#define TIME_MONTH 8 +#define TIME_DAY 20 +#define TIME_HOUR 10 +#define TIME_MINUTE 5 +#define TIME_SECOND 0 + +#define BMS_TARGET_HV_OFF 0 +#define BMS_TARGET_HV_ON 1 +#define BMS_TARGET_AC_CHARGING_EXT 3 //(HS + AC_2 contactors closed) +#define BMS_TARGET_AC_CHARGING 4 //(HS + AC contactors closed) +#define BMS_TARGET_DC_CHARGING 6 //(HS + DC contactors closed) +#define BMS_TARGET_INIT 7 + +#define DC_FASTCHARGE_NO_START_REQUEST 0x00 +#define DC_FASTCHARGE_VEHICLE 0x40 +#define DC_FASTCHARGE_LS1 0x80 +#define DC_FASTCHARGE_LS2 0xC0 + +CAN_frame MEB_POLLING_FRAME = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1C40007B, // SOC 02 8C + .data = {0x03, 0x22, 0x02, 0x8C, 0x55, 0x55, 0x55, 0x55}}; +CAN_frame MEB_ACK_FRAME = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1C40007B, // Ack + .data = {0x30, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55}}; +//Messages needed for contactor closing +CAN_frame MEB_040 = {.FD = true, // Airbag + .ext_ID = false, + .DLC = 8, + .ID = 0x040, //Frame5 has HV deactivate request. Needs to be 0x00 + .data = {0x7E, 0x83, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00}}; +CAN_frame MEB_0C0 = { + .FD = true, // EM1 message + .ext_ID = false, + .DLC = 32, + .ID = 0x0C0, //Frame 5-6 and maybe 7-8 important (external voltage at inverter) + .data = {0x77, 0x0A, 0xFE, 0xE7, 0x7F, 0x10, 0x27, 0x00, 0xE0, 0x7F, 0xFF, 0xF3, 0x3F, 0xFF, 0xF3, 0x3F, + 0xFC, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_0FC = { + .FD = true, // + .ext_ID = false, + .DLC = 48, + .ID = 0x0FC, //This message contains emergency regen request?(byte19), battery needs to see this message + .data = {0x07, 0x08, 0x00, 0x00, 0x7E, 0x00, 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFE, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xF4, 0x01, 0x40, 0xFF, 0xEB, 0x7F, 0x0A, 0x88, 0xE3, 0x81, 0xAF, 0x42}}; +CAN_frame MEB_6B2 = {.FD = true, // Diagnostics + .ext_ID = false, + .DLC = 8, + .ID = 0x6B2, + .data = {0x6A, 0xA7, 0x37, 0x80, 0xC9, 0xBD, 0xF6, 0xC2}}; +CAN_frame MEB_17FC007B_poll = {.FD = true, // Non period request + .ext_ID = true, + .DLC = 8, + .ID = 0x17FC007B, + .data = {0x03, 0x22, 0x1E, 0x3D, 0x55, 0x55, 0x55, 0x55}}; +CAN_frame MEB_1A5555A6 = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1A5555A6, + .data = {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_585 = { + .FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x585, + .data = {0xCF, 0x38, 0xAF, 0x5B, 0x25, 0x00, 0x00, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log +// .data = {0xCF, 0x38, 0x20, 0x02, 0x25, 0xF7, 0x30, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log +CAN_frame MEB_5F5 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x5F5, + .data = {0x23, 0x02, 0x39, 0xC0, 0x1B, 0x8B, 0xC8, 0x1B}}; +CAN_frame MEB_641 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x641, + .data = {0x37, 0x18, 0x00, 0x00, 0xF0, 0x00, 0xAA, 0x70}}; +CAN_frame MEB_3C0 = {.FD = true, + .ext_ID = false, + .DLC = 4, + .ID = 0x3C0, + .data = {0x66, 0x00, 0x00, 0x00}}; // Klemmen_status_01 +CAN_frame MEB_0FD = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x0FD, //CRC and counter, otherwise static + .data = {0x5F, 0xD0, 0x1F, 0x81, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_16A954FB = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x16A954FB, + .data = {0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_1A555548 = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1A555548, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_1A55552B = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x1A55552B, + .data = {0x00, 0x00, 0x00, 0xA0, 0x02, 0x04, 0x00, 0x30}}; +CAN_frame MEB_569 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x569, //HVEM + .data = {0x00, 0x00, 0x01, 0x3A, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_16A954B4 = {.FD = true, + .ext_ID = true, + .DLC = 8, + .ID = 0x16A954B4, //eTM + .data = {0xFE, 0xB6, 0x0D, 0x00, 0x00, 0xD5, 0x48, 0xFD}}; +CAN_frame MEB_1B000046 = {.FD = false, // Not FD + .ext_ID = true, + .DLC = 8, + .ID = 0x1B000046, // Klima + .data = {0x00, 0x40, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_1B000010 = {.FD = false, // Not FD + .ext_ID = true, + .DLC = 8, + .ID = 0x1B000010, // Gateway + .data = {0x00, 0x50, 0x08, 0x50, 0x01, 0xFF, 0x30, 0x00}}; +CAN_frame MEB_1B0000B9 = {.FD = false, // Not FD + .ext_ID = true, + .DLC = 8, + .ID = 0x1B0000B9, //DC/DC converter + .data = {0x00, 0x40, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame MEB_153 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x153, // Static content + .data = {0x00, 0x00, 0x00, 0xFF, 0xEF, 0xFE, 0xFF, 0xFF}}; +CAN_frame MEB_5E1 = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x5E1, // Static content + .data = {0x7F, 0x2A, 0x00, 0x60, 0xFE, 0x00, 0x00, 0x00}}; +CAN_frame MEB_3BE = {.FD = true, + .ext_ID = false, + .DLC = 8, + .ID = 0x3BE, // CRC, otherwise Static content + .data = {0x57, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x04, 0x40}}; +CAN_frame MEB_272 = {.FD = true, //HVLM_14 + .ext_ID = false, + .DLC = 8, + .ID = 0x272, // Static content + .data = {0x00, 0x00, 0x00, 0x00, 0x48, 0x08, 0x00, 0x94}}; +CAN_frame MEB_503 = {.FD = true, //HVK_01 + .ext_ID = false, + .DLC = 8, + .ID = 0x503, // Content varies. Frame1 & 3 has HV req + .data = {0x5D, 0x61, 0x00, 0xFF, 0x7F, 0x80, 0xE3, 0x03}}; +CAN_frame MEB_14C = { + .FD = true, //Motor message + .ext_ID = false, + .DLC = 32, + .ID = 0x14C, //CRC needed, static content otherwise + .data = {0x38, 0x0A, 0xFF, 0x01, 0x01, 0xFF, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x25, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE}}; +/** Calculate the CRC checksum for VAG CAN Messages + * + * The method used is described in Chapter "7.2.1.2 8-bit 0x2F polynomial CRC Calculation". + * CRC Parameters: + * 0x2F - Polynomial + * 0xFF - Initial Value + * 0xFF - XOR Output + * + * @see https://github.com/crasbe/VW-OnBoard-Charger + * @see https://github.com/colinoflynn/crcbeagle for CRC hacking :) + * @see https://github.com/commaai/opendbc/blob/master/can/common.cc#L110 + * @see https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf + * @see https://web.archive.org/web/20221105210302/https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf + */ +uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint16_t address) { + + const uint8_t poly = 0x2F; + const uint8_t xor_output = 0xFF; + // VAG Magic Bytes + const uint8_t MB0040[16] = {0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40}; + const uint8_t MB00C0[16] = {0x2f, 0x44, 0x72, 0xd3, 0x07, 0xf2, 0x39, 0x09, + 0x8d, 0x6f, 0x57, 0x20, 0x37, 0xf9, 0x9b, 0xfa}; + const uint8_t MB00FC[16] = {0x77, 0x5c, 0xa0, 0x89, 0x4b, 0x7c, 0xbb, 0xd6, + 0x1f, 0x6c, 0x4f, 0xf6, 0x20, 0x2b, 0x43, 0xdd}; + const uint8_t MB00FD[16] = {0xb4, 0xef, 0xf8, 0x49, 0x1e, 0xe5, 0xc2, 0xc0, + 0x97, 0x19, 0x3c, 0xc9, 0xf1, 0x98, 0xd6, 0x61}; + const uint8_t MB0097[16] = {0x3C, 0x54, 0xCF, 0xA3, 0x81, 0x93, 0x0B, 0xC7, + 0x3E, 0xDF, 0x1C, 0xB0, 0xA7, 0x25, 0xD3, 0xD8}; + const uint8_t MB00F7[16] = {0x5F, 0xA0, 0x44, 0xD0, 0x63, 0x59, 0x5B, 0xA2, + 0x68, 0x04, 0x90, 0x87, 0x52, 0x12, 0xB4, 0x9E}; + const uint8_t MB0124[16] = {0x12, 0x7E, 0x34, 0x16, 0x25, 0x8F, 0x8E, 0x35, + 0xBA, 0x7F, 0xEA, 0x59, 0x4C, 0xF0, 0x88, 0x15}; + const uint8_t MB0153[16] = {0x03, 0x13, 0x23, 0x7a, 0x40, 0x51, 0x68, 0xba, + 0xa8, 0xbe, 0x55, 0x02, 0x11, 0x31, 0x76, 0xec}; + const uint8_t MB014C[16] = {0x16, 0x35, 0x59, 0x15, 0x9a, 0x2a, 0x97, 0xb8, + 0x0e, 0x4e, 0x30, 0xcc, 0xb3, 0x07, 0x01, 0xad}; + const uint8_t MB0187[16] = {0x7F, 0xED, 0x17, 0xC2, 0x7C, 0xEB, 0x44, 0x21, + 0x01, 0xFA, 0xDB, 0x15, 0x4A, 0x6B, 0x23, 0x05}; + const uint8_t MB03A6[16] = {0xB6, 0x1C, 0xC1, 0x23, 0x6D, 0x8B, 0x0C, 0x51, + 0x38, 0x32, 0x24, 0xA8, 0x3F, 0x3A, 0xA4, 0x02}; + const uint8_t MB03AF[16] = {0x94, 0x6A, 0xB5, 0x38, 0x8A, 0xB4, 0xAB, 0x27, + 0xCB, 0x22, 0x88, 0xEF, 0xA3, 0xE1, 0xD0, 0xBB}; + const uint8_t MB03BE[16] = {0x1f, 0x28, 0xc6, 0x85, 0xe6, 0xf8, 0xb0, 0x19, + 0x5b, 0x64, 0x35, 0x21, 0xe4, 0xf7, 0x9c, 0x24}; + const uint8_t MB03C0[16] = {0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3}; + const uint8_t MB0503[16] = {0xed, 0xd6, 0x96, 0x63, 0xa5, 0x12, 0xd5, 0x9a, + 0x1e, 0x0d, 0x24, 0xcd, 0x8c, 0xa6, 0x2f, 0x41}; + const uint8_t MB0578[16] = {0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48}; + const uint8_t MB05CA[16] = {0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43}; + const uint8_t MB0641[16] = {0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47}; + const uint8_t MB06A3[16] = {0xC1, 0x8B, 0x38, 0xA8, 0xA4, 0x27, 0xEB, 0xC8, + 0xEF, 0x05, 0x9A, 0xBB, 0x39, 0xF7, 0x80, 0xA7}; + const uint8_t MB06A4[16] = {0xC7, 0xD8, 0xF1, 0xC4, 0xE3, 0x5E, 0x9A, 0xE2, + 0xA1, 0xCB, 0x02, 0x4F, 0x57, 0x4E, 0x8E, 0xE4}; + const uint8_t MB16A954A6[16] = {0x79, 0xB9, 0x67, 0xAD, 0xD5, 0xF7, 0x70, 0xAA, + 0x44, 0x61, 0x5A, 0xDC, 0x26, 0xB4, 0xD2, 0xC3}; + + uint8_t crc = 0xFF; + uint8_t magicByte = 0x00; + uint8_t counter = inputBytes[1] & 0x0F; // only the low byte of the couner is relevant + + switch (address) { + case 0x0040: // Airbag + magicByte = MB0040[counter]; + break; + case 0x00C0: // + magicByte = MB00C0[counter]; + break; + case 0x00FC: + magicByte = MB00FC[counter]; + break; + case 0x00FD: + magicByte = MB00FD[counter]; + break; + case 0x0097: // ?? + magicByte = MB0097[counter]; + break; + case 0x00F7: // ?? + magicByte = MB00F7[counter]; + break; + case 0x0124: // ?? + magicByte = MB0124[counter]; + break; + case 0x014C: // Motor + magicByte = MB014C[counter]; + break; + case 0x0153: // HYB30 + magicByte = MB0153[counter]; + break; + case 0x0187: // EV_Gearshift "Gear" selection data for EVs with no gearbox + magicByte = MB0187[counter]; + break; + case 0x03A6: // ?? + magicByte = MB03A6[counter]; + break; + case 0x03AF: // ?? + magicByte = MB03AF[counter]; + break; + case 0x03BE: // Motor + magicByte = MB03BE[counter]; + break; + case 0x03C0: // Klemmen status + magicByte = MB03C0[counter]; + break; + case 0x0503: // HVK + magicByte = MB0503[counter]; + break; + case 0x0578: // BMS DC + magicByte = MB0578[counter]; + break; + case 0x05CA: // BMS + magicByte = MB05CA[counter]; + break; + case 0x0641: // Motor + magicByte = MB0641[counter]; + break; + case 0x06A3: // ?? + magicByte = MB06A3[counter]; + break; + case 0x06A4: // ?? + magicByte = MB06A4[counter]; + break; + case 0x16A954A6: + magicByte = MB16A954A6[counter]; + break; + default: // this won't lead to correct CRC checksums + magicByte = 0x00; + break; + } + + for (uint8_t i = 1; i < length + 1; i++) { + // We skip the empty CRC position and start at the timer + // The last element is the VAG magic byte for address 0x187 depending on the counter value. + if (i < length) + crc ^= inputBytes[i]; + else + crc ^= magicByte; + + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80) + crc = (crc << 1) ^ poly; + else + crc = (crc << 1); + } + } + + crc ^= xor_output; + + return crc; +} + +void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + + datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100 battery_soc_polled * 10; + //Alternatively use battery_SOC for more precision + + datalayer.battery.status.soh_pptt; + + datalayer.battery.status.voltage_dV = BMS_voltage * 2.5; // *0.25*10 + + datalayer.battery.status.current_dA = (BMS_current / 10) - 1630; + + datalayer.battery.info.total_capacity_Wh = + ((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2; + + datalayer.battery.status.remaining_capacity_Wh = usable_energy_amount_Wh * 5; + //Alternatively use battery_Wh_left + + datalayer.battery.status.max_charge_power_W = (max_charge_power_watt * 100); + + datalayer.battery.status.max_discharge_power_W = (max_discharge_power_watt * 100); + + //Power in watts, Negative = charging batt + datalayer.battery.status.active_power_W = + ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); + + datalayer.battery.status.temperature_min_dC = (battery_min_temp - 350) / 2; + + datalayer.battery.status.temperature_max_dC = (battery_max_temp - 350) / 2; + + //Map all cell voltages to the global array + memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_polled, 108 * sizeof(uint16_t)); + + // Initialize min and max, lets find which cells are min and max! + uint16_t min_cell_mv_value = std::numeric_limits::max(); + uint16_t max_cell_mv_value = 0; + // Loop to find the min and max while ignoring zero values + for (uint8_t i = 0; i < 108; ++i) { + uint16_t voltage_mV = datalayer.battery.status.cell_voltages_mV[i]; + if (voltage_mV != 0) { // Skip unread values (0) + min_cell_mv_value = std::min(min_cell_mv_value, voltage_mV); + max_cell_mv_value = std::max(max_cell_mv_value, voltage_mV); + } + } + // If all array values are 0, reset min/max to 3700 + if (min_cell_mv_value == std::numeric_limits::max()) { + min_cell_mv_value = 3700; + max_cell_mv_value = 3700; + } + + datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value; + datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value; + //TODO, use actual_cellvoltage_lowest_mV instead to save performance + + if (service_disconnect_switch_missing) { + set_event(EVENT_HVIL_FAILURE, 1); + } else { + clear_event(EVENT_HVIL_FAILURE); + } + if (pilotline_open || BMS_HVIL_status == 2) { + set_event(EVENT_HVIL_FAILURE, 2); + } else { + clear_event(EVENT_HVIL_FAILURE); + } + + // Update webserver datalayer for "More battery info" page + datalayer_extended.meb.SDSW = service_disconnect_switch_missing; + datalayer_extended.meb.pilotline = pilotline_open; + datalayer_extended.meb.transportmode = transportation_mode_active; + datalayer_extended.meb.componentprotection = component_protection_active; + datalayer_extended.meb.shutdown_active = shutdown_active; + datalayer_extended.meb.HVIL = BMS_HVIL_status; + datalayer_extended.meb.BMS_mode = BMS_mode; + datalayer_extended.meb.battery_diagnostic = battery_diagnostic; + datalayer_extended.meb.status_HV_line = status_HV_line; + datalayer_extended.meb.warning_support = warning_support; + datalayer_extended.meb.BMS_status_voltage_free = BMS_status_voltage_free; + datalayer_extended.meb.BMS_OBD_MIL = BMS_OBD_MIL; + datalayer_extended.meb.BMS_error_status = BMS_error_status; + datalayer_extended.meb.BMS_error_lamp_req = BMS_error_lamp_req; + datalayer_extended.meb.BMS_warning_lamp_req = BMS_warning_lamp_req; + datalayer_extended.meb.BMS_Kl30c_Status = BMS_Kl30c_Status; + datalayer_extended.meb.BMS_voltage_intermediate_dV = (BMS_voltage_intermediate - 2000) * 10 / 2; + datalayer_extended.meb.BMS_voltage_dV = BMS_voltage * 10 / 4; + datalayer_extended.meb.isolation_resistance = isolation_resistance_kOhm * 5; + datalayer_extended.meb.battery_heating = battery_heating_active; + datalayer_extended.meb.rt_overcurrent = realtime_overcurrent_monitor; + datalayer_extended.meb.rt_CAN_fault = realtime_CAN_communication_fault; + datalayer_extended.meb.rt_overcharge = realtime_overcharge_warning; + datalayer_extended.meb.rt_SOC_high = realtime_SOC_too_high; + datalayer_extended.meb.rt_SOC_low = realtime_SOC_too_low; + datalayer_extended.meb.rt_SOC_jumping = realtime_SOC_jumping_warning; + datalayer_extended.meb.rt_temp_difference = realtime_temperature_difference_warning; + datalayer_extended.meb.rt_cell_overtemp = realtime_cell_overtemperature_warning; + datalayer_extended.meb.rt_cell_undertemp = realtime_cell_undertemperature_warning; + datalayer_extended.meb.rt_battery_overvolt = realtime_battery_overvoltage_warning; + datalayer_extended.meb.rt_battery_undervol = realtime_battery_undervoltage_warning; + datalayer_extended.meb.rt_cell_overvolt = realtime_cell_overvoltage_warning; + datalayer_extended.meb.rt_cell_undervol = realtime_cell_undervoltage_warning; + datalayer_extended.meb.rt_cell_imbalance = realtime_cell_imbalance_warning; + datalayer_extended.meb.rt_battery_unathorized = realtime_warning_battery_unathorized; +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + last_can_msg_timestamp = millis(); + if (first_can_msg == 0) + first_can_msg = last_can_msg_timestamp; + switch (rx_frame.ID) { + case 0x17F0007B: // BMS 500ms + component_protection_active = (rx_frame.data.u8[0] & 0x01); + shutdown_active = ((rx_frame.data.u8[0] & 0x02) >> 1); + transportation_mode_active = ((rx_frame.data.u8[0] & 0x02) >> 1); + KL15_mode = ((rx_frame.data.u8[0] & 0xF0) >> 4); + //0 = communication only when terminal 15 = ON (no run-on, cannot be woken up) + //1 = communication after terminal 15 = OFF (run-on, cannot be woken up) + //2 = communication when terminal 15 = OFF (run-on, can be woken up) + bus_knockout_timer = rx_frame.data.u8[5]; + hybrid_wakeup_reason = rx_frame.data.u8[6]; //(if several active, lowest wins) + //0 = wakeup cause not known 1 = Bus wakeup2 = KL15 HW 3 = TPA active + break; + case 0x17FE007B: // BMS - Offboard tester diag response + break; + case 0x1B00007B: // BMS - 200ms + wakeup_type = + ((rx_frame.data.u8[1] & 0x10) >> 4); //0 passive, SG has not woken up, 1 active, SG has woken up the network + instrumentation_cluster_request = ((rx_frame.data.u8[1] & 0x40) >> 6); //True/false + break; + case 0x12DD54D0: // BMS Limits 100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + max_discharge_power_watt = + ((rx_frame.data.u8[6] & 0x07) << 10) | (rx_frame.data.u8[5] << 2) | (rx_frame.data.u8[4] & 0xC0) >> 6; //*100 + max_discharge_current_amp = + ((rx_frame.data.u8[3] & 0x01) << 12) | (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4); //*0.2 + max_charge_power_watt = (rx_frame.data.u8[7] << 5) | (rx_frame.data.u8[6] >> 3); //*100 + max_charge_current_amp = ((rx_frame.data.u8[4] & 0x3F) << 7) | (rx_frame.data.u8[3] >> 1); //*0.2 + break; + case 0x12DD54D1: // BMS 100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + if (rx_frame.data.u8[6] != 0xFE || rx_frame.data.u8[7] != 0xFF) { // Init state, values below invalid + battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05 + usable_energy_amount_Wh = (rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]; //*5 + power_discharge_percentage = ((rx_frame.data.u8[4] & 0x3F) << 4) | rx_frame.data.u8[3] >> 4; //*0.2 + power_charge_percentage = (rx_frame.data.u8[5] << 2) | rx_frame.data.u8[4] >> 6; //*0.2 + } + status_HV_line = ((rx_frame.data.u8[2] & 0x01) << 1) | rx_frame.data.u8[1] >> 7; + warning_support = (rx_frame.data.u8[1] & 0x70) >> 4; + break; + case 0x12DD54D2: // BMS 100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery_heating_active = (rx_frame.data.u8[4] & 0x40) >> 6; + heating_request = (rx_frame.data.u8[5] & 0xE0) >> 5; + cooling_request = (rx_frame.data.u8[5] & 0x1C) >> 2; + power_battery_heating_watt = rx_frame.data.u8[6]; + power_battery_heating_req_watt = rx_frame.data.u8[7]; + break; + case 0x1A555550: // BMS 500ms + balancing_active = (rx_frame.data.u8[1] & 0xC0) >> 6; + charging_active = (rx_frame.data.u8[2] & 0x01); + max_energy_Wh = ((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[5]; //*40 + max_charge_percent = ((rx_frame.data.u8[7] << 3) | rx_frame.data.u8[6] >> 5); //*0.05 + min_charge_percent = ((rx_frame.data.u8[4] << 3) | rx_frame.data.u8[3] >> 5); //*0.05 + isolation_resistance_kOhm = (((rx_frame.data.u8[3] & 0x1F) << 7) | rx_frame.data.u8[2] >> 1); //*5 + break; + case 0x1A555551: // BMS 500ms + battery_heating_installed = (rx_frame.data.u8[1] & 0x20) >> 5; + error_NT_circuit = (rx_frame.data.u8[1] & 0x40) >> 6; + pump_1_control = rx_frame.data.u8[2] & 0x0F; //*10, percent + pump_2_control = (rx_frame.data.u8[2] & 0xF0) >> 4; //*10, percent + status_valve_1 = (rx_frame.data.u8[3] & 0x1C) >> 2; + status_valve_2 = (rx_frame.data.u8[3] & 0xE0) >> 5; + temperature_request = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7); + battery_temperature = rx_frame.data.u8[5]; //*0,5 -40 + target_flow_temperature_C = rx_frame.data.u8[6]; //*0,5 -40 + return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40 + break; + case 0x1A5555B2: // BMS + performance_index_discharge_peak_temperature_percentage = + (((rx_frame.data.u8[3] & 0x07) << 6) | rx_frame.data.u8[2] >> 2); //*0.2 + performance_index_charge_peak_temperature_percentage = + (((rx_frame.data.u8[4] & 0x3F) << 3) | rx_frame.data.u8[3] >> 5); //*0.2 + temperature_status_discharge = (rx_frame.data.u8[1] & 0x70) >> 4; + temperature_status_charge = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7); + break; + case 0x16A954A6: // BMS + BMS_16A954A6_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5; + isolation_status = (rx_frame.data.u8[2] & 0x1E) >> 1; + actual_temperature_highest_C = rx_frame.data.u8[3]; //*0,5 -40 + actual_temperature_lowest_C = rx_frame.data.u8[4]; //*0,5 -40 + actual_cellvoltage_highest_mV = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]); + actual_cellvoltage_lowest_mV = ((rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4); + break; + case 0x16A954F8: // BMS + predicted_power_dyn_standard_watt = ((rx_frame.data.u8[6] << 1) | rx_frame.data.u8[5] >> 7); //*50 + predicted_time_dyn_standard_minutes = rx_frame.data.u8[7]; + break; + case 0x16A954E8: // BMS Temperature and cellvoltages - 180ms + mux = (rx_frame.data.u8[0] & 0x0F); + switch (mux) { + case 0: // Temperatures 1-56. Value is 0xFD if sensor not present + for (uint8_t i = 0; i < 56; i++) { + celltemperature[i] = (rx_frame.data.u8[i + 1] / 2) - 40; + } + break; + case 1: // Cellvoltages 1-42 + cellvoltages[0] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[1] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[2] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[3] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[4] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[5] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[6] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[7] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[8] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[9] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[10] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[11] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[12] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[13] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[14] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[15] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[16] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[17] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[18] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[19] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[20] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[21] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[22] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[23] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[24] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[25] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[26] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[27] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[28] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[29] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[30] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[31] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[32] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[33] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + cellvoltages[34] = (((rx_frame.data.u8[53] & 0x0F) << 8) | rx_frame.data.u8[52]) + 1000; + cellvoltages[35] = ((rx_frame.data.u8[54] << 4) | (rx_frame.data.u8[53] >> 4)) + 1000; + cellvoltages[36] = (((rx_frame.data.u8[56] & 0x0F) << 8) | rx_frame.data.u8[55]) + 1000; + cellvoltages[37] = ((rx_frame.data.u8[57] << 4) | (rx_frame.data.u8[56] >> 4)) + 1000; + cellvoltages[38] = (((rx_frame.data.u8[59] & 0x0F) << 8) | rx_frame.data.u8[58]) + 1000; + cellvoltages[39] = ((rx_frame.data.u8[60] << 4) | (rx_frame.data.u8[59] >> 4)) + 1000; + cellvoltages[40] = (((rx_frame.data.u8[62] & 0x0F) << 8) | rx_frame.data.u8[61]) + 1000; + cellvoltages[41] = ((rx_frame.data.u8[63] << 4) | (rx_frame.data.u8[62] >> 4)) + 1000; + break; + case 2: // Cellvoltages 43-84 + cellvoltages[42] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[43] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[44] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[45] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[46] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[47] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[48] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[49] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[50] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[51] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[52] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[53] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[54] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[55] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[56] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[57] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[58] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[59] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[60] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[61] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[62] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[63] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[64] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[65] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[66] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[67] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[68] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[69] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[70] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[71] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[72] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[73] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[74] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[75] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + cellvoltages[76] = (((rx_frame.data.u8[53] & 0x0F) << 8) | rx_frame.data.u8[52]) + 1000; + cellvoltages[77] = ((rx_frame.data.u8[54] << 4) | (rx_frame.data.u8[53] >> 4)) + 1000; + cellvoltages[78] = (((rx_frame.data.u8[56] & 0x0F) << 8) | rx_frame.data.u8[55]) + 1000; + cellvoltages[79] = ((rx_frame.data.u8[57] << 4) | (rx_frame.data.u8[56] >> 4)) + 1000; + cellvoltages[80] = (((rx_frame.data.u8[59] & 0x0F) << 8) | rx_frame.data.u8[58]) + 1000; + cellvoltages[81] = ((rx_frame.data.u8[60] << 4) | (rx_frame.data.u8[59] >> 4)) + 1000; + cellvoltages[82] = (((rx_frame.data.u8[62] & 0x0F) << 8) | rx_frame.data.u8[61]) + 1000; + cellvoltages[83] = ((rx_frame.data.u8[63] << 4) | (rx_frame.data.u8[62] >> 4)) + 1000; + break; + case 3: // Cellvoltages 85-126 + cellvoltages[84] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[85] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[86] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[87] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[88] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[89] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[90] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[91] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[92] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[93] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[94] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[95] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[96] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[97] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[98] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[99] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[100] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[101] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[102] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[103] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[104] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[105] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[106] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[107] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[108] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[109] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[110] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[111] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[112] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[113] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[114] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[115] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[116] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[117] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + cellvoltages[118] = (((rx_frame.data.u8[53] & 0x0F) << 8) | rx_frame.data.u8[52]) + 1000; + cellvoltages[119] = ((rx_frame.data.u8[54] << 4) | (rx_frame.data.u8[53] >> 4)) + 1000; + cellvoltages[120] = (((rx_frame.data.u8[56] & 0x0F) << 8) | rx_frame.data.u8[55]) + 1000; + cellvoltages[121] = ((rx_frame.data.u8[57] << 4) | (rx_frame.data.u8[56] >> 4)) + 1000; + cellvoltages[122] = (((rx_frame.data.u8[59] & 0x0F) << 8) | rx_frame.data.u8[58]) + 1000; + cellvoltages[123] = ((rx_frame.data.u8[60] << 4) | (rx_frame.data.u8[59] >> 4)) + 1000; + cellvoltages[124] = (((rx_frame.data.u8[62] & 0x0F) << 8) | rx_frame.data.u8[61]) + 1000; + cellvoltages[125] = ((rx_frame.data.u8[63] << 4) | (rx_frame.data.u8[62] >> 4)) + 1000; + break; + case 4: //Cellvoltages 127-160 + cellvoltages[126] = (((rx_frame.data.u8[2] & 0x0F) << 8) | rx_frame.data.u8[1]) + 1000; + cellvoltages[127] = ((rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[2] >> 4)) + 1000; + cellvoltages[128] = (((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]) + 1000; + cellvoltages[129] = ((rx_frame.data.u8[6] << 4) | (rx_frame.data.u8[5] >> 4)) + 1000; + cellvoltages[130] = (((rx_frame.data.u8[8] & 0x0F) << 8) | rx_frame.data.u8[7]) + 1000; + cellvoltages[131] = ((rx_frame.data.u8[9] << 4) | (rx_frame.data.u8[8] >> 4)) + 1000; + cellvoltages[132] = (((rx_frame.data.u8[11] & 0x0F) << 8) | rx_frame.data.u8[10]) + 1000; + cellvoltages[133] = ((rx_frame.data.u8[12] << 4) | (rx_frame.data.u8[11] >> 4)) + 1000; + cellvoltages[134] = (((rx_frame.data.u8[14] & 0x0F) << 8) | rx_frame.data.u8[13]) + 1000; + cellvoltages[135] = ((rx_frame.data.u8[15] << 4) | (rx_frame.data.u8[14] >> 4)) + 1000; + cellvoltages[136] = (((rx_frame.data.u8[17] & 0x0F) << 8) | rx_frame.data.u8[16]) + 1000; + cellvoltages[137] = ((rx_frame.data.u8[18] << 4) | (rx_frame.data.u8[17] >> 4)) + 1000; + cellvoltages[138] = (((rx_frame.data.u8[20] & 0x0F) << 8) | rx_frame.data.u8[19]) + 1000; + cellvoltages[139] = ((rx_frame.data.u8[21] << 4) | (rx_frame.data.u8[20] >> 4)) + 1000; + cellvoltages[140] = (((rx_frame.data.u8[23] & 0x0F) << 8) | rx_frame.data.u8[22]) + 1000; + cellvoltages[141] = ((rx_frame.data.u8[24] << 4) | (rx_frame.data.u8[23] >> 4)) + 1000; + cellvoltages[142] = (((rx_frame.data.u8[26] & 0x0F) << 8) | rx_frame.data.u8[25]) + 1000; + cellvoltages[143] = ((rx_frame.data.u8[27] << 4) | (rx_frame.data.u8[26] >> 4)) + 1000; + cellvoltages[144] = (((rx_frame.data.u8[29] & 0x0F) << 8) | rx_frame.data.u8[28]) + 1000; + cellvoltages[145] = ((rx_frame.data.u8[30] << 4) | (rx_frame.data.u8[29] >> 4)) + 1000; + cellvoltages[146] = (((rx_frame.data.u8[32] & 0x0F) << 8) | rx_frame.data.u8[31]) + 1000; + cellvoltages[147] = ((rx_frame.data.u8[33] << 4) | (rx_frame.data.u8[32] >> 4)) + 1000; + cellvoltages[148] = (((rx_frame.data.u8[35] & 0x0F) << 8) | rx_frame.data.u8[34]) + 1000; + cellvoltages[149] = ((rx_frame.data.u8[36] << 4) | (rx_frame.data.u8[35] >> 4)) + 1000; + cellvoltages[150] = (((rx_frame.data.u8[38] & 0x0F) << 8) | rx_frame.data.u8[37]) + 1000; + cellvoltages[151] = ((rx_frame.data.u8[39] << 4) | (rx_frame.data.u8[38] >> 4)) + 1000; + cellvoltages[152] = (((rx_frame.data.u8[41] & 0x0F) << 8) | rx_frame.data.u8[40]) + 1000; + cellvoltages[153] = ((rx_frame.data.u8[42] << 4) | (rx_frame.data.u8[41] >> 4)) + 1000; + cellvoltages[154] = (((rx_frame.data.u8[44] & 0x0F) << 8) | rx_frame.data.u8[43]) + 1000; + cellvoltages[155] = ((rx_frame.data.u8[45] << 4) | (rx_frame.data.u8[44] >> 4)) + 1000; + cellvoltages[156] = (((rx_frame.data.u8[47] & 0x0F) << 8) | rx_frame.data.u8[46]) + 1000; + cellvoltages[157] = ((rx_frame.data.u8[48] << 4) | (rx_frame.data.u8[47] >> 4)) + 1000; + cellvoltages[158] = (((rx_frame.data.u8[50] & 0x0F) << 8) | rx_frame.data.u8[49]) + 1000; + cellvoltages[159] = ((rx_frame.data.u8[51] << 4) | (rx_frame.data.u8[50] >> 4)) + 1000; + break; + default: //Invalid mux + //TODO: Add corrupted CAN message counter tick? + break; + } + break; + case 0x1C42017B: // BMS - Non-Cyclic, TP_ISO + //hybrid_01_response_fd_data (Whole frame) + break; + case 0x1A5555B0: // BMS 1000ms cyclic + duration_discharge_power_watt = ((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]; + duration_charge_power_watt = (rx_frame.data.u8[7] << 4) | rx_frame.data.u8[6] >> 4; + maximum_voltage = ((rx_frame.data.u8[3] & 0x3F) << 4) | rx_frame.data.u8[2] >> 4; + minimum_voltage = (rx_frame.data.u8[4] << 2) | rx_frame.data.u8[3] >> 6; + break; + case 0x1A5555B1: // BMS 1000ms cyclic + // All realtime_ have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3 + realtime_overcurrent_monitor = ((rx_frame.data.u8[3] & 0x01) << 2) | rx_frame.data.u8[2] >> 6; + realtime_CAN_communication_fault = (rx_frame.data.u8[3] & 0x0E) >> 1; + realtime_overcharge_warning = (rx_frame.data.u8[3] & 0x70) >> 4; + realtime_SOC_too_high = ((rx_frame.data.u8[4] & 0x03) << 1) | rx_frame.data.u8[3] >> 7; + realtime_SOC_too_low = (rx_frame.data.u8[4] & 0x1C) >> 2; + realtime_SOC_jumping_warning = (rx_frame.data.u8[4] & 0xE0) >> 5; + realtime_temperature_difference_warning = rx_frame.data.u8[5] & 0x07; + realtime_cell_overtemperature_warning = (rx_frame.data.u8[5] & 0x38) >> 3; + realtime_cell_undertemperature_warning = ((rx_frame.data.u8[6] & 0x01) << 2) | rx_frame.data.u8[5] >> 6; + realtime_battery_overvoltage_warning = (rx_frame.data.u8[6] & 0x0E) >> 1; + realtime_battery_undervoltage_warning = (rx_frame.data.u8[6] & 0x70) >> 4; + realtime_cell_overvoltage_warning = ((rx_frame.data.u8[7] & 0x03) << 1) | rx_frame.data.u8[6] >> 7; + realtime_cell_undervoltage_warning = (rx_frame.data.u8[7] & 0x1C) >> 2; + realtime_cell_imbalance_warning = (rx_frame.data.u8[7] & 0xE0) >> 5; + for (uint8_t i = 0; i < 26; i++) { // Frame 9 to 34 is S/N for battery + battery_serialnumber[i] = rx_frame.data.u8[i + 9]; + } + realtime_warning_battery_unathorized = (rx_frame.data.u8[40] & 0x07); + break; + case 0x2AF: // BMS 50ms + actual_battery_voltage = + ((rx_frame.data.u8[1] & 0x3F) << 8) | rx_frame.data.u8[0]; //*0.0625 // Seems to be 0.125 in logging + regen_battery = ((rx_frame.data.u8[5] & 0x7F) << 8) | rx_frame.data.u8[4]; + energy_extracted_from_battery = ((rx_frame.data.u8[7] & 0x7F) << 8) | rx_frame.data.u8[6]; + break; + case 0x578: // BMS 100ms + BMS_578_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_578_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + BMS_Status_DCLS = ((rx_frame.data.u8[1] & 0x30) >> 4); + DC_voltage_DCLS = (rx_frame.data.u8[2] << 6) | (rx_frame.data.u8[1] >> 6); + max_fastcharging_current_amp = ((rx_frame.data.u8[4] & 0x01) << 8) | rx_frame.data.u8[3]; + DC_voltage_chargeport = (rx_frame.data.u8[7] << 4) | (rx_frame.data.u8[6] >> 4); + break; + case 0x5A2: // BMS 500ms normal, 100ms fast + BMS_5A2_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_5A2_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + service_disconnect_switch_missing = (rx_frame.data.u8[1] & 0x20) >> 5; + pilotline_open = (rx_frame.data.u8[1] & 0x10) >> 4; + BMS_status_voltage_free = (rx_frame.data.u8[1] & 0xC0) >> 6; + BMS_OBD_MIL = (rx_frame.data.u8[2] & 0x01); + BMS_error_status = (rx_frame.data.u8[2] & 0x70) >> 4; + BMS_error_lamp_req = (rx_frame.data.u8[4] & 0x04) >> 2; + BMS_warning_lamp_req = (rx_frame.data.u8[4] & 0x08) >> 3; + BMS_Kl30c_Status = (rx_frame.data.u8[4] & 0x30) >> 4; + if (BMS_Kl30c_Status != 0) { // init state + BMS_capacity_ah = ((rx_frame.data.u8[4] & 0x03) << 9) | (rx_frame.data.u8[3] << 1) | (rx_frame.data.u8[2] >> 7); + } + break; + case 0x5CA: // BMS 500ms + BMS_5CA_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_5CA_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + balancing_request = (rx_frame.data.u8[5] & 0x08) >> 3; //True/False + battery_diagnostic = (rx_frame.data.u8[3] & 0x07); + battery_Wh_left = + (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4); //*50 ! Not usable, seems to always contain 0x7F0 + battery_potential_status = + (rx_frame.data.u8[5] & 0x30) >> 4; //0 = function not enabled, 1= no potential, 2 = potential on, 3 = fault + battery_temperature_warning = + (rx_frame.data.u8[7] & 0x0C) >> 2; // 0 = no warning, 1 = temp level 1, 2=temp level 2 + battery_Wh_max = + ((rx_frame.data.u8[5] & 0x07) << 8) | rx_frame.data.u8[4]; //*50 ! Not usable, seems to always contain 0x7F0 + break; + case 0x0CF: //BMS 10ms + BMS_0CF_CRC = rx_frame.data.u8[0]; // Can be used to check CAN signal integrity later on + BMS_0CF_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on + BMS_welded_contactors_status = (rx_frame.data.u8[1] & 0x60) >> 5; + BMS_ext_limits_active = (rx_frame.data.u8[1] & 0x80) >> 7; + BMS_mode = (rx_frame.data.u8[2] & 0x07); + switch (BMS_mode) { + case 1: + case 3: + case 4: + datalayer.system.status.battery_allows_contactor_closing = true; + break; + default: + datalayer.system.status.battery_allows_contactor_closing = false; + } + BMS_HVIL_status = (rx_frame.data.u8[2] & 0x18) >> 3; + BMS_error_shutdown = (rx_frame.data.u8[2] & 0x20) >> 5; + BMS_error_shutdown_request = (rx_frame.data.u8[2] & 0x40) >> 6; + BMS_fault_performance = (rx_frame.data.u8[2] & 0x80) >> 7; + BMS_fault_emergency_shutdown_crash = (rx_frame.data.u8[4] & 0x80) >> 7; + if (BMS_mode != 7) { // Init state, values below are invalid + BMS_current = ((rx_frame.data.u8[4] & 0x7F) << 8) | rx_frame.data.u8[3]; + BMS_voltage_intermediate = (((rx_frame.data.u8[6] & 0x0F) << 8) + (rx_frame.data.u8[5])); + BMS_voltage = ((rx_frame.data.u8[7] << 4) + ((rx_frame.data.u8[6] & 0xF0) >> 4)); + } + break; + case 0x1C42007B: // Reply from battery + if (rx_frame.data.u8[0] == 0x10) { //PID header + transmit_can_frame(&MEB_ACK_FRAME, can_config.battery); + } + if (rx_frame.DLC == 8) { + pid_reply = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; + } else { //12 or 24bit message has reply in other location + pid_reply = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[4]; + } + + switch (pid_reply) { + case PID_SOC: + battery_soc_polled = rx_frame.data.u8[4] * 4; // 135*4 = 54.0% + case PID_VOLTAGE: + battery_voltage_polled = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_CURRENT: // IDLE 0A: 00 08 62 1E 3D (00 02) 49 F0 39 AA AA + battery_current_polled = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); //TODO: right bits? + break; + case PID_MAX_TEMP: + battery_max_temp = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_MIN_TEMP: + battery_min_temp = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_MAX_CHARGE_VOLTAGE: + battery_max_charge_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_MIN_DISCHARGE_VOLTAGE: + battery_min_discharge_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_ALLOWED_CHARGE_POWER: + battery_allowed_charge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_ALLOWED_DISCHARGE_POWER: + battery_allowed_discharge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case PID_CELLVOLTAGE_CELL_1: + cellvoltages_polled[0] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_2: + cellvoltages_polled[1] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_3: + cellvoltages_polled[2] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_4: + cellvoltages_polled[3] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_5: + cellvoltages_polled[4] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_6: + cellvoltages_polled[5] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_7: + cellvoltages_polled[6] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_8: + cellvoltages_polled[7] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_9: + cellvoltages_polled[8] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_10: + cellvoltages_polled[9] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_11: + cellvoltages_polled[10] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_12: + cellvoltages_polled[11] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_13: + cellvoltages_polled[12] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_14: + cellvoltages_polled[13] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_15: + cellvoltages_polled[14] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_16: + cellvoltages_polled[15] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_17: + cellvoltages_polled[16] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_18: + cellvoltages_polled[17] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_19: + cellvoltages_polled[18] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_20: + cellvoltages_polled[19] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_21: + cellvoltages_polled[20] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_22: + cellvoltages_polled[21] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_23: + cellvoltages_polled[22] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_24: + cellvoltages_polled[23] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_25: + cellvoltages_polled[24] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_26: + cellvoltages_polled[25] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_27: + cellvoltages_polled[26] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_28: + cellvoltages_polled[27] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_29: + cellvoltages_polled[28] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_30: + cellvoltages_polled[29] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_31: + cellvoltages_polled[30] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_32: + cellvoltages_polled[31] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_33: + cellvoltages_polled[32] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_34: + cellvoltages_polled[33] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_35: + cellvoltages_polled[34] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_36: + cellvoltages_polled[35] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_37: + cellvoltages_polled[36] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_38: + cellvoltages_polled[37] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_39: + cellvoltages_polled[38] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_40: + cellvoltages_polled[39] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_41: + cellvoltages_polled[40] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_42: + cellvoltages_polled[41] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_43: + cellvoltages_polled[42] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_44: + cellvoltages_polled[43] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_45: + cellvoltages_polled[44] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_46: + cellvoltages_polled[45] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_47: + cellvoltages_polled[46] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_48: + cellvoltages_polled[47] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_49: + cellvoltages_polled[48] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_50: + cellvoltages_polled[49] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_51: + cellvoltages_polled[50] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_52: + cellvoltages_polled[51] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_53: + cellvoltages_polled[52] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_54: + cellvoltages_polled[53] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_55: + cellvoltages_polled[54] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_56: + cellvoltages_polled[55] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_57: + cellvoltages_polled[56] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_58: + cellvoltages_polled[57] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_59: + cellvoltages_polled[58] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_60: + cellvoltages_polled[59] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_61: + cellvoltages_polled[60] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_62: + cellvoltages_polled[61] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_63: + cellvoltages_polled[62] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_64: + cellvoltages_polled[63] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_65: + cellvoltages_polled[64] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_66: + cellvoltages_polled[65] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_67: + cellvoltages_polled[66] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_68: + cellvoltages_polled[67] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_69: + cellvoltages_polled[68] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_70: + cellvoltages_polled[69] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_71: + cellvoltages_polled[70] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_72: + cellvoltages_polled[71] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_73: + cellvoltages_polled[72] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_74: + cellvoltages_polled[73] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_75: + cellvoltages_polled[74] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_76: + cellvoltages_polled[75] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_77: + cellvoltages_polled[76] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_78: + cellvoltages_polled[77] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_79: + cellvoltages_polled[78] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_80: + cellvoltages_polled[79] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_81: + cellvoltages_polled[80] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_82: + cellvoltages_polled[81] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_83: + cellvoltages_polled[82] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_84: + cellvoltages_polled[83] = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) + 1000); + break; + case PID_CELLVOLTAGE_CELL_85: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[84] = (tempval + 1000); + } else { // Cell 85 unavailable. We have a 84S battery (48kWh) + datalayer.battery.info.number_of_cells = 84; + nof_cells_determined = true; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_84S_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_84S_DV; + } + break; + case PID_CELLVOLTAGE_CELL_86: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[85] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_87: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[86] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_88: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[87] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_89: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[88] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_90: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[89] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_91: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[90] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_92: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[91] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_93: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[92] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_94: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[93] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_95: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[94] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_96: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[95] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_97: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[96] = (tempval + 1000); + } else { // Cell 97 unavailable. We have a 96S battery (55kWh) (Unless already specified as 84S) + if (datalayer.battery.info.number_of_cells == 84) { + // Do nothing, we already identified it as 84S + } else { + datalayer.battery.info.number_of_cells = 96; + nof_cells_determined = true; + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV; + } + } + break; + case PID_CELLVOLTAGE_CELL_98: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[97] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_99: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[98] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_100: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[99] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_101: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[100] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_102: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[101] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_103: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[102] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_104: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[103] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_105: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[104] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_106: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[105] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_107: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + if (tempval != 0xFFE) { + cellvoltages_polled[106] = (tempval + 1000); + } + break; + case PID_CELLVOLTAGE_CELL_108: + tempval = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + nof_cells_determined = true; // This is placed outside of the if, to make + // sure we only take the shortcuts to determine the number of cells once. + if (tempval != 0xFFE) { + cellvoltages_polled[107] = (tempval + 1000); + datalayer.battery.info.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; + } + break; + default: + break; + } + break; + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + // Send 10ms CAN Message + if (currentMillis > last_can_msg_timestamp + 500) { + first_can_msg = 0; + } + + if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { + // Check if sending of CAN messages has been delayed too much. + if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms)); + } else { + clear_event(EVENT_CAN_OVERRUN); + } + previousMillis10ms = currentMillis; + + MEB_0FC.data.u8[1] = ((MEB_0FC.data.u8[1] & 0xF0) | counter_10ms); + MEB_0FC.data.u8[0] = vw_crc_calc(MEB_0FC.data.u8, MEB_0FC.DLC, MEB_0FC.ID); + + counter_10ms = (counter_10ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + + transmit_can_frame(&MEB_0FC, can_config.battery); // Required for contactor closing + } + // Send 20ms CAN Message + if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) { + previousMillis20ms = currentMillis; + + MEB_0FD.data.u8[1] = ((MEB_0FD.data.u8[1] & 0xF0) | counter_20ms); + MEB_0FD.data.u8[0] = vw_crc_calc(MEB_0FD.data.u8, MEB_0FD.DLC, MEB_0FD.ID); + + counter_20ms = (counter_20ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + + transmit_can_frame(&MEB_0FD, can_config.battery); // Required for contactor closing + } + // Send 40ms CAN Message + if (currentMillis - previousMillis40ms >= INTERVAL_40_MS) { + previousMillis40ms = currentMillis; + + /* Handle content for 0x040 message */ + /* Airbag message, needed for BMS to function */ + MEB_040.data.u8[7] = counter_040; + MEB_040.data.u8[1] = ((MEB_040.data.u8[1] & 0xF0) | counter_40ms); + MEB_040.data.u8[0] = vw_crc_calc(MEB_040.data.u8, MEB_040.DLC, MEB_040.ID); + counter_40ms = (counter_40ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + if (toggle) { + counter_040 = (counter_040 + 1) % 256; // Increment only on every other pass + } + toggle = !toggle; // Flip the toggle each time the code block is executed + + transmit_can_frame(&MEB_040, can_config.battery); // Airbag message - Needed for contactor closing + } + // Send 50ms CAN Message + if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) { + previousMillis50ms = currentMillis; + + /* Handle content for 0x0C0 message */ + /* BMS needs to see this EM1 message. Content located in frame5&6 especially (can be static?)*/ + /* Also the voltage seen externally to battery is in frame 7&8. At least for the 62kWh ID3 version does not seem to matter, but we send it anyway. */ + MEB_0C0.data.u8[1] = ((MEB_0C0.data.u8[1] & 0xF0) | counter_50ms); + MEB_0C0.data.u8[7] = ((datalayer.battery.status.voltage_dV / 10) * 4) & 0x00FF; + MEB_0C0.data.u8[8] = + ((MEB_0C0.data.u8[8] & 0xF0) | ((((datalayer.battery.status.voltage_dV / 10) * 4) >> 8) & 0x0F)); + MEB_0C0.data.u8[0] = vw_crc_calc(MEB_0C0.data.u8, MEB_0C0.DLC, MEB_0C0.ID); + counter_50ms = (counter_50ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + + transmit_can_frame(&MEB_0C0, can_config.battery); // Needed for contactor closing + } + // Send 100ms CAN Message + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + //HV request and DC/DC control lies in 0x503 + MEB_503.data.u8[3] = 0x00; + if (datalayer.battery.status.bms_status != FAULT && first_can_msg > 0 && currentMillis > first_can_msg + 2000) { + MEB_503.data.u8[1] = 0xB0; + MEB_503.data.u8[3] = BMS_TARGET_HV_ON; //BMS_TARGET_AC_CHARGING; //TODO, should we try AC_2 or DC charging? + 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 + 2000) { //FAULT STATE, open contactors + MEB_503.data.u8[1] = 0x90; + MEB_503.data.u8[3] = BMS_TARGET_HV_OFF; + MEB_503.data.u8[5] = 0x80; // Bordnetz Inactive + MEB_503.data.u8[6] = + 0xE3; // Request emergency shutdown HV system == init (3) (not sure if we dare activate this, this is done with 0xE1) + } + MEB_503.data.u8[1] = ((MEB_503.data.u8[1] & 0xF0) | counter_100ms); + MEB_503.data.u8[0] = vw_crc_calc(MEB_503.data.u8, MEB_503.DLC, MEB_503.ID); + + //Bidirectional charging message + MEB_272.data.u8[1] = + 0x00; //0x80; // Bidirectional charging active (Set to 0x00 incase no bidirectional charging wanted) + MEB_272.data.u8[2] = 0x00; + //0x01; // High load bidirectional charging active (Set to 0x00 incase no bidirectional charging wanted) + MEB_272.data.u8[5] = DC_FASTCHARGE_NO_START_REQUEST; //DC_FASTCHARGE_VEHICLE; //DC charging + + //Klemmen status + MEB_3C0.data.u8[2] = 0x00; //0x02; //bit to signal that KL_15 is ON // Always 0 in start4.log + MEB_3C0.data.u8[1] = ((MEB_3C0.data.u8[1] & 0xF0) | counter_100ms); + MEB_3C0.data.u8[0] = vw_crc_calc(MEB_3C0.data.u8, MEB_3C0.DLC, MEB_3C0.ID); + + MEB_3BE.data.u8[1] = ((MEB_3BE.data.u8[1] & 0xF0) | counter_100ms); + MEB_3BE.data.u8[0] = vw_crc_calc(MEB_3BE.data.u8, MEB_3BE.DLC, MEB_3BE.ID); + + MEB_14C.data.u8[1] = ((MEB_14C.data.u8[1] & 0xF0) | counter_100ms); + MEB_14C.data.u8[0] = vw_crc_calc(MEB_14C.data.u8, MEB_14C.DLC, MEB_14C.ID); + + counter_100ms = (counter_100ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + transmit_can_frame(&MEB_503, can_config.battery); + transmit_can_frame(&MEB_272, can_config.battery); + transmit_can_frame(&MEB_3C0, can_config.battery); + transmit_can_frame(&MEB_3BE, can_config.battery); + transmit_can_frame(&MEB_14C, can_config.battery); + } + //Send 200ms message + if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) { + previousMillis200ms = currentMillis; + + //TODO: 153 does not seem to need CRC even though it has it? Empty in some logs and still works + + //TODO: MEB_1B0000B9 & MEB_1B000010 & MEB_1B000046 has CAN sleep commands, static OK? + + transmit_can_frame(&MEB_5E1, can_config.battery); + transmit_can_frame(&MEB_153, can_config.battery); + transmit_can_frame(&MEB_1B0000B9, can_config.battery); + transmit_can_frame(&MEB_1B000010, can_config.battery); + transmit_can_frame(&MEB_1B000046, can_config.battery); + + switch (poll_pid) { + case PID_SOC: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_SOC >> 8); // High byte + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_SOC; // Low byte + poll_pid = PID_VOLTAGE; + break; + case PID_VOLTAGE: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_VOLTAGE >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_VOLTAGE; + poll_pid = PID_CURRENT; + break; + case PID_CURRENT: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_CURRENT >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CURRENT; + poll_pid = PID_MAX_TEMP; + break; + case PID_MAX_TEMP: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MAX_TEMP >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MAX_TEMP; + poll_pid = PID_MIN_TEMP; + break; + case PID_MIN_TEMP: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MIN_TEMP >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MIN_TEMP; + poll_pid = PID_MAX_CHARGE_VOLTAGE; + break; + case PID_MAX_CHARGE_VOLTAGE: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MAX_CHARGE_VOLTAGE >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MAX_CHARGE_VOLTAGE; + poll_pid = PID_MIN_DISCHARGE_VOLTAGE; + break; + case PID_MIN_DISCHARGE_VOLTAGE: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MIN_DISCHARGE_VOLTAGE >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MIN_DISCHARGE_VOLTAGE; + poll_pid = PID_ALLOWED_CHARGE_POWER; + break; + case PID_ALLOWED_CHARGE_POWER: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_ALLOWED_CHARGE_POWER >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_ALLOWED_CHARGE_POWER; + poll_pid = PID_ALLOWED_DISCHARGE_POWER; + break; + case PID_ALLOWED_DISCHARGE_POWER: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_ALLOWED_DISCHARGE_POWER >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_ALLOWED_DISCHARGE_POWER; + poll_pid = PID_CELLVOLTAGE_CELL_1; // Start polling cell voltages + break; + // Cell Voltage Cases + case PID_CELLVOLTAGE_CELL_1: + MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_CELLVOLTAGE_CELL_1 >> 8); + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_1; + poll_pid = PID_CELLVOLTAGE_CELL_2; + break; + case PID_CELLVOLTAGE_CELL_2: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_2; + poll_pid = PID_CELLVOLTAGE_CELL_3; + break; + case PID_CELLVOLTAGE_CELL_3: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_3; + poll_pid = PID_CELLVOLTAGE_CELL_4; + break; + case PID_CELLVOLTAGE_CELL_4: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_4; + poll_pid = PID_CELLVOLTAGE_CELL_5; + break; + case PID_CELLVOLTAGE_CELL_5: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_5; + poll_pid = PID_CELLVOLTAGE_CELL_6; + break; + case PID_CELLVOLTAGE_CELL_6: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_6; + poll_pid = PID_CELLVOLTAGE_CELL_7; + break; + case PID_CELLVOLTAGE_CELL_7: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_7; + poll_pid = PID_CELLVOLTAGE_CELL_8; + break; + case PID_CELLVOLTAGE_CELL_8: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_8; + poll_pid = PID_CELLVOLTAGE_CELL_9; + break; + case PID_CELLVOLTAGE_CELL_9: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_9; + poll_pid = PID_CELLVOLTAGE_CELL_10; + break; + case PID_CELLVOLTAGE_CELL_10: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_10; + poll_pid = PID_CELLVOLTAGE_CELL_11; + break; + case PID_CELLVOLTAGE_CELL_11: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_11; + poll_pid = PID_CELLVOLTAGE_CELL_12; + break; + case PID_CELLVOLTAGE_CELL_12: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_12; + poll_pid = PID_CELLVOLTAGE_CELL_13; + break; + case PID_CELLVOLTAGE_CELL_13: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_13; + poll_pid = PID_CELLVOLTAGE_CELL_14; + break; + case PID_CELLVOLTAGE_CELL_14: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_14; + poll_pid = PID_CELLVOLTAGE_CELL_15; + break; + case PID_CELLVOLTAGE_CELL_15: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_15; + poll_pid = PID_CELLVOLTAGE_CELL_16; + break; + case PID_CELLVOLTAGE_CELL_16: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_16; + poll_pid = PID_CELLVOLTAGE_CELL_17; + break; + case PID_CELLVOLTAGE_CELL_17: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_17; + poll_pid = PID_CELLVOLTAGE_CELL_18; + break; + case PID_CELLVOLTAGE_CELL_18: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_18; + poll_pid = PID_CELLVOLTAGE_CELL_19; + break; + case PID_CELLVOLTAGE_CELL_19: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_19; + poll_pid = PID_CELLVOLTAGE_CELL_20; + break; + case PID_CELLVOLTAGE_CELL_20: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_20; + poll_pid = PID_CELLVOLTAGE_CELL_21; + break; + case PID_CELLVOLTAGE_CELL_21: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_21; + poll_pid = PID_CELLVOLTAGE_CELL_22; + break; + case PID_CELLVOLTAGE_CELL_22: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_22; + poll_pid = PID_CELLVOLTAGE_CELL_23; + break; + case PID_CELLVOLTAGE_CELL_23: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_23; + poll_pid = PID_CELLVOLTAGE_CELL_24; + break; + case PID_CELLVOLTAGE_CELL_24: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_24; + poll_pid = PID_CELLVOLTAGE_CELL_25; + break; + case PID_CELLVOLTAGE_CELL_25: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_25; + poll_pid = PID_CELLVOLTAGE_CELL_26; + break; + case PID_CELLVOLTAGE_CELL_26: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_26; + poll_pid = PID_CELLVOLTAGE_CELL_27; + break; + case PID_CELLVOLTAGE_CELL_27: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_27; + poll_pid = PID_CELLVOLTAGE_CELL_28; + break; + case PID_CELLVOLTAGE_CELL_28: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_28; + poll_pid = PID_CELLVOLTAGE_CELL_29; + break; + case PID_CELLVOLTAGE_CELL_29: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_29; + poll_pid = PID_CELLVOLTAGE_CELL_30; + break; + case PID_CELLVOLTAGE_CELL_30: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_30; + poll_pid = PID_CELLVOLTAGE_CELL_31; + break; + case PID_CELLVOLTAGE_CELL_31: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_31; + poll_pid = PID_CELLVOLTAGE_CELL_32; + break; + case PID_CELLVOLTAGE_CELL_32: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_32; + poll_pid = PID_CELLVOLTAGE_CELL_33; + break; + case PID_CELLVOLTAGE_CELL_33: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_33; + poll_pid = PID_CELLVOLTAGE_CELL_34; + break; + case PID_CELLVOLTAGE_CELL_34: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_34; + poll_pid = PID_CELLVOLTAGE_CELL_35; + break; + case PID_CELLVOLTAGE_CELL_35: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_35; + poll_pid = PID_CELLVOLTAGE_CELL_36; + break; + case PID_CELLVOLTAGE_CELL_36: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_36; + poll_pid = PID_CELLVOLTAGE_CELL_37; + break; + case PID_CELLVOLTAGE_CELL_37: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_37; + poll_pid = PID_CELLVOLTAGE_CELL_38; + break; + case PID_CELLVOLTAGE_CELL_38: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_38; + poll_pid = PID_CELLVOLTAGE_CELL_39; + break; + case PID_CELLVOLTAGE_CELL_39: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_39; + poll_pid = PID_CELLVOLTAGE_CELL_40; + break; + case PID_CELLVOLTAGE_CELL_40: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_40; + poll_pid = PID_CELLVOLTAGE_CELL_41; + break; + case PID_CELLVOLTAGE_CELL_41: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_41; + poll_pid = PID_CELLVOLTAGE_CELL_42; + break; + case PID_CELLVOLTAGE_CELL_42: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_42; + poll_pid = PID_CELLVOLTAGE_CELL_43; + break; + case PID_CELLVOLTAGE_CELL_43: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_43; + poll_pid = PID_CELLVOLTAGE_CELL_44; + break; + case PID_CELLVOLTAGE_CELL_44: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_44; + poll_pid = PID_CELLVOLTAGE_CELL_45; + break; + case PID_CELLVOLTAGE_CELL_45: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_45; + poll_pid = PID_CELLVOLTAGE_CELL_46; + break; + case PID_CELLVOLTAGE_CELL_46: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_46; + poll_pid = PID_CELLVOLTAGE_CELL_47; + break; + case PID_CELLVOLTAGE_CELL_47: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_47; + poll_pid = PID_CELLVOLTAGE_CELL_48; + break; + case PID_CELLVOLTAGE_CELL_48: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_48; + poll_pid = PID_CELLVOLTAGE_CELL_49; + break; + case PID_CELLVOLTAGE_CELL_49: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_49; + poll_pid = PID_CELLVOLTAGE_CELL_50; + break; + case PID_CELLVOLTAGE_CELL_50: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_50; + poll_pid = PID_CELLVOLTAGE_CELL_51; + break; + case PID_CELLVOLTAGE_CELL_51: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_51; + poll_pid = PID_CELLVOLTAGE_CELL_52; + break; + case PID_CELLVOLTAGE_CELL_52: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_52; + poll_pid = PID_CELLVOLTAGE_CELL_53; + break; + case PID_CELLVOLTAGE_CELL_53: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_53; + poll_pid = PID_CELLVOLTAGE_CELL_54; + break; + case PID_CELLVOLTAGE_CELL_54: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_54; + poll_pid = PID_CELLVOLTAGE_CELL_55; + break; + case PID_CELLVOLTAGE_CELL_55: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_55; + poll_pid = PID_CELLVOLTAGE_CELL_56; + break; + case PID_CELLVOLTAGE_CELL_56: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_56; + poll_pid = PID_CELLVOLTAGE_CELL_57; + break; + case PID_CELLVOLTAGE_CELL_57: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_57; + poll_pid = PID_CELLVOLTAGE_CELL_58; + break; + case PID_CELLVOLTAGE_CELL_58: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_58; + poll_pid = PID_CELLVOLTAGE_CELL_59; + break; + case PID_CELLVOLTAGE_CELL_59: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_59; + poll_pid = PID_CELLVOLTAGE_CELL_60; + break; + case PID_CELLVOLTAGE_CELL_60: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_60; + poll_pid = PID_CELLVOLTAGE_CELL_61; + break; + case PID_CELLVOLTAGE_CELL_61: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_61; + poll_pid = PID_CELLVOLTAGE_CELL_62; + break; + case PID_CELLVOLTAGE_CELL_62: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_62; + poll_pid = PID_CELLVOLTAGE_CELL_63; + break; + case PID_CELLVOLTAGE_CELL_63: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_63; + poll_pid = PID_CELLVOLTAGE_CELL_64; + break; + case PID_CELLVOLTAGE_CELL_64: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_64; + poll_pid = PID_CELLVOLTAGE_CELL_65; + break; + case PID_CELLVOLTAGE_CELL_65: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_65; + poll_pid = PID_CELLVOLTAGE_CELL_66; + break; + case PID_CELLVOLTAGE_CELL_66: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_66; + poll_pid = PID_CELLVOLTAGE_CELL_67; + break; + case PID_CELLVOLTAGE_CELL_67: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_67; + poll_pid = PID_CELLVOLTAGE_CELL_68; + break; + case PID_CELLVOLTAGE_CELL_68: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_68; + poll_pid = PID_CELLVOLTAGE_CELL_69; + break; + case PID_CELLVOLTAGE_CELL_69: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_69; + poll_pid = PID_CELLVOLTAGE_CELL_70; + break; + case PID_CELLVOLTAGE_CELL_70: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_70; + poll_pid = PID_CELLVOLTAGE_CELL_71; + break; + case PID_CELLVOLTAGE_CELL_71: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_71; + poll_pid = PID_CELLVOLTAGE_CELL_72; + break; + case PID_CELLVOLTAGE_CELL_72: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_72; + poll_pid = PID_CELLVOLTAGE_CELL_73; + break; + case PID_CELLVOLTAGE_CELL_73: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_73; + poll_pid = PID_CELLVOLTAGE_CELL_74; + break; + case PID_CELLVOLTAGE_CELL_74: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_74; + poll_pid = PID_CELLVOLTAGE_CELL_75; + break; + case PID_CELLVOLTAGE_CELL_75: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_75; + poll_pid = PID_CELLVOLTAGE_CELL_76; + break; + case PID_CELLVOLTAGE_CELL_76: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_76; + poll_pid = PID_CELLVOLTAGE_CELL_77; + break; + case PID_CELLVOLTAGE_CELL_77: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_77; + poll_pid = PID_CELLVOLTAGE_CELL_78; + break; + case PID_CELLVOLTAGE_CELL_78: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_78; + poll_pid = PID_CELLVOLTAGE_CELL_79; + break; + case PID_CELLVOLTAGE_CELL_79: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_79; + poll_pid = PID_CELLVOLTAGE_CELL_80; + break; + case PID_CELLVOLTAGE_CELL_80: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_80; + poll_pid = PID_CELLVOLTAGE_CELL_81; + break; + case PID_CELLVOLTAGE_CELL_81: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_81; + poll_pid = PID_CELLVOLTAGE_CELL_82; + break; + case PID_CELLVOLTAGE_CELL_82: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_82; + poll_pid = PID_CELLVOLTAGE_CELL_83; + break; + case PID_CELLVOLTAGE_CELL_83: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_83; + poll_pid = PID_CELLVOLTAGE_CELL_84; + break; + case PID_CELLVOLTAGE_CELL_84: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_84; + if (datalayer.battery.info.number_of_cells > 84) { + if (nof_cells_determined) { + poll_pid = PID_CELLVOLTAGE_CELL_85; + } else { + poll_pid = PID_CELLVOLTAGE_CELL_97; + } + } else { + poll_pid = PID_SOC; + } + break; + case PID_CELLVOLTAGE_CELL_85: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_85; + poll_pid = PID_CELLVOLTAGE_CELL_86; + break; + case PID_CELLVOLTAGE_CELL_86: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_86; + poll_pid = PID_CELLVOLTAGE_CELL_87; + break; + case PID_CELLVOLTAGE_CELL_87: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_87; + poll_pid = PID_CELLVOLTAGE_CELL_88; + break; + case PID_CELLVOLTAGE_CELL_88: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_88; + poll_pid = PID_CELLVOLTAGE_CELL_89; + break; + case PID_CELLVOLTAGE_CELL_89: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_89; + poll_pid = PID_CELLVOLTAGE_CELL_90; + break; + case PID_CELLVOLTAGE_CELL_90: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_90; + poll_pid = PID_CELLVOLTAGE_CELL_91; + break; + case PID_CELLVOLTAGE_CELL_91: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_91; + poll_pid = PID_CELLVOLTAGE_CELL_92; + break; + case PID_CELLVOLTAGE_CELL_92: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_92; + poll_pid = PID_CELLVOLTAGE_CELL_93; + break; + case PID_CELLVOLTAGE_CELL_93: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_93; + poll_pid = PID_CELLVOLTAGE_CELL_94; + break; + case PID_CELLVOLTAGE_CELL_94: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_94; + poll_pid = PID_CELLVOLTAGE_CELL_95; + break; + case PID_CELLVOLTAGE_CELL_95: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_95; + poll_pid = PID_CELLVOLTAGE_CELL_96; + break; + case PID_CELLVOLTAGE_CELL_96: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_96; + if (datalayer.battery.info.number_of_cells > 96) + poll_pid = PID_CELLVOLTAGE_CELL_97; + else + poll_pid = PID_SOC; + break; + case PID_CELLVOLTAGE_CELL_97: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_97; + poll_pid = PID_CELLVOLTAGE_CELL_98; + break; + case PID_CELLVOLTAGE_CELL_98: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_98; + poll_pid = PID_CELLVOLTAGE_CELL_99; + break; + case PID_CELLVOLTAGE_CELL_99: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_99; + poll_pid = PID_CELLVOLTAGE_CELL_100; + break; + case PID_CELLVOLTAGE_CELL_100: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_100; + poll_pid = PID_CELLVOLTAGE_CELL_101; + break; + case PID_CELLVOLTAGE_CELL_101: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_101; + poll_pid = PID_CELLVOLTAGE_CELL_102; + break; + case PID_CELLVOLTAGE_CELL_102: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_102; + poll_pid = PID_CELLVOLTAGE_CELL_103; + break; + case PID_CELLVOLTAGE_CELL_103: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_103; + poll_pid = PID_CELLVOLTAGE_CELL_104; + break; + case PID_CELLVOLTAGE_CELL_104: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_104; + poll_pid = PID_CELLVOLTAGE_CELL_105; + break; + case PID_CELLVOLTAGE_CELL_105: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_105; + poll_pid = PID_CELLVOLTAGE_CELL_106; + break; + case PID_CELLVOLTAGE_CELL_106: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_106; + poll_pid = PID_CELLVOLTAGE_CELL_107; + break; + case PID_CELLVOLTAGE_CELL_107: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_107; + poll_pid = PID_CELLVOLTAGE_CELL_108; + break; + case PID_CELLVOLTAGE_CELL_108: + MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_CELLVOLTAGE_CELL_108; + poll_pid = PID_SOC; + break; + default: + poll_pid = PID_SOC; + break; + } + if (first_can_msg > 0 && currentMillis > first_can_msg + 2000) { + transmit_can_frame(&MEB_POLLING_FRAME, can_config.battery); + } + } + + // Send 500ms CAN Message + if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { + previousMillis500ms = currentMillis; + + transmit_can_frame(&MEB_16A954B4, can_config.battery); //eTM, Cooling valves and pumps for BMS + transmit_can_frame(&MEB_569, can_config.battery); // Battery heating requests + transmit_can_frame(&MEB_1A55552B, can_config.battery); //Climate, heatpump and priorities + transmit_can_frame(&MEB_1A555548, can_config.battery); //ORU, OTA update message for reserving battery + transmit_can_frame(&MEB_16A954FB, can_config.battery); //Climate, request to BMS for starting preconditioning + } + + //Send 1s CANFD message + if (currentMillis - previousMillis1s >= INTERVAL_1_S) { + previousMillis1s = currentMillis; + + MEB_641.data.u8[1] = ((MEB_641.data.u8[1] & 0xF0) | counter_1000ms); + MEB_641.data.u8[0] = vw_crc_calc(MEB_641.data.u8, MEB_641.DLC, MEB_641.ID); + + MEB_1A5555A6.data.u8[2] = 0x7F; //Outside temperature, factor 0.5, offset -50 + + MEB_6B2.data.u8[0] = //driving cycle counter, 0-254 wrap around. 255 = invalid value + //MEB_6B2.data.u8[1-2-3b0-4] // Odometer, km (20 bits long) + MEB_6B2.data.u8[3] = (uint8_t)((TIME_YEAR - 2000) << 4) | MEB_6B2.data.u8[3]; + MEB_6B2.data.u8[4] = (uint8_t)((TIME_DAY & 0x01) << 7 | TIME_MONTH << 3 | (TIME_YEAR - 2000) >> 4); + MEB_6B2.data.u8[5] = (uint8_t)((TIME_HOUR & 0x0F) << 4 | TIME_DAY >> 1); + MEB_6B2.data.u8[6] = (uint8_t)((seconds & 0x01) << 7 | TIME_MINUTE << 1 | TIME_HOUR >> 4); + MEB_6B2.data.u8[7] = (uint8_t)((seconds & 0x3E) >> 1); + seconds = (seconds + 1) % 60; + + counter_1000ms = (counter_1000ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3.. + transmit_can_frame(&MEB_6B2, can_config.battery); // Diagnostics - Needed for contactor closing + transmit_can_frame(&MEB_641, can_config.battery); // Motor - OBD + transmit_can_frame(&MEB_5F5, can_config.battery); // Loading profile + transmit_can_frame(&MEB_585, can_config.battery); // Systeminfo + transmit_can_frame(&MEB_1A5555A6, can_config.battery); // Temperature QBit + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "Volkswagen Group MEB platform via CAN-FD", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.battery.info.number_of_cells = 108; //Startup in 108S mode. We figure out the actual count later. + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV; //Defined later to correct pack size + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_84S_DV; //Defined later to correct pack size + 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; +} + +#endif diff --git a/Software/src/battery/MEB-BATTERY.h b/Software/src/battery/MEB-BATTERY.h new file mode 100644 index 000000000..00be49b50 --- /dev/null +++ b/Software/src/battery/MEB-BATTERY.h @@ -0,0 +1,138 @@ +#ifndef MEB_BATTERY_H +#define MEB_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED +#define MAX_PACK_VOLTAGE_84S_DV 3528 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_84S_DV 2520 +#define MAX_PACK_VOLTAGE_96S_DV 4032 +#define MIN_PACK_VOLTAGE_96S_DV 2880 +#define MAX_PACK_VOLTAGE_108S_DV 4536 +#define MIN_PACK_VOLTAGE_108S_DV 3240 +#define MAX_CELL_DEVIATION_MV 150 +#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +#define PID_SOC 0x028C +#define PID_VOLTAGE 0x1E3B +#define PID_CURRENT 0x1E3D +#define PID_MAX_TEMP 0x1E0E +#define PID_MIN_TEMP 0x1E0F +#define PID_MAX_CHARGE_VOLTAGE 0x5171 +#define PID_MIN_DISCHARGE_VOLTAGE 0x5170 +#define PID_ALLOWED_CHARGE_POWER 0x1E1B +#define PID_ALLOWED_DISCHARGE_POWER 0x1E1C +#define PID_CELLVOLTAGE_CELL_1 0x1E40 +#define PID_CELLVOLTAGE_CELL_2 0x1E41 +#define PID_CELLVOLTAGE_CELL_3 0x1E42 +#define PID_CELLVOLTAGE_CELL_4 0x1E43 +#define PID_CELLVOLTAGE_CELL_5 0x1E44 +#define PID_CELLVOLTAGE_CELL_6 0x1E45 +#define PID_CELLVOLTAGE_CELL_7 0x1E46 +#define PID_CELLVOLTAGE_CELL_8 0x1E47 +#define PID_CELLVOLTAGE_CELL_9 0x1E48 +#define PID_CELLVOLTAGE_CELL_10 0x1E49 +#define PID_CELLVOLTAGE_CELL_11 0x1E4A +#define PID_CELLVOLTAGE_CELL_12 0x1E4B +#define PID_CELLVOLTAGE_CELL_13 0x1E4C +#define PID_CELLVOLTAGE_CELL_14 0x1E4D +#define PID_CELLVOLTAGE_CELL_15 0x1E4E +#define PID_CELLVOLTAGE_CELL_16 0x1E4F +#define PID_CELLVOLTAGE_CELL_17 0x1E50 +#define PID_CELLVOLTAGE_CELL_18 0x1E51 +#define PID_CELLVOLTAGE_CELL_19 0x1E52 +#define PID_CELLVOLTAGE_CELL_20 0x1E53 +#define PID_CELLVOLTAGE_CELL_21 0x1E54 +#define PID_CELLVOLTAGE_CELL_22 0x1E55 +#define PID_CELLVOLTAGE_CELL_23 0x1E56 +#define PID_CELLVOLTAGE_CELL_24 0x1E57 +#define PID_CELLVOLTAGE_CELL_25 0x1E58 +#define PID_CELLVOLTAGE_CELL_26 0x1E59 +#define PID_CELLVOLTAGE_CELL_27 0x1E5A +#define PID_CELLVOLTAGE_CELL_28 0x1E5B +#define PID_CELLVOLTAGE_CELL_29 0x1E5C +#define PID_CELLVOLTAGE_CELL_30 0x1E5D +#define PID_CELLVOLTAGE_CELL_31 0x1E5E +#define PID_CELLVOLTAGE_CELL_32 0x1E5F +#define PID_CELLVOLTAGE_CELL_33 0x1E60 +#define PID_CELLVOLTAGE_CELL_34 0x1E61 +#define PID_CELLVOLTAGE_CELL_35 0x1E62 +#define PID_CELLVOLTAGE_CELL_36 0x1E63 +#define PID_CELLVOLTAGE_CELL_37 0x1E64 +#define PID_CELLVOLTAGE_CELL_38 0x1E65 +#define PID_CELLVOLTAGE_CELL_39 0x1E66 +#define PID_CELLVOLTAGE_CELL_40 0x1E67 +#define PID_CELLVOLTAGE_CELL_41 0x1E68 +#define PID_CELLVOLTAGE_CELL_42 0x1E69 +#define PID_CELLVOLTAGE_CELL_43 0x1E6A +#define PID_CELLVOLTAGE_CELL_44 0x1E6B +#define PID_CELLVOLTAGE_CELL_45 0x1E6C +#define PID_CELLVOLTAGE_CELL_46 0x1E6D +#define PID_CELLVOLTAGE_CELL_47 0x1E6E +#define PID_CELLVOLTAGE_CELL_48 0x1E6F +#define PID_CELLVOLTAGE_CELL_49 0x1E70 +#define PID_CELLVOLTAGE_CELL_50 0x1E71 +#define PID_CELLVOLTAGE_CELL_51 0x1E72 +#define PID_CELLVOLTAGE_CELL_52 0x1E73 +#define PID_CELLVOLTAGE_CELL_53 0x1E74 +#define PID_CELLVOLTAGE_CELL_54 0x1E75 +#define PID_CELLVOLTAGE_CELL_55 0x1E76 +#define PID_CELLVOLTAGE_CELL_56 0x1E77 +#define PID_CELLVOLTAGE_CELL_57 0x1E78 +#define PID_CELLVOLTAGE_CELL_58 0x1E79 +#define PID_CELLVOLTAGE_CELL_59 0x1E7A +#define PID_CELLVOLTAGE_CELL_60 0x1E7B +#define PID_CELLVOLTAGE_CELL_61 0x1E7C +#define PID_CELLVOLTAGE_CELL_62 0x1E7D +#define PID_CELLVOLTAGE_CELL_63 0x1E7E +#define PID_CELLVOLTAGE_CELL_64 0x1E7F +#define PID_CELLVOLTAGE_CELL_65 0x1E80 +#define PID_CELLVOLTAGE_CELL_66 0x1E81 +#define PID_CELLVOLTAGE_CELL_67 0x1E82 +#define PID_CELLVOLTAGE_CELL_68 0x1E83 +#define PID_CELLVOLTAGE_CELL_69 0x1E84 +#define PID_CELLVOLTAGE_CELL_70 0x1E85 +#define PID_CELLVOLTAGE_CELL_71 0x1E86 +#define PID_CELLVOLTAGE_CELL_72 0x1E87 +#define PID_CELLVOLTAGE_CELL_73 0x1E88 +#define PID_CELLVOLTAGE_CELL_74 0x1E89 +#define PID_CELLVOLTAGE_CELL_75 0x1E8A +#define PID_CELLVOLTAGE_CELL_76 0x1E8B +#define PID_CELLVOLTAGE_CELL_77 0x1E8C +#define PID_CELLVOLTAGE_CELL_78 0x1E8D +#define PID_CELLVOLTAGE_CELL_79 0x1E8E +#define PID_CELLVOLTAGE_CELL_80 0x1E8F +#define PID_CELLVOLTAGE_CELL_81 0x1E90 +#define PID_CELLVOLTAGE_CELL_82 0x1E91 +#define PID_CELLVOLTAGE_CELL_83 0x1E92 +#define PID_CELLVOLTAGE_CELL_84 0x1E93 +#define PID_CELLVOLTAGE_CELL_85 0x1E94 +#define PID_CELLVOLTAGE_CELL_86 0x1E95 +#define PID_CELLVOLTAGE_CELL_87 0x1E96 +#define PID_CELLVOLTAGE_CELL_88 0x1E97 +#define PID_CELLVOLTAGE_CELL_89 0x1E98 +#define PID_CELLVOLTAGE_CELL_90 0x1E99 +#define PID_CELLVOLTAGE_CELL_91 0x1E9A +#define PID_CELLVOLTAGE_CELL_92 0x1E9B +#define PID_CELLVOLTAGE_CELL_93 0x1E9C +#define PID_CELLVOLTAGE_CELL_94 0x1E9D +#define PID_CELLVOLTAGE_CELL_95 0x1E9E +#define PID_CELLVOLTAGE_CELL_96 0x1E9F +#define PID_CELLVOLTAGE_CELL_97 0x1EA0 +#define PID_CELLVOLTAGE_CELL_98 0x1EA1 +#define PID_CELLVOLTAGE_CELL_99 0x1EA2 +#define PID_CELLVOLTAGE_CELL_100 0x1EA3 +#define PID_CELLVOLTAGE_CELL_101 0x1EA4 +#define PID_CELLVOLTAGE_CELL_102 0x1EA5 +#define PID_CELLVOLTAGE_CELL_103 0x1EA6 +#define PID_CELLVOLTAGE_CELL_104 0x1EA7 +#define PID_CELLVOLTAGE_CELL_105 0x1EA8 +#define PID_CELLVOLTAGE_CELL_106 0x1EA9 +#define PID_CELLVOLTAGE_CELL_107 0x1EAA +#define PID_CELLVOLTAGE_CELL_108 0x1EAB + +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/battery/MG-5-BATTERY.cpp b/Software/src/battery/MG-5-BATTERY.cpp index 35c8ee965..07c54a7d9 100644 --- a/Software/src/battery/MG-5-BATTERY.cpp +++ b/Software/src/battery/MG-5-BATTERY.cpp @@ -42,13 +42,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.temperature_min_dC; datalayer.battery.status.temperature_max_dC; - -#ifdef DEBUG_VIA_USB - -#endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(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 @@ -112,7 +108,7 @@ void receive_can_battery(CAN_frame rx_frame) { break; } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); //Send 10ms message if (currentMillis - previousMillis10 >= INTERVAL_10_MS) { @@ -124,13 +120,13 @@ void send_can_battery() { } previousMillis10 = currentMillis; - transmit_can(&MG_5_100, can_config.battery); + transmit_can_frame(&MG_5_100, can_config.battery); } // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; - //transmit_can(&MG_5_100, can_config.battery); + //transmit_can_frame(&MG_5_100, can_config.battery); } } diff --git a/Software/src/battery/MG-5-BATTERY.h b/Software/src/battery/MG-5-BATTERY.h index 4efd37356..415ed0dcb 100644 --- a/Software/src/battery/MG-5-BATTERY.h +++ b/Software/src/battery/MG-5-BATTERY.h @@ -11,6 +11,6 @@ #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 21267422c..8fa7df101 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -38,6 +38,8 @@ CAN_frame LEAF_1D4 = {.FD = false, .ID = 0x1D4, .data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}}; // Active polling messages +uint8_t PIDgroups[] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90}; +uint8_t PIDindex = 0; CAN_frame LEAF_GROUP_REQUEST = {.FD = false, .ext_ID = false, .DLC = 8, @@ -107,10 +109,9 @@ static bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a // Nissan LEAF battery data from polled CAN messages static uint8_t battery_request_idx = 0; static uint8_t group_7bb = 0; -static uint8_t group = 1; static bool stop_battery_query = true; -static uint8_t hold_off_with_polling_10seconds = 10; -static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages +static uint8_t hold_off_with_polling_10seconds = 2; //Paused for 20 seconds on startup +static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages static uint8_t battery_cellcounter = 0; static uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV static uint16_t battery_HX = 0; //Internal resistance @@ -124,7 +125,9 @@ static uint16_t battery_temp_raw_max = 0; static uint16_t battery_temp_raw_min = 0; static int16_t battery_temp_polled_max = 0; static int16_t battery_temp_polled_min = 0; - +static uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars +static uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars +static uint8_t BMSIDcode[8] = {0}; #ifdef DOUBLE_BATTERY static uint8_t LEAF_battery2_Type = ZE0_BATTERY; static bool battery2_can_alive = false; @@ -326,11 +329,15 @@ void update_values_battery() { /* This function maps all the values fetched via } // Update webserver datalayer + memcpy(datalayer_extended.nissanleaf.BatterySerialNumber, BatterySerialNumber, sizeof(BatterySerialNumber)); + memcpy(datalayer_extended.nissanleaf.BatteryPartNumber, BatteryPartNumber, sizeof(BatteryPartNumber)); + memcpy(datalayer_extended.nissanleaf.BMSIDcode, BMSIDcode, sizeof(BMSIDcode)); datalayer_extended.nissanleaf.LEAF_gen = LEAF_battery_Type; datalayer_extended.nissanleaf.GIDS = battery_GIDS; datalayer_extended.nissanleaf.ChargePowerLimit = battery_Charge_Power_Limit; datalayer_extended.nissanleaf.MaxPowerForCharger = battery_MAX_POWER_FOR_CHARGER; datalayer_extended.nissanleaf.Interlock = battery_Interlock; + datalayer_extended.nissanleaf.Insulation = battery_insulation; datalayer_extended.nissanleaf.RelayCutRequest = battery_Relay_Cut_Request; datalayer_extended.nissanleaf.FailsafeStatus = battery_Failsafe_Status; datalayer_extended.nissanleaf.Full = battery_Full_CHARGE_flag; @@ -495,7 +502,7 @@ void update_values_battery2() { // Handle the values coming in from battery #2 } } } -void receive_can_battery2(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x1DB: if (is_message_corrupt(rx_frame)) { @@ -597,22 +604,19 @@ void receive_can_battery2(CAN_frame rx_frame) { hold_off_with_polling_10seconds = 10; //Polling is paused for 100s break; case 0x7BB: - //First check which group data we are getting - if (rx_frame.data.u8[0] == 0x10) { //First message of a group - battery2_group_7bb = rx_frame.data.u8[3]; - if (battery2_group_7bb != 1 && battery2_group_7bb != 2 && - battery2_group_7bb != 4) { //We are only interested in groups 1,2 and 4 - break; - } - } if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling break; } - transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double); + //First check which group data we are getting + if (rx_frame.data.u8[0] == 0x10) { //First message of a group + battery2_group_7bb = rx_frame.data.u8[3]; + } + + transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double); - if (battery2_group_7bb == 1) //High precision SOC, Current, voltages etc. + if (battery2_group_7bb == 0x01) //High precision SOC, Current, voltages etc. { if (rx_frame.data.u8[0] == 0x10) { //First frame //High precision battery2_current_1 resides here, but has been deemed unusable by 62kWh owners @@ -630,7 +634,7 @@ void receive_can_battery2(CAN_frame rx_frame) { } } - if (battery2_group_7bb == 2) //Cell Voltages + if (battery2_group_7bb == 0x02) //Cell Voltages { if (rx_frame.data.u8[0] == 0x10) { //first frame is anomalous battery2_request_idx = 0; @@ -673,7 +677,7 @@ void receive_can_battery2(CAN_frame rx_frame) { } } - if (battery2_group_7bb == 4) { //Temperatures + if (battery2_group_7bb == 0x04) { //Temperatures if (rx_frame.data.u8[0] == 0x10) { //First message battery2_temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery2_temp_raw_2_highnibble = rx_frame.data.u8[7]; @@ -737,7 +741,7 @@ void receive_can_battery2(CAN_frame rx_frame) { } #endif // DOUBLE_BATTERY -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x1DB: if (is_message_corrupt(rx_frame)) { @@ -844,7 +848,7 @@ void receive_can_battery(CAN_frame rx_frame) { break; case 0x7BB: - // This section checks if we are doing a SOH reset towards BMS + // 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 if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data! @@ -859,20 +863,18 @@ void receive_can_battery(CAN_frame rx_frame) { break; } + if (stop_battery_query) { //Leafspy is active, stop our own polling + break; + } + //First check which group data we are getting if (rx_frame.data.u8[0] == 0x10) { //First message of a group group_7bb = rx_frame.data.u8[3]; - if (group_7bb != 1 && group_7bb != 2 && group_7bb != 4) { //We are only interested in groups 1,2 and 4 - break; - } } - if (stop_battery_query) { //Leafspy is active, stop our own polling - break; - } - transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group + transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group - if (group_7bb == 1) //High precision SOC, Current, voltages etc. + if (group_7bb == 0x01) //High precision SOC, Current, voltages etc. { if (rx_frame.data.u8[0] == 0x10) { //First frame //High precision Battery_current_1 resides here, but has been deemed unusable by 62kWh owners @@ -890,7 +892,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } - if (group_7bb == 2) //Cell Voltages + if (group_7bb == 0x02) //Cell Voltages { if (rx_frame.data.u8[0] == 0x10) { //first frame is anomalous battery_request_idx = 0; @@ -933,7 +935,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } - if (group_7bb == 4) { //Temperatures + if (group_7bb == 0x04) { //Temperatures if (rx_frame.data.u8[0] == 0x10) { //First message battery_temp_raw_1 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_temp_raw_2_highnibble = rx_frame.data.u8[7]; @@ -990,12 +992,72 @@ void receive_can_battery(CAN_frame rx_frame) { } } + if (group_7bb == 0x83) //BatteryPartNumber + { + if (rx_frame.data.u8[0] == 0x10) { //First frame (101A6183334E4B32) + BatteryPartNumber[0] = rx_frame.data.u8[4]; + BatteryPartNumber[1] = rx_frame.data.u8[5]; + BatteryPartNumber[2] = rx_frame.data.u8[6]; + BatteryPartNumber[3] = rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x21) { //Second frame (2141524205170000) + BatteryPartNumber[4] = rx_frame.data.u8[1]; + BatteryPartNumber[5] = rx_frame.data.u8[2]; + BatteryPartNumber[6] = rx_frame.data.u8[3]; + } + if (rx_frame.data.u8[0] == 0x22) { //Third frame (2200000002101311) + } + + if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23000000000080FF) + } + } + if (group_7bb == 0x84) { //BatterySerialNumber + if (rx_frame.data.u8[0] == 0x10) { //First frame (10 16 61 84 32 33 30 55) + BatterySerialNumber[0] = rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x21) { //Second frame (21 4B 31 31 39 32 45 30) + BatterySerialNumber[1] = rx_frame.data.u8[1]; + BatterySerialNumber[2] = rx_frame.data.u8[2]; + BatterySerialNumber[3] = rx_frame.data.u8[3]; + BatterySerialNumber[4] = rx_frame.data.u8[4]; + BatterySerialNumber[5] = rx_frame.data.u8[5]; + BatterySerialNumber[6] = rx_frame.data.u8[6]; + BatterySerialNumber[7] = rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x22) { //Third frame (22 30 31 34 38 32 20 A0) + BatterySerialNumber[8] = rx_frame.data.u8[1]; + BatterySerialNumber[9] = rx_frame.data.u8[2]; + BatterySerialNumber[10] = rx_frame.data.u8[3]; + BatterySerialNumber[11] = rx_frame.data.u8[4]; + BatterySerialNumber[12] = rx_frame.data.u8[5]; + BatterySerialNumber[13] = rx_frame.data.u8[6]; + BatterySerialNumber[14] = rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23 00 00 00 00 00 00 00) + } + } + + if (group_7bb == 0x90) { //BMSIDcode + if (rx_frame.data.u8[0] == 0x10) { //First frame (100A619044434131) + BMSIDcode[0] = rx_frame.data.u8[4]; + BMSIDcode[1] = rx_frame.data.u8[5]; + BMSIDcode[2] = rx_frame.data.u8[6]; + BMSIDcode[3] = rx_frame.data.u8[7]; + } + if (rx_frame.data.u8[0] == 0x21) { //Second frame (2130303535FFFFFF) + BMSIDcode[4] = rx_frame.data.u8[1]; + BMSIDcode[5] = rx_frame.data.u8[2]; + BMSIDcode[6] = rx_frame.data.u8[3]; + BMSIDcode[7] = rx_frame.data.u8[4]; + } + } + break; default: break; } } -void send_can_battery() { +void transmit_can_battery() { if (battery_can_alive) { unsigned long currentMillis = millis(); @@ -1028,9 +1090,9 @@ void send_can_battery() { LEAF_1D4.data.u8[7] = 0xDE; break; } - transmit_can(&LEAF_1D4, can_config.battery); + transmit_can_frame(&LEAF_1D4, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&LEAF_1D4, can_config.battery_double); + transmit_can_frame(&LEAF_1D4, can_config.battery_double); #endif // DOUBLE_BATTERY switch (mprun10r) { @@ -1124,9 +1186,9 @@ void send_can_battery() { //Only send this message when NISSANLEAF_CHARGER is not defined (otherwise it will collide!) #ifndef NISSANLEAF_CHARGER - transmit_can(&LEAF_1F2, can_config.battery); + transmit_can_frame(&LEAF_1F2, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&LEAF_1F2, can_config.battery_double); + transmit_can_frame(&LEAF_1F2, can_config.battery_double); #endif // DOUBLE_BATTERY #endif @@ -1151,9 +1213,9 @@ void send_can_battery() { } // VCM message, containing info if battery should sleep or stay awake - transmit_can(&LEAF_50B, can_config.battery); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 + transmit_can_frame(&LEAF_50B, can_config.battery); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 #ifdef DOUBLE_BATTERY - transmit_can(&LEAF_50B, can_config.battery_double); + transmit_can_frame(&LEAF_50B, can_config.battery_double); #endif // DOUBLE_BATTERY LEAF_50C.data.u8[3] = mprun100; @@ -1175,9 +1237,9 @@ void send_can_battery() { LEAF_50C.data.u8[5] = 0x9A; break; } - transmit_can(&LEAF_50C, can_config.battery); + transmit_can_frame(&LEAF_50C, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&LEAF_50C, can_config.battery_double); + transmit_can_frame(&LEAF_50C, can_config.battery_double); #endif // DOUBLE_BATTERY mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1... @@ -1189,12 +1251,14 @@ void send_can_battery() { //Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?) if (!stop_battery_query) { - group = (group == 1) ? 2 : (group == 2) ? 4 : 1; - // Cycle between group 1, 2, and 4 using ternary operation - LEAF_GROUP_REQUEST.data.u8[2] = group; - transmit_can(&LEAF_GROUP_REQUEST, can_config.battery); + + // Move to the next group + PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[] + LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex]; + + transmit_can_frame(&LEAF_GROUP_REQUEST, can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&LEAF_GROUP_REQUEST, can_config.battery_double); + transmit_can_frame(&LEAF_GROUP_REQUEST, can_config.battery_double); #endif // DOUBLE_BATTERY } @@ -1257,19 +1321,19 @@ void clearSOH(void) { break; case 1: // Set CAN_PROCESS_FLAG to 0xC0 LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 02 50 C0 FF FF FF FF FF stateMachineClearSOH = 2; break; case 2: // Set something ? LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 7E FF FF FF FF FF FF stateMachineClearSOH = 3; break; case 3: // Request challenge to solve LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF stateMachineClearSOH = 4; break; @@ -1277,34 +1341,34 @@ void clearSOH(void) { decodeChallengeData(incomingChallenge, solvedChallenge); LEAF_CLEAR_SOH.data = { 0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK) stateMachineClearSOH = 5; break; case 5: // Reply with even more decoded challenge data LEAF_CLEAR_SOH.data = { 0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); // BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data stateMachineClearSOH = 6; break; case 6: // Check if solved data was OK LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); //7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01. //Incase you sent wrong challenge, you get 03 7f 31 12 stateMachineClearSOH = 7; break; case 7: // Reset SOH% request LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); //7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command. //7BB 03 7f 31 12 means your challenge was wrong, so command ignored stateMachineClearSOH = 8; break; case 8: // Please proceed with resetting SOH LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00}; - transmit_can(&LEAF_CLEAR_SOH, can_config.battery); + transmit_can_frame(&LEAF_CLEAR_SOH, can_config.battery); // 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK stateMachineClearSOH = 255; break; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index 245f0f397..f5e7adcde 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -13,7 +13,7 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature); bool is_message_corrupt(CAN_frame rx_frame); void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void clearSOH(void); //Cryptographic functions void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer); diff --git a/Software/src/battery/PYLON-BATTERY.cpp b/Software/src/battery/PYLON-BATTERY.cpp index ec61901d0..86ea9c0fe 100644 --- a/Software/src/battery/PYLON-BATTERY.cpp +++ b/Software/src/battery/PYLON-BATTERY.cpp @@ -80,7 +80,7 @@ void update_values_battery() { datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage; } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { case 0x7310: @@ -156,17 +156,17 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 1s CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; - transmit_can(&PYLON_3010, can_config.battery); // Heartbeat - transmit_can(&PYLON_4200, can_config.battery); // Ensemble OR System equipment info, depends on frame0 - transmit_can(&PYLON_8200, can_config.battery); // Control device quit sleep status - transmit_can(&PYLON_8210, can_config.battery); // Charge command + transmit_can_frame(&PYLON_3010, can_config.battery); // Heartbeat + transmit_can_frame(&PYLON_4200, can_config.battery); // Ensemble OR System equipment info, depends on frame0 + transmit_can_frame(&PYLON_8200, can_config.battery); // Control device quit sleep status + transmit_can_frame(&PYLON_8210, can_config.battery); // Charge command if (ensemble_info_ack) { PYLON_4200.data.u8[0] = 0x00; //Request system equipment info diff --git a/Software/src/battery/PYLON-BATTERY.h b/Software/src/battery/PYLON-BATTERY.h index 98588c690..8d17c047d 100644 --- a/Software/src/battery/PYLON-BATTERY.h +++ b/Software/src/battery/PYLON-BATTERY.h @@ -13,6 +13,6 @@ #define MAX_CELL_DEVIATION_MV 500 void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp index 4ca3a3303..3ff1325ec 100644 --- a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp @@ -180,7 +180,7 @@ void update_values_battery() { datalayer.battery.info.min_design_voltage_dV = DischargeVoltageLimit * 10; } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { case 0x080: // 15ms @@ -301,14 +301,14 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 50ms CAN Message if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) { previousMillis50ms = currentMillis; - transmit_can(&RANGE_ROVER_18B, can_config.battery); + transmit_can_frame(&RANGE_ROVER_18B, can_config.battery); } } diff --git a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h index c4cdcf07b..9dfcaf064 100644 --- a/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h +++ b/Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h @@ -13,6 +13,6 @@ #define MAX_CELL_DEVIATION_MV 500 //TODO: Configure void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 096cb6292..4fa4a6e4a 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -103,41 +103,41 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage; -#ifdef DEBUG_VIA_USB - Serial.println("Values going to inverter:"); - Serial.print("SOH%: "); - Serial.print(datalayer.battery.status.soh_pptt); - Serial.print(", SOC% scaled: "); - Serial.print(datalayer.battery.status.reported_soc); - Serial.print(", Voltage: "); - Serial.print(datalayer.battery.status.voltage_dV); - Serial.print(", Max discharge power: "); - Serial.print(datalayer.battery.status.max_discharge_power_W); - Serial.print(", Max charge power: "); - Serial.print(datalayer.battery.status.max_charge_power_W); - Serial.print(", Max temp: "); - Serial.print(datalayer.battery.status.temperature_max_dC); - Serial.print(", Min temp: "); - Serial.print(datalayer.battery.status.temperature_min_dC); - Serial.print(", BMS Status (3=OK): "); - Serial.print(datalayer.battery.status.bms_status); - - Serial.println("Battery values: "); - Serial.print("Real SOC: "); - Serial.print(LB_SOC); - Serial.print(", Current: "); - Serial.print(LB_Current); - Serial.print(", kWh remain: "); - Serial.print(LB_kWh_Remaining); - Serial.print(", max mV: "); - Serial.print(LB_Cell_Max_Voltage); - Serial.print(", min mV: "); - Serial.print(LB_Cell_Min_Voltage); +#ifdef DEBUG_LOG + logging.println("Values going to inverter:"); + logging.print("SOH%: "); + logging.print(datalayer.battery.status.soh_pptt); + logging.print(", SOC% scaled: "); + logging.print(datalayer.battery.status.reported_soc); + logging.print(", Voltage: "); + logging.print(datalayer.battery.status.voltage_dV); + logging.print(", Max discharge power: "); + logging.print(datalayer.battery.status.max_discharge_power_W); + logging.print(", Max charge power: "); + logging.print(datalayer.battery.status.max_charge_power_W); + logging.print(", Max temp: "); + logging.print(datalayer.battery.status.temperature_max_dC); + logging.print(", Min temp: "); + logging.print(datalayer.battery.status.temperature_min_dC); + logging.print(", BMS Status (3=OK): "); + logging.print(datalayer.battery.status.bms_status); + + logging.println("Battery values: "); + logging.print("Real SOC: "); + logging.print(LB_SOC); + logging.print(", Current: "); + logging.print(LB_Current); + logging.print(", kWh remain: "); + logging.print(LB_kWh_Remaining); + logging.print(", max mV: "); + logging.print(LB_Cell_Max_Voltage); + logging.print(", min mV: "); + logging.print(LB_Cell_Min_Voltage); #endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x155: //BMS1 @@ -210,12 +210,12 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 100ms CAN Message (for 2.4s, then pause 10s) if ((currentMillis - previousMillis100) >= (INTERVAL_100_MS + GVL_pause)) { previousMillis100 = currentMillis; - transmit_can(&KANGOO_423, can_config.battery); + transmit_can_frame(&KANGOO_423, can_config.battery); GVI_Pollcounter++; GVL_pause = 0; if (GVI_Pollcounter >= 24) { @@ -227,9 +227,9 @@ void send_can_battery() { if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { previousMillis1000 = currentMillis; if (GVB_79B_Continue) - transmit_can(&KANGOO_79B_Continue, can_config.battery); + transmit_can_frame(&KANGOO_79B_Continue, can_config.battery); } else { - transmit_can(&KANGOO_79B, can_config.battery); + transmit_can_frame(&KANGOO_79B, can_config.battery); } } diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.h b/Software/src/battery/RENAULT-KANGOO-BATTERY.h index 6afb23358..25f739c6a 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.h +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.h @@ -12,6 +12,6 @@ #define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/RENAULT-TWIZY.cpp b/Software/src/battery/RENAULT-TWIZY.cpp index b0140eff3..b6c746f26 100644 --- a/Software/src/battery/RENAULT-TWIZY.cpp +++ b/Software/src/battery/RENAULT-TWIZY.cpp @@ -65,7 +65,7 @@ void update_values_battery() { max_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC)); } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { case 0x155: @@ -127,7 +127,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { // we do not need to send anything to the battery for now } diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp index ef9f54f82..28a69df63 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp @@ -1,6 +1,5 @@ #include "../include.h" #ifdef RENAULT_ZOE_GEN1_BATTERY -#include // For std::min and std::max #include "../datalayer/datalayer.h" #include "../devboard/utils/events.h" #include "RENAULT-ZOE-GEN1-BATTERY.h" @@ -9,20 +8,24 @@ https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage Still TODO: -- Find max discharge and max charge values (for now hardcoded to 5kW) -- Fix the missing cell96 issue (Only cells 1-95 is shown) -- Find current sensor value (OVMS code reads this from inverter, which we dont have) -- Figure out why SOH% is not read (low prio) +- Fix the missing cell96 issue (Only cells 1-95 is shown on cellmonitor page) +- Automatically detect if we are on 22 or 41kWh battery (Nice to have, requires log file from 22kWh battery) /* /* Do not change code below unless you are sure what you are doing */ static uint16_t LB_SOC = 50; +static uint16_t LB_Display_SOC = 50; static uint16_t LB_SOH = 99; static int16_t LB_Average_Temperature = 0; -static uint32_t LB_Charge_Power_W = 0; -static int32_t LB_Current = 0; +static uint32_t LB_Charging_Power_W = 0; +static uint32_t LB_Regen_allowed_W = 0; +static uint32_t LB_Discharge_allowed_W = 0; +static int16_t LB_Current = 0; +static int16_t LB_Cell_minimum_temperature = 0; +static int16_t LB_Cell_maximum_temperature = 0; static uint16_t LB_kWh_Remaining = 0; static uint16_t LB_Battery_Voltage = 3700; +static uint8_t LB_Heartbeat = 0; static uint8_t frame0 = 0; static uint8_t current_poll = 0; static uint8_t requested_poll = 0; @@ -77,32 +80,17 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00% datalayer.battery.status.real_soc = SOC_polled; + //datalayer.battery.status.real_soc = LB_Display_SOC; //Alternative would be to use Dash SOC% - datalayer.battery.status.current_dA = LB_Current; //TODO: Take from CAN + datalayer.battery.status.current_dA = LB_Current * 10; //Convert A to dA //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_discharge_power_W = 5000; //TODO: Take from CAN + datalayer.battery.status.max_discharge_power_W = LB_Discharge_allowed_W; - datalayer.battery.status.max_charge_power_W = 5000; //TODO: Take from CAN - // TODO: Remove this hacky wacky scaling down charge power when we find value from CAN - if (datalayer.battery.status.real_soc > 9500) { - datalayer.battery.status.max_charge_power_W = 3000; - } - if (datalayer.battery.status.real_soc > 9600) { - datalayer.battery.status.max_charge_power_W = 2000; - } - if (datalayer.battery.status.real_soc > 9700) { - datalayer.battery.status.max_charge_power_W = 1000; - } - if (datalayer.battery.status.real_soc > 9800) { - datalayer.battery.status.max_charge_power_W = 500; - } - if (datalayer.battery.status.real_soc > 9900) { - datalayer.battery.status.max_charge_power_W = 50; - } + datalayer.battery.status.max_charge_power_W = LB_Regen_allowed_W; int16_t temperatures[] = {cell_1_temperature_polled, cell_2_temperature_polled, cell_3_temperature_polled, cell_4_temperature_polled, cell_5_temperature_polled, cell_6_temperature_polled, @@ -145,28 +133,61 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value; datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value; datalayer.battery.status.voltage_dV = static_cast((calculated_total_pack_voltage_mV / 100)); // mV to dV - -#ifdef DEBUG_VIA_USB - -#endif } -void receive_can_battery(CAN_frame rx_frame) { - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { - case 0x427: - LB_Charge_Power_W = rx_frame.data.u8[5] * 300; + case 0x155: //10ms - Charging power, current and SOC + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + LB_Charging_Power_W = rx_frame.data.u8[0] * 300; + LB_Current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500); + LB_Display_SOC = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case 0x427: // NOTE: Not present on 41kWh battery! + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; LB_kWh_Remaining = (((((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])) >> 6) & 0x3ff) * 0.1); break; - case 0x42E: //HV SOC & Battery Temp & Charging Power + case 0x42E: //NOTE: Not present on 41kWh battery! + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; LB_Battery_Voltage = (((((rx_frame.data.u8[3] << 8) | (rx_frame.data.u8[4])) >> 5) & 0x3ff) * 0.5); //0.5V/bit LB_Average_Temperature = (((((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6])) >> 5) & 0x7F) - 40); break; + case 0x424: //100ms - Charge limits, Temperatures, SOH + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + LB_Regen_allowed_W = rx_frame.data.u8[2] * 500; + LB_Discharge_allowed_W = rx_frame.data.u8[3] * 500; + LB_Cell_minimum_temperature = (rx_frame.data.u8[4] - 40); + LB_SOH = rx_frame.data.u8[5]; + LB_Heartbeat = rx_frame.data.u8[6]; // Alternates between 0x55 and 0xAA every 500ms (Same as on Nissan LEAF) + LB_Cell_maximum_temperature = (rx_frame.data.u8[7] - 40); + break; + case 0x425: //100ms Unknown content + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //Sent only? by 41kWh battery + break; + case 0x445: //100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //Sent only? by 41kWh battery (potential use for detecting which generation we are on) + break; + case 0x4AE: //3000ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //Sent only? by 41kWh battery (potential use for detecting which generation we are on) + break; + case 0x4AF: //100ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //Sent only? by 41kWh battery (potential use for detecting which generation we are on) + break; case 0x654: //SOC + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; LB_SOC = rx_frame.data.u8[3]; break; - case 0x658: //SOH - LB_SOH = (rx_frame.data.u8[4] & 0x7F); + case 0x658: //SOH - NOTE: Not present on 41kWh battery! (Is this message on 21kWh?) + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //LB_SOH = (rx_frame.data.u8[4] & 0x7F); + break; + case 0x659: //3000ms + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + //Sent only? by 41kWh battery (potential use for detecting which generation we are on) break; case 0x7BB: //Reply from active polling frame0 = rx_frame.data.u8[0]; @@ -174,7 +195,7 @@ void receive_can_battery(CAN_frame rx_frame) { switch (frame0) { case 0x10: //PID HEADER, datarow 0 requested_poll = rx_frame.data.u8[3]; - transmit_can(&ZOE_ACK_79B, can_config.battery); + transmit_can_frame(&ZOE_ACK_79B, can_config.battery); if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) { cellvoltages[0] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; @@ -465,7 +486,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { @@ -474,7 +495,7 @@ void send_can_battery() { set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100)); } previousMillis100 = currentMillis; - transmit_can(&ZOE_423, can_config.battery); + transmit_can_frame(&ZOE_423, can_config.battery); if ((counter_423 / 5) % 2 == 0) { // Alternate every 5 messages between these two ZOE_423.data.u8[4] = 0xB2; @@ -513,7 +534,7 @@ void send_can_battery() { ZOE_POLL_79B.data.u8[2] = current_poll; - transmit_can(&ZOE_POLL_79B, can_config.battery); + transmit_can_frame(&ZOE_POLL_79B, can_config.battery); } } diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h index 81c7c2cc0..1a3968bfa 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h @@ -11,6 +11,6 @@ #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp index 08ac57bfe..ec0e8e8db 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp @@ -216,13 +216,9 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.zoePH2.battery_pack_time = battery_pack_time; datalayer_extended.zoePH2.battery_soc_min = battery_soc_min; datalayer_extended.zoePH2.battery_soc_max = battery_soc_max; - -#ifdef DEBUG_VIA_USB - -#endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { case 0x18DAF1DB: // LBC Reply from active polling @@ -362,7 +358,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 200ms CAN Message if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { @@ -379,8 +375,8 @@ void send_can_battery() { ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8); ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF); - transmit_can(&ZOE_POLL_18DADBF1, can_config.battery); - transmit_can(&ZOE_373, can_config.battery); + transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery); + transmit_can_frame(&ZOE_373, can_config.battery); } } diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h index c75bb774f..cfcc65d76 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h @@ -10,7 +10,7 @@ #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #define POLL_SOC 0x9001 #define POLL_USABLE_SOC 0x9002 diff --git a/Software/src/battery/RJXZS-BMS.cpp b/Software/src/battery/RJXZS-BMS.cpp index 85b96434c..345e8eaed 100644 --- a/Software/src/battery/RJXZS-BMS.cpp +++ b/Software/src/battery/RJXZS-BMS.cpp @@ -158,21 +158,21 @@ void update_values_battery() { datalayer.battery.status.cell_min_voltage_mV = minimum_cell_voltage; } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { /* // All CAN messages recieved will be logged via serial - Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D - Serial.print(" "); - Serial.print(rx_frame.ID, HEX); - Serial.print(" "); - Serial.print(rx_frame.DLC); - Serial.print(" "); + 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) { - Serial.print(rx_frame.data.u8[i], HEX); - Serial.print(" "); + logging.print(rx_frame.data.u8[i], HEX); + logging.print(" "); } - Serial.println(""); + logging.println(""); */ switch (rx_frame.ID) { case 0xF5: // This is the only message is sent from BMS @@ -550,7 +550,7 @@ void receive_can_battery(CAN_frame rx_frame) { } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 10s CAN Message if (currentMillis - previousMillis10s >= INTERVAL_10_S) { @@ -563,8 +563,8 @@ void send_can_battery() { } if (!setup_completed) { - transmit_can(&RJXZS_10, can_config.battery); // Communication connected flag - transmit_can(&RJXZS_1C, can_config.battery); // CAN OK + transmit_can_frame(&RJXZS_10, can_config.battery); // Communication connected flag + transmit_can_frame(&RJXZS_1C, can_config.battery); // CAN OK } } } diff --git a/Software/src/battery/RJXZS-BMS.h b/Software/src/battery/RJXZS-BMS.h index f42af7344..a60670391 100644 --- a/Software/src/battery/RJXZS-BMS.h +++ b/Software/src/battery/RJXZS-BMS.h @@ -19,6 +19,6 @@ #define NATIVECAN_250KBPS void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index e6f27accd..db1e43770 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -18,6 +18,8 @@ static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send static uint8_t poll_data_pid = 0; +static uint8_t counter_200 = 0; +static uint8_t checksum_200 = 0; static uint16_t SOC_Display = 0; static uint16_t batterySOH = 100; @@ -32,11 +34,27 @@ static int16_t leadAcidBatteryVoltage = 120; static int8_t temperatureMax = 0; static int8_t temperatureMin = 0; static int16_t batteryAmps = 0; -static uint8_t counter_200 = 0; -static uint8_t checksum_200 = 0; static uint8_t StatusBattery = 0; static uint16_t cellvoltages_mv[96]; +#ifdef DOUBLE_BATTERY +static uint16_t battery2_SOC_Display = 0; +static uint16_t battery2_SOH = 100; +static uint16_t battery2_CellVoltMax_mV = 3700; +static uint16_t battery2_CellVoltMin_mV = 3700; +static uint8_t battery2_CellVmaxNo = 0; +static uint8_t battery2_CellVminNo = 0; +static uint16_t battery2_allowedDischargePower = 0; +static uint16_t battery2_allowedChargePower = 0; +static uint16_t battery2_batteryVoltage = 0; +static int16_t battery2_leadAcidBatteryVoltage = 120; +static int8_t battery2_temperatureMax = 0; +static int8_t battery2_temperatureMin = 0; +static int16_t battery2_batteryAmps = 0; +static uint8_t battery2_StatusBattery = 0; +static uint16_t battery2_cellvoltages_mv[96]; +#endif //DOUBLE_BATTERY + CAN_frame SANTAFE_200 = {.FD = false, .ext_ID = false, .DLC = 8, @@ -96,13 +114,9 @@ void update_values_battery() { //This function maps all the values fetched via if (leadAcidBatteryVoltage < 110) { set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); } - -#ifdef DEBUG_VIA_USB - -#endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x1FF: datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; @@ -157,7 +171,8 @@ void receive_can_battery(CAN_frame rx_frame) { switch (rx_frame.data.u8[0]) { case 0x10: //"PID Header" if (rx_frame.data.u8[4] == poll_data_pid) { - transmit_can(&SANTAFE_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled + transmit_can_frame(&SANTAFE_7E4_ack, + can_config.battery); //Send ack to BMS if the same frame is sent as polled } break; case 0x21: //First frame in PID group @@ -318,7 +333,7 @@ void receive_can_battery(CAN_frame rx_frame) { break; } } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); //Send 10ms message @@ -337,11 +352,14 @@ void send_can_battery() { SANTAFE_200.data.u8[7] = checksum_200; - transmit_can(&SANTAFE_200, can_config.battery); - - transmit_can(&SANTAFE_2A1, can_config.battery); - - transmit_can(&SANTAFE_2F0, can_config.battery); + transmit_can_frame(&SANTAFE_200, can_config.battery); + transmit_can_frame(&SANTAFE_2A1, can_config.battery); + transmit_can_frame(&SANTAFE_2F0, can_config.battery); +#ifdef DOUBLE_BATTERY + transmit_can_frame(&SANTAFE_200, can_config.battery_double); + transmit_can_frame(&SANTAFE_2A1, can_config.battery_double); + transmit_can_frame(&SANTAFE_2F0, can_config.battery_double); +#endif //DOUBLE_BATTERY counter_200++; if (counter_200 > 0xF) { @@ -353,36 +371,279 @@ void send_can_battery() { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; - transmit_can(&SANTAFE_523, can_config.battery); + transmit_can_frame(&SANTAFE_523, can_config.battery); +#ifdef DOUBLE_BATTERY + transmit_can_frame(&SANTAFE_523, can_config.battery_double); +#endif //DOUBLE_BATTERY } // Send 500ms CAN Message if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { previousMillis500 = currentMillis; - //PID data is polled after last message sent from battery: - if (poll_data_pid >= 5) { //polling one of 5 PIDs at 100ms, resolution = 500ms - poll_data_pid = 0; - } - poll_data_pid++; - if (poll_data_pid == 1) { - SANTAFE_7E4_poll.data.u8[3] = 0x01; - transmit_can(&SANTAFE_7E4_poll, can_config.battery); - } else if (poll_data_pid == 2) { - SANTAFE_7E4_poll.data.u8[3] = 0x02; - transmit_can(&SANTAFE_7E4_poll, can_config.battery); - } else if (poll_data_pid == 3) { - SANTAFE_7E4_poll.data.u8[3] = 0x03; - transmit_can(&SANTAFE_7E4_poll, can_config.battery); - } else if (poll_data_pid == 4) { - SANTAFE_7E4_poll.data.u8[3] = 0x04; - transmit_can(&SANTAFE_7E4_poll, can_config.battery); - } else if (poll_data_pid == 5) { - SANTAFE_7E4_poll.data.u8[3] = 0x05; - transmit_can(&SANTAFE_7E4_poll, can_config.battery); - } + // PID data is polled after last message sent from battery: + poll_data_pid = (poll_data_pid % 5) + 1; + SANTAFE_7E4_poll.data.u8[3] = (uint8_t)poll_data_pid; + transmit_can_frame(&SANTAFE_7E4_poll, can_config.battery); +#ifdef DOUBLE_BATTERY + transmit_can_frame(&SANTAFE_7E4_poll, can_config.battery_double); +#endif //DOUBLE_BATTERY + } +} + +#ifdef DOUBLE_BATTERY +void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + + datalayer.battery2.status.real_soc = (battery2_SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00 + + datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00% + + datalayer.battery2.status.voltage_dV = battery2_batteryVoltage; + + datalayer.battery2.status.current_dA = -battery2_batteryAmps; + + datalayer.battery2.status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh); + + datalayer.battery2.status.max_discharge_power_W = battery2_allowedDischargePower * 10; + + datalayer.battery2.status.max_charge_power_W = battery2_allowedChargePower * 10; + + //Power in watts, Negative = charging batt + datalayer.battery2.status.active_power_W = + ((datalayer.battery2.status.voltage_dV * datalayer.battery2.status.current_dA) / 100); + + datalayer.battery2.status.cell_max_voltage_mV = battery2_CellVoltMax_mV; + + datalayer.battery2.status.cell_min_voltage_mV = battery2_CellVoltMin_mV; + + datalayer.battery2.status.temperature_min_dC = battery2_temperatureMin * 10; //Increase decimals, 17C -> 17.0C + + datalayer.battery2.status.temperature_max_dC = battery2_temperatureMax * 10; //Increase decimals, 18C -> 18.0C + + if (battery2_leadAcidBatteryVoltage < 110) { + set_event(EVENT_12V_LOW, battery2_leadAcidBatteryVoltage); + } +} + +void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x1FF: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery2_StatusBattery = (rx_frame.data.u8[0] & 0x0F); + break; + case 0x4D5: + break; + case 0x4DD: + break; + case 0x4DE: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x4E0: + break; + case 0x542: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery2_SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2; + break; + case 0x588: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery2_batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]); + break; + case 0x597: + break; + case 0x5A6: + break; + case 0x5A7: + break; + case 0x5AD: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery2_batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2]; + break; + case 0x5AE: + break; + case 0x5F1: + break; + case 0x620: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery2_leadAcidBatteryVoltage = rx_frame.data.u8[1]; + battery2_temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery + battery2_temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery + break; + case 0x670: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + battery2_allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); + battery2_allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); + break; + case 0x671: + datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x7EC: //Data From polled PID group, BigEndian + switch (rx_frame.data.u8[0]) { + case 0x10: //"PID Header" + if (rx_frame.data.u8[4] == poll_data_pid) { + transmit_can_frame(&SANTAFE_7E4_ack, + can_config.battery_double); //Send ack to BMS if the same frame is sent as polled + } + break; + case 0x21: //First frame in PID group + if (poll_data_pid == 1) { + } else if (poll_data_pid == 2) { + battery2_cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 3) { + battery2_cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 4) { + battery2_cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20); + } + break; + case 0x22: //Second datarow in PID group + if (poll_data_pid == 2) { + battery2_cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 3) { + battery2_cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[41] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 4) { + battery2_cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[73] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 6) { + } + break; + case 0x23: //Third datarow in PID group + if (poll_data_pid == 1) { + battery2_CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV + } else if (poll_data_pid == 2) { + battery2_cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 3) { + battery2_cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[48] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 4) { + battery2_cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[80] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 5) { + if (rx_frame.data.u8[6] > 0) { + battery2_SOH = rx_frame.data.u8[6]; + } + if (battery2_SOH > 100) { + battery2_SOH = 100; + } + } + break; + case 0x24: //Fourth datarow in PID group + if (poll_data_pid == 1) { + battery2_CellVmaxNo = rx_frame.data.u8[1]; + battery2_CellVminNo = rx_frame.data.u8[3]; + CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV + } else if (poll_data_pid == 2) { + battery2_cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 3) { + battery2_cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 4) { + battery2_cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20); + battery2_cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20); + battery2_cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20); + } else if (poll_data_pid == 5) { + } + break; + case 0x25: //Fifth datarow in PID group + if (poll_data_pid == 2) { + battery2_cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20); + } else if (poll_data_pid == 3) { + battery2_cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20); + } else if (poll_data_pid == 4) { + battery2_cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20); + battery2_cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20); + battery2_cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20); + battery2_cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20); + battery2_cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20); + + //Map all cell voltages to the global array, we have sampled them all! + memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages_mv, 96 * sizeof(uint16_t)); + } else if (poll_data_pid == 5) { + } + break; + case 0x26: //Sixth datarow in PID group + break; + case 0x27: //Seventh datarow in PID group + break; + case 0x28: //Eighth datarow in PID group + break; + } + break; + default: + break; } } +#endif //DOUBLE_BATTERY uint8_t CalculateCRC8(CAN_frame rx_frame) { int crc = 0; @@ -409,6 +670,16 @@ void setup_battery(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.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; + +#ifdef DOUBLE_BATTERY + datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells; + datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; + datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV; + datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV; + datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV; + datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV; +#endif //DOUBLE_BATTERY } #endif diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h index fb4774a5a..092a200e1 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h @@ -12,6 +12,6 @@ uint8_t CalculateCRC8(CAN_frame rx_frame); void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp index 41697c33b..35a7104cd 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp @@ -94,8 +94,8 @@ void manageSerialLinkReceiver() { bool readError = dataLinkReceive.checkReadError(true); // check for error & clear error flag if (readError) { - Serial.print(currentTime); - Serial.println(" - ERROR: SerialDataLink - Read Error"); + logging.print(currentTime); + logging.println(" - ERROR: SerialDataLink - Read Error"); lasterror = true; errors++; } @@ -112,8 +112,8 @@ void manageSerialLinkReceiver() { //bms_status = ACTIVE; // just testing if (lasterror) { lasterror = false; - Serial.print(currentTime); - Serial.println(" - RECOVERY: SerialDataLink - Read GOOD"); + logging.print(currentTime); + logging.println(" - RECOVERY: SerialDataLink - Read GOOD"); } } @@ -134,34 +134,34 @@ void manageSerialLinkReceiver() { // report Lost data & Max charge / Discharge reductions if (minutesLost != last_minutesLost) { last_minutesLost = minutesLost; - Serial.print(currentTime); + logging.print(currentTime); if (batteryFault) { - Serial.print("Battery Fault (minutes) : "); + logging.print("Battery Fault (minutes) : "); } else { - Serial.print(" - Minutes without data : "); + logging.print(" - Minutes without data : "); } - Serial.print(minutesLost); - Serial.print(", max Charge = "); - Serial.print(datalayer.battery.status.max_charge_power_W); - Serial.print(", max Discharge = "); - Serial.println(datalayer.battery.status.max_discharge_power_W); + logging.print(minutesLost); + logging.print(", max Charge = "); + logging.print(datalayer.battery.status.max_charge_power_W); + logging.print(", max Discharge = "); + logging.println(datalayer.battery.status.max_discharge_power_W); } } if (currentTime - reportTime > 59999) { reportTime = currentTime; - Serial.print(currentTime); - Serial.print(" SerialDataLink-Receiver - NewData :"); - Serial.print(reads); - Serial.print(" Errors : "); - Serial.println(errors); + logging.print(currentTime); + logging.print(" SerialDataLink-Receiver - NewData :"); + logging.print(reads); + logging.print(" Errors : "); + logging.println(errors); reads = 0; errors = 0; // --- printUsefullData(); -//Serial.print("SOC = "); -//Serial.println(SOC); -#ifdef DEBUG_VIA_USB +//logging.print("SOC = "); +//logging.println(SOC); +#ifdef DEBUG_LOG update_values_serial_link(); #endif } @@ -179,43 +179,43 @@ void manageSerialLinkReceiver() { } void update_values_serial_link() { - Serial.println("Values from battery: "); - Serial.print("SOC: "); - Serial.print(datalayer.battery.status.real_soc); - Serial.print(" SOH: "); - Serial.print(datalayer.battery.status.soh_pptt); - Serial.print(" Voltage: "); - Serial.print(datalayer.battery.status.voltage_dV); - Serial.print(" Current: "); - Serial.print(datalayer.battery.status.current_dA); - Serial.print(" Capacity: "); - Serial.print(datalayer.battery.info.total_capacity_Wh); - Serial.print(" Remain cap: "); - Serial.print(datalayer.battery.status.remaining_capacity_Wh); - Serial.print(" Max discharge W: "); - Serial.print(datalayer.battery.status.max_discharge_power_W); - Serial.print(" Max charge W: "); - Serial.print(datalayer.battery.status.max_charge_power_W); - Serial.print(" BMS status: "); - Serial.print(datalayer.battery.status.bms_status); - Serial.print(" Power: "); - Serial.print(datalayer.battery.status.active_power_W); - Serial.print(" Temp min: "); - Serial.print(datalayer.battery.status.temperature_min_dC); - Serial.print(" Temp max: "); - Serial.print(datalayer.battery.status.temperature_max_dC); - Serial.print(" Cell max: "); - Serial.print(datalayer.battery.status.cell_max_voltage_mV); - Serial.print(" Cell min: "); - Serial.print(datalayer.battery.status.cell_min_voltage_mV); - Serial.print(" LFP : "); - Serial.print(datalayer.battery.info.chemistry); - Serial.print(" Battery Allows Contactor Closing: "); - Serial.print(datalayer.system.status.battery_allows_contactor_closing); - Serial.print(" Inverter Allows Contactor Closing: "); - Serial.print(datalayer.system.status.inverter_allows_contactor_closing); - - Serial.println(""); + logging.println("Values from battery: "); + logging.print("SOC: "); + logging.print(datalayer.battery.status.real_soc); + logging.print(" SOH: "); + logging.print(datalayer.battery.status.soh_pptt); + logging.print(" Voltage: "); + logging.print(datalayer.battery.status.voltage_dV); + logging.print(" Current: "); + logging.print(datalayer.battery.status.current_dA); + logging.print(" Capacity: "); + logging.print(datalayer.battery.info.total_capacity_Wh); + logging.print(" Remain cap: "); + logging.print(datalayer.battery.status.remaining_capacity_Wh); + logging.print(" Max discharge W: "); + logging.print(datalayer.battery.status.max_discharge_power_W); + logging.print(" Max charge W: "); + logging.print(datalayer.battery.status.max_charge_power_W); + logging.print(" BMS status: "); + logging.print(datalayer.battery.status.bms_status); + logging.print(" Power: "); + logging.print(datalayer.battery.status.active_power_W); + logging.print(" Temp min: "); + logging.print(datalayer.battery.status.temperature_min_dC); + logging.print(" Temp max: "); + logging.print(datalayer.battery.status.temperature_max_dC); + logging.print(" Cell max: "); + logging.print(datalayer.battery.status.cell_max_voltage_mV); + logging.print(" Cell min: "); + logging.print(datalayer.battery.status.cell_min_voltage_mV); + logging.print(" LFP : "); + logging.print(datalayer.battery.info.chemistry); + logging.print(" Battery Allows Contactor Closing: "); + logging.print(datalayer.system.status.battery_allows_contactor_closing); + logging.print(" Inverter Allows Contactor Closing: "); + logging.print(datalayer.system.status.inverter_allows_contactor_closing); + + logging.println(""); } void setup_battery(void) { @@ -224,7 +224,7 @@ void setup_battery(void) { } // Needed to make the compiler happy void update_values_battery() {} -void send_can_battery() {} -void receive_can_battery(CAN_frame rx_frame) {} +void transmit_can_battery() {} +void handle_incoming_can_frame_battery(CAN_frame rx_frame) {} #endif diff --git a/Software/src/battery/SONO-BATTERY.cpp b/Software/src/battery/SONO-BATTERY.cpp new file mode 100644 index 000000000..d9de102fa --- /dev/null +++ b/Software/src/battery/SONO-BATTERY.cpp @@ -0,0 +1,183 @@ +#include "../include.h" +#ifdef SONO_BATTERY +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +#include "SONO-BATTERY.h" + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send + +static uint8_t seconds = 0; +static uint8_t functionalsafetybitmask = 0; +static uint16_t batteryVoltage = 3700; +static uint16_t allowedDischargePower = 0; +static uint16_t allowedChargePower = 0; +static uint16_t CellVoltMax_mV = 0; +static uint16_t CellVoltMin_mV = 0; +static int16_t batteryAmps = 0; +static int16_t temperatureMin = 0; +static int16_t temperatureMax = 0; +static uint8_t batterySOH = 99; +static uint8_t realSOC = 99; + +CAN_frame SONO_400 = {.FD = false, //Message of Vehicle Command, 100ms + .ext_ID = false, + .DLC = 8, + .ID = 0x400, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SONO_401 = {.FD = false, //Message of Vehicle Date, 1000ms + .ext_ID = false, + .DLC = 8, + .ID = 0x400, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus + + datalayer.battery.status.real_soc = (realSOC * 100); //increase SOC range from 0-100 -> 100.00 + + datalayer.battery.status.soh_pptt = (batterySOH * 100); //Increase decimals from 100% -> 100.00% + + datalayer.battery.status.voltage_dV = batteryVoltage; + + datalayer.battery.status.current_dA = batteryAmps; + + datalayer.battery.info.total_capacity_Wh = 54000; + + 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 = allowedDischargePower * 100; + + datalayer.battery.status.max_charge_power_W = allowedChargePower * 100; + + datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV; + + datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV; + + datalayer.battery.status.temperature_min_dC = temperatureMin; + + datalayer.battery.status.temperature_max_dC = temperatureMax; +} + +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x100: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x101: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x102: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + functionalsafetybitmask = rx_frame.data.u8[0]; //If any bits are high here, battery has a HSD fault active. + break; + case 0x200: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x220: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + allowedChargePower = (rx_frame.data.u8[0] << 8) + rx_frame.data.u8[1]; + allowedDischargePower = (rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]; + break; + case 0x221: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x300: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + batteryVoltage = (rx_frame.data.u8[4] << 8) + rx_frame.data.u8[5]; + break; + case 0x301: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + CellVoltMax_mV = (rx_frame.data.u8[1] << 8) + rx_frame.data.u8[2]; + CellVoltMin_mV = (rx_frame.data.u8[4] << 8) + rx_frame.data.u8[5]; + break; + case 0x310: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x311: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + batteryAmps = ((rx_frame.data.u8[4] << 8) + rx_frame.data.u8[5]) - 1000; + break; + case 0x320: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + temperatureMax = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[2]) - 400; + temperatureMin = ((rx_frame.data.u8[4] << 8) + rx_frame.data.u8[5]) - 400; + break; + case 0x321: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + batterySOH = rx_frame.data.u8[4]; + break; + case 0x330: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x331: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x601: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + realSOC = rx_frame.data.u8[0]; + break; + case 0x610: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x611: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x613: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x614: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x615: + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + default: + break; + } +} +void transmit_can_battery() { + unsigned long currentMillis = millis(); + + // Send 100ms CAN Message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + previousMillis100 = currentMillis; + //VCU Command message + SONO_400.data.u8[0] = 0x15; //Charging enabled bit01, dischargign enabled bit23, dc charging bit45 + + if (datalayer.battery.status.bms_status == FAULT) { + SONO_400.data.u8[0] = 0x14; //Charging DISABLED + } + transmit_can_frame(&SONO_400, can_config.battery); + } + // Send 1000ms CAN Message + if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { + previousMillis1000 = currentMillis; + + //Time and date + //Let's see if the battery is happy with just getting seconds incrementing + SONO_401.data.u8[0] = 2025; //Year + SONO_401.data.u8[1] = 1; //Month + SONO_401.data.u8[2] = 1; //Day + SONO_401.data.u8[3] = 12; //Hour + SONO_401.data.u8[4] = 15; //Minute + SONO_401.data.u8[5] = seconds; //Second + seconds = (seconds + 1) % 61; + transmit_can_frame(&SONO_401, can_config.battery); + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "Sono Motors Sion 64kWh LFP ", 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; + 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.chemistry = battery_chemistry_enum::LFP; +} + +#endif diff --git a/Software/src/battery/SONO-BATTERY.h b/Software/src/battery/SONO-BATTERY.h new file mode 100644 index 000000000..91a2db75f --- /dev/null +++ b/Software/src/battery/SONO-BATTERY.h @@ -0,0 +1,17 @@ +#ifndef SONO_BATTERY_H +#define SONO_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED +#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V +#define MIN_PACK_VOLTAGE_DV 2500 +#define MAX_CELL_DEVIATION_MV 250 +#define MAX_CELL_VOLTAGE_MV 3800 //Battery is put into emergency stop if one cell goes over this value +#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value + +uint8_t CalculateCRC8(CAN_frame rx_frame); +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 6e4ea6b1d..181bf903b 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -8,8 +8,9 @@ /* Do not change code below unless you are sure what you are doing */ /* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */ -static unsigned long previousMillis30 = 0; // will store last time a 30ms CAN Message was send - +static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +//0x221 545 VCFRONT_LVPowerState: "GenMsgCycleTime" 50ms CAN_frame TESLA_221_1 = { .FD = false, .ext_ID = false, @@ -22,213 +23,781 @@ CAN_frame TESLA_221_2 = { .DLC = 8, .ID = 0x221, .data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive +CAN_frame TESLA_602 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x602, + .data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Diagnostic request +static uint8_t stateMachineClearIsolationFault = 0xFF; static uint16_t sendContactorClosingMessagesStill = 300; +static uint16_t battery_cell_max_v = 3300; +static uint16_t battery_cell_min_v = 3300; +static uint16_t battery_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV +static bool cellvoltagesRead = false; +//0x3d2: 978 BMS_kwhCounter static uint32_t battery_total_discharge = 0; static uint32_t battery_total_charge = 0; -static uint16_t battery_volts = 0; // V -static int16_t battery_amps = 0; // A -static uint16_t battery_raw_amps = 0; // A -static int16_t battery_max_temp = 0; // C* -static int16_t battery_min_temp = 0; // C* -static uint16_t battery_energy_buffer = 0; -static uint16_t battery_energy_to_charge_complete = 0; -static uint16_t battery_expected_energy_remaining = 0; -static uint8_t battery_full_charge_complete = 0; -static uint16_t battery_ideal_energy_remaining = 0; -static uint16_t battery_nominal_energy_remaining = 0; -static uint16_t battery_nominal_full_pack_energy = 600; -static uint16_t battery_beginning_of_life = 600; +//0x352: 850 BMS_energyStatus +static uint16_t battery_energy_buffer = 0; // kWh +static uint16_t battery_energy_buffer_m1 = 0; // kWh +static uint16_t battery_energy_to_charge_complete = 0; // kWh +static uint16_t battery_energy_to_charge_complete_m1 = 0; // kWh +static uint16_t battery_expected_energy_remaining = 0; // kWh +static uint16_t battery_expected_energy_remaining_m1 = 0; // kWh +static bool battery_full_charge_complete = false; // Changed to bool +static bool battery_fully_charged = false; // Changed to bool +static uint16_t battery_ideal_energy_remaining = 0; // kWh +static uint16_t battery_ideal_energy_remaining_m0 = 0; // kWh +static uint16_t battery_nominal_energy_remaining = 0; // kWh +static uint16_t battery_nominal_energy_remaining_m0 = 0; // kWh +static uint16_t battery_nominal_full_pack_energy = 0; // Kwh +static uint16_t battery_nominal_full_pack_energy_m0 = 0; // Kwh +//0x132 306 HVBattAmpVolt +static uint16_t battery_volts = 0; // V +static int16_t battery_amps = 0; // A +static int16_t battery_raw_amps = 0; // A static uint16_t battery_charge_time_remaining = 0; // Minutes -static uint16_t battery_regenerative_limit = 0; -static uint16_t battery_discharge_limit = 0; -static uint16_t battery_max_heat_park = 0; -static uint16_t battery_hvac_max_power = 0; +//0x252 594 BMS_powerAvailable +static uint16_t BMS_maxRegenPower = 0; //rename from battery_regenerative_limit +static uint16_t BMS_maxDischargePower = 0; // rename from battery_discharge_limit +static uint16_t BMS_maxStationaryHeatPower = 0; //rename from battery_max_heat_park +static uint16_t BMS_hvacPowerBudget = 0; //rename from battery_hvac_max_power +static uint8_t BMS_notEnoughPowerForHeatPump = 0; +static uint8_t BMS_powerLimitState = 0; +static uint8_t BMS_inverterTQF = 0; +//0x2d2: 722 BMSVAlimits static uint16_t battery_max_discharge_current = 0; static uint16_t battery_max_charge_current = 0; static uint16_t battery_bms_max_voltage = 0; static uint16_t battery_bms_min_voltage = 0; -static uint16_t battery_high_voltage = 0; -static uint16_t battery_low_voltage = 0; -static uint16_t battery_output_current = 0; +//0x2b4: 692 PCS_dcdcRailStatus +static uint16_t battery_dcdcHvBusVolt = 0; // Change name from battery_high_voltage to battery_dcdcHvBusVolt +static uint16_t battery_dcdcLvBusVolt = 0; // Change name from battery_low_voltage to battery_dcdcLvBusVolt +static uint16_t battery_dcdcLvOutputCurrent = + 0; // Change name from battery_output_current to battery_dcdcLvOutputCurrent +//0x292: 658 BMS_socStatus +static uint16_t battery_beginning_of_life = 0; // kWh static uint16_t battery_soc_min = 0; static uint16_t battery_soc_max = 0; -static uint16_t battery_soc_vi = 0; +static uint16_t battery_soc_ui = 0; //Change name from battery_soc_vi to reflect DBC battery_soc_ui static uint16_t battery_soc_ave = 0; -static uint16_t battery_cell_max_v = 3700; -static uint16_t battery_cell_min_v = 3700; -static uint16_t battery_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV -static uint8_t battery_max_vno = 0; -static uint8_t battery_min_vno = 0; +static uint8_t battery_battTempPct = 0; +//0x392: BMS_packConfig +static uint32_t battery_packMass = 0; +static uint32_t battery_platformMaxBusVoltage = 0; +static uint32_t battery_packConfigMultiplexer = 0; +static uint32_t battery_moduleType = 0; +static uint32_t battery_reservedConfig = 0; +//0x332: 818 BattBrickMinMax:BMS_bmbMinMax +static int16_t battery_max_temp = 0; // C* +static int16_t battery_min_temp = 0; // C* +static uint16_t battery_BrickVoltageMax = 0; +static uint16_t battery_BrickVoltageMin = 0; +static uint8_t battery_BrickTempMaxNum = 0; +static uint8_t battery_BrickTempMinNum = 0; +static uint8_t battery_BrickModelTMax = 0; +static uint8_t battery_BrickModelTMin = 0; +static uint8_t battery_BrickVoltageMaxNum = 0; //rename from battery_max_vno +static uint8_t battery_BrickVoltageMinNum = 0; //rename from battery_min_vno +//0x20A: 522 HVP_contactorState static uint8_t battery_contactor = 0; //State of contactor static uint8_t battery_hvil_status = 0; static uint8_t battery_packContNegativeState = 0; static uint8_t battery_packContPositiveState = 0; static uint8_t battery_packContactorSetState = 0; -static uint8_t battery_packCtrsClosingAllowed = 0; -static uint8_t battery_pyroTestInProgress = 0; -//Fault codes -static uint8_t battery_WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. -static uint8_t battery_PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. -static uint8_t battery_SwAssertion = 0; //An internal software assertion has failed. -static uint8_t battery_CrashEvent = 0; //Warns if the crash signal is detected by HVP -static uint8_t battery_OverDchgCurrentFault = 0; //Warns if the pack discharge is above max discharge current limit -static uint8_t battery_OverChargeCurrentFault = - 0; //Warns if the pack discharge current is above max charge current limit -static uint8_t battery_OverCurrentFault = - 0; //Warns if the pack current (discharge or charge) is above max current limit. -static uint8_t battery_OverTemperatureFault = 0; //A pack module temperature is above maximum temperature limit -static uint8_t battery_OverVoltageFault = 0; //A brick voltage is above maximum voltage limit -static uint8_t battery_UnderVoltageFault = 0; //A brick voltage is below minimum voltage limit -static uint8_t battery_PrimaryBmbMiaFault = - 0; //Warns if the voltage and temperature readings from primary BMB chain are mia -static uint8_t battery_SecondaryBmbMiaFault = - 0; //Warns if the voltage and temperature readings from secondary BMB chain are mia -static uint8_t battery_BmbMismatchFault = - 0; //Warns if the primary and secondary BMB chain readings don't match with each other -static uint8_t battery_BmsHviMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN -static uint8_t battery_CpMiaFault = 0; //Warns if the CP node is mia on HVS CAN -static uint8_t battery_PcsMiaFault = 0; //The PCS node is mia on HVS CAN -static uint8_t battery_BmsFault = 0; //Warns if the BMS ECU has faulted -static uint8_t battery_PcsFault = 0; //Warns if the PCS ECU has faulted -static uint8_t battery_CpFault = 0; //Warns if the CP ECU has faulted -static uint8_t battery_ShuntHwMiaFault = 0; //Warns if the shunt current reading is not available -static uint8_t battery_PyroMiaFault = 0; //Warns if the pyro squib is not connected -static uint8_t battery_hvsMiaFault = 0; //Warns if the pack contactor hw fault -static uint8_t battery_hviMiaFault = 0; //Warns if the FC contactor hw fault -static uint8_t battery_Supply12vFault = 0; //Warns if the low voltage (12V) battery is below minimum voltage threshold -static uint8_t battery_VerSupplyFault = - 0; //Warns if the Energy reserve voltage supply is below minimum voltage threshold -static uint8_t battery_HvilFault = 0; //Warn if a High Voltage Inter Lock fault is detected -static uint8_t battery_BmsHvsMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN -static uint8_t battery_PackVoltMismatchFault = - 0; //Warns if the pack voltage doesn't match approximately with sum of brick voltages -static uint8_t battery_EnsMiaFault = 0; //Warns if the ENS line is not connected to HVC -static uint8_t battery_PackPosCtrArcFault = 0; //Warns if the HVP detectes series arc at pack contactor -static uint8_t battery_packNegCtrArcFault = 0; //Warns if the HVP detectes series arc at FC contactor -static uint8_t battery_ShuntHwAndBmsMiaFault = 0; -static uint8_t battery_fcContHwFault = 0; -static uint8_t battery_robinOverVoltageFault = 0; -static uint8_t battery_packContHwFault = 0; -static uint8_t battery_pyroFuseBlown = 0; -static uint8_t battery_pyroFuseFailedToBlow = 0; -static uint8_t battery_CpilFault = 0; -static uint8_t battery_PackContactorFellOpen = 0; -static uint8_t battery_FcContactorFellOpen = 0; -static uint8_t battery_packCtrCloseBlocked = 0; -static uint8_t battery_fcCtrCloseBlocked = 0; -static uint8_t battery_packContactorForceOpen = 0; -static uint8_t battery_fcContactorForceOpen = 0; -static uint8_t battery_dcLinkOverVoltage = 0; -static uint8_t battery_shuntOverTemperature = 0; -static uint8_t battery_passivePyroDeploy = 0; -static uint8_t battery_logUploadRequest = 0; -static uint8_t battery_packCtrCloseFailed = 0; -static uint8_t battery_fcCtrCloseFailed = 0; -static uint8_t battery_shuntThermistorMia = 0; - -#ifdef DOUBLE_BATTERY +static bool battery_packCtrsClosingAllowed = false; // Change to bool +static bool battery_pyroTestInProgress = false; // Change to bool +static bool battery_packCtrsOpenNowRequested = false; // Change to bool +static bool battery_packCtrsOpenRequested = false; // Change to bool +static uint8_t battery_packCtrsRequestStatus = 0; +static bool battery_packCtrsResetRequestRequired = false; // Change to bool +static bool battery_dcLinkAllowedToEnergize = false; // Change to bool +static bool battery_fcContNegativeAuxOpen = false; // Change to bool +static uint8_t battery_fcContNegativeState = 0; +static bool battery_fcContPositiveAuxOpen = false; // Change to bool +static uint8_t battery_fcContPositiveState = 0; +static uint8_t battery_fcContactorSetState = 0; +static bool battery_fcCtrsClosingAllowed = false; // Change to bool +static bool battery_fcCtrsOpenNowRequested = false; // Change to bool +static bool battery_fcCtrsOpenRequested = false; // Change to bool +static uint8_t battery_fcCtrsRequestStatus = 0; +static bool battery_fcCtrsResetRequestRequired = false; // Change to bool +static bool battery_fcLinkAllowedToEnergize = false; // Change to bool +//0x212: 530 BMS_status +static bool battery_BMS_hvacPowerRequest = false; //Change to bool +static bool battery_BMS_notEnoughPowerForDrive = false; //Change to bool +static bool battery_BMS_notEnoughPowerForSupport = false; //Change to bool +static bool battery_BMS_preconditionAllowed = false; //Change to bool +static bool battery_BMS_updateAllowed = false; //Change to bool +static bool battery_BMS_activeHeatingWorthwhile = false; //Change to bool +static bool battery_BMS_cpMiaOnHvs = false; //Change to bool +static uint8_t battery_BMS_contactorState = 0; +static uint8_t battery_BMS_state = 0; +static uint8_t battery_BMS_hvState = 0; +static uint16_t battery_BMS_isolationResistance = 0; +static bool battery_BMS_chargeRequest = false; //Change to bool +static bool battery_BMS_keepWarmRequest = false; //Change to bool +static uint8_t battery_BMS_uiChargeStatus = 0; +static bool battery_BMS_diLimpRequest = false; //Change to bool +static bool battery_BMS_okToShipByAir = false; //Change to bool +static bool battery_BMS_okToShipByLand = false; //Change to bool +static uint32_t battery_BMS_chgPowerAvailable = 0; +static uint8_t battery_BMS_chargeRetryCount = 0; +static bool battery_BMS_pcsPwmEnabled = false; //Change to bool +static bool battery_BMS_ecuLogUploadRequest = false; //Change to bool +static uint8_t battery_BMS_minPackTemperature = 0; +// 0x224:548 PCS_dcdcStatus +static uint8_t battery_PCS_dcdcPrechargeStatus = 0; +static uint8_t battery_PCS_dcdc12VSupportStatus = 0; +static uint8_t battery_PCS_dcdcHvBusDischargeStatus = 0; +static uint16_t battery_PCS_dcdcMainState = 0; +static uint8_t battery_PCS_dcdcSubState = 0; +static bool battery_PCS_dcdcFaulted = false; //Change to bool +static bool battery_PCS_dcdcOutputIsLimited = false; //Change to bool +static uint32_t battery_PCS_dcdcMaxOutputCurrentAllowed = 0; +static uint8_t battery_PCS_dcdcPrechargeRtyCnt = 0; +static uint8_t battery_PCS_dcdc12VSupportRtyCnt = 0; +static uint8_t battery_PCS_dcdcDischargeRtyCnt = 0; +static uint8_t battery_PCS_dcdcPwmEnableLine = 0; +static uint8_t battery_PCS_dcdcSupportingFixedLvTarget = 0; +static uint8_t battery_PCS_ecuLogUploadRequest = 0; +static uint8_t battery_PCS_dcdcPrechargeRestartCnt = 0; +static uint8_t battery_PCS_dcdcInitialPrechargeSubState = 0; +//0x312: 786 BMS_thermalStatus +static uint16_t BMS_powerDissipation = 0; +static uint16_t BMS_flowRequest = 0; +static uint16_t BMS_inletActiveCoolTargetT = 0; +static uint16_t BMS_inletPassiveTargetT = 0; +static uint16_t BMS_inletActiveHeatTargetT = 0; +static uint16_t BMS_packTMin = 0; +static uint16_t BMS_packTMax = 0; +static bool BMS_pcsNoFlowRequest = false; +static bool BMS_noFlowRequest = false; +//0x2A4; 676 PCS_thermalStatus +static int16_t PCS_chgPhATemp = 0; +static int16_t PCS_chgPhBTemp = 0; +static int16_t PCS_chgPhCTemp = 0; +static int16_t PCS_dcdcTemp = 0; +static int16_t PCS_ambientTemp = 0; +//0x2C4; 708 PCS_logging +static uint16_t PCS_logMessageSelect = 0; +static uint16_t PCS_dcdcMaxLvOutputCurrent = 0; +static uint16_t PCS_dcdcCurrentLimit = 0; +static uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0; +static uint16_t PCS_dcdcUnifiedCommand = 0; +static uint16_t PCS_dcdcCLAControllerOutput = 0; +static int16_t PCS_dcdcTankVoltage = 0; +static uint16_t PCS_dcdcTankVoltageTarget = 0; +static uint16_t PCS_dcdcClaCurrentFreq = 0; +static int16_t PCS_dcdcTCommMeasured = 0; +static uint16_t PCS_dcdcShortTimeUs = 0; +static uint16_t PCS_dcdcHalfPeriodUs = 0; +static uint16_t PCS_dcdcIntervalMaxFrequency = 0; +static uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0; +static uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0; +static uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0; +static uint16_t PCS_dcdcIntervalMinFrequency = 0; +static uint16_t PCS_dcdcIntervalMinHvBusVolt = 0; +static uint16_t PCS_dcdcIntervalMinLvBusVolt = 0; +static uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0; +static uint32_t PCS_dcdc12vSupportLifetimekWh = 0; +//0x7AA: //1962 HVP_debugMessage: +static uint8_t HVP_debugMessageMultiplexer = 0; +static bool HVP_gpioPassivePyroDepl = false; //Change to bool +static bool HVP_gpioPyroIsoEn = false; //Change to bool +static bool HVP_gpioCpFaultIn = false; //Change to bool +static bool HVP_gpioPackContPowerEn = false; //Change to bool +static bool HVP_gpioHvCablesOk = false; //Change to bool +static bool HVP_gpioHvpSelfEnable = false; //Change to bool +static bool HVP_gpioLed = false; //Change to bool +static bool HVP_gpioCrashSignal = false; //Change to bool +static bool HVP_gpioShuntDataReady = false; //Change to bool +static bool HVP_gpioFcContPosAux = false; //Change to bool +static bool HVP_gpioFcContNegAux = false; //Change to bool +static bool HVP_gpioBmsEout = false; //Change to bool +static bool HVP_gpioCpFaultOut = false; //Change to bool +static bool HVP_gpioPyroPor = false; //Change to bool +static bool HVP_gpioShuntEn = false; //Change to bool +static bool HVP_gpioHvpVerEn = false; //Change to bool +static bool HVP_gpioPackCoontPosFlywheel = false; //Change to bool +static bool HVP_gpioCpLatchEnable = false; //Change to bool +static bool HVP_gpioPcsEnable = false; //Change to bool +static bool HVP_gpioPcsDcdcPwmEnable = false; //Change to bool +static bool HVP_gpioPcsChargePwmEnable = false; //Change to bool +static bool HVP_gpioFcContPowerEnable = false; //Change to bool +static bool HVP_gpioHvilEnable = false; //Change to bool +static bool HVP_gpioSecDrdy = false; //Change to bool +static uint16_t HVP_hvp1v5Ref = 0; +static int16_t HVP_shuntCurrentDebug = 0; +static bool HVP_packCurrentMia = false; //Change to bool +static bool HVP_auxCurrentMia = false; //Change to bool +static bool HVP_currentSenseMia = false; //Change to bool +static bool HVP_shuntRefVoltageMismatch = false; //Change to bool +static bool HVP_shuntThermistorMia = false; //Change to bool +static bool HVP_shuntHwMia = false; //Change to bool +static int16_t HVP_dcLinkVoltage = 0; +static int16_t HVP_packVoltage = 0; +static int16_t HVP_fcLinkVoltage = 0; +static uint16_t HVP_packContVoltage = 0; +static int16_t HVP_packNegativeV = 0; +static int16_t HVP_packPositiveV = 0; +static uint16_t HVP_pyroAnalog = 0; +static int16_t HVP_dcLinkNegativeV = 0; +static int16_t HVP_dcLinkPositiveV = 0; +static int16_t HVP_fcLinkNegativeV = 0; +static uint16_t HVP_fcContCoilCurrent = 0; +static uint16_t HVP_fcContVoltage = 0; +static uint16_t HVP_hvilInVoltage = 0; +static uint16_t HVP_hvilOutVoltage = 0; +static int16_t HVP_fcLinkPositiveV = 0; +static uint16_t HVP_packContCoilCurrent = 0; +static uint16_t HVP_battery12V = 0; +static int16_t HVP_shuntRefVoltageDbg = 0; +static int16_t HVP_shuntAuxCurrentDbg = 0; +static int16_t HVP_shuntBarTempDbg = 0; +static int16_t HVP_shuntAsicTempDbg = 0; +static uint8_t HVP_shuntAuxCurrentStatus = 0; +static uint8_t HVP_shuntBarTempStatus = 0; +static uint8_t HVP_shuntAsicTempStatus = 0; +//0x3aa: HVP_alertMatrix1 Fault codes // Change to bool +static bool battery_WatchdogReset = false; //Warns if the processor has experienced a reset due to watchdog reset. +static bool battery_PowerLossReset = false; //Warns if the processor has experienced a reset due to power loss. +static bool battery_SwAssertion = false; //An internal software assertion has failed. +static bool battery_CrashEvent = false; //Warns if the crash signal is detected by HVP +static bool battery_OverDchgCurrentFault = false; //Warns if the pack discharge is above max discharge current limit +static bool battery_OverChargeCurrentFault = + false; //Warns if the pack discharge current is above max charge current limit +static bool battery_OverCurrentFault = + false; //Warns if the pack current (discharge or charge) is above max current limit. +static bool battery_OverTemperatureFault = false; //A pack module temperature is above maximum temperature limit +static bool battery_OverVoltageFault = false; //A brick voltage is above maximum voltage limit +static bool battery_UnderVoltageFault = false; //A brick voltage is below minimum voltage limit +static bool battery_PrimaryBmbMiaFault = + false; //Warns if the voltage and temperature readings from primary BMB chain are mia +static bool battery_SecondaryBmbMiaFault = + false; //Warns if the voltage and temperature readings from secondary BMB chain are mia +static bool battery_BmbMismatchFault = + false; //Warns if the primary and secondary BMB chain readings don't match with each other +static bool battery_BmsHviMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN +static bool battery_CpMiaFault = false; //Warns if the CP node is mia on HVS CAN +static bool battery_PcsMiaFault = false; //The PCS node is mia on HVS CAN +static bool battery_BmsFault = false; //Warns if the BMS ECU has faulted +static bool battery_PcsFault = false; //Warns if the PCS ECU has faulted +static bool battery_CpFault = false; //Warns if the CP ECU has faulted +static bool battery_ShuntHwMiaFault = false; //Warns if the shunt current reading is not available +static bool battery_PyroMiaFault = false; //Warns if the pyro squib is not connected +static bool battery_hvsMiaFault = false; //Warns if the pack contactor hw fault +static bool battery_hviMiaFault = false; //Warns if the FC contactor hw fault +static bool battery_Supply12vFault = false; //Warns if the low voltage (12V) battery is below minimum voltage threshold +static bool battery_VerSupplyFault = + false; //Warns if the Energy reserve voltage supply is below minimum voltage threshold +static bool battery_HvilFault = false; //Warn if a High Voltage Inter Lock fault is detected +static bool battery_BmsHvsMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN +static bool battery_PackVoltMismatchFault = + false; //Warns if the pack voltage doesn't match approximately with sum of brick voltages +static bool battery_EnsMiaFault = false; //Warns if the ENS line is not connected to HVC +static bool battery_PackPosCtrArcFault = false; //Warns if the HVP detectes series arc at pack contactor +static bool battery_packNegCtrArcFault = false; //Warns if the HVP detectes series arc at FC contactor +static bool battery_ShuntHwAndBmsMiaFault = false; +static bool battery_fcContHwFault = false; +static bool battery_robinOverVoltageFault = false; +static bool battery_packContHwFault = false; +static bool battery_pyroFuseBlown = false; +static bool battery_pyroFuseFailedToBlow = false; +static bool battery_CpilFault = false; +static bool battery_PackContactorFellOpen = false; +static bool battery_FcContactorFellOpen = false; +static bool battery_packCtrCloseBlocked = false; +static bool battery_fcCtrCloseBlocked = false; +static bool battery_packContactorForceOpen = false; +static bool battery_fcContactorForceOpen = false; +static bool battery_dcLinkOverVoltage = false; +static bool battery_shuntOverTemperature = false; +static bool battery_passivePyroDeploy = false; +static bool battery_logUploadRequest = false; +static bool battery_packCtrCloseFailed = false; +static bool battery_fcCtrCloseFailed = false; +static bool battery_shuntThermistorMia = false; +//0x320: 800 BMS_alertMatrix +static uint8_t battery_BMS_matrixIndex = 0; // Changed to bool +static bool battery_BMS_a061_robinBrickOverVoltage = false; +static bool battery_BMS_a062_SW_BrickV_Imbalance = false; +static bool battery_BMS_a063_SW_ChargePort_Fault = false; +static bool battery_BMS_a064_SW_SOC_Imbalance = false; +static bool battery_BMS_a127_SW_shunt_SNA = false; +static bool battery_BMS_a128_SW_shunt_MIA = false; +static bool battery_BMS_a069_SW_Low_Power = false; +static bool battery_BMS_a130_IO_CAN_Error = false; +static bool battery_BMS_a071_SW_SM_TransCon_Not_Met = false; +static bool battery_BMS_a132_HW_BMB_OTP_Uncorrctbl = false; +static bool battery_BMS_a134_SW_Delayed_Ctr_Off = false; +static bool battery_BMS_a075_SW_Chg_Disable_Failure = false; +static bool battery_BMS_a076_SW_Dch_While_Charging = false; +static bool battery_BMS_a017_SW_Brick_OV = false; +static bool battery_BMS_a018_SW_Brick_UV = false; +static bool battery_BMS_a019_SW_Module_OT = false; +static bool battery_BMS_a021_SW_Dr_Limits_Regulation = false; +static bool battery_BMS_a022_SW_Over_Current = false; +static bool battery_BMS_a023_SW_Stack_OV = false; +static bool battery_BMS_a024_SW_Islanded_Brick = false; +static bool battery_BMS_a025_SW_PwrBalance_Anomaly = false; +static bool battery_BMS_a026_SW_HFCurrent_Anomaly = false; +static bool battery_BMS_a087_SW_Feim_Test_Blocked = false; +static bool battery_BMS_a088_SW_VcFront_MIA_InDrive = false; +static bool battery_BMS_a089_SW_VcFront_MIA = false; +static bool battery_BMS_a090_SW_Gateway_MIA = false; +static bool battery_BMS_a091_SW_ChargePort_MIA = false; +static bool battery_BMS_a092_SW_ChargePort_Mia_On_Hv = false; +static bool battery_BMS_a034_SW_Passive_Isolation = false; +static bool battery_BMS_a035_SW_Isolation = false; +static bool battery_BMS_a036_SW_HvpHvilFault = false; +static bool battery_BMS_a037_SW_Flood_Port_Open = false; +static bool battery_BMS_a158_SW_HVP_HVI_Comms = false; +static bool battery_BMS_a039_SW_DC_Link_Over_Voltage = false; +static bool battery_BMS_a041_SW_Power_On_Reset = false; +static bool battery_BMS_a042_SW_MPU_Error = false; +static bool battery_BMS_a043_SW_Watch_Dog_Reset = false; +static bool battery_BMS_a044_SW_Assertion = false; +static bool battery_BMS_a045_SW_Exception = false; +static bool battery_BMS_a046_SW_Task_Stack_Usage = false; +static bool battery_BMS_a047_SW_Task_Stack_Overflow = false; +static bool battery_BMS_a048_SW_Log_Upload_Request = false; +static bool battery_BMS_a169_SW_FC_Pack_Weld = false; +static bool battery_BMS_a050_SW_Brick_Voltage_MIA = false; +static bool battery_BMS_a051_SW_HVC_Vref_Bad = false; +static bool battery_BMS_a052_SW_PCS_MIA = false; +static bool battery_BMS_a053_SW_ThermalModel_Sanity = false; +static bool battery_BMS_a054_SW_Ver_Supply_Fault = false; +static bool battery_BMS_a176_SW_GracefulPowerOff = false; +static bool battery_BMS_a059_SW_Pack_Voltage_Sensing = false; +static bool battery_BMS_a060_SW_Leakage_Test_Failure = false; +static bool battery_BMS_a077_SW_Charger_Regulation = false; +static bool battery_BMS_a081_SW_Ctr_Close_Blocked = false; +static bool battery_BMS_a082_SW_Ctr_Force_Open = false; +static bool battery_BMS_a083_SW_Ctr_Close_Failure = false; +static bool battery_BMS_a084_SW_Sleep_Wake_Aborted = false; +static bool battery_BMS_a094_SW_Drive_Inverter_MIA = false; +static bool battery_BMS_a099_SW_BMB_Communication = false; +static bool battery_BMS_a105_SW_One_Module_Tsense = false; +static bool battery_BMS_a106_SW_All_Module_Tsense = false; +static bool battery_BMS_a107_SW_Stack_Voltage_MIA = false; +static bool battery_BMS_a121_SW_NVRAM_Config_Error = false; +static bool battery_BMS_a122_SW_BMS_Therm_Irrational = false; +static bool battery_BMS_a123_SW_Internal_Isolation = false; +static bool battery_BMS_a129_SW_VSH_Failure = false; +static bool battery_BMS_a131_Bleed_FET_Failure = false; +static bool battery_BMS_a136_SW_Module_OT_Warning = false; +static bool battery_BMS_a137_SW_Brick_UV_Warning = false; +static bool battery_BMS_a138_SW_Brick_OV_Warning = false; +static bool battery_BMS_a139_SW_DC_Link_V_Irrational = false; +static bool battery_BMS_a141_SW_BMB_Status_Warning = false; +static bool battery_BMS_a144_Hvp_Config_Mismatch = false; +static bool battery_BMS_a145_SW_SOC_Change = false; +static bool battery_BMS_a146_SW_Brick_Overdischarged = false; +static bool battery_BMS_a149_SW_Missing_Config_Block = false; +static bool battery_BMS_a151_SW_external_isolation = false; +static bool battery_BMS_a156_SW_BMB_Vref_bad = false; +static bool battery_BMS_a157_SW_HVP_HVS_Comms = false; +static bool battery_BMS_a159_SW_HVP_ECU_Error = false; +static bool battery_BMS_a161_SW_DI_Open_Request = false; +static bool battery_BMS_a162_SW_No_Power_For_Support = false; +static bool battery_BMS_a163_SW_Contactor_Mismatch = false; +static bool battery_BMS_a164_SW_Uncontrolled_Regen = false; +static bool battery_BMS_a165_SW_Pack_Partial_Weld = false; +static bool battery_BMS_a166_SW_Pack_Full_Weld = false; +static bool battery_BMS_a167_SW_FC_Partial_Weld = false; +static bool battery_BMS_a168_SW_FC_Full_Weld = false; +static bool battery_BMS_a170_SW_Limp_Mode = false; +static bool battery_BMS_a171_SW_Stack_Voltage_Sense = false; +static bool battery_BMS_a174_SW_Charge_Failure = false; +static bool battery_BMS_a179_SW_Hvp_12V_Fault = false; +static bool battery_BMS_a180_SW_ECU_reset_blocked = false; + +#ifdef DOUBLE_BATTERY //need to update for battery2 + +static uint16_t battery2_cell_max_v = 3300; +static uint16_t battery2_cell_min_v = 3300; +static uint16_t battery2_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV +static bool battery2_cellvoltagesRead = false; +//0x3d2: 978 BMS_kwhCounter static uint32_t battery2_total_discharge = 0; static uint32_t battery2_total_charge = 0; -static uint16_t battery2_volts = 0; // V -static int16_t battery2_amps = 0; // A -static uint16_t battery2_raw_amps = 0; // A -static int16_t battery2_max_temp = 0; // C* -static int16_t battery2_min_temp = 0; // C* +//0x352: 850 BMS_energyStatus static uint16_t battery2_energy_buffer = 0; +static uint16_t battery2_energy_buffer_m1 = 0; // kWh static uint16_t battery2_energy_to_charge_complete = 0; +static uint16_t battery2_energy_to_charge_complete_m1 = 0; // kWh static uint16_t battery2_expected_energy_remaining = 0; -static uint8_t battery2_full_charge_complete = 0; +static uint16_t battery2_expected_energy_remaining_m1 = 0; // kWh +static bool battery2_full_charge_complete = false; +static bool battery2_fully_charged = false; static uint16_t battery2_ideal_energy_remaining = 0; +static uint16_t battery2_ideal_energy_remaining_m0 = 0; // kWh static uint16_t battery2_nominal_energy_remaining = 0; -static uint16_t battery2_nominal_full_pack_energy = 600; -static uint16_t battery2_beginning_of_life = 600; +static uint16_t battery2_nominal_energy_remaining_m0 = 0; // kWh +static uint16_t battery2_nominal_full_pack_energy = 0; +static uint16_t battery2_nominal_full_pack_energy_m0 = 0; // Kwh +//0x132 306 HVBattAmpVolt +static uint16_t battery2_volts = 0; // V +static int16_t battery2_amps = 0; // A +static int16_t battery2_raw_amps = 0; // A static uint16_t battery2_charge_time_remaining = 0; // Minutes -static uint16_t battery2_regenerative_limit = 0; -static uint16_t battery2_discharge_limit = 0; -static uint16_t battery2_max_heat_park = 0; -static uint16_t battery2_hvac_max_power = 0; +//0x252 594 BMS_powerAvailable +static uint16_t BMS2_maxRegenPower = 0; //rename from battery_regenerative_limit +static uint16_t BMS2_maxDischargePower = 0; // rename from battery_discharge_limit +static uint16_t BMS2_maxStationaryHeatPower = 0; //rename from battery_max_heat_park +static uint16_t BMS2_hvacPowerBudget = 0; //rename from battery_hvac_max_power +static uint8_t BMS2_notEnoughPowerForHeatPump = 0; +static uint8_t BMS2_powerLimitState = 0; +static uint8_t BMS2_inverterTQF = 0; +//0x2d2: 722 BMSVAlimits static uint16_t battery2_max_discharge_current = 0; static uint16_t battery2_max_charge_current = 0; static uint16_t battery2_bms_max_voltage = 0; static uint16_t battery2_bms_min_voltage = 0; -static uint16_t battery2_high_voltage = 0; -static uint16_t battery2_low_voltage = 0; -static uint16_t battery2_output_current = 0; +//0x2b4: 692 PCS_dcdcRailStatus +static uint16_t battery2_dcdcHvBusVolt = 0; //update name +static uint16_t battery2_dcdcLvBusVolt = 0; //update name +static uint16_t battery2_dcdcLvOutputCurrent = 0; //update name +//0x292: 658 BMS_socStatus +static uint16_t battery2_beginning_of_life = 0; static uint16_t battery2_soc_min = 0; static uint16_t battery2_soc_max = 0; -static uint16_t battery2_soc_vi = 0; +static uint16_t battery2_soc_ui = 0; static uint16_t battery2_soc_ave = 0; -static uint16_t battery2_cell_max_v = 3700; -static uint16_t battery2_cell_min_v = 3700; -static uint16_t battery2_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV -static uint8_t battery2_max_vno = 0; -static uint8_t battery2_min_vno = 0; +static uint8_t battery2_battTempPct = 0; +//0x392: BMS_packConfig +static uint32_t battery2_packMass = 0; +static uint32_t battery2_platformMaxBusVoltage = 0; +static uint32_t battery2_packConfigMultiplexer = 0; +static uint32_t battery2_moduleType = 0; +static uint32_t battery2_reservedConfig = 0; +//0x332: 818 BattBrickMinMax:BMS_bmbMinMax +static int16_t battery2_max_temp = 0; // C* +static int16_t battery2_min_temp = 0; // C* +static uint16_t battery2_BrickVoltageMax = 0; +static uint16_t battery2_BrickVoltageMin = 0; +static uint8_t battery2_BrickTempMaxNum = 0; +static uint8_t battery2_BrickTempMinNum = 0; +static uint8_t battery2_BrickModelTMax = 0; +static uint8_t battery2_BrickModelTMin = 0; +static uint8_t battery2_BrickVoltageMaxNum = 0; //rename from battery_max_vno +static uint8_t battery2_BrickVoltageMinNum = 0; //rename from battery_min_vno +//0x20A: 522 HVP_contactorState static uint8_t battery2_contactor = 0; //State of contactor static uint8_t battery2_hvil_status = 0; static uint8_t battery2_packContNegativeState = 0; static uint8_t battery2_packContPositiveState = 0; static uint8_t battery2_packContactorSetState = 0; -static uint8_t battery2_packCtrsClosingAllowed = 0; -static uint8_t battery2_pyroTestInProgress = 0; -//Fault codes -static uint8_t battery2_WatchdogReset = 0; //Warns if the processor has experienced a reset due to watchdog reset. -static uint8_t battery2_PowerLossReset = 0; //Warns if the processor has experienced a reset due to power loss. -static uint8_t battery2_SwAssertion = 0; //An internal software assertion has failed. -static uint8_t battery2_CrashEvent = 0; //Warns if the crash signal is detected by HVP -static uint8_t battery2_OverDchgCurrentFault = 0; //Warns if the pack discharge is above max discharge current limit -static uint8_t battery2_OverChargeCurrentFault = - 0; //Warns if the pack discharge current is above max charge current limit -static uint8_t battery2_OverCurrentFault = - 0; //Warns if the pack current (discharge or charge) is above max current limit. -static uint8_t battery2_OverTemperatureFault = 0; //A pack module temperature is above maximum temperature limit -static uint8_t battery2_OverVoltageFault = 0; //A brick voltage is above maximum voltage limit -static uint8_t battery2_UnderVoltageFault = 0; //A brick voltage is below minimum voltage limit -static uint8_t battery2_PrimaryBmbMiaFault = - 0; //Warns if the voltage and temperature readings from primary BMB chain are mia -static uint8_t battery2_SecondaryBmbMiaFault = - 0; //Warns if the voltage and temperature readings from secondary BMB chain are mia -static uint8_t battery2_BmbMismatchFault = - 0; //Warns if the primary and secondary BMB chain readings don't match with each other -static uint8_t battery2_BmsHviMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN -static uint8_t battery2_CpMiaFault = 0; //Warns if the CP node is mia on HVS CAN -static uint8_t battery2_PcsMiaFault = 0; //The PCS node is mia on HVS CAN -static uint8_t battery2_BmsFault = 0; //Warns if the BMS ECU has faulted -static uint8_t battery2_PcsFault = 0; //Warns if the PCS ECU has faulted -static uint8_t battery2_CpFault = 0; //Warns if the CP ECU has faulted -static uint8_t battery2_ShuntHwMiaFault = 0; //Warns if the shunt current reading is not available -static uint8_t battery2_PyroMiaFault = 0; //Warns if the pyro squib is not connected -static uint8_t battery2_hvsMiaFault = 0; //Warns if the pack contactor hw fault -static uint8_t battery2_hviMiaFault = 0; //Warns if the FC contactor hw fault -static uint8_t battery2_Supply12vFault = 0; //Warns if the low voltage (12V) battery is below minimum voltage threshold -static uint8_t battery2_VerSupplyFault = - 0; //Warns if the Energy reserve voltage supply is below minimum voltage threshold -static uint8_t battery2_HvilFault = 0; //Warn if a High Voltage Inter Lock fault is detected -static uint8_t battery2_BmsHvsMiaFault = 0; //Warns if the BMS node is mia on HVS or HVI CAN -static uint8_t battery2_PackVoltMismatchFault = - 0; //Warns if the pack voltage doesn't match approximately with sum of brick voltages -static uint8_t battery2_EnsMiaFault = 0; //Warns if the ENS line is not connected to HVC -static uint8_t battery2_PackPosCtrArcFault = 0; //Warns if the HVP detectes series arc at pack contactor -static uint8_t battery2_packNegCtrArcFault = 0; //Warns if the HVP detectes series arc at FC contactor -static uint8_t battery2_ShuntHwAndBmsMiaFault = 0; -static uint8_t battery2_fcContHwFault = 0; -static uint8_t battery2_robinOverVoltageFault = 0; -static uint8_t battery2_packContHwFault = 0; -static uint8_t battery2_pyroFuseBlown = 0; -static uint8_t battery2_pyroFuseFailedToBlow = 0; -static uint8_t battery2_CpilFault = 0; -static uint8_t battery2_PackContactorFellOpen = 0; -static uint8_t battery2_FcContactorFellOpen = 0; -static uint8_t battery2_packCtrCloseBlocked = 0; -static uint8_t battery2_fcCtrCloseBlocked = 0; -static uint8_t battery2_packContactorForceOpen = 0; -static uint8_t battery2_fcContactorForceOpen = 0; -static uint8_t battery2_dcLinkOverVoltage = 0; -static uint8_t battery2_shuntOverTemperature = 0; -static uint8_t battery2_passivePyroDeploy = 0; -static uint8_t battery2_logUploadRequest = 0; -static uint8_t battery2_packCtrCloseFailed = 0; -static uint8_t battery2_fcCtrCloseFailed = 0; -static uint8_t battery2_shuntThermistorMia = 0; +static bool battery2_packCtrsClosingAllowed = false; +static bool battery2_pyroTestInProgress = false; +static bool battery2_packCtrsOpenNowRequested = false; +static bool battery2_packCtrsOpenRequested = false; +static uint8_t battery2_packCtrsRequestStatus = 0; +static bool battery2_packCtrsResetRequestRequired = false; +static bool battery2_dcLinkAllowedToEnergize = false; +static bool battery2_fcContNegativeAuxOpen = false; +static uint8_t battery2_fcContNegativeState = 0; +static bool battery2_fcContPositiveAuxOpen = false; +static uint8_t battery2_fcContPositiveState = 0; +static uint8_t battery2_fcContactorSetState = 0; +static bool battery2_fcCtrsClosingAllowed = false; +static bool battery2_fcCtrsOpenNowRequested = false; +static bool battery2_fcCtrsOpenRequested = false; +static uint8_t battery2_fcCtrsRequestStatus = 0; +static bool battery2_fcCtrsResetRequestRequired = false; +static bool battery2_fcLinkAllowedToEnergize = false; +//0x212: 530 BMS_status +static bool battery2_BMS_hvacPowerRequest = false; +static bool battery2_BMS_notEnoughPowerForDrive = false; +static bool battery2_BMS_notEnoughPowerForSupport = false; +static bool battery2_BMS_preconditionAllowed = false; +static bool battery2_BMS_updateAllowed = false; +static bool battery2_BMS_activeHeatingWorthwhile = false; +static bool battery2_BMS_cpMiaOnHvs = false; +static uint8_t battery2_BMS_contactorState = 0; +static uint8_t battery2_BMS_state = 0; +static uint8_t battery2_BMS_hvState = 0; +static uint16_t battery2_BMS_isolationResistance = 0; +static bool battery2_BMS_chargeRequest = false; +static bool battery2_BMS_keepWarmRequest = false; +static uint8_t battery2_BMS_uiChargeStatus = 0; +static bool battery2_BMS_diLimpRequest = false; +static bool battery2_BMS_okToShipByAir = false; +static bool battery2_BMS_okToShipByLand = false; +static uint32_t battery2_BMS_chgPowerAvailable = 0; +static uint8_t battery2_BMS_chargeRetryCount = 0; +static bool battery2_BMS_pcsPwmEnabled = false; +static bool battery2_BMS_ecuLogUploadRequest = false; +static uint8_t battery2_BMS_minPackTemperature = 0; +// 0x224:548 PCS_dcdcStatus +static uint8_t battery2_PCS_dcdcPrechargeStatus = 0; +static uint8_t battery2_PCS_dcdc12VSupportStatus = 0; +static uint8_t battery2_PCS_dcdcHvBusDischargeStatus = 0; +static uint16_t battery2_PCS_dcdcMainState = 0; +static uint8_t battery2_PCS_dcdcSubState = 0; +static bool battery2_PCS_dcdcFaulted = false; +static bool battery2_PCS_dcdcOutputIsLimited = false; +static uint32_t battery2_PCS_dcdcMaxOutputCurrentAllowed = 0; +static uint8_t battery2_PCS_dcdcPrechargeRtyCnt = 0; +static uint8_t battery2_PCS_dcdc12VSupportRtyCnt = 0; +static uint8_t battery2_PCS_dcdcDischargeRtyCnt = 0; +static uint8_t battery2_PCS_dcdcPwmEnableLine = 0; +static uint8_t battery2_PCS_dcdcSupportingFixedLvTarget = 0; +static uint8_t battery2_PCS_ecuLogUploadRequest = 0; +static uint8_t battery2_PCS_dcdcPrechargeRestartCnt = 0; +static uint8_t battery2_PCS_dcdcInitialPrechargeSubState = 0; +//0x312: 786 BMS_thermalStatus +static uint16_t BMS2_powerDissipation = 0; +static uint16_t BMS2_flowRequest = 0; +static uint16_t BMS2_inletActiveCoolTargetT = 0; +static uint16_t BMS2_inletPassiveTargetT = 0; +static uint16_t BMS2_inletActiveHeatTargetT = 0; +static uint16_t BMS2_packTMin = 0; +static uint16_t BMS2_packTMax = 0; +static bool BMS2_pcsNoFlowRequest = false; +static bool BMS2_noFlowRequest = false; +//0x2A4; 676 PCS_thermalStatus +static int16_t PCS2_chgPhATemp = 0; +static int16_t PCS2_chgPhBTemp = 0; +static int16_t PCS2_chgPhCTemp = 0; +static int16_t PCS2_dcdcTemp = 0; +static int16_t PCS2_ambientTemp = 0; +//0x2C4; 708 PCS_logging +static uint16_t PCS2_logMessageSelect = 0; +static uint16_t PCS2_dcdcMaxLvOutputCurrent = 0; +static uint16_t PCS2_dcdcCurrentLimit = 0; +static uint16_t PCS2_dcdcLvOutputCurrentTempLimit = 0; +static uint16_t PCS2_dcdcUnifiedCommand = 0; +static uint16_t PCS2_dcdcCLAControllerOutput = 0; +static int16_t PCS2_dcdcTankVoltage = 0; +static uint16_t PCS2_dcdcTankVoltageTarget = 0; +static uint16_t PCS2_dcdcClaCurrentFreq = 0; +static int16_t PCS2_dcdcTCommMeasured = 0; +static uint16_t PCS2_dcdcShortTimeUs = 0; +static uint16_t PCS2_dcdcHalfPeriodUs = 0; +static uint16_t PCS2_dcdcIntervalMaxFrequency = 0; +static uint16_t PCS2_dcdcIntervalMaxHvBusVolt = 0; +static uint16_t PCS2_dcdcIntervalMaxLvBusVolt = 0; +static uint16_t PCS2_dcdcIntervalMaxLvOutputCurr = 0; +static uint16_t PCS2_dcdcIntervalMinFrequency = 0; +static uint16_t PCS2_dcdcIntervalMinHvBusVolt = 0; +static uint16_t PCS2_dcdcIntervalMinLvBusVolt = 0; +static uint16_t PCS2_dcdcIntervalMinLvOutputCurr = 0; +static uint32_t PCS2_dcdc12vSupportLifetimekWh = 0; +//0x7AA: //1962 HVP_debugMessage: +static uint8_t HVP2_debugMessageMultiplexer = 0; +static bool HVP2_gpioPassivePyroDepl = false; +static bool HVP2_gpioPyroIsoEn = false; +static bool HVP2_gpioCpFaultIn = false; +static bool HVP2_gpioPackContPowerEn = false; +static bool HVP2_gpioHvCablesOk = false; +static bool HVP2_gpioHVPSelfEnable = false; +static bool HVP2_gpioLed = false; +static bool HVP2_gpioCrashSignal = false; +static bool HVP2_gpioShuntDataReady = false; +static bool HVP2_gpioFcContPosAux = false; +static bool HVP2_gpioFcContNegAux = false; +static bool HVP2_gpioBmsEout = false; +static bool HVP2_gpioCpFaultOut = false; +static bool HVP2_gpioPyroPor = false; +static bool HVP2_gpioShuntEn = false; +static bool HVP2_gpioHVPVerEn = false; +static bool HVP2_gpioPackCoontPosFlywheel = false; +static bool HVP2_gpioCpLatchEnable = false; +static bool HVP2_gpioPcsEnable = false; +static bool HVP2_gpioPcsDcdcPwmEnable = false; +static bool HVP2_gpioPcsChargePwmEnable = false; +static bool HVP2_gpioFcContPowerEnable = false; +static bool HVP2_gpioHvilEnable = false; +static bool HVP2_gpioSecDrdy = false; +static uint16_t HVP2_hvp1v5Ref = 0; +static int16_t HVP2_shuntCurrentDebug = 0; +static bool HVP2_packCurrentMia = false; +static bool HVP2_auxCurrentMia = false; +static bool HVP2_currentSenseMia = false; +static bool HVP2_shuntRefVoltageMismatch = false; +static bool HVP2_shuntThermistorMia = false; +static bool HVP2_shuntHwMia = false; +static int16_t HVP2_dcLinkVoltage = 0; +static int16_t HVP2_packVoltage = 0; +static int16_t HVP2_fcLinkVoltage = 0; +static uint16_t HVP2_packContVoltage = 0; +static int16_t HVP2_packNegativeV = 0; +static int16_t HVP2_packPositiveV = 0; +static uint16_t HVP2_pyroAnalog = 0; +static int16_t HVP2_dcLinkNegativeV = 0; +static int16_t HVP2_dcLinkPositiveV = 0; +static int16_t HVP2_fcLinkNegativeV = 0; +static uint16_t HVP2_fcContCoilCurrent = 0; +static uint16_t HVP2_fcContVoltage = 0; +static uint16_t HVP2_hvilInVoltage = 0; +static uint16_t HVP2_hvilOutVoltage = 0; +static int16_t HVP2_fcLinkPositiveV = 0; +static uint16_t HVP2_packContCoilCurrent = 0; +static uint16_t HVP2_battery12V = 0; +static int16_t HVP2_shuntRefVoltageDbg = 0; +static int16_t HVP2_shuntAuxCurrentDbg = 0; +static int16_t HVP2_shuntBarTempDbg = 0; +static int16_t HVP2_shuntAsicTempDbg = 0; +static uint8_t HVP2_shuntAuxCurrentStatus = 0; +static uint8_t HVP2_shuntBarTempStatus = 0; +static uint8_t HVP2_shuntAsicTempStatus = 0; +//0x3aa: HVP_alertMatrix1 Fault codes +static bool battery2_WatchdogReset = false; //Warns if the processor has experienced a reset due to watchdog reset. +static bool battery2_PowerLossReset = false; //Warns if the processor has experienced a reset due to power loss. +static bool battery2_SwAssertion = false; //An internal software assertion has failed. +static bool battery2_CrashEvent = false; //Warns if the crash signal is detected by HVP +static bool battery2_OverDchgCurrentFault = false; //Warns if the pack discharge is above max discharge current limit +static bool battery2_OverChargeCurrentFault = + false; //Warns if the pack discharge current is above max charge current limit +static bool battery2_OverCurrentFault = + false; //Warns if the pack current (discharge or charge) is above max current limit. +static bool battery2_OverTemperatureFault = false; //A pack module temperature is above maximum temperature limit +static bool battery2_OverVoltageFault = false; //A brick voltage is above maximum voltage limit +static bool battery2_UnderVoltageFault = false; //A brick voltage is below minimum voltage limit +static bool battery2_PrimaryBmbMiaFault = + false; //Warns if the voltage and temperature readings from primary BMB chain are mia +static bool battery2_SecondaryBmbMiaFault = + false; //Warns if the voltage and temperature readings from secondary BMB chain are mia +static bool battery2_BmbMismatchFault = + false; //Warns if the primary and secondary BMB chain readings don't match with each other +static bool battery2_BmsHviMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN +static bool battery2_CpMiaFault = false; //Warns if the CP node is mia on HVS CAN +static bool battery2_PcsMiaFault = false; //The PCS node is mia on HVS CAN +static bool battery2_BmsFault = false; //Warns if the BMS ECU has faulted +static bool battery2_PcsFault = false; //Warns if the PCS ECU has faulted +static bool battery2_CpFault = false; //Warns if the CP ECU has faulted +static bool battery2_ShuntHwMiaFault = false; //Warns if the shunt current reading is not available +static bool battery2_PyroMiaFault = false; //Warns if the pyro squib is not connected +static bool battery2_hvsMiaFault = false; //Warns if the pack contactor hw fault +static bool battery2_hviMiaFault = false; //Warns if the FC contactor hw fault +static bool battery2_Supply12vFault = + false; //Warns if the low voltage (12V) battery is below minimum voltage threshold +static bool battery2_VerSupplyFault = + false; //Warns if the Energy reserve voltage supply is below minimum voltage threshold +static bool battery2_HvilFault = false; //Warn if a High Voltage Inter Lock fault is detected +static bool battery2_BmsHvsMiaFault = false; //Warns if the BMS node is mia on HVS or HVI CAN +static bool battery2_PackVoltMismatchFault = + false; //Warns if the pack voltage doesn't match approximately with sum of brick voltages +static bool battery2_EnsMiaFault = false; //Warns if the ENS line is not connected to HVC +static bool battery2_PackPosCtrArcFault = false; //Warns if the HVP detectes series arc at pack contactor +static bool battery2_packNegCtrArcFault = false; //Warns if the HVP detectes series arc at FC contactor +static bool battery2_ShuntHwAndBmsMiaFault = false; +static bool battery2_fcContHwFault = false; +static bool battery2_robinOverVoltageFault = false; +static bool battery2_packContHwFault = false; +static bool battery2_pyroFuseBlown = false; +static bool battery2_pyroFuseFailedToBlow = false; +static bool battery2_CpilFault = false; +static bool battery2_PackContactorFellOpen = false; +static bool battery2_FcContactorFellOpen = false; +static bool battery2_packCtrCloseBlocked = false; +static bool battery2_fcCtrCloseBlocked = false; +static bool battery2_packContactorForceOpen = false; +static bool battery2_fcContactorForceOpen = false; +static bool battery2_dcLinkOverVoltage = false; +static bool battery2_shuntOverTemperature = false; +static bool battery2_passivePyroDeploy = false; +static bool battery2_logUploadRequest = false; +static bool battery2_packCtrCloseFailed = false; +static bool battery2_fcCtrCloseFailed = false; +static bool battery2_shuntThermistorMia = false; +//0x320: 800 BMS_alertMatrix +static uint8_t battery2_BMS_matrixIndex = 4; +static bool battery2_BMS_a061_robinBrickOverVoltage = false; +static bool battery2_BMS_a062_SW_BrickV_Imbalance = false; +static bool battery2_BMS_a063_SW_ChargePort_Fault = false; +static bool battery2_BMS_a064_SW_SOC_Imbalance = false; +static bool battery2_BMS_a127_SW_shunt_SNA = false; +static bool battery2_BMS_a128_SW_shunt_MIA = false; +static bool battery2_BMS_a069_SW_Low_Power = false; +static bool battery2_BMS_a130_IO_CAN_Error = false; +static bool battery2_BMS_a071_SW_SM_TransCon_Not_Met = false; +static bool battery2_BMS_a132_HW_BMB_OTP_Uncorrctbl = false; +static bool battery2_BMS_a134_SW_Delayed_Ctr_Off = false; +static bool battery2_BMS_a075_SW_Chg_Disable_Failure = false; +static bool battery2_BMS_a076_SW_Dch_While_Charging = false; +static bool battery2_BMS_a017_SW_Brick_OV = false; +static bool battery2_BMS_a018_SW_Brick_UV = false; +static bool battery2_BMS_a019_SW_Module_OT = false; +static bool battery2_BMS_a021_SW_Dr_Limits_Regulation = false; +static bool battery2_BMS_a022_SW_Over_Current = false; +static bool battery2_BMS_a023_SW_Stack_OV = false; +static bool battery2_BMS_a024_SW_Islanded_Brick = false; +static bool battery2_BMS_a025_SW_PwrBalance_Anomaly = false; +static bool battery2_BMS_a026_SW_HFCurrent_Anomaly = false; +static bool battery2_BMS_a087_SW_Feim_Test_Blocked = false; +static bool battery2_BMS_a088_SW_VcFront_MIA_InDrive = false; +static bool battery2_BMS_a089_SW_VcFront_MIA = false; +static bool battery2_BMS_a090_SW_Gateway_MIA = false; +static bool battery2_BMS_a091_SW_ChargePort_MIA = false; +static bool battery2_BMS_a092_SW_ChargePort_Mia_On_Hv = false; +static bool battery2_BMS_a034_SW_Passive_Isolation = false; +static bool battery2_BMS_a035_SW_Isolation = false; +static bool battery2_BMS_a036_SW_HvpHvilFault = false; +static bool battery2_BMS_a037_SW_Flood_Port_Open = false; +static bool battery2_BMS_a158_SW_HVP_HVI_Comms = false; +static bool battery2_BMS_a039_SW_DC_Link_Over_Voltage = 1; +static bool battery2_BMS_a041_SW_Power_On_Reset = false; +static bool battery2_BMS_a042_SW_MPU_Error = false; +static bool battery2_BMS_a043_SW_Watch_Dog_Reset = false; +static bool battery2_BMS_a044_SW_Assertion = false; +static bool battery2_BMS_a045_SW_Exception = false; +static bool battery2_BMS_a046_SW_Task_Stack_Usage = false; +static bool battery2_BMS_a047_SW_Task_Stack_Overflow = false; +static bool battery2_BMS_a048_SW_Log_Upload_Request = false; +static bool battery2_BMS_a169_SW_FC_Pack_Weld = false; +static bool battery2_BMS_a050_SW_Brick_Voltage_MIA = false; +static bool battery2_BMS_a051_SW_HVC_Vref_Bad = false; +static bool battery2_BMS_a052_SW_PCS_MIA = false; +static bool battery2_BMS_a053_SW_ThermalModel_Sanity = false; +static bool battery2_BMS_a054_SW_Ver_Supply_Fault = false; +static bool battery2_BMS_a176_SW_GracefulPowerOff = false; +static bool battery2_BMS_a059_SW_Pack_Voltage_Sensing = false; +static bool battery2_BMS_a060_SW_Leakage_Test_Failure = false; +static bool battery2_BMS_a077_SW_Charger_Regulation = false; +static bool battery2_BMS_a081_SW_Ctr_Close_Blocked = false; +static bool battery2_BMS_a082_SW_Ctr_Force_Open = false; +static bool battery2_BMS_a083_SW_Ctr_Close_Failure = false; +static bool battery2_BMS_a084_SW_Sleep_Wake_Aborted = false; +static bool battery2_BMS_a094_SW_Drive_Inverter_MIA = false; +static bool battery2_BMS_a099_SW_BMB_Communication = false; +static bool battery2_BMS_a105_SW_One_Module_Tsense = false; +static bool battery2_BMS_a106_SW_All_Module_Tsense = false; +static bool battery2_BMS_a107_SW_Stack_Voltage_MIA = false; +static bool battery2_BMS_a121_SW_NVRAM_Config_Error = false; +static bool battery2_BMS_a122_SW_BMS_Therm_Irrational = false; +static bool battery2_BMS_a123_SW_Internal_Isolation = false; +static bool battery2_BMS_a129_SW_VSH_Failure = false; +static bool battery2_BMS_a131_Bleed_FET_Failure = false; +static bool battery2_BMS_a136_SW_Module_OT_Warning = false; +static bool battery2_BMS_a137_SW_Brick_UV_Warning = false; +static bool battery2_BMS_a138_SW_Brick_OV_Warning = false; +static bool battery2_BMS_a139_SW_DC_Link_V_Irrational = false; +static bool battery2_BMS_a141_SW_BMB_Status_Warning = false; +static bool battery2_BMS_a144_Hvp_Config_Mismatch = false; +static bool battery2_BMS_a145_SW_SOC_Change = false; +static bool battery2_BMS_a146_SW_Brick_Overdischarged = false; +static bool battery2_BMS_a149_SW_Missing_Config_Block = false; +static bool battery2_BMS_a151_SW_external_isolation = false; +static bool battery2_BMS_a156_SW_BMB_Vref_bad = false; +static bool battery2_BMS_a157_SW_HVP_HVS_Comms = false; +static bool battery2_BMS_a159_SW_HVP_ECU_Error = false; +static bool battery2_BMS_a16false_SW_DI_Open_Request = false; +static bool battery2_BMS_a162_SW_No_Power_For_Support = false; +static bool battery2_BMS_a163_SW_Contactor_Mismatch = false; +static bool battery2_BMS_a164_SW_Uncontrolled_Regen = false; +static bool battery2_BMS_a165_SW_Pack_Partial_Weld = false; +static bool battery2_BMS_a166_SW_Pack_Full_Weld = false; +static bool battery2_BMS_a167_SW_FC_Partial_Weld = false; +static bool battery2_BMS_a168_SW_FC_Full_Weld = false; +static bool battery2_BMS_a170_SW_Limp_Mode = false; +static bool battery2_BMS_a171_SW_Stack_Voltage_Sense = false; +static bool battery2_BMS_a174_SW_Charge_Failure = false; +static bool battery2_BMS_a179_SW_Hvp_12V_Fault = false; +static bool battery2_BMS_a180_SW_ECU_reset_blocked = false; + #endif //DOUBLE_BATTERY static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", @@ -254,12 +823,16 @@ static const char* hvilStatusState[] = {"NOT OK", "UNKNOWN(14)", "UNKNOWN(15)"}; +void clearIsolationFault() { + //CAN UDS messages to clear a latched isolation fault +} + void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus //After values are mapped, we perform some safety checks, and do some serial printouts datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% - datalayer.battery.status.real_soc = (battery_soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 + 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) @@ -277,12 +850,12 @@ void update_values_battery() { //This function maps all the values fetched via } //The allowed charge power behaves strangely. We instead estimate this value - if (battery_soc_vi > 990) { + if (battery_soc_ui > 990) { datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W; - } else if (battery_soc_vi > + } else if (battery_soc_ui > 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_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); + RAMPDOWNPOWERALLOWED * (1 - (battery_soc_ui - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); //If the cellvoltages start to reach overvoltage, only allow a small amount of power in if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { if (battery_cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { @@ -318,7 +891,7 @@ void update_values_battery() { //This function maps all the values fetched via #ifdef TESLA_MODEL_3Y_BATTERY // Autodetect algoritm for chemistry on 3/Y packs. - // NCM/A batteries have 96s, LFP has 102-106s + // NCM/A batteries have 96s, LFP has 102-108s // Drawback with this check is that it takes 3-5minutes before all cells have been counted! if (datalayer.battery.info.number_of_cells > 101) { datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; @@ -338,9 +911,26 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM; } + + // During forced balancing request via webserver, we allow the battery to exceed normal safety parameters + if (datalayer.battery.settings.user_requests_balancing) { + datalayer.battery.status.real_soc = 9900; //Force battery to show up as 99% when balancing + datalayer.battery.info.max_design_voltage_dV = datalayer.battery.settings.balancing_max_pack_voltage_dV; + datalayer.battery.info.max_cell_voltage_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV; + datalayer.battery.info.max_cell_voltage_deviation_mV = + datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV; + datalayer.battery.status.max_charge_power_W = datalayer.battery.settings.balancing_float_power_W; + } #endif // TESLA_MODEL_3Y_BATTERY + // Check if user requests some action + if (datalayer.battery.settings.user_requests_isolation_clear) { + stateMachineClearIsolationFault = 0; //Start the statemachine + datalayer.battery.settings.user_requests_isolation_clear = false; + } + // Update webserver datalayer + //0x20A datalayer_extended.tesla.status_contactor = battery_contactor; datalayer_extended.tesla.hvil_status = battery_hvil_status; datalayer_extended.tesla.packContNegativeState = battery_packContNegativeState; @@ -348,138 +938,433 @@ void update_values_battery() { //This function maps all the values fetched via datalayer_extended.tesla.packContactorSetState = battery_packContactorSetState; datalayer_extended.tesla.packCtrsClosingAllowed = battery_packCtrsClosingAllowed; datalayer_extended.tesla.pyroTestInProgress = battery_pyroTestInProgress; - -#ifdef DEBUG_VIA_USB + datalayer_extended.tesla.battery_packCtrsOpenNowRequested = battery_packCtrsOpenNowRequested; + datalayer_extended.tesla.battery_packCtrsOpenRequested = battery_packCtrsOpenRequested; + datalayer_extended.tesla.battery_packCtrsRequestStatus = battery_packCtrsRequestStatus; + datalayer_extended.tesla.battery_packCtrsResetRequestRequired = battery_packCtrsResetRequestRequired; + datalayer_extended.tesla.battery_dcLinkAllowedToEnergize = battery_dcLinkAllowedToEnergize; + //0x2B4 + datalayer_extended.tesla.battery_dcdcLvBusVolt = battery_dcdcLvBusVolt; + datalayer_extended.tesla.battery_dcdcHvBusVolt = battery_dcdcHvBusVolt; + datalayer_extended.tesla.battery_dcdcLvOutputCurrent = battery_dcdcLvOutputCurrent; + //0x352 + datalayer_extended.tesla.battery_nominal_full_pack_energy = battery_nominal_full_pack_energy; + datalayer_extended.tesla.battery_nominal_full_pack_energy_m0 = battery_nominal_full_pack_energy_m0; + datalayer_extended.tesla.battery_nominal_energy_remaining = battery_nominal_energy_remaining; + datalayer_extended.tesla.battery_nominal_energy_remaining_m0 = battery_nominal_energy_remaining_m0; + datalayer_extended.tesla.battery_ideal_energy_remaining = battery_ideal_energy_remaining; + datalayer_extended.tesla.battery_ideal_energy_remaining_m0 = battery_ideal_energy_remaining_m0; + datalayer_extended.tesla.battery_energy_to_charge_complete = battery_energy_to_charge_complete; + datalayer_extended.tesla.battery_energy_to_charge_complete_m1 = battery_energy_to_charge_complete_m1; + datalayer_extended.tesla.battery_energy_buffer = battery_energy_buffer; + datalayer_extended.tesla.battery_energy_buffer_m1 = battery_energy_buffer_m1; + datalayer_extended.tesla.battery_expected_energy_remaining_m1 = battery_expected_energy_remaining_m1; + datalayer_extended.tesla.battery_full_charge_complete = battery_full_charge_complete; + datalayer_extended.tesla.battery_fully_charged = battery_fully_charged; + //0x3D2 + datalayer_extended.tesla.battery_total_discharge = battery_total_discharge; + datalayer_extended.tesla.battery_total_charge = battery_total_charge; + //0x392 + datalayer_extended.tesla.battery_moduleType = battery_moduleType; + datalayer_extended.tesla.battery_packMass = battery_packMass; + datalayer_extended.tesla.battery_platformMaxBusVoltage = battery_platformMaxBusVoltage; + //0x2D2 + datalayer_extended.tesla.battery_bms_min_voltage = battery_bms_min_voltage; + datalayer_extended.tesla.battery_bms_max_voltage = battery_bms_max_voltage; + datalayer_extended.tesla.battery_max_charge_current = battery_max_charge_current; + datalayer_extended.tesla.battery_max_discharge_current = battery_max_discharge_current; + //0x292 + datalayer_extended.tesla.battery_beginning_of_life = battery_beginning_of_life; + datalayer_extended.tesla.battery_battTempPct = battery_battTempPct; + datalayer_extended.tesla.battery_soc_ave = battery_soc_ave; + datalayer_extended.tesla.battery_soc_max = battery_soc_max; + datalayer_extended.tesla.battery_soc_min = battery_soc_min; + datalayer_extended.tesla.battery_soc_ui = battery_soc_ui; + //0x332 + datalayer_extended.tesla.battery_BrickVoltageMax = battery_BrickVoltageMax; + datalayer_extended.tesla.battery_BrickVoltageMin = battery_BrickVoltageMin; + datalayer_extended.tesla.battery_BrickTempMaxNum = battery_BrickTempMaxNum; + datalayer_extended.tesla.battery_BrickTempMinNum = battery_BrickTempMinNum; + datalayer_extended.tesla.battery_BrickModelTMax = battery_BrickModelTMax; + datalayer_extended.tesla.battery_BrickModelTMin = battery_BrickModelTMin; + //0x212 + datalayer_extended.tesla.battery_BMS_isolationResistance = battery_BMS_isolationResistance; + datalayer_extended.tesla.battery_BMS_contactorState = battery_BMS_contactorState; + datalayer_extended.tesla.battery_BMS_state = battery_BMS_state; + datalayer_extended.tesla.battery_BMS_hvState = battery_BMS_hvState; + datalayer_extended.tesla.battery_BMS_uiChargeStatus = battery_BMS_uiChargeStatus; + datalayer_extended.tesla.battery_BMS_diLimpRequest = battery_BMS_diLimpRequest; + datalayer_extended.tesla.battery_BMS_chgPowerAvailable = battery_BMS_chgPowerAvailable; + datalayer_extended.tesla.battery_BMS_pcsPwmEnabled = battery_BMS_pcsPwmEnabled; + //0x224 + datalayer_extended.tesla.battery_PCS_dcdcPrechargeStatus = battery_PCS_dcdcPrechargeStatus; + datalayer_extended.tesla.battery_PCS_dcdc12VSupportStatus = battery_PCS_dcdc12VSupportStatus; + datalayer_extended.tesla.battery_PCS_dcdcHvBusDischargeStatus = battery_PCS_dcdcHvBusDischargeStatus; + datalayer_extended.tesla.battery_PCS_dcdcMainState = battery_PCS_dcdcMainState; + datalayer_extended.tesla.battery_PCS_dcdcSubState = battery_PCS_dcdcSubState; + datalayer_extended.tesla.battery_PCS_dcdcFaulted = battery_PCS_dcdcFaulted; + datalayer_extended.tesla.battery_PCS_dcdcOutputIsLimited = battery_PCS_dcdcOutputIsLimited; + datalayer_extended.tesla.battery_PCS_dcdcMaxOutputCurrentAllowed = battery_PCS_dcdcMaxOutputCurrentAllowed; + datalayer_extended.tesla.battery_PCS_dcdcPrechargeRtyCnt = battery_PCS_dcdcPrechargeRtyCnt; + datalayer_extended.tesla.battery_PCS_dcdc12VSupportRtyCnt = battery_PCS_dcdc12VSupportRtyCnt; + datalayer_extended.tesla.battery_PCS_dcdcDischargeRtyCnt = battery_PCS_dcdcDischargeRtyCnt; + datalayer_extended.tesla.battery_PCS_dcdcPwmEnableLine = battery_PCS_dcdcPwmEnableLine; + datalayer_extended.tesla.battery_PCS_dcdcSupportingFixedLvTarget = battery_PCS_dcdcSupportingFixedLvTarget; + datalayer_extended.tesla.battery_PCS_dcdcPrechargeRestartCnt = battery_PCS_dcdcPrechargeRestartCnt; + datalayer_extended.tesla.battery_PCS_dcdcInitialPrechargeSubState = battery_PCS_dcdcInitialPrechargeSubState; + //0x252 + datalayer_extended.tesla.BMS_maxRegenPower = BMS_maxRegenPower; + datalayer_extended.tesla.BMS_maxDischargePower = BMS_maxDischargePower; + datalayer_extended.tesla.BMS_maxStationaryHeatPower = BMS_maxStationaryHeatPower; + datalayer_extended.tesla.BMS_hvacPowerBudget = BMS_hvacPowerBudget; + datalayer_extended.tesla.BMS_notEnoughPowerForHeatPump = BMS_notEnoughPowerForHeatPump; + datalayer_extended.tesla.BMS_powerLimitState = BMS_powerLimitState; + datalayer_extended.tesla.BMS_inverterTQF = BMS_inverterTQF; + //0x312 + datalayer_extended.tesla.BMS_powerDissipation = BMS_powerDissipation; + datalayer_extended.tesla.BMS_flowRequest = BMS_flowRequest; + datalayer_extended.tesla.BMS_inletActiveCoolTargetT = BMS_inletActiveCoolTargetT; + datalayer_extended.tesla.BMS_inletPassiveTargetT = BMS_inletPassiveTargetT; + datalayer_extended.tesla.BMS_inletActiveHeatTargetT = BMS_inletActiveHeatTargetT; + datalayer_extended.tesla.BMS_packTMin = BMS_packTMin; + datalayer_extended.tesla.BMS_packTMax = BMS_packTMax; + datalayer_extended.tesla.BMS_pcsNoFlowRequest = BMS_pcsNoFlowRequest; + datalayer_extended.tesla.BMS_noFlowRequest = BMS_noFlowRequest; + //0x2A4 + datalayer_extended.tesla.PCS_dcdcTemp = PCS_dcdcTemp; + datalayer_extended.tesla.PCS_ambientTemp = PCS_ambientTemp; + //0x2C4 + datalayer_extended.tesla.PCS_dcdcMaxLvOutputCurrent = PCS_dcdcMaxLvOutputCurrent; + datalayer_extended.tesla.PCS_dcdcCurrentLimit = PCS_dcdcCurrentLimit; + datalayer_extended.tesla.PCS_dcdcLvOutputCurrentTempLimit = PCS_dcdcLvOutputCurrentTempLimit; + datalayer_extended.tesla.PCS_dcdcUnifiedCommand = PCS_dcdcUnifiedCommand; + datalayer_extended.tesla.PCS_dcdcCLAControllerOutput = PCS_dcdcCLAControllerOutput; + datalayer_extended.tesla.PCS_dcdcTankVoltage = PCS_dcdcTankVoltage; + datalayer_extended.tesla.PCS_dcdcTankVoltageTarget = PCS_dcdcTankVoltageTarget; + datalayer_extended.tesla.PCS_dcdcClaCurrentFreq = PCS_dcdcClaCurrentFreq; + datalayer_extended.tesla.PCS_dcdcTCommMeasured = PCS_dcdcTCommMeasured; + datalayer_extended.tesla.PCS_dcdcShortTimeUs = PCS_dcdcShortTimeUs; + datalayer_extended.tesla.PCS_dcdcHalfPeriodUs = PCS_dcdcHalfPeriodUs; + datalayer_extended.tesla.PCS_dcdcIntervalMaxFrequency = PCS_dcdcIntervalMaxFrequency; + datalayer_extended.tesla.PCS_dcdcIntervalMaxHvBusVolt = PCS_dcdcIntervalMaxHvBusVolt; + datalayer_extended.tesla.PCS_dcdcIntervalMaxLvBusVolt = PCS_dcdcIntervalMaxLvBusVolt; + datalayer_extended.tesla.PCS_dcdcIntervalMaxLvOutputCurr = PCS_dcdcIntervalMaxLvOutputCurr; + datalayer_extended.tesla.PCS_dcdcIntervalMinFrequency = PCS_dcdcIntervalMinFrequency; + datalayer_extended.tesla.PCS_dcdcIntervalMinHvBusVolt = PCS_dcdcIntervalMinHvBusVolt; + datalayer_extended.tesla.PCS_dcdcIntervalMinLvBusVolt = PCS_dcdcIntervalMinLvBusVolt; + datalayer_extended.tesla.PCS_dcdcIntervalMinLvOutputCurr = PCS_dcdcIntervalMinLvOutputCurr; + datalayer_extended.tesla.PCS_dcdc12vSupportLifetimekWh = PCS_dcdc12vSupportLifetimekWh; + //0x7AA + datalayer_extended.tesla.HVP_gpioPassivePyroDepl = HVP_gpioPassivePyroDepl; + datalayer_extended.tesla.HVP_gpioPyroIsoEn = HVP_gpioPyroIsoEn; + datalayer_extended.tesla.HVP_gpioCpFaultIn = HVP_gpioCpFaultIn; + datalayer_extended.tesla.HVP_gpioPackContPowerEn = HVP_gpioPackContPowerEn; + datalayer_extended.tesla.HVP_gpioHvCablesOk = HVP_gpioHvCablesOk; + datalayer_extended.tesla.HVP_gpioHvpSelfEnable = HVP_gpioHvpSelfEnable; + datalayer_extended.tesla.HVP_gpioLed = HVP_gpioLed; + datalayer_extended.tesla.HVP_gpioCrashSignal = HVP_gpioCrashSignal; + datalayer_extended.tesla.HVP_gpioShuntDataReady = HVP_gpioShuntDataReady; + datalayer_extended.tesla.HVP_gpioFcContPosAux = HVP_gpioFcContPosAux; + datalayer_extended.tesla.HVP_gpioFcContNegAux = HVP_gpioFcContNegAux; + datalayer_extended.tesla.HVP_gpioBmsEout = HVP_gpioBmsEout; + datalayer_extended.tesla.HVP_gpioCpFaultOut = HVP_gpioCpFaultOut; + datalayer_extended.tesla.HVP_gpioPyroPor = HVP_gpioPyroPor; + datalayer_extended.tesla.HVP_gpioShuntEn = HVP_gpioShuntEn; + datalayer_extended.tesla.HVP_gpioHvpVerEn = HVP_gpioHvpVerEn; + datalayer_extended.tesla.HVP_gpioPackCoontPosFlywheel = HVP_gpioPackCoontPosFlywheel; + datalayer_extended.tesla.HVP_gpioCpLatchEnable = HVP_gpioCpLatchEnable; + datalayer_extended.tesla.HVP_gpioPcsEnable = HVP_gpioPcsEnable; + datalayer_extended.tesla.HVP_gpioPcsDcdcPwmEnable = HVP_gpioPcsDcdcPwmEnable; + datalayer_extended.tesla.HVP_gpioPcsChargePwmEnable = HVP_gpioPcsChargePwmEnable; + datalayer_extended.tesla.HVP_gpioFcContPowerEnable = HVP_gpioFcContPowerEnable; + datalayer_extended.tesla.HVP_gpioHvilEnable = HVP_gpioHvilEnable; + datalayer_extended.tesla.HVP_gpioSecDrdy = HVP_gpioSecDrdy; + datalayer_extended.tesla.HVP_hvp1v5Ref = HVP_hvp1v5Ref; + datalayer_extended.tesla.HVP_shuntCurrentDebug = HVP_shuntCurrentDebug; + datalayer_extended.tesla.HVP_packCurrentMia = HVP_packCurrentMia; + datalayer_extended.tesla.HVP_auxCurrentMia = HVP_auxCurrentMia; + datalayer_extended.tesla.HVP_currentSenseMia = HVP_currentSenseMia; + datalayer_extended.tesla.HVP_shuntRefVoltageMismatch = HVP_shuntRefVoltageMismatch; + datalayer_extended.tesla.HVP_shuntThermistorMia = HVP_shuntThermistorMia; + datalayer_extended.tesla.HVP_shuntHwMia = HVP_shuntHwMia; + datalayer_extended.tesla.HVP_dcLinkVoltage = HVP_dcLinkVoltage; + datalayer_extended.tesla.HVP_packVoltage = HVP_packVoltage; + datalayer_extended.tesla.HVP_fcLinkVoltage = HVP_fcLinkVoltage; + datalayer_extended.tesla.HVP_packContVoltage = HVP_packContVoltage; + datalayer_extended.tesla.HVP_packNegativeV = HVP_packNegativeV; + datalayer_extended.tesla.HVP_packPositiveV = HVP_packPositiveV; + datalayer_extended.tesla.HVP_pyroAnalog = HVP_pyroAnalog; + datalayer_extended.tesla.HVP_dcLinkNegativeV = HVP_dcLinkNegativeV; + datalayer_extended.tesla.HVP_dcLinkPositiveV = HVP_dcLinkPositiveV; + datalayer_extended.tesla.HVP_fcLinkNegativeV = HVP_fcLinkNegativeV; + datalayer_extended.tesla.HVP_fcContCoilCurrent = HVP_fcContCoilCurrent; + datalayer_extended.tesla.HVP_fcContVoltage = HVP_fcContVoltage; + datalayer_extended.tesla.HVP_hvilInVoltage = HVP_hvilInVoltage; + datalayer_extended.tesla.HVP_hvilOutVoltage = HVP_hvilOutVoltage; + datalayer_extended.tesla.HVP_fcLinkPositiveV = HVP_fcLinkPositiveV; + datalayer_extended.tesla.HVP_packContCoilCurrent = HVP_packContCoilCurrent; + datalayer_extended.tesla.HVP_battery12V = HVP_battery12V; + datalayer_extended.tesla.HVP_shuntRefVoltageDbg = HVP_shuntRefVoltageDbg; + datalayer_extended.tesla.HVP_shuntAuxCurrentDbg = HVP_shuntAuxCurrentDbg; + datalayer_extended.tesla.HVP_shuntBarTempDbg = HVP_shuntBarTempDbg; + datalayer_extended.tesla.HVP_shuntAsicTempDbg = HVP_shuntAsicTempDbg; + datalayer_extended.tesla.HVP_shuntAuxCurrentStatus = HVP_shuntAuxCurrentStatus; + datalayer_extended.tesla.HVP_shuntBarTempStatus = HVP_shuntBarTempStatus; + datalayer_extended.tesla.HVP_shuntAsicTempStatus = HVP_shuntAsicTempStatus; + +#ifdef DEBUG_LOG printFaultCodesIfActive(); - Serial.print("STATUS: Contactor: "); - Serial.print(contactorText[battery_contactor]); //Display what state the contactor is in - Serial.print(", HVIL: "); - Serial.print(hvilStatusState[battery_hvil_status]); - Serial.print(", NegativeState: "); - Serial.print(contactorState[battery_packContNegativeState]); - Serial.print(", PositiveState: "); - Serial.print(contactorState[battery_packContPositiveState]); - Serial.print(", setState: "); - Serial.print(contactorState[battery_packContactorSetState]); - Serial.print(", close allowed: "); - Serial.print(battery_packCtrsClosingAllowed); - Serial.print(", Pyrotest: "); - Serial.println(battery_pyroTestInProgress); - - Serial.print("Battery values: "); - Serial.print("Real SOC: "); - Serial.print(battery_soc_vi / 10.0, 1); + logging.print("STATUS: Contactor: "); + logging.print(contactorText[battery_contactor]); //Display what state the contactor is in + logging.print(", HVIL: "); + logging.print(hvilStatusState[battery_hvil_status]); + logging.print(", NegativeState: "); + logging.print(contactorState[battery_packContNegativeState]); + logging.print(", PositiveState: "); + logging.print(contactorState[battery_packContPositiveState]); + logging.print(", setState: "); + logging.print(contactorState[battery_packContactorSetState]); + logging.print(", close allowed: "); + logging.print(battery_packCtrsClosingAllowed); + logging.print(", Pyrotest: "); + logging.println(battery_pyroTestInProgress); + + logging.print("Battery values: "); + logging.print("Real SOC: "); + logging.print(battery_soc_ui / 10.0, 1); print_int_with_units(", Battery voltage: ", battery_volts, "V"); print_int_with_units(", Battery HV current: ", (battery_amps * 0.1), "A"); - Serial.print(", Fully charged?: "); + logging.print(", Fully charged?: "); if (battery_full_charge_complete) - Serial.print("YES, "); + logging.print("YES, "); else - Serial.print("NO, "); + logging.print("NO, "); if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { - Serial.print("LFP chemistry detected!"); + logging.print("LFP chemistry detected!"); } - Serial.println(""); - Serial.print("Cellstats, Max: "); - Serial.print(battery_cell_max_v); - Serial.print("mV (cell "); - Serial.print(battery_max_vno); - Serial.print("), Min: "); - Serial.print(battery_cell_min_v); - Serial.print("mV (cell "); - Serial.print(battery_min_vno); - Serial.print("), Imbalance: "); - Serial.print(battery_cell_deviation_mV); - Serial.println("mV."); - - print_int_with_units("High Voltage Output Pins: ", battery_high_voltage, "V"); - Serial.print(", "); - print_int_with_units("Low Voltage: ", battery_low_voltage, "V"); - Serial.println(""); - print_int_with_units("DC/DC 12V current: ", battery_output_current, "A"); - Serial.println(""); - - Serial.println("Values passed to the inverter: "); + logging.println(""); + logging.print("Cellstats, Max: "); + logging.print(battery_cell_max_v); + logging.print("mV (cell "); + logging.print(battery_BrickVoltageMaxNum); + logging.print("), Min: "); + logging.print(battery_cell_min_v); + logging.print("mV (cell "); + logging.print(battery_BrickVoltageMinNum); + logging.print("), Imbalance: "); + logging.print(battery_cell_deviation_mV); + logging.println("mV."); + + print_int_with_units("High Voltage Output Pins: ", battery_dcdcHvBusVolt, "V"); + logging.print(", "); + print_int_with_units("Low Voltage: ", battery_dcdcLvBusVolt, "V"); + logging.println(""); + print_int_with_units("DC/DC 12V current: ", battery_dcdcLvOutputCurrent, "A"); + logging.println(""); + + logging.println("Values passed to the inverter: "); print_SOC(" SOC: ", datalayer.battery.status.reported_soc); print_int_with_units(" Max discharge power: ", datalayer.battery.status.max_discharge_power_W, "W"); - Serial.print(", "); + logging.print(", "); print_int_with_units(" Max charge power: ", datalayer.battery.status.max_charge_power_W, "W"); - Serial.println(""); + logging.println(""); print_int_with_units(" Max temperature: ", ((int16_t)datalayer.battery.status.temperature_min_dC * 0.1), "ยฐC"); - Serial.print(", "); + logging.print(", "); print_int_with_units(" Min temperature: ", ((int16_t)datalayer.battery.status.temperature_max_dC * 0.1), "ยฐC"); - Serial.println(""); -#endif //DEBUG_VIA_USB + logging.println(""); +#endif //DEBUG_LOG } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { static uint8_t mux = 0; static uint16_t temp = 0; switch (rx_frame.ID) { - case 0x352: - //SOC - battery_nominal_full_pack_energy = - (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - battery_nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * - 0.1; //Example 1247 * 0.1 = 124.7kWh - battery_expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) - battery_ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * - 0.1; //Example 311 * 0.1 = 31.1kWh - battery_energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * - 0.1; //Example 147 * 0.1 = 14.7kWh - battery_energy_buffer = - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0 - battery_full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); + case 0x352: // 850 BMS_energyStatus newer BMS + mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X + + if (mux == 0) { + battery_nominal_full_pack_energy_m0 = + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //16|16@1+ (0.02,0) [0|0] "kWh"//to datalayer_extended + battery_nominal_energy_remaining_m0 = + ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]); //32|16@1+ (0.02,0) [0|0] "kWh"//to datalayer_extended + battery_ideal_energy_remaining_m0 = + ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]); //48|16@1+ (0.02,0) [0|0] "kWh"//to datalayer_extended + } + if (mux == 1) { + battery_fully_charged = (rx_frame.data.u8[1] & 0x01); //15|1@1+ (1,0) [0|1]//to datalayer_extended + battery_energy_buffer_m1 = + (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //16|16@1+ (0.01,0) [0|0] "kWh"//to datalayer_extended + battery_expected_energy_remaining_m1 = + (rx_frame.data.u8[5] | rx_frame.data.u8[4]); //32|16@1+ (0.02,0) [0|0] "kWh"//to datalayer_extended + battery_energy_to_charge_complete_m1 = + (rx_frame.data.u8[7] | rx_frame.data.u8[6]); //48|16@1+ (0.02,0) [0|0] "kWh"//to datalayer_extended + } + if (mux == 2) {} + // Additional information needed on this mux, example frame: 02 26 02 20 02 80 00 00 doesn't change + // older BMS <2021 without mux + battery_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + battery_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); + (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh + battery_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) + battery_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh + battery_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh + battery_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 + battery_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); + ((rx_frame.data.u8[7] & 0x01) >> 7); break; - case 0x20A: - //Contactor state - battery_packContNegativeState = (rx_frame.data.u8[0] & 0x07); - battery_packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; - battery_contactor = (rx_frame.data.u8[1] & 0x0F); - battery_packContactorSetState = (rx_frame.data.u8[1] & 0x0F); - battery_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; - battery_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; - battery_hvil_status = (rx_frame.data.u8[5] & 0x0F); + case 0x20A: //522 HVP_contactorState: + battery_packContNegativeState = + (rx_frame.data.u8[0] & 0x07); //(_d[0] & (0x07U)); 0|3@1+ (1,0) [0|7] //to datalayer_extended + battery_packContPositiveState = + (rx_frame.data.u8[0] & 0x38) >> 3; //((_d[0] >> 3) & (0x07U)); 3|3@1+ (1,0) [0|7] //to datalayer_extended + battery_contactor = (rx_frame.data.u8[1] & 0x0F); // 8|4@1+ (1,0) [0|9] //to datalayer_extended + battery_packContactorSetState = + (rx_frame.data.u8[1] & 0x0F); //(_d[1] & (0x0FU)); 8|4@1+ (1,0) [0|9] //to datalayer_extended + battery_packCtrsClosingAllowed = + (rx_frame.data.u8[4] & 0x08) >> 3; //((_d[4] >> 3) & (0x01U)); 35|1@1+ (1,0) [0|1] //to datalayer_extended + battery_pyroTestInProgress = + (rx_frame.data.u8[4] & 0x20) >> 5; //((_d[4] >> 5) & (0x01U));//37|1@1+ (1,0) [0|1] //to datalayer_extended + battery_hvil_status = + (rx_frame.data.u8[5] & 0x0F); //(_d[5] & (0x0FU)); //40|4@1+ (1,0) [0|9] //to datalayer_extended + battery_packCtrsOpenNowRequested = + ((rx_frame.data.u8[4] >> 1) & (0x01U)); //33|1@1+ (1,0) [0|1] //to datalayer_extended + battery_packCtrsOpenRequested = + ((rx_frame.data.u8[4] >> 2) & (0x01U)); //34|1@1+ (1,0) [0|1] //to datalayer_extended + battery_packCtrsRequestStatus = + ((rx_frame.data.u8[3] >> 6) & (0x03U)); //30|2@1+ (1,0) [0|2] //to datalayer_extended + battery_packCtrsResetRequestRequired = + (rx_frame.data.u8[4] & (0x01U)); //32|1@1+ (1,0) [0|1] //to datalayer_extended + battery_dcLinkAllowedToEnergize = + ((rx_frame.data.u8[4] >> 4) & (0x01U)); //36|1@1+ (1,0) [0|1] //to datalayer_extended + battery_fcContNegativeAuxOpen = ((rx_frame.data.u8[0] >> 7) & (0x01U)); //7|1@1+ (1,0) [0|1] "" Receiver + battery_fcContNegativeState = ((rx_frame.data.u8[1] >> 4) & (0x07U)); //12|3@1+ (1,0) [0|7] "" Receiver + battery_fcContPositiveAuxOpen = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //6|1@1+ (1,0) [0|1] "" Receiver + battery_fcContPositiveState = (rx_frame.data.u8[2] & (0x07U)); //16|3@1+ (1,0) [0|7] "" Receiver + battery_fcContactorSetState = ((rx_frame.data.u8[2] >> 3) & (0x0FU)); //19|4@1+ (1,0) [0|9] "" Receiver + battery_fcCtrsClosingAllowed = ((rx_frame.data.u8[3] >> 5) & (0x01U)); //29|1@1+ (1,0) [0|1] "" Receiver + battery_fcCtrsOpenNowRequested = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|1] "" Receiver + battery_fcCtrsOpenRequested = ((rx_frame.data.u8[3] >> 4) & (0x01U)); //28|1@1+ (1,0) [0|1] "" Receiver + battery_fcCtrsRequestStatus = (rx_frame.data.u8[3] & (0x03U)); //24|2@1+ (1,0) [0|2] "" Receiver + battery_fcCtrsResetRequestRequired = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //26|1@1+ (1,0) [0|1] "" Receiver + battery_fcLinkAllowedToEnergize = ((rx_frame.data.u8[5] >> 4) & (0x03U)); //44|2@1+ (1,0) [0|2] "" Receiver break; - case 0x252: - //Limits - battery_regenerative_limit = - ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 4715 * 0.01 = 47.15kW - battery_discharge_limit = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.013; //Example 2009 * 0.013 = 26.117??? - battery_max_heat_park = - (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[4]) * 0.01; //Example 500 * 0.01 = 5kW - battery_hvac_max_power = - (((rx_frame.data.u8[7] << 6) | ((rx_frame.data.u8[6] & 0xFC) >> 2))) * 0.02; //Example 1000 * 0.02 = 20kW? + case 0x212: //530 BMS_status: 8 + battery_BMS_hvacPowerRequest = (rx_frame.data.u8[0] & (0x01U)); + battery_BMS_notEnoughPowerForDrive = ((rx_frame.data.u8[0] >> 1) & (0x01U)); + battery_BMS_notEnoughPowerForSupport = ((rx_frame.data.u8[0] >> 2) & (0x01U)); + battery_BMS_preconditionAllowed = ((rx_frame.data.u8[0] >> 3) & (0x01U)); + battery_BMS_updateAllowed = ((rx_frame.data.u8[0] >> 4) & (0x01U)); + battery_BMS_activeHeatingWorthwhile = ((rx_frame.data.u8[0] >> 5) & (0x01U)); + battery_BMS_cpMiaOnHvs = ((rx_frame.data.u8[0] >> 6) & (0x01U)); + battery_BMS_contactorState = + (rx_frame.data.u8[1] & + (0x07U)); //0 "SNA" 1 "OPEN" 2 "OPENING" 3 "CLOSING" 4 "CLOSED" 5 "WELDED" 6 "BLOCKED" ; + battery_BMS_state = + ((rx_frame.data.u8[1] >> 3) & + (0x0FU)); //0 "STANDBY" 1 "DRIVE" 2 "SUPPORT" 3 "CHARGE" 4 "FEIM" 5 "CLEAR_FAULT" 6 "FAULT" 7 "WELD" 8 "TEST" 9 "SNA" ; + battery_BMS_hvState = + (rx_frame.data.u8[2] & + (0x07U)); //0 "DOWN" 1 "COMING_UP" 2 "GOING_DOWN" 3 "UP_FOR_DRIVE" 4 "UP_FOR_CHARGE" 5 "UP_FOR_DC_CHARGE" 6 "UP" ; + battery_BMS_isolationResistance = + ((rx_frame.data.u8[3] & (0x1FU)) << 5) | + ((rx_frame.data.u8[2] >> 3) & (0x1FU)); //19|10@1+ (10,0) [0|0] "kOhm"/to datalayer_extended + battery_BMS_chargeRequest = ((rx_frame.data.u8[3] >> 5) & (0x01U)); + battery_BMS_keepWarmRequest = ((rx_frame.data.u8[3] >> 6) & (0x01U)); + battery_BMS_uiChargeStatus = + (rx_frame.data.u8[4] & + (0x07U)); // 0 "DISCONNECTED" 1 "NO_POWER" 2 "ABOUT_TO_CHARGE" 3 "CHARGING" 4 "CHARGE_COMPLETE" 5 "CHARGE_STOPPED" ; + battery_BMS_diLimpRequest = ((rx_frame.data.u8[4] >> 3) & (0x01U)); + battery_BMS_okToShipByAir = ((rx_frame.data.u8[4] >> 4) & (0x01U)); + battery_BMS_okToShipByLand = ((rx_frame.data.u8[4] >> 5) & (0x01U)); + battery_BMS_chgPowerAvailable = ((rx_frame.data.u8[6] & (0x01U)) << 10) | ((rx_frame.data.u8[5] & (0xFFU)) << 2) | + ((rx_frame.data.u8[4] >> 6) & (0x03U)); //38|11@1+ (0.125,0) [0|0] "kW" + battery_BMS_chargeRetryCount = ((rx_frame.data.u8[6] >> 1) & (0x0FU)); + battery_BMS_pcsPwmEnabled = ((rx_frame.data.u8[6] >> 5) & (0x01U)); + battery_BMS_ecuLogUploadRequest = ((rx_frame.data.u8[6] >> 6) & (0x03U)); + battery_BMS_minPackTemperature = (rx_frame.data.u8[7] & (0xFFU)); //56|8@1+ (0.5,-40) [0|0] "DegC break; - case 0x132: - //battery amps/volts - battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01; //Example 37030mv * 0.01 = 370V - battery_amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) - battery_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? + case 0x224: //548 PCS_dcdcStatus: + battery_PCS_dcdcPrechargeStatus = (rx_frame.data.u8[0] & (0x03U)); //0 "IDLE" 1 "ACTIVE" 2 "FAULTED" ; + battery_PCS_dcdc12VSupportStatus = ((rx_frame.data.u8[0] >> 2) & (0x03U)); //0 "IDLE" 1 "ACTIVE" 2 "FAULTED" + battery_PCS_dcdcHvBusDischargeStatus = ((rx_frame.data.u8[0] >> 4) & (0x03U)); //0 "IDLE" 1 "ACTIVE" 2 "FAULTED" + battery_PCS_dcdcMainState = + ((rx_frame.data.u8[1] & (0x03U)) << 2) | + ((rx_frame.data.u8[0] >> 6) & + (0x03U)); //0 "STANDBY" 1 "12V_SUPPORT_ACTIVE" 2 "PRECHARGE_STARTUP" 3 "PRECHARGE_ACTIVE" 4 "DIS_HVBUS_ACTIVE" 5 "SHUTDOWN" 6 "FAULTED" ; + battery_PCS_dcdcSubState = + ((rx_frame.data.u8[1] >> 2) & + (0x1FU)); //0 "PWR_UP_INIT" 1 "STANDBY" 2 "12V_SUPPORT_ACTIVE" 3 "DIS_HVBUS" 4 "PCHG_FAST_DIS_HVBUS" 5 "PCHG_SLOW_DIS_HVBUS" 6 "PCHG_DWELL_CHARGE" 7 "PCHG_DWELL_WAIT" 8 "PCHG_DI_RECOVERY_WAIT" 9 "PCHG_ACTIVE" 10 "PCHG_FLT_FAST_DIS_HVBUS" 11 "SHUTDOWN" 12 "12V_SUPPORT_FAULTED" 13 "DIS_HVBUS_FAULTED" 14 "PCHG_FAULTED" 15 "CLEAR_FAULTS" 16 "FAULTED" 17 "NUM" ; + battery_PCS_dcdcFaulted = ((rx_frame.data.u8[1] >> 7) & (0x01U)); + battery_PCS_dcdcOutputIsLimited = ((rx_frame.data.u8[3] >> 4) & (0x01U)); + battery_PCS_dcdcMaxOutputCurrentAllowed = ((rx_frame.data.u8[5] & (0x01U)) << 11) | + ((rx_frame.data.u8[4] & (0xFFU)) << 3) | + ((rx_frame.data.u8[3] >> 5) & (0x07U)); //29|12@1+ (0.1,0) [0|0] "A" + battery_PCS_dcdcPrechargeRtyCnt = ((rx_frame.data.u8[5] >> 1) & (0x07U)); + battery_PCS_dcdc12VSupportRtyCnt = ((rx_frame.data.u8[5] >> 4) & (0x0FU)); + battery_PCS_dcdcDischargeRtyCnt = (rx_frame.data.u8[6] & (0x0FU)); + battery_PCS_dcdcPwmEnableLine = ((rx_frame.data.u8[6] >> 4) & (0x01U)); + battery_PCS_dcdcSupportingFixedLvTarget = ((rx_frame.data.u8[6] >> 5) & (0x01U)); + battery_PCS_ecuLogUploadRequest = ((rx_frame.data.u8[6] >> 6) & (0x03U)); + battery_PCS_dcdcPrechargeRestartCnt = (rx_frame.data.u8[7] & (0x07U)); + battery_PCS_dcdcInitialPrechargeSubState = + ((rx_frame.data.u8[7] >> 3) & + (0x1FU)); //0 "PWR_UP_INIT" 1 "STANDBY" 2 "12V_SUPPORT_ACTIVE" 3 "DIS_HVBUS" 4 "PCHG_FAST_DIS_HVBUS" 5 "PCHG_SLOW_DIS_HVBUS" 6 "PCHG_DWELL_CHARGE" 7 "PCHG_DWELL_WAIT" 8 "PCHG_DI_RECOVERY_WAIT" 9 "PCHG_ACTIVE" 10 "PCHG_FLT_FAST_DIS_HVBUS" 11 "SHUTDOWN" 12 "12V_SUPPORT_FAULTED" 13 "DIS_HVBUS_FAULTED" 14 "PCHG_FAULTED" 15 "CLEAR_FAULTS" 16 "FAULTED" 17 "NUM" ; + break; + case 0x252: //Limit //594 BMS_powerAvailable: + BMS_maxRegenPower = ((rx_frame.data.u8[1] << 8) | + rx_frame.data.u8[0]); //0|16@1+ (0.01,0) [0|655.35] "kW" //Example 4715 * 0.01 = 47.15kW + BMS_maxDischargePower = + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8[2]); //16|16@1+ (0.013,0) [0|655.35] "kW" //Example 2009 * 0.013 = 26.117??? + BMS_maxStationaryHeatPower = + (((rx_frame.data.u8[5] & 0x03) << 8) | + rx_frame.data.u8[4]); //32|10@1+ (0.01,0) [0|10.23] "kW" //Example 500 * 0.01 = 5kW + BMS_hvacPowerBudget = + (((rx_frame.data.u8[7] << 6) | + ((rx_frame.data.u8[6] & 0xFC) >> 2))); //50|10@1+ (0.02,0) [0|20.46] "kW" //Example 1000 * 0.02 = 20kW? + BMS_notEnoughPowerForHeatPump = + ((rx_frame.data.u8[5] >> 2) & (0x01U)); //BMS_notEnoughPowerForHeatPump : 42|1@1+ (1,0) [0|1] "" Receiver + BMS_powerLimitState = + (rx_frame.data.u8[6] & + (0x01U)); //BMS_powerLimitsState : 48|1@1+ (1,0) [0|1] 0 "NOT_CALCULATED_FOR_DRIVE" 1 "CALCULATED_FOR_DRIVE" + BMS_inverterTQF = ((rx_frame.data.u8[7] >> 4) & (0x03U)); + break; + case 0x132: //battery amps/volts //HVBattAmpVolt + battery_volts = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * + 0.01; //0|16@1+ (0.01,0) [0|655.35] "V" //Example 37030mv * 0.01 = 370V + battery_amps = + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8 + [2]); //SmoothBattCurrent : 16|16@1- (-0.1,0) [-3276.7|3276.7] "A"//Example 65492 (-4.3A) OR 225 (22.5A) + battery_raw_amps = + ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05 + + 822; //RawBattCurrent : 32|16@1- (-0.05,822) [-1138.35|2138.4] "A" //Example 10425 * -0.05 = ? battery_charge_time_remaining = - (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min + (((rx_frame.data.u8[7] & 0x0F) << 8) | + rx_frame.data.u8[6]); //ChargeHoursRemaining : 48|12@1+ (1,0) [0|4095] "Min" //Example 228 * 0.1 = 22.8min if (battery_charge_time_remaining == 4095) { battery_charge_time_remaining = 0; } - break; - case 0x3D2: - // total charge/discharge kwh + 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.001; + 0.001; //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]) * - 0.001; + 0.001; //32|32@1+ (0.001,0) [0|4294970] "kWh" break; - case 0x332: - //min/max hist values - mux = (rx_frame.data.u8[0] & 0x03); + case 0x332: //min/max hist values //BattBrickMinMax: + mux = (rx_frame.data.u8[0] & 0x03); //BattBrickMultiplexer M : 0|2@1+ (1,0) [0|0] "" if (mux == 1) //Cell voltages { @@ -489,19 +1374,129 @@ void receive_can_battery(CAN_frame rx_frame) { temp = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); temp = (temp & 0xFFF); battery_cell_min_v = temp * 2; - battery_max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage - battery_min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage + cellvoltagesRead = true; + //BattBrickVoltageMax m1 : 2|12@1+ (0.002,0) [0|0] "V" Receiver ((_d[1] & (0x3FU)) << 6) | ((_d[0] >> 2) & (0x3FU)); + battery_BrickVoltageMax = + ((rx_frame.data.u8[1] & (0x3F)) << 6) | ((rx_frame.data.u8[0] >> 2) & (0x3F)); //to datalayer_extended + //BattBrickVoltageMin m1 : 16|12@1+ (0.002,0) [0|0] "V" Receiver ((_d[3] & (0x0FU)) << 8) | (_d[2] & (0xFFU)); + battery_BrickVoltageMin = + ((rx_frame.data.u8[3] & (0x0F)) << 8) | (rx_frame.data.u8[2] & (0xFF)); //to datalayer_extended + //BattBrickVoltageMaxNum m1 : 32|7@1+ (1,1) [0|0] "" Receiver + battery_BrickVoltageMaxNum = + 1 + (rx_frame.data.u8[4] & 0x7F); //(_d[4] & (0x7FU)); //This cell has highest voltage + //BattBrickVoltageMinNum m1 : 40|7@1+ (1,1) [0|0] "" Receiver + battery_BrickVoltageMinNum = + 1 + (rx_frame.data.u8[5] & 0x7F); //(_d[5] & (0x7FU)); //This cell has lowest voltage } if (mux == 0) //Temperature sensors - { + { //BattBrickTempMax m0 : 16|8@1+ (0.5,-40) [0|0] "C" (_d[2] & (0xFFU)); battery_max_temp = (rx_frame.data.u8[2] * 5) - 400; //Temperature values have 40.0*C offset, 0.5*C /bit + //BattBrickTempMin m0 : 24|8@1+ (0.5,-40) [0|0] "C" (_d[3] & (0xFFU)); battery_min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) + //BattBrickTempMaxNum m0 : 2|4@1+ (1,0) [0|0] "" ((_d[0] >> 2) & (0x0FU)); + battery_BrickTempMaxNum = ((rx_frame.data.u8[0] >> 2) & (0x0F)); //to datalayer_extended + //BattBrickTempMinNum m0 : 8|4@1+ (1,0) [0|0] "" (_d[1] & (0x0FU)); + battery_BrickTempMinNum = (rx_frame.data.u8[1] & (0x0F)); //to datalayer_extended + //BattBrickModelTMax m0 : 32|8@1+ (0.5,-40) [0|0] "C" (_d[4] & (0xFFU)); + battery_BrickModelTMax = (rx_frame.data.u8[4] & (0xFFU)); //to datalayer_extended + //BattBrickModelTMin m0 : 40|8@1+ (0.5,-40) [0|0] "C" (_d[5] & (0xFFU)); + battery_BrickModelTMin = (rx_frame.data.u8[5] & (0xFFU)); //to datalayer_extended } break; - case 0x401: // Cell stats - mux = (rx_frame.data.u8[0]); - + case 0x312: // 786 BMS_thermalStatus + BMS_powerDissipation = + ((rx_frame.data.u8[1] & (0x03U)) << 8) | (rx_frame.data.u8[0] & (0xFFU)); //0|10@1+ (0.02,0) [0|0] "kW" + BMS_flowRequest = ((rx_frame.data.u8[2] & (0x01U)) << 6) | + ((rx_frame.data.u8[1] >> 2) & (0x3FU)); //10|7@1+ (0.3,0) [0|0] "LPM" + BMS_inletActiveCoolTargetT = ((rx_frame.data.u8[3] & (0x03U)) << 7) | + ((rx_frame.data.u8[2] >> 1) & (0x7FU)); //17|9@1+ (0.25,-25) [0|0] "DegC" + BMS_inletPassiveTargetT = ((rx_frame.data.u8[4] & (0x07U)) << 6) | + ((rx_frame.data.u8[3] >> 2) & (0x3FU)); //26|9@1+ (0.25,-25) [0|0] "DegC" X + BMS_inletActiveHeatTargetT = ((rx_frame.data.u8[5] & (0x0FU)) << 5) | + ((rx_frame.data.u8[4] >> 3) & (0x1FU)); //35|9@1+ (0.25,-25) [0|0] "DegC" + BMS_packTMin = ((rx_frame.data.u8[6] & (0x1FU)) << 4) | + ((rx_frame.data.u8[5] >> 4) & (0x0FU)); //44|9@1+ (0.25,-25) [-25|100] "DegC" + BMS_packTMax = ((rx_frame.data.u8[7] & (0x3FU)) << 3) | + ((rx_frame.data.u8[6] >> 5) & (0x07U)); //53|9@1+ (0.25,-25) [-25|100] "DegC" + BMS_pcsNoFlowRequest = ((rx_frame.data.u8[7] >> 6) & (0x01U)); // 62|1@1+ (1,0) [0|0] "" + BMS_noFlowRequest = ((rx_frame.data.u8[7] >> 7) & (0x01U)); //63|1@1+ (1,0) [0|0] "" + break; + case 0x2A4: //676 PCS_thermalStatus + PCS_chgPhATemp = + ((rx_frame.data.u8[1] & (0x07U)) << 8) | (rx_frame.data.u8[0] & (0xFFU)); //0|11@1- (0.1,40) [0|0] "C + PCS_chgPhBTemp = + ((rx_frame.data.u8[2] & (0x3FU)) << 5) | ((rx_frame.data.u8[1] >> 3) & (0x1FU)); //11|11@1- (0.1,40) [0|0] "C + PCS_chgPhCTemp = + ((rx_frame.data.u8[4] & (0x07U)) << 8) | (rx_frame.data.u8[3] & (0xFFU)); //24|11@1- (0.1,40) [0|0] "C" + PCS_dcdcTemp = ((rx_frame.data.u8[5] & (0x3FU)) << 5) | + ((rx_frame.data.u8[4] >> 3) & (0x1FU)); //35|11@1- (0.1,40) [0|0] "C" + PCS_ambientTemp = + ((rx_frame.data.u8[7] & (0x07U)) << 8) | (rx_frame.data.u8[6] & (0xFFU)); //48|11@1- (0.1,40) [0|0] "C" + break; + case 0x2C4: // 708 PCS_logging: not all frames are listed, just ones relating to dcdc + mux = (rx_frame.data.u8[0] & (0x1FU)); + //PCS_logMessageSelect = (rx_frame.data.u8[0] & (0x1FU)); //0|5@1+ (1,0) [0|0] "" + if (mux == 6) { + PCS_dcdcMaxLvOutputCurrent = ((rx_frame.data.u8[4] & (0xFFU)) << 4) | + ((rx_frame.data.u8[3] >> 4) & (0x0FU)); //m6 : 28|12@1+ (0.1,0) [0|0] "A" X + PCS_dcdcCurrentLimit = ((rx_frame.data.u8[6] & (0x0FU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //m6 : 40|12@1+ (0.1,0) [0|0] "A" X + PCS_dcdcLvOutputCurrentTempLimit = ((rx_frame.data.u8[7] & (0xFFU)) << 4) | + ((rx_frame.data.u8[6] >> 4) & (0x0FU)); //m6 : 52|12@1+ (0.1,0) [0|0] "A" X + } + if (mux == 7) { + PCS_dcdcUnifiedCommand = ((rx_frame.data.u8[1] & (0x7FU)) << 3) | + ((rx_frame.data.u8[0] >> 5) & (0x07U)); //m7 : 5|10@1+ (0.001,0) [0|0] "1" X + PCS_dcdcCLAControllerOutput = ((rx_frame.data.u8[3] & (0x03U)) << 8) | + (rx_frame.data.u8[2] & (0xFFU)); //m7 : 16|10@1+ (0.001,0) [0|0] "1" X + PCS_dcdcTankVoltage = ((rx_frame.data.u8[4] & (0x1FU)) << 6) | + ((rx_frame.data.u8[3] >> 2) & (0x3FU)); //m7 : 26|11@1- (1,0) [0|0] "V" X + PCS_dcdcTankVoltageTarget = ((rx_frame.data.u8[5] & (0x7FU)) << 3) | + ((rx_frame.data.u8[4] >> 5) & (0x07U)); // m7 : 37|10@1+ (1,0) [0|0] "V" X + PCS_dcdcClaCurrentFreq = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //P m7 : 48|12@1+ (0.0976563,0) [0|0] "kHz" X + } + if (mux == 8) { + PCS_dcdcTCommMeasured = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); // m8 : 8|16@1- (0.00195313,0) [0|0] "us" X + PCS_dcdcShortTimeUs = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); // m8 : 24|16@1+ (0.000488281,0) [0|0] "us" X + PCS_dcdcHalfPeriodUs = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); // m8 : 40|16@1+ (0.000488281,0) [0|0] "us" X + } + if (mux == 18) { + PCS_dcdcIntervalMaxFrequency = ((rx_frame.data.u8[2] & (0x0FU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); // m18 : 8|12@1+ (1,0) [0|0] "kHz" X + PCS_dcdcIntervalMaxHvBusVolt = ((rx_frame.data.u8[4] & (0x1FU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //m18 : 24|13@1+ (0.1,0) [0|0] "V" X + PCS_dcdcIntervalMaxLvBusVolt = ((rx_frame.data.u8[5] & (0x3FU)) << 3) | + ((rx_frame.data.u8[4] >> 5) & (0x07U)); // m18 : 37|9@1+ (0.1,0) [0|0] "V" X + PCS_dcdcIntervalMaxLvOutputCurr = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //m18 : 48|12@1+ (1,0) [0|0] "A" X + } + if (mux == 19) { + PCS_dcdcIntervalMinFrequency = ((rx_frame.data.u8[2] & (0x0FU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //m19 : 8|12@1+ (1,0) [0|0] "kHz" X + PCS_dcdcIntervalMinHvBusVolt = ((rx_frame.data.u8[4] & (0x1FU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //m19 : 24|13@1+ (0.1,0) [0|0] "V" X + PCS_dcdcIntervalMinLvBusVolt = ((rx_frame.data.u8[5] & (0x3FU)) << 3) | + ((rx_frame.data.u8[4] >> 5) & (0x07U)); //m19 : 37|9@1+ (0.1,0) [0|0] "V" X + PCS_dcdcIntervalMinLvOutputCurr = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); // m19 : 48|12@1+ (1,0) [0|0] "A" X + } + if (mux == 22) { + PCS_dcdc12vSupportLifetimekWh = ((rx_frame.data.u8[3] & (0xFFU)) << 16) | + ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); // m22 : 8|24@1+ (0.01,0) [0|0] "kWh" X + } + break; + case 0x401: // Cell stats //BrickVoltages + mux = (rx_frame.data.u8[0]); //MultiplexSelector M : 0|8@1+ (1,0) [0|0] "" + //StatusFlags : 8|8@1+ (1,0) [0|0] "" + //Brick0 m0 : 16|16@1+ (0.0001,0) [0|0] "V" + //Brick1 m0 : 32|16@1+ (0.0001,0) [0|0] "V" + //Brick2 m0 : 48|16@1+ (0.0001,0) [0|0] "V" static uint16_t volts; static uint8_t mux_zero_counter = 0u; static uint8_t mux_max = 0u; @@ -530,29 +1525,153 @@ void receive_can_battery(CAN_frame rx_frame) { } } break; - case 0x2d2: - //Min / max limits + case 0x2d2: //BMSVAlimits: battery_bms_min_voltage = - ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V + ((rx_frame.data.u8[1] << 8) | + rx_frame.data.u8[0]); //0|16@1+ (0.01,0) [0|430] "V" //Example 24148mv * 0.01 = 241.48 V battery_bms_max_voltage = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V - battery_max_charge_current = - (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? - battery_max_discharge_current = - (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8[2]); //16|16@1+ (0.01,0) [0|430] "V" //Example 40282mv * 0.01 = 402.82 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" //Example 1301? * 0.1 = 130.1? + 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.9] "A" //Example 430? * 0.128 = 55.4? break; - case 0x2b4: - battery_low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; - battery_high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; - battery_output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; + case 0x2b4: //PCS_dcdcRailStatus: + battery_dcdcLvBusVolt = + (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); //0|10@1+ (0.0390625,0) [0|39.9609] "V" + battery_dcdcHvBusVolt = (((rx_frame.data.u8[2] & 0x3F) << 6) | + ((rx_frame.data.u8[1] & 0xFC) >> 2)); //10|12@1+ (0.146484,0) [0|599.854] "V" + battery_dcdcLvOutputCurrent = + (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]); //24|12@1+ (0.1,0) [0|400] "A" break; - case 0x292: + case 0x292: //BMS_socStatus datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS - battery_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); - battery_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); - battery_soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); - battery_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); - battery_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); + battery_beginning_of_life = + (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]) * 0.1; //40|10@1+ (0.1,0) [0|102.3] "kWh" + battery_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); //0|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_ui = + (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); //10|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_max = + (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); //20|10@1+ (0.1,0) [0|102.3] "%" + battery_soc_ave = + ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); //30|10@1+ (0.1,0) [0|102.3] "%" + battery_battTempPct = + (((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & 0x3F) >> 2); //50|8@1+ (0.4,0) [0|100] "%" + break; + case 0x392: //BMS_packConfig + mux = (rx_frame.data.u8[0] & (0xFF)); + if (mux == 1) { + battery_packConfigMultiplexer = (rx_frame.data.u8[0] & (0xff)); //0|8@1+ (1,0) [0|1] "" + battery_moduleType = + (rx_frame.data.u8[1] & + (0x07)); //8|3@1+ (1,0) [0|4] ""//0 "UNKNOWN" 1 "E3_NCT" 2 "E1_NCT" 3 "E3_CT" 4 "E1_CT" 5 "E1_CP" ;//to datalayer_extended + battery_packMass = (rx_frame.data.u8[2]) + 300; //16|8@1+ (1,300) [342|469] "kg" + battery_platformMaxBusVoltage = + (((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3])); //24|10@1+ (0.1,375) [0|0] "V" + } + if (mux == 0) { + battery_reservedConfig = + (rx_frame.data.u8[1] & + (0x1F)); //8|5@1+ (1,0) [0|31] ""//0 "BMS_CONFIG_0" 1 "BMS_CONFIG_1" 10 "BMS_CONFIG_10" 11 "BMS_CONFIG_11" 12 "BMS_CONFIG_12" 13 "BMS_CONFIG_13" 14 "BMS_CONFIG_14" 15 "BMS_CONFIG_15" 16 "BMS_CONFIG_16" 17 "BMS_CONFIG_17" 18 "BMS_CONFIG_18" 19 "BMS_CONFIG_19" 2 "BMS_CONFIG_2" 20 "BMS_CONFIG_20" 21 "BMS_CONFIG_21" 22 "BMS_CONFIG_22" 23 "BMS_CONFIG_23" 24 "BMS_CONFIG_24" 25 "BMS_CONFIG_25" 26 "BMS_CONFIG_26" 27 "BMS_CONFIG_27" 28 "BMS_CONFIG_28" 29 "BMS_CONFIG_29" 3 "BMS_CONFIG_3" 30 "BMS_CONFIG_30" 31 "BMS_CONFIG_31" 4 "BMS_CONFIG_4" 5 "BMS_CONFIG_5" 6 "BMS_CONFIG_6" 7 "BMS_CONFIG_7" 8 "BMS_CONFIG_8" 9 "BMS_CONFIG_9" ; + } + break; + case 0x7AA: //1962 HVP_debugMessage: + mux = (rx_frame.data.u8[0] & (0x0FU)); + //HVP_debugMessageMultiplexer = (rx_frame.data.u8[0] & (0x0FU)); //0|4@1+ (1,0) [0|6] "" + if (mux == 0) { + HVP_gpioPassivePyroDepl = ((rx_frame.data.u8[0] >> 4) & (0x01U)); //: 4|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPyroIsoEn = ((rx_frame.data.u8[0] >> 5) & (0x01U)); //: 5|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioCpFaultIn = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //: 6|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPackContPowerEn = ((rx_frame.data.u8[0] >> 7) & (0x01U)); //: 7|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioHvCablesOk = (rx_frame.data.u8[1] & (0x01U)); //: 8|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioHvpSelfEnable = ((rx_frame.data.u8[1] >> 1) & (0x01U)); //: 9|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioLed = ((rx_frame.data.u8[1] >> 2) & (0x01U)); //: 10|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioCrashSignal = ((rx_frame.data.u8[1] >> 3) & (0x01U)); //: 11|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioShuntDataReady = ((rx_frame.data.u8[1] >> 4) & (0x01U)); //: 12|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioFcContPosAux = ((rx_frame.data.u8[1] >> 5) & (0x01U)); //: 13|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioFcContNegAux = ((rx_frame.data.u8[1] >> 6) & (0x01U)); //: 14|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioBmsEout = ((rx_frame.data.u8[1] >> 7) & (0x01U)); //: 15|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioCpFaultOut = (rx_frame.data.u8[2] & (0x01U)); //: 16|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPyroPor = ((rx_frame.data.u8[2] >> 1) & (0x01U)); //: 17|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioShuntEn = ((rx_frame.data.u8[2] >> 2) & (0x01U)); //: 18|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioHvpVerEn = ((rx_frame.data.u8[2] >> 3) & (0x01U)); //: 19|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPackCoontPosFlywheel = ((rx_frame.data.u8[2] >> 4) & (0x01U)); //: 20|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioCpLatchEnable = ((rx_frame.data.u8[2] >> 5) & (0x01U)); //: 21|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPcsEnable = ((rx_frame.data.u8[2] >> 6) & (0x01U)); //: 22|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPcsDcdcPwmEnable = ((rx_frame.data.u8[2] >> 7) & (0x01U)); //: 23|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioPcsChargePwmEnable = (rx_frame.data.u8[3] & (0x01U)); //: 24|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioFcContPowerEnable = ((rx_frame.data.u8[3] >> 1) & (0x01U)); //: 25|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioHvilEnable = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //: 26|1@1+ (1,0) [0|1] "" Receiver + HVP_gpioSecDrdy = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //: 27|1@1+ (1,0) [0|1] "" Receiver + HVP_hvp1v5Ref = ((rx_frame.data.u8[4] & (0xFFU)) << 4) | + ((rx_frame.data.u8[3] >> 4) & (0x0FU)); //: 28|12@1+ (0.1,0) [0|3] "V" Receiver + HVP_shuntCurrentDebug = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.1,0) [-3276.8|3276.7] "A" Receiver + HVP_packCurrentMia = (rx_frame.data.u8[7] & (0x01U)); //: 56|1@1+ (1,0) [0|1] "" Receiver + HVP_auxCurrentMia = ((rx_frame.data.u8[7] >> 1) & (0x01U)); //: 57|1@1+ (1,0) [0|1] "" Receiver + HVP_currentSenseMia = ((rx_frame.data.u8[7] >> 2) & (0x03U)); //: 58|1@1+ (1,0) [0|1] "" Receiver + HVP_shuntRefVoltageMismatch = ((rx_frame.data.u8[7] >> 3) & (0x01U)); //: 59|1@1+ (1,0) [0|1] "" Receiver + HVP_shuntThermistorMia = ((rx_frame.data.u8[7] >> 4) & (0x01U)); //: 60|1@1+ (1,0) [0|1] "" Receiver + HVP_shuntHwMia = ((rx_frame.data.u8[7] >> 5) & (0x01U)); //: 61|1@1+ (1,0) [0|1] "" Receiver + } + if (mux == 1) { + HVP_dcLinkVoltage = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-3276.8|3276.7] "V" Receiver + HVP_packVoltage = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|16@1- (0.1,0) [-3276.8|3276.7] "V" Receiver + HVP_fcLinkVoltage = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.1,0) [-3276.8|3276.7] "V" Receiver + } + if (mux == 2) { + HVP_packContVoltage = ((rx_frame.data.u8[1] & (0xFFU)) << 4) | + ((rx_frame.data.u8[0] >> 4) & (0x0FU)); //: 4|12@1+ (0.1,0) [0|30] "V" Receiver + HVP_packNegativeV = ((rx_frame.data.u8[3] & (0xFFU)) << 8) | + (rx_frame.data.u8[2] & (0xFFU)); //: 16|16@1- (0.1,0) [-550|550] "V" Receiver + HVP_packPositiveV = ((rx_frame.data.u8[5] & (0xFFU)) << 8) | + (rx_frame.data.u8[4] & (0xFFU)); //: 32|16@1- (0.1,0) [-550|550] "V" Receiver + HVP_pyroAnalog = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //: 48|12@1+ (0.1,0) [0|3] "V" Receiver + } + if (mux == 3) { + HVP_dcLinkNegativeV = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-550|550] "V" Receiver + HVP_dcLinkPositiveV = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|16@1- (0.1,0) [-550|550] "V" Receiver + HVP_fcLinkNegativeV = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.1,0) [-550|550] "V" Receiver + } + if (mux == 4) { + HVP_fcContCoilCurrent = ((rx_frame.data.u8[1] & (0xFFU)) << 4) | + ((rx_frame.data.u8[0] >> 4) & (0x0FU)); //: 4|12@1+ (0.1,0) [0|7.5] "A" Receiver + HVP_fcContVoltage = ((rx_frame.data.u8[3] & (0x0FU)) << 8) | + (rx_frame.data.u8[2] & (0xFFU)); //: 16|12@1+ (0.1,0) [0|30] "V" Receiver + HVP_hvilInVoltage = ((rx_frame.data.u8[4] & (0xFFU)) << 4) | + ((rx_frame.data.u8[3] >> 4) & (0x0FU)); //: 28|12@1+ (0.1,0) [0|30] "V" Receiver + HVP_hvilOutVoltage = ((rx_frame.data.u8[6] & (0x0FU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|12@1+ (0.1,0) [0|30] "V" Receiver + } + if (mux == 5) { + HVP_fcLinkPositiveV = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-550|550] "V" Receiver + HVP_packContCoilCurrent = ((rx_frame.data.u8[4] & (0x0FU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|12@1+ (0.1,0) [0|7.5] "A" Receiver + HVP_battery12V = ((rx_frame.data.u8[5] & (0xFFU)) << 4) | + ((rx_frame.data.u8[4] >> 4) & (0x0FU)); //: 36|12@1+ (0.1,0) [0|30] "V" Receiver + HVP_shuntRefVoltageDbg = ((rx_frame.data.u8[7] & (0xFFU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //: 48|16@1- (0.001,0) [-32.768|32.767] "V" Receiver + } + if (mux == 6) { + HVP_shuntAuxCurrentDbg = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-3276.8|3276.7] "A" Receiver + HVP_shuntBarTempDbg = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|16@1- (0.01,0) [-327.67|327.67] "C" Receiver + HVP_shuntAsicTempDbg = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.01,0) [-327.67|327.67] "C" Receiver + HVP_shuntAuxCurrentStatus = (rx_frame.data.u8[7] & (0x03U)); //: 56|2@1+ (1,0) [0|3] "" Receiver + HVP_shuntBarTempStatus = ((rx_frame.data.u8[7] >> 2) & (0x03U)); //: 58|2@1+ (1,0) [0|3] "" Receiver + HVP_shuntAsicTempStatus = ((rx_frame.data.u8[7] >> 4) & (0x03U)); //: 60|2@1+ (1,0) [0|3] "" Receiver + } break; case 0x3aa: //HVP_alertMatrix1 battery_WatchdogReset = (rx_frame.data.u8[0] & 0x01); @@ -607,36 +1726,167 @@ void receive_can_battery(CAN_frame rx_frame) { battery_fcCtrCloseFailed = ((rx_frame.data.u8[6] & 0x02) >> 1); battery_shuntThermistorMia = ((rx_frame.data.u8[6] & 0x04) >> 2); break; + case 0x320: //800 BMS_alertMatrix //BMS_alertMatrix 800 BMS_alertMatrix: 8 VEH + mux = (rx_frame.data.u8[0] & (0x0F)); + if (mux == 0) + ; + { //mux0 + battery_BMS_matrixIndex = (rx_frame.data.u8[0] & (0x0F)); // 0|4@1+ (1,0) [0|0] "" X + battery_BMS_a017_SW_Brick_OV = ((rx_frame.data.u8[2] >> 4) & (0x01)); //20|1@1+ (1,0) [0|0] "" X + battery_BMS_a018_SW_Brick_UV = ((rx_frame.data.u8[2] >> 5) & (0x01)); //21|1@1+ (1,0) [0|0] "" X + battery_BMS_a019_SW_Module_OT = ((rx_frame.data.u8[2] >> 6) & (0x01)); //22|1@1+ (1,0) [0|0] "" X + battery_BMS_a021_SW_Dr_Limits_Regulation = (rx_frame.data.u8[3] & (0x01U)); //24|1@1+ (1,0) [0|0] "" X + battery_BMS_a022_SW_Over_Current = ((rx_frame.data.u8[3] >> 1) & (0x01U)); //25|1@1+ (1,0) [0|0] "" X + battery_BMS_a023_SW_Stack_OV = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //26|1@1+ (1,0) [0|0] "" X + battery_BMS_a024_SW_Islanded_Brick = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|0] "" X + battery_BMS_a025_SW_PwrBalance_Anomaly = ((rx_frame.data.u8[3] >> 4) & (0x01U)); //28|1@1+ (1,0) [0|0] "" X + battery_BMS_a026_SW_HFCurrent_Anomaly = ((rx_frame.data.u8[3] >> 5) & (0x01U)); //29|1@1+ (1,0) [0|0] "" X + battery_BMS_a034_SW_Passive_Isolation = ((rx_frame.data.u8[4] >> 5) & (0x01U)); //37|1@1+ (1,0) [0|0] "" X ? + battery_BMS_a035_SW_Isolation = ((rx_frame.data.u8[4] >> 6) & (0x01U)); //38|1@1+ (1,0) [0|0] "" X + battery_BMS_a036_SW_HvpHvilFault = ((rx_frame.data.u8[4] >> 6) & (0x01U)); //39|1@1+ (1,0) [0|0] "" X + battery_BMS_a037_SW_Flood_Port_Open = (rx_frame.data.u8[5] & (0x01U)); //40|1@1+ (1,0) [0|0] "" X + battery_BMS_a039_SW_DC_Link_Over_Voltage = ((rx_frame.data.u8[5] >> 2) & (0x01U)); //42|1@1+ (1,0) [0|0] "" X + battery_BMS_a041_SW_Power_On_Reset = ((rx_frame.data.u8[5] >> 4) & (0x01U)); //44|1@1+ (1,0) [0|0] "" X + battery_BMS_a042_SW_MPU_Error = ((rx_frame.data.u8[5] >> 5) & (0x01U)); //45|1@1+ (1,0) [0|0] "" X + battery_BMS_a043_SW_Watch_Dog_Reset = ((rx_frame.data.u8[5] >> 6) & (0x01U)); //46|1@1+ (1,0) [0|0] "" X + battery_BMS_a044_SW_Assertion = ((rx_frame.data.u8[5] >> 7) & (0x01U)); //47|1@1+ (1,0) [0|0] "" X + battery_BMS_a045_SW_Exception = (rx_frame.data.u8[6] & (0x01U)); //48|1@1+ (1,0) [0|0] "" X + battery_BMS_a046_SW_Task_Stack_Usage = ((rx_frame.data.u8[6] >> 1) & (0x01U)); //49|1@1+ (1,0) [0|0] "" X + battery_BMS_a047_SW_Task_Stack_Overflow = ((rx_frame.data.u8[6] >> 2) & (0x01U)); //50|1@1+ (1,0) [0|0] "" X + battery_BMS_a048_SW_Log_Upload_Request = ((rx_frame.data.u8[6] >> 3) & (0x01U)); //51|1@1+ (1,0) [0|0] "" X + battery_BMS_a050_SW_Brick_Voltage_MIA = ((rx_frame.data.u8[6] >> 5) & (0x01U)); //53|1@1+ (1,0) [0|0] "" X + battery_BMS_a051_SW_HVC_Vref_Bad = ((rx_frame.data.u8[6] >> 6) & (0x01U)); //54|1@1+ (1,0) [0|0] "" X + battery_BMS_a052_SW_PCS_MIA = ((rx_frame.data.u8[6] >> 7) & (0x01U)); //55|1@1+ (1,0) [0|0] "" X + battery_BMS_a053_SW_ThermalModel_Sanity = (rx_frame.data.u8[7] & (0x01U)); //56|1@1+ (1,0) [0|0] "" X + battery_BMS_a054_SW_Ver_Supply_Fault = ((rx_frame.data.u8[7] >> 1) & (0x01U)); //57|1@1+ (1,0) [0|0] "" X + battery_BMS_a059_SW_Pack_Voltage_Sensing = ((rx_frame.data.u8[7] >> 6) & (0x01U)); //62|1@1+ (1,0) [0|0] "" X + battery_BMS_a060_SW_Leakage_Test_Failure = ((rx_frame.data.u8[7] >> 7) & (0x01U)); //63|1@1+ (1,0) [0|0] "" X + } + if (mux == 1) { //mux1 + battery_BMS_a061_robinBrickOverVoltage = ((rx_frame.data.u8[0] >> 4) & (0x01U)); //4|1@1+ (1,0) [0|0] "" X + battery_BMS_a062_SW_BrickV_Imbalance = ((rx_frame.data.u8[0] >> 5) & (0x01U)); //5|1@1+ (1,0) [0|0] "" X + battery_BMS_a063_SW_ChargePort_Fault = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //6|1@1+ (1,0) [0|0] "" X + battery_BMS_a064_SW_SOC_Imbalance = ((rx_frame.data.u8[0] >> 7) & (0x01U)); //7|1@1+ (1,0) [0|0] "" X + battery_BMS_a069_SW_Low_Power = ((rx_frame.data.u8[1] >> 4) & (0x01U)); //12|1@1+ (1,0) [0|0] "" X + battery_BMS_a071_SW_SM_TransCon_Not_Met = ((rx_frame.data.u8[1] >> 6) & (0x01U)); //14|1@1+ (1,0) [0|0] "" X + battery_BMS_a075_SW_Chg_Disable_Failure = ((rx_frame.data.u8[2] >> 2) & (0x01U)); //18|1@1+ (1,0) [0|0] "" X + battery_BMS_a076_SW_Dch_While_Charging = ((rx_frame.data.u8[2] >> 3) & (0x01U)); //19|1@1+ (1,0) [0|0] "" X + battery_BMS_a077_SW_Charger_Regulation = ((rx_frame.data.u8[2] >> 4) & (0x01U)); //20|1@1+ (1,0) [0|0] "" X + battery_BMS_a081_SW_Ctr_Close_Blocked = (rx_frame.data.u8[3] & (0x01U)); //24|1@1+ (1,0) [0|0] "" X + battery_BMS_a082_SW_Ctr_Force_Open = ((rx_frame.data.u8[3] >> 1) & (0x01U)); //25|1@1+ (1,0) [0|0] "" X + battery_BMS_a083_SW_Ctr_Close_Failure = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //26|1@1+ (1,0) [0|0] "" X + battery_BMS_a084_SW_Sleep_Wake_Aborted = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|0] "" X + battery_BMS_a087_SW_Feim_Test_Blocked = ((rx_frame.data.u8[3] >> 6) & (0x01U)); //30|1@1+ (1,0) [0|0] "" X + battery_BMS_a088_SW_VcFront_MIA_InDrive = ((rx_frame.data.u8[3] >> 7) & (0x01U)); //31|1@1+ (1,0) [0|0] "" X + battery_BMS_a089_SW_VcFront_MIA = (rx_frame.data.u8[4] & (0x01U)); //32|1@1+ (1,0) [0|0] "" X + battery_BMS_a090_SW_Gateway_MIA = ((rx_frame.data.u8[4] >> 1) & (0x01U)); //33|1@1+ (1,0) [0|0] "" X + battery_BMS_a091_SW_ChargePort_MIA = ((rx_frame.data.u8[4] >> 2) & (0x01U)); //34|1@1+ (1,0) [0|0] "" X + battery_BMS_a092_SW_ChargePort_Mia_On_Hv = ((rx_frame.data.u8[4] >> 3) & (0x01U)); //35|1@1+ (1,0) [0|0] "" X + battery_BMS_a094_SW_Drive_Inverter_MIA = ((rx_frame.data.u8[4] >> 5) & (0x01U)); //37|1@1+ (1,0) [0|0] "" X + battery_BMS_a099_SW_BMB_Communication = ((rx_frame.data.u8[5] >> 2) & (0x01U)); //42|1@1+ (1,0) [0|0] "" X + battery_BMS_a105_SW_One_Module_Tsense = (rx_frame.data.u8[6] & (0x01U)); //48|1@1+ (1,0) [0|0] "" X + battery_BMS_a106_SW_All_Module_Tsense = ((rx_frame.data.u8[6] >> 1) & (0x01U)); //49|1@1+ (1,0) [0|0] "" X + battery_BMS_a107_SW_Stack_Voltage_MIA = ((rx_frame.data.u8[6] >> 2) & (0x01U)); //50|1@1+ (1,0) [0|0] "" X + } + if (mux == 2) { //mux2 + battery_BMS_a121_SW_NVRAM_Config_Error = ((rx_frame.data.u8[0] >> 4) & (0x01U)); // 4|1@1+ (1,0) [0|0] "" X + battery_BMS_a122_SW_BMS_Therm_Irrational = ((rx_frame.data.u8[0] >> 5) & (0x01U)); //5|1@1+ (1,0) [0|0] "" X + battery_BMS_a123_SW_Internal_Isolation = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //6|1@1+ (1,0) [0|0] "" X + battery_BMS_a127_SW_shunt_SNA = ((rx_frame.data.u8[1] >> 2) & (0x01U)); //10|1@1+ (1,0) [0|0] "" X + battery_BMS_a128_SW_shunt_MIA = ((rx_frame.data.u8[1] >> 3) & (0x01U)); //11|1@1+ (1,0) [0|0] "" X + battery_BMS_a129_SW_VSH_Failure = ((rx_frame.data.u8[1] >> 4) & (0x01U)); //12|1@1+ (1,0) [0|0] "" X + battery_BMS_a130_IO_CAN_Error = ((rx_frame.data.u8[1] >> 5) & (0x01U)); //13|1@1+ (1,0) [0|0] "" X + battery_BMS_a131_Bleed_FET_Failure = ((rx_frame.data.u8[1] >> 6) & (0x01U)); //14|1@1+ (1,0) [0|0] "" X + battery_BMS_a132_HW_BMB_OTP_Uncorrctbl = ((rx_frame.data.u8[1] >> 7) & (0x01U)); //15|1@1+ (1,0) [0|0] "" X + battery_BMS_a134_SW_Delayed_Ctr_Off = ((rx_frame.data.u8[2] >> 1) & (0x01U)); //17|1@1+ (1,0) [0|0] "" X + battery_BMS_a136_SW_Module_OT_Warning = ((rx_frame.data.u8[2] >> 3) & (0x01U)); //19|1@1+ (1,0) [0|0] "" X + battery_BMS_a137_SW_Brick_UV_Warning = ((rx_frame.data.u8[2] >> 4) & (0x01U)); //20|1@1+ (1,0) [0|0] "" X + battery_BMS_a138_SW_Brick_OV_Warning = ((rx_frame.data.u8[2] >> 5) & (0x01U)); //21|1@1+ (1,0) [0|0] "" X + battery_BMS_a139_SW_DC_Link_V_Irrational = ((rx_frame.data.u8[2] >> 6) & (0x01U)); //22|1@1+ (1,0) [0|0] "" X + battery_BMS_a141_SW_BMB_Status_Warning = (rx_frame.data.u8[3] & (0x01U)); //24|1@1+ (1,0) [0|0] "" X + battery_BMS_a144_Hvp_Config_Mismatch = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|0] "" X + battery_BMS_a145_SW_SOC_Change = ((rx_frame.data.u8[3] >> 4) & (0x01U)); //28|1@1+ (1,0) [0|0] "" X + battery_BMS_a146_SW_Brick_Overdischarged = ((rx_frame.data.u8[3] >> 5) & (0x01U)); //29|1@1+ (1,0) [0|0] "" X + battery_BMS_a149_SW_Missing_Config_Block = (rx_frame.data.u8[4] & (0x01U)); //32|1@1+ (1,0) [0|0] "" X + battery_BMS_a151_SW_external_isolation = ((rx_frame.data.u8[4] >> 2) & (0x01U)); //34|1@1+ (1,0) [0|0] "" X + battery_BMS_a156_SW_BMB_Vref_bad = ((rx_frame.data.u8[4] >> 7) & (0x01U)); //39|1@1+ (1,0) [0|0] "" X + battery_BMS_a157_SW_HVP_HVS_Comms = (rx_frame.data.u8[5] & (0x01U)); //40|1@1+ (1,0) [0|0] "" X + battery_BMS_a158_SW_HVP_HVI_Comms = ((rx_frame.data.u8[5] >> 1) & (0x01U)); //41|1@1+ (1,0) [0|0] "" X + battery_BMS_a159_SW_HVP_ECU_Error = ((rx_frame.data.u8[5] >> 2) & (0x01U)); //42|1@1+ (1,0) [0|0] "" X + battery_BMS_a161_SW_DI_Open_Request = ((rx_frame.data.u8[5] >> 4) & (0x01U)); //44|1@1+ (1,0) [0|0] "" X + battery_BMS_a162_SW_No_Power_For_Support = ((rx_frame.data.u8[5] >> 5) & (0x01U)); //45|1@1+ (1,0) [0|0] "" X + battery_BMS_a163_SW_Contactor_Mismatch = ((rx_frame.data.u8[5] >> 6) & (0x01U)); //46|1@1+ (1,0) [0|0] "" X + battery_BMS_a164_SW_Uncontrolled_Regen = ((rx_frame.data.u8[5] >> 7) & (0x01U)); //47|1@1+ (1,0) [0|0] "" X + battery_BMS_a165_SW_Pack_Partial_Weld = (rx_frame.data.u8[6] & (0x01U)); //48|1@1+ (1,0) [0|0] "" X + battery_BMS_a166_SW_Pack_Full_Weld = ((rx_frame.data.u8[6] >> 1) & (0x01U)); //49|1@1+ (1,0) [0|0] "" X + battery_BMS_a167_SW_FC_Partial_Weld = ((rx_frame.data.u8[6] >> 2) & (0x01U)); //50|1@1+ (1,0) [0|0] "" X + battery_BMS_a168_SW_FC_Full_Weld = ((rx_frame.data.u8[6] >> 3) & (0x01U)); //51|1@1+ (1,0) [0|0] "" X + battery_BMS_a169_SW_FC_Pack_Weld = ((rx_frame.data.u8[6] >> 4) & (0x01U)); //52|1@1+ (1,0) [0|0] "" X + battery_BMS_a170_SW_Limp_Mode = ((rx_frame.data.u8[6] >> 5) & (0x01U)); //53|1@1+ (1,0) [0|0] "" X + battery_BMS_a171_SW_Stack_Voltage_Sense = ((rx_frame.data.u8[6] >> 6) & (0x01U)); //54|1@1+ (1,0) [0|0] "" X + battery_BMS_a174_SW_Charge_Failure = ((rx_frame.data.u8[7] >> 1) & (0x01U)); //57|1@1+ (1,0) [0|0] "" X + battery_BMS_a176_SW_GracefulPowerOff = ((rx_frame.data.u8[7] >> 3) & (0x01U)); //59|1@1+ (1,0) [0|0] "" X + battery_BMS_a179_SW_Hvp_12V_Fault = ((rx_frame.data.u8[7] >> 6) & (0x01U)); //62|1@1+ (1,0) [0|0] "" X + battery_BMS_a180_SW_ECU_reset_blocked = ((rx_frame.data.u8[7] >> 7) & (0x01U)); //63|1@1+ (1,0) [0|0] "" X + } + break; default: break; } } -#ifdef DOUBLE_BATTERY +#ifdef DOUBLE_BATTERY //Need to update battery2 -void receive_can_battery2(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { static uint8_t mux = 0; static uint16_t temp = 0; switch (rx_frame.ID) { - case 0x352: - //SOC - battery2_nominal_full_pack_energy = - (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) - battery2_nominal_energy_remaining = (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0xF8) >> 3)) * - 0.1; //Example 1247 * 0.1 = 124.7kWh - battery2_expected_energy_remaining = (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | - ((rx_frame.data.u8[2] & 0xC0) >> 6)); //Example 622 (62.2kWh) - battery2_ideal_energy_remaining = (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0xFE) >> 1)) * - 0.1; //Example 311 * 0.1 = 31.1kWh - battery2_energy_to_charge_complete = (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0xF0) >> 4)) * - 0.1; //Example 147 * 0.1 = 14.7kWh - battery2_energy_buffer = - (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x80) >> 7)) * 0.1; //Example 1 * 0.1 = 0 - battery2_full_charge_complete = ((rx_frame.data.u8[7] & 0x80) >> 7); + case 0x352: // BMS_energyStatus // newer BMS >2021 + mux = (rx_frame.data.u8[0] & 0x02); //BMS_energyStatusIndex M : 0|2@1+ (1,0) [0|0] "" X + + if (mux == 0) { + battery2_nominal_full_pack_energy_m0 = + ((rx_frame.data.u8[3] << 8) | + rx_frame.data.u8[2]); //BMS_nominalFullPackEnergy m0 : 16|16@1+ (0.02,0) [0|0] "kWh" X + battery2_nominal_energy_remaining_m0 = + ((rx_frame.data.u8[5] << 8) | + rx_frame.data.u8[4]); //BMS_nominalEnergyRemaining m0 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery2_ideal_energy_remaining_m0 = + ((rx_frame.data.u8[7] << 8) | + rx_frame.data.u8[6]); //BMS_idealEnergyRemaining m0 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } + if (mux == 1) { + battery2_fully_charged = (rx_frame.data.u8[1] & 0x01); //BMS_fullyCharged m1 : 15|1@1+ (1,0) [0|1] "" X + battery2_energy_buffer_m1 = + (rx_frame.data.u8[3] | rx_frame.data.u8[2]); //BMS_energyBuffer m1 : 16|16@1+ (0.01,0) [0|0] "kWh" X + battery2_expected_energy_remaining_m1 = + (rx_frame.data.u8[5] | + rx_frame.data.u8[4]); //BMS_expectedEnergyRemaining m1 : 32|16@1+ (0.02,0) [0|0] "kWh" X + battery2_energy_to_charge_complete_m1 = + (rx_frame.data.u8[7] | + rx_frame.data.u8[6]); //BMS_energyToChargeComplete m1 : 48|16@1+ (0.02,0) [0|0] "kWh" X + } + if (mux == 2) {} + // Additional information needed on this mux, example frame: 02 26 02 20 02 80 00 00 doesn't change + // older BMS <2021 without mux + battery2_nominal_full_pack_energy = //BMS_nominalFullPackEnergy : 0|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[1] & (0x07U)) << 8) | (_d[0] & (0xFFU)); + (((rx_frame.data.u8[1] & 0x07) << 8) | (rx_frame.data.u8[0])); //Example 752 (75.2kWh) + battery2_nominal_energy_remaining = //BMS_nominalEnergyRemaining : 11|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[2] & (0x3FU)) << 5) | ((_d[1] >> 3) & (0x1FU)); + (((rx_frame.data.u8[2] & 0x3F) << 5) | ((rx_frame.data.u8[1] & 0x1F) >> 3)); //Example 1247 * 0.1 = 124.7kWh + battery2_expected_energy_remaining = //BMS_expectedEnergyRemaining : 22|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[4] & (0x01U)) << 10) | ((_d[3] & (0xFFU)) << 2) | ((_d[2] >> 6) & (0x03U)); + (((rx_frame.data.u8[4] & 0x01) << 10) | (rx_frame.data.u8[3] << 2) | + ((rx_frame.data.u8[2] & 0x03) >> 6)); //Example 622 (62.2kWh) + battery2_ideal_energy_remaining = //BMS_idealEnergyRemaining : 33|11@1+ (0.1,0) [0|204.6] "KWh" //((_d[5] & (0x0FU)) << 7) | ((_d[4] >> 1) & (0x7FU)); + (((rx_frame.data.u8[5] & 0x0F) << 7) | ((rx_frame.data.u8[4] & 0x7F) >> 1)); //Example 311 * 0.1 = 31.1kWh + battery2_energy_to_charge_complete = // BMS_energyToChargeComplete : 44|11@1+ (0.1,0) [0|204.6] "KWh"// ((_d[6] & (0x7FU)) << 4) | ((_d[5] >> 4) & (0x0FU)); + (((rx_frame.data.u8[6] & 0x7F) << 4) | ((rx_frame.data.u8[5] & 0x0F) << 4)); //Example 147 * 0.1 = 14.7kWh + battery2_energy_buffer = //BMS_energyBuffer : 55|8@1+ (0.1,0) [0|25.4] "KWh"// ((_d[7] & (0x7FU)) << 1) | ((_d[6] >> 7) & (0x01U)); + (((rx_frame.data.u8[7] & 0x7F) << 1) | ((rx_frame.data.u8[6] & 0x01) >> 7)); //Example 1 * 0.1 = 0 + battery2_full_charge_complete = //BMS_fullChargeComplete : 63|1@1+ (1,0) [0|1] ""//((_d[7] >> 7) & (0x01U)); + ((rx_frame.data.u8[7] & 0x01) >> 7); break; - case 0x20A: - //Contactor state + case 0x20A: //522 HVP_contactorState: battery2_packContNegativeState = (rx_frame.data.u8[0] & 0x07); battery2_packContPositiveState = (rx_frame.data.u8[0] & 0x38) >> 3; battery2_contactor = (rx_frame.data.u8[1] & 0x0F); @@ -644,6 +1894,90 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_packCtrsClosingAllowed = (rx_frame.data.u8[4] & 0x08) >> 3; battery2_pyroTestInProgress = (rx_frame.data.u8[4] & 0x20) >> 5; battery2_hvil_status = (rx_frame.data.u8[5] & 0x0F); + battery2_packCtrsOpenNowRequested = + ((rx_frame.data.u8[4] >> 1) & (0x01U)); //33|1@1+ (1,0) [0|1] //to datalayer_extended + battery2_packCtrsOpenRequested = + ((rx_frame.data.u8[4] >> 2) & (0x01U)); //34|1@1+ (1,0) [0|1] //to datalayer_extended + battery2_packCtrsRequestStatus = + ((rx_frame.data.u8[3] >> 6) & (0x03U)); //30|2@1+ (1,0) [0|2] //to datalayer_extended + battery2_packCtrsResetRequestRequired = + (rx_frame.data.u8[4] & (0x01U)); //32|1@1+ (1,0) [0|1] //to datalayer_extended + battery2_dcLinkAllowedToEnergize = + ((rx_frame.data.u8[4] >> 4) & (0x01U)); //36|1@1+ (1,0) [0|1] //to datalayer_extended + battery2_fcContNegativeAuxOpen = ((rx_frame.data.u8[0] >> 7) & (0x01U)); //7|1@1+ (1,0) [0|1] "" Receiver + battery2_fcContNegativeState = ((rx_frame.data.u8[1] >> 4) & (0x07U)); //12|3@1+ (1,0) [0|7] "" Receiver + battery2_fcContPositiveAuxOpen = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //6|1@1+ (1,0) [0|1] "" Receiver + battery2_fcContPositiveState = (rx_frame.data.u8[2] & (0x07U)); //16|3@1+ (1,0) [0|7] "" Receiver + battery2_fcContactorSetState = ((rx_frame.data.u8[2] >> 3) & (0x0FU)); //19|4@1+ (1,0) [0|9] "" Receiver + battery2_fcCtrsClosingAllowed = ((rx_frame.data.u8[3] >> 5) & (0x01U)); //29|1@1+ (1,0) [0|1] "" Receiver + battery2_fcCtrsOpenNowRequested = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|1] "" Receiver + battery2_fcCtrsOpenRequested = ((rx_frame.data.u8[3] >> 4) & (0x01U)); //28|1@1+ (1,0) [0|1] "" Receiver + battery2_fcCtrsRequestStatus = (rx_frame.data.u8[3] & (0x03U)); //24|2@1+ (1,0) [0|2] "" Receiver + battery2_fcCtrsResetRequestRequired = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //26|1@1+ (1,0) [0|1] "" Receiver + battery2_fcLinkAllowedToEnergize = ((rx_frame.data.u8[5] >> 4) & (0x03U)); //44|2@1+ (1,0) [0|2] "" Receiver + break; + case 0x212: //530 BMS_status: 8 + battery2_BMS_hvacPowerRequest = (rx_frame.data.u8[0] & (0x01U)); + battery2_BMS_notEnoughPowerForDrive = ((rx_frame.data.u8[0] >> 1) & (0x01U)); + battery2_BMS_notEnoughPowerForSupport = ((rx_frame.data.u8[0] >> 2) & (0x01U)); + battery2_BMS_preconditionAllowed = ((rx_frame.data.u8[0] >> 3) & (0x01U)); + battery2_BMS_updateAllowed = ((rx_frame.data.u8[0] >> 4) & (0x01U)); + battery2_BMS_activeHeatingWorthwhile = ((rx_frame.data.u8[0] >> 5) & (0x01U)); + battery2_BMS_cpMiaOnHvs = ((rx_frame.data.u8[0] >> 6) & (0x01U)); + battery2_BMS_contactorState = + (rx_frame.data.u8[1] & + (0x07U)); //0 "SNA" 1 "OPEN" 2 "OPENING" 3 "CLOSING" 4 "CLOSED" 5 "WELDED" 6 "BLOCKED" ; + battery2_BMS_state = + ((rx_frame.data.u8[1] >> 3) & + (0x0FU)); //0 "STANDBY" 1 "DRIVE" 2 "SUPPORT" 3 "CHARGE" 4 "FEIM" 5 "CLEAR_FAULT" 6 "FAULT" 7 "WELD" 8 "TEST" 9 "SNA" ; + battery2_BMS_hvState = + (rx_frame.data.u8[2] & + (0x07U)); //0 "DOWN" 1 "COMING_UP" 2 "GOING_DOWN" 3 "UP_FOR_DRIVE" 4 "UP_FOR_CHARGE" 5 "UP_FOR_DC_CHARGE" 6 "UP" ; + battery2_BMS_isolationResistance = + ((rx_frame.data.u8[3] & (0x1FU)) << 5) | + ((rx_frame.data.u8[2] >> 3) & (0x1FU)); //19|10@1+ (10,0) [0|0] "kOhm"/to datalayer_extended + battery2_BMS_chargeRequest = ((rx_frame.data.u8[3] >> 5) & (0x01U)); + battery2_BMS_keepWarmRequest = ((rx_frame.data.u8[3] >> 6) & (0x01U)); + battery2_BMS_uiChargeStatus = + (rx_frame.data.u8[4] & + (0x07U)); // 0 "DISCONNECTED" 1 "NO_POWER" 2 "ABOUT_TO_CHARGE" 3 "CHARGING" 4 "CHARGE_COMPLETE" 5 "CHARGE_STOPPED" ; + battery2_BMS_diLimpRequest = ((rx_frame.data.u8[4] >> 3) & (0x01U)); + battery2_BMS_okToShipByAir = ((rx_frame.data.u8[4] >> 4) & (0x01U)); + battery2_BMS_okToShipByLand = ((rx_frame.data.u8[4] >> 5) & (0x01U)); + battery2_BMS_chgPowerAvailable = ((rx_frame.data.u8[6] & (0x01U)) << 10) | + ((rx_frame.data.u8[5] & (0xFFU)) << 2) | + ((rx_frame.data.u8[4] >> 6) & (0x03U)); //38|11@1+ (0.125,0) [0|0] "kW" + battery2_BMS_chargeRetryCount = ((rx_frame.data.u8[6] >> 1) & (0x0FU)); + battery2_BMS_pcsPwmEnabled = ((rx_frame.data.u8[6] >> 5) & (0x01U)); + battery2_BMS_ecuLogUploadRequest = ((rx_frame.data.u8[6] >> 6) & (0x03U)); + battery2_BMS_minPackTemperature = (rx_frame.data.u8[7] & (0xFFU)); //56|8@1+ (0.5,-40) [0|0] "DegC + break; + case 0x224: //548 PCS_dcdcStatus: + battery2_PCS_dcdcPrechargeStatus = (rx_frame.data.u8[0] & (0x03U)); //0 "IDLE" 1 "ACTIVE" 2 "FAULTED" ; + battery2_PCS_dcdc12VSupportStatus = ((rx_frame.data.u8[0] >> 2) & (0x03U)); //0 "IDLE" 1 "ACTIVE" 2 "FAULTED" + battery2_PCS_dcdcHvBusDischargeStatus = ((rx_frame.data.u8[0] >> 4) & (0x03U)); //0 "IDLE" 1 "ACTIVE" 2 "FAULTED" + battery2_PCS_dcdcMainState = + ((rx_frame.data.u8[1] & (0x03U)) << 2) | + ((rx_frame.data.u8[0] >> 6) & + (0x03U)); //0 "STANDBY" 1 "12V_SUPPORT_ACTIVE" 2 "PRECHARGE_STARTUP" 3 "PRECHARGE_ACTIVE" 4 "DIS_HVBUS_ACTIVE" 5 "SHUTDOWN" 6 "FAULTED" ; + battery2_PCS_dcdcSubState = + ((rx_frame.data.u8[1] >> 2) & + (0x1FU)); //0 "PWR_UP_INIT" 1 "STANDBY" 2 "12V_SUPPORT_ACTIVE" 3 "DIS_HVBUS" 4 "PCHG_FAST_DIS_HVBUS" 5 "PCHG_SLOW_DIS_HVBUS" 6 "PCHG_DWELL_CHARGE" 7 "PCHG_DWELL_WAIT" 8 "PCHG_DI_RECOVERY_WAIT" 9 "PCHG_ACTIVE" 10 "PCHG_FLT_FAST_DIS_HVBUS" 11 "SHUTDOWN" 12 "12V_SUPPORT_FAULTED" 13 "DIS_HVBUS_FAULTED" 14 "PCHG_FAULTED" 15 "CLEAR_FAULTS" 16 "FAULTED" 17 "NUM" ; + battery2_PCS_dcdcFaulted = ((rx_frame.data.u8[1] >> 7) & (0x01U)); + battery2_PCS_dcdcOutputIsLimited = ((rx_frame.data.u8[3] >> 4) & (0x01U)); + battery2_PCS_dcdcMaxOutputCurrentAllowed = ((rx_frame.data.u8[5] & (0x01U)) << 11) | + ((rx_frame.data.u8[4] & (0xFFU)) << 3) | + ((rx_frame.data.u8[3] >> 5) & (0x07U)); //29|12@1+ (0.1,0) [0|0] "A" + battery2_PCS_dcdcPrechargeRtyCnt = ((rx_frame.data.u8[5] >> 1) & (0x07U)); + battery2_PCS_dcdc12VSupportRtyCnt = ((rx_frame.data.u8[5] >> 4) & (0x0FU)); + battery2_PCS_dcdcDischargeRtyCnt = (rx_frame.data.u8[6] & (0x0FU)); + battery2_PCS_dcdcPwmEnableLine = ((rx_frame.data.u8[6] >> 4) & (0x01U)); + battery2_PCS_dcdcSupportingFixedLvTarget = ((rx_frame.data.u8[6] >> 5) & (0x01U)); + battery2_PCS_ecuLogUploadRequest = ((rx_frame.data.u8[6] >> 6) & (0x03U)); + battery2_PCS_dcdcPrechargeRestartCnt = (rx_frame.data.u8[7] & (0x07U)); + battery2_PCS_dcdcInitialPrechargeSubState = + ((rx_frame.data.u8[7] >> 3) & + (0x1FU)); //0 "PWR_UP_INIT" 1 "STANDBY" 2 "12V_SUPPORT_ACTIVE" 3 "DIS_HVBUS" 4 "PCHG_FAST_DIS_HVBUS" 5 "PCHG_SLOW_DIS_HVBUS" 6 "PCHG_DWELL_CHARGE" 7 "PCHG_DWELL_WAIT" 8 "PCHG_DI_RECOVERY_WAIT" 9 "PCHG_ACTIVE" 10 "PCHG_FLT_FAST_DIS_HVBUS" 11 "SHUTDOWN" 12 "12V_SUPPORT_FAULTED" 13 "DIS_HVBUS_FAULTED" 14 "PCHG_FAULTED" 15 "CLEAR_FAULTS" 16 "FAULTED" 17 "NUM" ; break; case 0x252: //Limits @@ -662,14 +1996,12 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_amps = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 65492 (-4.3A) OR 225 (22.5A) battery2_raw_amps = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * -0.05; //Example 10425 * -0.05 = ? battery2_charge_time_remaining = - (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]) * 0.1; //Example 228 * 0.1 = 22.8min + (((rx_frame.data.u8[7] & 0x0F) << 8) | rx_frame.data.u8[6]); //Example 228 * 0.1 = 22.8min if (battery2_charge_time_remaining == 4095) { battery2_charge_time_remaining = 0; } - break; - case 0x3D2: - // total charge/discharge kwh + case 0x3D2: // total charge/discharge kwh battery2_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.001; @@ -677,8 +2009,7 @@ void receive_can_battery2(CAN_frame rx_frame) { rx_frame.data.u8[4]) * 0.001; break; - case 0x332: - //min/max hist values + case 0x332: //min/max hist values //BattBrickMinMax: mux = (rx_frame.data.u8[0] & 0x03); if (mux == 1) //Cell voltages @@ -689,14 +2020,118 @@ void receive_can_battery2(CAN_frame rx_frame) { temp = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); temp = (temp & 0xFFF); battery2_cell_min_v = temp * 2; - battery2_max_vno = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage - battery2_min_vno = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage + battery2_cellvoltagesRead = true; + //BattBrickVoltageMax m1 : 2|12@1+ (0.002,0) [0|0] "V" Receiver ((_d[1] & (0x3FU)) << 6) | ((_d[0] >> 2) & (0x3FU)); + battery2_BrickVoltageMax = + ((rx_frame.data.u8[1] & (0x3F)) << 6) | ((rx_frame.data.u8[0] >> 2) & (0x3F)); //to datalayer_extended + //BattBrickVoltageMin m1 : 16|12@1+ (0.002,0) [0|0] "V" Receiver ((_d[3] & (0x0FU)) << 8) | (_d[2] & (0xFFU)); + battery2_BrickVoltageMin = + ((rx_frame.data.u8[3] & (0x0F)) << 8) | (rx_frame.data.u8[2] & (0xFF)); //to datalayer_extended + //BattBrickVoltageMaxNum m1 : 32|7@1+ (1,1) [0|0] "" Receiver + battery2_BrickVoltageMaxNum = 1 + (rx_frame.data.u8[4] & 0x7F); //This cell has highest voltage + battery2_BrickVoltageMinNum = 1 + (rx_frame.data.u8[5] & 0x7F); //This cell has lowest voltage } if (mux == 0) //Temperature sensors { battery2_max_temp = (rx_frame.data.u8[2] * 5) - 400; //Temperature values have 40.0*C offset, 0.5*C /bit battery2_min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) + //BattBrickTempMaxNum m0 : 2|4@1+ (1,0) [0|0] "" ((_d[0] >> 2) & (0x0FU)); + battery2_BrickTempMaxNum = ((rx_frame.data.u8[0] >> 2) & (0x0F)); //to datalayer_extended + //BattBrickTempMinNum m0 : 8|4@1+ (1,0) [0|0] "" (_d[1] & (0x0FU)); + battery2_BrickTempMinNum = (rx_frame.data.u8[1] & (0x0F)); //to datalayer_extended + //BattBrickModelTMax m0 : 32|8@1+ (0.5,-40) [0|0] "C" (_d[4] & (0xFFU)); + battery2_BrickModelTMax = (rx_frame.data.u8[4] & (0xFFU)); //to datalayer_extended + //BattBrickModelTMin m0 : 40|8@1+ (0.5,-40) [0|0] "C" (_d[5] & (0xFFU)); + battery2_BrickModelTMin = (rx_frame.data.u8[5] & (0xFFU)); //to datalayer_extended + } + break; + case 0x312: // 786 BMS_thermalStatus + BMS2_powerDissipation = + ((rx_frame.data.u8[1] & (0x03U)) << 8) | (rx_frame.data.u8[0] & (0xFFU)); //0|10@1+ (0.02,0) [0|0] "kW" + BMS2_flowRequest = ((rx_frame.data.u8[2] & (0x01U)) << 6) | + ((rx_frame.data.u8[1] >> 2) & (0x3FU)); //10|7@1+ (0.3,0) [0|0] "LPM" + BMS2_inletActiveCoolTargetT = ((rx_frame.data.u8[3] & (0x03U)) << 7) | + ((rx_frame.data.u8[2] >> 1) & (0x7FU)); //17|9@1+ (0.25,-25) [0|0] "DegC" + BMS2_inletPassiveTargetT = ((rx_frame.data.u8[4] & (0x07U)) << 6) | + ((rx_frame.data.u8[3] >> 2) & (0x3FU)); //26|9@1+ (0.25,-25) [0|0] "DegC" X + BMS2_inletActiveHeatTargetT = ((rx_frame.data.u8[5] & (0x0FU)) << 5) | + ((rx_frame.data.u8[4] >> 3) & (0x1FU)); //35|9@1+ (0.25,-25) [0|0] "DegC" + BMS2_packTMin = ((rx_frame.data.u8[6] & (0x1FU)) << 4) | + ((rx_frame.data.u8[5] >> 4) & (0x0FU)); //44|9@1+ (0.25,-25) [-25|100] "DegC" + BMS2_packTMax = ((rx_frame.data.u8[7] & (0x3FU)) << 3) | + ((rx_frame.data.u8[6] >> 5) & (0x07U)); //53|9@1+ (0.25,-25) [-25|100] "DegC" + BMS2_pcsNoFlowRequest = ((rx_frame.data.u8[7] >> 6) & (0x01U)); // 62|1@1+ (1,0) [0|0] "" + BMS2_noFlowRequest = ((rx_frame.data.u8[7] >> 7) & (0x01U)); //63|1@1+ (1,0) [0|0] "" + break; + case 0x2A4: //676 PCS_thermalStatus + PCS2_chgPhATemp = + ((rx_frame.data.u8[1] & (0x07U)) << 8) | (rx_frame.data.u8[0] & (0xFFU)); //0|11@1- (0.1,40) [0|0] "C + PCS2_chgPhBTemp = + ((rx_frame.data.u8[2] & (0x3FU)) << 5) | ((rx_frame.data.u8[1] >> 3) & (0x1FU)); //11|11@1- (0.1,40) [0|0] "C + PCS2_chgPhCTemp = + ((rx_frame.data.u8[4] & (0x07U)) << 8) | (rx_frame.data.u8[3] & (0xFFU)); //24|11@1- (0.1,40) [0|0] "C" + PCS2_dcdcTemp = ((rx_frame.data.u8[5] & (0x3FU)) << 5) | + ((rx_frame.data.u8[4] >> 3) & (0x1FU)); //35|11@1- (0.1,40) [0|0] "C" + PCS2_ambientTemp = + ((rx_frame.data.u8[7] & (0x07U)) << 8) | (rx_frame.data.u8[6] & (0xFFU)); //48|11@1- (0.1,40) [0|0] "C" + break; + case 0x2C4: // 708 PCS_logging: not all frames are listed, just ones relating to dcdc + mux = (rx_frame.data.u8[0] & (0x1FU)); + //PCS_logMessageSelect = (rx_frame.data.u8[0] & (0x1FU)); //0|5@1+ (1,0) [0|0] "" + if (mux == 6) { + PCS2_dcdcMaxLvOutputCurrent = ((rx_frame.data.u8[4] & (0xFFU)) << 4) | + ((rx_frame.data.u8[3] >> 4) & (0x0FU)); //m6 : 28|12@1+ (0.1,0) [0|0] "A" X + PCS2_dcdcCurrentLimit = ((rx_frame.data.u8[6] & (0x0FU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //m6 : 40|12@1+ (0.1,0) [0|0] "A" X + PCS2_dcdcLvOutputCurrentTempLimit = + ((rx_frame.data.u8[7] & (0xFFU)) << 4) | + ((rx_frame.data.u8[6] >> 4) & (0x0FU)); //m6 : 52|12@1+ (0.1,0) [0|0] "A" X + } + if (mux == 7) { + PCS2_dcdcUnifiedCommand = ((rx_frame.data.u8[1] & (0x7FU)) << 3) | + ((rx_frame.data.u8[0] >> 5) & (0x07U)); //m7 : 5|10@1+ (0.001,0) [0|0] "1" X + PCS2_dcdcCLAControllerOutput = ((rx_frame.data.u8[3] & (0x03U)) << 8) | + (rx_frame.data.u8[2] & (0xFFU)); //m7 : 16|10@1+ (0.001,0) [0|0] "1" X + PCS2_dcdcTankVoltage = ((rx_frame.data.u8[4] & (0x1FU)) << 6) | + ((rx_frame.data.u8[3] >> 2) & (0x3FU)); //m7 : 26|11@1- (1,0) [0|0] "V" X + PCS2_dcdcTankVoltageTarget = ((rx_frame.data.u8[5] & (0x7FU)) << 3) | + ((rx_frame.data.u8[4] >> 5) & (0x07U)); // m7 : 37|10@1+ (1,0) [0|0] "V" X + PCS2_dcdcClaCurrentFreq = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //P m7 : 48|12@1+ (0.0976563,0) [0|0] "kHz" X + } + if (mux == 8) { + PCS2_dcdcTCommMeasured = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); // m8 : 8|16@1- (0.00195313,0) [0|0] "us" X + PCS2_dcdcShortTimeUs = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); // m8 : 24|16@1+ (0.000488281,0) [0|0] "us" X + PCS2_dcdcHalfPeriodUs = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); // m8 : 40|16@1+ (0.000488281,0) [0|0] "us" X + } + if (mux == 18) { + PCS2_dcdcIntervalMaxFrequency = ((rx_frame.data.u8[2] & (0x0FU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); // m18 : 8|12@1+ (1,0) [0|0] "kHz" X + PCS2_dcdcIntervalMaxHvBusVolt = ((rx_frame.data.u8[4] & (0x1FU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //m18 : 24|13@1+ (0.1,0) [0|0] "V" X + PCS2_dcdcIntervalMaxLvBusVolt = ((rx_frame.data.u8[5] & (0x3FU)) << 3) | + ((rx_frame.data.u8[4] >> 5) & (0x07U)); // m18 : 37|9@1+ (0.1,0) [0|0] "V" X + PCS2_dcdcIntervalMaxLvOutputCurr = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //m18 : 48|12@1+ (1,0) [0|0] "A" X + } + if (mux == 19) { + PCS2_dcdcIntervalMinFrequency = ((rx_frame.data.u8[2] & (0x0FU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //m19 : 8|12@1+ (1,0) [0|0] "kHz" X + PCS2_dcdcIntervalMinHvBusVolt = ((rx_frame.data.u8[4] & (0x1FU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //m19 : 24|13@1+ (0.1,0) [0|0] "V" X + PCS2_dcdcIntervalMinLvBusVolt = ((rx_frame.data.u8[5] & (0x3FU)) << 3) | + ((rx_frame.data.u8[4] >> 5) & (0x07U)); //m19 : 37|9@1+ (0.1,0) [0|0] "V" X + PCS2_dcdcIntervalMinLvOutputCurr = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); // m19 : 48|12@1+ (1,0) [0|0] "A" X + } + if (mux == 22) { + PCS2_dcdc12vSupportLifetimekWh = ((rx_frame.data.u8[3] & (0xFFU)) << 16) | + ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); // m22 : 8|24@1+ (0.01,0) [0|0] "kWh" X } break; case 0x401: // Cell stats @@ -730,29 +2165,140 @@ void receive_can_battery2(CAN_frame rx_frame) { } } break; - case 0x2d2: + case 0x2d2: //BMSVAlimits: //Min / max limits battery2_bms_min_voltage = - ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V + ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]); //Example 24148mv * 0.01 = 241.48 V battery2_bms_max_voltage = - ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V + ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]); //Example 40282mv * 0.01 = 402.82 V battery2_max_charge_current = (((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1? battery2_max_discharge_current = (((rx_frame.data.u8[7] & 0x3F) << 8) | rx_frame.data.u8[6]) * 0.128; //Example 430? * 0.128 = 55.4? break; - case 0x2b4: - battery2_low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; - battery2_high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; - battery2_output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; + case 0x2b4: //PCS_dcdcRailStatus: + battery2_dcdcLvBusVolt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); + battery2_dcdcHvBusVolt = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))); + battery2_dcdcLvOutputCurrent = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]); break; - case 0x292: + case 0x292: //BMS_socStatus datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS - battery2_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]); + battery2_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]) * 0.1; battery2_soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]); - battery2_soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); + battery2_soc_ui = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2)); battery2_soc_max = (((rx_frame.data.u8[3] & 0x3F) << 4) | ((rx_frame.data.u8[2] & 0xF0) >> 4)); battery2_soc_ave = ((rx_frame.data.u8[4] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6)); + battery2_battTempPct = ((rx_frame.data.u8[7] & 0x03) << 6) | (rx_frame.data.u8[6] & (0x3F) >> 2); + break; + case 0x392: //BMS_packConfig + mux = (rx_frame.data.u8[0] & (0xFF)); + if (mux == 1) { + battery2_packConfigMultiplexer = (rx_frame.data.u8[0] & (0xff)); + battery2_moduleType = (rx_frame.data.u8[1] & (0x07)); + battery2_packMass = (rx_frame.data.u8[2]); + battery2_platformMaxBusVoltage = ((rx_frame.data.u8[4] & 0x03) << 8) | (rx_frame.data.u8[3]); + } + if (mux == 0) { + battery2_reservedConfig = (rx_frame.data.u8[1] & (0x1F)); + } + break; + case 0x7AA: //1962 HVP_debugMessage: + mux = (rx_frame.data.u8[0] & (0x0FU)); + //HVP_debugMessageMultiplexer = (rx_frame.data.u8[0] & (0x0FU)); //0|4@1+ (1,0) [0|6] "" + if (mux == 0) { + HVP2_gpioPassivePyroDepl = ((rx_frame.data.u8[0] >> 4) & (0x01U)); //: 4|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPyroIsoEn = ((rx_frame.data.u8[0] >> 5) & (0x01U)); //: 5|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioCpFaultIn = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //: 6|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPackContPowerEn = ((rx_frame.data.u8[0] >> 7) & (0x01U)); //: 7|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioHvCablesOk = (rx_frame.data.u8[1] & (0x01U)); //: 8|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioHvpSelfEnable = ((rx_frame.data.u8[1] >> 1) & (0x01U)); //: 9|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioLed = ((rx_frame.data.u8[1] >> 2) & (0x01U)); //: 10|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioCrashSignal = ((rx_frame.data.u8[1] >> 3) & (0x01U)); //: 11|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioShuntDataReady = ((rx_frame.data.u8[1] >> 4) & (0x01U)); //: 12|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioFcContPosAux = ((rx_frame.data.u8[1] >> 5) & (0x01U)); //: 13|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioFcContNegAux = ((rx_frame.data.u8[1] >> 6) & (0x01U)); //: 14|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioBmsEout = ((rx_frame.data.u8[1] >> 7) & (0x01U)); //: 15|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioCpFaultOut = (rx_frame.data.u8[2] & (0x01U)); //: 16|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPyroPor = ((rx_frame.data.u8[2] >> 1) & (0x01U)); //: 17|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioShuntEn = ((rx_frame.data.u8[2] >> 2) & (0x01U)); //: 18|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioHvpVerEn = ((rx_frame.data.u8[2] >> 3) & (0x01U)); //: 19|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPackCoontPosFlywheel = ((rx_frame.data.u8[2] >> 4) & (0x01U)); //: 20|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioCpLatchEnable = ((rx_frame.data.u8[2] >> 5) & (0x01U)); //: 21|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPcsEnable = ((rx_frame.data.u8[2] >> 6) & (0x01U)); //: 22|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPcsDcdcPwmEnable = ((rx_frame.data.u8[2] >> 7) & (0x01U)); //: 23|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioPcsChargePwmEnable = (rx_frame.data.u8[3] & (0x01U)); //: 24|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioFcContPowerEnable = ((rx_frame.data.u8[3] >> 1) & (0x01U)); //: 25|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioHvilEnable = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //: 26|1@1+ (1,0) [0|1] "" Receiver + HVP2_gpioSecDrdy = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //: 27|1@1+ (1,0) [0|1] "" Receiver + HVP2_hvp1v5Ref = ((rx_frame.data.u8[4] & (0xFFU)) << 4) | + ((rx_frame.data.u8[3] >> 4) & (0x0FU)); //: 28|12@1+ (0.1,0) [0|3] "V" Receiver + HVP2_shuntCurrentDebug = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.1,0) [-3276.8|3276.7] "A" Receiver + HVP2_packCurrentMia = (rx_frame.data.u8[7] & (0x01U)); //: 56|1@1+ (1,0) [0|1] "" Receiver + HVP2_auxCurrentMia = ((rx_frame.data.u8[7] >> 1) & (0x01U)); //: 57|1@1+ (1,0) [0|1] "" Receiver + HVP2_currentSenseMia = ((rx_frame.data.u8[7] >> 2) & (0x03U)); //: 58|1@1+ (1,0) [0|1] "" Receiver + HVP2_shuntRefVoltageMismatch = ((rx_frame.data.u8[7] >> 3) & (0x01U)); //: 59|1@1+ (1,0) [0|1] "" Receiver + HVP2_shuntThermistorMia = ((rx_frame.data.u8[7] >> 4) & (0x01U)); //: 60|1@1+ (1,0) [0|1] "" Receiver + HVP2_shuntHwMia = ((rx_frame.data.u8[7] >> 5) & (0x01U)); //: 61|1@1+ (1,0) [0|1] "" Receiver + } + if (mux == 1) { + HVP2_dcLinkVoltage = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-3276.8|3276.7] "V" Receiver + HVP2_packVoltage = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|16@1- (0.1,0) [-3276.8|3276.7] "V" Receiver + HVP2_fcLinkVoltage = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.1,0) [-3276.8|3276.7] "V" Receiver + } + if (mux == 2) { + HVP2_packContVoltage = ((rx_frame.data.u8[1] & (0xFFU)) << 4) | + ((rx_frame.data.u8[0] >> 4) & (0x0FU)); //: 4|12@1+ (0.1,0) [0|30] "V" Receiver + HVP2_packNegativeV = ((rx_frame.data.u8[3] & (0xFFU)) << 8) | + (rx_frame.data.u8[2] & (0xFFU)); //: 16|16@1- (0.1,0) [-550|550] "V" Receiver + HVP2_packPositiveV = ((rx_frame.data.u8[5] & (0xFFU)) << 8) | + (rx_frame.data.u8[4] & (0xFFU)); //: 32|16@1- (0.1,0) [-550|550] "V" Receiver + HVP2_pyroAnalog = ((rx_frame.data.u8[7] & (0x0FU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //: 48|12@1+ (0.1,0) [0|3] "V" Receiver + } + if (mux == 3) { + HVP2_dcLinkNegativeV = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-550|550] "V" Receiver + HVP2_dcLinkPositiveV = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|16@1- (0.1,0) [-550|550] "V" Receiver + HVP2_fcLinkNegativeV = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.1,0) [-550|550] "V" Receiver + } + if (mux == 4) { + HVP2_fcContCoilCurrent = ((rx_frame.data.u8[1] & (0xFFU)) << 4) | + ((rx_frame.data.u8[0] >> 4) & (0x0FU)); //: 4|12@1+ (0.1,0) [0|7.5] "A" Receiver + HVP2_fcContVoltage = ((rx_frame.data.u8[3] & (0x0FU)) << 8) | + (rx_frame.data.u8[2] & (0xFFU)); //: 16|12@1+ (0.1,0) [0|30] "V" Receiver + HVP2_hvilInVoltage = ((rx_frame.data.u8[4] & (0xFFU)) << 4) | + ((rx_frame.data.u8[3] >> 4) & (0x0FU)); //: 28|12@1+ (0.1,0) [0|30] "V" Receiver + HVP2_hvilOutVoltage = ((rx_frame.data.u8[6] & (0x0FU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|12@1+ (0.1,0) [0|30] "V" Receiver + } + if (mux == 5) { + HVP2_fcLinkPositiveV = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-550|550] "V" Receiver + HVP2_packContCoilCurrent = ((rx_frame.data.u8[4] & (0x0FU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|12@1+ (0.1,0) [0|7.5] "A" Receiver + HVP2_battery12V = ((rx_frame.data.u8[5] & (0xFFU)) << 4) | + ((rx_frame.data.u8[4] >> 4) & (0x0FU)); //: 36|12@1+ (0.1,0) [0|30] "V" Receiver + HVP2_shuntRefVoltageDbg = + ((rx_frame.data.u8[7] & (0xFFU)) << 8) | + (rx_frame.data.u8[6] & (0xFFU)); //: 48|16@1- (0.001,0) [-32.768|32.767] "V" Receiver + } + if (mux == 6) { + HVP2_shuntAuxCurrentDbg = ((rx_frame.data.u8[2] & (0xFFU)) << 8) | + (rx_frame.data.u8[1] & (0xFFU)); //: 8|16@1- (0.1,0) [-3276.8|3276.7] "A" Receiver + HVP2_shuntBarTempDbg = ((rx_frame.data.u8[4] & (0xFFU)) << 8) | + (rx_frame.data.u8[3] & (0xFFU)); //: 24|16@1- (0.01,0) [-327.67|327.67] "C" Receiver + HVP2_shuntAsicTempDbg = ((rx_frame.data.u8[6] & (0xFFU)) << 8) | + (rx_frame.data.u8[5] & (0xFFU)); //: 40|16@1- (0.01,0) [-327.67|327.67] "C" Receiver + HVP2_shuntAuxCurrentStatus = (rx_frame.data.u8[7] & (0x03U)); //: 56|2@1+ (1,0) [0|3] "" Receiver + HVP2_shuntBarTempStatus = ((rx_frame.data.u8[7] >> 2) & (0x03U)); //: 58|2@1+ (1,0) [0|3] "" Receiver + HVP2_shuntAsicTempStatus = ((rx_frame.data.u8[7] >> 4) & (0x03U)); //: 60|2@1+ (1,0) [0|3] "" Receiver + } break; case 0x3aa: //HVP_alertMatrix1 battery2_WatchdogReset = (rx_frame.data.u8[0] & 0x01); @@ -807,6 +2353,110 @@ void receive_can_battery2(CAN_frame rx_frame) { battery2_fcCtrCloseFailed = ((rx_frame.data.u8[6] & 0x02) >> 1); battery2_shuntThermistorMia = ((rx_frame.data.u8[6] & 0x04) >> 2); break; + case 0x320: //800 BMS_alertMatrix //BMS_alertMatrix 800 BMS_alertMatrix: 8 VEH + mux = (rx_frame.data.u8[0] & (0x0F)); + if (mux == 0) + ; + { //mux0 + battery2_BMS_matrixIndex = (rx_frame.data.u8[0] & (0x0F)); // 0|4@1+ (1,0) [0|0] "" X + battery2_BMS_a017_SW_Brick_OV = ((rx_frame.data.u8[2] >> 4) & (0x01)); //20|1@1+ (1,0) [0|0] "" X + battery2_BMS_a018_SW_Brick_UV = ((rx_frame.data.u8[2] >> 5) & (0x01)); //21|1@1+ (1,0) [0|0] "" X + battery2_BMS_a019_SW_Module_OT = ((rx_frame.data.u8[2] >> 6) & (0x01)); //22|1@1+ (1,0) [0|0] "" X + battery2_BMS_a021_SW_Dr_Limits_Regulation = (rx_frame.data.u8[3] & (0x01U)); //24|1@1+ (1,0) [0|0] "" X + battery2_BMS_a022_SW_Over_Current = ((rx_frame.data.u8[3] >> 1) & (0x01U)); //25|1@1+ (1,0) [0|0] "" X + battery2_BMS_a023_SW_Stack_OV = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //26|1@1+ (1,0) [0|0] "" X + battery2_BMS_a024_SW_Islanded_Brick = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|0] "" X + battery2_BMS_a025_SW_PwrBalance_Anomaly = ((rx_frame.data.u8[3] >> 4) & (0x01U)); //28|1@1+ (1,0) [0|0] "" X + battery2_BMS_a026_SW_HFCurrent_Anomaly = ((rx_frame.data.u8[3] >> 5) & (0x01U)); //29|1@1+ (1,0) [0|0] "" X + battery2_BMS_a034_SW_Passive_Isolation = ((rx_frame.data.u8[4] >> 5) & (0x01U)); //37|1@1+ (1,0) [0|0] "" X ? + battery2_BMS_a035_SW_Isolation = ((rx_frame.data.u8[4] >> 6) & (0x01U)); //38|1@1+ (1,0) [0|0] "" X + battery2_BMS_a036_SW_HvpHvilFault = ((rx_frame.data.u8[4] >> 6) & (0x01U)); //39|1@1+ (1,0) [0|0] "" X + battery2_BMS_a037_SW_Flood_Port_Open = (rx_frame.data.u8[5] & (0x01U)); //40|1@1+ (1,0) [0|0] "" X + battery2_BMS_a039_SW_DC_Link_Over_Voltage = ((rx_frame.data.u8[5] >> 2) & (0x01U)); //42|1@1+ (1,0) [0|0] "" X + battery2_BMS_a041_SW_Power_On_Reset = ((rx_frame.data.u8[5] >> 4) & (0x01U)); //44|1@1+ (1,0) [0|0] "" X + battery2_BMS_a042_SW_MPU_Error = ((rx_frame.data.u8[5] >> 5) & (0x01U)); //45|1@1+ (1,0) [0|0] "" X + battery2_BMS_a043_SW_Watch_Dog_Reset = ((rx_frame.data.u8[5] >> 6) & (0x01U)); //46|1@1+ (1,0) [0|0] "" X + battery2_BMS_a044_SW_Assertion = ((rx_frame.data.u8[5] >> 7) & (0x01U)); //47|1@1+ (1,0) [0|0] "" X + battery2_BMS_a045_SW_Exception = (rx_frame.data.u8[6] & (0x01U)); //48|1@1+ (1,0) [0|0] "" X + battery2_BMS_a046_SW_Task_Stack_Usage = ((rx_frame.data.u8[6] >> 1) & (0x01U)); //49|1@1+ (1,0) [0|0] "" X + battery2_BMS_a047_SW_Task_Stack_Overflow = ((rx_frame.data.u8[6] >> 2) & (0x01U)); //50|1@1+ (1,0) [0|0] "" X + battery2_BMS_a048_SW_Log_Upload_Request = ((rx_frame.data.u8[6] >> 3) & (0x01U)); //51|1@1+ (1,0) [0|0] "" X + battery2_BMS_a050_SW_Brick_Voltage_MIA = ((rx_frame.data.u8[6] >> 5) & (0x01U)); //53|1@1+ (1,0) [0|0] "" X + battery2_BMS_a051_SW_HVC_Vref_Bad = ((rx_frame.data.u8[6] >> 6) & (0x01U)); //54|1@1+ (1,0) [0|0] "" X + battery2_BMS_a052_SW_PCS_MIA = ((rx_frame.data.u8[6] >> 7) & (0x01U)); //55|1@1+ (1,0) [0|0] "" X + battery2_BMS_a053_SW_ThermalModel_Sanity = (rx_frame.data.u8[7] & (0x01U)); //56|1@1+ (1,0) [0|0] "" X + battery2_BMS_a054_SW_Ver_Supply_Fault = ((rx_frame.data.u8[7] >> 1) & (0x01U)); //57|1@1+ (1,0) [0|0] "" X + battery2_BMS_a059_SW_Pack_Voltage_Sensing = ((rx_frame.data.u8[7] >> 6) & (0x01U)); //62|1@1+ (1,0) [0|0] "" X + battery2_BMS_a060_SW_Leakage_Test_Failure = ((rx_frame.data.u8[7] >> 7) & (0x01U)); //63|1@1+ (1,0) [0|0] "" X + } + if (mux == 1) { //mux1 + battery2_BMS_a061_robinBrickOverVoltage = ((rx_frame.data.u8[0] >> 4) & (0x01U)); //4|1@1+ (1,0) [0|0] "" X + battery2_BMS_a062_SW_BrickV_Imbalance = ((rx_frame.data.u8[0] >> 5) & (0x01U)); //5|1@1+ (1,0) [0|0] "" X + battery2_BMS_a063_SW_ChargePort_Fault = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //6|1@1+ (1,0) [0|0] "" X + battery2_BMS_a064_SW_SOC_Imbalance = ((rx_frame.data.u8[0] >> 7) & (0x01U)); //7|1@1+ (1,0) [0|0] "" X + battery2_BMS_a069_SW_Low_Power = ((rx_frame.data.u8[1] >> 4) & (0x01U)); //12|1@1+ (1,0) [0|0] "" X + battery2_BMS_a071_SW_SM_TransCon_Not_Met = ((rx_frame.data.u8[1] >> 6) & (0x01U)); //14|1@1+ (1,0) [0|0] "" X + battery2_BMS_a075_SW_Chg_Disable_Failure = ((rx_frame.data.u8[2] >> 2) & (0x01U)); //18|1@1+ (1,0) [0|0] "" X + battery2_BMS_a076_SW_Dch_While_Charging = ((rx_frame.data.u8[2] >> 3) & (0x01U)); //19|1@1+ (1,0) [0|0] "" X + battery2_BMS_a077_SW_Charger_Regulation = ((rx_frame.data.u8[2] >> 4) & (0x01U)); //20|1@1+ (1,0) [0|0] "" X + battery2_BMS_a081_SW_Ctr_Close_Blocked = (rx_frame.data.u8[3] & (0x01U)); //24|1@1+ (1,0) [0|0] "" X + battery2_BMS_a082_SW_Ctr_Force_Open = ((rx_frame.data.u8[3] >> 1) & (0x01U)); //25|1@1+ (1,0) [0|0] "" X + battery2_BMS_a083_SW_Ctr_Close_Failure = ((rx_frame.data.u8[3] >> 2) & (0x01U)); //26|1@1+ (1,0) [0|0] "" X + battery2_BMS_a084_SW_Sleep_Wake_Aborted = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|0] "" X + battery2_BMS_a087_SW_Feim_Test_Blocked = ((rx_frame.data.u8[3] >> 6) & (0x01U)); //30|1@1+ (1,0) [0|0] "" X + battery2_BMS_a088_SW_VcFront_MIA_InDrive = ((rx_frame.data.u8[3] >> 7) & (0x01U)); //31|1@1+ (1,0) [0|0] "" X + battery2_BMS_a089_SW_VcFront_MIA = (rx_frame.data.u8[4] & (0x01U)); //32|1@1+ (1,0) [0|0] "" X + battery2_BMS_a090_SW_Gateway_MIA = ((rx_frame.data.u8[4] >> 1) & (0x01U)); //33|1@1+ (1,0) [0|0] "" X + battery2_BMS_a091_SW_ChargePort_MIA = ((rx_frame.data.u8[4] >> 2) & (0x01U)); //34|1@1+ (1,0) [0|0] "" X + battery2_BMS_a092_SW_ChargePort_Mia_On_Hv = ((rx_frame.data.u8[4] >> 3) & (0x01U)); //35|1@1+ (1,0) [0|0] "" X + battery2_BMS_a094_SW_Drive_Inverter_MIA = ((rx_frame.data.u8[4] >> 5) & (0x01U)); //37|1@1+ (1,0) [0|0] "" X + battery2_BMS_a099_SW_BMB_Communication = ((rx_frame.data.u8[5] >> 2) & (0x01U)); //42|1@1+ (1,0) [0|0] "" X + battery2_BMS_a105_SW_One_Module_Tsense = (rx_frame.data.u8[6] & (0x01U)); //48|1@1+ (1,0) [0|0] "" X + battery2_BMS_a106_SW_All_Module_Tsense = ((rx_frame.data.u8[6] >> 1) & (0x01U)); //49|1@1+ (1,0) [0|0] "" X + battery2_BMS_a107_SW_Stack_Voltage_MIA = ((rx_frame.data.u8[6] >> 2) & (0x01U)); //50|1@1+ (1,0) [0|0] "" X + } + if (mux == 2) { //mux2 + battery2_BMS_a121_SW_NVRAM_Config_Error = ((rx_frame.data.u8[0] >> 4) & (0x01U)); // 4|1@1+ (1,0) [0|0] "" X + battery2_BMS_a122_SW_BMS_Therm_Irrational = ((rx_frame.data.u8[0] >> 5) & (0x01U)); //5|1@1+ (1,0) [0|0] "" X + battery2_BMS_a123_SW_Internal_Isolation = ((rx_frame.data.u8[0] >> 6) & (0x01U)); //6|1@1+ (1,0) [0|0] "" X + battery2_BMS_a127_SW_shunt_SNA = ((rx_frame.data.u8[1] >> 2) & (0x01U)); //10|1@1+ (1,0) [0|0] "" X + battery2_BMS_a128_SW_shunt_MIA = ((rx_frame.data.u8[1] >> 3) & (0x01U)); //11|1@1+ (1,0) [0|0] "" X + battery2_BMS_a129_SW_VSH_Failure = ((rx_frame.data.u8[1] >> 4) & (0x01U)); //12|1@1+ (1,0) [0|0] "" X + battery2_BMS_a130_IO_CAN_Error = ((rx_frame.data.u8[1] >> 5) & (0x01U)); //13|1@1+ (1,0) [0|0] "" X + battery2_BMS_a131_Bleed_FET_Failure = ((rx_frame.data.u8[1] >> 6) & (0x01U)); //14|1@1+ (1,0) [0|0] "" X + battery2_BMS_a132_HW_BMB_OTP_Uncorrctbl = ((rx_frame.data.u8[1] >> 7) & (0x01U)); //15|1@1+ (1,0) [0|0] "" X + battery2_BMS_a134_SW_Delayed_Ctr_Off = ((rx_frame.data.u8[2] >> 1) & (0x01U)); //17|1@1+ (1,0) [0|0] "" X + battery2_BMS_a136_SW_Module_OT_Warning = ((rx_frame.data.u8[2] >> 3) & (0x01U)); //19|1@1+ (1,0) [0|0] "" X + battery2_BMS_a137_SW_Brick_UV_Warning = ((rx_frame.data.u8[2] >> 4) & (0x01U)); //20|1@1+ (1,0) [0|0] "" X + battery2_BMS_a138_SW_Brick_OV_Warning = ((rx_frame.data.u8[2] >> 5) & (0x01U)); //21|1@1+ (1,0) [0|0] "" X + battery2_BMS_a139_SW_DC_Link_V_Irrational = ((rx_frame.data.u8[2] >> 6) & (0x01U)); //22|1@1+ (1,0) [0|0] "" X + battery2_BMS_a141_SW_BMB_Status_Warning = (rx_frame.data.u8[3] & (0x01U)); //24|1@1+ (1,0) [0|0] "" X + battery2_BMS_a144_Hvp_Config_Mismatch = ((rx_frame.data.u8[3] >> 3) & (0x01U)); //27|1@1+ (1,0) [0|0] "" X + battery2_BMS_a145_SW_SOC_Change = ((rx_frame.data.u8[3] >> 4) & (0x01U)); //28|1@1+ (1,0) [0|0] "" X + battery2_BMS_a146_SW_Brick_Overdischarged = ((rx_frame.data.u8[3] >> 5) & (0x01U)); //29|1@1+ (1,0) [0|0] "" X + battery2_BMS_a149_SW_Missing_Config_Block = (rx_frame.data.u8[4] & (0x01U)); //32|1@1+ (1,0) [0|0] "" X + battery2_BMS_a151_SW_external_isolation = ((rx_frame.data.u8[4] >> 2) & (0x01U)); //34|1@1+ (1,0) [0|0] "" X + battery2_BMS_a156_SW_BMB_Vref_bad = ((rx_frame.data.u8[4] >> 7) & (0x01U)); //39|1@1+ (1,0) [0|0] "" X + battery2_BMS_a157_SW_HVP_HVS_Comms = (rx_frame.data.u8[5] & (0x01U)); //40|1@1+ (1,0) [0|0] "" X + battery2_BMS_a158_SW_HVP_HVI_Comms = ((rx_frame.data.u8[5] >> 1) & (0x01U)); //41|1@1+ (1,0) [0|0] "" X + battery2_BMS_a159_SW_HVP_ECU_Error = ((rx_frame.data.u8[5] >> 2) & (0x01U)); //42|1@1+ (1,0) [0|0] "" X + battery2_BMS_a161_SW_DI_Open_Request = ((rx_frame.data.u8[5] >> 4) & (0x01U)); //44|1@1+ (1,0) [0|0] "" X + battery2_BMS_a162_SW_No_Power_For_Support = ((rx_frame.data.u8[5] >> 5) & (0x01U)); //45|1@1+ (1,0) [0|0] "" X + battery2_BMS_a163_SW_Contactor_Mismatch = ((rx_frame.data.u8[5] >> 6) & (0x01U)); //46|1@1+ (1,0) [0|0] "" X + battery2_BMS_a164_SW_Uncontrolled_Regen = ((rx_frame.data.u8[5] >> 7) & (0x01U)); //47|1@1+ (1,0) [0|0] "" X + battery2_BMS_a165_SW_Pack_Partial_Weld = (rx_frame.data.u8[6] & (0x01U)); //48|1@1+ (1,0) [0|0] "" X + battery2_BMS_a166_SW_Pack_Full_Weld = ((rx_frame.data.u8[6] >> 1) & (0x01U)); //49|1@1+ (1,0) [0|0] "" X + battery2_BMS_a167_SW_FC_Partial_Weld = ((rx_frame.data.u8[6] >> 2) & (0x01U)); //50|1@1+ (1,0) [0|0] "" X + battery2_BMS_a168_SW_FC_Full_Weld = ((rx_frame.data.u8[6] >> 3) & (0x01U)); //51|1@1+ (1,0) [0|0] "" X + battery2_BMS_a169_SW_FC_Pack_Weld = ((rx_frame.data.u8[6] >> 4) & (0x01U)); //52|1@1+ (1,0) [0|0] "" X + battery2_BMS_a170_SW_Limp_Mode = ((rx_frame.data.u8[6] >> 5) & (0x01U)); //53|1@1+ (1,0) [0|0] "" X + battery2_BMS_a171_SW_Stack_Voltage_Sense = ((rx_frame.data.u8[6] >> 6) & (0x01U)); //54|1@1+ (1,0) [0|0] "" X + battery2_BMS_a174_SW_Charge_Failure = ((rx_frame.data.u8[7] >> 1) & (0x01U)); //57|1@1+ (1,0) [0|0] "" X + battery2_BMS_a176_SW_GracefulPowerOff = ((rx_frame.data.u8[7] >> 3) & (0x01U)); //59|1@1+ (1,0) [0|0] "" X + battery2_BMS_a179_SW_Hvp_12V_Fault = ((rx_frame.data.u8[7] >> 6) & (0x01U)); //62|1@1+ (1,0) [0|0] "" X + battery2_BMS_a180_SW_ECU_reset_blocked = ((rx_frame.data.u8[7] >> 7) & (0x01U)); //63|1@1+ (1,0) [0|0] "" X + } + break; default: break; } @@ -817,7 +2467,7 @@ void update_values_battery2() { //This function maps all the values fetched via datalayer.battery2.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99% - datalayer.battery2.status.real_soc = (battery2_soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00 + datalayer.battery2.status.real_soc = (battery2_soc_ui * 10); //increase SOC range from 0-100.0 -> 100.00 datalayer.battery2.status.voltage_dV = (battery2_volts * 10); //One more decimal needed (370 -> 3700) @@ -835,12 +2485,12 @@ void update_values_battery2() { //This function maps all the values fetched via } //The allowed charge power behaves strangely. We instead estimate this value - if (battery2_soc_vi > 990) { + if (battery2_soc_ui > 990) { datalayer.battery2.status.max_charge_power_W = FLOAT_MAX_POWER_W; - } else if (battery2_soc_vi > + } else if (battery2_soc_ui > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 datalayer.battery2.status.max_charge_power_W = - MAXCHARGEPOWERALLOWED * (1 - (battery2_soc_vi - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); + MAXCHARGEPOWERALLOWED * (1 - (battery2_soc_ui - RAMPDOWN_SOC) / (1000.0 - RAMPDOWN_SOC)); //If the cellvoltages start to reach overvoltage, only allow a small amount of power in if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { if (battery2_cell_max_v > (MAX_CELL_VOLTAGE_LFP - FLOAT_START_MV)) { @@ -898,69 +2548,69 @@ void update_values_battery2() { //This function maps all the values fetched via } #endif // TESLA_MODEL_3Y_BATTERY -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG printFaultCodesIfActive_battery2(); - Serial.print("STATUS: Contactor: "); - Serial.print(contactorText[battery2_contactor]); //Display what state the contactor is in - Serial.print(", HVIL: "); - Serial.print(hvilStatusState[battery2_hvil_status]); - Serial.print(", NegativeState: "); - Serial.print(contactorState[battery2_packContNegativeState]); - Serial.print(", PositiveState: "); - Serial.print(contactorState[battery2_packContPositiveState]); - Serial.print(", setState: "); - Serial.print(contactorState[battery2_packContactorSetState]); - Serial.print(", close allowed: "); - Serial.print(battery2_packCtrsClosingAllowed); - Serial.print(", Pyrotest: "); - Serial.println(battery2_pyroTestInProgress); - - Serial.print("Battery2 values: "); - Serial.print("Real SOC: "); - Serial.print(battery2_soc_vi / 10.0, 1); + logging.print("STATUS: Contactor: "); + logging.print(contactorText[battery2_contactor]); //Display what state the contactor is in + logging.print(", HVIL: "); + logging.print(hvilStatusState[battery2_hvil_status]); + logging.print(", NegativeState: "); + logging.print(contactorState[battery2_packContNegativeState]); + logging.print(", PositiveState: "); + logging.print(contactorState[battery2_packContPositiveState]); + logging.print(", setState: "); + logging.print(contactorState[battery2_packContactorSetState]); + logging.print(", close allowed: "); + logging.print(battery2_packCtrsClosingAllowed); + logging.print(", Pyrotest: "); + logging.println(battery2_pyroTestInProgress); + + logging.print("Battery2 values: "); + logging.print("Real SOC: "); + logging.print(battery2_soc_ui / 10.0, 1); print_int_with_units(", Battery2 voltage: ", battery2_volts, "V"); print_int_with_units(", Battery2 HV current: ", (battery2_amps * 0.1), "A"); - Serial.print(", Fully charged?: "); + logging.print(", Fully charged?: "); if (battery2_full_charge_complete) - Serial.print("YES, "); + logging.print("YES, "); else - Serial.print("NO, "); + logging.print("NO, "); if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) { - Serial.print("LFP chemistry detected!"); + logging.print("LFP chemistry detected!"); } - Serial.println(""); - Serial.print("Cellstats, Max: "); - Serial.print(battery2_cell_max_v); - Serial.print("mV (cell "); - Serial.print(battery2_max_vno); - Serial.print("), Min: "); - Serial.print(battery2_cell_min_v); - Serial.print("mV (cell "); - Serial.print(battery2_min_vno); - Serial.print("), Imbalance: "); - Serial.print(battery2_cell_deviation_mV); - Serial.println("mV."); - - print_int_with_units("High Voltage Output Pins: ", battery2_high_voltage, "V"); - Serial.print(", "); - print_int_with_units("Low Voltage: ", battery2_low_voltage, "V"); - Serial.println(""); - print_int_with_units("DC/DC 12V current: ", battery2_output_current, "A"); - Serial.println(""); - - Serial.println("Values passed to the inverter: "); + logging.println(""); + logging.print("Cellstats, Max: "); + logging.print(battery2_cell_max_v); + logging.print("mV (cell "); + logging.print(battery2_max_vno); + logging.print("), Min: "); + logging.print(battery2_cell_min_v); + logging.print("mV (cell "); + logging.print(battery2_min_vno); + logging.print("), Imbalance: "); + logging.print(battery2_cell_deviation_mV); + logging.println("mV."); + + print_int_with_units("High Voltage Output Pins: ", battery2_dcdcHvBusVolt, "V"); + logging.print(", "); + print_int_with_units("Low Voltage: ", battery2_dcdcLvBusVolt, "V"); + logging.println(""); + print_int_with_units("DC/DC 12V current: ", battery2_dcdcLvOutputCurrent, "A"); + logging.println(""); + + logging.println("Values passed to the inverter: "); print_SOC(" SOC: ", datalayer.battery2.status.reported_soc); print_int_with_units(" Max discharge power: ", datalayer.battery2.status.max_discharge_power_W, "W"); - Serial.print(", "); + logging.print(", "); print_int_with_units(" Max charge power: ", datalayer.battery2.status.max_charge_power_W, "W"); - Serial.println(""); + logging.println(""); print_int_with_units(" Max temperature: ", ((int16_t)datalayer.battery2.status.temperature_min_dC * 0.1), "ยฐC"); - Serial.print(", "); + logging.print(", "); print_int_with_units(" Min temperature: ", ((int16_t)datalayer.battery2.status.temperature_max_dC * 0.1), "ยฐC"); - Serial.println(""); -#endif // DEBUG_VIA_USB + logging.println(""); +#endif // DEBUG_LOG } #endif //DOUBLE_BATTERY @@ -1001,7 +2651,7 @@ int index_1CF = 0; int index_118 = 0; #endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) -void send_can_battery() { +void transmit_can_battery() { /*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then, to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send @@ -1009,13 +2659,17 @@ the first, for a few cycles, then stop all messages which causes the contactor unsigned long currentMillis = millis(); + if (!cellvoltagesRead) { + return; //All cellvoltages not read yet, do not proceed with contactor closing + } + #if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) { if (currentMillis - lastSend1CF >= 10) { - transmit_can(&can_msg_1CF[index_1CF], can_config.battery); + transmit_can_frame(&can_msg_1CF[index_1CF], can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double); + transmit_can_frame(&can_msg_1CF[index_1CF], can_config.battery_double); #endif // DOUBLE_BATTERY index_1CF = (index_1CF + 1) % 8; @@ -1023,9 +2677,9 @@ the first, for a few cycles, then stop all messages which causes the contactor } if (currentMillis - lastSend118 >= 10) { - transmit_can(&can_msg_118[index_118], can_config.battery); + transmit_can_frame(&can_msg_118[index_118], can_config.battery); #ifdef DOUBLE_BATTERY - transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double); + transmit_can_frame(&can_msg_1CF[index_1CF], can_config.battery_double); #endif //DOUBLE_BATTERY index_118 = (index_118 + 1) % 16; @@ -1037,73 +2691,122 @@ the first, for a few cycles, then stop all messages which causes the contactor } #endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL) - //Send 30ms message - if (currentMillis - previousMillis30 >= INTERVAL_30_MS) { + //Send 50ms message + if (currentMillis - previousMillis50 >= INTERVAL_50_MS) { // Check if sending of CAN messages has been delayed too much. - if ((currentMillis - previousMillis30 >= INTERVAL_30_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { - set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis30)); + if ((currentMillis - previousMillis50 >= INTERVAL_50_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { + set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis50)); } else { clear_event(EVENT_CAN_OVERRUN); } - previousMillis30 = currentMillis; + previousMillis50 = currentMillis; if ((datalayer.system.status.inverter_allows_contactor_closing == true) && (datalayer.battery.status.bms_status != FAULT)) { sendContactorClosingMessagesStill = 300; - transmit_can(&TESLA_221_1, can_config.battery); - transmit_can(&TESLA_221_2, can_config.battery); + transmit_can_frame(&TESLA_221_1, can_config.battery); + transmit_can_frame(&TESLA_221_2, can_config.battery); #ifdef DOUBLE_BATTERY if (datalayer.system.status.battery2_allows_contactor_closing) { - transmit_can(&TESLA_221_1, can_config.battery_double); - transmit_can(&TESLA_221_2, can_config.battery_double); + transmit_can_frame(&TESLA_221_1, can_config.battery_double); + transmit_can_frame(&TESLA_221_2, can_config.battery_double); } #endif //DOUBLE_BATTERY } else { // Faulted state, or inverter blocks contactor closing if (sendContactorClosingMessagesStill > 0) { - transmit_can(&TESLA_221_1, can_config.battery); + transmit_can_frame(&TESLA_221_1, can_config.battery); sendContactorClosingMessagesStill--; #ifdef DOUBLE_BATTERY - transmit_can(&TESLA_221_1, can_config.battery_double); + transmit_can_frame(&TESLA_221_1, can_config.battery_double); #endif //DOUBLE_BATTERY } } } + + //Send 100ms message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + previousMillis100 = currentMillis; + + if (stateMachineClearIsolationFault != 0xFF) { + //This implementation should be rewritten to actually replying to the UDS replied sent by the BMS + //While this may work, it is not the correct way to implement this clearing logic + switch (stateMachineClearIsolationFault) { + case 0: + TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineClearIsolationFault = 1; + break; + case 1: + TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + // BMS should reply 02 50 C0 FF FF FF FF FF + stateMachineClearIsolationFault = 2; + break; + case 2: + TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36}; + transmit_can_frame(&TESLA_602, can_config.battery); + // BMS should reply 7E FF FF FF FF FF FF + stateMachineClearIsolationFault = 3; + break; + case 3: + TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineClearIsolationFault = 4; + break; + case 4: + TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineClearIsolationFault = 5; + break; + case 5: + TESLA_602.data = {0x04, 0x31, 0x01, 0x04, 0x0A, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602, can_config.battery); + stateMachineClearIsolationFault = 0xFF; + break; + default: + //Something went wrong. Reset all and cancel + stateMachineClearIsolationFault = 0xFF; + break; + } + } + } } void print_int_with_units(char* header, int value, char* units) { - Serial.print(header); - Serial.print(value); - Serial.print(units); + logging.print(header); + logging.print(value); + logging.print(units); } void print_SOC(char* header, int SOC) { - Serial.print(header); - Serial.print(SOC / 100); - Serial.print("."); + logging.print(header); + logging.print(SOC / 100); + logging.print("."); int hundredth = SOC % 100; if (hundredth < 10) - Serial.print(0); - Serial.print(hundredth); - Serial.println("%"); + logging.print(0); + logging.print(hundredth); + logging.println("%"); } void printFaultCodesIfActive() { if (battery_packCtrsClosingAllowed == 0) { - Serial.println( + logging.println( "ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: "); } if (battery_pyroTestInProgress == 1) { - Serial.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!"); + logging.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!"); } if (datalayer.system.status.inverter_allows_contactor_closing == false) { - Serial.println( + logging.println( "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter " "OR " "disable the inverter protocol to proceed with contactor closing"); } // Check each symbol and print debug information if its value is 1 - printDebugIfActive(battery_WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); + // 0X3AA: 938 HVP_alertMatrix1 + //printDebugIfActive(battery_WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); //Uncommented due to not affecting usage printDebugIfActive(battery_PowerLossReset, "ERROR: The processor has experienced a reset due to power loss"); printDebugIfActive(battery_SwAssertion, "ERROR: An internal software assertion has failed"); printDebugIfActive(battery_CrashEvent, "ERROR: crash signal is detected by HVP"); @@ -1161,25 +2864,117 @@ void printFaultCodesIfActive() { printDebugIfActive(battery_packCtrCloseFailed, "ERROR: packCtrCloseFailed is active"); printDebugIfActive(battery_fcCtrCloseFailed, "ERROR: fcCtrCloseFailed is active"); printDebugIfActive(battery_shuntThermistorMia, "ERROR: shuntThermistorMia is active"); + // 0x320 800 BMS_alertMatrix + printDebugIfActive(battery_BMS_a017_SW_Brick_OV, "ERROR: BMS_a017_SW_Brick_OV"); + printDebugIfActive(battery_BMS_a018_SW_Brick_UV, "ERROR: BMS_a018_SW_Brick_UV"); + printDebugIfActive(battery_BMS_a019_SW_Module_OT, "ERROR: BMS_a019_SW_Module_OT"); + printDebugIfActive(battery_BMS_a021_SW_Dr_Limits_Regulation, "ERROR: BMS_a021_SW_Dr_Limits_Regulation"); + printDebugIfActive(battery_BMS_a022_SW_Over_Current, "ERROR: BMS_a022_SW_Over_Current"); + printDebugIfActive(battery_BMS_a023_SW_Stack_OV, "ERROR: BMS_a023_SW_Stack_OV"); + printDebugIfActive(battery_BMS_a024_SW_Islanded_Brick, "ERROR: BMS_a024_SW_Islanded_Brick"); + printDebugIfActive(battery_BMS_a025_SW_PwrBalance_Anomaly, "ERROR: BMS_a025_SW_PwrBalance_Anomaly"); + printDebugIfActive(battery_BMS_a026_SW_HFCurrent_Anomaly, "ERROR: BMS_a026_SW_HFCurrent_Anomaly"); + printDebugIfActive(battery_BMS_a034_SW_Passive_Isolation, "ERROR: BMS_a034_SW_Passive_Isolation"); + printDebugIfActive(battery_BMS_a035_SW_Isolation, "ERROR: BMS_a035_SW_Isolation"); + printDebugIfActive(battery_BMS_a036_SW_HvpHvilFault, "ERROR: BMS_a036_SW_HvpHvilFault"); + printDebugIfActive(battery_BMS_a037_SW_Flood_Port_Open, "ERROR: BMS_a037_SW_Flood_Port_Open"); + printDebugIfActive(battery_BMS_a039_SW_DC_Link_Over_Voltage, "ERROR: BMS_a039_SW_DC_Link_Over_Voltage"); + printDebugIfActive(battery_BMS_a041_SW_Power_On_Reset, "ERROR: BMS_a041_SW_Power_On_Reset"); + printDebugIfActive(battery_BMS_a042_SW_MPU_Error, "ERROR: BMS_a042_SW_MPU_Error"); + printDebugIfActive(battery_BMS_a043_SW_Watch_Dog_Reset, "ERROR: BMS_a043_SW_Watch_Dog_Reset"); + printDebugIfActive(battery_BMS_a044_SW_Assertion, "ERROR: BMS_a044_SW_Assertion"); + printDebugIfActive(battery_BMS_a045_SW_Exception, "ERROR: BMS_a045_SW_Exception"); + printDebugIfActive(battery_BMS_a046_SW_Task_Stack_Usage, "ERROR: BMS_a046_SW_Task_Stack_Usage"); + printDebugIfActive(battery_BMS_a047_SW_Task_Stack_Overflow, "ERROR: BMS_a047_SW_Task_Stack_Overflow"); + printDebugIfActive(battery_BMS_a048_SW_Log_Upload_Request, "ERROR: BMS_a048_SW_Log_Upload_Request"); + printDebugIfActive(battery_BMS_a050_SW_Brick_Voltage_MIA, "ERROR: BMS_a050_SW_Brick_Voltage_MIA"); + printDebugIfActive(battery_BMS_a051_SW_HVC_Vref_Bad, "ERROR: BMS_a051_SW_HVC_Vref_Bad"); + printDebugIfActive(battery_BMS_a052_SW_PCS_MIA, "ERROR: BMS_a052_SW_PCS_MIA"); + printDebugIfActive(battery_BMS_a053_SW_ThermalModel_Sanity, "ERROR: BMS_a053_SW_ThermalModel_Sanity"); + printDebugIfActive(battery_BMS_a054_SW_Ver_Supply_Fault, "ERROR: BMS_a054_SW_Ver_Supply_Fault"); + printDebugIfActive(battery_BMS_a059_SW_Pack_Voltage_Sensing, "ERROR: BMS_a059_SW_Pack_Voltage_Sensing"); + printDebugIfActive(battery_BMS_a060_SW_Leakage_Test_Failure, "ERROR: BMS_a060_SW_Leakage_Test_Failure"); + printDebugIfActive(battery_BMS_a061_robinBrickOverVoltage, "ERROR: BMS_a061_robinBrickOverVoltage"); + printDebugIfActive(battery_BMS_a062_SW_BrickV_Imbalance, "ERROR: BMS_a062_SW_BrickV_Imbalance"); + printDebugIfActive(battery_BMS_a063_SW_ChargePort_Fault, "ERROR: BMS_a063_SW_ChargePort_Fault"); + printDebugIfActive(battery_BMS_a064_SW_SOC_Imbalance, "ERROR: BMS_a064_SW_SOC_Imbalance"); + printDebugIfActive(battery_BMS_a069_SW_Low_Power, "ERROR: BMS_a069_SW_Low_Power"); + printDebugIfActive(battery_BMS_a071_SW_SM_TransCon_Not_Met, "ERROR: BMS_a071_SW_SM_TransCon_Not_Met"); + printDebugIfActive(battery_BMS_a075_SW_Chg_Disable_Failure, "ERROR: BMS_a075_SW_Chg_Disable_Failure"); + printDebugIfActive(battery_BMS_a076_SW_Dch_While_Charging, "ERROR: BMS_a076_SW_Dch_While_Charging"); + printDebugIfActive(battery_BMS_a077_SW_Charger_Regulation, "ERROR: BMS_a077_SW_Charger_Regulation"); + printDebugIfActive(battery_BMS_a081_SW_Ctr_Close_Blocked, "ERROR: BMS_a081_SW_Ctr_Close_Blocked"); + printDebugIfActive(battery_BMS_a082_SW_Ctr_Force_Open, "ERROR: BMS_a082_SW_Ctr_Force_Open"); + printDebugIfActive(battery_BMS_a083_SW_Ctr_Close_Failure, "ERROR: BMS_a083_SW_Ctr_Close_Failure"); + printDebugIfActive(battery_BMS_a084_SW_Sleep_Wake_Aborted, "ERROR: BMS_a084_SW_Sleep_Wake_Aborted"); + printDebugIfActive(battery_BMS_a087_SW_Feim_Test_Blocked, "ERROR: BMS_a087_SW_Feim_Test_Blocked"); + printDebugIfActive(battery_BMS_a088_SW_VcFront_MIA_InDrive, "ERROR: BMS_a088_SW_VcFront_MIA_InDrive"); + printDebugIfActive(battery_BMS_a089_SW_VcFront_MIA, "ERROR: BMS_a089_SW_VcFront_MIA"); + printDebugIfActive(battery_BMS_a090_SW_Gateway_MIA, "ERROR: BMS_a090_SW_Gateway_MIA"); + printDebugIfActive(battery_BMS_a091_SW_ChargePort_MIA, "ERROR: BMS_a091_SW_ChargePort_MIA"); + printDebugIfActive(battery_BMS_a092_SW_ChargePort_Mia_On_Hv, "ERROR: BMS_a092_SW_ChargePort_Mia_On_Hv"); + printDebugIfActive(battery_BMS_a094_SW_Drive_Inverter_MIA, "ERROR: BMS_a094_SW_Drive_Inverter_MIA"); + printDebugIfActive(battery_BMS_a099_SW_BMB_Communication, "ERROR: BMS_a099_SW_BMB_Communication"); + printDebugIfActive(battery_BMS_a105_SW_One_Module_Tsense, "ERROR: BMS_a105_SW_One_Module_Tsense"); + printDebugIfActive(battery_BMS_a106_SW_All_Module_Tsense, "ERROR: BMS_a106_SW_All_Module_Tsense"); + printDebugIfActive(battery_BMS_a107_SW_Stack_Voltage_MIA, "ERROR: BMS_a107_SW_Stack_Voltage_MIA"); + printDebugIfActive(battery_BMS_a121_SW_NVRAM_Config_Error, "ERROR: BMS_a121_SW_NVRAM_Config_Error"); + printDebugIfActive(battery_BMS_a122_SW_BMS_Therm_Irrational, "ERROR: BMS_a122_SW_BMS_Therm_Irrational"); + printDebugIfActive(battery_BMS_a123_SW_Internal_Isolation, "ERROR: BMS_a123_SW_Internal_Isolation"); + printDebugIfActive(battery_BMS_a127_SW_shunt_SNA, "ERROR: BMS_a127_SW_shunt_SNA"); + printDebugIfActive(battery_BMS_a128_SW_shunt_MIA, "ERROR: BMS_a128_SW_shunt_MIA"); + printDebugIfActive(battery_BMS_a129_SW_VSH_Failure, "ERROR: BMS_a129_SW_VSH_Failure"); + printDebugIfActive(battery_BMS_a130_IO_CAN_Error, "ERROR: BMS_a130_IO_CAN_Error"); + printDebugIfActive(battery_BMS_a131_Bleed_FET_Failure, "ERROR: BMS_a131_Bleed_FET_Failure"); + printDebugIfActive(battery_BMS_a132_HW_BMB_OTP_Uncorrctbl, "ERROR: BMS_a132_HW_BMB_OTP_Uncorrctbl"); + printDebugIfActive(battery_BMS_a134_SW_Delayed_Ctr_Off, "ERROR: BMS_a134_SW_Delayed_Ctr_Off"); + printDebugIfActive(battery_BMS_a136_SW_Module_OT_Warning, "ERROR: BMS_a136_SW_Module_OT_Warning"); + printDebugIfActive(battery_BMS_a137_SW_Brick_UV_Warning, "ERROR: BMS_a137_SW_Brick_UV_Warning"); + printDebugIfActive(battery_BMS_a139_SW_DC_Link_V_Irrational, "ERROR: BMS_a139_SW_DC_Link_V_Irrational"); + printDebugIfActive(battery_BMS_a141_SW_BMB_Status_Warning, "ERROR: BMS_a141_SW_BMB_Status_Warning"); + printDebugIfActive(battery_BMS_a144_Hvp_Config_Mismatch, "ERROR: BMS_a144_Hvp_Config_Mismatch"); + printDebugIfActive(battery_BMS_a145_SW_SOC_Change, "ERROR: BMS_a145_SW_SOC_Change"); + printDebugIfActive(battery_BMS_a146_SW_Brick_Overdischarged, "ERROR: BMS_a146_SW_Brick_Overdischarged"); + printDebugIfActive(battery_BMS_a149_SW_Missing_Config_Block, "ERROR: BMS_a149_SW_Missing_Config_Block"); + printDebugIfActive(battery_BMS_a151_SW_external_isolation, "ERROR: BMS_a151_SW_external_isolation"); + printDebugIfActive(battery_BMS_a156_SW_BMB_Vref_bad, "ERROR: BMS_a156_SW_BMB_Vref_bad"); + printDebugIfActive(battery_BMS_a157_SW_HVP_HVS_Comms, "ERROR: BMS_a157_SW_HVP_HVS_Comms"); + printDebugIfActive(battery_BMS_a158_SW_HVP_HVI_Comms, "ERROR: BMS_a158_SW_HVP_HVI_Comms"); + printDebugIfActive(battery_BMS_a159_SW_HVP_ECU_Error, "ERROR: BMS_a159_SW_HVP_ECU_Error"); + printDebugIfActive(battery_BMS_a161_SW_DI_Open_Request, "ERROR: BMS_a161_SW_DI_Open_Request"); + printDebugIfActive(battery_BMS_a162_SW_No_Power_For_Support, "ERROR: BMS_a162_SW_No_Power_For_Support"); + printDebugIfActive(battery_BMS_a163_SW_Contactor_Mismatch, "ERROR: BMS_a163_SW_Contactor_Mismatch"); + printDebugIfActive(battery_BMS_a164_SW_Uncontrolled_Regen, "ERROR: BMS_a164_SW_Uncontrolled_Regen"); + printDebugIfActive(battery_BMS_a165_SW_Pack_Partial_Weld, "ERROR: BMS_a165_SW_Pack_Partial_Weld"); + printDebugIfActive(battery_BMS_a166_SW_Pack_Full_Weld, "ERROR: BMS_a166_SW_Pack_Full_Weld"); + printDebugIfActive(battery_BMS_a167_SW_FC_Partial_Weld, "ERROR: BMS_a167_SW_FC_Partial_Weld"); + printDebugIfActive(battery_BMS_a168_SW_FC_Full_Weld, "ERROR: BMS_a168_SW_FC_Full_Weld"); + printDebugIfActive(battery_BMS_a169_SW_FC_Pack_Weld, "ERROR: BMS_a169_SW_FC_Pack_Weld"); + printDebugIfActive(battery_BMS_a170_SW_Limp_Mode, "ERROR: BMS_a170_SW_Limp_Mode"); + printDebugIfActive(battery_BMS_a171_SW_Stack_Voltage_Sense, "ERROR: BMS_a171_SW_Stack_Voltage_Sense"); + printDebugIfActive(battery_BMS_a174_SW_Charge_Failure, "ERROR: BMS_a174_SW_Charge_Failure"); + printDebugIfActive(battery_BMS_a176_SW_GracefulPowerOff, "ERROR: BMS_a176_SW_GracefulPowerOff"); + printDebugIfActive(battery_BMS_a179_SW_Hvp_12V_Fault, "ERROR: BMS_a179_SW_Hvp_12V_Fault"); + printDebugIfActive(battery_BMS_a180_SW_ECU_reset_blocked, "ERROR: BMS_a180_SW_ECU_reset_blocked"); } -#ifdef DOUBLE_BATTERY +#ifdef DOUBLE_BATTERY //need to update battery2 void printFaultCodesIfActive_battery2() { if (battery2_packCtrsClosingAllowed == 0) { - Serial.println( + logging.println( "ERROR: Check high voltage connectors and interlock circuit! Closing contactor not allowed! Values: "); } if (battery2_pyroTestInProgress == 1) { - Serial.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!"); + logging.println("ERROR: Please wait for Pyro Connection check to finish, HV cables successfully seated!"); } if (datalayer.system.status.inverter_allows_contactor_closing == false) { - Serial.println( + logging.println( "ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter " "OR " "disable the inverter protocol to proceed with contactor closing"); } // Check each symbol and print debug information if its value is 1 - printDebugIfActive(battery2_WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); + //printDebugIfActive(battery2_WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset"); //Uncommented due to not affecting usage printDebugIfActive(battery2_PowerLossReset, "ERROR: The processor has experienced a reset due to power loss"); printDebugIfActive(battery2_SwAssertion, "ERROR: An internal software assertion has failed"); printDebugIfActive(battery2_CrashEvent, "ERROR: crash signal is detected by HVP"); @@ -1239,12 +3034,104 @@ void printFaultCodesIfActive_battery2() { printDebugIfActive(battery2_packCtrCloseFailed, "ERROR: packCtrCloseFailed is active"); printDebugIfActive(battery2_fcCtrCloseFailed, "ERROR: fcCtrCloseFailed is active"); printDebugIfActive(battery2_shuntThermistorMia, "ERROR: shuntThermistorMia is active"); + // 0x320 800 BMS_alertMatrix + printDebugIfActive(battery2_BMS_a017_SW_Brick_OV, "ERROR: BMS_a017_SW_Brick_OV"); + printDebugIfActive(battery2_BMS_a018_SW_Brick_UV, "ERROR: BMS_a018_SW_Brick_UV"); + printDebugIfActive(battery2_BMS_a019_SW_Module_OT, "ERROR: BMS_a019_SW_Module_OT"); + printDebugIfActive(battery2_BMS_a021_SW_Dr_Limits_Regulation, "ERROR: BMS_a021_SW_Dr_Limits_Regulation"); + printDebugIfActive(battery2_BMS_a022_SW_Over_Current, "ERROR: BMS_a022_SW_Over_Current"); + printDebugIfActive(battery2_BMS_a023_SW_Stack_OV, "ERROR: BMS_a023_SW_Stack_OV"); + printDebugIfActive(battery2_BMS_a024_SW_Islanded_Brick, "ERROR: BMS_a024_SW_Islanded_Brick"); + printDebugIfActive(battery2_BMS_a025_SW_PwrBalance_Anomaly, "ERROR: BMS_a025_SW_PwrBalance_Anomaly"); + printDebugIfActive(battery2_BMS_a026_SW_HFCurrent_Anomaly, "ERROR: BMS_a026_SW_HFCurrent_Anomaly"); + printDebugIfActive(battery2_BMS_a034_SW_Passive_Isolation, "ERROR: BMS_a034_SW_Passive_Isolation"); + printDebugIfActive(battery2_BMS_a035_SW_Isolation, "ERROR: BMS_a035_SW_Isolation"); + printDebugIfActive(battery2_BMS_a036_SW_HvpHvilFault, "ERROR: BMS_a036_SW_HvpHvilFault"); + printDebugIfActive(battery2_BMS_a037_SW_Flood_Port_Open, "ERROR: BMS_a037_SW_Flood_Port_Open"); + printDebugIfActive(battery2_BMS_a039_SW_DC_Link_Over_Voltage, "ERROR: BMS_a039_SW_DC_Link_Over_Voltage"); + printDebugIfActive(battery2_BMS_a041_SW_Power_On_Reset, "ERROR: BMS_a041_SW_Power_On_Reset"); + printDebugIfActive(battery2_BMS_a042_SW_MPU_Error, "ERROR: BMS_a042_SW_MPU_Error"); + printDebugIfActive(battery2_BMS_a043_SW_Watch_Dog_Reset, "ERROR: BMS_a043_SW_Watch_Dog_Reset"); + printDebugIfActive(battery2_BMS_a044_SW_Assertion, "ERROR: BMS_a044_SW_Assertion"); + printDebugIfActive(battery2_BMS_a045_SW_Exception, "ERROR: BMS_a045_SW_Exception"); + printDebugIfActive(battery2_BMS_a046_SW_Task_Stack_Usage, "ERROR: BMS_a046_SW_Task_Stack_Usage"); + printDebugIfActive(battery2_BMS_a047_SW_Task_Stack_Overflow, "ERROR: BMS_a047_SW_Task_Stack_Overflow"); + printDebugIfActive(battery2_BMS_a048_SW_Log_Upload_Request, "ERROR: BMS_a048_SW_Log_Upload_Request"); + printDebugIfActive(battery2_BMS_a050_SW_Brick_Voltage_MIA, "ERROR: BMS_a050_SW_Brick_Voltage_MIA"); + printDebugIfActive(battery2_BMS_a051_SW_HVC_Vref_Bad, "ERROR: BMS_a051_SW_HVC_Vref_Bad"); + printDebugIfActive(battery2_BMS_a052_SW_PCS_MIA, "ERROR: BMS_a052_SW_PCS_MIA"); + printDebugIfActive(battery2_BMS_a053_SW_ThermalModel_Sanity, "ERROR: BMS_a053_SW_ThermalModel_Sanity"); + printDebugIfActive(battery2_BMS_a054_SW_Ver_Supply_Fault, "ERROR: BMS_a054_SW_Ver_Supply_Fault"); + printDebugIfActive(battery2_BMS_a059_SW_Pack_Voltage_Sensing, "ERROR: BMS_a059_SW_Pack_Voltage_Sensing"); + printDebugIfActive(battery2_BMS_a060_SW_Leakage_Test_Failure, "ERROR: BMS_a060_SW_Leakage_Test_Failure"); + printDebugIfActive(battery2_BMS_a061_robinBrickOverVoltage, "ERROR: BMS_a061_robinBrickOverVoltage"); + printDebugIfActive(battery2_BMS_a062_SW_BrickV_Imbalance, "ERROR: BMS_a062_SW_BrickV_Imbalance"); + printDebugIfActive(battery2_BMS_a063_SW_ChargePort_Fault, "ERROR: BMS_a063_SW_ChargePort_Fault"); + printDebugIfActive(battery2_BMS_a064_SW_SOC_Imbalance, "ERROR: BMS_a064_SW_SOC_Imbalance"); + printDebugIfActive(battery2_BMS_a069_SW_Low_Power, "ERROR: BMS_a069_SW_Low_Power"); + printDebugIfActive(battery2_BMS_a071_SW_SM_TransCon_Not_Met, "ERROR: BMS_a071_SW_SM_TransCon_Not_Met"); + printDebugIfActive(battery2_BMS_a075_SW_Chg_Disable_Failure, "ERROR: BMS_a075_SW_Chg_Disable_Failure"); + printDebugIfActive(battery2_BMS_a076_SW_Dch_While_Charging, "ERROR: BMS_a076_SW_Dch_While_Charging"); + printDebugIfActive(battery2_BMS_a077_SW_Charger_Regulation, "ERROR: BMS_a077_SW_Charger_Regulation"); + printDebugIfActive(battery2_BMS_a081_SW_Ctr_Close_Blocked, "ERROR: BMS_a081_SW_Ctr_Close_Blocked"); + printDebugIfActive(battery2_BMS_a082_SW_Ctr_Force_Open, "ERROR: BMS_a082_SW_Ctr_Force_Open"); + printDebugIfActive(battery2_BMS_a083_SW_Ctr_Close_Failure, "ERROR: BMS_a083_SW_Ctr_Close_Failure"); + printDebugIfActive(battery2_BMS_a084_SW_Sleep_Wake_Aborted, "ERROR: BMS_a084_SW_Sleep_Wake_Aborted"); + printDebugIfActive(battery2_BMS_a087_SW_Feim_Test_Blocked, "ERROR: BMS_a087_SW_Feim_Test_Blocked"); + printDebugIfActive(battery2_BMS_a088_SW_VcFront_MIA_InDrive, "ERROR: BMS_a088_SW_VcFront_MIA_InDrive"); + printDebugIfActive(battery2_BMS_a089_SW_VcFront_MIA, "ERROR: BMS_a089_SW_VcFront_MIA"); + printDebugIfActive(battery2_BMS_a090_SW_Gateway_MIA, "ERROR: BMS_a090_SW_Gateway_MIA"); + printDebugIfActive(battery2_BMS_a091_SW_ChargePort_MIA, "ERROR: BMS_a091_SW_ChargePort_MIA"); + printDebugIfActive(battery2_BMS_a092_SW_ChargePort_Mia_On_Hv, "ERROR: BMS_a092_SW_ChargePort_Mia_On_Hv"); + printDebugIfActive(battery2_BMS_a094_SW_Drive_Inverter_MIA, "ERROR: BMS_a094_SW_Drive_Inverter_MIA"); + printDebugIfActive(battery2_BMS_a099_SW_BMB_Communication, "ERROR: BMS_a099_SW_BMB_Communication"); + printDebugIfActive(battery2_BMS_a105_SW_One_Module_Tsense, "ERROR: BMS_a105_SW_One_Module_Tsense"); + printDebugIfActive(battery2_BMS_a106_SW_All_Module_Tsense, "ERROR: BMS_a106_SW_All_Module_Tsense"); + printDebugIfActive(battery2_BMS_a107_SW_Stack_Voltage_MIA, "ERROR: BMS_a107_SW_Stack_Voltage_MIA"); + printDebugIfActive(battery2_BMS_a121_SW_NVRAM_Config_Error, "ERROR: BMS_a121_SW_NVRAM_Config_Error"); + printDebugIfActive(battery2_BMS_a122_SW_BMS_Therm_Irrational, "ERROR: BMS_a122_SW_BMS_Therm_Irrational"); + printDebugIfActive(battery2_BMS_a123_SW_Internal_Isolation, "ERROR: BMS_a123_SW_Internal_Isolation"); + printDebugIfActive(battery2_BMS_a127_SW_shunt_SNA, "ERROR: BMS_a127_SW_shunt_SNA"); + printDebugIfActive(battery2_BMS_a128_SW_shunt_MIA, "ERROR: BMS_a128_SW_shunt_MIA"); + printDebugIfActive(battery2_BMS_a129_SW_VSH_Failure, "ERROR: BMS_a129_SW_VSH_Failure"); + printDebugIfActive(battery2_BMS_a130_IO_CAN_Error, "ERROR: BMS_a130_IO_CAN_Error"); + printDebugIfActive(battery2_BMS_a131_Bleed_FET_Failure, "ERROR: BMS_a131_Bleed_FET_Failure"); + printDebugIfActive(battery2_BMS_a132_HW_BMB_OTP_Uncorrctbl, "ERROR: BMS_a132_HW_BMB_OTP_Uncorrctbl"); + printDebugIfActive(battery2_BMS_a134_SW_Delayed_Ctr_Off, "ERROR: BMS_a134_SW_Delayed_Ctr_Off"); + printDebugIfActive(battery2_BMS_a136_SW_Module_OT_Warning, "ERROR: BMS_a136_SW_Module_OT_Warning"); + printDebugIfActive(battery2_BMS_a137_SW_Brick_UV_Warning, "ERROR: BMS_a137_SW_Brick_UV_Warning"); + printDebugIfActive(battery2_BMS_a139_SW_DC_Link_V_Irrational, "ERROR: BMS_a139_SW_DC_Link_V_Irrational"); + printDebugIfActive(battery2_BMS_a141_SW_BMB_Status_Warning, "ERROR: BMS_a141_SW_BMB_Status_Warning"); + printDebugIfActive(battery2_BMS_a144_Hvp_Config_Mismatch, "ERROR: BMS_a144_Hvp_Config_Mismatch"); + printDebugIfActive(battery2_BMS_a145_SW_SOC_Change, "ERROR: BMS_a145_SW_SOC_Change"); + printDebugIfActive(battery2_BMS_a146_SW_Brick_Overdischarged, "ERROR: BMS_a146_SW_Brick_Overdischarged"); + printDebugIfActive(battery2_BMS_a149_SW_Missing_Config_Block, "ERROR: BMS_a149_SW_Missing_Config_Block"); + printDebugIfActive(battery2_BMS_a151_SW_external_isolation, "ERROR: BMS_a151_SW_external_isolation"); + printDebugIfActive(battery2_BMS_a156_SW_BMB_Vref_bad, "ERROR: BMS_a156_SW_BMB_Vref_bad"); + printDebugIfActive(battery2_BMS_a157_SW_HVP_HVS_Comms, "ERROR: BMS_a157_SW_HVP_HVS_Comms"); + printDebugIfActive(battery2_BMS_a158_SW_HVP_HVI_Comms, "ERROR: BMS_a158_SW_HVP_HVI_Comms"); + printDebugIfActive(battery2_BMS_a159_SW_HVP_ECU_Error, "ERROR: BMS_a159_SW_HVP_ECU_Error"); + printDebugIfActive(battery2_BMS_a161_SW_DI_Open_Request, "ERROR: BMS_a161_SW_DI_Open_Request"); + printDebugIfActive(battery2_BMS_a162_SW_No_Power_For_Support, "ERROR: BMS_a162_SW_No_Power_For_Support"); + printDebugIfActive(battery2_BMS_a163_SW_Contactor_Mismatch, "ERROR: BMS_a163_SW_Contactor_Mismatch"); + printDebugIfActive(battery2_BMS_a164_SW_Uncontrolled_Regen, "ERROR: BMS_a164_SW_Uncontrolled_Regen"); + printDebugIfActive(battery2_BMS_a165_SW_Pack_Partial_Weld, "ERROR: BMS_a165_SW_Pack_Partial_Weld"); + printDebugIfActive(battery2_BMS_a166_SW_Pack_Full_Weld, "ERROR: BMS_a166_SW_Pack_Full_Weld"); + printDebugIfActive(battery2_BMS_a167_SW_FC_Partial_Weld, "ERROR: BMS_a167_SW_FC_Partial_Weld"); + printDebugIfActive(battery2_BMS_a168_SW_FC_Full_Weld, "ERROR: BMS_a168_SW_FC_Full_Weld"); + printDebugIfActive(battery2_BMS_a169_SW_FC_Pack_Weld, "ERROR: BMS_a169_SW_FC_Pack_Weld"); + printDebugIfActive(battery2_BMS_a170_SW_Limp_Mode, "ERROR: BMS_a170_SW_Limp_Mode"); + printDebugIfActive(battery2_BMS_a171_SW_Stack_Voltage_Sense, "ERROR: BMS_a171_SW_Stack_Voltage_Sense"); + printDebugIfActive(battery2_BMS_a174_SW_Charge_Failure, "ERROR: BMS_a174_SW_Charge_Failure"); + printDebugIfActive(battery2_BMS_a176_SW_GracefulPowerOff, "ERROR: BMS_a176_SW_GracefulPowerOff"); + printDebugIfActive(battery2_BMS_a179_SW_Hvp_12V_Fault, "ERROR: BMS_a179_SW_Hvp_12V_Fault"); + printDebugIfActive(battery2_BMS_a180_SW_ECU_reset_blocked, "ERROR: BMS_a180_SW_ECU_reset_blocked"); } #endif //DOUBLE_BATTERY void printDebugIfActive(uint8_t symbol, const char* message) { if (symbol == 1) { - Serial.println(message); + logging.println(message); } } diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 4f6c1689a..51439722e 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -10,10 +10,11 @@ #define MAXDISCHARGEPOWERALLOWED 60000 // 60000W we use a define since the value supplied by Tesla is always 0 /* Do not change the defines below */ -#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00% -#define RAMPDOWNPOWERALLOWED 15000 // What power we ramp down from towards top balancing -#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery -#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging +#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00% +#define RAMPDOWNPOWERALLOWED \ + 15000 // What power we ramp down from towards top balancing (usually same as MAXCHARGEPOWERALLOWED) +#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery +#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging #define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops #define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops @@ -22,10 +23,10 @@ #define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops #define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops #define MAX_CELL_DEVIATION_NCA_NCM 500 //LED turns yellow on the board if mv delta exceeds this value -#define MAX_CELL_DEVIATION_LFP 200 //LED turns yellow on the board if mv delta exceeds this value +#define MAX_CELL_DEVIATION_LFP 400 //LED turns yellow on the board if mv delta exceeds this value #define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value -#define MAX_CELL_VOLTAGE_LFP 3550 //Battery is put into emergency stop if one cell goes over this value +#define MAX_CELL_VOLTAGE_LFP 3650 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value //#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware. @@ -35,7 +36,7 @@ void printDebugIfActive(uint8_t symbol, const char* message); void print_int_with_units(char* header, int value, char* units); void print_SOC(char* header, int SOC); void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #ifdef DOUBLE_BATTERY void printFaultCodesIfActive_battery2(); #endif //DOUBLE_BATTERY diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 30d9119dc..fb1cf729b 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -15,9 +15,9 @@ CAN_frame TEST = {.FD = false, .data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}}; void print_units(char* header, int value, char* units) { - Serial.print(header); - Serial.print(value); - Serial.print(units); + logging.print(header); + logging.print(value); + logging.print(units); } void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */ @@ -54,8 +54,8 @@ void update_values_battery() { /* This function puts fake values onto the parame datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; /*Finally print out values to serial if configured to do so*/ -#ifdef DEBUG_VIA_USB - Serial.println("FAKE Values going to inverter"); +#ifdef DEBUG_LOG + logging.println("FAKE Values going to inverter"); print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% "); print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% "); print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V "); @@ -65,7 +65,7 @@ void update_values_battery() { /* This function puts fake values onto the parame print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "ยฐC "); print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV "); print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV "); - Serial.println(""); + logging.println(""); #endif } @@ -107,8 +107,8 @@ void update_values_battery2() { // Handle the values coming in from battery #2 datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; /*Finally print out values to serial if configured to do so*/ -#ifdef DEBUG_VIA_USB - Serial.println("FAKE Values battery 2 going to inverter"); +#ifdef DEBUG_LOG + logging.println("FAKE Values battery 2 going to inverter"); print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% "); print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% "); print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V "); @@ -118,25 +118,25 @@ void update_values_battery2() { // Handle the values coming in from battery #2 print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "ยฐC "); print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV "); print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV "); - Serial.println(""); + logging.println(""); #endif } -void receive_can_battery2(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery2(CAN_frame rx_frame) { datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; } #endif // DOUBLE_BATTERY -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // 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(&TEST, can_config.battery); + //transmit_can_frame(&TEST, can_config.battery); } } diff --git a/Software/src/battery/TEST-FAKE-BATTERY.h b/Software/src/battery/TEST-FAKE-BATTERY.h index 27b56fe2a..c54f1577c 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.h +++ b/Software/src/battery/TEST-FAKE-BATTERY.h @@ -6,6 +6,6 @@ #define MAX_CELL_DEVIATION_MV 9999 void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index 839665c88..86b16f092 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -1,11 +1,13 @@ #include "../include.h" #ifdef VOLVO_SPA_BATTERY #include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage #include "../devboard/utils/events.h" #include "VOLVO-SPA-BATTERY.h" /* Do not change code below unless you are sure what you are doing */ static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send static float BATT_U = 0; //0x3A @@ -19,23 +21,42 @@ static float BATT_T_MIN = 0; //0x413 static float BATT_T_AVG = 0; //0x413 static uint16_t SOC_BMS = 0; //0X37D static uint16_t SOC_CALC = 0; -static uint16_t CELL_U_MAX = 3700; //0x37D -static uint16_t CELL_U_MIN = 3700; //0x37D -static uint8_t CELL_ID_U_MAX = 0; //0x37D -static uint16_t HvBattPwrLimDchaSoft = 0; //0x369 -static uint8_t batteryModuleNumber = 0x10; // First battery module +static uint16_t CELL_U_MAX = 3700; //0x37D +static uint16_t CELL_U_MIN = 3700; //0x37D +static uint8_t CELL_ID_U_MAX = 0; //0x37D +static uint16_t HvBattPwrLimDchaSoft = 0; //0x369 +static uint16_t HvBattPwrLimDcha1 = 0; //0x175 +static uint16_t HvBattPwrLimDchaSlowAgi = 0; //0x177 +static uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177 +static uint8_t batteryModuleNumber = 0x10; // First battery module static uint8_t battery_request_idx = 0; static uint8_t rxConsecutiveFrames = 0; static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV static uint8_t cellcounter = 0; static uint32_t remaining_capacity = 0; static uint16_t cell_voltages[108]; //array with all the cellvoltages +static bool startedUp = false; +static uint8_t DTC_reset_counter = 0; CAN_frame VOLVO_536 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x536, - .data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame + //.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame + .data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame + +CAN_frame VOLVO_140_CLOSE = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x140, + .data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message + +CAN_frame VOLVO_140_OPEN = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x140, + .data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message + CAN_frame VOLVO_372 = { .FD = false, .ext_ID = false, @@ -57,10 +78,62 @@ CAN_frame VOLVO_SOH_Req = {.FD = false, .DLC = 8, .ID = 0x735, .data = {0x03, 0x22, 0x49, 0x6D, 0x00, 0x00, 0x00, 0x00}}; //Battery SOH request frame +CAN_frame VOLVO_BECMsupplyVoltage_Req = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x735, + .data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame +CAN_frame VOLVO_DTC_Erase = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x7FF, + .data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}}; //Global DTC erase +CAN_frame VOLVO_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) +CAN_frame VOLVO_DTCreadout = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x7FF, + .data = {0x02, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Global DTC readout void update_values_battery() { //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.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; + + datalayer_extended.VolvoPolestar.HvBattPwrLimDcha1 = HvBattPwrLimDcha1; + datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSoft = HvBattPwrLimDchaSoft; + datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSlowAgi = HvBattPwrLimDchaSlowAgi; + datalayer_extended.VolvoPolestar.HvBattPwrLimChrgSlowAgi = HvBattPwrLimChrgSlowAgi; + + // Update requests from webserver datalayer + if (datalayer_extended.VolvoPolestar.UserRequestDTCreset) { + transmit_can_frame(&VOLVO_DTC_Erase, can_config.battery); //Send global DTC erase command + datalayer_extended.VolvoPolestar.UserRequestDTCreset = false; + } + if (datalayer_extended.VolvoPolestar.UserRequestBECMecuReset) { + transmit_can_frame(&VOLVO_BECM_ECUreset, can_config.battery); //Send BECM ecu reset command + datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = false; + } + if (datalayer_extended.VolvoPolestar.UserRequestDTCreadout) { + transmit_can_frame(&VOLVO_DTCreadout, can_config.battery); //Send DTC readout command + datalayer_extended.VolvoPolestar.UserRequestDTCreadout = false; + } + remaining_capacity = (78200 - 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 @@ -80,9 +153,8 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.current_dA = BATT_I * 10; datalayer.battery.status.remaining_capacity_Wh = remaining_capacity; - //datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSoft * 1000; // Use power limit reported from BMS, not trusted ATM - datalayer.battery.status.max_discharge_power_W = 30000; - datalayer.battery.status.max_charge_power_W = 30000; + datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSlowAgi * 1000; //kW to W + datalayer.battery.status.max_charge_power_W = HvBattPwrLimChrgSlowAgi * 1000; //kW to W datalayer.battery.status.temperature_min_dC = BATT_T_MIN; datalayer.battery.status.temperature_max_dC = BATT_T_MAX; @@ -94,53 +166,53 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i]; } -#ifdef DEBUG_VIA_USB - Serial.print("BMS reported SOC%: "); - Serial.println(SOC_BMS); - Serial.print("Calculated SOC%: "); - Serial.println(SOC_CALC); - Serial.print("Rescaled SOC%: "); - Serial.println(datalayer.battery.status.reported_soc / 100); - Serial.print("Battery current: "); - Serial.println(BATT_I); - Serial.print("Battery voltage: "); - Serial.println(BATT_U); - Serial.print("Battery maximum voltage limit: "); - Serial.println(MAX_U); - Serial.print("Battery minimum voltage limit: "); - Serial.println(MIN_U); - Serial.print("Remaining Energy: "); - Serial.println(remaining_capacity); - Serial.print("Discharge limit: "); - Serial.println(HvBattPwrLimDchaSoft); - Serial.print("Battery Error Indication: "); - Serial.println(BATT_ERR_INDICATION); - Serial.print("Maximum battery temperature: "); - Serial.println(BATT_T_MAX / 10); - Serial.print("Minimum battery temperature: "); - Serial.println(BATT_T_MIN / 10); - Serial.print("Average battery temperature: "); - Serial.println(BATT_T_AVG / 10); - Serial.print("BMS Highest cell voltage: "); - Serial.println(CELL_U_MAX * 10); - Serial.print("BMS Lowest cell voltage: "); - Serial.println(CELL_U_MIN * 10); - Serial.print("BMS Highest cell nr: "); - Serial.println(CELL_ID_U_MAX); - Serial.print("Highest cell voltage: "); - Serial.println(min_max_voltage[1]); - Serial.print("Lowest cell voltage: "); - Serial.println(min_max_voltage[0]); - Serial.print("Cell voltage,"); +#ifdef DEBUG_LOG + logging.print("BMS reported SOC%: "); + logging.println(SOC_BMS); + logging.print("Calculated SOC%: "); + logging.println(SOC_CALC); + logging.print("Rescaled SOC%: "); + logging.println(datalayer.battery.status.reported_soc / 100); + logging.print("Battery current: "); + logging.println(BATT_I); + logging.print("Battery voltage: "); + logging.println(BATT_U); + logging.print("Battery maximum voltage limit: "); + logging.println(MAX_U); + logging.print("Battery minimum voltage limit: "); + logging.println(MIN_U); + logging.print("Remaining Energy: "); + logging.println(remaining_capacity); + logging.print("Discharge limit: "); + logging.println(HvBattPwrLimDchaSoft); + logging.print("Battery Error Indication: "); + logging.println(BATT_ERR_INDICATION); + logging.print("Maximum battery temperature: "); + logging.println(BATT_T_MAX / 10); + logging.print("Minimum battery temperature: "); + logging.println(BATT_T_MIN / 10); + logging.print("Average battery temperature: "); + logging.println(BATT_T_AVG / 10); + logging.print("BMS Highest cell voltage: "); + logging.println(CELL_U_MAX * 10); + logging.print("BMS Lowest cell voltage: "); + logging.println(CELL_U_MIN * 10); + logging.print("BMS Highest cell nr: "); + logging.println(CELL_ID_U_MAX); + logging.print("Highest cell voltage: "); + logging.println(min_max_voltage[1]); + logging.print("Lowest cell voltage: "); + logging.println(min_max_voltage[0]); + logging.print("Cell voltage,"); while (cnt < 108) { - Serial.print(cell_voltages[cnt++]); - Serial.print(","); + logging.print(cell_voltages[cnt++]); + logging.print(","); } - Serial.println(";"); + logging.println(";"); #endif } -void receive_can_battery(CAN_frame rx_frame) { +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; switch (rx_frame.ID) { case 0x3A: @@ -148,8 +220,8 @@ void receive_can_battery(CAN_frame rx_frame) { BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638)); else { BATT_I = 0; -#ifdef DEBUG_VIA_USB - Serial.println("BATT_I not valid"); +#ifdef DEBUG_LOG + logging.println("BATT_I not valid"); #endif } @@ -157,24 +229,43 @@ void receive_can_battery(CAN_frame rx_frame) { MAX_U = (((rx_frame.data.u8[2] & 0x07) * 256.0 + rx_frame.data.u8[3]) * 0.25); else { //MAX_U = 0; - //Serial.println("MAX_U not valid"); // Value toggles between true/false from BMS + //logging.println("MAX_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) * 256.0 + rx_frame.data.u8[5]) * 0.25); else { //MIN_U = 0; - //Serial.println("MIN_U not valid"); // Value toggles between true/false from BMS + //logging.println("MIN_U not valid"); // Value toggles between true/false from BMS } 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); else { BATT_U = 0; -#ifdef DEBUG_VIA_USB - Serial.println("BATT_U not valid"); +#ifdef DEBUG_LOG + logging.println("BATT_U not valid"); #endif } + + if ((rx_frame.data.u8[0] & 0x40) == 0x40) + datalayer_extended.VolvoPolestar.HVSysRlySts = ((rx_frame.data.u8[0] & 0x30) >> 4); + else + datalayer_extended.VolvoPolestar.HVSysRlySts = 0xFF; + + if ((rx_frame.data.u8[2] & 0x40) == 0x40) + datalayer_extended.VolvoPolestar.HVSysDCRlySts1 = ((rx_frame.data.u8[2] & 0x30) >> 4); + else + datalayer_extended.VolvoPolestar.HVSysDCRlySts1 = 0xFF; + if ((rx_frame.data.u8[2] & 0x80) == 0x80) + datalayer_extended.VolvoPolestar.HVSysDCRlySts2 = ((rx_frame.data.u8[4] & 0x30) >> 4); + else + datalayer_extended.VolvoPolestar.HVSysDCRlySts2 = 0xFF; + if ((rx_frame.data.u8[0] & 0x80) == 0x80) + datalayer_extended.VolvoPolestar.HVSysIsoRMonrSts = ((rx_frame.data.u8[4] & 0xC0) >> 6); + else + datalayer_extended.VolvoPolestar.HVSysIsoRMonrSts = 0xFF; + break; case 0x1A1: if ((rx_frame.data.u8[4] & 0x10) == 0x10) @@ -189,8 +280,8 @@ void receive_can_battery(CAN_frame rx_frame) { BATT_ERR_INDICATION = ((rx_frame.data.u8[0] & 0x40) >> 6); else { BATT_ERR_INDICATION = 0; -#ifdef DEBUG_VIA_USB - Serial.println("BATT_ERR_INDICATION not valid"); +#ifdef DEBUG_LOG + logging.println("BATT_ERR_INDICATION not valid"); #endif } if ((rx_frame.data.u8[0] & 0x20) == 0x20) { @@ -201,8 +292,8 @@ void receive_can_battery(CAN_frame rx_frame) { BATT_T_MAX = 0; BATT_T_MIN = 0; BATT_T_AVG = 0; -#ifdef DEBUG_VIA_USB - Serial.println("BATT_T not valid"); +#ifdef DEBUG_LOG + logging.println("BATT_T not valid"); #endif } break; @@ -211,18 +302,37 @@ void receive_can_battery(CAN_frame rx_frame) { HvBattPwrLimDchaSoft = (((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[6]) >> 2); } else { HvBattPwrLimDchaSoft = 0; -#ifdef DEBUG_VIA_USB - Serial.println("HvBattPwrLimDchaSoft not valid"); +#ifdef DEBUG_LOG + logging.println("HvBattPwrLimDchaSoft not valid"); #endif } break; + case 0x175: + if ((rx_frame.data.u8[4] & 0x80) == 0x80) { + HvBattPwrLimDcha1 = (((rx_frame.data.u8[2] & 0x07) * 256 + rx_frame.data.u8[3]) >> 2); + } else { + HvBattPwrLimDcha1 = 0; + } + break; + case 0x177: + if ((rx_frame.data.u8[4] & 0x08) == 0x08) { + HvBattPwrLimDchaSlowAgi = (((rx_frame.data.u8[4] & 0x07) * 256 + rx_frame.data.u8[5]) >> 2); + } else { + HvBattPwrLimDchaSlowAgi = 0; + } + if ((rx_frame.data.u8[2] & 0x08) == 0x08) { + HvBattPwrLimChrgSlowAgi = (((rx_frame.data.u8[2] & 0x07) * 256 + rx_frame.data.u8[3]) >> 2); + } else { + HvBattPwrLimChrgSlowAgi = 0; + } + break; 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; -#ifdef DEBUG_VIA_USB - Serial.println("SOC_BMS not valid"); +#ifdef DEBUG_LOG + logging.println("SOC_BMS not valid"); #endif } @@ -230,8 +340,8 @@ void receive_can_battery(CAN_frame rx_frame) { CELL_U_MAX = ((rx_frame.data.u8[2] & 0x01) * 256 + rx_frame.data.u8[3]); else { CELL_U_MAX = 0; -#ifdef DEBUG_VIA_USB - Serial.println("CELL_U_MAX not valid"); +#ifdef DEBUG_LOG + logging.println("CELL_U_MAX not valid"); #endif } @@ -239,8 +349,8 @@ void receive_can_battery(CAN_frame rx_frame) { CELL_U_MIN = ((rx_frame.data.u8[0] & 0x01) * 256.0 + rx_frame.data.u8[1]); else { CELL_U_MIN = 0; -#ifdef DEBUG_VIA_USB - Serial.println("CELL_U_MIN not valid"); +#ifdef DEBUG_LOG + logging.println("CELL_U_MIN not valid"); #endif } @@ -248,8 +358,8 @@ void receive_can_battery(CAN_frame rx_frame) { CELL_ID_U_MAX = ((rx_frame.data.u8[4] & 0x01) * 256.0 + rx_frame.data.u8[5]); else { CELL_ID_U_MAX = 0; -#ifdef DEBUG_VIA_USB - Serial.println("CELL_ID_U_MAX not valid"); +#ifdef DEBUG_LOG + logging.println("CELL_ID_U_MAX not valid"); #endif } break; @@ -258,13 +368,22 @@ void receive_can_battery(CAN_frame rx_frame) { (rx_frame.data.u8[3] == 0x6D)) // SOH response frame { datalayer.battery.status.soh_pptt = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); + transmit_can_frame(&VOLVO_BECMsupplyVoltage_Req, can_config.battery); //Send BECM supply voltage req + } else if ((rx_frame.data.u8[0] == 0x05) && (rx_frame.data.u8[1] == 0x62) && (rx_frame.data.u8[2] == 0xF4) && + (rx_frame.data.u8[3] == 0x42)) // BECM module voltage supply + { + datalayer_extended.VolvoPolestar.BECMsupplyVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); } else if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[1] == 0x0B) && (rx_frame.data.u8[2] == 0x62) && (rx_frame.data.u8[3] == 0x4B)) // First response frame of cell voltages { cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8); - transmit_can(&VOLVO_FlowControl, can_config.battery); // Send flow control + transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // Send flow control rxConsecutiveFrames = 1; + } else if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[2] == 0x59) && + (rx_frame.data.u8[3] == 0x03)) // First response frame for DTC with more than one code + { + transmit_can_frame(&VOLVO_FlowControl, can_config.battery); // 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[2] << 8) | rx_frame.data.u8[3]; @@ -273,7 +392,7 @@ void receive_can_battery(CAN_frame rx_frame) { if (batteryModuleNumber <= 0x2A) // Run until last pack is read { VOLVO_CELL_U_Req.data.u8[3] = batteryModuleNumber++; - transmit_can(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for next module + transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for next module } else { min_max_voltage[0] = 9999; min_max_voltage[1] = 0; @@ -283,8 +402,7 @@ void receive_can_battery(CAN_frame rx_frame) { if (min_max_voltage[1] < cell_voltages[cellcounter]) min_max_voltage[1] = cell_voltages[cellcounter]; } - - transmit_can(&VOLVO_SOH_Req, can_config.battery); //Send SOH read request + transmit_can_frame(&VOLVO_SOH_Req, can_config.battery); //Send SOH read request } rxConsecutiveFrames = 0; } @@ -299,10 +417,10 @@ void readCellVoltages() { batteryModuleNumber = 0x10; rxConsecutiveFrames = 0; VOLVO_CELL_U_Req.data.u8[3] = batteryModuleNumber++; - transmit_can(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module + transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module } -void send_can_battery() { +void transmit_can_battery() { unsigned long currentMillis = millis(); // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { @@ -314,13 +432,26 @@ void send_can_battery() { } previousMillis100 = currentMillis; - transmit_can(&VOLVO_536, can_config.battery); //Send 0x536 Network managing frame to keep BMS alive - transmit_can(&VOLVO_372, can_config.battery); //Send 0x372 ECMAmbientTempCalculated + transmit_can_frame(&VOLVO_536, can_config.battery); //Send 0x536 Network managing frame to keep BMS alive + transmit_can_frame(&VOLVO_372, can_config.battery); //Send 0x372 ECMAmbientTempCalculated - if (datalayer.battery.status.bms_status == ACTIVE) { + if ((datalayer.battery.status.bms_status == ACTIVE) && startedUp) { datalayer.system.status.battery_allows_contactor_closing = true; - } else { //datalayer.battery.status.bms_status == FAULT or inverter requested opening contactors + transmit_can_frame(&VOLVO_140_CLOSE, can_config.battery); //Send 0x140 Close contactors message + } else { //datalayer.battery.status.bms_status == FAULT , OR inverter requested opening contactors, OR system not started yet datalayer.system.status.battery_allows_contactor_closing = false; + transmit_can_frame(&VOLVO_140_OPEN, can_config.battery); //Send 0x140 Open contactors message + } + } + if (currentMillis - previousMillis1s >= INTERVAL_1_S) { + previousMillis1s = currentMillis; + + if (!startedUp) { + transmit_can_frame(&VOLVO_DTC_Erase, can_config.battery); //Erase any DTCs preventing startup + DTC_reset_counter++; + if (DTC_reset_counter > 1) { // Performed twice before starting + startedUp = true; + } } } if (currentMillis - previousMillis60s >= INTERVAL_60_S) { diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.h b/Software/src/battery/VOLVO-SPA-BATTERY.h index 76c28074f..6dcda0e77 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.h +++ b/Software/src/battery/VOLVO-SPA-BATTERY.h @@ -11,6 +11,6 @@ #define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value void setup_battery(void); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); #endif diff --git a/Software/src/charger/CHARGERS.h b/Software/src/charger/CHARGERS.h index 7aa68c774..a41b5bc93 100644 --- a/Software/src/charger/CHARGERS.h +++ b/Software/src/charger/CHARGERS.h @@ -10,7 +10,7 @@ #include "NISSAN-LEAF-CHARGER.h" #endif -void receive_can_charger(CAN_frame rx_frame); -void send_can_charger(); +void map_can_frame_to_variable_charger(CAN_frame rx_frame); +void transmit_can_charger(); #endif diff --git a/Software/src/charger/CHEVY-VOLT-CHARGER.cpp b/Software/src/charger/CHEVY-VOLT-CHARGER.cpp index 75cfd0215..618c7d2a5 100644 --- a/Software/src/charger/CHEVY-VOLT-CHARGER.cpp +++ b/Software/src/charger/CHEVY-VOLT-CHARGER.cpp @@ -56,7 +56,7 @@ static CAN_frame charger_set_targets = { .data = {0x40, 0x00, 0x00, 0x00}}; // data[0] is a static value, meaning unknown /* We are mostly sending out not receiving */ -void receive_can_charger(CAN_frame rx_frame) { +void map_can_frame_to_variable_charger(CAN_frame rx_frame) { uint16_t charger_stat_HVcur_temp = 0; uint16_t charger_stat_HVvol_temp = 0; uint16_t charger_stat_LVcur_temp = 0; @@ -101,14 +101,14 @@ void receive_can_charger(CAN_frame rx_frame) { case 0x308: break; default: -#ifdef DEBUG_VIA_USB - Serial.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID); +#ifdef DEBUG_LOG + logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID); #endif break; } } -void send_can_charger() { +void transmit_can_charger() { unsigned long currentMillis = millis(); uint16_t Vol_temp = 0; @@ -137,7 +137,7 @@ void send_can_charger() { charger_keepalive_frame.data.u8[0] = charger_mode; - transmit_can(&charger_keepalive_frame, can_config.charger); + transmit_can_frame(&charger_keepalive_frame, can_config.charger); } /* Send current targets every 200ms */ @@ -174,18 +174,18 @@ void send_can_charger() { /* LSB of the voltage command. Then MSB LSB is divided by 2 */ charger_set_targets.data.u8[3] = lowByte(Vol_temp); - transmit_can(&charger_set_targets, can_config.charger); + transmit_can_frame(&charger_set_targets, can_config.charger); } -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG /* Serial echo every 5s of charger stats */ if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) { previousMillis5000ms = currentMillis; - Serial.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol); - Serial.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol); - Serial.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol); - Serial.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled"); - Serial.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END); + logging.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol); + logging.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol); + logging.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol); + 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); } #endif } diff --git a/Software/src/charger/NISSAN-LEAF-CHARGER.cpp b/Software/src/charger/NISSAN-LEAF-CHARGER.cpp index 0c756cf9f..104e6a9b6 100644 --- a/Software/src/charger/NISSAN-LEAF-CHARGER.cpp +++ b/Software/src/charger/NISSAN-LEAF-CHARGER.cpp @@ -129,7 +129,7 @@ static uint8_t calculate_checksum_nibble(CAN_frame* frame) { return sum; } -void receive_can_charger(CAN_frame rx_frame) { +void map_can_frame_to_variable_charger(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x679: // This message fires once when charging cable is plugged in @@ -166,7 +166,7 @@ void receive_can_charger(CAN_frame rx_frame) { } } -void send_can_charger() { +void transmit_can_charger() { unsigned long currentMillis = millis(); /* Send keepalive with mode every 10ms */ @@ -182,13 +182,13 @@ void send_can_charger() { #ifndef NISSAN_LEAF_BATTERY // VCM message, containing info if battery should sleep or stay awake - transmit_can(&LEAF_50B, can_config.charger); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 + transmit_can_frame(&LEAF_50B, can_config.charger); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 LEAF_1DB.data.u8[7] = calculate_CRC_Nissan(&LEAF_1DB); - transmit_can(&LEAF_1DB, can_config.charger); + transmit_can_frame(&LEAF_1DB, can_config.charger); LEAF_1DC.data.u8[7] = calculate_CRC_Nissan(&LEAF_1DC); - transmit_can(&LEAF_1DC, can_config.charger); + transmit_can_frame(&LEAF_1DC, can_config.charger); #endif OBCpowerSetpoint = ((charger_setpoint_HV_IDC * 4) + 0x64); @@ -233,8 +233,9 @@ void send_can_charger() { LEAF_1F2.data.u8[6] = mprun10; LEAF_1F2.data.u8[7] = calculate_checksum_nibble(&LEAF_1F2); - transmit_can(&LEAF_1F2, - can_config.charger); // Sending of 1F2 message is halted in LEAF-BATTERY function incase used here + transmit_can_frame( + &LEAF_1F2, + can_config.charger); // Sending of 1F2 message is halted in LEAF-BATTERY function incase used here } /* Send messages every 100ms here */ @@ -252,11 +253,11 @@ void send_can_charger() { LEAF_55B.data.u8[6] = ((0x1 << 4) | (mprun100)); LEAF_55B.data.u8[7] = calculate_CRC_Nissan(&LEAF_55B); - transmit_can(&LEAF_55B, can_config.charger); + transmit_can_frame(&LEAF_55B, can_config.charger); - transmit_can(&LEAF_59E, can_config.charger); + transmit_can_frame(&LEAF_59E, can_config.charger); - transmit_can(&LEAF_5BC, can_config.charger); + transmit_can_frame(&LEAF_5BC, can_config.charger); #endif } } diff --git a/Software/src/communication/can/comm_can.cpp b/Software/src/communication/can/comm_can.cpp new file mode 100644 index 000000000..3c9e1be4c --- /dev/null +++ b/Software/src/communication/can/comm_can.cpp @@ -0,0 +1,353 @@ +#include "comm_can.h" +#include "../../include.h" +#include "src/devboard/sdcard/sdcard.h" + +// Parameters + +CAN_device_t CAN_cfg; // CAN Config +const int rx_queue_size = 10; // Receive Queue size +volatile bool send_ok = 0; + +#ifdef CAN_ADDON +static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h +SPIClass SPI2515; +ACAN2515 can(MCP2515_CS, SPI2515, MCP2515_INT); +static ACAN2515_Buffer16 gBuffer; +#endif //CAN_ADDON +#ifdef CANFD_ADDON +SPIClass SPI2517; +ACAN2517FD canfd(MCP2517_CS, SPI2517, MCP2517_INT); +#endif //CANFD_ADDON + +// Initialization functions + +void init_CAN() { +// CAN pins +#ifdef CAN_SE_PIN + pinMode(CAN_SE_PIN, OUTPUT); + digitalWrite(CAN_SE_PIN, LOW); +#endif // CAN_SE_PIN + CAN_cfg.speed = CAN_SPEED_500KBPS; +#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed + CAN_cfg.speed = CAN_SPEED_250KBPS; +#endif // NATIVECAN_250KBPS + CAN_cfg.tx_pin_id = CAN_TX_PIN; + CAN_cfg.rx_pin_id = CAN_RX_PIN; + CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t)); + // Init CAN Module + ESP32Can.CANInit(); + +#ifdef CAN_ADDON +#ifdef DEBUG_LOG + logging.println("Dual CAN Bus (ESP32+MCP2515) selected"); +#endif // DEBUG_LOG + gBuffer.initWithSize(25); + SPI2515.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI); + ACAN2515Settings settings2515(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s + settings2515.mRequestedMode = ACAN2515Settings::NormalMode; + const uint16_t errorCode2515 = can.begin(settings2515, [] { can.isr(); }); + if (errorCode2515 == 0) { +#ifdef DEBUG_LOG + logging.println("Can ok"); +#endif // DEBUG_LOG + } else { +#ifdef DEBUG_LOG + logging.print("Error Can: 0x"); + logging.println(errorCode2515, HEX); +#endif // DEBUG_LOG + set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515); + } +#endif // CAN_ADDON + +#ifdef CANFD_ADDON +#ifdef DEBUG_LOG + logging.println("CAN FD add-on (ESP32+MCP2517) selected"); +#endif // DEBUG_LOG + SPI2517.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI); + ACAN2517FDSettings settings2517(CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000, + DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s +#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN + settings2517.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD +#else // not USE_CANFD_INTERFACE_AS_CLASSIC_CAN + settings2517.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD +#endif // USE_CANFD_INTERFACE_AS_CLASSIC_CAN + const uint32_t errorCode2517 = canfd.begin(settings2517, [] { canfd.isr(); }); + canfd.poll(); + if (errorCode2517 == 0) { +#ifdef DEBUG_LOG + logging.print("Bit Rate prescaler: "); + logging.println(settings2517.mBitRatePrescaler); + logging.print("Arbitration Phase segment 1: "); + logging.print(settings2517.mArbitrationPhaseSegment1); + logging.print(" segment 2: "); + logging.print(settings2517.mArbitrationPhaseSegment2); + logging.print(" SJW: "); + logging.println(settings2517.mArbitrationSJW); + logging.print("Actual Arbitration Bit Rate: "); + logging.print(settings2517.actualArbitrationBitRate()); + logging.print(" bit/s"); + logging.print(" (Exact:"); + logging.println(settings2517.exactArbitrationBitRate() ? "yes)" : "no)"); + logging.print("Arbitration Sample point: "); + logging.print(settings2517.arbitrationSamplePointFromBitStart()); + logging.println("%"); +#endif // DEBUG_LOG + } else { +#ifdef DEBUG_LOG + logging.print("CAN-FD Configuration error 0x"); + logging.println(errorCode2517, HEX); +#endif // DEBUG_LOG + set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517); + } +#endif // CANFD_ADDON +} + +// Transmit functions +void transmit_can() { + if (!allowed_to_send_CAN) { + return; + } + transmit_can_battery(); + +#ifdef CAN_INVERTER_SELECTED + transmit_can_inverter(); +#endif // CAN_INVERTER_SELECTED + +#ifdef CHARGER_SELECTED + transmit_can_charger(); +#endif // CHARGER_SELECTED + +#ifdef CAN_SHUNT_SELECTED + transmit_can_shunt(); +#endif // CAN_SHUNT_SELECTED +} + +void transmit_can_frame(CAN_frame* tx_frame, int interface) { + if (!allowed_to_send_CAN) { + return; + } + print_can_frame(*tx_frame, frameDirection(MSG_TX)); + +#ifdef LOG_CAN_TO_SD + add_can_frame_to_buffer(*tx_frame, frameDirection(MSG_TX)); +#endif + + switch (interface) { + case CAN_NATIVE: + CAN_frame_t frame; + frame.MsgID = tx_frame->ID; + frame.FIR.B.FF = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; + frame.FIR.B.DLC = tx_frame->DLC; + frame.FIR.B.RTR = CAN_no_RTR; + for (uint8_t i = 0; i < tx_frame->DLC; i++) { + frame.data.u8[i] = tx_frame->data.u8[i]; + } + ESP32Can.CANWriteFrame(&frame); + break; + case CAN_ADDON_MCP2515: { +#ifdef CAN_ADDON + //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 + CANMessage MCP2515Frame; + MCP2515Frame.id = tx_frame->ID; + MCP2515Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; + MCP2515Frame.len = tx_frame->DLC; + MCP2515Frame.rtr = false; + for (uint8_t i = 0; i < MCP2515Frame.len; i++) { + MCP2515Frame.data[i] = tx_frame->data.u8[i]; + } + can.tryToSend(MCP2515Frame); +#else // Interface not compiled, and settings try to use it + set_event(EVENT_INTERFACE_MISSING, interface); +#endif //CAN_ADDON + } break; + case CANFD_NATIVE: + case CANFD_ADDON_MCP2518: { +#ifdef CANFD_ADDON + CANFDMessage MCP2518Frame; + if (tx_frame->FD) { + MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH; + } else { //Classic CAN message + MCP2518Frame.type = CANFDMessage::CAN_DATA; + } + MCP2518Frame.id = tx_frame->ID; + MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std; + MCP2518Frame.len = tx_frame->DLC; + for (uint8_t i = 0; i < MCP2518Frame.len; i++) { + MCP2518Frame.data[i] = tx_frame->data.u8[i]; + } + send_ok = canfd.tryToSend(MCP2518Frame); + if (!send_ok) { + set_event(EVENT_CANFD_BUFFER_FULL, interface); + } else { + clear_event(EVENT_CANFD_BUFFER_FULL); + } +#else // Interface not compiled, and settings try to use it + set_event(EVENT_INTERFACE_MISSING, interface); +#endif //CANFD_ADDON + } break; + default: + // Invalid interface sent with function call. TODO: Raise event that coders messed up + break; + } +} + +// Receive functions +void receive_can() { + receive_frame_can_native(); // Receive CAN messages from native CAN port +#ifdef CAN_ADDON + receive_frame_can_addon(); // Receive CAN messages on add-on MCP2515 chip +#endif // CAN_ADDON +#ifdef CANFD_ADDON + receive_frame_canfd_addon(); // Receive CAN-FD messages. +#endif // CANFD_ADDON +} + +void receive_frame_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port + CAN_frame_t rx_frame_native; + if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame_native, 0) == pdTRUE) { + CAN_frame rx_frame; + rx_frame.ID = rx_frame_native.MsgID; + if (rx_frame_native.FIR.B.FF == CAN_frame_std) { + rx_frame.ext_ID = false; + } else { //CAN_frame_ext == 1 + rx_frame.ext_ID = true; + } + rx_frame.DLC = rx_frame_native.FIR.B.DLC; + for (uint8_t i = 0; i < rx_frame.DLC && i < 8; i++) { + rx_frame.data.u8[i] = rx_frame_native.data.u8[i]; + } + //message incoming, pass it on to the handler + map_can_frame_to_variable(&rx_frame, CAN_NATIVE); + } +} + +#ifdef CAN_ADDON +void receive_frame_can_addon() { // This section checks if we have a complete CAN message incoming on add-on CAN port + CAN_frame rx_frame; // Struct with our CAN format + CANMessage MCP2515frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library + + if (can.available()) { + can.receive(MCP2515frame); + + rx_frame.ID = MCP2515frame.id; + rx_frame.ext_ID = MCP2515frame.ext ? CAN_frame_ext : CAN_frame_std; + rx_frame.DLC = MCP2515frame.len; + for (uint8_t i = 0; i < MCP2515frame.len && i < 8; i++) { + rx_frame.data.u8[i] = MCP2515frame.data[i]; + } + + //message incoming, pass it on to the handler + map_can_frame_to_variable(&rx_frame, CAN_ADDON_MCP2515); + } +} +#endif // CAN_ADDON + +#ifdef CANFD_ADDON +void receive_frame_canfd_addon() { // This section checks if we have a complete CAN-FD message incoming + CANFDMessage MCP2518frame; + int count = 0; + while (canfd.available() && count++ < 16) { + canfd.receive(MCP2518frame); + + CAN_frame rx_frame; + rx_frame.ID = MCP2518frame.id; + rx_frame.ext_ID = MCP2518frame.ext; + rx_frame.DLC = MCP2518frame.len; + memcpy(rx_frame.data.u8, MCP2518frame.data, MIN(rx_frame.DLC, 64)); + //message incoming, pass it on to the handler + map_can_frame_to_variable(&rx_frame, CANFD_ADDON_MCP2518); + map_can_frame_to_variable(&rx_frame, CANFD_NATIVE); + } +} +#endif // CANFD_ADDON + +// Support functions +void print_can_frame(CAN_frame frame, frameDirection msgDir) { +#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB + uint8_t i = 0; + Serial.print("("); + Serial.print(millis() / 1000.0); + (msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 "); + Serial.print(frame.ID, HEX); + Serial.print(" ["); + Serial.print(frame.DLC); + Serial.print("] "); + for (i = 0; i < frame.DLC; i++) { + Serial.print(frame.data.u8[i] < 16 ? "0" : ""); + Serial.print(frame.data.u8[i], HEX); + if (i < frame.DLC - 1) + Serial.print(" "); + } + Serial.println(""); +#endif // DEBUG_CAN_DATA + + if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording + 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); + + if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) { + // Not enough space, reset and start from the beginning + offset = 0; + } + unsigned long currentTime = millis(); + // Add timestamp + offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000, + currentTime % 1000); + + // Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus. + offset += + snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1"); + + // Add ID and DLC + offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC); + + // Add data bytes + for (uint8_t i = 0; i < frame.DLC; i++) { + if (i < frame.DLC - 1) { + offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]); + } else { + offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]); + } + } + // Add linebreak + offset += snprintf(message_string + offset, message_string_size - offset, "\n"); + + datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer + } +} + +void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) { + print_can_frame(*rx_frame, frameDirection(MSG_RX)); + +#ifdef LOG_CAN_TO_SD + add_can_frame_to_buffer(*rx_frame, frameDirection(MSG_RX)); +#endif + + if (interface == can_config.battery) { + handle_incoming_can_frame_battery(*rx_frame); +#ifdef CHADEMO_BATTERY + ISA_handleFrame(rx_frame); +#endif + } + if (interface == can_config.inverter) { +#ifdef CAN_INVERTER_SELECTED + map_can_frame_to_variable_inverter(*rx_frame); +#endif + } + if (interface == can_config.battery_double) { +#ifdef DOUBLE_BATTERY + handle_incoming_can_frame_battery2(*rx_frame); +#endif + } + if (interface == can_config.charger) { +#ifdef CHARGER_SELECTED + map_can_frame_to_variable_charger(*rx_frame); +#endif + } + if (interface == can_config.shunt) { +#ifdef CAN_SHUNT_SELECTED + handle_incoming_can_frame_shunt(*rx_frame); +#endif + } +} diff --git a/Software/src/communication/can/comm_can.h b/Software/src/communication/can/comm_can.h new file mode 100644 index 000000000..a5117f16f --- /dev/null +++ b/Software/src/communication/can/comm_can.h @@ -0,0 +1,101 @@ +#ifndef _COMM_CAN_H_ +#define _COMM_CAN_H_ + +#include "../../include.h" + +#include "../../datalayer/datalayer.h" +#include "../../devboard/utils/events.h" +#include "../../devboard/utils/value_mapping.h" +#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h" +#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" +#ifdef CAN_ADDON +#include "../../lib/pierremolinaro-acan2515/ACAN2515.h" +#endif //CAN_ADDON +#ifdef CANFD_ADDON +#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" +#endif //CANFD_ADDON + +/** + * @brief Initialization function for CAN. + * + * @param[in] void + * + * @return void + */ +void init_CAN(); + +/** + * @brief Transmit one CAN frame + * + * @param[in] CAN_frame* tx_frame + * @param[in] int interface + * + * @return void + */ +void transmit_can_frame(); + +/** + * @brief Send CAN messages to all components + * + * @param[in] void + * + * @return void + */ +void transmit_can(); + +/** + * @brief Receive CAN messages from all interfaces + * + * @param[in] void + * + * @return void + */ +void receive_can(); + +/** + * @brief Receive CAN messages from CAN tranceiver natively installed on Lilygo hardware + * + * @param[in] void + * + * @return void + */ +void receive_frame_can_native(); + +/** + * @brief Receive CAN messages from CAN addon chip + * + * @param[in] void + * + * @return void + */ +void receive_frame_can_addon(); + +/** + * @brief Receive CAN messages from CANFD addon chip + * + * @param[in] void + * + * @return void + */ +void receive_frame_canfd_addon(); + +/** + * @brief print CAN frames via USB + * + * @param[in] void + * + * @return void + */ +void print_can_frame(CAN_frame frame, frameDirection msgDir); + +/** + * @brief Map CAN frame from specified interface to variable + * + * @param[in] CAN_frame* rx_frame + * @param[in] int interface + * + * @return void + */ +void map_can_frame_to_variable(CAN_frame* rx_frame, int interface); + +#endif diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp new file mode 100644 index 000000000..6b6db39e6 --- /dev/null +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp @@ -0,0 +1,302 @@ +#include "comm_contactorcontrol.h" +#include "../../include.h" + +// Parameters +#ifndef CONTACTOR_CONTROL +#ifdef PWM_CONTACTOR_CONTROL +#error CONTACTOR_CONTROL needs to be enabled for PWM_CONTACTOR_CONTROL +#endif +#endif + +#ifdef CONTACTOR_CONTROL +enum State { DISCONNECTED, START_PRECHARGE, PRECHARGE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED }; +State contactorStatus = DISCONNECTED; + +#define ON 1 +#define OFF 0 + +#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic +#undef ON +#define ON 0 +#undef OFF +#define OFF 1 +#endif //NC_CONTACTORS + +#define MAX_ALLOWED_FAULT_TICKS 1000 +#define NEGATIVE_CONTACTOR_TIME_MS \ + 500 // Time after negative contactor is turned on, to start precharge (not actual precharge time!) +#define PRECHARGE_COMPLETED_TIME_MS \ + 1000 // After successful precharge, resistor is turned off after this delay (and contactors are economized if PWM enabled) +#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range +#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100% +#define PWM_HOLD_DUTY 250 +#define PWM_OFF_DUTY 0 +#define PWM_ON_DUTY 1023 +#define PWM_Positive_Channel 0 +#define PWM_Negative_Channel 1 +unsigned long prechargeStartTime = 0; +unsigned long negativeStartTime = 0; +unsigned long prechargeCompletedTime = 0; +unsigned long timeSpentInFaultedMode = 0; +#endif +unsigned long currentTime = 0; +unsigned long lastPowerRemovalTime = 0; +const unsigned long powerRemovalInterval = 24 * 60 * 60 * 1000; // 24 hours in milliseconds +const unsigned long powerRemovalDuration = 30000; // 30 seconds in milliseconds +bool isBMSResetActive = false; + +void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) { +#ifdef PWM_CONTACTOR_CONTROL + if (pwm_freq != 0xFFFF) { + ledcWrite(pin, pwm_freq); + return; + } +#endif + if (direction == 1) { + digitalWrite(pin, HIGH); + } else { // 0 + digitalWrite(pin, LOW); + } +} + +// Initialization functions + +void init_contactors() { + // Init contactor pins +#ifdef CONTACTOR_CONTROL +#ifdef PWM_CONTACTOR_CONTROL + // Setup PWM Channel Frequency and Resolution + ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Positive_Channel); + ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Negative_Channel); + // Set all pins OFF (0% PWM) + ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); + ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); +#else //Normal CONTACTOR_CONTROL + pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); + set(POSITIVE_CONTACTOR_PIN, OFF); + pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); + set(NEGATIVE_CONTACTOR_PIN, OFF); +#endif // Precharge never has PWM regardless of setting + pinMode(PRECHARGE_PIN, OUTPUT); + set(PRECHARGE_PIN, OFF); +#endif // CONTACTOR_CONTROL +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT); + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); +#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY +// Init BMS contactor +#if defined HW_STARK || defined HW_3LB // This hardware has dedicated pin, always enable on start + pinMode(BMS_POWER, OUTPUT); //LilyGo is omitted from this, only enabled if user selects PERIODIC_BMS_RESET + digitalWrite(BMS_POWER, HIGH); +#ifdef BMS_2_POWER //Hardware supports 2x BMS + pinMode(BMS_2_POWER, OUTPUT); + digitalWrite(BMS_2_POWER, HIGH); +#endif BMS_2_POWER +#endif // HW with dedicated BMS pins +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) // User has enabled BMS reset, turn on output on start + pinMode(BMS_POWER, OUTPUT); + digitalWrite(BMS_POWER, HIGH); +#ifdef BMS_2_POWER //Hardware supports 2x BMS + pinMode(BMS_2_POWER, OUTPUT); + digitalWrite(BMS_2_POWER, HIGH); +#endif //BMS_2_POWER +#endif //PERIODIC_BMS_RESET +} + +static void dbg_contactors(const char* state) { +#ifdef DEBUG_LOG + logging.print("["); + logging.print(millis()); + logging.print(" ms] contactors control: "); + logging.println(state); +#endif +} + +// Main functions of the handle_contactors include checking if inverter allows for closing, checking battery 2, checking BMS power output, and actual contactor closing/precharge via GPIO +void handle_contactors() { +#if defined(SMA_BYD_H_CAN) || defined(SMA_BYD_HVS_CAN) || defined(SMA_TRIPOWER_CAN) + datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN); +#endif + + handle_BMSpower(); // Some batteries need to be periodically power cycled + +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY + handle_contactors_battery2(); +#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY + +#ifdef CONTACTOR_CONTROL + // First check if we have any active errors, incase we do, turn off the battery + if (datalayer.battery.status.bms_status == FAULT) { + timeSpentInFaultedMode++; + } else { + timeSpentInFaultedMode = 0; + } + + //handle contactor control SHUTDOWN_REQUESTED + if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) { + contactorStatus = SHUTDOWN_REQUESTED; + } + + if (contactorStatus == SHUTDOWN_REQUESTED) { + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); + datalayer.system.status.contactors_engaged = false; + return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) + } + + // After that, check if we are OK to start turning on the battery + if (contactorStatus == DISCONNECTED) { + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + + if (datalayer.system.status.battery_allows_contactor_closing && + datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) { + contactorStatus = START_PRECHARGE; + } + } + + // 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) { + contactorStatus = DISCONNECTED; + } + // Skip running the state machine below if it has already completed + return; + } + + currentTime = millis(); + + if (currentTime < INTERVAL_10_S) { + // Skip running the state machine before system has started up. + // Gives the system some time to detect any faults from battery before blindly just engaging the contactors + return; + } + + // Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge + switch (contactorStatus) { + case START_PRECHARGE: + set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); + dbg_contactors("NEGATIVE"); + prechargeStartTime = currentTime; + contactorStatus = PRECHARGE; + break; + + case PRECHARGE: + if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) { + set(PRECHARGE_PIN, ON); + dbg_contactors("PRECHARGE"); + negativeStartTime = currentTime; + contactorStatus = POSITIVE; + } + break; + + case POSITIVE: + if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) { + set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); + dbg_contactors("POSITIVE"); + prechargeCompletedTime = currentTime; + contactorStatus = PRECHARGE_OFF; + } + break; + + case PRECHARGE_OFF: + if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) { + set(PRECHARGE_PIN, OFF); + set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); + set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); + dbg_contactors("PRECHARGE_OFF"); + contactorStatus = COMPLETED; + datalayer.system.status.contactors_engaged = true; + } + break; + default: + break; + } +#endif // CONTACTOR_CONTROL +} + +#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY +void handle_contactors_battery2() { + if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) { + set(SECOND_NEGATIVE_CONTACTOR_PIN, ON); + set(SECOND_POSITIVE_CONTACTOR_PIN, ON); + datalayer.system.status.contactors_battery2_engaged = true; + } else { // Closing contactors on secondary battery not allowed + set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF); + set(SECOND_POSITIVE_CONTACTOR_PIN, OFF); + datalayer.system.status.contactors_battery2_engaged = false; + } +} +#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY + +/* 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. + +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 in the USER_SETTINGS */ + +void handle_BMSpower() { +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) + // Get current time + currentTime = millis(); + +#ifdef PERIODIC_BMS_RESET + // Check if 24 hours have passed since the last power removal + if (currentTime - lastPowerRemovalTime >= powerRemovalInterval) { + start_bms_reset(); + } +#endif //PERIODIC_BMS_RESET + + // If power has been removed for 30 seconds, restore the power and resume the emulator + if (isBMSResetActive && currentTime - lastPowerRemovalTime >= powerRemovalDuration) { + // Reapply power to the BMS + digitalWrite(BMS_POWER, HIGH); + + //Resume the battery pause and CAN communication + setBatteryPause(false, false, false, false); + + isBMSResetActive = false; // Reset the power removal flag + } +#endif //defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) +} + +void start_bms_reset() { +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) + if (!isBMSResetActive) { + lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started + + // Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop) + // TODO: We try to keep contactors engaged during this pause, and just ramp power down to 0. + // If this turns out to not work properly, set also the third option to true to open contactors + setBatteryPause(true, true, false, false); + + digitalWrite(BMS_POWER, LOW); // Remove power by setting the BMS power pin to LOW +#ifdef BMS_2_POWER + digitalWrite(BMS_2_POWER, LOW); // Same for battery 2 +#endif + + isBMSResetActive = true; // Set a flag to indicate power removal is active + } + + // If power has been removed for 30 seconds, restore the power and resume the emulator + if (isBMSResetActive && currentTime - lastPowerRemovalTime >= powerRemovalDuration) { + // Reapply power to the BMS + digitalWrite(BMS_POWER, HIGH); +#ifdef BMS_2_POWER + digitalWrite(BMS_2_POWER, HIGH); // Same for battery 2 +#endif + + //Resume the battery pause and CAN communication + setBatteryPause(false, false, false, false); + + isBMSResetActive = false; // Reset the power removal flag + } +#endif //PERIODIC_BMS_RESET +} diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h new file mode 100644 index 000000000..febfd1186 --- /dev/null +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h @@ -0,0 +1,54 @@ +#ifndef _COMM_CONTACTORCONTROL_H_ +#define _COMM_CONTACTORCONTROL_H_ + +#include "../../include.h" + +#include "../../datalayer/datalayer.h" +#include "../../devboard/utils/events.h" + +/** + * @brief Handle BMS power output + * + * @param[in] void + * + * @return void + */ +void handle_BMSpower(); + +/** + * @brief Start BMS reset sequence + * + * @param[in] void + * + * @return void + */ +void start_bms_reset(); + +/** + * @brief Contactor initialization + * + * @param[in] void + * + * @return void + */ +void init_contactors(); + +/** + * @brief Handle contactors + * + * @param[in] void + * + * @return void + */ +void handle_contactors(); + +/** + * @brief Handle contactors of battery 2 + * + * @param[in] void + * + * @return void + */ +void handle_contactors_battery2(); + +#endif diff --git a/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp new file mode 100644 index 000000000..2a6a3d7da --- /dev/null +++ b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp @@ -0,0 +1,51 @@ +#include "comm_equipmentstopbutton.h" +#include "../../include.h" + +// Parameters +#ifdef EQUIPMENT_STOP_BUTTON +const unsigned long equipment_button_long_press_duration = + 15000; // 15 seconds for long press in case of MOMENTARY_SWITCH +const unsigned long equipment_button_debounce_duration = 200; // 200ms for debouncing the button +unsigned long timeSincePress = 0; // Variable to store the time since the last press +DebouncedButton equipment_stop_button; // Debounced button object +#endif // EQUIPMENT_STOP_BUTTON + +// Initialization functions +#ifdef EQUIPMENT_STOP_BUTTON +void init_equipment_stop_button() { + //using external pullup resistors NC + pinMode(EQUIPMENT_STOP_PIN, INPUT); + // Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time + initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration); +} +#endif // EQUIPMENT_STOP_BUTTON + +// Main functions + +#ifdef EQUIPMENT_STOP_BUTTON +void monitor_equipment_stop_button() { + + ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress); + + if (equipment_stop_behavior == LATCHING_SWITCH) { + if (changed_state == PRESSED) { + // Changed to ON โ€“ initiating equipment stop. + setBatteryPause(true, false, true); + } else if (changed_state == RELEASED) { + // Changed to OFF โ€“ ending equipment stop. + setBatteryPause(false, false, false); + } + } else if (equipment_stop_behavior == MOMENTARY_SWITCH) { + if (changed_state == RELEASED) { // button is released + + if (timeSincePress < equipment_button_long_press_duration) { + // Short press detected, trigger equipment stop + setBatteryPause(true, false, true); + } else { + // Long press detected, reset equipment stop state + setBatteryPause(false, false, false); + } + } + } +} +#endif // EQUIPMENT_STOP_BUTTON diff --git a/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h new file mode 100644 index 000000000..f4f5a989b --- /dev/null +++ b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h @@ -0,0 +1,28 @@ +#ifndef _COMM_EQUIPMENTSTOPBUTTON_H_ +#define _COMM_EQUIPMENTSTOPBUTTON_H_ + +#include "../../include.h" + +#ifdef EQUIPMENT_STOP_BUTTON +#include "../../devboard/utils/debounce_button.h" +#endif + +/** + * @brief Initialization of equipment stop button + * + * @param[in] void + * + * @return void + */ +void init_equipment_stop_button(); + +/** + * @brief Monitor equipment stop button + * + * @param[in] void + * + * @return void + */ +void monitor_equipment_stop_button(); + +#endif diff --git a/Software/src/communication/nvm/comm_nvm.cpp b/Software/src/communication/nvm/comm_nvm.cpp new file mode 100644 index 000000000..03afbee07 --- /dev/null +++ b/Software/src/communication/nvm/comm_nvm.cpp @@ -0,0 +1,125 @@ +#include "comm_nvm.h" +#include "../../include.h" + +// Parameters +Preferences settings; // Store user settings + +// Initialization functions + +void init_stored_settings() { + static uint32_t temp = 0; + // ATTENTION ! The maximum length for settings keys is 15 characters + 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) { + set_event(EVENT_EQUIPMENT_STOP, 1); + } + +#ifndef LOAD_SAVED_SETTINGS_ON_BOOT + settings.clear(); // If this clear function is executed, no settings will be read from storage + + //always save the equipment stop status + settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active); +#endif // LOAD_SAVED_SETTINGS_ON_BOOT + +#ifdef WIFI + char tempSSIDstring[63]; // Allocate buffer with sufficient size + size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring)); + if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID! + ssid = tempSSIDstring; + } else { // Reading from settings failed. Do nothing with SSID. Raise event? + } + char tempPasswordString[63]; // Allocate buffer with sufficient size + size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString)); + if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password! + password = tempPasswordString; + } else { // Reading from settings failed. Do nothing with SSID. Raise event? + } +#endif // WIFI + + temp = settings.getUInt("BATTERY_WH_MAX", false); + if (temp != 0) { + datalayer.battery.info.total_capacity_Wh = temp; + } + temp = settings.getUInt("MAXPERCENTAGE", false); + if (temp != 0) { + datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility + } + temp = settings.getUInt("MINPERCENTAGE", false); + if (temp != 0) { + datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility + } + temp = settings.getUInt("MAXCHARGEAMP", false); + if (temp != 0) { + datalayer.battery.settings.max_user_set_charge_dA = temp; + } + temp = settings.getUInt("MAXDISCHARGEAMP", false); + if (temp != 0) { + datalayer.battery.settings.max_user_set_discharge_dA = temp; + } + datalayer.battery.settings.soc_scaling_active = settings.getBool("USE_SCALED_SOC", false); + temp = settings.getUInt("TARGETCHVOLT", false); + if (temp != 0) { + datalayer.battery.settings.max_user_set_charge_voltage_dV = temp; + } + temp = settings.getUInt("TARGETDISCHVOLT", false); + if (temp != 0) { + datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp; + } + datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USEVOLTLIMITS", false); + settings.end(); +} + +void store_settings_equipment_stop() { + settings.begin("batterySettings", false); + settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active); + settings.end(); +} + +void store_settings() { + // ATTENTION ! The maximum length for settings keys is 15 characters + if (!settings.begin("batterySettings", false)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 0); + return; + } + +#ifdef WIFI + if (!settings.putString("SSID", String(ssid.c_str()))) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 1); + } + if (!settings.putString("PASSWORD", String(password.c_str()))) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 2); + } +#endif + + if (!settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 3); + } + if (!settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 4); + } + if (!settings.putUInt("MAXPERCENTAGE", datalayer.battery.settings.max_percentage / 10)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 5); + } + if (!settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 6); + } + if (!settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 7); + } + if (!settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 8); + } + if (!settings.putBool("USEVOLTLIMITS", datalayer.battery.settings.user_set_voltage_limits_active)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 9); + } + if (!settings.putUInt("TARGETCHVOLT", datalayer.battery.settings.max_user_set_charge_voltage_dV)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 10); + } + if (!settings.putUInt("TARGETDISCHVOLT", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) { + set_event(EVENT_PERSISTENT_SAVE_INFO, 11); + } + settings.end(); // Close preferences handle +} diff --git a/Software/src/communication/nvm/comm_nvm.h b/Software/src/communication/nvm/comm_nvm.h new file mode 100644 index 000000000..eb624e899 --- /dev/null +++ b/Software/src/communication/nvm/comm_nvm.h @@ -0,0 +1,37 @@ +#ifndef _COMM_NVM_H_ +#define _COMM_NVM_H_ + +#include "../../include.h" + +#include "../../datalayer/datalayer.h" +#include "../../devboard/utils/events.h" +#include "../../devboard/wifi/wifi.h" + +/** + * @brief Initialization of setting storage + * + * @param[in] void + * + * @return void + */ +void init_stored_settings(); + +/** + * @brief Store settings of equipment stop button + * + * @param[in] void + * + * @return void + */ +void store_settings_equipment_stop(); + +/** + * @brief Store settings + * + * @param[in] void + * + * @return void + */ +void store_settings(); + +#endif diff --git a/Software/src/communication/rs485/comm_rs485.cpp b/Software/src/communication/rs485/comm_rs485.cpp new file mode 100644 index 000000000..feec33aa5 --- /dev/null +++ b/Software/src/communication/rs485/comm_rs485.cpp @@ -0,0 +1,51 @@ +#include "comm_rs485.h" +#include "../../include.h" + +// Parameters + +#ifdef MODBUS_INVERTER_SELECTED +#define MB_RTU_NUM_VALUES 13100 +uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory +// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout +ModbusServerRTU MBserver(Serial2, 2000); +#endif +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +#define SERIAL_LINK_BAUDRATE 112500 +#endif + +// Initialization functions + +void init_rs485() { +// Set up Modbus RTU Server +#ifdef RS485_EN_PIN + pinMode(RS485_EN_PIN, OUTPUT); + digitalWrite(RS485_EN_PIN, HIGH); +#endif // RS485_EN_PIN +#ifdef RS485_SE_PIN + pinMode(RS485_SE_PIN, OUTPUT); + digitalWrite(RS485_SE_PIN, HIGH); +#endif // RS485_SE_PIN +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, HIGH); +#endif // PIN_5V_EN +#ifdef RS485_INVERTER_SELECTED + Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); +#endif // RS485_INVERTER_SELECTED +#ifdef MODBUS_INVERTER_SELECTED +#ifdef BYD_MODBUS + // Init Static data to the RTU Modbus + handle_static_data_modbus_byd(); +#endif // BYD_MODBUS + // Init Serial2 connected to the RTU Modbus + RTUutils::prepareHardwareSerial(Serial2); + Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); + // Register served function code worker for server + MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03); + MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06); + MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16); + MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23); + // Start ModbusRTU background task + MBserver.begin(Serial2, MODBUS_CORE); +#endif // MODBUS_INVERTER_SELECTED +} diff --git a/Software/src/communication/rs485/comm_rs485.h b/Software/src/communication/rs485/comm_rs485.h new file mode 100644 index 000000000..b4077dd48 --- /dev/null +++ b/Software/src/communication/rs485/comm_rs485.h @@ -0,0 +1,19 @@ +#ifndef _COMM_RS485_H_ +#define _COMM_RS485_H_ + +#include "../../include.h" + +#include "../../lib/eModbus-eModbus/Logging.h" +#include "../../lib/eModbus-eModbus/ModbusServerRTU.h" +#include "../../lib/eModbus-eModbus/scripts/mbServerFCs.h" + +/** + * @brief Initialization of RS485 + * + * @param[in] void + * + * @return void + */ +void init_rs485(); + +#endif diff --git a/Software/src/communication/seriallink/comm_seriallink.cpp b/Software/src/communication/seriallink/comm_seriallink.cpp new file mode 100644 index 000000000..b355ae973 --- /dev/null +++ b/Software/src/communication/seriallink/comm_seriallink.cpp @@ -0,0 +1,35 @@ +#include "comm_seriallink.h" +#include "../../include.h" + +// Parameters + +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +#define SERIAL_LINK_BAUDRATE 112500 +#endif + +// Initialization functions + +void init_serialDataLink() { +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) + Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); +#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER +} + +// Main functions + +#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER) +void run_serialDataLink() { + static unsigned long updateTime = 0; + unsigned long currentMillis = millis(); + + if ((currentMillis - updateTime) > 1) { //Every 2ms + updateTime = currentMillis; +#ifdef SERIAL_LINK_RECEIVER + manageSerialLinkReceiver(); +#endif +#ifdef SERIAL_LINK_TRANSMITTER + manageSerialLinkTransmitter(); +#endif + } +} +#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER diff --git a/Software/src/communication/seriallink/comm_seriallink.h b/Software/src/communication/seriallink/comm_seriallink.h new file mode 100644 index 000000000..c88b34373 --- /dev/null +++ b/Software/src/communication/seriallink/comm_seriallink.h @@ -0,0 +1,17 @@ +#ifndef _COMM_SERIALLINK_H_ +#define _COMM_SERIALLINK_H_ + +#include "../../include.h" + +/** + * @brief Initialization of serial data link + * + * @param[in] void + * + * @return void + */ +void init_serialDataLink(); + +void run_serialDataLink(); + +#endif diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index dcf75b7b2..d18bf87f6 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -107,10 +107,36 @@ typedef struct { * you want the inverter to be able to use. At this real SOC, the inverter * will "see" 100% */ uint16_t max_percentage = BATTERY_MAXPERCENTAGE; + /** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */ uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP; /** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */ uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP; + + /** User specified discharge/charge voltages in use. Set to true to use user specified values */ + /** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/ + bool user_set_voltage_limits_active = BATTERY_USE_VOLTAGE_LIMITS; + /** The user specified maximum allowed charge voltage, in deciVolt. 4000 = 400.0 V */ + uint16_t max_user_set_charge_voltage_dV = BATTERY_MAX_CHARGE_VOLTAGE; + /** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */ + uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE; + + /** 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_isolation_clear = 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; + } DATALAYER_BATTERY_SETTINGS_TYPE; typedef struct { @@ -124,6 +150,20 @@ typedef struct { 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; + /** True if contactors are precharging state */ + bool precharging = false; + /** True if the contactor controlled by battery-emulator is closed */ + bool contactors_engaged = false; + /** True if shunt communication ok **/ + bool available = false; } DATALAYER_SHUNT_TYPE; typedef struct { @@ -131,6 +171,14 @@ typedef struct { char battery_protocol[64] = {0}; /** array with type of inverter used, for displaying on webserver */ char inverter_protocol[64] = {0}; + /** array with type of battery used, for displaying on webserver */ + char shunt_protocol[64] = {0}; + /** array with incoming CAN messages, for displaying on webserver */ + char logged_can_messages[15000] = {0}; + size_t logged_can_messages_offset = 0; + /** bool, determines if CAN messages should be logged for webserver */ + bool can_logging_active = false; + } DATALAYER_SYSTEM_INFO_TYPE; typedef struct { diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index d4fd2444a..3bf20f218 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -3,6 +3,42 @@ #include "../include.h" +typedef struct { + /** 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; + uint16_t battery_SOC_raw_highprec = 0; + uint16_t battery_max_temperature = 0; + uint16_t battery_min_temperature = 0; + uint16_t battery_max_cell_voltage = 0; + uint16_t battery_min_cell_voltage = 0; + uint16_t battery_lowest_cell = 0; + uint16_t battery_highest_cell = 0; + uint16_t battery_internal_resistance = 0; + uint16_t battery_voltage_polled = 0; + uint16_t battery_vehicle_isolation = 0; + uint16_t battery_isolation_kohm = 0; + uint16_t battery_HV_locked = 0; + uint16_t battery_crash_event = 0; + uint16_t battery_HVIL = 0; + uint16_t battery_HVIL_status = 0; + int16_t battery_current_7E4 = 0; +} DATALAYER_INFO_BOLTAMPERA; + typedef struct { /** uint16_t */ /** Terminal 30 - 12V SME Supply Voltage */ @@ -158,6 +194,17 @@ typedef struct { bool warning_Charger_not_responding = false; } DATALAYER_INFO_CELLPOWER; +typedef struct { + 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; +} DATALAYER_INFO_KIAHYUNDAI64; + typedef struct { /** uint8_t */ /** Contactor status */ @@ -179,11 +226,179 @@ typedef struct { uint8_t packCtrsClosingAllowed = 0; /** uint8_t */ /** Pyro test in progress */ - uint8_t pyroTestInProgress = 0; - + 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 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; + uint16_t battery_nominal_full_pack_energy = 0; + uint16_t battery_nominal_full_pack_energy_m0 = 0; + uint16_t battery_nominal_energy_remaining = 0; + uint16_t battery_nominal_energy_remaining_m0 = 0; + uint16_t battery_ideal_energy_remaining = 0; + uint16_t battery_ideal_energy_remaining_m0 = 0; + uint16_t battery_energy_to_charge_complete = 0; + uint16_t battery_energy_to_charge_complete_m1 = 0; + uint16_t battery_energy_buffer = 0; + 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_total_discharge = 0; + uint16_t battery_total_charge = 0; + 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 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 battery_bms_min_voltage = 0; + uint32_t battery_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; + uint8_t battery_BMS_contactorState = 0; + uint8_t battery_BMS_state = 0; + uint8_t battery_BMS_hvState = 0; + uint16_t battery_BMS_isolationResistance = 0; + uint8_t battery_BMS_uiChargeStatus = 0; + bool battery_BMS_diLimpRequest = false; + uint16_t battery_BMS_chgPowerAvailable = 0; + bool battery_BMS_pcsPwmEnabled = false; + uint8_t battery_PCS_dcdcPrechargeStatus = 0; + uint8_t battery_PCS_dcdc12VSupportStatus = 0; + uint8_t battery_PCS_dcdcHvBusDischargeStatus = 0; + uint8_t battery_PCS_dcdcMainState = 0; + uint8_t battery_PCS_dcdcSubState = 0; + bool battery_PCS_dcdcFaulted = false; + bool battery_PCS_dcdcOutputIsLimited = false; + uint16_t battery_PCS_dcdcMaxOutputCurrentAllowed = 0; + uint8_t battery_PCS_dcdcPrechargeRtyCnt = 0; + uint8_t battery_PCS_dcdc12VSupportRtyCnt = 0; + uint8_t battery_PCS_dcdcDischargeRtyCnt = 0; + uint8_t battery_PCS_dcdcPwmEnableLine = 0; + uint8_t battery_PCS_dcdcSupportingFixedLvTarget = 0; + uint8_t battery_PCS_dcdcPrechargeRestartCnt = 0; + uint8_t battery_PCS_dcdcInitialPrechargeSubState = 0; + 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_dcdcTemp = 0; + uint16_t PCS_ambientTemp = 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; + bool HVP_gpioPassivePyroDepl = false; + bool HVP_gpioPyroIsoEn = false; + bool HVP_gpioCpFaultIn = false; + bool HVP_gpioPackContPowerEn = false; + bool HVP_gpioHvCablesOk = false; + bool HVP_gpioHvpSelfEnable = false; + bool HVP_gpioLed = false; + bool HVP_gpioCrashSignal = false; + bool HVP_gpioShuntDataReady = false; + bool HVP_gpioFcContPosAux = false; + bool HVP_gpioFcContNegAux = false; + bool HVP_gpioBmsEout = false; + bool HVP_gpioCpFaultOut = false; + bool HVP_gpioPyroPor = false; + bool HVP_gpioShuntEn = false; + bool HVP_gpioHvpVerEn = false; + bool HVP_gpioPackCoontPosFlywheel = false; + bool HVP_gpioCpLatchEnable = false; + bool HVP_gpioPcsEnable = false; + bool HVP_gpioPcsDcdcPwmEnable = false; + bool HVP_gpioPcsChargePwmEnable = false; + 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_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; } DATALAYER_INFO_TESLA; typedef struct { + /** 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; @@ -199,6 +414,9 @@ typedef struct { /** bool */ /** Interlock status */ bool Interlock = false; + /** int16_t */ + /** Insulation resistance, most likely kOhm */ + uint16_t Insulation = 0; /** uint8_t */ /** battery_FAIL status */ uint8_t RelayCutRequest = 0; @@ -244,6 +462,108 @@ typedef struct { } DATALAYER_INFO_NISSAN_LEAF; +typedef struct { + /** 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 */ + /** 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; + uint8_t rt_overcharge = 0; + uint8_t rt_SOC_high = 0; + uint8_t rt_SOC_low = 0; + uint8_t rt_SOC_jumping = 0; + uint8_t rt_temp_difference = 0; + uint8_t rt_cell_overtemp = 0; + uint8_t rt_cell_undertemp = 0; + uint8_t rt_battery_overvolt = 0; + uint8_t rt_battery_undervol = 0; + uint8_t rt_cell_overvolt = 0; + 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 */ + 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; +} DATALAYER_INFO_MEB; + +typedef struct { + uint16_t soc_bms = 0; + uint16_t soc_calc = 0; + uint16_t soc_rescaled = 0; + uint16_t soh_bms = 0; + uint16_t BECMsupplyVoltage = 0; + + uint16_t BECMBatteryVoltage = 0; + uint16_t BECMBatteryCurrent = 0; + uint16_t BECMUDynMaxLim = 0; + uint16_t BECMUDynMinLim = 0; + + uint16_t HvBattPwrLimDcha1 = 0; + uint16_t HvBattPwrLimDchaSoft = 0; + uint16_t HvBattPwrLimDchaSlowAgi = 0; + uint16_t HvBattPwrLimChrgSlowAgi = 0; + + uint8_t HVSysRlySts = 0; + uint8_t HVSysDCRlySts1 = 0; + uint8_t HVSysDCRlySts2 = 0; + uint8_t HVSysIsoRMonrSts = 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; + +} DATALAYER_INFO_VOLVO_POLESTAR; + typedef struct { /** uint16_t */ /** Values WIP*/ @@ -292,12 +612,16 @@ typedef struct { class DataLayerExtended { public: + DATALAYER_INFO_BOLTAMPERA boltampera; DATALAYER_INFO_BMWIX bmwix; DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_CELLPOWER cellpower; + DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64; DATALAYER_INFO_TESLA tesla; DATALAYER_INFO_NISSAN_LEAF nissanleaf; + DATALAYER_INFO_MEB meb; + DATALAYER_INFO_VOLVO_POLESTAR VolvoPolestar; DATALAYER_INFO_ZOE_PH2 zoePH2; }; diff --git a/Software/src/devboard/hal/hal.h b/Software/src/devboard/hal/hal.h index dc3240cef..667fdb19e 100644 --- a/Software/src/devboard/hal/hal.h +++ b/Software/src/devboard/hal/hal.h @@ -9,6 +9,8 @@ #include "hw_stark.h" #elif defined(HW_3LB) #include "hw_3LB.h" +#elif defined(HW_DEVKIT) +#include "hw_devkit.h" #endif #endif diff --git a/Software/src/devboard/hal/hw_3LB.h b/Software/src/devboard/hal/hw_3LB.h index a54de2a47..ded31420d 100644 --- a/Software/src/devboard/hal/hw_3LB.h +++ b/Software/src/devboard/hal/hw_3LB.h @@ -26,14 +26,14 @@ // CAN2 defines below -// DUAL_CAN defines +// CAN_ADDON defines #define MCP2515_SCK 12 // SCK input of MCP2515 #define MCP2515_MOSI 5 // SDI input of MCP2515 #define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors #define MCP2515_CS 18 // CS input of MCP2515 #define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors -// CAN_FD defines +// CANFD_ADDON defines #define MCP2517_SCK 17 // SCK input of MCP2517 #define MCP2517_SDI 23 // SDI input of MCP2517 #define MCP2517_SDO 39 // SDO output of MCP2517 @@ -51,10 +51,12 @@ #define POSITIVE_CONTACTOR_PIN 32 #define NEGATIVE_CONTACTOR_PIN 33 #define PRECHARGE_PIN 25 +#define BMS_POWER 2 #define SECOND_POSITIVE_CONTACTOR_PIN 13 #define SECOND_NEGATIVE_CONTACTOR_PIN 16 #define SECOND_PRECHARGE_PIN 18 +#define BMS_2_POWER 12 // SMA CAN contactor pins #define INVERTER_CONTACTOR_ENABLE_PIN 36 @@ -72,6 +74,14 @@ // Equipment stop pin #define EQUIPMENT_STOP_PIN 35 +// BMW_I3_BATTERY wake up pin +#ifdef BMW_I3_BATTERY +#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 +#ifdef DOUBLE_BATTERY +#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 +#endif // DOUBLE_BATTERY +#endif // BMW_I3_BATTERY + /* ----- Error checks below, don't change (can't be moved to separate file) ----- */ #ifndef HW_CONFIGURED #define HW_CONFIGURED @@ -80,17 +90,17 @@ #endif #ifdef CHADEMO_BATTERY -#ifdef DUAL_CAN -#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage +#ifdef CAN_ADDON +#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage #endif #endif #ifdef EQUIPMENT_STOP_BUTTON -#ifdef DUAL_CAN -#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage +#ifdef CAN_ADDON +#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage #endif -#ifdef CAN_FD -#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage +#ifdef CANFD_ADDON +#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage #endif #ifdef CHADEMO_BATTERY #error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage @@ -98,9 +108,12 @@ #endif #ifdef BMW_I3_BATTERY -#ifdef CONTACTOR_CONTROL +#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN1) #error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL #endif +#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN2) +#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL +#endif #endif #endif diff --git a/Software/src/devboard/hal/hw_devkit.h b/Software/src/devboard/hal/hw_devkit.h new file mode 100644 index 000000000..c16621b56 --- /dev/null +++ b/Software/src/devboard/hal/hw_devkit.h @@ -0,0 +1,93 @@ +#ifndef __HW_DEVKIT_H__ +#define __HW_DEVKIT_H__ + +/* +ESP32 DevKit V1 development board with 30 pins. +For more information, see: https://lastminuteengineers.com/esp32-pinout-reference/. + +The pin layout below supports the following: +- 1x RS485 +- 2x CAN (1x via SN65HVD230 (UART), 1x via MCP2515 (SPI)) +- 1x CANFD (via MCP2518FD (SPI)) +*/ + +// Board boot-up time +#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up + +// Core assignment +#define CORE_FUNCTION_CORE 1 +#define MODBUS_CORE 0 +#define WIFI_CORE 0 + +// RS485 +#define RS485_TX_PIN GPIO_NUM_1 +#define RS485_RX_PIN GPIO_NUM_3 + +// CAN settings +#define CAN_1_TYPE ESP32CAN +//#define CAN_2_TYPE MCP2515 +//#define CAN_3_TYPE MCP2518FD + +// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB +#define CAN_TX_PIN GPIO_NUM_27 +#define CAN_RX_PIN GPIO_NUM_26 + +// CAN_ADDON defines +#define MCP2515_SCK GPIO_NUM_22 // SCK input of MCP2515 +#define MCP2515_MOSI GPIO_NUM_21 // SDI input of MCP2515 +#define MCP2515_MISO GPIO_NUM_19 // SDO output of MCP2515 +#define MCP2515_CS GPIO_NUM_18 // CS input of MCP2515 +#define MCP2515_INT GPIO_NUM_23 // INT output of MCP2515 + +// CANFD_ADDON defines +#define MCP2517_SCK GPIO_NUM_33 // SCK input of MCP2517 +#define MCP2517_SDI GPIO_NUM_32 // SDI input of MCP2517 +#define MCP2517_SDO GPIO_NUM_35 // SDO output of MCP2517 | Pin 35 is input only, without pullup/down resistors +#define MCP2517_CS GPIO_NUM_25 // CS input of MCP2517 +#define MCP2517_INT GPIO_NUM_34 // INT output of MCP2517 | Pin 34 is input only, without pullup/down resistors + +// Contactor handling +#define POSITIVE_CONTACTOR_PIN GPIO_NUM_5 +#define NEGATIVE_CONTACTOR_PIN GPIO_NUM_16 +#define PRECHARGE_PIN GPIO_NUM_17 + +// SMA CAN contactor pins +#define INVERTER_CONTACTOR_ENABLE_PIN GPIO_NUM_14 + +// LED +#define LED_PIN GPIO_NUM_4 +#define LED_MAX_BRIGHTNESS 40 +#define INVERTER_CONTACTOR_ENABLE_LED_PIN GPIO_NUM_2 + +// Equipment stop pin +#define EQUIPMENT_STOP_PIN GPIO_NUM_12 + +// BMW_I3_BATTERY wake up pin +#ifdef BMW_I3_BATTERY +#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 +#ifdef DOUBLE_BATTERY +#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 +#endif // DOUBLE_BATTERY +#endif // BMW_I3_BATTERY + +/* ----- 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 // HW_CONFIGURED + +#ifdef CHADEMO_BATTERY +#error CHADEMO pins are not defined for this hardware. +#endif // CHADEMO_BATTERY + +#ifdef BMW_I3_BATTERY +#if defined(WUP_PIN1) && defined(CANFD_ADDON) +#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and a CANFD addon board using these pins. Choose between BMW_I3_BATTERY and CANFD_ADDON +#endif // defined(WUP_PIN1) && defined(CANFD_ADDON) +#if defined(WUP_PIN2) && defined(CANFD_ADDON) +#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and a CANFD addon board using these pins. Choose between BMW_I3_BATTERY and CANFD_ADDON +#endif // defined(WUP_PIN2) && defined(CANFD_ADDON) +#endif // BMW_I3_BATTERY + +#endif // __HW_DEVKIT_H__ diff --git a/Software/src/devboard/hal/hw_lilygo.h b/Software/src/devboard/hal/hw_lilygo.h index d4d7ce2c7..887f925f6 100644 --- a/Software/src/devboard/hal/hw_lilygo.h +++ b/Software/src/devboard/hal/hw_lilygo.h @@ -26,14 +26,14 @@ // CAN2 defines below -// DUAL_CAN defines +// CAN_ADDON defines #define MCP2515_SCK 12 // SCK input of MCP2515 #define MCP2515_MOSI 5 // SDI input of MCP2515 #define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors #define MCP2515_CS 18 // CS input of MCP2515 #define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors -// CAN_FD defines +// CANFD_ADDON defines #define MCP2517_SCK 12 // SCK input of MCP2517 #define MCP2517_SDI 5 // SDI input of MCP2517 #define MCP2517_SDO 34 // SDO output of MCP2517 @@ -51,6 +51,7 @@ #define POSITIVE_CONTACTOR_PIN 32 #define NEGATIVE_CONTACTOR_PIN 33 #define PRECHARGE_PIN 25 +#define BMS_POWER 18 // Note, this pin collides with CAN add-ons and Chademo // SMA CAN contactor pins #define INVERTER_CONTACTOR_ENABLE_PIN 5 @@ -68,6 +69,14 @@ // Equipment stop pin #define EQUIPMENT_STOP_PIN 35 +// BMW_I3_BATTERY wake up pin +#ifdef BMW_I3_BATTERY +#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 +#ifdef DOUBLE_BATTERY +#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 +#endif // DOUBLE_BATTERY +#endif // BMW_I3_BATTERY + /* ----- Error checks below, don't change (can't be moved to separate file) ----- */ #ifndef HW_CONFIGURED #define HW_CONFIGURED @@ -75,18 +84,23 @@ #error Multiple HW defined! Please select a single HW #endif +#if defined(CAN_ADDON) && defined(CANFD_ADDON) +// Check that user did not try to use dual can and fd-can on same hardware pins +#error CAN_ADDON AND CANFD_ADDON CANNOT BE USED SIMULTANEOUSLY +#endif + #ifdef CHADEMO_BATTERY -#ifdef DUAL_CAN -#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage +#ifdef CAN_ADDON +#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage #endif #endif #ifdef EQUIPMENT_STOP_BUTTON -#ifdef DUAL_CAN -#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage +#ifdef CAN_ADDON +#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage #endif -#ifdef CAN_FD -#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage +#ifdef CANFD_ADDON +#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage #endif #ifdef CHADEMO_BATTERY #error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage @@ -94,9 +108,12 @@ #endif #ifdef BMW_I3_BATTERY -#ifdef CONTACTOR_CONTROL +#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN1) #error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL #endif +#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN2) +#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL +#endif #endif #endif diff --git a/Software/src/devboard/hal/hw_stark.h b/Software/src/devboard/hal/hw_stark.h index 5e26f559f..064db9da2 100644 --- a/Software/src/devboard/hal/hw_stark.h +++ b/Software/src/devboard/hal/hw_stark.h @@ -38,7 +38,7 @@ GPIOs on extra header #define CAN_RX_PIN GPIO_NUM_26 // #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK) -// CAN_FD defines +// CANFD_ADDON defines #define MCP2517_SCK 17 // SCK input of MCP2517 #define MCP2517_SDI 5 // SDI input of MCP2517 #define MCP2517_SDO 34 // SDO output of MCP2517 @@ -61,11 +61,28 @@ GPIOs on extra header // Equipment stop pin #define EQUIPMENT_STOP_PIN 2 +// BMW_I3_BATTERY wake up pin +#ifdef BMW_I3_BATTERY +#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 +#ifdef DOUBLE_BATTERY +#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 +#endif // DOUBLE_BATTERY +#endif // BMW_I3_BATTERY + /* ----- 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 // HW_CONFIGURED +#ifdef BMW_I3_BATTERY +#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN1) +#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL #endif +#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN2) +#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL +#endif +#endif // BMW_I3_BATTERY + +#endif // __HW_STARK_H__ diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 95f5444a7..e0aef8935 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -2,8 +2,10 @@ #include #include #include +#include "../../../USER_SECRETS.h" #include "../../../USER_SETTINGS.h" #include "../../battery/BATTERIES.h" +#include "../../communication/contactorcontrol/comm_contactorcontrol.h" #include "../../datalayer/datalayer.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../../lib/knolleary-pubsubclient/PubSubClient.h" @@ -19,6 +21,7 @@ MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, static String topic_name = ""; static String object_id_prefix = ""; static String device_name = ""; +static String device_id = ""; // Tracking reconnection attempts and failures static unsigned long lastReconnectAttempt = 0; @@ -89,6 +92,8 @@ SensorConfig sensorConfigs[] = { #endif // DOUBLE_BATTERY }; +SensorConfig buttonConfigs[] = {{"BMSRESET", "Reset BMS", "", "", ""}}; + static String generateCommonInfoAutoConfigTopic(const char* object_id) { return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } @@ -101,6 +106,14 @@ static String generateEventsAutoConfigTopic(const char* object_id) { return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config"; } +static String generateButtonTopic(const char* subtype) { + return "homeassistant/button/" + topic_name + "/" + String(subtype); +} + +static String generateButtonAutoConfigTopic(const char* subtype) { + return generateButtonTopic(subtype) + "/config"; +} + #endif // HA_AUTODISCOVERY static std::vector order_events; @@ -129,7 +142,7 @@ static void publish_common_info(void) { } doc["enabled_by_default"] = true; doc["expire_after"] = 240; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -194,9 +207,9 @@ static void publish_common_info(void) { #endif // DOUBLE_BATTERY serializeJson(doc, mqtt_msg); if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { -#ifdef DEBUG_VIA_USB - Serial.println("Common info MQTT msg could not be sent"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.println("Common info MQTT msg could not be sent"); +#endif // DEBUG_LOG } doc.clear(); #ifdef HA_AUTODISCOVERY @@ -234,7 +247,7 @@ static void publish_cell_voltages(void) { doc["enabled_by_default"] = true; doc["expire_after"] = 240; doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -263,7 +276,7 @@ static void publish_cell_voltages(void) { doc["enabled_by_default"] = true; doc["expire_after"] = 240; doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -292,9 +305,9 @@ static void publish_cell_voltages(void) { serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { -#ifdef DEBUG_VIA_USB - Serial.println("Cell voltage MQTT msg could not be sent"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.println("Cell voltage MQTT msg could not be sent"); +#endif // DEBUG_LOG } doc.clear(); } @@ -312,9 +325,9 @@ static void publish_cell_voltages(void) { serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) { -#ifdef DEBUG_VIA_USB - Serial.println("Cell voltage MQTT msg could not be sent"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.println("Cell voltage MQTT msg could not be sent"); +#endif // DEBUG_LOG } doc.clear(); } @@ -342,7 +355,7 @@ void publish_events() { doc["json_attributes_topic"] = state_topic; doc["json_attributes_template"] = "{{ value_json | tojson }}"; doc["enabled_by_default"] = true; - doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["identifiers"][0] = ha_device_id; doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["model"] = "BatteryEmulator"; doc["device"]["name"] = device_name; @@ -384,9 +397,9 @@ void publish_events() { serializeJson(doc, mqtt_msg); if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { -#ifdef DEBUG_VIA_USB - Serial.println("Common info MQTT msg could not be sent"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.println("Common info MQTT msg could not be sent"); +#endif // DEBUG_LOG } else { set_event_MQTTpublished(event_handle); } @@ -399,12 +412,72 @@ void publish_events() { #endif // HA_AUTODISCOVERY } +static void publish_buttons_discovery(void) { +#ifdef HA_AUTODISCOVERY + static bool mqtt_first_transmission = true; + if (mqtt_first_transmission == true) { + mqtt_first_transmission = false; + +#ifdef DEBUG_LOG + logging.println("Publishing buttons discovery"); +#endif // DEBUG_LOG + + static JsonDocument doc; + for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { + SensorConfig& config = buttonConfigs[i]; + doc["name"] = config.name; + doc["unique_id"] = config.object_id; + doc["command_topic"] = generateButtonTopic(config.object_id); + doc["enabled_by_default"] = true; + doc["expire_after"] = 240; + doc["device"]["identifiers"][0] = ha_device_id; + doc["device"]["manufacturer"] = "DalaTech"; + doc["device"]["model"] = "BatteryEmulator"; + doc["device"]["name"] = device_name; + doc["origin"]["name"] = "BatteryEmulator"; + doc["origin"]["sw"] = String(version_number) + "-mqtt"; + doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + serializeJson(doc, mqtt_msg); + mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true); + doc.clear(); + } + } +#endif // HA_AUTODISCOVERY +} + +static void subscribe() { + for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { + SensorConfig& config = buttonConfigs[i]; + const char* topic = generateButtonTopic(config.object_id).c_str(); +#ifdef DEBUG_LOG + logging.printf("Subscribing to topic: [%s]\n", topic); +#endif // DEBUG_LOG + client.subscribe(topic); + } +} + +void mqtt_message_received(char* topic, byte* payload, unsigned int length) { +#ifdef DEBUG_LOG + logging.printf("MQTT message arrived: [%s]\n", topic); +#endif // DEBUG_LOG + +#ifdef REMOTE_BMS_RESET + const char* bmsreset_topic = generateButtonTopic("BMSRESET").c_str(); + if (strcmp(topic, bmsreset_topic) == 0) { +#ifdef DEBUG_LOG + logging.println("Triggering BMS reset"); +#endif // DEBUG_LOG + start_bms_reset(); + } +#endif // REMOTE_BMS_RESET +} + /* If we lose the connection, get it back */ static bool reconnect() { // attempt one reconnection -#ifdef DEBUG_VIA_USB - Serial.print("Attempting MQTT connection... "); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.print("Attempting MQTT connection... "); +#endif // DEBUG_LOG char clientId[64]; // Adjust the size as needed snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname()); // Attempt to connect @@ -413,19 +486,23 @@ static bool reconnect() { clear_event(EVENT_MQTT_DISCONNECT); set_event(EVENT_MQTT_CONNECT, 0); reconnectAttempts = 0; // Reset attempts on successful connection -#ifdef DEBUG_VIA_USB - Serial.println("connected"); -#endif // DEBUG_VIA_USB +#ifdef HA_AUTODISCOVERY + publish_buttons_discovery(); +#endif + subscribe(); +#ifdef DEBUG_LOG + logging.println("connected"); +#endif // DEBUG_LOG clear_event(EVENT_MQTT_CONNECT); } else { if (connected_once) set_event(EVENT_MQTT_DISCONNECT, 0); reconnectAttempts++; // Count failed attempts -#ifdef DEBUG_VIA_USB - Serial.print("failed, rc="); - Serial.print(client.state()); - Serial.println(" try again in 5 seconds"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.print("failed, rc="); + logging.print(client.state()); + logging.println(" try again in 5 seconds"); +#endif // DEBUG_LOG // Wait 5 seconds before retrying } return client.connected(); @@ -439,19 +516,21 @@ void init_mqtt(void) { topic_name = mqtt_topic_name; object_id_prefix = mqtt_object_id_prefix; device_name = mqtt_device_name; + device_id = ha_device_id; #else // Use default naming based on WiFi hostname for topic, object ID prefix, and device name topic_name = "battery-emulator_" + String(WiFi.getHostname()); object_id_prefix = String(WiFi.getHostname()) + String("_"); device_name = "BatteryEmulator_" + String(WiFi.getHostname()); - + device_id = "battery-emulator"; #endif #endif client.setServer(MQTT_SERVER, MQTT_PORT); -#ifdef DEBUG_VIA_USB - Serial.println("MQTT initialized"); -#endif // DEBUG_VIA_USB + client.setCallback(mqtt_message_received); +#ifdef DEBUG_LOG + logging.println("MQTT initialized"); +#endif // DEBUG_LOG client.setKeepAlive(30); // Increase keepalive to manage network latency better. default is 15 @@ -478,8 +557,8 @@ void mqtt_loop(void) { if (reconnect()) { lastReconnectAttempt = 0; } else if (reconnectAttempts >= maxReconnectAttempts) { -#ifdef DEBUG_VIA_USB - Serial.println("Too many failed reconnect attempts, restarting client."); +#ifdef DEBUG_LOG + logging.println("Too many failed reconnect attempts, restarting client."); #endif client.disconnect(); // Force close the MQTT client connection reconnectAttempts = 0; // Reset attempts to avoid infinite loop diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index 91ddc0eaa..1ee57f307 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -47,6 +47,7 @@ extern const char* mqtt_password; extern const char* mqtt_topic_name; extern const char* mqtt_object_id_prefix; extern const char* mqtt_device_name; +extern const char* ha_device_id; extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index ee72af88b..ca69e5fb7 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -97,7 +97,7 @@ void update_machineryprotection() { clear_event(EVENT_SOH_LOW); } -#if !defined(PYLON_BATTERY) && !defined(RENAULT_TWIZY_BATTERY) +#ifdef NISSAN_LEAF_BATTERY // Check if SOC% is plausible if (datalayer.battery.status.voltage_dV > (datalayer.battery.info.max_design_voltage_dV - @@ -108,10 +108,11 @@ void update_machineryprotection() { clear_event(EVENT_SOC_PLAUSIBILITY_ERROR); } } -#endif +#endif //NISSAN_LEAF_BATTERY // Check diff between highest and lowest cell - cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV); + cell_deviation_mV = + std::abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV); if (cell_deviation_mV > datalayer.battery.info.max_cell_voltage_deviation_mV) { set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20)); } else { @@ -237,6 +238,26 @@ void update_machineryprotection() { if (datalayer.battery.status.max_charge_power_W == 0) { datalayer.battery.status.max_charge_current_dA = 0; } + + //Decrement the forced balancing timer incase user requested it + if (datalayer.battery.settings.user_requests_balancing) { + // If this is the start of the balancing period, capture the current time + if (datalayer.battery.settings.balancing_start_time_ms == 0) { + datalayer.battery.settings.balancing_start_time_ms = millis(); + set_event(EVENT_BALANCING_START, 0); + } else { + clear_event(EVENT_BALANCING_START); + } + + // Check if the elapsed time exceeds the balancing time + if (millis() - datalayer.battery.settings.balancing_start_time_ms >= datalayer.battery.settings.balancing_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); + } else { + clear_event(EVENT_BALANCING_END); + } + } } //battery pause status begin @@ -282,12 +303,12 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo } //immediate check if we can send CAN messages - emulator_pause_state_send_CAN_battery(); + emulator_pause_state_transmit_can_battery(); } /// @brief handle emulator pause status /// @return true if CAN messages should be sent to battery, false if not -void emulator_pause_state_send_CAN_battery() { +void emulator_pause_state_transmit_can_battery() { bool previous_allowed_to_send_CAN = allowed_to_send_CAN; if (emulator_pause_status == NORMAL) { diff --git a/Software/src/devboard/safety/safety.h b/Software/src/devboard/safety/safety.h index 32ae0ab85..8fa98a3d1 100644 --- a/Software/src/devboard/safety/safety.h +++ b/Software/src/devboard/safety/safety.h @@ -22,7 +22,7 @@ void update_machineryprotection(); //battery pause status begin void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop = false, bool store_settings = true); -void emulator_pause_state_send_CAN_battery(); +void emulator_pause_state_transmit_can_battery(); std::string get_emulator_pause_status(); //battery pause status end diff --git a/Software/src/devboard/sdcard/sdcard.cpp b/Software/src/devboard/sdcard/sdcard.cpp new file mode 100644 index 000000000..c71fb5701 --- /dev/null +++ b/Software/src/devboard/sdcard/sdcard.cpp @@ -0,0 +1,246 @@ +#include "sdcard.h" +#include "freertos/ringbuf.h" + +#if defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && \ + defined(SD_MISO_PIN) // ensure code is only compiled if all SD card pins are defined + +File can_log_file; +File log_file; +RingbufHandle_t can_bufferHandle; +RingbufHandle_t log_bufferHandle; + +bool can_logging_paused = false; +bool can_file_open = false; +bool delete_can_file = false; + +bool logging_paused = false; +bool log_file_open = false; +bool delete_log_file = false; + +bool sd_card_active = false; + +void delete_can_log() { + can_logging_paused = true; + delete_can_file = true; +} + +void resume_can_writing() { + can_logging_paused = false; + can_log_file = SD_MMC.open(CAN_LOG_FILE, FILE_APPEND); + can_file_open = true; +} + +void pause_can_writing() { + can_logging_paused = true; +} + +void delete_log() { + logging_paused = true; + if (log_file_open) { + log_file.close(); + log_file_open = false; + } + SD_MMC.remove(LOG_FILE); + logging_paused = false; +} + +void resume_log_writing() { + logging_paused = false; + log_file = SD_MMC.open(LOG_FILE, FILE_APPEND); + log_file_open = true; +} + +void pause_log_writing() { + logging_paused = true; +} + +void add_can_frame_to_buffer(CAN_frame frame, frameDirection msgDir) { + + if (!sd_card_active) + return; + + 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); + + if (xRingbufferSend(can_bufferHandle, &messagestr_buffer, size, pdMS_TO_TICKS(2)) != pdTRUE) { +#ifdef DEBUG_VIA_USB + Serial.println("Failed to send message to can ring buffer!"); +#endif // DEBUG_VIA_USB + return; + } + + uint8_t i = 0; + for (i = 0; i < frame.DLC; i++) { + if (i < frame.DLC - 1) + size = snprintf(messagestr_buffer, sizeof(messagestr_buffer), "%02X ", frame.data.u8[i]); + else + size = snprintf(messagestr_buffer, sizeof(messagestr_buffer), "%02X\n", frame.data.u8[i]); + + if (xRingbufferSend(can_bufferHandle, &messagestr_buffer, size, pdMS_TO_TICKS(2)) != pdTRUE) { +#ifdef DEBUG_VIA_USB + Serial.println("Failed to send message to can ring buffer!"); +#endif // DEBUG_VIA_USB + return; + } + } +} + +void write_can_frame_to_sdcard() { + + if (!sd_card_active) + return; + + size_t receivedMessageSize; + uint8_t* buffer = (uint8_t*)xRingbufferReceive(can_bufferHandle, &receivedMessageSize, pdMS_TO_TICKS(10)); + + if (buffer != NULL) { + + if (can_logging_paused) { + if (can_file_open) { + can_log_file.close(); + can_file_open = false; + } + if (delete_can_file) { + SD_MMC.remove(CAN_LOG_FILE); + delete_can_file = false; + can_logging_paused = false; + } + vRingbufferReturnItem(can_bufferHandle, (void*)buffer); + return; + } + + if (can_file_open == false) { + can_log_file = SD_MMC.open(CAN_LOG_FILE, FILE_APPEND); + can_file_open = true; + } + + can_log_file.write(buffer, receivedMessageSize); + can_log_file.flush(); + + vRingbufferReturnItem(can_bufferHandle, (void*)buffer); + } +} + +void add_log_to_buffer(const uint8_t* buffer, size_t size) { + + if (!sd_card_active) + return; + + if (xRingbufferSend(log_bufferHandle, buffer, size, pdMS_TO_TICKS(1)) != pdTRUE) { +#ifdef DEBUG_VIA_USB + Serial.println("Failed to send message to log ring buffer!"); +#endif // DEBUG_VIA_USB + return; + } +} + +void write_log_to_sdcard() { + + if (!sd_card_active) + return; + + size_t receivedMessageSize; + uint8_t* buffer = (uint8_t*)xRingbufferReceive(log_bufferHandle, &receivedMessageSize, pdMS_TO_TICKS(10)); + + if (buffer != NULL) { + + if (logging_paused) { + vRingbufferReturnItem(log_bufferHandle, (void*)buffer); + return; + } + + if (log_file_open == false) { + log_file = SD_MMC.open(LOG_FILE, FILE_APPEND); + log_file_open = true; + } + + log_file.write(buffer, receivedMessageSize); + log_file.flush(); + vRingbufferReturnItem(log_bufferHandle, (void*)buffer); + } +} + +void init_logging_buffers() { +#if defined(LOG_CAN_TO_SD) + can_bufferHandle = xRingbufferCreate(32 * 1024, RINGBUF_TYPE_BYTEBUF); + if (can_bufferHandle == NULL) { +#ifdef DEBUG_LOG + logging.println("Failed to create CAN ring buffer!"); +#endif // DEBUG_LOG + return; + } +#endif // defined(LOG_CAN_TO_SD) + +#if defined(LOG_TO_SD) + log_bufferHandle = xRingbufferCreate(1024, RINGBUF_TYPE_BYTEBUF); + if (log_bufferHandle == NULL) { +#ifdef DEBUG_LOG + logging.println("Failed to create log ring buffer!"); +#endif // DEBUG_LOG + return; + } +#endif // defined(LOG_TO_SD) +} + +void init_sdcard() { + + pinMode(SD_MISO_PIN, INPUT_PULLUP); + + SD_MMC.setPins(SD_SCLK_PIN, SD_MOSI_PIN, SD_MISO_PIN); + if (!SD_MMC.begin("/root", true, true, SDMMC_FREQ_HIGHSPEED)) { +#ifdef DEBUG_LOG + logging.println("SD Card initialization failed!"); +#endif // DEBUG_LOG + return; + } + +#ifdef DEBUG_LOG + logging.println("SD Card initialization successful."); +#endif // DEBUG_LOG + + sd_card_active = true; + +#ifdef DEBUG_LOG + log_sdcard_details(); +#endif // DEBUG_LOG +} + +void log_sdcard_details() { + + logging.print("SD Card Type: "); + switch (SD_MMC.cardType()) { + case CARD_MMC: + logging.println("MMC"); + break; + case CARD_SD: + logging.println("SD"); + break; + case CARD_SDHC: + logging.println("SDHC"); + break; + case CARD_UNKNOWN: + logging.println("UNKNOWN"); + break; + case CARD_NONE: + logging.println("No SD Card found"); + break; + } + + if (SD_MMC.cardType() != CARD_NONE) { + logging.print("SD Card Size: "); + logging.print(SD_MMC.cardSize() / 1024 / 1024); + logging.println(" MB"); + + logging.print("Total space: "); + logging.print(SD_MMC.totalBytes() / 1024 / 1024); + logging.println(" MB"); + + logging.print("Used space: "); + logging.print(SD_MMC.usedBytes() / 1024 / 1024); + logging.println(" MB"); + } +} +#endif // defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && defined(SD_MISO_PIN) diff --git a/Software/src/devboard/sdcard/sdcard.h b/Software/src/devboard/sdcard/sdcard.h new file mode 100644 index 000000000..d628cc214 --- /dev/null +++ b/Software/src/devboard/sdcard/sdcard.h @@ -0,0 +1,32 @@ +#ifndef SDCARD_H +#define SDCARD_H + +#include +#include "../../communication/can/comm_can.h" +#include "../hal/hal.h" + +#if defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && \ + defined(SD_MISO_PIN) // ensure code is only compiled if all SD card pins are defined +#define CAN_LOG_FILE "/canlog.txt" +#define LOG_FILE "/log.txt" + +void init_logging_buffers(); + +void init_sdcard(); +void log_sdcard_details(); + +void add_can_frame_to_buffer(CAN_frame frame, frameDirection msgDir); +void write_can_frame_to_sdcard(); + +void pause_can_writing(); +void resume_can_writing(); +void delete_can_log(); +void delete_log(); +void resume_log_writing(); +void pause_log_writing(); + +void add_log_to_buffer(const uint8_t* buffer, size_t size); +void write_log_to_sdcard(); + +#endif // defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && defined(SD_MISO_PIN) +#endif // SDCARD_H diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 795848852..9c9d1d81b 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -5,14 +5,8 @@ #endif #include "../../../USER_SETTINGS.h" -#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime.h" #include "timer.h" -// Time conversion macros -#define DAYS_TO_SECS 86400 // 24 * 60 * 60 -#define HOURS_TO_SECS 3600 // 60 * 60 -#define MINUTES_TO_SECS 60 - #define EE_NOF_EVENT_ENTRIES 30 #define EE_EVENT_ENTRY_SIZE sizeof(EVENT_LOG_ENTRY_TYPE) #define EE_WRITE_PERIOD_MINUTES 10 @@ -70,10 +64,8 @@ static uint32_t lastMillis = millis(); static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched); static void update_event_level(void); static void update_bms_status(void); - static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data); static void print_event_log(void); -static void check_ee_write(void); uint8_t millisrolloverCount = 0; @@ -87,8 +79,6 @@ void run_event_handling(void) { } lastMillis = currentMillis; - run_sequence_on_target(); - //check_ee_write(); update_event_level(); } @@ -118,15 +108,15 @@ void init_events(void) { // Push changes to eeprom EEPROM.commit(); -#ifdef DEBUG_VIA_USB - Serial.println("EEPROM wasn't ready"); +#ifdef DEBUG_LOG + logging.println("EEPROM wasn't ready"); #endif } else { events.event_log_head_index = EEPROM.readUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS); events.event_log_tail_index = EEPROM.readUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS); -#ifdef DEBUG_VIA_USB - Serial.println("EEPROM was initialized for event logging"); - Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index)); +#ifdef DEBUG_LOG + logging.println("EEPROM was initialized for event logging"); + logging.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index)); #endif print_event_log(); } @@ -140,10 +130,11 @@ void init_events(void) { events.entries[i].MQTTpublished = false; // Not published by default } - events.entries[EVENT_CANFD_INIT_FAILURE].level = EVENT_LEVEL_WARNING; - events.entries[EVENT_CANMCP_INIT_FAILURE].level = EVENT_LEVEL_WARNING; + events.entries[EVENT_CANMCP2517FD_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_OVERRUN].level = EVENT_LEVEL_INFO; + events.entries[EVENT_CANFD_RX_OVERRUN].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_CAN2_RX_FAILURE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR; @@ -158,6 +149,8 @@ void init_events(void) { events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_WARNING; events.entries[EVENT_SOC_UNAVAILABLE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO; + events.entries[EVENT_BALANCING_START].level = EVENT_LEVEL_INFO; + events.entries[EVENT_BALANCING_END].level = EVENT_LEVEL_INFO; events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO; events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO; events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO; @@ -190,6 +183,7 @@ void init_events(void) { events.entries[EVENT_DUMMY_DEBUG].level = EVENT_LEVEL_DEBUG; events.entries[EVENT_DUMMY_WARNING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_DUMMY_ERROR].level = EVENT_LEVEL_ERROR; + events.entries[EVENT_PERSISTENT_SAVE_INFO].level = EVENT_LEVEL_INFO; events.entries[EVENT_SERIAL_RX_WARNING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_SERIAL_RX_FAILURE].level = EVENT_LEVEL_ERROR; events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR; @@ -262,14 +256,16 @@ void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) { const char* get_event_message_string(EVENTS_ENUM_TYPE event) { switch (event) { - case EVENT_CANFD_INIT_FAILURE: + case EVENT_CANMCP2517FD_INIT_FAILURE: return "CAN-FD initialization failed. Check hardware or bitrate settings"; - case EVENT_CANMCP_INIT_FAILURE: + case EVENT_CANMCP2515_INIT_FAILURE: return "CAN-MCP addon initialization failed. Check hardware"; case EVENT_CANFD_BUFFER_FULL: return "CAN-FD buffer overflowed. Some CAN messages were not sent. Contact developers."; case EVENT_CAN_OVERRUN: return "CAN message failed to send within defined time. Contact developers, CPU load might be too high."; + case EVENT_CANFD_RX_OVERRUN: + return "CAN-FD failed to receive all messages from CAN bus. Contact developers, CPU load might be too high."; case EVENT_CAN_RX_FAILURE: return "No CAN communication detected for 60s. Shutting down battery control."; case EVENT_CAN2_RX_FAILURE: @@ -277,84 +273,88 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { case EVENT_CANFD_RX_FAILURE: return "No CANFD communication detected for 60s. Shutting down battery control."; case EVENT_CAN_RX_WARNING: - return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"; + return "High amount of corrupted CAN messages detected. Check CAN wire shielding!"; case EVENT_CAN_TX_FAILURE: - return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!"; + return "CAN messages failed to transmit, or no one on the bus to ACK the message!"; case EVENT_CAN_INVERTER_MISSING: - return "Warning: Inverter not sending messages on CAN bus. Check wiring!"; + return "Inverter not sending messages on CAN bus. Check wiring!"; case EVENT_CONTACTOR_WELDED: - return "Warning: Contactors sticking/welded. Inspect battery with caution!"; + return "Contactors sticking/welded. Inspect battery with caution!"; case EVENT_CHARGE_LIMIT_EXCEEDED: - return "Info: Inverter is charging faster than battery is allowing."; + return "Inverter is charging faster than battery is allowing."; case EVENT_DISCHARGE_LIMIT_EXCEEDED: - return "Info: Inverter is discharging faster than battery is allowing."; + return "Inverter is discharging faster than battery is allowing."; case EVENT_WATER_INGRESS: return "Water leakage inside battery detected. Operation halted. Inspect battery!"; case EVENT_12V_LOW: return "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"; case EVENT_SOC_PLAUSIBILITY_ERROR: - return "Warning: SOC reported by battery not plausible. Restart battery!"; + return "SOC reported by battery not plausible. Restart battery!"; case EVENT_SOC_UNAVAILABLE: - return "Warning: SOC not sent by BMS. Calibrate BMS via app."; + return "SOC not sent by BMS. Calibrate BMS via app."; case EVENT_KWH_PLAUSIBILITY_ERROR: - return "Info: kWh remaining reported by battery not plausible. Battery needs cycling."; + return "kWh remaining reported by battery not plausible. Battery needs cycling."; + case EVENT_BALANCING_START: + return "Balancing has started"; + case EVENT_BALANCING_END: + return "Balancing has ended"; case EVENT_BATTERY_EMPTY: - return "Info: Battery is completely discharged"; + return "Battery is completely discharged"; case EVENT_BATTERY_FULL: - return "Info: Battery is fully charged"; + return "Battery is fully charged"; case EVENT_BATTERY_FROZEN: - return "Info: Battery is too cold to operate optimally. Consider warming it up!"; + return "Battery is too cold to operate optimally. Consider warming it up!"; case EVENT_BATTERY_CAUTION: - return "Info: Battery has raised a general caution flag. Might want to inspect it closely."; + return "Battery has raised a general caution flag. Might want to inspect it closely."; case EVENT_BATTERY_CHG_STOP_REQ: - return "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"; + return "Battery raised caution indicator AND requested charge stop. Inspect battery status!"; case EVENT_BATTERY_DISCHG_STOP_REQ: - return "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"; + return "Battery raised caution indicator AND requested discharge stop. Inspect battery status!"; case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: - return "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"; + return "Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"; case EVENT_BATTERY_REQUESTS_HEAT: - return "Info: COLD BATTERY! Battery requesting heating pads to activate!"; + return "COLD BATTERY! Battery requesting heating pads to activate!"; case EVENT_BATTERY_WARMED_UP: - return "Info: Battery requesting heating pads to stop. The battery is now warm enough."; + return "Battery requesting heating pads to stop. The battery is now warm enough."; case EVENT_BATTERY_OVERHEAT: - return "ERROR: Battery overheated. Shutting down to prevent thermal runaway!"; + return "Battery overheated. Shutting down to prevent thermal runaway!"; case EVENT_BATTERY_OVERVOLTAGE: - return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!"; + return "Battery exceeding maximum design voltage. Discharge battery to prevent damage!"; case EVENT_BATTERY_UNDERVOLTAGE: - return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!"; + return "Battery under minimum design voltage. Charge battery to prevent damage!"; case EVENT_BATTERY_VALUE_UNAVAILABLE: - return "Warning: Battery measurement unavailable. Check 12V power supply and battery wiring!"; + return "Battery measurement unavailable. Check 12V power supply and battery wiring!"; case EVENT_BATTERY_ISOLATION: - return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!"; + return "Battery reports isolation error. High voltage might be leaking to ground. Check battery!"; case EVENT_VOLTAGE_DIFFERENCE: - return "Info: Too large voltage diff between the batteries. Second battery cannot join the DC-link"; + return "Too large voltage diff between the batteries. Second battery cannot join the DC-link"; case EVENT_SOH_DIFFERENCE: - return "Warning: Large deviation in State of health between packs. Inspect battery."; + return "Large deviation in State of health between packs. Inspect battery."; case EVENT_SOH_LOW: - return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle " + return "State of health critically low. Battery internal resistance too high to continue. Recycle " "battery."; case EVENT_HVIL_FAILURE: - return "ERROR: Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. " + return "Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. " "Battery will be disabled!"; case EVENT_PRECHARGE_FAILURE: - return "Info: Battery failed to precharge. Check that capacitor is seated on high voltage output."; + return "Battery failed to precharge. Check that capacitor is seated on high voltage output."; case EVENT_INTERNAL_OPEN_FAULT: - return "ERROR: High voltage cable removed while battery running. Opening contactors!"; + return "High voltage cable removed while battery running. Opening contactors!"; case EVENT_INVERTER_OPEN_CONTACTOR: - return "Info: Inverter side opened contactors. Normal operation."; + return "Inverter side opened contactors. Normal operation."; case EVENT_INTERFACE_MISSING: - return "Info: Configuration trying to use CAN interface not baked into the software. Recompile software!"; + return "Configuration trying to use CAN interface not baked into the software. Recompile software!"; case EVENT_ERROR_OPEN_CONTACTOR: - return "Info: Too much time spent in error state. Opening contactors, not safe to continue charging. " + return "Too much time spent in error state. Opening contactors, not safe to continue charging. " "Check other error code for reason!"; case EVENT_MODBUS_INVERTER_MISSING: - return "Info: Modbus inverter has not sent any data. Inspect communication wiring!"; + return "Modbus inverter has not sent any data. Inspect communication wiring!"; case EVENT_CELL_UNDER_VOLTAGE: - return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; + return "CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; case EVENT_CELL_OVER_VOLTAGE: - return "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; + return "CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; case EVENT_CELL_DEVIATION_HIGH: - return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"; + return "HIGH CELL DEVIATION!!! Inspect battery!"; case EVENT_UNKNOWN_EVENT_SET: return "An unknown event was set! Review your code!"; case EVENT_DUMMY_INFO: @@ -365,6 +365,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "The dummy warning event was set!"; // Don't change this event message! case EVENT_DUMMY_ERROR: return "The dummy error event was set!"; // Don't change this event message! + case EVENT_PERSISTENT_SAVE_INFO: + return "Failed to save user settings. Namespace full?"; case EVENT_SERIAL_RX_WARNING: return "Error in serial function: No data received for some time, see data for minutes"; case EVENT_SERIAL_RX_FAILURE: @@ -378,54 +380,54 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { case EVENT_OTA_UPDATE_TIMEOUT: return "OTA update timed out!"; case EVENT_EEPROM_WRITE: - return "Info: The EEPROM was written"; + return "The EEPROM was written"; case EVENT_RESET_UNKNOWN: - return "Info: The board was reset unexpectedly, and reason can't be determined"; + return "The board was reset unexpectedly, and reason can't be determined"; case EVENT_RESET_POWERON: - return "Info: The board was reset from a power-on event. Normal operation"; + return "The board was reset from a power-on event. Normal operation"; case EVENT_RESET_EXT: - return "Info: The board was reset from an external pin"; + return "The board was reset from an external pin"; case EVENT_RESET_SW: - return "Info: The board was reset via software, webserver or OTA. Normal operation"; + return "The board was reset via software, webserver or OTA. Normal operation"; case EVENT_RESET_PANIC: - return "Warning: The board was reset due to an exception or panic. Inform developers!"; + return "The board was reset due to an exception or panic. Inform developers!"; case EVENT_RESET_INT_WDT: - return "Warning: The board was reset due to an interrupt watchdog timeout. Inform developers!"; + return "The board was reset due to an interrupt watchdog timeout. Inform developers!"; case EVENT_RESET_TASK_WDT: - return "Warning: The board was reset due to a task watchdog timeout. Inform developers!"; + return "The board was reset due to a task watchdog timeout. Inform developers!"; case EVENT_RESET_WDT: - return "Warning: The board was reset due to other watchdog timeout. Inform developers!"; + return "The board was reset due to other watchdog timeout. Inform developers!"; case EVENT_RESET_DEEPSLEEP: - return "Info: The board was reset after exiting deep sleep mode"; + return "The board was reset after exiting deep sleep mode"; case EVENT_RESET_BROWNOUT: - return "Info: The board was reset due to a momentary low voltage condition. This is expected during certain " + return "The board was reset due to a momentary low voltage condition. This is expected during certain " "operations like flashing via USB"; case EVENT_RESET_SDIO: - return "Info: The board was reset over SDIO"; + return "The board was reset over SDIO"; case EVENT_RESET_USB: - return "Info: The board was reset by the USB peripheral"; + return "The board was reset by the USB peripheral"; case EVENT_RESET_JTAG: - return "Info: The board was reset by JTAG"; + return "The board was reset by JTAG"; case EVENT_RESET_EFUSE: - return "Info: The board was reset due to an efuse error"; + return "The board was reset due to an efuse error"; case EVENT_RESET_PWR_GLITCH: - return "Info: The board was reset due to a detected power glitch"; + return "The board was reset due to a detected power glitch"; case EVENT_RESET_CPU_LOCKUP: - return "Warning: The board was reset due to CPU lockup. Inform developers!"; + return "The board was reset due to CPU lockup. Inform developers!"; case EVENT_PAUSE_BEGIN: - return "Warning: The emulator is trying to pause the battery."; + return "The emulator is trying to pause the battery."; case EVENT_PAUSE_END: - return "Info: The emulator is attempting to resume battery operation from pause."; + return "The emulator is attempting to resume battery operation from pause."; case EVENT_WIFI_CONNECT: - return "Info: Wifi connected."; + return "Wifi connected."; case EVENT_WIFI_DISCONNECT: - return "Info: Wifi disconnected."; + return "Wifi disconnected."; case EVENT_MQTT_CONNECT: - return "Info: MQTT connected."; + return "MQTT connected."; case EVENT_MQTT_DISCONNECT: - return "Info: MQTT disconnected."; + return "MQTT disconnected."; case EVENT_EQUIPMENT_STOP: - return "ERROR: EQUIPMENT STOP ACTIVATED!!!"; + return "EQUIPMENT STOP ACTIVATED!!!"; default: return ""; } @@ -465,6 +467,10 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) { if (events.entries[event].log) { log_event(event, events.entries[event].millisrolloverCount, events.entries[event].timestamp, data); } +#ifdef DEBUG_LOG + logging.print("Event: "); + logging.println(get_event_message_string(event)); +#endif } // We should set the event, update event info @@ -478,10 +484,6 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) { events.level = max(events.level, events.entries[event].level); update_bms_status(); - -#ifdef DEBUG_VIA_USB - Serial.println(get_event_message_string(event)); -#endif } static void update_bms_status(void) { @@ -555,8 +557,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint3 // Store the new indices EEPROM.writeUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS, events.event_log_head_index); EEPROM.writeUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS, events.event_log_tail_index); - //Serial.println("Wrote event " + String(event) + " to " + String(entry_address)); - //Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index)); + //logging.println("Wrote event " + String(event) + " to " + String(entry_address)); + //logging.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index)); // We don't need the exact number, it's just for deciding to store or not events.nof_logged_events += (events.nof_logged_events < 255) ? 1 : 0; @@ -565,8 +567,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint3 static void print_event_log(void) { // If the head actually points to the tail, the log is probably blank if (events.event_log_head_index == events.event_log_tail_index) { -#ifdef DEBUG_VIA_USB - Serial.println("No events in log"); +#ifdef DEBUG_LOG + logging.println("No events in log"); #endif return; } @@ -582,28 +584,12 @@ static void print_event_log(void) { // The entry is a blank that has been left behind somehow continue; } -#ifdef DEBUG_VIA_USB - Serial.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) + - ", time: " + String(entry.timestamp)); +#ifdef DEBUG_LOG + logging.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) + + ", time: " + String(entry.timestamp)); #endif if (index == events.event_log_head_index) { break; } } } - -static void check_ee_write(void) { - // Only actually write to flash emulated EEPROM every EE_WRITE_PERIOD_MINUTES minutes, - // and only if we've logged any events - if (events.ee_timer.elapsed() && (events.nof_logged_events > 0)) { - EEPROM.commit(); - events.nof_eeprom_writes += (events.nof_eeprom_writes < 65535) ? 1 : 0; - events.nof_logged_events = 0; - - // We want to know how many writes we have, and to increment the occurrence counter - // we need to clear it first. It's just the way life is. Using events is a smooth - // way to visualize it in the web UI - clear_event(EVENT_EEPROM_WRITE); - set_event(EVENT_EEPROM_WRITE, events.nof_eeprom_writes); - } -} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index dcdcfd6a7..c0c27b68e 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0019 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -26,10 +26,11 @@ */ #define EVENTS_ENUM_TYPE(XX) \ - XX(EVENT_CANFD_INIT_FAILURE) \ - XX(EVENT_CANMCP_INIT_FAILURE) \ + XX(EVENT_CANMCP2517FD_INIT_FAILURE) \ + XX(EVENT_CANMCP2515_INIT_FAILURE) \ XX(EVENT_CANFD_BUFFER_FULL) \ XX(EVENT_CAN_OVERRUN) \ + XX(EVENT_CANFD_RX_OVERRUN) \ XX(EVENT_CAN_RX_FAILURE) \ XX(EVENT_CAN2_RX_FAILURE) \ XX(EVENT_CANFD_RX_FAILURE) \ @@ -44,6 +45,8 @@ XX(EVENT_SOC_PLAUSIBILITY_ERROR) \ XX(EVENT_SOC_UNAVAILABLE) \ XX(EVENT_KWH_PLAUSIBILITY_ERROR) \ + XX(EVENT_BALANCING_START) \ + XX(EVENT_BALANCING_END) \ XX(EVENT_BATTERY_EMPTY) \ XX(EVENT_BATTERY_FULL) \ XX(EVENT_BATTERY_FROZEN) \ @@ -78,6 +81,7 @@ XX(EVENT_DUMMY_DEBUG) \ XX(EVENT_DUMMY_WARNING) \ XX(EVENT_DUMMY_ERROR) \ + XX(EVENT_PERSISTENT_SAVE_INFO) \ XX(EVENT_SERIAL_RX_WARNING) \ XX(EVENT_SERIAL_RX_FAILURE) \ XX(EVENT_SERIAL_TX_FAILURE) \ diff --git a/Software/src/devboard/utils/events_test_on_target.cpp b/Software/src/devboard/utils/events_test_on_target.cpp deleted file mode 100644 index 9f91e8df6..000000000 --- a/Software/src/devboard/utils/events_test_on_target.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "events.h" -#include "timer.h" - -typedef enum { - ETOT_INIT, - ETOT_FIRST_WAIT, - ETOT_INFO, - ETOT_INFO_CLEAR, - ETOT_DEBUG, - ETOT_DEBUG_CLEAR, - ETOT_WARNING, - ETOT_WARNING_CLEAR, - ETOT_ERROR, - ETOT_ERROR_CLEAR, - ETOT_ERROR_LATCHED, - ETOT_DONE -} ETOT_TYPE; - -MyTimer timer(5000); -ETOT_TYPE events_test_state = ETOT_INIT; - -void run_sequence_on_target(void) { -#ifdef INCLUDE_EVENTS_TEST - switch (events_test_state) { - case ETOT_INIT: - timer.set_interval(10000); - events_test_state = ETOT_FIRST_WAIT; - Serial.println("Events test: initialized"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - break; - case ETOT_FIRST_WAIT: - if (timer.elapsed()) { - timer.set_interval(8000); - events_test_state = ETOT_INFO; - set_event(EVENT_DUMMY_INFO, 123); - set_event(EVENT_DUMMY_INFO, 234); // 234 should show, occurrence 1 - Serial.println("Events test: info event set, data: 234"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_INFO: - if (timer.elapsed()) { - timer.set_interval(8000); - clear_event(EVENT_DUMMY_INFO); - events_test_state = ETOT_INFO_CLEAR; - Serial.println("Events test : info event cleared"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_INFO_CLEAR: - if (timer.elapsed()) { - timer.set_interval(8000); - events_test_state = ETOT_DEBUG; - set_event(EVENT_DUMMY_DEBUG, 111); - set_event(EVENT_DUMMY_DEBUG, 222); // 222 should show, occurrence 1 - Serial.println("Events test : debug event set, data: 222"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_DEBUG: - if (timer.elapsed()) { - timer.set_interval(8000); - clear_event(EVENT_DUMMY_DEBUG); - events_test_state = ETOT_DEBUG_CLEAR; - Serial.println("Events test : info event cleared"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_DEBUG_CLEAR: - if (timer.elapsed()) { - timer.set_interval(8000); - events_test_state = ETOT_WARNING; - set_event(EVENT_DUMMY_WARNING, 234); - set_event(EVENT_DUMMY_WARNING, 121); // 121 should show, occurrence 1 - Serial.println("Events test : warning event set, data: 121"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_WARNING: - if (timer.elapsed()) { - timer.set_interval(8000); - clear_event(EVENT_DUMMY_WARNING); - events_test_state = ETOT_WARNING_CLEAR; - Serial.println("Events test : warning event cleared"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_WARNING_CLEAR: - if (timer.elapsed()) { - timer.set_interval(8000); - events_test_state = ETOT_ERROR; - set_event(EVENT_DUMMY_ERROR, 221); - set_event(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1 - Serial.println("Events test : error event set, data: 133"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_ERROR: - if (timer.elapsed()) { - timer.set_interval(8000); - clear_event(EVENT_DUMMY_ERROR); - events_test_state = ETOT_ERROR_CLEAR; - Serial.println("Events test : error event cleared"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_ERROR_CLEAR: - if (timer.elapsed()) { - timer.set_interval(8000); - events_test_state = ETOT_ERROR_LATCHED; - set_event_latched(EVENT_DUMMY_ERROR, 221); - set_event_latched(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1 - Serial.println("Events test : latched error event set, data: 133"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_ERROR_LATCHED: - if (timer.elapsed()) { - timer.set_interval(8000); - clear_event(EVENT_DUMMY_ERROR); - events_test_state = ETOT_DONE; - Serial.println("Events test : latched error event cleared?"); - Serial.print("datalayer.battery.status.bms_status: "); - Serial.println(datalayer.battery.status.bms_status); - } - break; - case ETOT_DONE: - default: - break; - } -#endif -} diff --git a/Software/src/devboard/utils/led_handler.cpp b/Software/src/devboard/utils/led_handler.cpp index 16b2edfa2..0879c36ac 100644 --- a/Software/src/devboard/utils/led_handler.cpp +++ b/Software/src/devboard/utils/led_handler.cpp @@ -2,7 +2,6 @@ #include "../../datalayer/datalayer.h" #include "../../include.h" #include "events.h" -#include "timer.h" #include "value_mapping.h" #define COLOR_GREEN(x) (((uint32_t)0 << 16) | ((uint32_t)x << 8) | 0) @@ -30,58 +29,43 @@ led_color led_get_color() { } void LED::exe(void) { - // Don't run too often - if (!timer.elapsed()) { - return; - } - switch (state) { - default: - case LED_NORMAL: - // Update brightness - switch (mode) { - case led_mode::FLOW: - flow_run(); - break; - case led_mode::HEARTBEAT: - heartbeat_run(); - break; - case led_mode::CLASSIC: - default: - classic_run(); - break; - } - - // Set color - switch (get_event_level()) { - case EVENT_LEVEL_INFO: - color = led_color::GREEN; - pixels.setPixelColor(0, COLOR_GREEN(brightness)); // Green pulsing LED - break; - case EVENT_LEVEL_WARNING: - color = led_color::YELLOW; - pixels.setPixelColor(0, COLOR_YELLOW(brightness)); // Yellow pulsing LED - break; - case EVENT_LEVEL_DEBUG: - case EVENT_LEVEL_UPDATE: - color = led_color::BLUE; - pixels.setPixelColor(0, COLOR_BLUE(brightness)); // Blue pulsing LED - break; - case EVENT_LEVEL_ERROR: - color = led_color::RED; - pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness - break; - default: - break; - } + // Update brightness + switch (mode) { + case led_mode::FLOW: + flow_run(); break; - case LED_COMMAND: + case led_mode::HEARTBEAT: + heartbeat_run(); break; - case LED_RGB: - rainbow_run(); + case led_mode::CLASSIC: + default: + classic_run(); break; } + // Set color + switch (get_event_level()) { + case EVENT_LEVEL_INFO: + color = led_color::GREEN; + pixels.setPixelColor(0, COLOR_GREEN(brightness)); // Green pulsing LED + break; + case EVENT_LEVEL_WARNING: + color = led_color::YELLOW; + pixels.setPixelColor(0, COLOR_YELLOW(brightness)); // Yellow pulsing LED + break; + case EVENT_LEVEL_DEBUG: + case EVENT_LEVEL_UPDATE: + color = led_color::BLUE; + pixels.setPixelColor(0, COLOR_BLUE(brightness)); // Blue pulsing LED + break; + case EVENT_LEVEL_ERROR: + color = led_color::RED; + pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness + break; + default: + break; + } pixels.show(); // This sends the updated pixel color to the hardware. } @@ -92,11 +76,10 @@ void LED::classic_run(void) { void LED::flow_run(void) { // Determine how bright the LED should be - int16_t power_W = datalayer.battery.status.active_power_W; - if (power_W < -50) { + if (datalayer.battery.status.active_power_W < -50) { // Discharging brightness = max_brightness - up_down(0.95); - } else if (power_W > 50) { + } else if (datalayer.battery.status.active_power_W > 50) { // Charging brightness = up_down(0.95); } else { @@ -146,39 +129,6 @@ void LED::heartbeat_run(void) { brightness = (uint8_t)(brightness_f * LED_MAX_BRIGHTNESS); } -void LED::rainbow_run(void) { - brightness = LED_MAX_BRIGHTNESS / 2; - - uint16_t ms = (uint16_t)(millis() % LED_PERIOD_MS); - float value = ((float)ms) / LED_PERIOD_MS; - - // Clamp the input value to the range [0.0, 1.0] - value = value < 0.0f ? 0.0f : value; - value = value > 1.0f ? 1.0f : value; - - uint8_t r = 0, g = 0, b = 0; - - // Scale the value to the range [0, 3), which will be used to transition through the colors - float scaledValue = value * 3.0f; - - if (scaledValue < 1.0f) { - // From red to green - r = static_cast((1.0f - scaledValue) * brightness); - g = static_cast((scaledValue - 0.0f) * brightness); - } else if (scaledValue < 2.0f) { - // From green to blue - g = static_cast((2.0f - scaledValue) * brightness); - b = static_cast((scaledValue - 1.0f) * brightness); - } else { - // From blue back to red - b = static_cast((3.0f - scaledValue) * brightness); - r = static_cast((scaledValue - 2.0f) * brightness); - } - - // Assemble the color - pixels.setPixelColor(0, pixels.Color(r, g, b)); // RGB -} - 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); diff --git a/Software/src/devboard/utils/led_handler.h b/Software/src/devboard/utils/led_handler.h index e6fbe8648..6296e1c6a 100644 --- a/Software/src/devboard/utils/led_handler.h +++ b/Software/src/devboard/utils/led_handler.h @@ -3,10 +3,8 @@ #include "../../include.h" #include "../../lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" -#include "timer.h" enum led_mode { CLASSIC, FLOW, HEARTBEAT }; -enum led_state { LED_NORMAL, LED_COMMAND, LED_RGB }; class LED { public: @@ -16,17 +14,13 @@ class LED { : pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800), max_brightness(LED_MAX_BRIGHTNESS), brightness(LED_MAX_BRIGHTNESS), - mode(led_mode::CLASSIC), - state(LED_NORMAL), - timer(LED_EXECUTION_FREQUENCY) {} + mode(led_mode::CLASSIC) {} LED(led_mode mode) : pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800), max_brightness(LED_MAX_BRIGHTNESS), brightness(LED_MAX_BRIGHTNESS), - mode(mode), - state(LED_NORMAL), - timer(LED_EXECUTION_FREQUENCY) {} + mode(mode) {} void exe(void); void init(void) { pixels.begin(); } @@ -36,12 +30,9 @@ class LED { uint8_t max_brightness; uint8_t brightness; led_mode mode; - led_state state = LED_NORMAL; - MyTimer timer; void classic_run(void); void flow_run(void); - void rainbow_run(void); void heartbeat_run(void); uint8_t up_down(float middle_point_f); diff --git a/Software/src/devboard/utils/logging.cpp b/Software/src/devboard/utils/logging.cpp new file mode 100644 index 000000000..c6811697d --- /dev/null +++ b/Software/src/devboard/utils/logging.cpp @@ -0,0 +1,136 @@ +#include "logging.h" +#include "../../datalayer/datalayer.h" +#include "../sdcard/sdcard.h" + +#define MAX_LINE_LENGTH_PRINTF 128 +#define MAX_LENGTH_TIME_STR 14 + +bool previous_message_was_newline = true; + +void Logging::add_timestamp(size_t size) { +#ifdef DEBUG_LOG + 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(); + char* timestr; + static char timestr_buffer[MAX_LENGTH_TIME_STR]; + +#ifdef DEBUG_VIA_WEB + if (!datalayer.system.info.can_logging_active) { + /* If web debug is active and can logging is inactive, + * we use the debug logging memory directly for writing the timestring */ + if (offset + size + MAX_LENGTH_TIME_STR > message_string_size) { + offset = 0; + } + timestr = datalayer.system.info.logged_can_messages + offset; + } else { + timestr = timestr_buffer; + } +#else + timestr = timestr_buffer; +#endif // DEBUG_VIA_WEB + + offset += min(MAX_LENGTH_TIME_STR - 1, + snprintf(timestr, MAX_LENGTH_TIME_STR, "%8lu.%03lu ", currentTime / 1000, currentTime % 1000)); + +#ifdef DEBUG_VIA_WEB + if (!datalayer.system.info.can_logging_active) { + datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer + } +#endif // DEBUG_VIA_WEB + +#ifdef LOG_TO_SD + add_log_to_buffer((uint8_t*)timestr, MAX_LENGTH_TIME_STR); +#endif // LOG_TO_SD + +#ifdef DEBUG_VIA_USB + Serial.write(timestr); +#endif // DEBUG_VIA_USB + +#endif // DEBUG_LOG +} + +size_t Logging::write(const uint8_t* buffer, size_t size) { +#ifdef DEBUG_LOG + if (previous_message_was_newline) { + add_timestamp(size); + } + +#ifdef LOG_TO_SD + add_log_to_buffer(buffer, size); +#endif +#ifdef DEBUG_VIA_USB + Serial.write(buffer, size); +#endif +#ifdef DEBUG_VIA_WEB + if (!datalayer.system.info.can_logging_active) { + 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); + + if (offset + size > message_string_size) { + offset = 0; + } + memcpy(message_string + offset, buffer, size); + datalayer.system.info.logged_can_messages_offset = offset + size; // Update offset in buffer + } +#endif // DEBUG_VIA_WEB + + previous_message_was_newline = buffer[size - 1] == '\n'; + return size; +#endif // DEBUG_LOG + return 0; +} + +void Logging::printf(const char* fmt, ...) { +#ifdef DEBUG_LOG + if (previous_message_was_newline) { + add_timestamp(MAX_LINE_LENGTH_PRINTF); + } + + char* message_string = datalayer.system.info.logged_can_messages; + size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages); + int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer + static char buffer[MAX_LINE_LENGTH_PRINTF]; + char* message_buffer; +#ifdef DEBUG_VIA_WEB + if (!datalayer.system.info.can_logging_active) { + /* If web debug is active and can logging is inactive, + * we use the debug logging memory directly for writing the output */ + if (offset + MAX_LINE_LENGTH_PRINTF > message_string_size) { + // Not enough space, reset and start from the beginning + offset = 0; + } + message_buffer = message_string + offset; + } else { + message_buffer = buffer; + } +#else + message_buffer = buffer; +#endif // DEBUG_VIA_WEB + + 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); + +#ifdef LOG_TO_SD + add_log_to_buffer((uint8_t*)message_buffer, size); +#endif // LOG_TO_SD + +#ifdef DEBUG_VIA_USB + Serial.write(message_buffer, size); +#endif // DEBUG_VIA_USB + +#ifdef DEBUG_VIA_WEB + if (!datalayer.system.info.can_logging_active) { + // Data was already added to buffer, just move offset + datalayer.system.info.logged_can_messages_offset = + offset + size; // Keeps track of the current position in the buffer + } +#endif // DEBUG_VIA_WEB + + previous_message_was_newline = message_buffer[size - 1] == '\n'; +#endif // DEBUG_LOG +} diff --git a/Software/src/devboard/utils/logging.h b/Software/src/devboard/utils/logging.h new file mode 100644 index 000000000..84fc9a97f --- /dev/null +++ b/Software/src/devboard/utils/logging.h @@ -0,0 +1,18 @@ +#ifndef __LOGGING_H__ +#define __LOGGING_H__ + +#include +#include "Print.h" + +class Logging : public Print { + void add_timestamp(size_t size); + + public: + virtual size_t write(const uint8_t* buffer, size_t size); + virtual size_t write(uint8_t) { return 0; } + void printf(const char* fmt, ...); + Logging() {} +}; + +extern Logging logging; +#endif // __LOGGING_H__ diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h index 5af0845f5..45a443948 100644 --- a/Software/src/devboard/utils/types.h +++ b/Software/src/devboard/utils/types.h @@ -5,7 +5,7 @@ enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 }; enum battery_chemistry_enum { NCA, NMC, LFP }; -enum led_color { GREEN, YELLOW, RED, BLUE, RGB }; +enum led_color { GREEN, YELLOW, RED, BLUE }; #define DISCHARGING 1 #define CHARGING 2 @@ -13,7 +13,9 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB }; #define INTERVAL_10_MS 10 #define INTERVAL_20_MS 20 #define INTERVAL_30_MS 30 +#define INTERVAL_40_MS 40 #define INTERVAL_50_MS 50 +#define INTERVAL_70_MS 70 #define INTERVAL_100_MS 100 #define INTERVAL_200_MS 200 #define INTERVAL_250_MS 250 @@ -49,6 +51,13 @@ typedef struct { } data; } CAN_frame; +enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1 + +typedef struct { + CAN_frame frame; + frameDirection direction; +} CAN_log_frame; + std::string getBMSStatus(bms_status_enum status); #endif diff --git a/Software/src/devboard/webserver/README.md b/Software/src/devboard/webserver/README.md deleted file mode 100644 index 2c9d83653..000000000 --- a/Software/src/devboard/webserver/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Battery Emulator Webserver -This webserver creates a WiFi access point. It also connects ot an existing network. -The webserver intends to display useful information to the user of the battery emulator -development board, without the need to physically connect to the board via USB. -The webserver implementation also provides the option to update the firmware of the board over the air. - -To use the webserver, follow the following steps: -- Connect to the board via Serial, and boot up the board. -- The IP address of the WiFi access point is printed to Serial when the board boots up. Note this down. -- Connect to the access point created by board via a WiFi-capable device -- On that device, open a webbrowser and type the IP address of the WiFi access point. -- If the ssid and password of an existing WiFi network are provided, the board will also connect to this network. The IP address obtained on the existing network is shown in the webserver. Note this down. -- From this point onwards, any device connected to the existing WiFi network can access the webserver via a webbrowser. To do this: - - Connect your WiFi-capable device to the existing nwetork. - - Open a webbrowser and type the IP address obtained on the existing WiFi network. - -To update the software over the air: -- In Arduino, go to `Sketch` > `Export Compiled Binary`. This will create the `.bin` file that you need to update the firmware. It is found in the folder `Software/build/` -- In your webbrowser, go to the url consisting of the IP address, followed by `/update`, for instance `http://192.168.0.224/update`. -- In the webbrowser, follow the steps to select the `.bin` file and to upload the file to the board. - -Security Concerns -(https://randomnerdtutorials.com/esp32-esp8266-web-server-http-authentication/) -Authentication implemented here is meant to be used in your local network to protect from anyone just typing the ESP IP address and accessing the web server (like unauthorized family member or friend). - -## Future work -This section lists a number of features that can be implemented as part of the webserver in the future. - -- TODO: list all available ssids: scan WiFi Networks https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/ -- TODO: add option to add/change ssid and password and save, connect to the new ssid (Option: save ssid and password using Preferences.h library https://randomnerdtutorials.com/esp32-save-data-permanently-preferences/) -- TODO: add functionality to turn WiFi AP off -- TODO: fix IP address on home network (https://randomnerdtutorials.com/esp32-static-fixed-ip-address-arduino-ide/) -- TODO: set hostname (https://randomnerdtutorials.com/esp32-set-custom-hostname-arduino/) - -# References -The code for this webserver is based on code provided by Rui Santos at https://randomnerdtutorials.com. diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 659bcda89..43ed7c301 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -9,6 +9,10 @@ String advanced_battery_processor(const String& var) { //Page format content += ""; content += ""; @@ -16,6 +20,44 @@ String advanced_battery_processor(const String& var) { // Start a new block with a specific background color content += "
"; +#ifdef BOLT_AMPERA_BATTERY + content += "

5V Reference: " + String(datalayer_extended.boltampera.battery_5V_ref) + "

"; + content += "

Module 1 temp: " + String(datalayer_extended.boltampera.battery_module_temp_1) + "

"; + content += "

Module 2 temp: " + String(datalayer_extended.boltampera.battery_module_temp_2) + "

"; + content += "

Module 3 temp: " + String(datalayer_extended.boltampera.battery_module_temp_3) + "

"; + content += "

Module 4 temp: " + String(datalayer_extended.boltampera.battery_module_temp_4) + "

"; + content += "

Module 5 temp: " + String(datalayer_extended.boltampera.battery_module_temp_5) + "

"; + content += "

Module 6 temp: " + String(datalayer_extended.boltampera.battery_module_temp_6) + "

"; + content += + "

Cell average voltage: " + String(datalayer_extended.boltampera.battery_cell_average_voltage) + "

"; + content += + "

Cell average voltage 2: " + String(datalayer_extended.boltampera.battery_cell_average_voltage_2) + "

"; + content += "

Terminal voltage: " + String(datalayer_extended.boltampera.battery_terminal_voltage) + "

"; + content += + "

Ignition power mode: " + String(datalayer_extended.boltampera.battery_ignition_power_mode) + "

"; + content += "

Battery current (7E7): " + String(datalayer_extended.boltampera.battery_current_7E7) + "

"; + content += "

Capacity MY17-18: " + String(datalayer_extended.boltampera.battery_capacity_my17_18) + "

"; + content += "

Capacity MY19+: " + String(datalayer_extended.boltampera.battery_capacity_my19plus) + "

"; + content += "

SOC Display: " + String(datalayer_extended.boltampera.battery_SOC_display) + "

"; + content += "

SOC Raw highprec: " + String(datalayer_extended.boltampera.battery_SOC_raw_highprec) + "

"; + content += "

Max temp: " + String(datalayer_extended.boltampera.battery_max_temperature) + "

"; + content += "

Min temp: " + String(datalayer_extended.boltampera.battery_min_temperature) + "

"; + content += "

Cell max mV: " + String(datalayer_extended.boltampera.battery_max_cell_voltage) + "

"; + content += "

Cell min mV: " + String(datalayer_extended.boltampera.battery_min_cell_voltage) + "

"; + content += "

Lowest cell: " + String(datalayer_extended.boltampera.battery_lowest_cell) + "

"; + content += "

Highest cell: " + String(datalayer_extended.boltampera.battery_highest_cell) + "

"; + content += + "

Internal resistance: " + String(datalayer_extended.boltampera.battery_internal_resistance) + "

"; + content += "

Voltage: " + String(datalayer_extended.boltampera.battery_voltage_polled) + "

"; + content += "

Isolation Ohm: " + String(datalayer_extended.boltampera.battery_vehicle_isolation) + "

"; + content += "

Isolation kOhm: " + String(datalayer_extended.boltampera.battery_isolation_kohm) + "

"; + content += "

HV locked: " + String(datalayer_extended.boltampera.battery_HV_locked) + "

"; + content += "

Crash event: " + String(datalayer_extended.boltampera.battery_crash_event) + "

"; + content += "

HVIL: " + String(datalayer_extended.boltampera.battery_HVIL) + "

"; + content += "

HVIL status: " + String(datalayer_extended.boltampera.battery_HVIL_status) + "

"; + content += "

Current (7E4): " + String(datalayer_extended.boltampera.battery_current_7E4) + "

"; +#endif //BOLT_AMPERA_BATTERY + #ifdef BMW_IX_BATTERY content += "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + @@ -274,6 +316,19 @@ String advanced_battery_processor(const String& var) { String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + "

"; #endif //CELLPOWER_BMS +#ifdef KIA_HYUNDAI_64_BATTERY + content += "

Cells: " + String(datalayer_extended.KiaHyundai64.total_cell_count) + "S

"; + content += "

12V voltage: " + String(datalayer_extended.KiaHyundai64.battery_12V / 10.0, 1) + "

"; + content += "

Waterleakage: " + String(datalayer_extended.KiaHyundai64.waterleakageSensor) + "

"; + content += + "

Temperature, water inlet: " + String(datalayer_extended.KiaHyundai64.temperature_water_inlet) + "

"; + content += + "

Temperature, power relay: " + String(datalayer_extended.KiaHyundai64.powerRelayTemperature) + "

"; + content += "

Batterymanagement mode: " + String(datalayer_extended.KiaHyundai64.batteryManagementMode) + "

"; + content += "

BMS ignition: " + String(datalayer_extended.KiaHyundai64.BMS_ign) + "

"; + content += "

Battery relay: " + String(datalayer_extended.KiaHyundai64.batteryRelay) + "

"; +#endif //KIA_HYUNDAI_64_BATTERY + #ifdef BYD_ATTO_3_BATTERY static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"}; content += "

SOC method used: " + String(SOCmethod[datalayer_extended.bydAtto3.SOC_method]) + "

"; @@ -285,11 +340,122 @@ String advanced_battery_processor(const String& var) { #endif //BYD_ATTO_3_BATTERY #ifdef TESLA_BATTERY + 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 nominal_full_pack_energy = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1; + float nominal_full_pack_energy_m0 = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy_m0) * 0.02; + float nominal_energy_remaining = + static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1; + 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; + float ideal_energy_remaining_m0 = + static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining_m0) * 0.02; + float energy_to_charge_complete = + static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1; + 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; + float expected_energy_remaining_m1 = + static_cast(datalayer_extended.tesla.battery_expected_energy_remaining_m1) * 0.02; + float total_discharge = static_cast(datalayer_extended.tesla.battery_total_discharge); + float total_charge = static_cast(datalayer_extended.tesla.battery_total_charge); + 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.battery_bms_min_voltage) * 0.01 * 2; + float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage) * 0.01 * 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_BrickTempMinNum) * 0.5 - 40; + float BrickModelTMin = static_cast(datalayer_extended.tesla.battery_BrickModelTMin) * 0.5 - 40; + float isolationResistance = static_cast(datalayer_extended.tesla.battery_BMS_isolationResistance) * 10; + float PCS_dcdcMaxOutputCurrentAllowed = + static_cast(datalayer_extended.tesla.battery_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 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; + 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; + 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; + float PCS_dcdcLvOutputCurrentTempLimit = + static_cast(datalayer_extended.tesla.PCS_dcdcLvOutputCurrentTempLimit) * 0.1; + float PCS_dcdcUnifiedCommand = static_cast(datalayer_extended.tesla.PCS_dcdcUnifiedCommand) * 0.001; + float PCS_dcdcCLAControllerOutput = + static_cast(datalayer_extended.tesla.PCS_dcdcCLAControllerOutput * 0.001); + 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_dcdcIntervalMaxFrequency = static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxFrequency); + float PCS_dcdcIntervalMaxHvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxHvBusVolt) * 0.1; + float PCS_dcdcIntervalMaxLvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvBusVolt) * 0.1; + 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; + float PCS_dcdcIntervalMinLvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinLvBusVolt) * 0.1; + 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 const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", "UNKNOWN(10)", "UNKNOWN(11)", "UNKNOWN(12)"}; - content += "

Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "

"; - static const char* hvilStatusState[] = {"NOT OK", + static const char* hvilStatusState[] = {"NOT Ok", "STATUS_OK", "CURRENT_SOURCE_FAULT", "INTERNAL_OPEN_FAULT", @@ -305,25 +471,318 @@ String advanced_battery_processor(const String& var) { "UNKNOWN(13)", "UNKNOWN(14)", "UNKNOWN(15)"}; - content += "

HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "

"; static const char* contactorState[] = {"SNA", "OPEN", "PRECHARGE", "BLOCKED", "PULLED_IN", "OPENING", "ECONOMIZED", "WELDED", "UNKNOWN(8)", "UNKNOWN(9)", "UNKNOWN(10)", "UNKNOWN(11)"}; + static const char* BMS_state[] = {"STANDBY", "DRIVE", "SUPPORT", "CHARGE", "FEIM", + "CLEAR_FAULT", "FAULT", "WELD", "TEST", "SNA"}; + static const char* BMS_contactorState[] = {"SNA", "OPEN", "OPENING", "CLOSING", "CLOSED", "WELDED", "BLOCKED"}; + static const char* BMS_hvState[] = {"DOWN", "COMING_UP", "GOING_DOWN", "UP_FOR_DRIVE", + "UP_FOR_CHARGE", "UP_FOR_DC_CHARGE", "UP"}; + static const char* BMS_uiChargeStatus[] = {"DISCONNECTED", "NO_POWER", "ABOUT_TO_CHARGE", + "CHARGING", "CHARGE_COMPLETE", "CHARGE_STOPPED"}; + static const char* PCS_dcdcStatus[] = {"IDLE", "ACTIVE", "FAULTED"}; + static const char* PCS_dcdcMainState[] = {"STANDBY", "12V_SUPPORT_ACTIVE", "PRECHARGE_STARTUP", + "PRECHARGE_ACTIVE", "DIS_HVBUS_ACTIVE", "SHUTDOWN", + "FAULTED"}; + static const char* PCS_dcdcSubState[] = {"PWR_UP_INIT", + "STANDBY", + "12V_SUPPORT_ACTIVE", + "DIS_HVBUS", + "PCHG_FAST_DIS_HVBUS", + "PCHG_SLOW_DIS_HVBUS", + "PCHG_DWELL_CHARGE", + "PCHG_DWELL_WAIT", + "PCHG_DI_RECOVERY_WAIT", + "PCHG_ACTIVE", + "PCHG_FLT_FAST_DIS_HVBUS", + "SHUTDOWN", + "12V_SUPPORT_FAULTED", + "DIS_HVBUS_FAULTED", + "PCHG_FAULTED", + "CLEAR_FAULTS", + "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_contactor[] = {"NOT_ACTIVE", "ACTIVE", "COMPLETED"}; + static const char* falseTrue[] = {"False", "True"}; + static const char* noYes[] = {"No", "Yes"}; + static const char* Fault[] = {"NOT_ACTIVE", "ACTIVE"}; + //Buttons for user action + content += ""; + //0x20A 522 HVP_contatorState + content += "

Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "

"; + content += "

HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "

"; content += "

Negative contactor: " + String(contactorState[datalayer_extended.tesla.packContNegativeState]) + "

"; content += "

Positive contactor: " + String(contactorState[datalayer_extended.tesla.packContPositiveState]) + "

"; - static const char* falseTrue[] = {"False", "True"}; - content += "

Closing allowed?: " + String(falseTrue[datalayer_extended.tesla.packCtrsClosingAllowed]) + "

"; - content += "

Pyrotest: " + String(falseTrue[datalayer_extended.tesla.pyroTestInProgress]) + "

"; + content += + "

Closing allowed?: " + String(noYes[datalayer_extended.tesla.packCtrsClosingAllowed]) + "

"; //bool + content += + "

Pyrotest in Progress: " + String(noYes[datalayer_extended.tesla.pyroTestInProgress]) + "

"; //bool + content += "

Contactors Open Now Requested: " + + String(noYes[datalayer_extended.tesla.battery_packCtrsOpenNowRequested]) + "

"; //bool + content += + "

Contactors Open Requested: " + String(noYes[datalayer_extended.tesla.battery_packCtrsOpenRequested]) + + "

"; //bool + content += "

Contactors Request Status: " + + String(HVP_contactor[datalayer_extended.tesla.battery_packCtrsRequestStatus]) + "

"; + content += "

Contactors Reset Request Required: " + + String(noYes[datalayer_extended.tesla.battery_packCtrsResetRequestRequired]) + "

"; //bool + content += + "

DC Link Allowed to Energize: " + String(noYes[datalayer_extended.tesla.battery_dcLinkAllowedToEnergize]) + + "

"; //bool + // Comment what data you would like to dislay, order can be changed. + //0x292 658 BMS_socStates + content += "

Battery Beginning of Life: " + String(beginning_of_life) + " KWh

"; + content += "

BattTempPct: " + String(battTempPct) + "

"; + content += "

Battery SOC Ave: " + String(soc_ave) + "

"; + content += "

Battery SOC Max: " + String(soc_max) + "

"; + content += "

Battery SOC Min: " + String(soc_min) + "

"; + content += "

Battery SOC UI: " + String(soc_ui) + "

"; + //0x2B4 PCS_dcdcRailStatus + content += "

PCS Lv Bus: " + String(dcdcLvBusVolt) + " V

"; + content += "

PCS Hv Bus: " + String(dcdcHvBusVolt) + " V

"; + content += "

PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A

"; + //0x2A4 676 PCS_thermalStatus + content += "

PCS dcdc Temp: " + String(PCS_dcdcTemp) + " DegC

"; + content += "

PCS Ambient Temp: " + String(PCS_ambientTemp) + " DegC

"; + //0x224 548 PCS_dcdcStatus + content += + "

Precharge Status: " + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdcPrechargeStatus]) + + "

"; + content += + "

12V Support Status: " + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdc12VSupportStatus]) + + "

"; + content += "

HV Bus Discharge Status: " + + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdcHvBusDischargeStatus]) + "

"; + content += + "

Main State: " + String(PCS_dcdcMainState[datalayer_extended.tesla.battery_PCS_dcdcMainState]) + "

"; + content += + "

Sub State: " + String(PCS_dcdcSubState[datalayer_extended.tesla.battery_PCS_dcdcSubState]) + "

"; + content += "

PCS Faulted: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcFaulted]) + "

"; //bool + content += "

Output Is Limited: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcOutputIsLimited]) + + "

"; //bool + content += "

Max Output Current Allowed: " + String(PCS_dcdcMaxOutputCurrentAllowed) + " A

"; + content += "

Precharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRtyCnt]) + + "

"; //bool + content += + "

12V Support Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdc12VSupportRtyCnt]) + + "

"; // bool + content += "

Discharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcDischargeRtyCnt]) + + "

"; //bool + content += "

PWM Enable Line: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcPwmEnableLine]) + + "

"; //bool + content += "

Supporting Fixed LV Target: " + + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcSupportingFixedLvTarget]) + "

"; //bool + content += "

Precharge Restart Cnt: " + + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRestartCnt]) + "

"; //bool + content += "

Initial Precharge Substate: " + + String(PCS_dcdcSubState[datalayer_extended.tesla.battery_PCS_dcdcInitialPrechargeSubState]) + "

"; + //0x2C4 708 PCS_logging + content += "

PCS_dcdcMaxLvOutputCurrent: " + String(PCS_dcdcMaxLvOutputCurrent) + " A

"; + content += "

PCS_dcdcCurrentLimit: " + String(PCS_dcdcCurrentLimit) + " A

"; + content += "

PCS_dcdcLvOutputCurrentTempLimit: " + String(PCS_dcdcLvOutputCurrentTempLimit) + " A

"; + content += "

PCS_dcdcUnifiedCommand: " + String(PCS_dcdcUnifiedCommand) + "

"; + content += "

PCS_dcdcCLAControllerOutput: " + String(PCS_dcdcCLAControllerOutput) + "

"; + content += "

PCS_dcdcTankVoltage: " + String(PCS_dcdcTankVoltage) + " V

"; + content += "

PCS_dcdcTankVoltageTarget: " + String(PCS_dcdcTankVoltageTarget) + " V

"; + content += "

PCS_dcdcClaCurrentFreq: " + String(PCS_dcdcClaCurrentFreq) + " kHz

"; + content += "

PCS_dcdcTCommMeasured: " + String(PCS_dcdcTCommMeasured) + " us

"; + content += "

PCS_dcdcShortTimeUs: " + String(PCS_dcdcShortTimeUs) + " us

"; + content += "

PCS_dcdcHalfPeriodUs: " + String(PCS_dcdcHalfPeriodUs) + " us

"; + content += "

PCS_dcdcIntervalMaxFrequency: " + String(PCS_dcdcIntervalMaxFrequency) + " kHz

"; + content += "

PCS_dcdcIntervalMaxHvBusVolt: " + String(PCS_dcdcIntervalMaxHvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMaxLvBusVolt: " + String(PCS_dcdcIntervalMaxLvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMaxLvOutputCurr: " + String(PCS_dcdcIntervalMaxLvOutputCurr) + " A

"; + content += "

PCS_dcdcIntervalMinFrequency: " + String(PCS_dcdcIntervalMinFrequency) + " kHz

"; + content += "

PCS_dcdcIntervalMinHvBusVolt: " + String(PCS_dcdcIntervalMinHvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMinLvBusVolt: " + String(PCS_dcdcIntervalMinLvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMinLvOutputCurr: " + String(PCS_dcdcIntervalMinLvOutputCurr) + " A

"; + content += "

PCS_dcdc12vSupportLifetimekWh: " + String(PCS_dcdc12vSupportLifetimekWh) + " kWh

"; + //0x3D2 978 BMS_kwhCounter + content += "

Total Discharge: " + String(total_discharge) + " KWh

"; + content += "

Total Charge: " + String(total_charge) + " KWh

"; + //0x212 530 BMS_status + content += "

Isolation Resistance: " + String(isolationResistance) + " kOhms

"; + content += + "

BMS Contactor State: " + String(BMS_contactorState[datalayer_extended.tesla.battery_BMS_contactorState]) + + "

"; + content += "

BMS State: " + String(BMS_state[datalayer_extended.tesla.battery_BMS_state]) + "

"; + content += "

BMS HV State: " + String(BMS_hvState[datalayer_extended.tesla.battery_BMS_hvState]) + "

"; + content += "

BMS UI Charge Status: " + String(BMS_uiChargeStatus[datalayer_extended.tesla.battery_BMS_hvState]) + + "

"; + content += "

BMS PCS PWM Enabled: " + String(Fault[datalayer_extended.tesla.battery_BMS_pcsPwmEnabled]) + + "

"; //bool + //0x352 850 BMS_energyStatus + content += "

Early BMS 0x352:

"; //if using older BMS <2021 and comment 0x352 without MUX + content += "

Calculated SOH: " + String(nominal_full_pack_energy * 100 / beginning_of_life) + "

"; + content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " KWh

"; + content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining) + " KWh

"; + content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining) + " KWh

"; + content += "

Energy to Charge Complete: " + String(energy_to_charge_complete) + " KWh

"; + content += "

Energy Buffer: " + String(energy_buffer) + " KWh

"; + content += "

Full Charge Complete: " + String(noYes[datalayer_extended.tesla.battery_full_charge_complete]) + + "

"; //bool + //0x352 850 BMS_energyStatus + content += "

Late BMS 0x352 with Mux:

"; //if using newer BMS >2021 and comment 0x352 with MUX + content += "

Calculated SOH: " + String(nominal_full_pack_energy_m0 * 100 / beginning_of_life) + "

"; + content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy_m0) + " KWh

"; + content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining_m0) + " KWh

"; + content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining_m0) + " KWh

"; + content += "

Energy to Charge Complete: " + String(energy_to_charge_complete_m1) + " KWh

"; + content += "

Energy Buffer: " + String(energy_buffer_m1) + " KWh

"; + content += "

Expected Energy Remaining: " + String(expected_energy_remaining_m1) + " KWh

"; + content += "

Fully Charged: " + String(noYes[datalayer_extended.tesla.battery_fully_charged]) + "

"; //bool + //0x392 BMS_packConfig + //content += "

packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "

"; + //content += "

moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "

"; + //content += "

reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "

"; + content += "

Battery Pack Mass: " + String(packMass) + " KG

"; + content += "

Platform Max Bus Voltage: " + String(platformMaxBusVoltage) + " V

"; + //0x2D2 722 BMSVAlimits + content += "

BMS Min Voltage: " + String(bms_min_voltage) + " V

"; + content += "

BMS Max Voltage: " + String(bms_max_voltage) + " V

"; + content += "

Max Charge Current: " + String(max_charge_current) + " A

"; + content += "

Max Discharge Current: " + String(max_discharge_current) + " A

"; + //0x332 818 BMS_bmbMinMax + content += "

Brick Voltage Max: " + String(BrickVoltageMax) + " V

"; + content += "

Brick Voltage Min: " + String(BrickVoltageMin) + " V

"; + content += "

Brick Temp Max Num: " + String(datalayer_extended.tesla.battery_BrickTempMaxNum) + "

"; + content += "

Brick Temp Min Num: " + String(datalayer_extended.tesla.battery_BrickTempMinNum) + "

"; + content += "

Brick Model Temp Max: " + String(BrickModelTMax) + " C

"; + content += "

Brick Model Temp Min: " + String(BrickModelTMin) + " C

"; + //0x252 594 BMS_powerAvailable + content += "

Max Regen Power: " + String(BMS_maxRegenPower) + " KW

"; + content += "

Max Discharge Power: " + String(BMS_maxDischargePower) + " KW

"; + content += "

Max Stationary Heat Power: " + String(BMS_maxStationaryHeatPower) + " KWh

"; + content += "

HVAC Power Budget: " + String(BMS_hvacPowerBudget) + " KW

"; + content += "

Not Enough Power For Heat Pump: " + + String(falseTrue[datalayer_extended.tesla.BMS_notEnoughPowerForHeatPump]) + "

"; //bool + content += + "

Power Limit State: " + String(BMS_powerLimitState[datalayer_extended.tesla.BMS_powerLimitState]) + "

"; + content += "

Inverter TQF: " + String(datalayer_extended.tesla.BMS_inverterTQF) + "

"; + //0x312 786 BMS_thermalStatus + content += "

Power Dissipation: " + String(BMS_powerDissipation) + " kW

"; + content += "

Flow Request: " + String(BMS_flowRequest) + " LPM

"; + content += "

Inlet Active Cool Target Temp: " + String(BMS_inletActiveCoolTargetT) + " DegC

"; + content += "

Inlet Passive Target Temp: " + String(BMS_inletPassiveTargetT) + " DegC

"; + content += "

Inlet Active Heat Target Temp: " + String(BMS_inletActiveHeatTargetT) + " DegC

"; + content += "

Pack Temp Min: " + String(BMS_packTMin) + " DegC

"; + content += "

Pack Temp Max: " + String(BMS_packTMax) + " DegC

"; + content += + "

PCS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_pcsNoFlowRequest]) + "

"; //bool + content += + "

BMS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_noFlowRequest]) + "

"; //bool + //0x7AA 1962 HVP_debugMessage + content += "

HVP_gpioPassivePyroDepl: " + String(Fault[datalayer_extended.tesla.HVP_gpioPassivePyroDepl]) + + "

"; //bool + content += "

HVP_gpioPyroIsoEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroIsoEn]) + "

"; //bool + content += "

HVP_gpioCpFaultIn: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultIn]) + "

"; //bool + content += "

HVP_gpioPackContPowerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackContPowerEn]) + + "

"; //bool + content += + "

HVP_gpioHvCablesOk: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvCablesOk]) + "

"; //bool + content += + "

HVP_gpioHvpSelfEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpSelfEnable]) + "

"; //bool + content += "

HVP_gpioLed: " + String(Fault[datalayer_extended.tesla.HVP_gpioLed]) + "

"; //bool + content += + "

HVP_gpioCrashSignal: " + String(Fault[datalayer_extended.tesla.HVP_gpioCrashSignal]) + "

"; //bool + content += "

HVP_gpioShuntDataReady: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntDataReady]) + + "

"; //bool + content += + "

HVP_gpioFcContPosAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPosAux]) + "

"; //bool + content += + "

HVP_gpioFcContNegAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContNegAux]) + "

"; //bool + content += "

HVP_gpioBmsEout: " + String(Fault[datalayer_extended.tesla.HVP_gpioBmsEout]) + "

"; //bool + content += + "

HVP_gpioCpFaultOut: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultOut]) + "

"; //bool + content += "

HVP_gpioPyroPor: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroPor]) + "

"; //bool + content += "

HVP_gpioShuntEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntEn]) + "

"; //bool + content += "

HVP_gpioHvpVerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpVerEn]) + "

"; //bool + content += + "

HVP_gpioPackCoontPosFlywheel: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackCoontPosFlywheel]) + + "

"; //bool + content += + "

HVP_gpioCpLatchEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpLatchEnable]) + "

"; //bool + content += "

HVP_gpioPcsEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsEnable]) + "

"; //bool + content += "

HVP_gpioPcsDcdcPwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsDcdcPwmEnable]) + + "

"; //bool + content += "

HVP_gpioPcsChargePwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsChargePwmEnable]) + + "

"; //bool + content += "

HVP_gpioFcContPowerEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPowerEnable]) + + "

"; //bool + content += + "

HVP_gpioHvilEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvilEnable]) + "

"; //bool + content += "

HVP_gpioSecDrdy: " + String(Fault[datalayer_extended.tesla.HVP_gpioSecDrdy]) + "

"; //bool + content += "

HVP_hvp1v5Ref: " + String(HVP_hvp1v5Ref) + " V

"; + content += "

HVP_shuntCurrentDebug: " + String(HVP_shuntCurrentDebug) + " A

"; + content += + "

HVP_packCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_packCurrentMia]) + "

"; //bool + content += "

HVP_auxCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_auxCurrentMia]) + "

"; //bool + content += + "

HVP_currentSenseMia: " + String(noYes[datalayer_extended.tesla.HVP_currentSenseMia]) + "

"; //bool + content += + "

HVP_shuntRefVoltageMismatch: " + String(noYes[datalayer_extended.tesla.HVP_shuntRefVoltageMismatch]) + + "

"; //bool + content += "

HVP_shuntThermistorMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntThermistorMia]) + + "

"; //bool + content += "

HVP_shuntHwMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntHwMia]) + "

"; //bool + content += "

HVP_dcLinkVoltage: " + String(HVP_dcLinkVoltage) + " V

"; + content += "

HVP_packVoltage: " + String(HVP_packVoltage) + " V

"; + content += "

HVP_fcLinkVoltage: " + String(HVP_fcLinkVoltage) + " V

"; + content += "

HVP_packContVoltage: " + String(HVP_packContVoltage) + " V

"; + content += "

HVP_packNegativeV: " + String(HVP_packNegativeV) + " V

"; + content += "

HVP_packPositiveV: " + String(HVP_packPositiveV) + " V

"; + content += "

HVP_pyroAnalog: " + String(HVP_pyroAnalog) + " V

"; + content += "

HVP_dcLinkNegativeV: " + String(HVP_dcLinkNegativeV) + " V

"; + content += "

HVP_dcLinkPositiveV: " + String(HVP_dcLinkPositiveV) + " V

"; + content += "

HVP_fcLinkNegativeV: " + String(HVP_fcLinkNegativeV) + " V

"; + content += "

HVP_fcContCoilCurrent: " + String(HVP_fcContCoilCurrent) + " A

"; + content += "

HVP_fcContVoltage: " + String(HVP_fcContVoltage) + " V

"; + content += "

HVP_hvilInVoltage: " + String(HVP_hvilInVoltage) + " V

"; + content += "

HVP_hvilOutVoltage: " + String(HVP_hvilOutVoltage) + " V

"; + content += "

HVP_fcLinkPositiveV: " + String(HVP_fcLinkPositiveV) + " V

"; + content += "

HVP_packContCoilCurrent: " + String(HVP_packContCoilCurrent) + " A

"; + content += "

HVP_battery12V: " + String(HVP_battery12V) + " V

"; + content += "

HVP_shuntRefVoltageDbg: " + String(HVP_shuntRefVoltageDbg) + " V

"; + content += "

HVP_shuntAuxCurrentDbg: " + String(HVP_shuntAuxCurrentDbg) + " A

"; + content += "

HVP_shuntBarTempDbg: " + String(HVP_shuntBarTempDbg) + " DegC

"; + content += "

HVP_shuntAsicTempDbg: " + String(HVP_shuntAsicTempDbg) + " DegC

"; + content += + "

HVP_shuntAuxCurrentStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAuxCurrentStatus]) + + "

"; + content += + "

HVP_shuntBarTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntBarTempStatus]) + "

"; + content += "

HVP_shuntAsicTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAsicTempStatus]) + + "

"; + #endif #ifdef NISSAN_LEAF_BATTERY - content += "

LEAF generation: " + String(datalayer_extended.nissanleaf.LEAF_gen) + "

"; + static const char* LEAFgen[] = {"ZE0", "AZE0", "ZE1"}; + content += "

LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "

"; + char readableSerialNumber[16]; // One extra space for null terminator + memcpy(readableSerialNumber, datalayer_extended.nissanleaf.BatterySerialNumber, + sizeof(datalayer_extended.nissanleaf.BatterySerialNumber)); + readableSerialNumber[15] = '\0'; // Null terminate the string + content += "

Serial number: " + String(readableSerialNumber) + "

"; + char readablePartNumber[8]; // One extra space for null terminator + memcpy(readablePartNumber, datalayer_extended.nissanleaf.BatteryPartNumber, + sizeof(datalayer_extended.nissanleaf.BatteryPartNumber)); + readablePartNumber[7] = '\0'; // Null terminate the string + content += "

Part number: " + String(readablePartNumber) + "

"; + char readableBMSID[9]; // One extra space for null terminator + memcpy(readableBMSID, datalayer_extended.nissanleaf.BMSIDcode, sizeof(datalayer_extended.nissanleaf.BMSIDcode)); + readableBMSID[8] = '\0'; // Null terminate the string + content += "

BMS ID: " + String(readableBMSID) + "

"; content += "

GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "

"; content += "

Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "

"; content += "

Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "

"; content += "

Interlock: " + String(datalayer_extended.nissanleaf.Interlock) + "

"; + content += "

Insulation: " + String(datalayer_extended.nissanleaf.Insulation) + "

"; content += "

Relay cut request: " + String(datalayer_extended.nissanleaf.RelayCutRequest) + "

"; content += "

Failsafe status: " + String(datalayer_extended.nissanleaf.FailsafeStatus) + "

"; content += "

Fully charged: " + String(datalayer_extended.nissanleaf.Full) + "

"; @@ -340,6 +799,205 @@ String advanced_battery_processor(const String& var) { content += "

Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "

"; #endif +#ifdef MEB_BATTERY + content += datalayer_extended.meb.SDSW ? "

Service disconnect switch: Missing!

" + : "

Service disconnect switch: OK

"; + content += datalayer_extended.meb.pilotline ? "

Pilotline: Open!

" : "

Pilotline: OK

"; + content += datalayer_extended.meb.transportmode ? "

Transportmode: Locked!

" : "

Transportmode: OK

"; + content += datalayer_extended.meb.shutdown_active ? "

Shutdown: Active!

" : "

Shutdown: No

"; + content += datalayer_extended.meb.componentprotection ? "

Component protection: Active!

" + : "

Component protection: No

"; + content += "

HVIL status: "; + switch (datalayer_extended.meb.HVIL) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("Open!"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

KL30C status: "; + switch (datalayer_extended.meb.BMS_Kl30c_Status) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("Open!"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

BMS mode: "; + switch (datalayer_extended.meb.BMS_mode) { + case 0: + content += String("HV inactive"); + break; + case 1: + content += String("HV active"); + break; + case 2: + content += String("Balancing"); + break; + case 3: + content += String("Extern charging"); + break; + case 4: + content += String("AC charging"); + break; + case 5: + content += String("Battery error"); + break; + case 6: + content += String("DC charging"); + break; + case 7: + content += String("Init"); + break; + default: + content += String("?"); + } + content += "

Diagnostic: "; + switch (datalayer_extended.meb.battery_diagnostic) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Battery display"); + break; + case 4: + content += String("Battery display OK"); + break; + case 6: + content += String("Battery display check"); + break; + case 7: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

HV line status: "; + switch (datalayer_extended.meb.status_HV_line) { + case 0: + content += String("Init"); + break; + case 1: + content += String("No open HV line detected"); + break; + case 2: + content += String("Open HV line"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("? ") + String(datalayer_extended.meb.status_HV_line); + } + content += "

Warning support: "; + switch (datalayer_extended.meb.warning_support) { + case 0: + content += String("OK"); + break; + case 1: + content += String("Not OK"); + break; + case 6: + content += String("Init"); + break; + case 7: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) + + "V) status: "; + switch (datalayer_extended.meb.BMS_status_voltage_free) { + case 0: + content += String("Init"); + break; + case 1: + content += String("BMS interm circuit voltage free (U<20V)"); + break; + case 2: + content += String("BMS interm circuit not voltage free (U >= 25V)"); + break; + case 3: + content += String("Error"); + break; + default: + content += String("?"); + } + content += "

BMS error status: "; + switch (datalayer_extended.meb.BMS_error_status) { + case 0: + content += String("Component IO"); + break; + case 1: + content += String("Iso Error 1"); + break; + case 2: + content += String("Iso Error 2"); + break; + case 3: + content += String("Interlock"); + break; + case 4: + content += String("SD"); + break; + case 5: + content += String("Performance red"); + break; + case 6: + content += String("No component function"); + break; + case 7: + content += String("Init"); + break; + default: + content += String("?"); + } + content += "

BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 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

"; + content += datalayer_extended.meb.BMS_warning_lamp_req ? "

Yellow warning lamp: ON!

" + : "

Yellow warning lamp: Off

"; + content += "

Isolation resistance: " + String(datalayer_extended.meb.isolation_resistance) + " kOhm

"; + content += + datalayer_extended.meb.battery_heating ? "

Battery heating: Active!

" : "

Battery heating: Off

"; + const char* rt_enum[] = {"No", "Error level 1", "Error level 2", "Error level 3"}; + content += "

Overcurrent: " + String(rt_enum[datalayer_extended.meb.rt_overcurrent]) + "

"; + content += "

CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault]) + "

"; + content += "

Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge]) + "

"; + content += "

SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high]) + "

"; + content += "

SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low]) + "

"; + content += "

SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping]) + "

"; + content += "

Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference]) + "

"; + content += "

Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp]) + "

"; + content += "

Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp]) + "

"; + content += "

Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt]) + "

"; + content += "

Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol]) + "

"; + content += "

Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt]) + "

"; + content += "

Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol]) + "

"; + content += "

Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance]) + "

"; + content += "

Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized]) + "

"; +#endif //MEB_BATTERY + #ifdef RENAULT_ZOE_GEN2_BATTERY content += "

soc: " + String(datalayer_extended.zoePH2.battery_soc) + "

"; content += "

usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "

"; @@ -387,13 +1045,123 @@ String advanced_battery_processor(const String& var) { content += "

soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "

"; #endif //RENAULT_ZOE_GEN2_BATTERY -#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \ - !defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) +#ifdef VOLVO_SPA_BATTERY + content += "

BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms) + "

"; + content += "

Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc) + "

"; + content += "

Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10) + "

"; + content += "

BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms) + "

"; + 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

"; + + content += + "

Discharge power limit 1: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDcha1) + " kW

"; + content += + "

Discharge soft power limit: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSoft) + " kW

"; + content += + "

Discharge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSlowAgi) + + " kW

"; + content += + "

Charge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimChrgSlowAgi) + + " kW

"; + + content += "

HV system relay status: "; + switch (datalayer_extended.VolvoPolestar.HVSysRlySts) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("OpenAndRequestActiveDischarge"); + break; + default: + content += String("Not valid"); + } + content += "

HV system relay status 1: "; + switch (datalayer_extended.VolvoPolestar.HVSysDCRlySts1) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("Not valid"); + } + content += "

HV system relay status 2: "; + switch (datalayer_extended.VolvoPolestar.HVSysDCRlySts2) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("Not valid"); + } + content += "

HV system isolation resistance monitoring status: "; + switch (datalayer_extended.VolvoPolestar.HVSysIsoRMonrSts) { + case 0: + content += String("Not valid 1"); + break; + case 1: + content += String("False"); + break; + case 2: + content += String("True"); + break; + case 3: + content += String("Not valid 2"); + break; + default: + content += String("Not valid"); + } + + content += "


"; + content += "
"; + content += ""; +#endif // VOLVO_SPA_BATTERY + +#if !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && !defined(TESLA_BATTERY) && \ + !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && !defined(BYD_ATTO_3_BATTERY) && \ + !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && !defined(MEB_BATTERY) && \ + !defined(VOLVO_SPA_BATTERY) && !defined(KIA_HYUNDAI_64_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif content += "

"; - + content += ""; content += ""; + content += ""; + + content += ""; + + content += ""; + return content; } return String(); diff --git a/Software/src/devboard/webserver/can_logging_html.cpp b/Software/src/devboard/webserver/can_logging_html.cpp new file mode 100644 index 000000000..146566c7d --- /dev/null +++ b/Software/src/devboard/webserver/can_logging_html.cpp @@ -0,0 +1,67 @@ +#include "can_logging_html.h" +#include +#include "../../datalayer/datalayer.h" +#include "index_html.h" + +String can_logger_processor(void) { + if (!datalayer.system.info.can_logging_active) { + datalayer.system.info.logged_can_messages_offset = 0; + datalayer.system.info.logged_can_messages[0] = '\0'; + } + datalayer.system.info.can_logging_active = + true; // Signal to main loop that we should log messages. Disabled by default for performance reasons + String content = index_html_header; + // Page format + content += ""; + content += " "; + content += " "; +#ifdef LOG_CAN_TO_SD + content += " "; +#endif + content += ""; + + // Start a new block for the CAN messages + content += "
"; + + // Check for messages + if (datalayer.system.info.logged_can_messages[0] == 0) { + content += "CAN logger started! Refresh page to display incoming(RX) and outgoing(TX) messages"; + } else { + // Split the messages using the newline character + String messages = String(datalayer.system.info.logged_can_messages); + int startIndex = 0; + int endIndex = messages.indexOf('\n'); + while (endIndex != -1) { + // Extract a single message and wrap it in a styled div + String singleMessage = messages.substring(startIndex, endIndex); + content += "
" + singleMessage + "
"; + startIndex = endIndex + 1; // Move past the newline character + endIndex = messages.indexOf('\n', startIndex); + } + } + + content += "
"; + + // Add JavaScript for navigation + content += ""; + content += index_html_footer; + return content; +} diff --git a/Software/src/devboard/webserver/can_logging_html.h b/Software/src/devboard/webserver/can_logging_html.h new file mode 100644 index 000000000..8d68e6840 --- /dev/null +++ b/Software/src/devboard/webserver/can_logging_html.h @@ -0,0 +1,16 @@ +#ifndef CANLOGGER_H +#define CANLOGGER_H + +#include +#include + +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String can_logger_processor(void); + +#endif diff --git a/Software/src/devboard/webserver/cellmonitor_html.cpp b/Software/src/devboard/webserver/cellmonitor_html.cpp index 21a5c1df5..f37f5d9d0 100644 --- a/Software/src/devboard/webserver/cellmonitor_html.cpp +++ b/Software/src/devboard/webserver/cellmonitor_html.cpp @@ -8,6 +8,10 @@ String cellmonitor_processor(const String& var) { // Page format content += ""; +#ifdef DEBUG_VIA_WEB + content += " "; +#endif + content += " "; +#ifdef LOG_TO_SD + content += " "; +#endif + content += ""; + + // Start a new block for the debug log messages + content += "
";
+  content += String(datalayer.system.info.logged_can_messages);
+  content += "
"; + + // Add JavaScript for navigation + content += ""; + content += index_html_footer; + return content; +} +#endif diff --git a/Software/src/devboard/webserver/debug_logging_html.h b/Software/src/devboard/webserver/debug_logging_html.h new file mode 100644 index 000000000..9d848f516 --- /dev/null +++ b/Software/src/devboard/webserver/debug_logging_html.h @@ -0,0 +1,16 @@ +#ifndef DEBUGLOGGER_H +#define DEBUGLOGGER_H + +#include +#include + +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String debug_logger_processor(void); + +#endif diff --git a/Software/src/devboard/webserver/events_html.cpp b/Software/src/devboard/webserver/events_html.cpp index 11ff6a7f3..a602b938f 100644 --- a/Software/src/devboard/webserver/events_html.cpp +++ b/Software/src/devboard/webserver/events_html.cpp @@ -5,6 +5,8 @@ const char EVENTS_HTML_START[] = R"=====( )====="; const char EVENTS_HTML_END[] = R"=====( + @@ -37,11 +39,11 @@ String events_processor(const String& var) { for (const auto& event : order_events) { EVENTS_ENUM_TYPE event_handle = event.event_handle; event_pointer = event.event_pointer; -#ifdef DEBUG_VIA_USB - Serial.println("Event: " + String(get_event_enum_string(event_handle)) + - " count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) + - " data: " + String(event_pointer->data) + - " level: " + String(get_event_level_string(event_handle))); +#ifdef DEBUG_LOG + logging.println("Showing Event: " + String(get_event_enum_string(event_handle)) + + " count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) + + " data: " + String(event_pointer->data) + + " level: " + String(get_event_level_string(event_handle))); #endif content.concat("
"); content.concat("
" + String(get_event_enum_string(event_handle)) + "
"); @@ -58,8 +60,8 @@ String events_processor(const String& var) { order_events.clear(); content.concat(FPSTR(EVENTS_HTML_END)); return content; - return String(); } + return String(); } /* Script for displaying event log before it gets minified diff --git a/Software/src/devboard/webserver/index_html.cpp b/Software/src/devboard/webserver/index_html.cpp index 5d3d3bfd7..f22ad4889 100644 --- a/Software/src/devboard/webserver/index_html.cpp +++ b/Software/src/devboard/webserver/index_html.cpp @@ -1,6 +1,12 @@ -const char index_html[] = R"rawliteral( -Battery Emulator%X% -)rawliteral"; +#include "index_html.h" + +#define INDEX_HTML_HEADER \ + R"rawliteral(Battery Emulator)rawliteral" +#define INDEX_HTML_FOOTER R"rawliteral()rawliteral"; + +const char index_html[] = INDEX_HTML_HEADER "%X%" INDEX_HTML_FOOTER; +const char index_html_header[] = INDEX_HTML_HEADER; +const char index_html_footer[] = INDEX_HTML_FOOTER; /* The above code is minified (https://kangax.github.io/html-minifier/) to increase performance. Here is the full HTML function: diff --git a/Software/src/devboard/webserver/index_html.h b/Software/src/devboard/webserver/index_html.h new file mode 100644 index 000000000..8a095650a --- /dev/null +++ b/Software/src/devboard/webserver/index_html.h @@ -0,0 +1,8 @@ +#ifndef INDEX_HTML_H +#define INDEX_HTML_H + +extern const char index_html[]; +extern const char index_html_header[]; +extern const char index_html_footer[]; + +#endif // INDEX_HTML_H diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index caac64d40..67e1ba99b 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -8,6 +8,10 @@ String settings_processor(const String& var) { //Page format content += ""; content += ""; @@ -37,6 +41,11 @@ String settings_processor(const String& var) { content += "

Inverter interface: RS485

"; #endif +#ifdef CAN_SHUNT_SELECTED + content += "

Shunt Interface: " + + String(getCANInterfaceName(can_config.shunt)) + "

"; +#endif //CAN_SHUNT_SELECTED + // Close the block content += "
"; @@ -63,6 +72,21 @@ String settings_processor(const String& var) { content += "

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

"; + content += "

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

"; + content += + "

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

"; + content += "

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

"; // Close the block content += ""; @@ -78,6 +102,42 @@ String settings_processor(const String& var) { content += ""; #endif +#ifdef TESLA_MODEL_3Y_BATTERY + + // Start a new block with grey background color + content += "
"; + + content += + "

Manual LFP balancing: " + + String(datalayer.battery.settings.user_requests_balancing ? "" + : "") + + "

"; + content += + "

Balancing max time: " + String(datalayer.battery.settings.balancing_time_ms / 60000.0, 1) + + " Minutes

"; + content += + "

Balancing float power: " + String(datalayer.battery.settings.balancing_float_power_W / 1.0, 0) + + " W

"; + content += + "

Max battery voltage: " + String(datalayer.battery.settings.balancing_max_pack_voltage_dV / 10.0, 0) + + " V

"; + content += + "

Max cell voltage: " + String(datalayer.battery.settings.balancing_max_cell_voltage_mV / 1.0, 0) + + " mV

"; + content += + "

Max cell voltage deviation: " + + String(datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV / 1.0, 0) + + " mV

"; + + // Close the block + content += "
"; +#endif + #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Start a new block with orange background color @@ -126,7 +186,9 @@ String settings_processor(const String& var) { "updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 " "and 120000.');}}}"; content += - "function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (0 = No, 1 = " + "function editUseScaledSOC(){var value=prompt('Extends battery life by rescaling the SOC within the configured " + "minimum " + "and maximum percentage. Should SOC scaling be applied? (0 = No, 1 = " "Yes):');if(value!==null){if(value==0||value==1){var xhr=new " "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" "updateUseScaledSOC?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 " @@ -157,6 +219,74 @@ String settings_processor(const String& var) { "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" "updateMaxDischargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 " "and 1000.0');}}}"; + content += + "function editUseVoltageLimit(){var value=prompt('Enable this option to manually restrict charge/discharge to " + "a specific voltage set below." + "If disabled the emulator automatically determines this based on battery limits. Restrict manually? (0 = No, 1 " + "= Yes)" + ":');if(value!==null){if(value==0||value==1){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "updateUseVoltageLimit?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between " + "0 " + "and 1.');}}}"; + content += + "function editMaxChargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter new " + "voltage setpoint batttery should charge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var " + "xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "updateMaxChargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 0 " + "and 1000.0');}}}"; + content += + "function editMaxDischargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter " + "new " + "voltage setpoint batttery should discharge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var " + "xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "updateMaxDischargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 0 " + "and 1000.0');}}}"; + +#ifdef TESLA_MODEL_3Y_BATTERY + content += + "function editTeslaBalAct(){var value=prompt('Enable or disable forced LFP balancing. Makes the battery charge " + "to 101percent. This should be performed once every month, to keep LFP batteries balanced. Ensure battery is " + "fully charged before enabling, and also that you have enough sun or grid power to feed power into the battery " + "while balancing is active. Enter 1 for enabled, 0 " + "for disabled');if(value!==null){if(value==0||value==1){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "TeslaBalAct?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}"; + content += + "function editBalTime(){var value=prompt('Enter new max balancing time in " + "minutes');if(value!==null){if(value>=1&&value<=300){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalTime?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 1 and 300');}}}"; + content += + "function editBalFloatPower(){var value=prompt('Power level in Watt to float charge during forced " + "balancing');if(value!==null){if(value>=100&&value<=2000){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalFloatPower?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 100 and 2000');}}}"; + content += + "function editBalMaxPackV(){var value=prompt('Battery pack max voltage temporarily raised to this value during " + "forced balancing. Value in V');if(value!==null){if(value>=380&&value<=410){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalMaxPackV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 380 and 410');}}}"; + content += + "function editBalMaxCellV(){var value=prompt('Cellvoltage max temporarily raised to this value during forced " + "balancing. Value in mV');if(value!==null){if(value>=3400&&value<=3750){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalMaxCellV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 3400 and 3750');}}}"; + content += + "function editBalMaxDevCellV(){var value=prompt('Cellvoltage max deviation temporarily raised to this value " + "during forced balancing. Value in mV');if(value!==null){if(value>=300&&value<=600){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalMaxDevCellV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 300 and 600');}}}"; +#endif #ifdef TEST_FAKE_BATTERY content += @@ -223,7 +353,7 @@ const char* getCANInterfaceName(CAN_Interface interface) { #endif case CAN_ADDON_MCP2515: return "Add-on CAN via GPIO MCP2515"; - case CAN_ADDON_FD_MCP2518: + case CANFD_ADDON_MCP2518: #ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN return "Add-on CAN-FD via GPIO MCP2518 (Classic CAN)"; #else diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 360787d2c..fff877fb0 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,8 +1,11 @@ #include "webserver.h" #include +#include +#include "../../../USER_SECRETS.h" #include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer_extended.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" +#include "../sdcard/sdcard.h" #include "../utils/events.h" #include "../utils/led_handler.h" #include "../utils/timer.h" @@ -14,9 +17,11 @@ AsyncWebServer server(80); unsigned long ota_progress_millis = 0; #include "advanced_battery_html.h" +#include "can_logging_html.h" #include "cellmonitor_html.h" +#include "debug_logging_html.h" #include "events_html.h" -#include "index_html.cpp" +#include "index_html.h" #include "settings_html.h" MyTimer ota_timeout_timer = MyTimer(15000); @@ -26,8 +31,6 @@ const char get_firmware_info_html[] = R"rawliteral(%X%)rawliteral"; void init_webserver() { - String content = index_html; - server.on("/logout", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(401); }); // Route for firmware info from ota update page @@ -56,6 +59,114 @@ void init_webserver() { request->send_P(200, "text/html", index_html, advanced_battery_processor); }); + // Route for going to CAN logging web page + server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse(200, "text/html", can_logger_processor()); + request->send(response); + }); + +#if defined(DEBUG_VIA_WEB) || defined(LOG_TO_SD) + // Route for going to debug logging web page + server.on("/log", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse(200, "text/html", debug_logger_processor()); + request->send(response); + }); +#endif // DEBUG_VIA_WEB + + // Define the handler to stop can logging + server.on("/stop_can_logging", HTTP_GET, [](AsyncWebServerRequest* request) { + datalayer.system.info.can_logging_active = false; + request->send_P(200, "text/plain", "Logging stopped"); + }); + +#ifndef LOG_CAN_TO_SD + // Define the handler to export can log + server.on("/export_can_log", HTTP_GET, [](AsyncWebServerRequest* request) { + String logs = String(datalayer.system.info.logged_can_messages); + if (logs.length() == 0) { + logs = "No logs available."; + } + + // Get the current time + time_t now = time(nullptr); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + + // Ensure time retrieval was successful + char filename[32]; + if (strftime(filename, sizeof(filename), "canlog_%H-%M-%S.txt", &timeinfo)) { + // Valid filename created + } else { + // Fallback filename if automatic timestamping failed + strcpy(filename, "battery_emulator_can_log.txt"); + } + + // Use request->send with dynamic headers + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs); + response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\""); + request->send(response); + }); +#endif + +#ifdef LOG_CAN_TO_SD + // Define the handler to export can log + server.on("/export_can_log", HTTP_GET, [](AsyncWebServerRequest* request) { + pause_can_writing(); + request->send(SD_MMC, CAN_LOG_FILE, String(), true); + resume_can_writing(); + }); + + // Define the handler to delete can log + server.on("/delete_can_log", HTTP_GET, [](AsyncWebServerRequest* request) { + delete_can_log(); + request->send_P(200, "text/plain", "Log file deleted"); + }); +#endif + +#ifdef LOG_TO_SD + // Define the handler to delete log file + server.on("/delete_log", HTTP_GET, [](AsyncWebServerRequest* request) { + delete_log(); + request->send_P(200, "text/plain", "Log file deleted"); + }); + + // Define the handler to export debug log + server.on("/export_log", HTTP_GET, [](AsyncWebServerRequest* request) { + pause_log_writing(); + request->send(SD_MMC, LOG_FILE, String(), true); + resume_log_writing(); + }); +#endif + +#ifndef LOG_TO_SD + // Define the handler to export debug log + server.on("/export_log", HTTP_GET, [](AsyncWebServerRequest* request) { + String logs = String(datalayer.system.info.logged_can_messages); + if (logs.length() == 0) { + logs = "No logs available."; + } + + // Get the current time + time_t now = time(nullptr); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + + // Ensure time retrieval was successful + char filename[32]; + if (strftime(filename, sizeof(filename), "log_%H-%M-%S.txt", &timeinfo)) { + // Valid filename created + } else { + // Fallback filename if automatic timestamping failed + strcpy(filename, "battery_emulator_log.txt"); + } + + // Use request->send with dynamic headers + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs); + response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\""); + request->send(response); + }); +#endif + // Route for going to cellmonitor web page server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) { if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) @@ -91,7 +202,7 @@ void init_webserver() { String value = request->getParam("value")->value(); if (value.length() <= 63) { // Check if SSID is within the allowable length ssid = value.c_str(); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "SSID must be 63 characters or less"); @@ -108,7 +219,7 @@ void init_webserver() { String value = request->getParam("value")->value(); if (value.length() > 8) { // Check if password is within the allowable length password = value.c_str(); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Password must be atleast 8 characters"); @@ -125,7 +236,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); datalayer.battery.info.total_capacity_Wh = value.toInt(); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -139,7 +250,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); datalayer.battery.settings.soc_scaling_active = value.toInt(); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -153,7 +264,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); datalayer.battery.settings.max_percentage = static_cast(value.toFloat() * 100); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -197,7 +308,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); datalayer.battery.settings.min_percentage = static_cast(value.toFloat() * 100); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -211,7 +322,7 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); datalayer.battery.settings.max_user_set_charge_dA = static_cast(value.toFloat() * 10); - storeSettings(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); @@ -225,13 +336,64 @@ void init_webserver() { if (request->hasParam("value")) { String value = request->getParam("value")->value(); datalayer.battery.settings.max_user_set_discharge_dA = static_cast(value.toFloat() * 10); - storeSettings(); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing BATTERY_USE_VOLTAGE_LIMITS + server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.user_set_voltage_limits_active = value.toInt(); + store_settings(); request->send(200, "text/plain", "Updated successfully"); } else { request->send(400, "text/plain", "Bad Request"); } }); + // Route for editing MaxChargeVoltage + server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast(value.toFloat() * 10); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing MaxDischargeVoltage + server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast(value.toFloat() * 10); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for clearing isolation faults on Tesla + server.on("/clearIsolation", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + datalayer.battery.settings.user_requests_isolation_clear = true; + request->send(200, "text/plain", "Updated successfully"); + }); + // Route for resetting SOH on Nissan LEAF batteries server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) { if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { @@ -241,6 +403,33 @@ void init_webserver() { request->send(200, "text/plain", "Updated successfully"); }); + // Route for erasing DTC on Volvo/Polestar batteries + server.on("/volvoEraseDTC", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + datalayer_extended.VolvoPolestar.UserRequestDTCreset = true; + request->send(200, "text/plain", "Updated successfully"); + }); + + // Route for reading DTC on Volvo/Polestar batteries + server.on("/volvoReadDTC", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + datalayer_extended.VolvoPolestar.UserRequestDTCreadout = true; + request->send(200, "text/plain", "Updated successfully"); + }); + + // Route for performing ECU reset on Volvo/Polestar batteries + server.on("/volvoBECMecuReset", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + return request->requestAuthentication(); + } + datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = true; + request->send(200, "text/plain", "Updated successfully"); + }); + #ifdef TEST_FAKE_BATTERY // Route for editing FakeBatteryVoltage server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { @@ -259,6 +448,93 @@ void init_webserver() { }); #endif // TEST_FAKE_BATTERY +#ifdef TESLA_MODEL_3Y_BATTERY + + // Route for editing balancing enabled + server.on("/TeslaBalAct", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.user_requests_balancing = value.toInt(); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max time + server.on("/BalTime", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_time_ms = static_cast(value.toFloat() * 60000); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max power + server.on("/BalFloatPower", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_float_power_W = static_cast(value.toFloat()); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max pack voltage + server.on("/BalMaxPackV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_max_pack_voltage_dV = static_cast(value.toFloat() * 10); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max cell voltage + server.on("/BalMaxCellV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_max_cell_voltage_mV = static_cast(value.toFloat()); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max cell voltage deviation + server.on("/BalMaxDevCellV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV = static_cast(value.toFloat()); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); +#endif + #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Route for editing ChargerTargetV server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) { @@ -427,7 +703,10 @@ String get_firmware_info_processor(const String& var) { #endif // HW_STARK #ifdef HW_3LB doc["hardware"] = "3LB board"; -#endif // HW_STARK +#endif // HW_3LB +#ifdef HW_DEVKIT + doc["hardware"] = "ESP32 DevKit V1"; +#endif // HW_DEVKIT doc["firmware"] = String(version_number); serializeJson(doc, content); @@ -443,20 +722,25 @@ String processor(const String& var) { //Page format content += ""; // Start a new block with a specific background color content += "
"; // Show version number - content += "

Software: " + String(version_number) + "

"; + content += "

Software: " + String(version_number); // Show hardware used: #ifdef HW_LILYGO - content += "

Hardware: LilyGo T-CAN485

"; + content += " Hardware: LilyGo T-CAN485"; #endif // HW_LILYGO #ifdef HW_STARK - content += "

Hardware: Stark CMR Module

"; + content += " Hardware: Stark CMR Module"; #endif // HW_STARK + content += ""; content += "

Uptime: " + uptime_formatter::getUptime() + "

"; #ifdef FUNCTION_TIME_MEASUREMENT // Load information @@ -480,11 +764,14 @@ String processor(const String& var) { wl_status_t status = WiFi.status(); // Display ssid of network connected to and, if connected to the WiFi, its own IP - content += "

SSID: " + String(ssid.c_str()) + "

"; + content += "

SSID: " + String(ssid.c_str()); if (status == WL_CONNECTED) { - content += "

IP: " + WiFi.localIP().toString() + "

"; // Get and display the signal strength (RSSI) and channel - content += "

Signal strength: " + String(WiFi.RSSI()) + " dBm, at channel " + String(WiFi.channel()) + "

"; + content += " RSSI:" + String(WiFi.RSSI()) + " dBm Ch: " + String(WiFi.channel()); + } + content += ""; + if (status == WL_CONNECTED) { + content += "

IP: " + WiFi.localIP().toString() + "

"; } else { content += "

Wifi state: " + getConnectResultString(status) + "

"; } @@ -508,6 +795,12 @@ String processor(const String& var) { } content += ""; +#ifdef CAN_SHUNT_SELECTED + content += "

Shunt protocol: "; + content += datalayer.system.info.shunt_protocol; + content += "

"; +#endif + #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER content += "

Charger protocol: "; #ifdef CHEVYVOLT_CHARGER @@ -539,7 +832,6 @@ String processor(const String& var) { content += "#F5CC00;"; break; case led_color::BLUE: - case led_color::RGB: content += "#2B35AF;"; // Blue in test mode break; case led_color::RED: @@ -606,13 +898,30 @@ String processor(const String& var) { } content += "

Temperature max: " + String(tempMaxFloat, 1) + " C

"; content += "

Temperature min: " + String(tempMinFloat, 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

"; + + content += "

System status: "; + switch (datalayer.battery.status.bms_status) { + case ACTIVE: + content += String("OK"); + break; + case UPDATING: + content += String("UPDATING"); + break; + case FAULT: + content += String("FAULT"); + break; + case INACTIVE: + content += String("INACTIVE"); + break; + case STANDBY: + content += String("STANDBY"); + break; + default: + content += String("??"); + break; } + content += "

"; + if (datalayer.battery.status.current_dA == 0) { content += "

Battery idle

"; } else if (datalayer.battery.status.current_dA < 0) { @@ -641,7 +950,7 @@ String processor(const String& var) { content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; #ifdef CONTACTOR_CONTROL - content += "

Contactors controlled by Battery-Emulator: "; + content += "

Contactors controlled by emulator, state: "; if (datalayer.system.status.contactors_engaged) { content += "ON"; } else { @@ -649,13 +958,21 @@ String processor(const String& var) { } content += "

"; - content += "

Pre Charge: "; - if (digitalRead(PRECHARGE_PIN) == HIGH) { - content += ""; + content += "

Precharge: ("; + content += PRECHARGE_TIME_MS; + content += " ms) Cont. Neg.: "; +#ifdef PWM_CONTACTOR_CONTROL + if (datalayer.system.status.contactors_engaged) { + content += "Economized"; + content += " Cont. Pos.: "; + content += "Economized"; } else { content += ""; + content += " Cont. Pos.: "; + content += ""; } - content += " Cont. Neg.: "; + +#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) { content += ""; } else { @@ -668,6 +985,7 @@ String processor(const String& var) { } else { content += ""; } +#endif //no PWM_CONTACTOR_CONTROL content += "

"; #endif @@ -772,7 +1090,7 @@ String processor(const String& var) { content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; #ifdef CONTACTOR_CONTROL - content += "

Contactors controlled by Battery-Emulator: "; + content += "

Contactors controlled by emulator, state: "; if (datalayer.system.status.contactors_battery2_engaged) { content += "ON"; } else { @@ -780,13 +1098,19 @@ String processor(const String& var) { } content += "

"; #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY - content += "

Pre Charge: "; - if (digitalRead(SECOND_PRECHARGE_PIN) == HIGH) { - content += ""; + content += "

Cont. Neg.: "; +#ifdef PWM_CONTACTOR_CONTROL + if (datalayer.system.status.contactors_battery2_engaged) { + content += "Economized"; + content += " Cont. Pos.: "; + content += "Economized"; } else { content += ""; + content += " Cont. Pos.: "; + content += ""; } - content += " Cont. Neg.: "; + +#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) { content += ""; } else { @@ -799,6 +1123,7 @@ String processor(const String& var) { } else { content += ""; } +#endif //no PWM_CONTACTOR_CONTROL content += "

"; #endif // CONTACTOR_CONTROL_DOUBLE_BATTERY #endif // CONTACTOR_CONTROL @@ -874,6 +1199,10 @@ String processor(const String& var) { content += " "; content += " "; content += " "; + content += " "; +#if defined(DEBUG_VIA_WEB) || defined(LOG_TO_SD) + content += " "; +#endif // DEBUG_VIA_WEB content += " "; content += " "; content += ""; @@ -898,6 +1227,8 @@ String processor(const String& var) { content += "function Cellmon() { window.location.href = '/cellmonitor'; }"; content += "function Settings() { window.location.href = '/settings'; }"; content += "function Advanced() { window.location.href = '/advanced'; }"; + content += "function CANlog() { window.location.href = '/canlog'; }"; + content += "function Log() { window.location.href = '/log'; }"; content += "function Events() { window.location.href = '/events'; }"; content += "function askReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If " @@ -958,9 +1289,9 @@ void onOTAProgress(size_t current, size_t final) { // Log every 1 second if (millis() - ota_progress_millis > 1000) { ota_progress_millis = millis(); -#ifdef DEBUG_VIA_USB - Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); +#endif // DEBUG_LOG // Reset the "watchdog" ota_timeout_timer.reset(); } @@ -977,13 +1308,13 @@ void onOTAEnd(bool success) { // Max Charge/Discharge = 0; CAN = stop; contactors = open setBatteryPause(true, true, true, false); // a reboot will be done by the OTA library. no need to do anything here -#ifdef DEBUG_VIA_USB - Serial.println("OTA update finished successfully!"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.println("OTA update finished successfully!"); +#endif // DEBUG_LOG } else { -#ifdef DEBUG_VIA_USB - Serial.println("There was an error during OTA update!"); -#endif // DEBUG_VIA_USB +#ifdef DEBUG_LOG + logging.println("There was an error during OTA update!"); +#endif // DEBUG_LOG //try to Resume the battery pause and CAN communication setBatteryPause(false, false); } diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 21416850b..a75ef8fb6 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -104,7 +104,7 @@ void onOTAEnd(bool success); template String formatPowerValue(String label, T value, String unit, int precision, String color = "white"); -extern void storeSettings(); +extern void store_settings(); void ota_monitor(); diff --git a/Software/src/devboard/wifi/wifi.cpp b/Software/src/devboard/wifi/wifi.cpp index 91862e2a9..c158b52ad 100644 --- a/Software/src/devboard/wifi/wifi.cpp +++ b/Software/src/devboard/wifi/wifi.cpp @@ -29,12 +29,8 @@ static bool connected_once = false; void init_WiFi() { #ifdef WIFIAP - if (AccessPointEnabled) { - WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection - init_WiFi_AP(); - } else { - WiFi.mode(WIFI_STA); // Only Router connection - } + WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection + init_WiFi_AP(); #else WiFi.mode(WIFI_STA); // Only Router connection #endif // WIFIAP @@ -72,28 +68,28 @@ void wifi_monitor() { // Increase the current check interval if it's not at the maximum if (current_check_interval + STEP_WIFI_CHECK_INTERVAL <= MAX_STEP_WIFI_CHECK_INTERVAL) current_check_interval += STEP_WIFI_CHECK_INTERVAL; -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi not connected, attempting to reconnect..."); +#ifdef DEBUG_LOG + logging.println("Wi-Fi not connected, attempting to reconnect..."); #endif // Try WiFi.reconnect() if it was successfully connected at least once if (hasConnectedBefore) { lastReconnectAttempt = millis(); // Reset reconnection attempt timer -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi reconnect attempt..."); +#ifdef DEBUG_LOG + logging.println("Wi-Fi reconnect attempt..."); #endif if (WiFi.reconnect()) { -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi reconnect attempt sucess..."); +#ifdef DEBUG_LOG + logging.println("Wi-Fi reconnect attempt sucess..."); #endif reconnectAttempts = 0; // Reset the attempt counter on successful reconnect } else { -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi reconnect attempt error..."); +#ifdef DEBUG_LOG + logging.println("Wi-Fi reconnect attempt error..."); #endif reconnectAttempts++; if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { -#ifdef DEBUG_VIA_USB - Serial.println("Failed to reconnect multiple times, forcing a full connection attempt..."); +#ifdef DEBUG_LOG + logging.println("Failed to reconnect multiple times, forcing a full connection attempt..."); #endif FullReconnectToWiFi(); } @@ -101,8 +97,8 @@ void wifi_monitor() { } else { // If no previous connection, force a full connection attempt if (currentMillis - lastReconnectAttempt > current_full_reconnect_interval) { -#ifdef DEBUG_VIA_USB - Serial.println("No previous OK connection, force a full connection attempt..."); +#ifdef DEBUG_LOG + logging.println("No previous OK connection, force a full connection attempt..."); #endif FullReconnectToWiFi(); } @@ -127,13 +123,13 @@ static void FullReconnectToWiFi() { static void connectToWiFi() { if (WiFi.status() != WL_CONNECTED) { lastReconnectAttempt = millis(); // Reset the reconnect attempt timer -#ifdef DEBUG_VIA_USB - Serial.println("Connecting to Wi-Fi..."); +#ifdef DEBUG_LOG + logging.println("Connecting to Wi-Fi..."); #endif WiFi.begin(ssid.c_str(), password.c_str(), wifi_channel); } else { -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi already connected."); +#ifdef DEBUG_LOG + logging.println("Wi-Fi already connected."); #endif } } @@ -143,10 +139,11 @@ static void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) { clear_event(EVENT_WIFI_DISCONNECT); set_event(EVENT_WIFI_CONNECT, 0); connected_once = true; -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi connected."); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); +#ifdef DEBUG_LOG + logging.print("Wi-Fi connected. RSSI: "); + logging.print(-WiFi.RSSI()); + logging.print(" dBm, IP address: "); + logging.println(WiFi.localIP().toString()); #endif hasConnectedBefore = true; // Mark as successfully connected at least once reconnectAttempts = 0; // Reset the attempt counter @@ -159,10 +156,10 @@ static void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) { static void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { //clear disconnects events if we got a IP clear_event(EVENT_WIFI_DISCONNECT); -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi Got IP."); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); +#ifdef DEBUG_LOG + logging.print("Wi-Fi Got IP. "); + logging.print("IP address: "); + logging.println(WiFi.localIP().toString()); #endif } @@ -170,8 +167,8 @@ static void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { static void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) { if (connected_once) set_event(EVENT_WIFI_DISCONNECT, 0); -#ifdef DEBUG_VIA_USB - Serial.println("Wi-Fi disconnected."); +#ifdef DEBUG_LOG + logging.println("Wi-Fi disconnected."); #endif //we dont do anything here, the reconnect will be handled by the monitor //too many events received when the connection is lost @@ -188,8 +185,8 @@ void init_mDNS() { // Initialize mDNS .local resolution if (!MDNS.begin(mdnsHost)) { -#ifdef DEBUG_VIA_USB - Serial.println("Error setting up MDNS responder!"); +#ifdef DEBUG_LOG + logging.println("Error setting up MDNS responder!"); #endif } else { // Advertise via bonjour the service so we can auto discover these battery emulators on the local network. @@ -200,16 +197,16 @@ void init_mDNS() { #ifdef WIFIAP void init_WiFi_AP() { -#ifdef DEBUG_VIA_USB - Serial.println("Creating Access Point: " + String(ssidAP)); - Serial.println("With password: " + String(passwordAP)); +#ifdef DEBUG_LOG + logging.println("Creating Access Point: " + String(ssidAP)); + logging.println("With password: " + String(passwordAP)); #endif WiFi.softAP(ssidAP, passwordAP); IPAddress IP = WiFi.softAPIP(); -#ifdef DEBUG_VIA_USB - Serial.println("Access Point created."); - Serial.print("IP address: "); - Serial.println(IP); +#ifdef DEBUG_LOG + logging.println("Access Point created."); + logging.print("IP address: "); + logging.println(IP.toString()); #endif } #endif // WIFIAP diff --git a/Software/src/include.h b/Software/src/include.h index f00e23825..792b84465 100644 --- a/Software/src/include.h +++ b/Software/src/include.h @@ -9,6 +9,7 @@ #include "devboard/hal/hal.h" #include "devboard/safety/safety.h" +#include "devboard/utils/logging.h" #include "devboard/utils/time_meas.h" #include "devboard/utils/types.h" @@ -19,18 +20,13 @@ /* - ERROR CHECKS BELOW, DON'T TOUCH - */ #if !defined(HW_CONFIGURED) -#error You must select a HW to run on! -#endif - -#if defined(DUAL_CAN) && defined(CAN_FD) -// Check that user did not try to use dual can and fd-can on same hardware pins -#error CAN-FD AND DUAL-CAN CANNOT BE USED SIMULTANEOUSLY +#error You must select a target hardware in the USER_SERTTINGS.h file! #endif #ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN -#if !defined(CAN_FD) +#if !defined(CANFD_ADDON) // Check that user did not try to use classic CAN over FD, without FD component -#error PLEASE ENABLE CAN_FD TO USE CLASSIC CAN OVER CANFD INTERFACE +#error PLEASE ENABLE CANFD_ADDON TO USE CLASSIC CAN OVER CANFD INTERFACE #endif #endif @@ -48,8 +44,23 @@ #endif #endif +#ifdef HW_LILYGO +#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) +#if defined(CAN_ADDON) || defined(CANFD_ADDON) || defined(CHADEMO_BATTERY) +//Check that BMS reset is not used at the same time as Chademo and CAN addons +#error BMS RESET CANNOT BE USED AT SAME TIME AS CAN-ADDONS / CHADMEO! NOT ENOUGH GPIO! +#endif +#endif +#endif + #ifndef BATTERY_SELECTED #error No battery selected! Choose one from the USER_SETTINGS.h file #endif +#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) +#if !defined(HW_LILYGO) +#error The SD card logging feature is only available on LilyGo hardware +#endif +#endif + #endif diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp index a8d551d77..d9037d2ce 100644 --- a/Software/src/inverter/AFORE-CAN.cpp +++ b/Software/src/inverter/AFORE-CAN.cpp @@ -200,7 +200,7 @@ void update_values_can_inverter() { //This function maps all the values fetched */ } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x305: // Every 1s from inverter datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -217,19 +217,19 @@ void receive_can_inverter(CAN_frame rx_frame) { } } -void send_can_inverter() { +void transmit_can_inverter() { if (time_to_send_info) { // Set every 1s if we get message from inverter - transmit_can(&AFORE_350, can_config.inverter); - transmit_can(&AFORE_351, can_config.inverter); - transmit_can(&AFORE_352, can_config.inverter); - transmit_can(&AFORE_353, can_config.inverter); - transmit_can(&AFORE_354, can_config.inverter); - transmit_can(&AFORE_355, can_config.inverter); - transmit_can(&AFORE_356, can_config.inverter); - transmit_can(&AFORE_357, can_config.inverter); - transmit_can(&AFORE_358, can_config.inverter); - transmit_can(&AFORE_359, can_config.inverter); - transmit_can(&AFORE_35A, can_config.inverter); + transmit_can_frame(&AFORE_350, can_config.inverter); + transmit_can_frame(&AFORE_351, can_config.inverter); + transmit_can_frame(&AFORE_352, can_config.inverter); + transmit_can_frame(&AFORE_353, can_config.inverter); + transmit_can_frame(&AFORE_354, can_config.inverter); + transmit_can_frame(&AFORE_355, can_config.inverter); + transmit_can_frame(&AFORE_356, can_config.inverter); + transmit_can_frame(&AFORE_357, can_config.inverter); + transmit_can_frame(&AFORE_358, can_config.inverter); + transmit_can_frame(&AFORE_359, can_config.inverter); + transmit_can_frame(&AFORE_35A, can_config.inverter); time_to_send_info = false; } } diff --git a/Software/src/inverter/AFORE-CAN.h b/Software/src/inverter/AFORE-CAN.h index 9829befca..875502792 100644 --- a/Software/src/inverter/AFORE-CAN.h +++ b/Software/src/inverter/AFORE-CAN.h @@ -4,7 +4,7 @@ #define CAN_INVERTER_SELECTED -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 4ffa7b204..a8ebde6fb 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -8,6 +8,8 @@ static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Me static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send +#define VOLTAGE_OFFSET_DV 20 + CAN_frame BYD_250 = {.FD = false, .ext_ID = false, .DLC = 8, @@ -81,7 +83,8 @@ static int16_t inverter_temperature = 0; static uint16_t remaining_capacity_ah = 0; static uint16_t fully_charged_capacity_ah = 0; static long inverter_timestamp = 0; -static bool initialDataSent = 0; +static bool initialDataSent = false; +static bool inverterStartedUp = false; void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages @@ -98,12 +101,22 @@ void update_values_can_inverter() { //This function maps all the values fetched } //Map values to CAN messages - //Maxvoltage (eg 400.0V = 4000 , 16bits long) - BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); - BYD_110.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); - //Minvoltage (eg 300.0V = 3000 , 16bits long) - BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); - BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); + if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage + //Target charge voltage (eg 400.0V = 4000 , 16bits long) + BYD_110.data.u8[0] = (datalayer.battery.settings.max_user_set_charge_voltage_dV >> 8); + BYD_110.data.u8[1] = (datalayer.battery.settings.max_user_set_charge_voltage_dV & 0x00FF); + //Target discharge voltage (eg 300.0V = 3000 , 16bits long) + BYD_110.data.u8[2] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV >> 8); + BYD_110.data.u8[3] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV & 0x00FF); + } else { //Use the voltage based on battery reported design voltage +- offset to avoid triggering events + //Target charge voltage (eg 400.0V = 4000 , 16bits long) + BYD_110.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8); + BYD_110.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF); + //Target discharge voltage (eg 300.0V = 3000 , 16bits long) + BYD_110.data.u8[2] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8); + BYD_110.data.u8[3] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF); + } + //Maximum discharge power allowed (Unit: A+1) BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); @@ -141,20 +154,21 @@ void update_values_can_inverter() { //This function maps all the values fetched BYD_210.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG if (inverter_name[0] != 0) { - Serial.print("Detected inverter: "); + logging.print("Detected inverter: "); for (uint8_t i = 0; i < 7; i++) { - Serial.print((char)inverter_name[i]); + logging.print((char)inverter_name[i]); } - Serial.println(); + logging.println(); } #endif } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier! + inverterStartedUp = true; datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification send_intial_data(); @@ -165,16 +179,19 @@ void receive_can_inverter(CAN_frame rx_frame) { } break; case 0x091: + inverterStartedUp = true; datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; inverter_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) * 0.1; inverter_temperature = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1; break; case 0x0D1: + inverterStartedUp = true; datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; inverter_SOC = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; break; case 0x111: + inverterStartedUp = true; datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; inverter_timestamp = ((rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); @@ -184,44 +201,50 @@ void receive_can_inverter(CAN_frame rx_frame) { } } -void send_can_inverter() { +void transmit_can_inverter() { unsigned long currentMillis = millis(); + + if (!inverterStartedUp) { + //Avoid sending messages towards inverter, unless it has woken up and sent something to us first + return; + } + // Send initial CAN data once on bootup if (!initialDataSent) { send_intial_data(); - initialDataSent = 1; + initialDataSent = true; } // Send 2s CAN Message if (currentMillis - previousMillis2s >= INTERVAL_2_S) { previousMillis2s = currentMillis; - transmit_can(&BYD_110, can_config.inverter); + transmit_can_frame(&BYD_110, can_config.inverter); } // Send 10s CAN Message if (currentMillis - previousMillis10s >= INTERVAL_10_S) { previousMillis10s = currentMillis; - transmit_can(&BYD_150, can_config.inverter); - transmit_can(&BYD_1D0, can_config.inverter); - transmit_can(&BYD_210, can_config.inverter); + transmit_can_frame(&BYD_150, can_config.inverter); + transmit_can_frame(&BYD_1D0, can_config.inverter); + transmit_can_frame(&BYD_210, can_config.inverter); } //Send 60s message if (currentMillis - previousMillis60s >= INTERVAL_60_S) { previousMillis60s = currentMillis; - transmit_can(&BYD_190, can_config.inverter); + transmit_can_frame(&BYD_190, can_config.inverter); } } void send_intial_data() { - transmit_can(&BYD_250, can_config.inverter); - transmit_can(&BYD_290, can_config.inverter); - transmit_can(&BYD_2D0, can_config.inverter); - transmit_can(&BYD_3D0_0, can_config.inverter); - transmit_can(&BYD_3D0_1, can_config.inverter); - transmit_can(&BYD_3D0_2, can_config.inverter); - transmit_can(&BYD_3D0_3, can_config.inverter); + transmit_can_frame(&BYD_250, can_config.inverter); + transmit_can_frame(&BYD_290, can_config.inverter); + transmit_can_frame(&BYD_2D0, can_config.inverter); + transmit_can_frame(&BYD_3D0_0, can_config.inverter); + transmit_can_frame(&BYD_3D0_1, can_config.inverter); + transmit_can_frame(&BYD_3D0_2, can_config.inverter); + transmit_can_frame(&BYD_3D0_3, can_config.inverter); } void setup_inverter(void) { // Performs one time setup at startup over CAN bus strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63); diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h index 5a90d6bab..91d82d39c 100644 --- a/Software/src/inverter/BYD-CAN.h +++ b/Software/src/inverter/BYD-CAN.h @@ -7,7 +7,7 @@ #define FW_MINOR_VERSION 0x29 void send_intial_data(); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-MODBUS.h b/Software/src/inverter/BYD-MODBUS.h index 487c97847..d13266093 100644 --- a/Software/src/inverter/BYD-MODBUS.h +++ b/Software/src/inverter/BYD-MODBUS.h @@ -4,7 +4,7 @@ #define MODBUS_INVERTER_SELECTED -#define MB_RTU_NUM_VALUES 30000 +#define MB_RTU_NUM_VALUES 13100 #define MAX_POWER 40960 //BYD Modbus specific value extern uint16_t mbPV[MB_RTU_NUM_VALUES]; diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index e23f90062..da6d5d458 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -583,7 +583,7 @@ void update_values_can_inverter() { //This function maps all the CAN values fet // So do we really need to map the same two values over and over to 32 places? } -void send_can_inverter() { // This function loops as fast as possible +void transmit_can_inverter() { // This function loops as fast as possible if (send_cellvoltages) { unsigned long currentMillis = millis(); // Get the current time @@ -595,68 +595,68 @@ void send_can_inverter() { // This function loops as fast as possible // Send a subset of messages per iteration to avoid overloading the CAN bus / transmit buffer switch (can_message_cellvolt_index) { case 0: -#ifdef DEBUG_VIA_USB - Serial.println("Sending large batch"); +#ifdef DEBUG_LOG + logging.println("Sending large batch"); #endif - transmit_can(&FOXESS_0C1D, can_config.inverter); - transmit_can(&FOXESS_0C21, can_config.inverter); - transmit_can(&FOXESS_0C29, can_config.inverter); - transmit_can(&FOXESS_0C2D, can_config.inverter); - transmit_can(&FOXESS_0C31, can_config.inverter); + transmit_can_frame(&FOXESS_0C1D, can_config.inverter); + transmit_can_frame(&FOXESS_0C21, can_config.inverter); + transmit_can_frame(&FOXESS_0C29, can_config.inverter); + transmit_can_frame(&FOXESS_0C2D, can_config.inverter); + transmit_can_frame(&FOXESS_0C31, can_config.inverter); break; case 1: - transmit_can(&FOXESS_0C35, can_config.inverter); - transmit_can(&FOXESS_0C39, can_config.inverter); - transmit_can(&FOXESS_0C3D, can_config.inverter); - transmit_can(&FOXESS_0C41, can_config.inverter); - transmit_can(&FOXESS_0C45, can_config.inverter); + transmit_can_frame(&FOXESS_0C35, can_config.inverter); + transmit_can_frame(&FOXESS_0C39, can_config.inverter); + transmit_can_frame(&FOXESS_0C3D, can_config.inverter); + transmit_can_frame(&FOXESS_0C41, can_config.inverter); + transmit_can_frame(&FOXESS_0C45, can_config.inverter); break; case 2: - transmit_can(&FOXESS_0C49, can_config.inverter); - transmit_can(&FOXESS_0C4D, can_config.inverter); - transmit_can(&FOXESS_0C51, can_config.inverter); - transmit_can(&FOXESS_0C55, can_config.inverter); - transmit_can(&FOXESS_0C59, can_config.inverter); + transmit_can_frame(&FOXESS_0C49, can_config.inverter); + transmit_can_frame(&FOXESS_0C4D, can_config.inverter); + transmit_can_frame(&FOXESS_0C51, can_config.inverter); + transmit_can_frame(&FOXESS_0C55, can_config.inverter); + transmit_can_frame(&FOXESS_0C59, can_config.inverter); break; case 3: - transmit_can(&FOXESS_0C5D, can_config.inverter); - transmit_can(&FOXESS_0C61, can_config.inverter); - transmit_can(&FOXESS_0C65, can_config.inverter); - transmit_can(&FOXESS_0C69, can_config.inverter); - transmit_can(&FOXESS_0C6D, can_config.inverter); + transmit_can_frame(&FOXESS_0C5D, can_config.inverter); + transmit_can_frame(&FOXESS_0C61, can_config.inverter); + transmit_can_frame(&FOXESS_0C65, can_config.inverter); + transmit_can_frame(&FOXESS_0C69, can_config.inverter); + transmit_can_frame(&FOXESS_0C6D, can_config.inverter); break; case 4: - transmit_can(&FOXESS_0C71, can_config.inverter); - transmit_can(&FOXESS_0C75, can_config.inverter); - transmit_can(&FOXESS_0C79, can_config.inverter); - transmit_can(&FOXESS_0C7D, can_config.inverter); - transmit_can(&FOXESS_0C81, can_config.inverter); + transmit_can_frame(&FOXESS_0C71, can_config.inverter); + transmit_can_frame(&FOXESS_0C75, can_config.inverter); + transmit_can_frame(&FOXESS_0C79, can_config.inverter); + transmit_can_frame(&FOXESS_0C7D, can_config.inverter); + transmit_can_frame(&FOXESS_0C81, can_config.inverter); break; case 5: - transmit_can(&FOXESS_0C85, can_config.inverter); - transmit_can(&FOXESS_0C89, can_config.inverter); - transmit_can(&FOXESS_0C8D, can_config.inverter); - transmit_can(&FOXESS_0C91, can_config.inverter); - transmit_can(&FOXESS_0C95, can_config.inverter); + transmit_can_frame(&FOXESS_0C85, can_config.inverter); + transmit_can_frame(&FOXESS_0C89, can_config.inverter); + transmit_can_frame(&FOXESS_0C8D, can_config.inverter); + transmit_can_frame(&FOXESS_0C91, can_config.inverter); + transmit_can_frame(&FOXESS_0C95, can_config.inverter); break; case 6: - transmit_can(&FOXESS_0C99, can_config.inverter); - transmit_can(&FOXESS_0C9D, can_config.inverter); - transmit_can(&FOXESS_0CA1, can_config.inverter); - transmit_can(&FOXESS_0CA5, can_config.inverter); - transmit_can(&FOXESS_0CA9, can_config.inverter); + transmit_can_frame(&FOXESS_0C99, can_config.inverter); + transmit_can_frame(&FOXESS_0C9D, can_config.inverter); + transmit_can_frame(&FOXESS_0CA1, can_config.inverter); + transmit_can_frame(&FOXESS_0CA5, can_config.inverter); + transmit_can_frame(&FOXESS_0CA9, can_config.inverter); break; case 7: //Celltemperatures - transmit_can(&FOXESS_0D21, can_config.inverter); - transmit_can(&FOXESS_0D29, can_config.inverter); - transmit_can(&FOXESS_0D31, can_config.inverter); - transmit_can(&FOXESS_0D39, can_config.inverter); - transmit_can(&FOXESS_0D41, can_config.inverter); - transmit_can(&FOXESS_0D49, can_config.inverter); - transmit_can(&FOXESS_0D51, can_config.inverter); - transmit_can(&FOXESS_0D59, can_config.inverter); -#ifdef DEBUG_VIA_USB - Serial.println("Sending completed"); + transmit_can_frame(&FOXESS_0D21, can_config.inverter); + transmit_can_frame(&FOXESS_0D29, can_config.inverter); + transmit_can_frame(&FOXESS_0D31, can_config.inverter); + transmit_can_frame(&FOXESS_0D39, can_config.inverter); + transmit_can_frame(&FOXESS_0D41, can_config.inverter); + transmit_can_frame(&FOXESS_0D49, can_config.inverter); + transmit_can_frame(&FOXESS_0D51, can_config.inverter); + transmit_can_frame(&FOXESS_0D59, can_config.inverter); +#ifdef DEBUG_LOG + logging.println("Sending completed"); #endif send_cellvoltages = false; break; @@ -672,67 +672,68 @@ void send_can_inverter() { // This function loops as fast as possible } } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { if (rx_frame.ID == 0x1871) { datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; if (rx_frame.data.u8[0] == 0x03) { //0x1871 [0x03, 0x06, 0x17, 0x05, 0x09, 0x09, 0x28, 0x22] //This message is sent by the inverter every '6' seconds (0.5s after the pack serial numbers) //and contains a timestamp in bytes 2-7 i.e. ,,
,,, -#ifdef DEBUG_VIA_USB - Serial.println("Inverter sends current time and date"); +#ifdef DEBUG_LOG + logging.println("Inverter sends current time and date"); #endif } else if (rx_frame.data.u8[0] == 0x01) { if (rx_frame.data.u8[4] == 0x00) { // Inverter wants to know bms info (every 1s) -#ifdef DEBUG_VIA_USB - Serial.println("Inverter requests 1s BMS info, we reply"); +#ifdef DEBUG_LOG + logging.println("Inverter requests 1s BMS info, we reply"); #endif - transmit_can(&FOXESS_1872, can_config.inverter); - transmit_can(&FOXESS_1873, can_config.inverter); - transmit_can(&FOXESS_1874, can_config.inverter); - transmit_can(&FOXESS_1875, can_config.inverter); - transmit_can(&FOXESS_1876, can_config.inverter); - transmit_can(&FOXESS_1877, can_config.inverter); - transmit_can(&FOXESS_1878, can_config.inverter); - transmit_can(&FOXESS_1879, can_config.inverter); + transmit_can_frame(&FOXESS_1872, can_config.inverter); + transmit_can_frame(&FOXESS_1873, can_config.inverter); + transmit_can_frame(&FOXESS_1874, can_config.inverter); + transmit_can_frame(&FOXESS_1875, can_config.inverter); + transmit_can_frame(&FOXESS_1876, can_config.inverter); + transmit_can_frame(&FOXESS_1877, can_config.inverter); + transmit_can_frame(&FOXESS_1878, can_config.inverter); + transmit_can_frame(&FOXESS_1879, can_config.inverter); } else if (rx_frame.data.u8[4] == 0x01) { // b4 0x01 , 0x1871 [0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00] //Inverter wants to know all individual cellvoltages (occurs 6 seconds after valid BMS reply) -#ifdef DEBUG_VIA_USB - Serial.println("Inverter requests individual battery pack status, we reply"); +#ifdef DEBUG_LOG + logging.println("Inverter requests individual battery pack status, we reply"); #endif - transmit_can(&FOXESS_0C05, can_config.inverter); //TODO, should we limit this incase NUMBER_OF_PACKS =! 8? - transmit_can(&FOXESS_0C06, can_config.inverter); - transmit_can(&FOXESS_0C07, can_config.inverter); - transmit_can(&FOXESS_0C08, can_config.inverter); - transmit_can(&FOXESS_0C09, can_config.inverter); - transmit_can(&FOXESS_0C0A, can_config.inverter); - transmit_can(&FOXESS_0C0B, can_config.inverter); - transmit_can(&FOXESS_0C0C, can_config.inverter); + transmit_can_frame(&FOXESS_0C05, + can_config.inverter); //TODO, should we limit this incase NUMBER_OF_PACKS =! 8? + transmit_can_frame(&FOXESS_0C06, can_config.inverter); + transmit_can_frame(&FOXESS_0C07, can_config.inverter); + transmit_can_frame(&FOXESS_0C08, can_config.inverter); + transmit_can_frame(&FOXESS_0C09, can_config.inverter); + transmit_can_frame(&FOXESS_0C0A, can_config.inverter); + transmit_can_frame(&FOXESS_0C0B, can_config.inverter); + transmit_can_frame(&FOXESS_0C0C, can_config.inverter); } else if (rx_frame.data.u8[4] == 0x04) { // b4 0x01 , 0x1871 [0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00] //Inverter wants to know all individual cellvoltages (occurs 6 seconds after valid BMS reply) -#ifdef DEBUG_VIA_USB - Serial.println("Inverter requests cellvoltages and temps, we reply"); +#ifdef DEBUG_LOG + logging.println("Inverter requests cellvoltages and temps, we reply"); #endif send_cellvoltages = true; } } else if (rx_frame.data.u8[0] == 0x02) { //0x1871 [0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00] // Ack message -#ifdef DEBUG_VIA_USB - Serial.println("Inverter acks, no reply needed"); +#ifdef DEBUG_LOG + logging.println("Inverter acks, no reply needed"); #endif } else if (rx_frame.data.u8[0] == 0x05) { //0x1871 [0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00] -#ifdef DEBUG_VIA_USB - Serial.println("Inverter wants to know serial numbers, we reply"); +#ifdef DEBUG_LOG + logging.println("Inverter wants to know serial numbers, we reply"); #endif for (uint8_t i = 0; i < (NUMBER_OF_PACKS + 1); i++) { FOXESS_1881.data.u8[0] = (uint8_t)i; FOXESS_1882.data.u8[0] = (uint8_t)i; FOXESS_1883.data.u8[0] = (uint8_t)i; //TODO, should we add something to serial number field? - transmit_can(&FOXESS_1881, can_config.inverter); - transmit_can(&FOXESS_1882, can_config.inverter); - transmit_can(&FOXESS_1883, can_config.inverter); + transmit_can_frame(&FOXESS_1881, can_config.inverter); + transmit_can_frame(&FOXESS_1882, can_config.inverter); + transmit_can_frame(&FOXESS_1883, can_config.inverter); } } } diff --git a/Software/src/inverter/FOXESS-CAN.h b/Software/src/inverter/FOXESS-CAN.h index 365a559d0..456d5c83f 100644 --- a/Software/src/inverter/FOXESS-CAN.h +++ b/Software/src/inverter/FOXESS-CAN.h @@ -4,7 +4,7 @@ #define CAN_INVERTER_SELECTED -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/GROWATT-LV-CAN.cpp b/Software/src/inverter/GROWATT-LV-CAN.cpp new file mode 100644 index 000000000..01f62619c --- /dev/null +++ b/Software/src/inverter/GROWATT-LV-CAN.cpp @@ -0,0 +1,278 @@ +#include "../include.h" +#ifdef GROWATT_LV_CAN +#include "../datalayer/datalayer.h" +#include "GROWATT-LV-CAN.h" + +/* Growatt BMS CAN-Bus-protocol Low Voltage Rev_04 +CAN 2.0A +500kBit/sec +Big-endian + +The inverter replies data every second (standard frame/decimal)0x301:*/ + +/* Do not change code below unless you are sure what you are doing */ + +//Actual content messages +CAN_frame GROWATT_311 = {.FD = false, //Voltage and charge limits and status + .ext_ID = false, + .DLC = 8, + .ID = 0x311, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_312 = {.FD = false, //status bits , pack number, total cell number + .ext_ID = false, + .DLC = 8, + .ID = 0x312, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_313 = {.FD = false, //voltage, current, temp, soc, soh + .ext_ID = false, + .DLC = 8, + .ID = 0x313, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_314 = {.FD = false, //capacity, delta V, cycle count + .ext_ID = false, + .DLC = 8, + .ID = 0x314, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_319 = {.FD = false, //max/min cell voltage, num of cell max/min, protect pack ID + .ext_ID = false, + .DLC = 8, + .ID = 0x319, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_320 = {.FD = false, //manufacturer name, hw ver, sw ver, date and time + .ext_ID = false, + .DLC = 8, + .ID = 0x320, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_321 = {.FD = false, //Update status, ID + .ext_ID = false, + .DLC = 8, + .ID = 0x321, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +//Cellvoltages +CAN_frame GROWATT_315 = {.FD = false, //Cells 1-4 + .ext_ID = false, + .DLC = 8, + .ID = 0x315, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_316 = {.FD = false, //Cells 5-8 + .ext_ID = false, + .DLC = 8, + .ID = 0x316, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_317 = {.FD = false, //Cells 9-12 + .ext_ID = false, + .DLC = 8, + .ID = 0x317, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame GROWATT_318 = {.FD = false, //Cells 13-16 + .ext_ID = false, + .DLC = 8, + .ID = 0x318, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +#define VOLTAGE_OFFSET_DV 40 //Offset in deciVolt from max charge voltage and min discharge voltage +#define MAX_VOLTAGE_DV 630 +#define MIN_VOLTAGE_DV 410 + +static uint16_t cell_delta_mV = 0; +static uint16_t ampere_hours_remaining = 0; +static uint16_t ampere_hours_full = 0; + +void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages + + cell_delta_mV = datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV; + + if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 + ampere_hours_remaining = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + ampere_hours_full = ((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + } + //Map values to CAN messages + + //Battery charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 41V, MAX 63V, default 54V) + GROWATT_311.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8); + GROWATT_311.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF); + //Charge limited current, 125 =12.5A (0.1, A) + GROWATT_311.data.u8[2] = (datalayer.battery.status.max_charge_current_dA >> 8); + GROWATT_311.data.u8[3] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + //Discharge limited current, 500 = 50A, (0.1, A) + GROWATT_311.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + GROWATT_311.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + //Status bits (see documentation for all bits, most important are bit0-1 (Status), and bit 10-11 (SP status)) + if (datalayer.battery.status.active_power_W < -1) { // Discharging + GROWATT_311.data.u8[6] = 0x0C; //0b11 discharging on bit10-11 + GROWATT_311.data.u8[7] = 0x03; //0b11 discharging on bit0-1 + } else if (datalayer.battery.status.active_power_W > 1) { // Charging + GROWATT_311.data.u8[6] = 0x08; //0b10 charging on bit10-11 + GROWATT_311.data.u8[7] = 0x02; //0b10 charging on bit0-1 + } else { //Idle + GROWATT_311.data.u8[6] = 0x04; //0b01 charging on bit10-11 + GROWATT_311.data.u8[7] = 0x01; //0b01 charging on bit0-1 + } + + //Fault status bits. TODO, map these according to docmentation. + //GROWATT_312.data.u8[0] = + //GROWATT_312.data.u8[1] = + //GROWATT_312.data.u8[2] = + //GROWATT_312.data.u8[3] = + GROWATT_312.data.u8[4] = 0x01; // Pack number + GROWATT_312.data.u8[5] = 0xAA; // Manufacturer code + GROWATT_312.data.u8[6] = 0xBB; // Manufacturer code + GROWATT_312.data.u8[7] = datalayer.battery.info.number_of_cells; // Total cell number (1-254) + + //Voltage of single module or Average module voltage of system (0.01V) + GROWATT_313.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 8); + GROWATT_313.data.u8[1] = ((datalayer.battery.status.voltage_dV * 10) & 0x00FF); + //Module or system total current (0.1A Sint16) + GROWATT_313.data.u8[2] = (datalayer.battery.status.current_dA >> 8); + GROWATT_313.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); + //Cell max temperature (0.1C) + GROWATT_313.data.u8[4] = (datalayer.battery.status.temperature_max_dC >> 8); + GROWATT_313.data.u8[5] = (datalayer.battery.status.temperature_max_dC & 0x00FF); + //SOC of single module or average value of system (%) + GROWATT_313.data.u8[6] = (datalayer.battery.status.reported_soc / 100); + //SOH (%) (Bit 0~ Bit6 SOH Counters) Bit7 SOH flag (unsure what this is) + GROWATT_313.data.u8[7] = (datalayer.battery.status.soh_pptt / 100); + + //Remaining capacity (10 mAh) + GROWATT_314.data.u8[0] = ((ampere_hours_remaining * 100) >> 8); + GROWATT_314.data.u8[1] = ((ampere_hours_remaining * 100) & 0x00FF); + //Fully charged capacity (10 mAh) + GROWATT_314.data.u8[2] = ((ampere_hours_full * 100) >> 8); + GROWATT_314.data.u8[3] = ((ampere_hours_full * 100) & 0x00FF); + //Delta V (mV) + GROWATT_314.data.u8[4] = (cell_delta_mV >> 8); + GROWATT_314.data.u8[5] = (cell_delta_mV & 0x00FF); + //Cycle count (h) + GROWATT_314.data.u8[6] = 0; + GROWATT_314.data.u8[7] = 0; + + //Request charge/discharge + if (datalayer.battery.status.bms_status == ACTIVE) { + GROWATT_319.data.u8[0] = + 0xC0; //Bit7 charge enabled, Bit 6 discharge enabled (bit5 req force charge, bit 4 req force charge 2) + } else { + GROWATT_319.data.u8[0] = 0x00; + } + //TODO: if battery falls below SOC 5% during long idle time, we should set bit 5 + + //Maximum cell voltage (mV) + GROWATT_319.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8); + GROWATT_319.data.u8[2] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF); + // Min cell voltage (mV) + GROWATT_319.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8); + GROWATT_319.data.u8[4] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF); + //Maximum cell voltage number + GROWATT_319.data.u8[5] = 1; //Fake + // Min cell voltage number + GROWATT_319.data.u8[6] = 2; //Fake + //Protect pack ID + GROWATT_319.data.u8[7] = 0; //? + + // Manufacturer name (ASCII) Battery manufacturer abbreviation in capital letters + GROWATT_320.data.u8[0] = 0x42; //B + GROWATT_320.data.u8[1] = 0x45; //E + // Hardware revision (1-9) + GROWATT_320.data.u8[2] = 0x01; + // Software version (1-9) + GROWATT_320.data.u8[3] = 0x01; + //Date and Time + //Bit 0~5 Second0~59 + //Bit 6~11 Minute0~59 + //Bit 12~16 Hour0~23 + //Bit 17~21Day1~31 + //Bit 22~25 Month 1-12 + //Bit 26~31 Year (2000-2063) + GROWATT_320.data.u8[4] = 0; //TODO + GROWATT_320.data.u8[5] = 0; + GROWATT_320.data.u8[6] = 0; + GROWATT_320.data.u8[7] = 0; + + //Message 0x321 is update status. All blank is OK + + //Cellvoltage #1 + GROWATT_315.data.u8[0] = (datalayer.battery.status.cell_voltages_mV[0] >> 8); + GROWATT_315.data.u8[1] = (datalayer.battery.status.cell_voltages_mV[0] & 0x00FF); + //Cellvoltage #2 + GROWATT_315.data.u8[2] = (datalayer.battery.status.cell_voltages_mV[1] >> 8); + GROWATT_315.data.u8[3] = (datalayer.battery.status.cell_voltages_mV[1] & 0x00FF); + //Cellvoltage #3 + GROWATT_315.data.u8[4] = (datalayer.battery.status.cell_voltages_mV[2] >> 8); + GROWATT_315.data.u8[5] = (datalayer.battery.status.cell_voltages_mV[2] & 0x00FF); + //Cellvoltage #4 + GROWATT_315.data.u8[6] = (datalayer.battery.status.cell_voltages_mV[3] >> 8); + GROWATT_315.data.u8[7] = (datalayer.battery.status.cell_voltages_mV[3] & 0x00FF); + + //Cellvoltage #5 + GROWATT_316.data.u8[0] = (datalayer.battery.status.cell_voltages_mV[4] >> 8); + GROWATT_316.data.u8[1] = (datalayer.battery.status.cell_voltages_mV[4] & 0x00FF); + //Cellvoltage #6 + GROWATT_316.data.u8[2] = (datalayer.battery.status.cell_voltages_mV[5] >> 8); + GROWATT_316.data.u8[3] = (datalayer.battery.status.cell_voltages_mV[5] & 0x00FF); + //Cellvoltage #7 + GROWATT_316.data.u8[4] = (datalayer.battery.status.cell_voltages_mV[6] >> 8); + GROWATT_316.data.u8[5] = (datalayer.battery.status.cell_voltages_mV[6] & 0x00FF); + //Cellvoltage #8 + GROWATT_316.data.u8[6] = (datalayer.battery.status.cell_voltages_mV[7] >> 8); + GROWATT_316.data.u8[7] = (datalayer.battery.status.cell_voltages_mV[7] & 0x00FF); + + //Cellvoltage #9 + GROWATT_317.data.u8[0] = (datalayer.battery.status.cell_voltages_mV[8] >> 8); + GROWATT_317.data.u8[1] = (datalayer.battery.status.cell_voltages_mV[8] & 0x00FF); + //Cellvoltage #10 + GROWATT_317.data.u8[2] = (datalayer.battery.status.cell_voltages_mV[9] >> 8); + GROWATT_317.data.u8[3] = (datalayer.battery.status.cell_voltages_mV[9] & 0x00FF); + //Cellvoltage #11 + GROWATT_317.data.u8[4] = (datalayer.battery.status.cell_voltages_mV[10] >> 8); + GROWATT_317.data.u8[5] = (datalayer.battery.status.cell_voltages_mV[10] & 0x00FF); + //Cellvoltage #12 + GROWATT_317.data.u8[6] = (datalayer.battery.status.cell_voltages_mV[11] >> 8); + GROWATT_317.data.u8[7] = (datalayer.battery.status.cell_voltages_mV[11] & 0x00FF); + + //Cellvoltage #13 + GROWATT_318.data.u8[0] = (datalayer.battery.status.cell_voltages_mV[12] >> 8); + GROWATT_318.data.u8[1] = (datalayer.battery.status.cell_voltages_mV[12] & 0x00FF); + //Cellvoltage #14 + GROWATT_318.data.u8[2] = (datalayer.battery.status.cell_voltages_mV[13] >> 8); + GROWATT_318.data.u8[3] = (datalayer.battery.status.cell_voltages_mV[13] & 0x00FF); + //Cellvoltage #15 + GROWATT_318.data.u8[4] = (datalayer.battery.status.cell_voltages_mV[14] >> 8); + GROWATT_318.data.u8[5] = (datalayer.battery.status.cell_voltages_mV[14] & 0x00FF); + //Cellvoltage #16 + GROWATT_318.data.u8[6] = (datalayer.battery.status.cell_voltages_mV[15] >> 8); + GROWATT_318.data.u8[7] = (datalayer.battery.status.cell_voltages_mV[15] & 0x00FF); +} + +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x301: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + transmit_can_frame(&GROWATT_311, can_config.inverter); + transmit_can_frame(&GROWATT_312, can_config.inverter); + transmit_can_frame(&GROWATT_313, can_config.inverter); + transmit_can_frame(&GROWATT_314, can_config.inverter); + transmit_can_frame(&GROWATT_315, can_config.inverter); + transmit_can_frame(&GROWATT_316, can_config.inverter); + transmit_can_frame(&GROWATT_317, can_config.inverter); + transmit_can_frame(&GROWATT_318, can_config.inverter); + transmit_can_frame(&GROWATT_319, can_config.inverter); + transmit_can_frame(&GROWATT_320, can_config.inverter); + transmit_can_frame(&GROWATT_321, can_config.inverter); + break; + default: + break; + } +} + +void transmit_can_inverter() { + // No periodic sending for this battery type. Data is sent when inverter requests it +} + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "Growatt Low Voltage (48V) protocol via CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} +#endif diff --git a/Software/src/inverter/GROWATT-LV-CAN.h b/Software/src/inverter/GROWATT-LV-CAN.h new file mode 100644 index 000000000..fe6dce341 --- /dev/null +++ b/Software/src/inverter/GROWATT-LV-CAN.h @@ -0,0 +1,10 @@ +#ifndef GROWATT_LV_CAN_H +#define GROWATT_LV_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +void transmit_can_frame(CAN_frame* tx_frame, int interface); +void setup_inverter(void); + +#endif diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index f1944ecfe..30786dc4f 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -15,10 +15,6 @@ #include "BYD-MODBUS.h" #endif -#ifdef BYD_SMA -#include "BYD-SMA.h" -#endif - #ifdef BYD_KOSTAL_RS485 #include "KOSTAL-RS485.h" #endif @@ -27,6 +23,10 @@ #include "FOXESS-CAN.h" #endif +#ifdef GROWATT_LV_CAN +#include "GROWATT-LV-CAN.h" +#endif + #ifdef PYLON_CAN #include "PYLON-CAN.h" #endif @@ -39,8 +39,16 @@ #include "SCHNEIDER-CAN.h" #endif -#ifdef SMA_CAN -#include "SMA-CAN.h" +#ifdef SMA_BYD_H_CAN +#include "SMA-BYD-H-CAN.h" +#endif + +#ifdef SMA_BYD_HVS_CAN +#include "SMA-BYD-HVS-CAN.h" +#endif + +#ifdef SMA_LV_CAN +#include "SMA-LV-CAN.h" #endif #ifdef SMA_TRIPOWER_CAN @@ -61,8 +69,8 @@ #ifdef CAN_INVERTER_SELECTED void update_values_can_inverter(); -void receive_can_inverter(CAN_frame rx_frame); -void send_can_inverter(); +void map_can_frame_to_variable_inverter(CAN_frame rx_frame); +void transmit_can_inverter(); #endif #ifdef MODBUS_INVERTER_SELECTED diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 7df23ff1f..881907f58 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -117,19 +117,40 @@ void float2frameMSB(byte* arr, float value, byte framepointer) { arr[framepointer + 1] = g.b[3]; } -void send_kostal(byte* arr, int alen) { +static void dbg_timestamp(void) { #ifdef DEBUG_KOSTAL_RS485_DATA - Serial.print("TX: "); - for (int i = 0; i < alen; i++) { - if (arr[i] < 0x10) { - Serial.print("0"); + logging.print("["); + logging.print(millis()); + logging.print(" ms] "); +#endif +} + +static void dbg_frame(byte* frame, int len, const char* prefix) { + dbg_timestamp(); +#ifdef DEBUG_KOSTAL_RS485_DATA + logging.print(prefix); + logging.print(": "); + for (uint8_t i = 0; i < len; i++) { + if (frame[i] < 0x10) { + logging.print("0"); } - Serial.print(arr[i], HEX); - Serial.print(" "); + logging.print(frame[i], HEX); + logging.print(" "); } - Serial.println("\n"); + logging.println(""); #endif - Serial2.write(arr, alen); +} + +static void dbg_message(const char* msg) { + dbg_timestamp(); +#ifdef DEBUG_KOSTAL_RS485_DATA + logging.println(msg); +#endif +} + +static void send_kostal(byte* frame, int len) { + dbg_frame(frame, len, "TX"); + Serial2.write(frame, len); } byte calculate_longframe_crc(byte* lfc, int lastbyte) { @@ -247,6 +268,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } if (currentMillis - contactorMillis >= INTERVAL_2_S & !RX_allow) { RX_allow = true; + dbg_message("RX_allow -> true"); } if (startupMillis) { @@ -255,10 +277,12 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream // Disconnect allowed only, when curren zero if (datalayer.battery.status.current_dA == 0) { datalayer.system.status.inverter_allows_contactor_closing = false; + dbg_message("inverter_allows_contactor_closing -> false"); } } else if (((currentMillis - startupMillis) >= 7000) & datalayer.system.status.inverter_allows_contactor_closing == false) { datalayer.system.status.inverter_allows_contactor_closing = true; + dbg_message("inverter_allows_contactor_closing -> true"); } } @@ -266,6 +290,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if ((currentMillis - B1_last_millis) > INTERVAL_1_S) { send_kostal(frameB1b, 8); B1_delay = false; + dbg_message("B1_delay -> false"); } } else if (Serial2.available()) { RS485_RXFRAME[rx_index] = Serial2.read(); @@ -273,14 +298,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream rx_index++; if (RS485_RXFRAME[rx_index - 1] == 0x00) { if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) { -#ifdef DEBUG_KOSTAL_RS485_DATA - Serial.print("RX: "); - for (uint8_t i = 0; i < 10; i++) { - Serial.print(RS485_RXFRAME[i], HEX); - Serial.print(" "); - } - Serial.println(""); -#endif + dbg_frame(RS485_RXFRAME, 10, "RX"); rx_index = 0; if (check_kostal_frame_crc()) { incoming_message_counter = RS485_HEALTHY; @@ -299,6 +317,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) { send_kostal(frameB1, 10); B1_delay = true; + dbg_message("B1_delay -> true"); B1_last_millis = currentMillis; } @@ -334,11 +353,14 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream send_kostal(frame3, 9); } } + } else { + dbg_frame(RS485_RXFRAME, 10, "RX (dropped)"); } rx_index = 0; } } if (rx_index >= 10) { + dbg_frame(RS485_RXFRAME, 10, "RX (!RX_allow)"); rx_index = 0; } } diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index 3de0697d9..6f638ac6a 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -4,6 +4,10 @@ #include "../include.h" #define RS485_INVERTER_SELECTED -//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial +//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via logging + +#if defined(DEBUG_KOSTAL_RS485_DATA) && !defined(DEBUG_LOG) +#error "enable LOG_TO_SD, DEBUG_VIA_USB or DEBUG_VIA_WEB in order to use DEBUG_KOSTAL_RS485_DATA" +#endif #endif diff --git a/Software/src/inverter/PYLON-CAN.cpp b/Software/src/inverter/PYLON-CAN.cpp index 2145b3e7f..1c041b74f 100644 --- a/Software/src/inverter/PYLON-CAN.cpp +++ b/Software/src/inverter/PYLON-CAN.cpp @@ -134,7 +134,21 @@ CAN_frame PYLON_4291 = {.FD = false, .ID = 0x4291, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +static uint16_t discharge_cutoff_voltage_dV = 0; +static uint16_t charge_cutoff_voltage_dV = 0; +#define VOLTAGE_OFFSET_DV 20 // Small offset voltage to avoid generating voltage events + void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages + + //Check what discharge and charge cutoff voltages to send + if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage + discharge_cutoff_voltage_dV = datalayer.battery.settings.max_user_set_discharge_voltage_dV; + charge_cutoff_voltage_dV = datalayer.battery.settings.max_user_set_charge_voltage_dV; + } else { + discharge_cutoff_voltage_dV = (datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV); + charge_cutoff_voltage_dV = (datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV); + } + //There are more mappings that could be added, but this should be enough to use as a starting point // Note we map both 0 and 1 messages @@ -166,12 +180,12 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); -#else +#else // Not INVERT_LOW_HIGH_BYTES PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); -#endif +#endif // INVERT_LOW_HIGH_BYTES //SOC (100.00%) PYLON_4210.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals PYLON_4211.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals @@ -204,12 +218,12 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8); PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF); PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8); -#else +#else // Not SET_30K_OFFSET PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF); PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA >> 8); PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF); PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA >> 8); -#endif +#endif //SET_30K_OFFSET // BMS Temperature (We dont have BMS temp, send max cell voltage instead) PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); @@ -217,17 +231,17 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); - //Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage - PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); - PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8); - PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); - PYLON_4221.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8); + //Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage + PYLON_4220.data.u8[0] = (charge_cutoff_voltage_dV & 0x00FF); + PYLON_4220.data.u8[1] = (charge_cutoff_voltage_dV >> 8); + PYLON_4221.data.u8[0] = (charge_cutoff_voltage_dV & 0x00FF); + PYLON_4221.data.u8[1] = (charge_cutoff_voltage_dV >> 8); - //Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage - PYLON_4220.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); - PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8); - PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); - PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8); + //Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage + PYLON_4220.data.u8[2] = (discharge_cutoff_voltage_dV & 0x00FF); + PYLON_4220.data.u8[3] = (discharge_cutoff_voltage_dV >> 8); + PYLON_4221.data.u8[2] = (discharge_cutoff_voltage_dV & 0x00FF); + PYLON_4221.data.u8[3] = (discharge_cutoff_voltage_dV >> 8); #ifdef SET_30K_OFFSET //Max ChargeCurrent @@ -241,7 +255,7 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8); PYLON_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF); PYLON_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8); -#else +#else // Not SET_30K_OFFSET //Max ChargeCurrent PYLON_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); PYLON_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8); @@ -253,7 +267,7 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8); PYLON_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); PYLON_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8); -#endif +#endif // SET_30K_OFFSET //Max cell voltage PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF); @@ -276,8 +290,8 @@ void update_values_can_inverter() { //This function maps all the values fetched //Max/Min temperature per cell PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF); PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8); - PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF); - PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8); + PYLON_4241.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF); + PYLON_4241.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8); //Max temperature per module PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF); @@ -290,7 +304,7 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8); PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF); PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8); -#else +#else // Not INVERT_LOW_HIGH_BYTES //Voltage (370.0) PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8; PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF); @@ -303,12 +317,12 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF); PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8); PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF); -#else +#else // Not SET_30K_OFFSET PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8); PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8); PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); -#endif +#endif //SET_30K_OFFSET // BMS Temperature (We dont have BMS temp, send max cell voltage instead) PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); @@ -316,17 +330,17 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8); PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF); - //Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage - PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); - PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); - PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); - PYLON_4221.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); + //Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage + PYLON_4220.data.u8[0] = (charge_cutoff_voltage_dV >> 8); + PYLON_4220.data.u8[1] = (charge_cutoff_voltage_dV & 0x00FF); + PYLON_4221.data.u8[0] = (charge_cutoff_voltage_dV >> 8); + PYLON_4221.data.u8[1] = (charge_cutoff_voltage_dV & 0x00FF); - //Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage - PYLON_4220.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); - PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); - PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); - PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); + //Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage + PYLON_4220.data.u8[2] = (discharge_cutoff_voltage_dV >> 8); + PYLON_4220.data.u8[3] = (discharge_cutoff_voltage_dV & 0x00FF); + PYLON_4221.data.u8[2] = (discharge_cutoff_voltage_dV >> 8); + PYLON_4221.data.u8[3] = (discharge_cutoff_voltage_dV & 0x00FF); #ifdef SET_30K_OFFSET //Max ChargeCurrent @@ -340,7 +354,7 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF); PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) >> 8); PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF); -#else +#else // Not SET_30K_OFFSET //Max ChargeCurrent PYLON_4220.data.u8[4] = (max_charge_current >> 8); PYLON_4220.data.u8[5] = (max_charge_current & 0x00FF); @@ -352,7 +366,7 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4220.data.u8[7] = (max_discharge_current & 0x00FF); PYLON_4221.data.u8[6] = (max_discharge_current >> 8); PYLON_4221.data.u8[7] = (max_discharge_current & 0x00FF); -#endif +#endif //SET_30K_OFFSET //Max cell voltage PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8); @@ -375,8 +389,8 @@ void update_values_can_inverter() { //This function maps all the values fetched //Max/Min temperature per cell PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); - PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); - PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); + PYLON_4241.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); + PYLON_4241.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); //Max temperature per module PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8); @@ -389,25 +403,7 @@ void update_values_can_inverter() { //This function maps all the values fetched PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); -#endif - - //Max/Min cell voltage - PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8); - PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF); - PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8); - PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF); - - //Max/Min temperature per cell - PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8); - PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF); - PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); - PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); - - //Max/Min temperature per module - PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8); - PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF); - PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); - PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); +#endif // Not INVERT_LOW_HIGH_BYTES //In case we run into any errors/faults, we can set charge / discharge forbidden if (datalayer.battery.status.bms_status == FAULT) { @@ -422,7 +418,7 @@ void update_values_can_inverter() { //This function maps all the values fetched } } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -438,43 +434,43 @@ void receive_can_inverter(CAN_frame rx_frame) { } } -void send_can_inverter() { +void transmit_can_inverter() { // No periodic sending, we only react on received can messages } void send_setup_info() { //Ensemble information #ifdef SEND_0 - transmit_can(&PYLON_7310, can_config.inverter); - transmit_can(&PYLON_7320, can_config.inverter); + transmit_can_frame(&PYLON_7310, can_config.inverter); + transmit_can_frame(&PYLON_7320, can_config.inverter); #endif #ifdef SEND_1 - transmit_can(&PYLON_7311, can_config.inverter); - transmit_can(&PYLON_7321, can_config.inverter); + transmit_can_frame(&PYLON_7311, can_config.inverter); + transmit_can_frame(&PYLON_7321, can_config.inverter); #endif } void send_system_data() { //System equipment information #ifdef SEND_0 - transmit_can(&PYLON_4210, can_config.inverter); - transmit_can(&PYLON_4220, can_config.inverter); - transmit_can(&PYLON_4230, can_config.inverter); - transmit_can(&PYLON_4240, can_config.inverter); - transmit_can(&PYLON_4250, can_config.inverter); - transmit_can(&PYLON_4260, can_config.inverter); - transmit_can(&PYLON_4270, can_config.inverter); - transmit_can(&PYLON_4280, can_config.inverter); - transmit_can(&PYLON_4290, can_config.inverter); + transmit_can_frame(&PYLON_4210, can_config.inverter); + transmit_can_frame(&PYLON_4220, can_config.inverter); + transmit_can_frame(&PYLON_4230, can_config.inverter); + transmit_can_frame(&PYLON_4240, can_config.inverter); + transmit_can_frame(&PYLON_4250, can_config.inverter); + transmit_can_frame(&PYLON_4260, can_config.inverter); + transmit_can_frame(&PYLON_4270, can_config.inverter); + transmit_can_frame(&PYLON_4280, can_config.inverter); + transmit_can_frame(&PYLON_4290, can_config.inverter); #endif #ifdef SEND_1 - transmit_can(&PYLON_4211, can_config.inverter); - transmit_can(&PYLON_4221, can_config.inverter); - transmit_can(&PYLON_4231, can_config.inverter); - transmit_can(&PYLON_4241, can_config.inverter); - transmit_can(&PYLON_4251, can_config.inverter); - transmit_can(&PYLON_4261, can_config.inverter); - transmit_can(&PYLON_4271, can_config.inverter); - transmit_can(&PYLON_4281, can_config.inverter); - transmit_can(&PYLON_4291, can_config.inverter); + transmit_can_frame(&PYLON_4211, can_config.inverter); + transmit_can_frame(&PYLON_4221, can_config.inverter); + transmit_can_frame(&PYLON_4231, can_config.inverter); + transmit_can_frame(&PYLON_4241, can_config.inverter); + transmit_can_frame(&PYLON_4251, can_config.inverter); + transmit_can_frame(&PYLON_4261, can_config.inverter); + transmit_can_frame(&PYLON_4271, can_config.inverter); + transmit_can_frame(&PYLON_4281, can_config.inverter); + transmit_can_frame(&PYLON_4291, can_config.inverter); #endif } void setup_inverter(void) { // Performs one time setup at startup over CAN bus diff --git a/Software/src/inverter/PYLON-CAN.h b/Software/src/inverter/PYLON-CAN.h index 6b39afcde..bbe9b80d8 100644 --- a/Software/src/inverter/PYLON-CAN.h +++ b/Software/src/inverter/PYLON-CAN.h @@ -6,7 +6,7 @@ void send_system_data(); void send_setup_info(); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index e5eb2e874..ddc2ce5a3 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -108,7 +108,7 @@ void update_values_can_inverter() { // PYLON_35E is pre-filled with the manufacturer name } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x305: //Message originating from inverter. // according to the spec, this message includes only 0-bytes @@ -119,18 +119,18 @@ void receive_can_inverter(CAN_frame rx_frame) { } } -void send_can_inverter() { +void transmit_can_inverter() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis1000ms >= 1000) { previousMillis1000ms = currentMillis; - transmit_can(&PYLON_351, can_config.inverter); - transmit_can(&PYLON_355, can_config.inverter); - transmit_can(&PYLON_356, can_config.inverter); - transmit_can(&PYLON_359, can_config.inverter); - transmit_can(&PYLON_35C, can_config.inverter); - transmit_can(&PYLON_35E, can_config.inverter); + transmit_can_frame(&PYLON_351, can_config.inverter); + transmit_can_frame(&PYLON_355, can_config.inverter); + transmit_can_frame(&PYLON_356, can_config.inverter); + transmit_can_frame(&PYLON_359, can_config.inverter); + transmit_can_frame(&PYLON_35C, can_config.inverter); + transmit_can_frame(&PYLON_35E, can_config.inverter); } } void setup_inverter(void) { // Performs one time setup at startup over CAN bus diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h index ca6922eb2..e38f5506a 100644 --- a/Software/src/inverter/PYLON-LV-CAN.h +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -11,7 +11,7 @@ void send_system_data(); void send_setup_info(); -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/SCHNEIDER-CAN.cpp b/Software/src/inverter/SCHNEIDER-CAN.cpp index 68021ef01..38cf1ed93 100644 --- a/Software/src/inverter/SCHNEIDER-CAN.cpp +++ b/Software/src/inverter/SCHNEIDER-CAN.cpp @@ -255,7 +255,7 @@ void update_values_can_inverter() { //This function maps all the values fetched SE_320.data.u8[1] = 0x02; } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x310: // Still alive message from inverter, every 1s datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -265,35 +265,35 @@ void receive_can_inverter(CAN_frame rx_frame) { } } -void send_can_inverter() { +void transmit_can_inverter() { unsigned long currentMillis = millis(); // Send 500ms CAN Message if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { previousMillis500ms = currentMillis; - transmit_can(&SE_321, can_config.inverter); - transmit_can(&SE_322, can_config.inverter); - transmit_can(&SE_323, can_config.inverter); - transmit_can(&SE_324, can_config.inverter); - transmit_can(&SE_325, can_config.inverter); + transmit_can_frame(&SE_321, can_config.inverter); + transmit_can_frame(&SE_322, can_config.inverter); + transmit_can_frame(&SE_323, can_config.inverter); + transmit_can_frame(&SE_324, can_config.inverter); + transmit_can_frame(&SE_325, can_config.inverter); } // Send 2s CAN Message if (currentMillis - previousMillis2s >= INTERVAL_2_S) { previousMillis2s = currentMillis; - transmit_can(&SE_320, can_config.inverter); - transmit_can(&SE_326, can_config.inverter); - transmit_can(&SE_327, can_config.inverter); + transmit_can_frame(&SE_320, can_config.inverter); + transmit_can_frame(&SE_326, can_config.inverter); + transmit_can_frame(&SE_327, can_config.inverter); } // Send 10s CAN Message if (currentMillis - previousMillis10s >= INTERVAL_10_S) { previousMillis10s = currentMillis; - transmit_can(&SE_328, can_config.inverter); - transmit_can(&SE_330, can_config.inverter); - transmit_can(&SE_331, can_config.inverter); - transmit_can(&SE_332, can_config.inverter); - transmit_can(&SE_333, can_config.inverter); + transmit_can_frame(&SE_328, can_config.inverter); + transmit_can_frame(&SE_330, can_config.inverter); + transmit_can_frame(&SE_331, can_config.inverter); + transmit_can_frame(&SE_332, can_config.inverter); + transmit_can_frame(&SE_333, can_config.inverter); } } diff --git a/Software/src/inverter/SCHNEIDER-CAN.h b/Software/src/inverter/SCHNEIDER-CAN.h index 83e7c87ae..d576308cb 100644 --- a/Software/src/inverter/SCHNEIDER-CAN.h +++ b/Software/src/inverter/SCHNEIDER-CAN.h @@ -27,7 +27,7 @@ #define COMMAND_CHARGE_AND_DISCHARGE_ALLOWED 0x06 #define COMMAND_STOP 0x08 -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp index a61ea7dc0..35772e4a4 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp @@ -60,8 +60,8 @@ void manageSerialLinkTransmitter() { } bool sendError = dataLinkTransmit.checkTransmissionError(true); if (sendError) { - Serial.print(currentTime); - Serial.println(" - ERROR: Serial Data Link - SEND Error"); + logging.print(currentTime); + logging.println(" - ERROR: Serial Data Link - SEND Error"); lasterror = true; transmitGoodSince = currentTime; } @@ -82,17 +82,17 @@ void manageSerialLinkTransmitter() { if (lasterror && (ackReceived > 0)) { lasterror = false; - Serial.print(currentTime); - Serial.println(" - RECOVERY: Serial Data Link - Send GOOD"); + logging.print(currentTime); + logging.println(" - RECOVERY: Serial Data Link - Send GOOD"); } //--- reporting every 60 seconds that transmission is good if (currentTime - transmitGoodSince > INTERVAL_60_S) { transmitGoodSince = currentTime; - Serial.print(currentTime); - Serial.println(" - Transmit Good"); + logging.print(currentTime); + logging.println(" - Transmit Good"); // printUsefullData(); -#ifdef DEBUG_VIA_USB +#ifdef DEBUG_LOG void printSendingValues(); #endif } @@ -100,13 +100,13 @@ void manageSerialLinkTransmitter() { //--- report that Errors been ocurring for > 60 seconds if (currentTime - lastGood > INTERVAL_60_S) { lastGood = currentTime; - Serial.print(currentTime); - Serial.println(" - Transmit Failed : 60 seconds"); + logging.print(currentTime); + logging.println(" - Transmit Failed : 60 seconds"); // print the max_ data - Serial.println("SerialDataLink : bms_status=4"); - Serial.println("SerialDataLink : LEDcolor = RED"); - Serial.println("SerialDataLink : max_target_discharge_power = 0"); - Serial.println("SerialDataLink : max_target_charge_power = 0"); + logging.println("SerialDataLink : bms_status=4"); + logging.println("SerialDataLink : LEDcolor = RED"); + logging.println("SerialDataLink : max_target_discharge_power = 0"); + logging.println("SerialDataLink : max_target_charge_power = 0"); datalayer.battery.status.max_discharge_power_W = 0; datalayer.battery.status.max_charge_power_W = 0; @@ -117,8 +117,8 @@ void manageSerialLinkTransmitter() { // lastMessageReceived from CAN bus (Battery) if (currentTime - lastMessageReceived > (5 * 60000) ) // 5 minutes { - Serial.print(millis()); - Serial.println(" - Data Stale : 5 minutes"); + logging.print(millis()); + logging.println(" - Data Stale : 5 minutes"); // throw error // stop transmitting until fresh @@ -154,42 +154,42 @@ void manageSerialLinkTransmitter() { } void printSendingValues() { - Serial.println("Values from battery: "); - Serial.print("SOC: "); - Serial.print(datalayer.battery.status.real_soc); - Serial.print(" SOH: "); - Serial.print(datalayer.battery.status.soh_pptt); - Serial.print(" Voltage: "); - Serial.print(datalayer.battery.status.voltage_dV); - Serial.print(" Current: "); - Serial.print(datalayer.battery.status.current_dA); - Serial.print(" Capacity: "); - Serial.print(datalayer.battery.info.total_capacity_Wh); - Serial.print(" Remain cap: "); - Serial.print(datalayer.battery.status.remaining_capacity_Wh); - Serial.print(" Max discharge W: "); - Serial.print(datalayer.battery.status.max_discharge_power_W); - Serial.print(" Max charge W: "); - Serial.print(datalayer.battery.status.max_charge_power_W); - Serial.print(" BMS status: "); - Serial.print(datalayer.battery.status.bms_status); - Serial.print(" Power: "); - Serial.print(datalayer.battery.status.active_power_W); - Serial.print(" Temp min: "); - Serial.print(datalayer.battery.status.temperature_min_dC); - Serial.print(" Temp max: "); - Serial.print(datalayer.battery.status.temperature_max_dC); - Serial.print(" Cell max: "); - Serial.print(datalayer.battery.status.cell_max_voltage_mV); - Serial.print(" Cell min: "); - Serial.print(datalayer.battery.status.cell_min_voltage_mV); - Serial.print(" LFP : "); - Serial.print(datalayer.battery.info.chemistry); - Serial.print(" Battery Allows Contactor Closing: "); - Serial.print(datalayer.system.status.battery_allows_contactor_closing); - Serial.print(" Inverter Allows Contactor Closing: "); - Serial.print(datalayer.system.status.inverter_allows_contactor_closing); - - Serial.println(""); + logging.println("Values from battery: "); + logging.print("SOC: "); + logging.print(datalayer.battery.status.real_soc); + logging.print(" SOH: "); + logging.print(datalayer.battery.status.soh_pptt); + logging.print(" Voltage: "); + logging.print(datalayer.battery.status.voltage_dV); + logging.print(" Current: "); + logging.print(datalayer.battery.status.current_dA); + logging.print(" Capacity: "); + logging.print(datalayer.battery.info.total_capacity_Wh); + logging.print(" Remain cap: "); + logging.print(datalayer.battery.status.remaining_capacity_Wh); + logging.print(" Max discharge W: "); + logging.print(datalayer.battery.status.max_discharge_power_W); + logging.print(" Max charge W: "); + logging.print(datalayer.battery.status.max_charge_power_W); + logging.print(" BMS status: "); + logging.print(datalayer.battery.status.bms_status); + logging.print(" Power: "); + logging.print(datalayer.battery.status.active_power_W); + logging.print(" Temp min: "); + logging.print(datalayer.battery.status.temperature_min_dC); + logging.print(" Temp max: "); + logging.print(datalayer.battery.status.temperature_max_dC); + logging.print(" Cell max: "); + logging.print(datalayer.battery.status.cell_max_voltage_mV); + logging.print(" Cell min: "); + logging.print(datalayer.battery.status.cell_min_voltage_mV); + logging.print(" LFP : "); + logging.print(datalayer.battery.info.chemistry); + logging.print(" Battery Allows Contactor Closing: "); + logging.print(datalayer.system.status.battery_allows_contactor_closing); + logging.print(" Inverter Allows Contactor Closing: "); + logging.print(datalayer.system.status.inverter_allows_contactor_closing); + + logging.println(""); } #endif diff --git a/Software/src/inverter/SMA-CAN.cpp b/Software/src/inverter/SMA-BYD-H-CAN.cpp similarity index 74% rename from Software/src/inverter/SMA-CAN.cpp rename to Software/src/inverter/SMA-BYD-H-CAN.cpp index 5831406d2..5bd68f8d0 100644 --- a/Software/src/inverter/SMA-CAN.cpp +++ b/Software/src/inverter/SMA-BYD-H-CAN.cpp @@ -1,14 +1,48 @@ #include "../include.h" -#ifdef SMA_CAN +#ifdef SMA_BYD_H_CAN #include "../datalayer/datalayer.h" -#include "SMA-CAN.h" +#include "SMA-BYD-H-CAN.h" /* TODO: Map error bits in 0x158 */ /* Do not change code below unless you are sure what you are doing */ static unsigned long previousMillis100ms = 0; +static uint32_t inverter_time = 0; +static uint16_t inverter_voltage = 0; +static int16_t inverter_current = 0; + //Actual content messages +CAN_frame SMA_158 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x158, + .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}}; +CAN_frame SMA_358 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x358, + .data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_3D8 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x3D8, + .data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}}; +CAN_frame SMA_458 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x458, + .data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}}; +CAN_frame SMA_4D8 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x4D8, + .data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}}; +CAN_frame SMA_518 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x518, + .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}}; CAN_frame SMA_558 = {.FD = false, .ext_ID = false, .DLC = 8, @@ -39,43 +73,12 @@ CAN_frame SMA_618_3 = {.FD = false, .DLC = 8, .ID = 0x618, .data = {0x02, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}}; //2 - 0 -CAN_frame SMA_358 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x358, - .data = {0x0F, 0x6C, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_3D8 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x3D8, - .data = {0x04, 0x10, 0x27, 0x10, 0x00, 0x18, 0xF9, 0x00}}; -CAN_frame SMA_458 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x458, - .data = {0x00, 0x00, 0x06, 0x75, 0x00, 0x00, 0x05, 0xD6}}; -CAN_frame SMA_518 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x518, - .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}}; -CAN_frame SMA_4D8 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x4D8, - .data = {0x09, 0xFD, 0x00, 0x00, 0x00, 0xA8, 0x02, 0x08}}; -CAN_frame SMA_158 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x158, - .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}}; static int16_t temperature_average = 0; static uint16_t ampere_hours_remaining = 0; -void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages - //Calculate values - +void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN + // Update values temperature_average = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); @@ -86,6 +89,7 @@ void update_values_can_inverter() { //This function maps all the values fetched } //Map values to CAN messages + //Maxvoltage (eg 400.0V = 4000 , 16bits long) SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); @@ -127,10 +131,18 @@ void update_values_can_inverter() { //This function maps all the values fetched } //Error bits - if (!datalayer.system.status.inverter_allows_contactor_closing) { - SMA_158.data.u8[2] = 0x6A; - } else { + if (datalayer.system.status.inverter_allows_contactor_closing) { SMA_158.data.u8[2] = 0xAA; +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, + HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN + } else { + SMA_158.data.u8[2] = 0x6A; +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, + LOW); // Turn off LED to indicate that SMA inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN } /* @@ -184,12 +196,12 @@ void update_values_can_inverter() { //This function maps all the values fetched */ } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x360: //Message originating from SMA inverter - Voltage and current datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - //Frame0-1 Voltage - //Frame2-3 Current + inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; break; case 0x3E0: //Message originating from SMA inverter - ? datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -198,60 +210,73 @@ void receive_can_inverter(CAN_frame rx_frame) { datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; //Frame0-3 Timestamp /* - transmit_can(&SMA_158, can_config.inverter); - transmit_can(&SMA_358, can_config.inverter); - transmit_can(&SMA_3D8, can_config.inverter); - transmit_can(&SMA_458, can_config.inverter); - transmit_can(&SMA_518, can_config.inverter); - transmit_can(&SMA_4D8, can_config.inverter); + transmit_can_frame(&SMA_158, can_config.inverter); + transmit_can_frame(&SMA_358, can_config.inverter); + transmit_can_frame(&SMA_3D8, can_config.inverter); + transmit_can_frame(&SMA_458, can_config.inverter); + transmit_can_frame(&SMA_518, can_config.inverter); + transmit_can_frame(&SMA_4D8, can_config.inverter); */ + inverter_time = + (rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; break; - case 0x5E0: //Message originating from SMA inverter - String + case 0x560: //Message originating from SMA inverter - Init datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; break; - case 0x560: //Message originating from SMA inverter - Init + case 0x5E0: //Message originating from SMA inverter - String datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA break; case 0x5E7: //Pairing request datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - transmit_can(&SMA_558, can_config.inverter); - transmit_can(&SMA_598, can_config.inverter); - transmit_can(&SMA_5D8, can_config.inverter); - transmit_can(&SMA_618_1, can_config.inverter); - transmit_can(&SMA_618_2, can_config.inverter); - transmit_can(&SMA_618_3, can_config.inverter); - transmit_can(&SMA_158, can_config.inverter); - transmit_can(&SMA_358, can_config.inverter); - transmit_can(&SMA_3D8, can_config.inverter); - transmit_can(&SMA_458, can_config.inverter); - transmit_can(&SMA_518, can_config.inverter); - transmit_can(&SMA_4D8, can_config.inverter); + transmit_can_init(); break; default: break; } } -void send_can_inverter() { +void transmit_can_inverter() { unsigned long currentMillis = millis(); - // Send CAN Message every 100ms if Enable line is HIGH + // Send CAN Message every 100ms if inverter allows contactor closing if (datalayer.system.status.inverter_allows_contactor_closing) { if (currentMillis - previousMillis100ms >= 100) { previousMillis100ms = currentMillis; - transmit_can(&SMA_158, can_config.inverter); - transmit_can(&SMA_358, can_config.inverter); - transmit_can(&SMA_3D8, can_config.inverter); - transmit_can(&SMA_458, can_config.inverter); - transmit_can(&SMA_518, can_config.inverter); - transmit_can(&SMA_4D8, can_config.inverter); + transmit_can_frame(&SMA_158, can_config.inverter); + transmit_can_frame(&SMA_358, can_config.inverter); + transmit_can_frame(&SMA_3D8, can_config.inverter); + transmit_can_frame(&SMA_458, can_config.inverter); + transmit_can_frame(&SMA_518, can_config.inverter); + transmit_can_frame(&SMA_4D8, can_config.inverter); } } } +void transmit_can_init() { + transmit_can_frame(&SMA_558, can_config.inverter); + transmit_can_frame(&SMA_598, can_config.inverter); + transmit_can_frame(&SMA_5D8, can_config.inverter); + transmit_can_frame(&SMA_618_1, can_config.inverter); + transmit_can_frame(&SMA_618_2, can_config.inverter); + transmit_can_frame(&SMA_618_3, can_config.inverter); + transmit_can_frame(&SMA_158, can_config.inverter); + transmit_can_frame(&SMA_358, can_config.inverter); + transmit_can_frame(&SMA_3D8, can_config.inverter); + transmit_can_frame(&SMA_458, can_config.inverter); + transmit_can_frame(&SMA_518, can_config.inverter); + transmit_can_frame(&SMA_4D8, can_config.inverter); +} + void setup_inverter(void) { // Performs one time setup at startup over CAN bus strncpy(datalayer.system.info.inverter_protocol, "SMA CAN", 63); datalayer.system.info.inverter_protocol[63] = '\0'; + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first + pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT); +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT); + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN } #endif diff --git a/Software/src/inverter/SMA-CAN.h b/Software/src/inverter/SMA-BYD-H-CAN.h similarity index 51% rename from Software/src/inverter/SMA-CAN.h rename to Software/src/inverter/SMA-BYD-H-CAN.h index 111044a46..8e9413c55 100644 --- a/Software/src/inverter/SMA-CAN.h +++ b/Software/src/inverter/SMA-BYD-H-CAN.h @@ -1,5 +1,5 @@ -#ifndef SMA_CAN_H -#define SMA_CAN_H +#ifndef SMA_BYD_H_CAN_H +#define SMA_BYD_H_CAN_H #include "../include.h" #define CAN_INVERTER_SELECTED @@ -7,7 +7,8 @@ #define READY_STATE 0x03 #define STOP_STATE 0x02 -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); +void transmit_can_init(); void setup_inverter(void); #endif diff --git a/Software/src/inverter/BYD-SMA.cpp b/Software/src/inverter/SMA-BYD-HVS-CAN.cpp similarity index 75% rename from Software/src/inverter/BYD-SMA.cpp rename to Software/src/inverter/SMA-BYD-HVS-CAN.cpp index 34e33063a..a5df8beec 100644 --- a/Software/src/inverter/BYD-SMA.cpp +++ b/Software/src/inverter/SMA-BYD-HVS-CAN.cpp @@ -1,14 +1,23 @@ #include "../include.h" -#ifdef BYD_SMA +#ifdef SMA_BYD_HVS_CAN #include "../datalayer/datalayer.h" -#include "BYD-SMA.h" +#include "SMA-BYD-HVS-CAN.h" /* TODO: Map error bits in 0x158 */ /* Do not change code below unless you are sure what you are doing */ static unsigned long previousMillis100ms = 0; +static uint32_t inverter_time = 0; +static uint16_t inverter_voltage = 0; +static int16_t inverter_current = 0; + //Actual content messages +CAN_frame SMA_158 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x158, // All 0xAA, no faults active + .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}}; CAN_frame SMA_358 = {.FD = false, .ext_ID = false, .DLC = 8, @@ -24,21 +33,16 @@ CAN_frame SMA_458 = {.FD = false, .DLC = 8, .ID = 0x458, .data = {0x00, 0x00, 0x11, 0xC8, 0x00, 0x00, 0x0E, 0xF4}}; -CAN_frame SMA_518 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x518, - .data = {0x01, 0x4A, 0x01, 0x25, 0xFF, 0xFF, 0xFF, 0xFF}}; CAN_frame SMA_4D8 = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x4D8, .data = {0x10, 0x62, 0x00, 0x16, 0x01, 0x68, 0x03, 0x08}}; -CAN_frame SMA_158 = {.FD = false, +CAN_frame SMA_518 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x158, // All 0xAA, no faults active - .data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}}; + .ID = 0x518, + .data = {0x01, 0x4A, 0x01, 0x25, 0xFF, 0xFF, 0xFF, 0xFF}}; // Pairing/Battery setup information @@ -78,8 +82,8 @@ static int16_t charge_current = 0; static int16_t temperature_average = 0; static uint16_t ampere_hours_remaining = 0; -void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages - //Calculate values +void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN + // Update values temperature_average = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); @@ -90,6 +94,7 @@ void update_values_can_inverter() { //This function maps all the values fetched } //Map values to CAN messages + //Maxvoltage (eg 400.0V = 4000 , 16bits long) SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); @@ -130,10 +135,18 @@ void update_values_can_inverter() { //This function maps all the values fetched } //Error bits - if (!datalayer.system.status.inverter_allows_contactor_closing) { - SMA_158.data.u8[2] = 0x6A; - } else { + if (datalayer.system.status.inverter_allows_contactor_closing) { SMA_158.data.u8[2] = 0xAA; +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, + HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN + } else { + SMA_158.data.u8[2] = 0x6A; +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, + LOW); // Turn off LED to indicate that SMA inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN } /* @@ -187,12 +200,12 @@ void update_values_can_inverter() { //This function maps all the values fetched */ } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { case 0x360: //Message originating from SMA inverter - Voltage and current datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - //Frame0-1 Voltage - //Frame2-3 Current + inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; break; case 0x3E0: //Message originating from SMA inverter - ? datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -201,60 +214,72 @@ void receive_can_inverter(CAN_frame rx_frame) { datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; //Frame0-3 Timestamp /* - transmit_can(&SMA_158, can_config.inverter); - transmit_can(&SMA_358, can_config.inverter); - transmit_can(&SMA_3D8, can_config.inverter); - transmit_can(&SMA_458, can_config.inverter); - transmit_can(&SMA_518, can_config.inverter); - transmit_can(&SMA_4D8, can_config.inverter); + transmit_can_frame(&SMA_158, can_config.inverter); + transmit_can_frame(&SMA_358, can_config.inverter); + transmit_can_frame(&SMA_3D8, can_config.inverter); + transmit_can_frame(&SMA_458, can_config.inverter); + transmit_can_frame(&SMA_518, can_config.inverter); + transmit_can_frame(&SMA_4D8, can_config.inverter); */ + inverter_time = + (rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; break; - case 0x5E0: //Message originating from SMA inverter - String + case 0x560: //Message originating from SMA inverter - Init datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; break; - case 0x560: //Message originating from SMA inverter - Init + case 0x5E0: //Message originating from SMA inverter - String datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA break; case 0x5E7: //Pairing request datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - transmit_can(&SMA_558, can_config.inverter); - transmit_can(&SMA_598, can_config.inverter); - transmit_can(&SMA_5D8, can_config.inverter); - transmit_can(&SMA_618_1, can_config.inverter); - transmit_can(&SMA_618_2, can_config.inverter); - transmit_can(&SMA_618_3, can_config.inverter); - transmit_can(&SMA_158, can_config.inverter); - transmit_can(&SMA_358, can_config.inverter); - transmit_can(&SMA_3D8, can_config.inverter); - transmit_can(&SMA_458, can_config.inverter); - transmit_can(&SMA_518, can_config.inverter); - transmit_can(&SMA_4D8, can_config.inverter); + transmit_can_init(); break; default: break; } } -void send_can_inverter() { +void transmit_can_inverter() { unsigned long currentMillis = millis(); - // Send CAN Message every 100ms if we're enabled + // Send CAN Message every 100ms if inverter allows contactor closing if (datalayer.system.status.inverter_allows_contactor_closing) { if (currentMillis - previousMillis100ms >= 100) { previousMillis100ms = currentMillis; - transmit_can(&SMA_158, can_config.inverter); - transmit_can(&SMA_358, can_config.inverter); - transmit_can(&SMA_3D8, can_config.inverter); - transmit_can(&SMA_458, can_config.inverter); - transmit_can(&SMA_518, can_config.inverter); - transmit_can(&SMA_4D8, can_config.inverter); + transmit_can_frame(&SMA_158, can_config.inverter); + transmit_can_frame(&SMA_358, can_config.inverter); + transmit_can_frame(&SMA_3D8, can_config.inverter); + transmit_can_frame(&SMA_458, can_config.inverter); + transmit_can_frame(&SMA_518, can_config.inverter); + transmit_can_frame(&SMA_4D8, can_config.inverter); } } } + +void transmit_can_init() { + transmit_can_frame(&SMA_558, can_config.inverter); + transmit_can_frame(&SMA_598, can_config.inverter); + transmit_can_frame(&SMA_5D8, can_config.inverter); + transmit_can_frame(&SMA_618_1, can_config.inverter); + transmit_can_frame(&SMA_618_2, can_config.inverter); + transmit_can_frame(&SMA_618_3, can_config.inverter); + transmit_can_frame(&SMA_158, can_config.inverter); + transmit_can_frame(&SMA_358, can_config.inverter); + transmit_can_frame(&SMA_3D8, can_config.inverter); + transmit_can_frame(&SMA_458, can_config.inverter); + transmit_can_frame(&SMA_518, can_config.inverter); + transmit_can_frame(&SMA_4D8, can_config.inverter); +} + void setup_inverter(void) { // Performs one time setup at startup over CAN bus strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box HVS over SMA CAN", 63); datalayer.system.info.inverter_protocol[63] = '\0'; datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT); +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT); + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN } #endif diff --git a/Software/src/inverter/SMA-BYD-HVS-CAN.h b/Software/src/inverter/SMA-BYD-HVS-CAN.h new file mode 100644 index 000000000..615def836 --- /dev/null +++ b/Software/src/inverter/SMA-BYD-HVS-CAN.h @@ -0,0 +1,14 @@ +#ifndef SMA_BYD_HVS_CAN_H +#define SMA_BYD_HVS_CAN_H +#include "../include.h" + +#define CAN_INVERTER_SELECTED + +#define READY_STATE 0x03 +#define STOP_STATE 0x02 + +void transmit_can_frame(CAN_frame* tx_frame, int interface); +void transmit_can_init(); +void setup_inverter(void); + +#endif diff --git a/Software/src/inverter/SMA-LV-CAN.cpp b/Software/src/inverter/SMA-LV-CAN.cpp new file mode 100644 index 000000000..eff1598c1 --- /dev/null +++ b/Software/src/inverter/SMA-LV-CAN.cpp @@ -0,0 +1,166 @@ +#include "../include.h" +#ifdef SMA_LV_CAN +#include "../datalayer/datalayer.h" +#include "SMA-LV-CAN.h" + +/* SMA Sunny Island Low Voltage (48V) CAN protocol: +CAN 2.0A +500kBit/sec +11-Bit Identifiers */ + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis100ms = 0; + +#define VOLTAGE_OFFSET_DV 40 //Offset in deciVolt from max charge voltage and min discharge voltage +#define MAX_VOLTAGE_DV 630 +#define MIN_VOLTAGE_DV 41 + +//Actual content messages +CAN_frame SMA_00F = {.FD = false, // Emergency stop message + .ext_ID = false, + .DLC = 8, //Documentation unclear, should message even have any content? + .ID = 0x00F, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_351 = {.FD = false, // Battery charge voltage, charge/discharge limit, min discharge voltage + .ext_ID = false, + .DLC = 8, + .ID = 0x351, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_355 = {.FD = false, // SOC, SOH, HiResSOC + .ext_ID = false, + .DLC = 8, + .ID = 0x355, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_356 = {.FD = false, // Battery voltage, current, temperature + .ext_ID = false, + .DLC = 8, + .ID = 0x356, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35A = {.FD = false, // Alarms & Warnings + .ext_ID = false, + .DLC = 8, + .ID = 0x35A, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35B = {.FD = false, // Events + .ext_ID = false, + .DLC = 8, + .ID = 0x35B, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35E = {.FD = false, // Manufacturer ASCII + .ext_ID = false, + .DLC = 8, + .ID = 0x35E, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_35F = {.FD = false, // Battery Type, version, capacity, ID + .ext_ID = false, + .DLC = 8, + .ID = 0x35F, + .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +static int16_t temperature_average = 0; + +void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages + // Update values + temperature_average = + ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); + + //Map values to CAN messages + + //Battery charge voltage (eg 400.0V = 4000 , 16bits long) (MIN 41V, MAX 63V, default 54V) + SMA_351.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8); + SMA_351.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF); + if (datalayer.battery.info.max_design_voltage_dV > MAX_VOLTAGE_DV) { + //If the battery is designed for more than 63.0V, cap the value + SMA_351.data.u8[0] = (MAX_VOLTAGE_DV >> 8); + SMA_351.data.u8[1] = (MAX_VOLTAGE_DV & 0x00FF); + //TODO; raise event? + } + //Discharge limited current, 500 = 50A, (0.1, A) (MIN 0, MAX 1200) + SMA_351.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_351.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + //Charge limited current, 125 =12.5A (0.1, A) (MIN 0, MAX 1200) + SMA_351.data.u8[4] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_351.data.u8[5] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + //Discharge voltage (eg 300.0V = 3000 , 16bits long) (MIN 41V, MAX 48V, default 41V) + SMA_351.data.u8[6] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8); + SMA_351.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF); + if (datalayer.battery.info.min_design_voltage_dV < MIN_VOLTAGE_DV) { + //If the battery is designed for discharge voltage below 41.0V, cap the value + SMA_351.data.u8[6] = (MIN_VOLTAGE_DV >> 8); + SMA_351.data.u8[7] = (MIN_VOLTAGE_DV & 0x00FF); + //TODO; raise event? + } + + //SOC (100%) + SMA_355.data.u8[0] = ((datalayer.battery.status.reported_soc / 100) >> 8); + SMA_355.data.u8[1] = ((datalayer.battery.status.reported_soc / 100) & 0x00FF); + //StateOfHealth (100%) + SMA_355.data.u8[2] = ((datalayer.battery.status.soh_pptt / 100) >> 8); + SMA_355.data.u8[3] = ((datalayer.battery.status.soh_pptt / 100) & 0x00FF); + //State of charge High Precision (100.00%) + SMA_355.data.u8[4] = (datalayer.battery.status.reported_soc >> 8); + SMA_355.data.u8[5] = (datalayer.battery.status.reported_soc & 0x00FF); + + //Voltage (370.0) + SMA_356.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 8); + SMA_356.data.u8[1] = ((datalayer.battery.status.voltage_dV * 10) & 0x00FF); + //Current (S16 dA) + SMA_356.data.u8[2] = (datalayer.battery.status.current_dA >> 8); + SMA_356.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); + //Temperature (s16 degC) + SMA_356.data.u8[4] = (temperature_average >> 8); + SMA_356.data.u8[5] = (temperature_average & 0x00FF); + + //TODO: Map error/warnings in 0x35A +} + +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x305: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 Battery Voltage + //Frame2-3 Battery Current + //Frame4-5 Battery Temperature + //Frame6-7 SOC Battery + break; + case 0x306: + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + //Frame0-1 SOH Battery + //Frame2 Charging procedure + //Frame3 Operating state + //Frame4-5 Active error message + //Frame6-7 Battery charge voltage setpoint + break; + default: + break; + } +} + +void transmit_can_inverter() { + unsigned long currentMillis = millis(); + + if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) { + previousMillis100ms = currentMillis; + + transmit_can_frame(&SMA_351, can_config.inverter); + transmit_can_frame(&SMA_355, can_config.inverter); + transmit_can_frame(&SMA_356, can_config.inverter); + transmit_can_frame(&SMA_35A, can_config.inverter); + transmit_can_frame(&SMA_35B, can_config.inverter); + transmit_can_frame(&SMA_35E, can_config.inverter); + transmit_can_frame(&SMA_35F, can_config.inverter); + + //Remote quick stop (optional) + if (datalayer.battery.status.bms_status == FAULT) { + transmit_can_frame(&SMA_00F, can_config.inverter); + //After receiving this message, Sunny Island will immediately go into standby. + //Please send start command, to start again. Manual start is also possible. + } + } +} + +void setup_inverter(void) { // Performs one time setup at startup over CAN bus + strncpy(datalayer.system.info.inverter_protocol, "SMA Low Voltage (48V) protocol via CAN", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} +#endif diff --git a/Software/src/inverter/BYD-SMA.h b/Software/src/inverter/SMA-LV-CAN.h similarity index 57% rename from Software/src/inverter/BYD-SMA.h rename to Software/src/inverter/SMA-LV-CAN.h index e787191d9..29164eb6b 100644 --- a/Software/src/inverter/BYD-SMA.h +++ b/Software/src/inverter/SMA-LV-CAN.h @@ -1,5 +1,5 @@ -#ifndef BYD_SMA_H -#define BYD_SMA_H +#ifndef SMA_LV_CAN_H +#define SMA_LV_CAN_H #include "../include.h" #define CAN_INVERTER_SELECTED @@ -7,7 +7,7 @@ #define READY_STATE 0x03 #define STOP_STATE 0x02 -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp index 7a242daee..9828dd7cd 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp @@ -4,325 +4,262 @@ #include "SMA-TRIPOWER-CAN.h" /* TODO: -- Figure out the manufacturer info needed in send_tripower_init() CAN messages +- Figure out the manufacturer info needed in transmit_can_init() CAN messages - CAN logs from real system might be needed - Figure out how cellvoltages need to be displayed -- Figure out if sending send_tripower_init() like we do now is OK +- Figure out if sending transmit_can_init() like we do now is OK - Figure out how to send the non-cyclic messages when needed */ /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis500ms = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send +static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send +static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send +static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send + +typedef struct { + CAN_frame* frame; + void (*callback)(); +} Frame; + +static unsigned short listLength = 0; +static Frame framesToSend[20]; + +static uint32_t inverter_time = 0; +static uint16_t inverter_voltage = 0; +static int16_t inverter_current = 0; +static bool pairing_completed = false; +static int16_t temperature_average = 0; +static uint16_t ampere_hours_remaining = 0; //Actual content messages -CAN_frame SMA_00D = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x00D, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_00F = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x00F, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_011 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x011, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_013 = {.FD = false, +CAN_frame SMA_358 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x013, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_014 = {.FD = false, + .ID = 0x358, + .data = {0x12, 0x40, 0x0C, 0x80, 0x01, 0x00, 0x01, 0x00}}; +CAN_frame SMA_3D8 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x014, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_005 = {.FD = false, + .ID = 0x3D8, + .data = {0x04, 0x06, 0x27, 0x10, 0x00, 0x19, 0x00, 0xFA}}; +CAN_frame SMA_458 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x005, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_007 = {.FD = false, + .ID = 0x458, + .data = {0x00, 0x00, 0x73, 0xAE, 0x00, 0x00, 0x64, 0x64}}; +CAN_frame SMA_4D8 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x007, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_006 = {.FD = false, + .ID = 0x4D8, + .data = {0x10, 0x62, 0x00, 0x00, 0x00, 0x78, 0x02, 0x08}}; +CAN_frame SMA_518 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x006, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_008 = {.FD = false, + .ID = 0x518, + .data = {0x00, 0x96, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00}}; +CAN_frame SMA_558 = {.FD = false, //Pairing first message .ext_ID = false, .DLC = 8, - .ID = 0x008, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_015 = {.FD = false, + .ID = 0x558, // BYD HVS 10.2 kWh (0x66 might be kWh) + .data = {0x03, 0x24, 0x00, 0x04, 0x00, 0x66, 0x04, 0x09}}; //Amount of modules? Vendor ID? +CAN_frame SMA_598 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x015, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_016 = {.FD = false, + .ID = 0x598, + .data = {0x12, 0xD6, 0x43, 0xA4, 0x00, 0x00, 0x00, 0x00}}; //B0-4 Serial 301100932 +CAN_frame SMA_5D8 = {.FD = false, .ext_ID = false, .DLC = 8, - .ID = 0x016, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_017 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x017, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -CAN_frame SMA_018 = {.FD = false, - .ext_ID = false, - .DLC = 8, - .ID = 0x018, - .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; - -static int16_t temperature_average = 0; -static uint16_t ampere_hours_remaining = 0; -static uint16_t ampere_hours_max = 0; -static bool batteryAlarm = false; -static bool BMSevent = false; - -enum BatteryState { NA, INIT, BAT_STANDBY, OPERATE, WARNING, FAULTED, UPDATE, BAT_UPDATE }; -BatteryState batteryState = OPERATE; -enum InverterControlFlags { - EMG_CHARGE_REQUEST, - EMG_DISCHARGE_REQUEST, - NOT_ENOUGH_ENERGY_FOR_START, - INVERTER_STAY_ON, - FORCED_BATTERY_SHUTDOWN, - RESERVED, - BATTERY_UPDATE_AVAILABLE, - NO_BATTERY_UPDATED_BY_INV -}; -InverterControlFlags inverterControlFlags = BATTERY_UPDATE_AVAILABLE; -enum Events0 { - START_SOC_CALIBRATE, - STOP_SOC_CALIBRATE, - START_POWERLIMIT, - STOP_POWERLIMIT, - PREVENTATIVE_BAT_SHUTDOWN, - THERMAL_MANAGEMENT, - START_BALANCING, - STOP_BALANCING -}; -Events0 events0 = START_BALANCING; -enum Events1 { START_BATTERY_SELFTEST, STOP_BATTERY_SELFTEST }; -Events1 events1 = START_BATTERY_SELFTEST; -enum Command2Battery { IDLE, RUN, NOT_USED1, NOT_USED2, SHUTDOWN, FIRMWARE_UPDATE, BATSELFUPDATE, NOT_USED3 }; -Command2Battery command2Battery = RUN; -enum InvInitState { SYSTEM_FREQUENCY, XPHASE_SYSTEM, BLACKSTART_OPERATION }; -InvInitState invInitState = SYSTEM_FREQUENCY; + .ID = 0x5D8, + .data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //B Y D +CAN_frame SMA_618_0 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //BATTERY +CAN_frame SMA_618_1 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr +CAN_frame SMA_618_2 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H +CAN_frame SMA_618_3 = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x618, + .data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN - //Calculate values - + // Update values temperature_average = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); - ampere_hours_remaining = - ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * - 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) - ampere_hours_max = ((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * - 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) - - batteryState = OPERATE; - inverterControlFlags = INVERTER_STAY_ON; + if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 + ampere_hours_remaining = + ((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * + 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah) + } //Map values to CAN messages - // Battery Limits - //Battery Max Charge Voltage (eg 400.0V = 4000 , 16bits long) - SMA_00D.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); - SMA_00D.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); - //Battery Min Discharge Voltage (eg 300.0V = 3000 , 16bits long) - SMA_00D.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); - SMA_00D.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); + + //Maxvoltage (eg 400.0V = 4000 , 16bits long) + SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); + SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF); + //Minvoltage (eg 300.0V = 3000 , 16bits long) + SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8); + SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF); //Discharge limited current, 500 = 50A, (0.1, A) - SMA_00D.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); - SMA_00D.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); + SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8); + SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF); //Charge limited current, 125 =12.5A (0.1, A) - SMA_00D.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8); - SMA_00D.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); + SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8); + SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF); - // Battery State //SOC (100.00%) - SMA_00F.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); - SMA_00F.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF); + SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8); + SMA_3D8.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF); //StateOfHealth (100.00%) - SMA_00F.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8); - SMA_00F.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF); + SMA_3D8.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8); + SMA_3D8.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF); //State of charge (AH, 0.1) - SMA_00F.data.u8[4] = (ampere_hours_remaining >> 8); - SMA_00F.data.u8[5] = (ampere_hours_remaining & 0x00FF); - //Fully charged (AH, 0.1) - SMA_00F.data.u8[6] = (ampere_hours_max >> 8); - SMA_00F.data.u8[7] = (ampere_hours_max & 0x00FF); - - // Battery Energy - //Charged Energy Counter TODO: are these needed? - //SMA_011.data.u8[0] = (X >> 8); - //SMA_011.data.u8[1] = (X & 0x00FF); - //SMA_011.data.u8[2] = (X >> 8); - //SMA_011.data.u8[3] = (X & 0x00FF); - //Discharged Energy Counter TODO: are these needed? - //SMA_011.data.u8[4] = (X >> 8); - //SMA_011.data.u8[5] = (X & 0x00FF); - //SMA_011.data.u8[6] = (X >> 8); - //SMA_011.data.u8[7] = (X & 0x00FF); + SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8); + SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF); - // Battery Measurements //Voltage (370.0) - SMA_013.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8); - SMA_013.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF); + SMA_4D8.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8); + SMA_4D8.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF); //Current (TODO: signed OK?) - SMA_013.data.u8[2] = (datalayer.battery.status.current_dA >> 8); - SMA_013.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); + SMA_4D8.data.u8[2] = (datalayer.battery.status.current_dA >> 8); + SMA_4D8.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF); //Temperature average - SMA_013.data.u8[4] = (temperature_average >> 8); - SMA_013.data.u8[5] = (temperature_average & 0x00FF); - //Battery state - SMA_013.data.u8[6] = batteryState; - SMA_013.data.u8[6] = inverterControlFlags; - - // Battery Temperature and Cellvoltages - // Battery max temperature - SMA_014.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8); - SMA_014.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF); - // Battery min temperature - SMA_014.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8); - SMA_014.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF); - // Battery Cell Voltage (sum) - //SMA_014.data.u8[4] = (??? >> 8); //TODO scaling? - //SMA_014.data.u8[5] = (??? & 0x00FF); //TODO scaling? - // Cell voltage min - //SMA_014.data.u8[6] = (??? >> 8); //TODO scaling? 0-255 - // Cell voltage max - //SMA_014.data.u8[7] = (??? >> 8); //TODO scaling? 0-255 - - //SMA_006.data.u8[0] = (ErrorCode >> 8); - //SMA_006.data.u8[1] = (ErrorCode & 0x00FF); - //SMA_006.data.u8[2] = ModuleNumber; - //SMA_006.data.u8[3] = ErrorLevel; - //SMA_008.data.u8[0] = Events0; - //SMA_008.data.u8[1] = Events1; - - //SMA_005.data.u8[0] = BMSalarms0; - //SMA_005.data.u8[1] = BMSalarms1; - //SMA_005.data.u8[2] = BMSalarms2; - //SMA_005.data.u8[3] = BMSalarms3; - //SMA_005.data.u8[4] = BMSalarms4; - //SMA_005.data.u8[5] = BMSalarms5; - //SMA_005.data.u8[6] = BMSalarms6; - //SMA_005.data.u8[7] = BMSalarms7; - - //SMA_007.data.u8[0] = DCDCalarms0; - //SMA_007.data.u8[1] = DCDCalarms1; - //SMA_007.data.u8[2] = DCDCalarms2; - //SMA_007.data.u8[3] = DCDCalarms3; - //SMA_007.data.u8[4] = DCDCwarnings0; - //SMA_007.data.u8[5] = DCDCwarnings1; - //SMA_007.data.u8[6] = DCDCwarnings2; - //SMA_007.data.u8[7] = DCDCwarnings3; - - //SMA_015.data.u8[0] = BatterySystemVersion; - //SMA_015.data.u8[1] = BatterySystemVersion; - //SMA_015.data.u8[2] = BatterySystemVersion; - //SMA_015.data.u8[3] = BatterySystemVersion; - //SMA_015.data.u8[4] = BatteryCapacity; - //SMA_015.data.u8[5] = BatteryCapacity; - //SMA_015.data.u8[6] = NumberOfModules; - //SMA_015.data.u8[7] = BatteryManufacturerID; - - //SMA_016.data.u8[0] = SerialNumber; - //SMA_016.data.u8[1] = SerialNumber; - //SMA_016.data.u8[2] = SerialNumber; - //SMA_016.data.u8[3] = SerialNumber; - //SMA_016.data.u8[4] = ManufacturingDate; - //SMA_016.data.u8[5] = ManufacturingDate; - //SMA_016.data.u8[6] = ManufacturingDate; - //SMA_016.data.u8[7] = ManufacturingDate; - - //SMA_017.data.u8[0] = Multiplex; - //SMA_017.data.u8[1] = ManufacturerName; - //SMA_017.data.u8[2] = ManufacturerName; - //SMA_017.data.u8[3] = ManufacturerName; - //SMA_017.data.u8[4] = ManufacturerName; - //SMA_017.data.u8[5] = ManufacturerName; - //SMA_017.data.u8[6] = ManufacturerName; - //SMA_017.data.u8[7] = ManufacturerName; - - //SMA_018.data.u8[0] = Multiplex; - //SMA_018.data.u8[1] = BatteryName; - //SMA_018.data.u8[2] = BatteryName; - //SMA_018.data.u8[3] = BatteryName; - //SMA_018.data.u8[4] = BatteryName; - //SMA_018.data.u8[5] = BatteryName; - //SMA_018.data.u8[6] = BatteryName; - //SMA_018.data.u8[7] = BatteryName; + SMA_4D8.data.u8[4] = (temperature_average >> 8); + SMA_4D8.data.u8[5] = (temperature_average & 0x00FF); + //Battery ready + if (datalayer.battery.status.bms_status == FAULT) { + SMA_4D8.data.u8[6] = STOP_STATE; + } else { + SMA_4D8.data.u8[6] = READY_STATE; + } } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { - case 0x00D: //Inverter Measurements + case 0x360: //Message originating from SMA inverter - Voltage and current datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + inverter_voltage = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; + inverter_current = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; break; - case 0x00F: //Inverter Feedback + case 0x3E0: //Message originating from SMA inverter - ? datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; break; - case 0x010: //Time from inverter + case 0x420: //Message originating from SMA inverter - Timestamp datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + inverter_time = + (rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; break; - case 0x015: //Initialization message from inverter + case 0x560: //Message originating from SMA inverter - Init datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - send_tripower_init(); break; - case 0x017: //Initialization message from inverter 2 + case 0x5E0: //Message originating from SMA inverter - String datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; - //send_tripower_init(); + //Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA + break; + case 0x660: //Message originating from SMA inverter - Pairing request + datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; + transmit_can_init(); break; default: break; } } -void send_can_inverter() { +void pushFrame(CAN_frame* frame, void (*callback)() = NULL) { + if (listLength >= 20) { + return; //TODO: scream. + } + framesToSend[listLength] = { + .frame = frame, + .callback = callback, + }; + listLength++; +} + +void transmit_can_inverter() { unsigned long currentMillis = millis(); - // Send CAN Message every 500ms - if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) { - previousMillis500ms = currentMillis; + // Send CAN Message only if we're enabled by inverter + if (!datalayer.system.status.inverter_allows_contactor_closing) { + return; + } - transmit_can(&SMA_00D, can_config.inverter); //Battery limits - transmit_can(&SMA_00F, can_config.inverter); // Battery state - transmit_can(&SMA_011, can_config.inverter); // Battery Energy - transmit_can(&SMA_013, can_config.inverter); // Battery Measurements - transmit_can(&SMA_014, can_config.inverter); // Battery Temperatures and cellvoltages + if (listLength > 0 && currentMillis - previousMillis250ms >= INTERVAL_250_MS) { + previousMillis250ms = currentMillis; + // Send next frame. + Frame frame = framesToSend[0]; + transmit_can_frame(frame.frame, can_config.inverter); + if (frame.callback != NULL) { + frame.callback(); + } + for (int i = 0; i < listLength - 1; i++) { + framesToSend[i] = framesToSend[i + 1]; + } + listLength--; } - if (batteryAlarm) { //Non-cyclic - transmit_can(&SMA_005, can_config.inverter); // Battery Alarms 1 - transmit_can(&SMA_007, can_config.inverter); // Battery Alarms 2 + if (!pairing_completed) { + return; } - if (BMSevent) { //Non-cyclic - transmit_can(&SMA_006, can_config.inverter); // Battery Errorcode - transmit_can(&SMA_008, can_config.inverter); // Battery Events + // Send CAN Message every 2s + if (currentMillis - previousMillis2s >= INTERVAL_2_S) { + previousMillis2s = currentMillis; + pushFrame(&SMA_358); + } + // Send CAN Message every 10s + if (currentMillis - previousMillis10s >= INTERVAL_10_S) { + previousMillis10s = currentMillis; + pushFrame(&SMA_518); + pushFrame(&SMA_4D8); + pushFrame(&SMA_3D8); } } -void send_tripower_init() { - transmit_can(&SMA_015, can_config.inverter); // Battery Data 1 - transmit_can(&SMA_016, can_config.inverter); // Battery Data 2 - transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer - transmit_can(&SMA_018, can_config.inverter); // Battery Name +void completePairing() { + pairing_completed = true; +} + +void transmit_can_init() { + listLength = 0; // clear all frames + + pushFrame(&SMA_558); //Pairing start - Vendor + pushFrame(&SMA_598); //Serial + pushFrame(&SMA_5D8); //BYD + pushFrame(&SMA_618_0); //BATTERY + pushFrame(&SMA_618_1); //-Box Pr + pushFrame(&SMA_618_2); //emium H + pushFrame(&SMA_618_3); //VS + pushFrame(&SMA_358); + pushFrame(&SMA_3D8); + pushFrame(&SMA_458); + pushFrame(&SMA_4D8); + pushFrame(&SMA_518, completePairing); } void setup_inverter(void) { // Performs one time setup at startup over CAN bus strncpy(datalayer.system.info.inverter_protocol, "SMA Tripower CAN", 63); datalayer.system.info.inverter_protocol[63] = '\0'; + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first + pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT); +#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN + pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT); + digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing +#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN } + #endif diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.h b/Software/src/inverter/SMA-TRIPOWER-CAN.h index 90967001f..d868fbb19 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.h +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.h @@ -4,8 +4,11 @@ #define CAN_INVERTER_SELECTED -void send_tripower_init(); -void transmit_can(CAN_frame* tx_frame, int interface); +#define READY_STATE 0x03 +#define STOP_STATE 0x02 + +void transmit_can_frame(CAN_frame* tx_frame, int interface); +void transmit_can_init(); void setup_inverter(void); #endif diff --git a/Software/src/inverter/SOFAR-CAN.cpp b/Software/src/inverter/SOFAR-CAN.cpp index 838ebf932..bd6916dd3 100644 --- a/Software/src/inverter/SOFAR-CAN.cpp +++ b/Software/src/inverter/SOFAR-CAN.cpp @@ -230,7 +230,7 @@ void update_values_can_inverter() { //This function maps all the values fetched SOFAR_356.data.u8[3] = (datalayer.battery.status.temperature_max_dC & 0x00FF); } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { switch (rx_frame.ID) { //In here we need to respond to the inverter. TODO: make logic case 0x605: datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -247,20 +247,20 @@ void receive_can_inverter(CAN_frame rx_frame) { } } -void send_can_inverter() { +void transmit_can_inverter() { unsigned long currentMillis = millis(); // Send 100ms CAN Message if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { previousMillis100 = currentMillis; //Frames actively reported by BMS - transmit_can(&SOFAR_351, can_config.inverter); - transmit_can(&SOFAR_355, can_config.inverter); - transmit_can(&SOFAR_356, can_config.inverter); - transmit_can(&SOFAR_30F, can_config.inverter); - transmit_can(&SOFAR_359, can_config.inverter); - transmit_can(&SOFAR_35E, can_config.inverter); - transmit_can(&SOFAR_35F, can_config.inverter); - transmit_can(&SOFAR_35A, can_config.inverter); + transmit_can_frame(&SOFAR_351, can_config.inverter); + transmit_can_frame(&SOFAR_355, can_config.inverter); + transmit_can_frame(&SOFAR_356, can_config.inverter); + transmit_can_frame(&SOFAR_30F, can_config.inverter); + transmit_can_frame(&SOFAR_359, can_config.inverter); + transmit_can_frame(&SOFAR_35E, can_config.inverter); + transmit_can_frame(&SOFAR_35F, can_config.inverter); + transmit_can_frame(&SOFAR_35A, can_config.inverter); } } diff --git a/Software/src/inverter/SOFAR-CAN.h b/Software/src/inverter/SOFAR-CAN.h index 7a80bf62a..dfbe2ef7e 100644 --- a/Software/src/inverter/SOFAR-CAN.h +++ b/Software/src/inverter/SOFAR-CAN.h @@ -4,7 +4,7 @@ #define CAN_INVERTER_SELECTED -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index 9bef56ec8..e4aa6274f 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -64,6 +64,31 @@ CAN_frame SOLAX_1879 = {.FD = false, .DLC = 8, .ID = 0x1879, .data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; +CAN_frame SOLAX_187E = {.FD = false, //Needed for Ultra + .ext_ID = true, + .DLC = 8, + .ID = 0x187E, + .data = {0x60, 0xEA, 0x0, 0x0, 0x64, 0x0, 0x0, 0x0}}; +CAN_frame SOLAX_187D = {.FD = false, //Needed for Ultra + .ext_ID = true, + .DLC = 8, + .ID = 0x187D, + .data = {0x8B, 0x01, 0x0, 0x0, 0x8B, 0x1, 0x0, 0x0}}; +CAN_frame SOLAX_187C = {.FD = false, //Needed for Ultra + .ext_ID = true, + .DLC = 8, + .ID = 0x187C, + .data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; +CAN_frame SOLAX_187B = {.FD = false, //Needed for Ultra + .ext_ID = true, + .DLC = 8, + .ID = 0x187B, + .data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; +CAN_frame SOLAX_187A = {.FD = false, //Needed for Ultra + .ext_ID = true, + .DLC = 8, + .ID = 0x187A, + .data = {0x01, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; CAN_frame SOLAX_1881 = {.FD = false, .ext_ID = true, .DLC = 8, @@ -165,13 +190,20 @@ void update_values_can_inverter() { //This function maps all the values fetched SOLAX_1801.data.u8[0] = 2; SOLAX_1801.data.u8[2] = 1; SOLAX_1801.data.u8[4] = 1; + + //Ultra messages + SOLAX_187E.data.u8[0] = (uint8_t)capped_remaining_capacity_Wh; + SOLAX_187E.data.u8[1] = (capped_remaining_capacity_Wh >> 8); + SOLAX_187E.data.u8[2] = 0; + SOLAX_187E.data.u8[3] = 0; + SOLAX_187E.data.u8[5] = (uint8_t)(datalayer.battery.status.reported_soc / 100); } -void send_can_inverter() { +void transmit_can_inverter() { // No periodic sending used on this protocol, we react only on incoming CAN messages! } -void receive_can_inverter(CAN_frame rx_frame) { +void map_can_frame_to_variable_inverter(CAN_frame rx_frame) { if (rx_frame.ID == 0x1871) { datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; @@ -182,21 +214,23 @@ void receive_can_inverter(CAN_frame rx_frame) { LastFrameTime = millis(); switch (STATE) { case (BATTERY_ANNOUNCE): -#ifdef DEBUG_VIA_USB - Serial.println("Solax Battery State: Announce"); +#ifdef DEBUG_LOG + logging.println("Solax Battery State: Announce"); #endif datalayer.system.status.inverter_allows_contactor_closing = false; SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on. - for (int i = 0; i <= number_of_batteries; i++) { - transmit_can(&SOLAX_1872, can_config.inverter); - transmit_can(&SOLAX_1873, can_config.inverter); - transmit_can(&SOLAX_1874, can_config.inverter); - transmit_can(&SOLAX_1875, can_config.inverter); - transmit_can(&SOLAX_1876, can_config.inverter); - transmit_can(&SOLAX_1877, can_config.inverter); - transmit_can(&SOLAX_1878, can_config.inverter); + for (uint8_t i = 0; i <= number_of_batteries; i++) { + transmit_can_frame(&SOLAX_187E, can_config.inverter); + transmit_can_frame(&SOLAX_187A, can_config.inverter); + transmit_can_frame(&SOLAX_1872, can_config.inverter); + transmit_can_frame(&SOLAX_1873, can_config.inverter); + transmit_can_frame(&SOLAX_1874, can_config.inverter); + transmit_can_frame(&SOLAX_1875, can_config.inverter); + transmit_can_frame(&SOLAX_1876, can_config.inverter); + transmit_can_frame(&SOLAX_1877, can_config.inverter); + transmit_can_frame(&SOLAX_1878, can_config.inverter); } - transmit_can(&SOLAX_100A001, can_config.inverter); //BMS Announce + transmit_can_frame(&SOLAX_100A001, can_config.inverter); //BMS Announce // Message from the inverter to proceed to contactor closing // Byte 4 changes from 0 to 1 if (rx_frame.data.u64 == Contactor_Close_Payload) @@ -205,30 +239,34 @@ void receive_can_inverter(CAN_frame rx_frame) { case (WAITING_FOR_CONTACTOR): SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on. - transmit_can(&SOLAX_1872, can_config.inverter); - transmit_can(&SOLAX_1873, can_config.inverter); - transmit_can(&SOLAX_1874, can_config.inverter); - transmit_can(&SOLAX_1875, can_config.inverter); - transmit_can(&SOLAX_1876, can_config.inverter); - transmit_can(&SOLAX_1877, can_config.inverter); - transmit_can(&SOLAX_1878, can_config.inverter); - transmit_can(&SOLAX_1801, can_config.inverter); // Announce that the battery will be connected - STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State -#ifdef DEBUG_VIA_USB - Serial.println("Solax Battery State: Contactor Closed"); + transmit_can_frame(&SOLAX_187E, can_config.inverter); + transmit_can_frame(&SOLAX_187A, can_config.inverter); + transmit_can_frame(&SOLAX_1872, can_config.inverter); + transmit_can_frame(&SOLAX_1873, can_config.inverter); + transmit_can_frame(&SOLAX_1874, can_config.inverter); + transmit_can_frame(&SOLAX_1875, can_config.inverter); + transmit_can_frame(&SOLAX_1876, can_config.inverter); + transmit_can_frame(&SOLAX_1877, can_config.inverter); + transmit_can_frame(&SOLAX_1878, can_config.inverter); + transmit_can_frame(&SOLAX_1801, can_config.inverter); // Announce that the battery will be connected + STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State +#ifdef DEBUG_LOG + logging.println("Solax Battery State: Contactor Closed"); #endif break; case (CONTACTOR_CLOSED): datalayer.system.status.inverter_allows_contactor_closing = true; SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on. - transmit_can(&SOLAX_1872, can_config.inverter); - transmit_can(&SOLAX_1873, can_config.inverter); - transmit_can(&SOLAX_1874, can_config.inverter); - transmit_can(&SOLAX_1875, can_config.inverter); - transmit_can(&SOLAX_1876, can_config.inverter); - transmit_can(&SOLAX_1877, can_config.inverter); - transmit_can(&SOLAX_1878, can_config.inverter); + transmit_can_frame(&SOLAX_187E, can_config.inverter); + transmit_can_frame(&SOLAX_187A, can_config.inverter); + transmit_can_frame(&SOLAX_1872, can_config.inverter); + transmit_can_frame(&SOLAX_1873, can_config.inverter); + transmit_can_frame(&SOLAX_1874, can_config.inverter); + transmit_can_frame(&SOLAX_1875, can_config.inverter); + transmit_can_frame(&SOLAX_1876, can_config.inverter); + transmit_can_frame(&SOLAX_1877, can_config.inverter); + transmit_can_frame(&SOLAX_1878, can_config.inverter); // Message from the inverter to open contactor // Byte 4 changes from 1 to 0 if (rx_frame.data.u64 == Contactor_Open_Payload) { @@ -240,15 +278,15 @@ void receive_can_inverter(CAN_frame rx_frame) { } if (rx_frame.ID == 0x1871 && rx_frame.data.u64 == __builtin_bswap64(0x0500010000000000)) { - transmit_can(&SOLAX_1881, can_config.inverter); - transmit_can(&SOLAX_1882, can_config.inverter); -#ifdef DEBUG_VIA_USB - Serial.println("1871 05-frame received from inverter"); + transmit_can_frame(&SOLAX_1881, can_config.inverter); + transmit_can_frame(&SOLAX_1882, can_config.inverter); +#ifdef DEBUG_LOG + logging.println("1871 05-frame received from inverter"); #endif } if (rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x03)) { -#ifdef DEBUG_VIA_USB - Serial.println("1871 03-frame received from inverter"); +#ifdef DEBUG_LOG + logging.println("1871 03-frame received from inverter"); #endif } } diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h index 68373ec2a..614e4245e 100644 --- a/Software/src/inverter/SOLAX-CAN.h +++ b/Software/src/inverter/SOLAX-CAN.h @@ -14,7 +14,7 @@ #define FAULT_SOLAX 3 #define UPDATING_FW 4 -void transmit_can(CAN_frame* tx_frame, int interface); +void transmit_can_frame(CAN_frame* tx_frame, int interface); void setup_inverter(void); #endif diff --git a/Software/src/lib/YiannisBourkelis-Uptime-Library/examples/DeviceUptime/DeviceUptime.ino b/Software/src/lib/YiannisBourkelis-Uptime-Library/examples/DeviceUptime/DeviceUptime.ino deleted file mode 100644 index 085d5172b..000000000 --- a/Software/src/lib/YiannisBourkelis-Uptime-Library/examples/DeviceUptime/DeviceUptime.ino +++ /dev/null @@ -1,35 +0,0 @@ -/* *********************************************************************** - * Uptime library for Arduino boards and compatible systems - * (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/) - * - * This file is part of Uptime library for Arduino boards and compatible systems - * - * Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Uptime library for Arduino boards and compatible systems. If not, see . - * ***********************************************************************/ - -#include "uptime_formatter.h" - -void setup() { - // connect at 115200 so we can read the uptime fast enough - Serial.begin(115200); -} - -void loop() { - //uptime_formatter::get_uptime() returns a string - //containing the total device uptime since startup in days, hours, minutes and seconds - Serial.println("up " + uptime_formatter::getUptime()); - - //wait 1 second - delay(1000); -} diff --git a/Software/src/lib/YiannisBourkelis-Uptime-Library/examples/DeviceUptimeCustomFormatting/DeviceUptimeCustomFormatting.ino b/Software/src/lib/YiannisBourkelis-Uptime-Library/examples/DeviceUptimeCustomFormatting/DeviceUptimeCustomFormatting.ino deleted file mode 100644 index 2a8a36f1f..000000000 --- a/Software/src/lib/YiannisBourkelis-Uptime-Library/examples/DeviceUptimeCustomFormatting/DeviceUptimeCustomFormatting.ino +++ /dev/null @@ -1,56 +0,0 @@ -/* *********************************************************************** - * Uptime library for Arduino boards and compatible systems - * (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/) - * - * This file is part of Uptime library for Arduino boards and compatible systems - * - * Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Uptime library for Arduino boards and compatible systems. If not, see . - * ***********************************************************************/ - -#include "uptime.h" - -void setup() { - // connect at 115200 so we can read the uptime fast enough - Serial.begin(115200); -} - -void loop() { - //If you do not want to use the string library - //you can get the total uptime variables - //and format the output the way you want. - - //First call calculate_uptime() to calculate the uptime - //and then read the uptime variables. - uptime::calculateUptime(); - - Serial.print("days: "); - Serial.println(uptime::getDays()); - - Serial.print("hours: "); - Serial.println(uptime::getHours()); - - Serial.print("minutes: "); - Serial.println(uptime::getMinutes()); - - Serial.print("seconds: "); - Serial.println(uptime::getSeconds()); - - Serial.print("milliseconds: "); - Serial.println(uptime::getMilliseconds()); - - Serial.print("\n"); - - //wait 1 second - delay(1000); -} diff --git a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.cpp b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.cpp index 6c5a30dda..6b711651f 100644 --- a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.cpp +++ b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.cpp @@ -59,19 +59,15 @@ unsigned long uptime::m_remaining_days = 0; //private variables that in combination hold the actual time passed //Use the coresponding uptime::get_.... to read these private variables unsigned long uptime::m_mod_milliseconds; -unsigned long uptime::m_mod_seconds; -unsigned long uptime::m_mod_minutes; -unsigned long uptime::m_mod_hours; +uint8_t uptime::m_mod_seconds; +uint8_t uptime::m_mod_minutes; +uint8_t uptime::m_mod_hours; uptime::uptime() { } /**** get the actual time passed from device boot time ****/ -unsigned long uptime::getMilliseconds() -{ - return uptime::m_mod_milliseconds; -} unsigned long uptime::getSeconds() { return uptime::m_mod_seconds; diff --git a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.h b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.h index 1c3ed60bd..fc00bbb7e 100644 --- a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.h +++ b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.h @@ -47,7 +47,6 @@ class uptime static void calculateUptime(); - static unsigned long getMilliseconds(); static unsigned long getSeconds(); static unsigned long getMinutes(); static unsigned long getHours(); @@ -61,9 +60,9 @@ class uptime static unsigned long m_days; static unsigned long m_mod_milliseconds; - static unsigned long m_mod_seconds; - static unsigned long m_mod_minutes; - static unsigned long m_mod_hours; + static uint8_t m_mod_seconds; + static uint8_t m_mod_minutes; + static uint8_t m_mod_hours; static unsigned long m_last_milliseconds; static unsigned long m_remaining_seconds; diff --git a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.cpp b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.cpp index 0859f2bbd..a55eced6e 100644 --- a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.cpp +++ b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.cpp @@ -36,11 +36,3 @@ String uptime_formatter::getUptime() (String)(uptime::getMinutes()) + " minutes, " + (String)(uptime::getSeconds()) + " seconds"; } - -//returns the actual time passed since device boot -//in the format: x days, y hours, z minutes, s seconds, n milliseconds -String uptime_formatter::getUptimeWithMillis() -{ - return uptime_formatter::getUptime() + ", " + - (String)(uptime::getMilliseconds()) + " milliseconds"; -} diff --git a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h index 85c4979e9..5c06873bb 100644 --- a/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h +++ b/Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h @@ -31,6 +31,5 @@ class uptime_formatter uptime_formatter(); static String getUptime(); - static String getUptimeWithMillis(); }; #endif diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/.gitattributes b/Software/src/lib/ayushsharma82-ElegantOTA/.gitattributes deleted file mode 100644 index dfe077042..000000000 --- a/Software/src/lib/ayushsharma82-ElegantOTA/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/.gitignore b/Software/src/lib/ayushsharma82-ElegantOTA/.gitignore deleted file mode 100644 index e1a65ae7f..000000000 --- a/Software/src/lib/ayushsharma82-ElegantOTA/.gitignore +++ /dev/null @@ -1,40 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj -.docusaurus - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -node_modules -.DS_Store -.vscode -/build -/portal -.pio diff --git a/Software/src/lib/ayushsharma82-ElegantOTA/howtochangeHTML.txt b/Software/src/lib/ayushsharma82-ElegantOTA/howtochangeHTML.txt deleted file mode 100644 index 09191a52a..000000000 --- a/Software/src/lib/ayushsharma82-ElegantOTA/howtochangeHTML.txt +++ /dev/null @@ -1,30 +0,0 @@ -Modifying HTML for the ElegantOTA Library - -This guide provides the necessary steps to update and modify the HTML used in the ElegantOTA library. -Follow these steps carefully to ensure that your changes are properly integrated. - -Steps to Modify the HTML: - -1 - Edit the CurrentPlainHTML.txt: - -Locate the file CurrentPlainHTML.txt in your project directory. -Modify the HTML content as needed. This file contains the plain HTML code that will be served through the OTA interface. - -Convert the HTML to GZIP and Decimal Format: - -Copy the content of the updated CurrentPlainHTML.txt. -Navigate to the CyberChef tool for encoding and compression. -Apply the following recipe: -Gzip with the "Dynamic Huffman Coding" option enabled. -Convert to Decimal with a comma separator. -Use this link for the process: https://gchq.github.io/CyberChef/#recipe=Gzip('Dynamic%20Huffman%20Coding','','',false)To_Decimal('Comma',false) - -2 - Update the ELEGANT_HTML Array: - -Copy the resulting decimal output from CyberChef. -Replace the existing content of the ELEGANT_HTML array in elop.cpp with the new decimal data from CyberChef. - -3 - Adjust the ELEGANT_HTML Array Size: - -After updating the ELEGANT_HTML array in both elop.h and elop.cpp, update the array size to match the length of the new output from CyberChef. -Ensure that the array size reflects the new length of the compressed HTML to avoid errors during compilation. diff --git a/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h b/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h index 7ef4fa1a0..644d19c6c 100644 --- a/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h +++ b/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h @@ -53,9 +53,6 @@ # define ARDUINOJSON_ENABLE_STRING_VIEW 0 # endif #endif -#ifndef ARDUINOJSON_USE_DOUBLE -# define ARDUINOJSON_USE_DOUBLE 1 -#endif #ifndef ARDUINOJSON_SIZEOF_POINTER # if defined(__SIZEOF_POINTER__) # define ARDUINOJSON_SIZEOF_POINTER __SIZEOF_POINTER__ @@ -65,6 +62,13 @@ # define ARDUINOJSON_SIZEOF_POINTER 4 // assume 32 bits otherwise # endif #endif +#ifndef ARDUINOJSON_USE_DOUBLE +# if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems +# define ARDUINOJSON_USE_DOUBLE 1 +# else +# define ARDUINOJSON_USE_DOUBLE 0 +# endif +#endif #ifndef ARDUINOJSON_USE_LONG_LONG # if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems # define ARDUINOJSON_USE_LONG_LONG 1 @@ -77,20 +81,20 @@ #endif #ifndef ARDUINOJSON_SLOT_ID_SIZE # if ARDUINOJSON_SIZEOF_POINTER <= 2 -# define ARDUINOJSON_SLOT_ID_SIZE 1 // up to 255 slots +# define ARDUINOJSON_SLOT_ID_SIZE 1 # elif ARDUINOJSON_SIZEOF_POINTER == 4 -# define ARDUINOJSON_SLOT_ID_SIZE 2 // up to 65535 slots +# define ARDUINOJSON_SLOT_ID_SIZE 2 # else -# define ARDUINOJSON_SLOT_ID_SIZE 4 // up to 4294967295 slots +# define ARDUINOJSON_SLOT_ID_SIZE 4 # endif #endif #ifndef ARDUINOJSON_POOL_CAPACITY -# if ARDUINOJSON_SIZEOF_POINTER <= 2 -# define ARDUINOJSON_POOL_CAPACITY 16 // 128 bytes -# elif ARDUINOJSON_SIZEOF_POINTER == 4 -# define ARDUINOJSON_POOL_CAPACITY 64 // 1024 bytes +# if ARDUINOJSON_SLOT_ID_SIZE == 1 +# define ARDUINOJSON_POOL_CAPACITY 16 // 96 bytes +# elif ARDUINOJSON_SLOT_ID_SIZE == 2 +# define ARDUINOJSON_POOL_CAPACITY 128 // 1024 bytes # else -# define ARDUINOJSON_POOL_CAPACITY 128 // 3072 bytes +# define ARDUINOJSON_POOL_CAPACITY 256 // 4096 bytes # endif #endif #ifndef ARDUINOJSON_INITIAL_POOL_COUNT @@ -189,6 +193,11 @@ # define ARDUINOJSON_DEBUG 0 # endif #endif +#if ARDUINOJSON_USE_LONG_LONG || ARDUINOJSON_USE_DOUBLE +# define ARDUINOJSON_USE_EXTENSIONS 1 +#else +# define ARDUINOJSON_USE_EXTENSIONS 0 +#endif #if defined(nullptr) # error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr #endif @@ -206,8 +215,12 @@ #endif #define ARDUINOJSON_CONCAT_(A, B) A##B #define ARDUINOJSON_CONCAT2(A, B) ARDUINOJSON_CONCAT_(A, B) +#define ARDUINOJSON_CONCAT3(A, B, C) \ + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT2(A, B), C) #define ARDUINOJSON_CONCAT4(A, B, C, D) \ - ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT2(A, B), ARDUINOJSON_CONCAT2(C, D)) + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT3(A, B, C), D) +#define ARDUINOJSON_CONCAT5(A, B, C, D, E) \ + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT4(A, B, C, D), E) #define ARDUINOJSON_BIN2ALPHA_0000() A #define ARDUINOJSON_BIN2ALPHA_0001() B #define ARDUINOJSON_BIN2ALPHA_0010() C @@ -226,22 +239,22 @@ #define ARDUINOJSON_BIN2ALPHA_1111() P #define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D() #define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D) -#define ARDUINOJSON_VERSION "7.0.4" +#define ARDUINOJSON_VERSION "7.3.0" #define ARDUINOJSON_VERSION_MAJOR 7 -#define ARDUINOJSON_VERSION_MINOR 0 -#define ARDUINOJSON_VERSION_REVISION 4 -#define ARDUINOJSON_VERSION_MACRO V704 +#define ARDUINOJSON_VERSION_MINOR 3 +#define ARDUINOJSON_VERSION_REVISION 0 +#define ARDUINOJSON_VERSION_MACRO V730 #ifndef ARDUINOJSON_VERSION_NAMESPACE -# define ARDUINOJSON_VERSION_NAMESPACE \ - ARDUINOJSON_CONCAT4(ARDUINOJSON_VERSION_MACRO, \ - ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_PROGMEM, \ - ARDUINOJSON_USE_LONG_LONG, \ - ARDUINOJSON_USE_DOUBLE, 1), \ - ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_NAN, \ - ARDUINOJSON_ENABLE_INFINITY, \ - ARDUINOJSON_ENABLE_COMMENTS, \ - ARDUINOJSON_DECODE_UNICODE), \ - ARDUINOJSON_SLOT_ID_SIZE) +# define ARDUINOJSON_VERSION_NAMESPACE \ + ARDUINOJSON_CONCAT5( \ + ARDUINOJSON_VERSION_MACRO, \ + ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_PROGMEM, \ + ARDUINOJSON_USE_LONG_LONG, \ + ARDUINOJSON_USE_DOUBLE, 1), \ + ARDUINOJSON_BIN2ALPHA( \ + ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \ + ARDUINOJSON_ENABLE_COMMENTS, ARDUINOJSON_DECODE_UNICODE), \ + ARDUINOJSON_SLOT_ID_SIZE, ARDUINOJSON_STRING_LENGTH_SIZE) #endif #define ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE \ namespace ArduinoJson { \ @@ -264,8 +277,6 @@ ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template class InvalidConversion; // Error here? See https://arduinojson.org/v7/invalid-conversion/ -template -struct ConverterNeedsWriteableRef; ARDUINOJSON_END_PRIVATE_NAMESPACE #include #include @@ -309,39 +320,155 @@ ARDUINOJSON_END_PUBLIC_NAMESPACE #endif ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template -struct uint_t; +struct uint_; template <> -struct uint_t<8> { - typedef uint8_t type; +struct uint_<8> { + using type = uint8_t; }; template <> -struct uint_t<16> { - typedef uint16_t type; +struct uint_<16> { + using type = uint16_t; }; template <> -struct uint_t<32> { - typedef uint32_t type; +struct uint_<32> { + using type = uint32_t; +}; +template +using uint_t = typename uint_::type; +using SlotId = uint_t; +using SlotCount = SlotId; +const SlotId NULL_SLOT = SlotId(-1); +template +class Slot { + public: + Slot() : ptr_(nullptr), id_(NULL_SLOT) {} + Slot(T* p, SlotId id) : ptr_(p), id_(id) { + ARDUINOJSON_ASSERT((p == nullptr) == (id == NULL_SLOT)); + } + explicit operator bool() const { + return ptr_ != nullptr; + } + SlotId id() const { + return id_; + } + T* ptr() const { + return ptr_; + } + T* operator->() const { + ARDUINOJSON_ASSERT(ptr_ != nullptr); + return ptr_; + } + private: + T* ptr_; + SlotId id_; +}; +template +class MemoryPool { + public: + void create(SlotCount cap, Allocator* allocator) { + ARDUINOJSON_ASSERT(cap > 0); + slots_ = reinterpret_cast(allocator->allocate(slotsToBytes(cap))); + capacity_ = slots_ ? cap : 0; + usage_ = 0; + } + void destroy(Allocator* allocator) { + if (slots_) + allocator->deallocate(slots_); + slots_ = nullptr; + capacity_ = 0; + usage_ = 0; + } + Slot allocSlot() { + if (!slots_) + return {}; + if (usage_ >= capacity_) + return {}; + auto index = usage_++; + return {slots_ + index, SlotId(index)}; + } + T* getSlot(SlotId id) const { + ARDUINOJSON_ASSERT(id < usage_); + return slots_ + id; + } + void clear() { + usage_ = 0; + } + void shrinkToFit(Allocator* allocator) { + auto newSlots = reinterpret_cast( + allocator->reallocate(slots_, slotsToBytes(usage_))); + if (newSlots) { + slots_ = newSlots; + capacity_ = usage_; + } + } + SlotCount usage() const { + return usage_; + } + static SlotCount bytesToSlots(size_t n) { + return static_cast(n / sizeof(T)); + } + static size_t slotsToBytes(SlotCount n) { + return n * sizeof(T); + } + private: + SlotCount capacity_; + SlotCount usage_; + T* slots_; }; template struct conditional { - typedef TrueType type; + using type = TrueType; }; template struct conditional { - typedef FalseType type; + using type = FalseType; +}; +template +using conditional_t = + typename conditional::type; +template +struct decay { + using type = T; }; +template +struct decay : decay {}; +template +struct decay : decay {}; +template +struct decay : decay {}; +template +struct decay : decay {}; +template +using decay_t = typename decay::type; template struct enable_if {}; template struct enable_if { - typedef T type; + using type = T; +}; +template +using enable_if_t = typename enable_if::type; +template +struct function_traits; +template +struct function_traits { + using return_type = ReturnType; + using arg1_type = Arg1; +}; +template +struct function_traits { + using return_type = ReturnType; + using arg1_type = Arg1; + using arg2_type = Arg2; }; template struct integral_constant { static const T value = v; }; -typedef integral_constant true_type; -typedef integral_constant false_type; +template +using bool_constant = integral_constant; +using true_type = bool_constant; +using false_type = bool_constant; template struct is_array : false_type {}; template @@ -350,12 +477,14 @@ template struct is_array : true_type {}; template struct remove_reference { - typedef T type; + using type = T; }; template struct remove_reference { - typedef T type; + using type = T; }; +template +using remove_reference_t = typename remove_reference::type; template class is_base_of { protected: // <- to avoid GCC's "all member functions in class are private" @@ -363,8 +492,8 @@ class is_base_of { static char probe(...); public: static const bool value = - sizeof(probe(reinterpret_cast::type*>( - 0))) == sizeof(int); + sizeof(probe(reinterpret_cast*>(0))) == + sizeof(int); }; template T&& declval(); @@ -396,7 +525,7 @@ struct is_convertible { protected: // <- to avoid GCC's "all member functions in class are private" static int probe(To); static char probe(...); - static From& from_; + static const From& from_; public: static const bool value = sizeof(probe(from_)) == sizeof(int); }; @@ -414,43 +543,44 @@ template struct is_same : true_type {}; template struct remove_cv { - typedef T type; + using type = T; }; template struct remove_cv { - typedef T type; + using type = T; }; template struct remove_cv { - typedef T type; + using type = T; }; template struct remove_cv { - typedef T type; + using type = T; }; +template +using remove_cv_t = typename remove_cv::type; template struct is_floating_point - : integral_constant< - bool, // - is_same::type>::value || - is_same::type>::value> {}; + : integral_constant>::value || + is_same>::value> {}; template struct is_integral : integral_constant::type, signed char>::value || - is_same::type, unsigned char>::value || - is_same::type, signed short>::value || - is_same::type, unsigned short>::value || - is_same::type, signed int>::value || - is_same::type, unsigned int>::value || - is_same::type, signed long>::value || - is_same::type, unsigned long>::value || - is_same::type, signed long long>::value || - is_same::type, unsigned long long>::value || - is_same::type, char>::value || - is_same::type, bool>::value> {}; + is_same, signed char>::value || + is_same, unsigned char>::value || + is_same, signed short>::value || + is_same, unsigned short>::value || + is_same, signed int>::value || + is_same, unsigned int>::value || + is_same, signed long>::value || + is_same, unsigned long>::value || + is_same, signed long long>::value || + is_same, unsigned long long>::value || + is_same, char>::value || + is_same, bool>::value> {}; template struct is_enum { - static const bool value = is_convertible::value && + static const bool value = is_convertible::value && !is_class::value && !is_integral::value && !is_floating_point::value; }; @@ -460,25 +590,25 @@ template struct is_pointer : true_type {}; template struct is_signed : integral_constant::type, char>::value || - is_same::type, signed char>::value || - is_same::type, signed short>::value || - is_same::type, signed int>::value || - is_same::type, signed long>::value || - is_same::type, signed long long>::value || - is_same::type, float>::value || - is_same::type, double>::value> {}; + is_same, char>::value || + is_same, signed char>::value || + is_same, signed short>::value || + is_same, signed int>::value || + is_same, signed long>::value || + is_same, signed long long>::value || + is_same, float>::value || + is_same, double>::value> {}; template struct is_unsigned : integral_constant::type, unsigned char>::value || - is_same::type, unsigned short>::value || - is_same::type, unsigned int>::value || - is_same::type, unsigned long>::value || - is_same::type, unsigned long long>::value || - is_same::type, bool>::value> {}; + is_same, unsigned char>::value || + is_same, unsigned short>::value || + is_same, unsigned int>::value || + is_same, unsigned long>::value || + is_same, unsigned long long>::value || + is_same, bool>::value> {}; template struct type_identity { - typedef T type; + using type = T; }; template struct make_unsigned; @@ -504,17 +634,204 @@ template <> struct make_unsigned : type_identity {}; template <> struct make_unsigned : type_identity {}; -template -struct make_void { - typedef void type; -}; +template +using make_unsigned_t = typename make_unsigned::type; template struct remove_const { - typedef T type; + using type = T; }; template struct remove_const { - typedef T type; + using type = T; +}; +template +using remove_const_t = typename remove_const::type; +template +struct make_void { + using type = void; +}; +template +using void_t = typename make_void::type; +using nullptr_t = decltype(nullptr); +template +T&& forward(remove_reference_t& t) noexcept { + return static_cast(t); +} +template +remove_reference_t&& move(T&& t) { + return static_cast&&>(t); +} +template +void swap_(T& a, T& b) { + T tmp = move(a); + a = move(b); + b = move(tmp); +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#include +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +using PoolCount = SlotId; +template +class MemoryPoolList { + struct FreeSlot { + SlotId next; + }; + static_assert(sizeof(FreeSlot) <= sizeof(T), "T is too small"); + public: + using Pool = MemoryPool; + MemoryPoolList() = default; + ~MemoryPoolList() { + ARDUINOJSON_ASSERT(count_ == 0); + } + friend void swap(MemoryPoolList& a, MemoryPoolList& b) { + bool aUsedPreallocated = a.pools_ == a.preallocatedPools_; + bool bUsedPreallocated = b.pools_ == b.preallocatedPools_; + if (aUsedPreallocated && bUsedPreallocated) { + for (PoolCount i = 0; i < ARDUINOJSON_INITIAL_POOL_COUNT; i++) + swap_(a.preallocatedPools_[i], b.preallocatedPools_[i]); + } else if (bUsedPreallocated) { + for (PoolCount i = 0; i < b.count_; i++) + a.preallocatedPools_[i] = b.preallocatedPools_[i]; + b.pools_ = a.pools_; + a.pools_ = a.preallocatedPools_; + } else if (aUsedPreallocated) { + for (PoolCount i = 0; i < a.count_; i++) + b.preallocatedPools_[i] = a.preallocatedPools_[i]; + a.pools_ = b.pools_; + b.pools_ = b.preallocatedPools_; + } else { + swap_(a.pools_, b.pools_); + } + swap_(a.count_, b.count_); + swap_(a.capacity_, b.capacity_); + swap_(a.freeList_, b.freeList_); + } + MemoryPoolList& operator=(MemoryPoolList&& src) { + ARDUINOJSON_ASSERT(count_ == 0); + if (src.pools_ == src.preallocatedPools_) { + memcpy(preallocatedPools_, src.preallocatedPools_, + sizeof(preallocatedPools_)); + pools_ = preallocatedPools_; + } else { + pools_ = src.pools_; + src.pools_ = nullptr; + } + count_ = src.count_; + capacity_ = src.capacity_; + src.count_ = 0; + src.capacity_ = 0; + return *this; + } + Slot allocSlot(Allocator* allocator) { + if (freeList_ != NULL_SLOT) { + return allocFromFreeList(); + } + if (count_) { + auto slot = allocFromLastPool(); + if (slot) + return slot; + } + auto pool = addPool(allocator); + if (!pool) + return {}; + return allocFromLastPool(); + } + void freeSlot(Slot slot) { + reinterpret_cast(slot.ptr())->next = freeList_; + freeList_ = slot.id(); + } + T* getSlot(SlotId id) const { + if (id == NULL_SLOT) + return nullptr; + auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY); + auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY); + ARDUINOJSON_ASSERT(poolIndex < count_); + return pools_[poolIndex].getSlot(indexInPool); + } + void clear(Allocator* allocator) { + for (PoolCount i = 0; i < count_; i++) + pools_[i].destroy(allocator); + count_ = 0; + freeList_ = NULL_SLOT; + if (pools_ != preallocatedPools_) { + allocator->deallocate(pools_); + pools_ = preallocatedPools_; + capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + } + } + SlotCount usage() const { + SlotCount total = 0; + for (PoolCount i = 0; i < count_; i++) + total = SlotCount(total + pools_[i].usage()); + return total; + } + size_t size() const { + return Pool::slotsToBytes(usage()); + } + void shrinkToFit(Allocator* allocator) { + if (count_ > 0) + pools_[count_ - 1].shrinkToFit(allocator); + if (pools_ != preallocatedPools_ && count_ != capacity_) { + pools_ = static_cast( + allocator->reallocate(pools_, count_ * sizeof(Pool))); + ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail + capacity_ = count_; + } + } + private: + Slot allocFromFreeList() { + ARDUINOJSON_ASSERT(freeList_ != NULL_SLOT); + auto id = freeList_; + auto slot = getSlot(freeList_); + freeList_ = reinterpret_cast(slot)->next; + return {slot, id}; + } + Slot allocFromLastPool() { + ARDUINOJSON_ASSERT(count_ > 0); + auto poolIndex = SlotId(count_ - 1); + auto slot = pools_[poolIndex].allocSlot(); + if (!slot) + return {}; + return {slot.ptr(), + SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())}; + } + Pool* addPool(Allocator* allocator) { + if (count_ == capacity_ && !increaseCapacity(allocator)) + return nullptr; + auto pool = &pools_[count_++]; + SlotCount poolCapacity = ARDUINOJSON_POOL_CAPACITY; + if (count_ == maxPools) // last pool is smaller because of NULL_SLOT + poolCapacity--; + pool->create(poolCapacity, allocator); + return pool; + } + bool increaseCapacity(Allocator* allocator) { + if (capacity_ == maxPools) + return false; + void* newPools; + auto newCapacity = PoolCount(capacity_ * 2); + if (pools_ == preallocatedPools_) { + newPools = allocator->allocate(newCapacity * sizeof(Pool)); + if (!newPools) + return false; + memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_)); + } else { + newPools = allocator->reallocate(pools_, newCapacity * sizeof(Pool)); + if (!newPools) + return false; + } + pools_ = static_cast(newPools); + capacity_ = newCapacity; + return true; + } + Pool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT]; + Pool* pools_ = preallocatedPools_; + PoolCount count_ = 0; + PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + SlotId freeList_ = NULL_SLOT; + public: + static const PoolCount maxPools = + PoolCount(NULL_SLOT / ARDUINOJSON_POOL_CAPACITY + 1); }; ARDUINOJSON_END_PRIVATE_NAMESPACE #ifdef _MSC_VER @@ -525,7 +842,7 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template struct numeric_limits; template -struct numeric_limits::value>::type> { +struct numeric_limits::value>> { static constexpr T lowest() { return 0; } @@ -535,7 +852,7 @@ struct numeric_limits::value>::type> { }; template struct numeric_limits< - T, typename enable_if::value && is_signed::value>::type> { + T, enable_if_t::value && is_signed::value>> { static constexpr T lowest() { return T(T(1) << (sizeof(T) * 8 - 1)); } @@ -549,8 +866,8 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE #endif ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE struct StringNode { - using references_type = uint_t::type; - using length_type = uint_t::type; + using references_type = uint_t; + using length_type = uint_t; struct StringNode* next; references_type references; length_type length; @@ -562,8 +879,10 @@ struct StringNode { static StringNode* create(size_t length, Allocator* allocator) { if (length > maxLength) return nullptr; - auto node = reinterpret_cast( - allocator->allocate(sizeForLength(length))); + auto size = sizeForLength(length); + if (size < length) // integer overflow + return nullptr; // (not testable on 64-bit) + auto node = reinterpret_cast(allocator->allocate(size)); if (node) { node->length = length_type(length); node->references = 1; @@ -592,23 +911,7 @@ struct StringNode { constexpr size_t sizeofString(size_t n) { return StringNode::sizeForLength(n); } -using nullptr_t = decltype(nullptr); -template -T&& forward(typename remove_reference::type& t) noexcept { - return static_cast(t); -} -template -typename remove_reference::type&& move(T&& t) { - return static_cast::type&&>(t); -} -template -void swap_(T& a, T& b) { - T tmp = move(a); - a = move(b); - b = move(tmp); -} ARDUINOJSON_END_PRIVATE_NAMESPACE -#include #ifdef _MSC_VER // Visual Studio # define FORCE_INLINE // __forceinline causes C4714 when returning std::string # ifndef ARDUINOJSON_DEPRECATED @@ -639,35 +942,52 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE # define ARDUINOJSON_NO_SANITIZE(check) #endif ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct IsStringLiteral : false_type {}; +template +struct IsStringLiteral : true_type {}; template struct StringAdapter; template struct SizedStringAdapter; template -typename StringAdapter::AdaptedString adaptString(const TString& s) { - return StringAdapter::adapt(s); +using StringAdapterFor = + StringAdapter::value, TString, + remove_cv_t>>>; +template +using AdaptedString = typename StringAdapterFor::AdaptedString; +template +AdaptedString adaptString(TString&& s) { + return StringAdapterFor::adapt(detail::forward(s)); } -template -typename StringAdapter::AdaptedString adaptString(TChar* p) { +template ::value, int> = 0> +AdaptedString adaptString(TChar* p) { return StringAdapter::adapt(p); } template -typename SizedStringAdapter::AdaptedString adaptString(TChar* p, - size_t n) { +AdaptedString adaptString(TChar* p, size_t n) { return SizedStringAdapter::adapt(p, n); } template struct IsChar : integral_constant::value && sizeof(T) == 1> {}; -class ZeroTerminatedRamString { +class RamString { public: - static const size_t typeSortKey = 3; - ZeroTerminatedRamString(const char* str) : str_(str) {} + static const size_t typeSortKey = 2; +#if ARDUINOJSON_SIZEOF_POINTER <= 2 + static constexpr size_t sizeMask = size_t(-1) >> 1; +#else + static constexpr size_t sizeMask = size_t(-1); +#endif + RamString(const char* str, size_t sz, bool isStatic = false) + : str_(str), size_(sz & sizeMask), static_(isStatic) { + ARDUINOJSON_ASSERT(size_ == sz); + } bool isNull() const { return !str_; } - FORCE_INLINE size_t size() const { - return str_ ? ::strlen(str_) : 0; + size_t size() const { + return size_; } char operator[](size_t i) const { ARDUINOJSON_ASSERT(str_ != 0); @@ -677,183 +997,71 @@ class ZeroTerminatedRamString { const char* data() const { return str_; } - friend int stringCompare(ZeroTerminatedRamString a, - ZeroTerminatedRamString b) { - ARDUINOJSON_ASSERT(!a.isNull()); - ARDUINOJSON_ASSERT(!b.isNull()); - return ::strcmp(a.str_, b.str_); - } - friend bool stringEquals(ZeroTerminatedRamString a, - ZeroTerminatedRamString b) { - return stringCompare(a, b) == 0; - } - bool isLinked() const { - return false; + bool isStatic() const { + return static_; } protected: const char* str_; +#if ARDUINOJSON_SIZEOF_POINTER <= 2 + size_t size_ : sizeof(size_t) * 8 - 1; + bool static_ : 1; +#else + size_t size_; + bool static_; +#endif }; template -struct StringAdapter::value>::type> { - typedef ZeroTerminatedRamString AdaptedString; +struct StringAdapter::value>> { + using AdaptedString = RamString; static AdaptedString adapt(const TChar* p) { - return AdaptedString(reinterpret_cast(p)); + auto str = reinterpret_cast(p); + return AdaptedString(str, str ? ::strlen(str) : 0); } }; -template -struct StringAdapter::value>::type> { - typedef ZeroTerminatedRamString AdaptedString; - static AdaptedString adapt(const TChar* p) { - return AdaptedString(reinterpret_cast(p)); - } -}; -class StaticStringAdapter : public ZeroTerminatedRamString { - public: - StaticStringAdapter(const char* str) : ZeroTerminatedRamString(str) {} - bool isLinked() const { - return true; - } -}; -template <> -struct StringAdapter { - typedef StaticStringAdapter AdaptedString; - static AdaptedString adapt(const char* p) { - return AdaptedString(p); +template +struct StringAdapter { + using AdaptedString = RamString; + static AdaptedString adapt(const char (&p)[N]) { + return RamString(p, N - 1, true); } }; -class SizedRamString { - public: - static const size_t typeSortKey = 2; - SizedRamString(const char* str, size_t sz) : str_(str), size_(sz) {} - bool isNull() const { - return !str_; - } - size_t size() const { - return size_; - } - char operator[](size_t i) const { - ARDUINOJSON_ASSERT(str_ != 0); - ARDUINOJSON_ASSERT(i <= size()); - return str_[i]; - } - const char* data() const { - return str_; - } - bool isLinked() const { - return false; +template +struct StringAdapter::value>> { + using AdaptedString = RamString; + static AdaptedString adapt(const TChar* p) { + auto str = reinterpret_cast(p); + return AdaptedString(str, str ? ::strlen(str) : 0); } - protected: - const char* str_; - size_t size_; }; template -struct SizedStringAdapter::value>::type> { - typedef SizedRamString AdaptedString; +struct SizedStringAdapter::value>> { + using AdaptedString = RamString; static AdaptedString adapt(const TChar* p, size_t n) { return AdaptedString(reinterpret_cast(p), n); } }; -ARDUINOJSON_END_PRIVATE_NAMESPACE -#if ARDUINOJSON_ENABLE_STD_STREAM -#include -#endif -ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE -class JsonString { - public: - enum Ownership { Copied, Linked }; - JsonString() : data_(0), size_(0), ownership_(Linked) {} - JsonString(const char* data, Ownership ownership = Linked) - : data_(data), size_(data ? ::strlen(data) : 0), ownership_(ownership) {} - JsonString(const char* data, size_t sz, Ownership ownership = Linked) - : data_(data), size_(sz), ownership_(ownership) {} - const char* c_str() const { - return data_; - } - bool isNull() const { - return !data_; - } - bool isLinked() const { - return ownership_ == Linked; - } - size_t size() const { - return size_; - } - explicit operator bool() const { - return data_ != 0; - } - friend bool operator==(JsonString lhs, JsonString rhs) { - if (lhs.size_ != rhs.size_) - return false; - if (lhs.data_ == rhs.data_) - return true; - if (!lhs.data_) - return false; - if (!rhs.data_) - return false; - return memcmp(lhs.data_, rhs.data_, lhs.size_) == 0; - } - friend bool operator!=(JsonString lhs, JsonString rhs) { - return !(lhs == rhs); - } -#if ARDUINOJSON_ENABLE_STD_STREAM - friend std::ostream& operator<<(std::ostream& lhs, const JsonString& rhs) { - lhs.write(rhs.c_str(), static_cast(rhs.size())); - return lhs; - } -#endif - private: - const char* data_; - size_t size_; - Ownership ownership_; -}; -ARDUINOJSON_END_PUBLIC_NAMESPACE -ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE -class JsonStringAdapter : public SizedRamString { - public: - JsonStringAdapter(const JsonString& s) - : SizedRamString(s.c_str(), s.size()), linked_(s.isLinked()) {} - bool isLinked() const { - return linked_; - } - private: - bool linked_; -}; -template <> -struct StringAdapter { - typedef JsonStringAdapter AdaptedString; - static AdaptedString adapt(const JsonString& s) { - return AdaptedString(s); - } -}; namespace string_traits_impl { template struct has_cstr : false_type {}; template -struct has_cstr().c_str()), - const char*>::value>::type> - : true_type {}; +struct has_cstr().c_str()), + const char*>::value>> : true_type {}; template struct has_data : false_type {}; template -struct has_data().data()), - const char*>::value>::type> - : true_type {}; +struct has_data().data()), + const char*>::value>> : true_type {}; template struct has_length : false_type {}; template struct has_length< - T, typename enable_if< - is_same().length()), size_t>::value>::type> + T, enable_if_t().length())>::value>> : true_type {}; template struct has_size : false_type {}; template struct has_size< - T, typename enable_if< - is_same().size()), size_t>::value>::type> + T, enable_if_t().size()), size_t>::value>> : true_type {}; } // namespace string_traits_impl template @@ -868,32 +1076,29 @@ struct string_traits { template struct StringAdapter< T, - typename enable_if< - (string_traits::has_cstr || string_traits::has_data) && - (string_traits::has_length || string_traits::has_size)>::type> { - typedef SizedRamString AdaptedString; + enable_if_t<(string_traits::has_cstr || string_traits::has_data) && + (string_traits::has_length || string_traits::has_size)>> { + using AdaptedString = RamString; static AdaptedString adapt(const T& s) { return AdaptedString(get_data(s), get_size(s)); } private: template - static typename enable_if::has_size, size_t>::type get_size( - const U& s) { + static enable_if_t::has_size, size_t> get_size(const U& s) { return s.size(); } template - static typename enable_if::has_size, size_t>::type get_size( - const U& s) { + static enable_if_t::has_size, size_t> get_size(const U& s) { return s.length(); } template - static typename enable_if::has_data, const char*>::type - get_data(const U& s) { + static enable_if_t::has_data, const char*> get_data( + const U& s) { return s.data(); } template - static typename enable_if::has_data, const char*>::type - get_data(const U& s) { + static enable_if_t::has_data, const char*> get_data( + const U& s) { return s.c_str(); } }; @@ -1038,7 +1243,7 @@ class FlashString { size_t size() const { return size_; } - friend bool stringEquals(FlashString a, SizedRamString b) { + friend bool stringEquals(FlashString a, RamString b) { ARDUINOJSON_ASSERT(a.typeSortKey < b.typeSortKey); ARDUINOJSON_ASSERT(!a.isNull()); ARDUINOJSON_ASSERT(!b.isNull()); @@ -1046,7 +1251,7 @@ class FlashString { return false; return ::memcmp_P(b.data(), a.str_, a.size_) == 0; } - friend int stringCompare(FlashString a, SizedRamString b) { + friend int stringCompare(FlashString a, RamString b) { ARDUINOJSON_ASSERT(a.typeSortKey < b.typeSortKey); ARDUINOJSON_ASSERT(!a.isNull()); ARDUINOJSON_ASSERT(!b.isNull()); @@ -1064,7 +1269,7 @@ class FlashString { ARDUINOJSON_ASSERT(s.size() <= n); ::memcpy_P(p, s.str_, n); } - bool isLinked() const { + bool isStatic() const { return false; } private: @@ -1073,14 +1278,14 @@ class FlashString { }; template <> struct StringAdapter { - typedef FlashString AdaptedString; + using AdaptedString = FlashString; static AdaptedString adapt(const __FlashStringHelper* s) { return AdaptedString(s, s ? strlen_P(reinterpret_cast(s)) : 0); } }; template <> struct SizedStringAdapter { - typedef FlashString AdaptedString; + using AdaptedString = FlashString; static AdaptedString adapt(const __FlashStringHelper* s, size_t n) { return AdaptedString(s, n); } @@ -1089,8 +1294,7 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE #endif ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template -typename enable_if::type +enable_if_t stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { ARDUINOJSON_ASSERT(!s1.isNull()); ARDUINOJSON_ASSERT(!s2.isNull()); @@ -1108,14 +1312,12 @@ stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { return 0; } template -typename enable_if< - (TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), int>::type +enable_if_t<(TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), int> stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { return -stringCompare(s2, s1); } template -typename enable_if::type +enable_if_t stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { ARDUINOJSON_ASSERT(!s1.isNull()); ARDUINOJSON_ASSERT(!s2.isNull()); @@ -1130,8 +1332,7 @@ stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { return true; } template -typename enable_if< - (TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), bool>::type +enable_if_t<(TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), bool> stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { return stringEquals(s2, s1); } @@ -1142,8 +1343,6 @@ static void stringGetChars(TAdaptedString s, char* p, size_t n) { p[i] = s[i]; } } -class VariantSlot; -class VariantPool; class StringPool { public: StringPool() = default; @@ -1217,285 +1416,6 @@ class StringPool { private: StringNode* strings_ = nullptr; }; -class VariantSlot; -using SlotId = uint_t::type; -using SlotCount = SlotId; -const SlotId NULL_SLOT = SlotId(-1); -class SlotWithId { - public: - SlotWithId() : slot_(nullptr), id_(NULL_SLOT) {} - SlotWithId(VariantSlot* slot, SlotId id) : slot_(slot), id_(id) { - ARDUINOJSON_ASSERT((slot == nullptr) == (id == NULL_SLOT)); - } - SlotId id() const { - return id_; - } - operator VariantSlot*() { - return slot_; - } - VariantSlot* operator->() { - ARDUINOJSON_ASSERT(slot_ != nullptr); - return slot_; - } - private: - VariantSlot* slot_; - SlotId id_; -}; -class VariantPool { - public: - void create(SlotCount cap, Allocator* allocator); - void destroy(Allocator* allocator); - SlotWithId allocSlot(); - VariantSlot* getSlot(SlotId id) const; - void clear(); - void shrinkToFit(Allocator*); - SlotCount usage() const; - static SlotCount bytesToSlots(size_t); - static size_t slotsToBytes(SlotCount); - private: - SlotCount capacity_; - SlotCount usage_; - VariantSlot* slots_; -}; -using PoolCount = SlotId; -class VariantPoolList { - public: - VariantPoolList() = default; - ~VariantPoolList() { - ARDUINOJSON_ASSERT(count_ == 0); - } - friend void swap(VariantPoolList& a, VariantPoolList& b) { - bool aUsedPreallocated = a.pools_ == a.preallocatedPools_; - bool bUsedPreallocated = b.pools_ == b.preallocatedPools_; - if (aUsedPreallocated && bUsedPreallocated) { - for (PoolCount i = 0; i < ARDUINOJSON_INITIAL_POOL_COUNT; i++) - swap_(a.preallocatedPools_[i], b.preallocatedPools_[i]); - } else if (bUsedPreallocated) { - for (PoolCount i = 0; i < b.count_; i++) - a.preallocatedPools_[i] = b.preallocatedPools_[i]; - b.pools_ = a.pools_; - a.pools_ = a.preallocatedPools_; - } else if (aUsedPreallocated) { - for (PoolCount i = 0; i < a.count_; i++) - b.preallocatedPools_[i] = a.preallocatedPools_[i]; - a.pools_ = b.pools_; - b.pools_ = b.preallocatedPools_; - } else { - swap_(a.pools_, b.pools_); - } - swap_(a.count_, b.count_); - swap_(a.capacity_, b.capacity_); - swap_(a.freeList_, b.freeList_); - } - VariantPoolList& operator=(VariantPoolList&& src) { - ARDUINOJSON_ASSERT(count_ == 0); - if (src.pools_ == src.preallocatedPools_) { - memcpy(preallocatedPools_, src.preallocatedPools_, - sizeof(preallocatedPools_)); - pools_ = preallocatedPools_; - } else { - pools_ = src.pools_; - src.pools_ = nullptr; - } - count_ = src.count_; - capacity_ = src.capacity_; - src.count_ = 0; - src.capacity_ = 0; - return *this; - } - SlotWithId allocSlot(Allocator* allocator) { - if (freeList_ != NULL_SLOT) { - return allocFromFreeList(); - } - if (count_) { - auto slot = allocFromLastPool(); - if (slot) - return slot; - } - auto pool = addPool(allocator); - if (!pool) - return {}; - return allocFromLastPool(); - } - void freeSlot(SlotWithId slot); - VariantSlot* getSlot(SlotId id) const { - if (id == NULL_SLOT) - return nullptr; - auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY); - auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY); - ARDUINOJSON_ASSERT(poolIndex < count_); - return pools_[poolIndex].getSlot(indexInPool); - } - void clear(Allocator* allocator) { - for (PoolCount i = 0; i < count_; i++) - pools_[i].destroy(allocator); - count_ = 0; - freeList_ = NULL_SLOT; - if (pools_ != preallocatedPools_) { - allocator->deallocate(pools_); - pools_ = preallocatedPools_; - capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; - } - } - SlotCount usage() const { - SlotCount total = 0; - for (PoolCount i = 0; i < count_; i++) - total = SlotCount(total + pools_[i].usage()); - return total; - } - void shrinkToFit(Allocator* allocator) { - if (count_ > 0) - pools_[count_ - 1].shrinkToFit(allocator); - if (pools_ != preallocatedPools_ && count_ != capacity_) { - pools_ = static_cast( - allocator->reallocate(pools_, count_ * sizeof(VariantPool))); - ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail - capacity_ = count_; - } - } - private: - SlotWithId allocFromFreeList(); - SlotWithId allocFromLastPool() { - ARDUINOJSON_ASSERT(count_ > 0); - auto poolIndex = SlotId(count_ - 1); - auto slot = pools_[poolIndex].allocSlot(); - if (!slot) - return {}; - return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())}; - } - VariantPool* addPool(Allocator* allocator) { - if (count_ == capacity_ && !increaseCapacity(allocator)) - return nullptr; - auto pool = &pools_[count_++]; - SlotCount poolCapacity = ARDUINOJSON_POOL_CAPACITY; - if (count_ == maxPools) // last pool is smaller because of NULL_SLOT - poolCapacity--; - pool->create(poolCapacity, allocator); - return pool; - } - bool increaseCapacity(Allocator* allocator) { - if (capacity_ == maxPools) - return false; - void* newPools; - auto newCapacity = PoolCount(capacity_ * 2); - if (pools_ == preallocatedPools_) { - newPools = allocator->allocate(newCapacity * sizeof(VariantPool)); - if (!newPools) - return false; - memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_)); - } else { - newPools = - allocator->reallocate(pools_, newCapacity * sizeof(VariantPool)); - if (!newPools) - return false; - } - pools_ = static_cast(newPools); - capacity_ = newCapacity; - return true; - } - VariantPool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT]; - VariantPool* pools_ = preallocatedPools_; - PoolCount count_ = 0; - PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; - SlotId freeList_ = NULL_SLOT; - public: - static const PoolCount maxPools = - PoolCount(NULL_SLOT / ARDUINOJSON_POOL_CAPACITY + 1); -}; -class VariantSlot; -class VariantPool; -class ResourceManager { - public: - ResourceManager(Allocator* allocator = DefaultAllocator::instance()) - : allocator_(allocator), overflowed_(false) {} - ~ResourceManager() { - stringPool_.clear(allocator_); - variantPools_.clear(allocator_); - } - ResourceManager(const ResourceManager&) = delete; - ResourceManager& operator=(const ResourceManager& src) = delete; - friend void swap(ResourceManager& a, ResourceManager& b) { - swap(a.stringPool_, b.stringPool_); - swap(a.variantPools_, b.variantPools_); - swap_(a.allocator_, b.allocator_); - swap_(a.overflowed_, b.overflowed_); - } - Allocator* allocator() const { - return allocator_; - } - size_t size() const { - return VariantPool::slotsToBytes(variantPools_.usage()) + - stringPool_.size(); - } - bool overflowed() const { - return overflowed_; - } - SlotWithId allocSlot() { - auto p = variantPools_.allocSlot(allocator_); - if (!p) - overflowed_ = true; - return p; - } - void freeSlot(SlotWithId id) { - variantPools_.freeSlot(id); - } - VariantSlot* getSlot(SlotId id) const { - return variantPools_.getSlot(id); - } - template - StringNode* saveString(TAdaptedString str) { - if (str.isNull()) - return 0; - auto node = stringPool_.add(str, allocator_); - if (!node) - overflowed_ = true; - return node; - } - void saveString(StringNode* node) { - stringPool_.add(node); - } - template - StringNode* getString(const TAdaptedString& str) const { - return stringPool_.get(str); - } - StringNode* createString(size_t length) { - auto node = StringNode::create(length, allocator_); - if (!node) - overflowed_ = true; - return node; - } - StringNode* resizeString(StringNode* node, size_t length) { - node = StringNode::resize(node, length, allocator_); - if (!node) - overflowed_ = true; - return node; - } - void destroyString(StringNode* node) { - StringNode::destroy(node, allocator_); - } - void dereferenceString(const char* s) { - stringPool_.dereference(s, allocator_); - } - void clear() { - variantPools_.clear(allocator_); - overflowed_ = false; - stringPool_.clear(allocator_); - } - void shrinkToFit() { - variantPools_.shrinkToFit(allocator_); - } - private: - Allocator* allocator_; - bool overflowed_; - StringPool stringPool_; - VariantPoolList variantPools_; -}; -template -struct IsString : false_type {}; -template -struct IsString< - T, typename make_void::AdaptedString>::type> - : true_type {}; ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE template @@ -1624,11 +1544,11 @@ template struct FloatTraits {}; template struct FloatTraits { - typedef uint64_t mantissa_type; + using mantissa_type = uint64_t; static const short mantissa_bits = 52; static const mantissa_type mantissa_max = (mantissa_type(1) << mantissa_bits) - 1; - typedef int16_t exponent_type; + using exponent_type = int16_t; static const exponent_type exponent_max = 308; static pgm_ptr positiveBinaryPowersOfTen() { ARDUINOJSON_DEFINE_PROGMEM_ARRAY( // @@ -1673,16 +1593,16 @@ struct FloatTraits { } template // int64_t static T highest_for( - typename enable_if::value && is_signed::value && - sizeof(TOut) == 8, - signed>::type* = 0) { + enable_if_t::value && is_signed::value && + sizeof(TOut) == 8, + signed>* = 0) { return forge(0x43DFFFFFFFFFFFFF); // 9.2233720368547748e+18 } template // uint64_t static T highest_for( - typename enable_if::value && is_unsigned::value && - sizeof(TOut) == 8, - unsigned>::type* = 0) { + enable_if_t::value && is_unsigned::value && + sizeof(TOut) == 8, + unsigned>* = 0) { return forge(0x43EFFFFFFFFFFFFF); // 1.8446744073709549568e+19 } static T lowest() { @@ -1694,11 +1614,11 @@ struct FloatTraits { }; template struct FloatTraits { - typedef uint32_t mantissa_type; + using mantissa_type = uint32_t; static const short mantissa_bits = 23; static const mantissa_type mantissa_max = (mantissa_type(1) << mantissa_bits) - 1; - typedef int8_t exponent_type; + using exponent_type = int8_t; static const exponent_type exponent_max = 38; static pgm_ptr positiveBinaryPowersOfTen() { ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors, @@ -1738,30 +1658,30 @@ struct FloatTraits { } template // int32_t static T highest_for( - typename enable_if::value && is_signed::value && - sizeof(TOut) == 4, - signed>::type* = 0) { + enable_if_t::value && is_signed::value && + sizeof(TOut) == 4, + signed>* = 0) { return forge(0x4EFFFFFF); // 2.14748352E9 } template // uint32_t static T highest_for( - typename enable_if::value && is_unsigned::value && - sizeof(TOut) == 4, - unsigned>::type* = 0) { + enable_if_t::value && is_unsigned::value && + sizeof(TOut) == 4, + unsigned>* = 0) { return forge(0x4F7FFFFF); // 4.29496704E9 } template // int64_t static T highest_for( - typename enable_if::value && is_signed::value && - sizeof(TOut) == 8, - signed>::type* = 0) { + enable_if_t::value && is_signed::value && + sizeof(TOut) == 8, + signed>* = 0) { return forge(0x5EFFFFFF); // 9.22337148709896192E18 } template // uint64_t static T highest_for( - typename enable_if::value && is_unsigned::value && - sizeof(TOut) == 8, - unsigned>::type* = 0) { + enable_if_t::value && is_unsigned::value && + sizeof(TOut) == 8, + unsigned>* = 0) { return forge(0x5F7FFFFF); // 1.844674297419792384E19 } static T lowest() { @@ -1785,86 +1705,91 @@ inline TFloat make_float(TFloat m, TExponent e) { ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE #if ARDUINOJSON_USE_DOUBLE -typedef double JsonFloat; +using JsonFloat = double; #else -typedef float JsonFloat; +using JsonFloat = float; #endif ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template -typename enable_if::value && is_unsigned::value && - is_integral::value && sizeof(TOut) <= sizeof(TIn), - bool>::type +enable_if_t::value && is_unsigned::value && + is_integral::value && sizeof(TOut) <= sizeof(TIn), + bool> canConvertNumber(TIn value) { return value <= TIn(numeric_limits::highest()); } template -typename enable_if::value && is_unsigned::value && - is_integral::value && sizeof(TIn) < sizeof(TOut), - bool>::type +enable_if_t::value && is_unsigned::value && + is_integral::value && sizeof(TIn) < sizeof(TOut), + bool> canConvertNumber(TIn) { return true; } template -typename enable_if::value && is_floating_point::value, - bool>::type +enable_if_t::value && is_floating_point::value, bool> canConvertNumber(TIn) { return true; } template -typename enable_if::value && is_signed::value && - is_integral::value && is_signed::value && - sizeof(TOut) < sizeof(TIn), - bool>::type +enable_if_t::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TOut) < sizeof(TIn), + bool> canConvertNumber(TIn value) { return value >= TIn(numeric_limits::lowest()) && value <= TIn(numeric_limits::highest()); } template -typename enable_if::value && is_signed::value && - is_integral::value && is_signed::value && - sizeof(TIn) <= sizeof(TOut), - bool>::type +enable_if_t::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TIn) <= sizeof(TOut), + bool> canConvertNumber(TIn) { return true; } template -typename enable_if::value && is_signed::value && - is_integral::value && is_unsigned::value && - sizeof(TOut) >= sizeof(TIn), - bool>::type +enable_if_t::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) >= sizeof(TIn), + bool> canConvertNumber(TIn value) { if (value < 0) return false; return TOut(value) <= numeric_limits::highest(); } template -typename enable_if::value && is_signed::value && - is_integral::value && is_unsigned::value && - sizeof(TOut) < sizeof(TIn), - bool>::type +enable_if_t::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) < sizeof(TIn), + bool> canConvertNumber(TIn value) { if (value < 0) return false; return value <= TIn(numeric_limits::highest()); } template -typename enable_if::value && is_integral::value && - sizeof(TOut) < sizeof(TIn), - bool>::type +enable_if_t::value && is_integral::value && + sizeof(TOut) < sizeof(TIn), + bool> canConvertNumber(TIn value) { return value >= numeric_limits::lowest() && value <= numeric_limits::highest(); } template -typename enable_if::value && is_integral::value && - sizeof(TOut) >= sizeof(TIn), - bool>::type +enable_if_t::value && is_integral::value && + sizeof(TOut) >= sizeof(TIn), + bool> canConvertNumber(TIn value) { return value >= numeric_limits::lowest() && value <= FloatTraits::template highest_for(); } template +enable_if_t::value && is_floating_point::value, + bool> +canConvertNumber(TIn) { + return true; +} +template TOut convertNumber(TIn value) { return canConvertNumber(value) ? TOut(value) : 0; } @@ -1874,9 +1799,73 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif +#if ARDUINOJSON_ENABLE_STD_STREAM +#include +#endif +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonString { + friend struct detail::StringAdapter; + public: + JsonString() : str_(nullptr, 0, true) {} + JsonString(const char* data, bool isStatic = false) + : str_(data, data ? ::strlen(data) : 0, isStatic) {} + template ::value && + !detail::is_same::value, + int> = 0> + JsonString(const char* data, TSize sz, bool isStatic = false) + : str_(data, size_t(sz), isStatic) {} + const char* c_str() const { + return str_.data(); + } + bool isNull() const { + return str_.isNull(); + } + bool isStatic() const { + return str_.isStatic(); + } + size_t size() const { + return str_.size(); + } + explicit operator bool() const { + return str_.data() != 0; + } + friend bool operator==(JsonString lhs, JsonString rhs) { + if (lhs.size() != rhs.size()) + return false; + if (lhs.c_str() == rhs.c_str()) + return true; + if (!lhs.c_str()) + return false; + if (!rhs.c_str()) + return false; + return memcmp(lhs.c_str(), rhs.c_str(), lhs.size()) == 0; + } + friend bool operator!=(JsonString lhs, JsonString rhs) { + return !(lhs == rhs); + } +#if ARDUINOJSON_ENABLE_STD_STREAM + friend std::ostream& operator<<(std::ostream& lhs, const JsonString& rhs) { + lhs.write(rhs.c_str(), static_cast(rhs.size())); + return lhs; + } +#endif + private: + detail::RamString str_; +}; +namespace detail { +template <> +struct StringAdapter { + using AdaptedString = RamString; + static const AdaptedString& adapt(const JsonString& s) { + return s.str_; + } +}; +} // namespace detail +ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE class VariantData; -class VariantSlot; +class ResourceManager; class CollectionIterator { friend class CollectionData; public: @@ -1903,10 +1892,6 @@ class CollectionIterator { ARDUINOJSON_ASSERT(slot_ != nullptr); return *data(); } - const char* key() const; - bool ownsKey() const; - void setKey(StringNode*); - void setKey(const char*); VariantData* data() { return reinterpret_cast(slot_); } @@ -1914,8 +1899,8 @@ class CollectionIterator { return reinterpret_cast(slot_); } private: - CollectionIterator(VariantSlot* slot, SlotId slotId); - VariantSlot* slot_; + CollectionIterator(VariantData* slot, SlotId slotId); + VariantData* slot_; SlotId currentId_, nextId_; }; class CollectionData { @@ -1927,9 +1912,7 @@ class CollectionData { } static void operator delete(void*, void*) noexcept {} using iterator = CollectionIterator; - iterator createIterator(const ResourceManager* resources) const { - return iterator(resources->getSlot(head_), head_); - } + iterator createIterator(const ResourceManager* resources) const; size_t size(const ResourceManager*) const; size_t nesting(const ResourceManager*) const; void clear(ResourceManager* resources); @@ -1938,20 +1921,17 @@ class CollectionData { return; collection->clear(resources); } - void remove(iterator it, ResourceManager* resources); - static void remove(CollectionData* collection, iterator it, - ResourceManager* resources) { - if (collection) - return collection->remove(it, resources); - } SlotId head() const { return head_; } protected: - iterator addSlot(ResourceManager*); + void appendOne(Slot slot, const ResourceManager* resources); + void appendPair(Slot key, Slot value, + const ResourceManager* resources); + void removeOne(iterator it, ResourceManager* resources); + void removePair(iterator it, ResourceManager* resources); private: - SlotWithId getPreviousSlot(VariantSlot*, const ResourceManager*) const; - void releaseSlot(SlotWithId, ResourceManager*); + Slot getPreviousSlot(VariantData*, const ResourceManager*) const; }; inline const VariantData* collectionToVariant( const CollectionData* collection) { @@ -1964,14 +1944,21 @@ inline VariantData* collectionToVariant(CollectionData* collection) { } class ArrayData : public CollectionData { public: - VariantData* addElement(ResourceManager* resources) { - return addSlot(resources).data(); - } + VariantData* addElement(ResourceManager* resources); static VariantData* addElement(ArrayData* array, ResourceManager* resources) { if (!array) return nullptr; return array->addElement(resources); } + template + bool addValue(const T& value, ResourceManager* resources); + template + static bool addValue(ArrayData* array, const T& value, + ResourceManager* resources) { + if (!array) + return false; + return array->addValue(value, resources); + } VariantData* getOrAddElement(size_t index, ResourceManager* resources); VariantData* getElement(size_t index, const ResourceManager* resources) const; static VariantData* getElement(const ArrayData* array, size_t index, @@ -1987,12 +1974,13 @@ class ArrayData : public CollectionData { return; array->removeElement(index, resources); } - bool copyFrom(const ArrayData& src, ResourceManager* resources); - static bool copy(ArrayData* dst, const ArrayData* src, - ResourceManager* resources) { - if (!dst || !src) - return false; - return dst->copyFrom(*src, resources); + void remove(iterator it, ResourceManager* resources) { + CollectionData::removeOne(it, resources); + } + static void remove(ArrayData* array, iterator it, + ResourceManager* resources) { + if (array) + return array->remove(it, resources); } private: iterator at(size_t index, const ResourceManager* resources) const; @@ -2000,11 +1988,11 @@ class ArrayData : public CollectionData { ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE #if ARDUINOJSON_USE_LONG_LONG -typedef int64_t JsonInteger; -typedef uint64_t JsonUInt; +using JsonInteger = int64_t; +using JsonUInt = uint64_t; #else -typedef long JsonInteger; -typedef unsigned long JsonUInt; +using JsonInteger = long; +using JsonUInt = unsigned long; #endif ARDUINOJSON_END_PUBLIC_NAMESPACE #define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) \ @@ -2015,32 +2003,8 @@ ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE class ObjectData : public CollectionData { public: - VariantData* addMember(StringNode* key, ResourceManager* resources) { - ARDUINOJSON_ASSERT(key != nullptr); - auto it = addSlot(resources); - if (it.done()) - return nullptr; - it.setKey(key); - return it.data(); - } - template - VariantData* addMember(TAdaptedString key, ResourceManager* resources) { - ARDUINOJSON_ASSERT(!key.isNull()); - if (key.isLinked()) { - auto it = addSlot(resources); - if (!it.done()) - it.setKey(key.data()); - return it.data(); - } else { - auto storedKey = resources->saveString(key); - if (!storedKey) - return nullptr; - auto it = addSlot(resources); - if (!it.done()) - it.setKey(storedKey); - return it.data(); - } - } + template // also works with StringNode* + VariantData* addMember(TAdaptedString key, ResourceManager* resources); template VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources); template @@ -2062,119 +2026,139 @@ class ObjectData : public CollectionData { return; obj->removeMember(key, resources); } + void remove(iterator it, ResourceManager* resources) { + CollectionData::removePair(it, resources); + } + static void remove(ObjectData* obj, ObjectData::iterator it, + ResourceManager* resources) { + if (!obj) + return; + obj->remove(it, resources); + } + size_t size(const ResourceManager* resources) const { + return CollectionData::size(resources) / 2; + } + static size_t size(const ObjectData* obj, const ResourceManager* resources) { + if (!obj) + return 0; + return obj->size(resources); + } private: template iterator findKey(TAdaptedString key, const ResourceManager* resources) const; }; -enum { - VALUE_MASK = 0x7F, - OWNED_VALUE_BIT = 0x01, - VALUE_IS_NULL = 0, - VALUE_IS_RAW_STRING = 0x03, - VALUE_IS_LINKED_STRING = 0x04, - VALUE_IS_OWNED_STRING = 0x05, - VALUE_IS_BOOLEAN = 0x06, - NUMBER_BIT = 0x08, - VALUE_IS_UNSIGNED_INTEGER = 0x08, - VALUE_IS_SIGNED_INTEGER = 0x0A, - VALUE_IS_FLOAT = 0x0C, - COLLECTION_MASK = 0x60, - VALUE_IS_OBJECT = 0x20, - VALUE_IS_ARRAY = 0x40, - OWNED_KEY_BIT = 0x80 +enum class VariantTypeBits : uint8_t { + OwnedStringBit = 0x01, // 0000 0001 + NumberBit = 0x08, // 0000 1000 +#if ARDUINOJSON_USE_EXTENSIONS + ExtensionBit = 0x10, // 0001 0000 +#endif + CollectionMask = 0x60, +}; +enum class VariantType : uint8_t { + Null = 0, // 0000 0000 + RawString = 0x03, // 0000 0011 + LinkedString = 0x04, // 0000 0100 + OwnedString = 0x05, // 0000 0101 + Boolean = 0x06, // 0000 0110 + Uint32 = 0x0A, // 0000 1010 + Int32 = 0x0C, // 0000 1100 + Float = 0x0E, // 0000 1110 +#if ARDUINOJSON_USE_LONG_LONG + Uint64 = 0x1A, // 0001 1010 + Int64 = 0x1C, // 0001 1100 +#endif +#if ARDUINOJSON_USE_DOUBLE + Double = 0x1E, // 0001 1110 +#endif + Object = 0x20, + Array = 0x40, }; +inline bool operator&(VariantType type, VariantTypeBits bit) { + return (uint8_t(type) & uint8_t(bit)) != 0; +} union VariantContent { VariantContent() {} - JsonFloat asFloat; + float asFloat; bool asBoolean; - JsonUInt asUnsignedInteger; - JsonInteger asSignedInteger; + uint32_t asUint32; + int32_t asInt32; +#if ARDUINOJSON_USE_EXTENSIONS + SlotId asSlotId; +#endif ArrayData asArray; ObjectData asObject; CollectionData asCollection; const char* asLinkedString; struct StringNode* asOwnedString; }; -struct StringNode; -class VariantSlot { - VariantContent content_; - uint8_t flags_; +#if ARDUINOJSON_USE_EXTENSIONS +union VariantExtension { +# if ARDUINOJSON_USE_LONG_LONG + uint64_t asUint64; + int64_t asInt64; +# endif +# if ARDUINOJSON_USE_DOUBLE + double asDouble; +# endif +}; +#endif +template +T parseNumber(const char* s); +class VariantData { + VariantContent content_; // must be first to allow cast from array to variant + VariantType type_; SlotId next_; - const char* key_; public: static void* operator new(size_t, void* p) noexcept { return p; } static void operator delete(void*, void*) noexcept {} - VariantSlot() : flags_(0), next_(NULL_SLOT), key_(0) {} - VariantData* data() { - return reinterpret_cast(&content_); - } - const VariantData* data() const { - return reinterpret_cast(&content_); - } + VariantData() : type_(VariantType::Null), next_(NULL_SLOT) {} SlotId next() const { return next_; } void setNext(SlotId slot) { next_ = slot; } - void setKey(const char* k) { - ARDUINOJSON_ASSERT(k); - flags_ &= VALUE_MASK; - key_ = k; - } - void setKey(StringNode* k) { - ARDUINOJSON_ASSERT(k); - flags_ |= OWNED_KEY_BIT; - key_ = k->data; - } - const char* key() const { - return key_; - } - bool ownsKey() const { - return (flags_ & OWNED_KEY_BIT) != 0; - } -}; -inline VariantData* slotData(VariantSlot* slot) { - return reinterpret_cast(slot); -} -constexpr size_t sizeofArray(size_t n) { - return n * sizeof(VariantSlot); -} -constexpr size_t sizeofObject(size_t n) { - return n * sizeof(VariantSlot); -} -template -T parseNumber(const char* s); -class VariantData { - VariantContent content_; // must be first to allow cast from array to variant - uint8_t flags_; - public: - VariantData() : flags_(VALUE_IS_NULL) {} template - typename TVisitor::result_type accept(TVisitor& visit) const { - switch (type()) { - case VALUE_IS_FLOAT: + typename TVisitor::result_type accept( + TVisitor& visit, const ResourceManager* resources) const { +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Float: return visit.visit(content_.asFloat); - case VALUE_IS_ARRAY: +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return visit.visit(extension->asDouble); +#endif + case VariantType::Array: return visit.visit(content_.asArray); - case VALUE_IS_OBJECT: + case VariantType::Object: return visit.visit(content_.asObject); - case VALUE_IS_LINKED_STRING: - return visit.visit(JsonString(content_.asLinkedString)); - case VALUE_IS_OWNED_STRING: + case VariantType::LinkedString: + return visit.visit(JsonString(content_.asLinkedString, true)); + case VariantType::OwnedString: return visit.visit(JsonString(content_.asOwnedString->data, - content_.asOwnedString->length, - JsonString::Copied)); - case VALUE_IS_RAW_STRING: + content_.asOwnedString->length)); + case VariantType::RawString: return visit.visit(RawString(content_.asOwnedString->data, content_.asOwnedString->length)); - case VALUE_IS_SIGNED_INTEGER: - return visit.visit(content_.asSignedInteger); - case VALUE_IS_UNSIGNED_INTEGER: - return visit.visit(content_.asUnsignedInteger); - case VALUE_IS_BOOLEAN: + case VariantType::Int32: + return visit.visit(static_cast(content_.asInt32)); + case VariantType::Uint32: + return visit.visit(static_cast(content_.asUint32)); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Int64: + return visit.visit(extension->asInt64); + case VariantType::Uint64: + return visit.visit(extension->asUint64); +#endif + case VariantType::Boolean: return visit.visit(content_.asBoolean != 0); default: return visit.visit(nullptr); @@ -2182,9 +2166,10 @@ class VariantData { } template static typename TVisitor::result_type accept(const VariantData* var, + const ResourceManager* resources, TVisitor& visit) { if (var != 0) - return var->accept(visit); + return var->accept(visit, resources); else return visit.visit(nullptr); } @@ -2197,17 +2182,43 @@ class VariantData { return nullptr; return var->addElement(resources); } - bool asBoolean() const { - switch (type()) { - case VALUE_IS_BOOLEAN: + template + bool addValue(const T& value, ResourceManager* resources) { + auto array = isNull() ? &toArray() : asArray(); + return detail::ArrayData::addValue(array, value, resources); + } + template + static bool addValue(VariantData* var, const T& value, + ResourceManager* resources) { + if (!var) + return false; + return var->addValue(value, resources); + } + bool asBoolean(const ResourceManager* resources) const { +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Boolean: return content_.asBoolean; - case VALUE_IS_SIGNED_INTEGER: - case VALUE_IS_UNSIGNED_INTEGER: - return content_.asUnsignedInteger != 0; - case VALUE_IS_FLOAT: + case VariantType::Uint32: + case VariantType::Int32: + return content_.asUint32 != 0; + case VariantType::Float: return content_.asFloat != 0; - case VALUE_IS_NULL: +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return extension->asDouble != 0; +#endif + case VariantType::Null: return false; +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + case VariantType::Int64: + return extension->asUint64 != 0; +#endif default: return true; } @@ -2225,40 +2236,70 @@ class VariantData { return const_cast(this)->asCollection(); } template - T asFloat() const { + T asFloat(const ResourceManager* resources) const { static_assert(is_floating_point::value, "T must be a floating point"); - switch (type()) { - case VALUE_IS_BOOLEAN: +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Boolean: return static_cast(content_.asBoolean); - case VALUE_IS_UNSIGNED_INTEGER: - return static_cast(content_.asUnsignedInteger); - case VALUE_IS_SIGNED_INTEGER: - return static_cast(content_.asSignedInteger); - case VALUE_IS_LINKED_STRING: - case VALUE_IS_OWNED_STRING: + case VariantType::Uint32: + return static_cast(content_.asUint32); + case VariantType::Int32: + return static_cast(content_.asInt32); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + return static_cast(extension->asUint64); + case VariantType::Int64: + return static_cast(extension->asInt64); +#endif + case VariantType::LinkedString: + case VariantType::OwnedString: return parseNumber(content_.asOwnedString->data); - case VALUE_IS_FLOAT: + case VariantType::Float: return static_cast(content_.asFloat); +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return static_cast(extension->asDouble); +#endif default: return 0; } } template - T asIntegral() const { + T asIntegral(const ResourceManager* resources) const { static_assert(is_integral::value, "T must be an integral type"); - switch (type()) { - case VALUE_IS_BOOLEAN: +#if ARDUINOJSON_USE_EXTENSIONS + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Boolean: return content_.asBoolean; - case VALUE_IS_UNSIGNED_INTEGER: - return convertNumber(content_.asUnsignedInteger); - case VALUE_IS_SIGNED_INTEGER: - return convertNumber(content_.asSignedInteger); - case VALUE_IS_LINKED_STRING: + case VariantType::Uint32: + return convertNumber(content_.asUint32); + case VariantType::Int32: + return convertNumber(content_.asInt32); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + return convertNumber(extension->asUint64); + case VariantType::Int64: + return convertNumber(extension->asInt64); +#endif + case VariantType::LinkedString: return parseNumber(content_.asLinkedString); - case VALUE_IS_OWNED_STRING: + case VariantType::OwnedString: return parseNumber(content_.asOwnedString->data); - case VALUE_IS_FLOAT: + case VariantType::Float: return convertNumber(content_.asFloat); +#if ARDUINOJSON_USE_DOUBLE + case VariantType::Double: + return convertNumber(extension->asDouble); +#endif default: return 0; } @@ -2270,25 +2311,28 @@ class VariantData { return const_cast(this)->asObject(); } JsonString asRawString() const { - switch (type()) { - case VALUE_IS_RAW_STRING: + switch (type_) { + case VariantType::RawString: return JsonString(content_.asOwnedString->data, - content_.asOwnedString->length, JsonString::Copied); + content_.asOwnedString->length); default: return JsonString(); } } JsonString asString() const { - switch (type()) { - case VALUE_IS_LINKED_STRING: - return JsonString(content_.asLinkedString, JsonString::Linked); - case VALUE_IS_OWNED_STRING: + switch (type_) { + case VariantType::LinkedString: + return JsonString(content_.asLinkedString, true); + case VariantType::OwnedString: return JsonString(content_.asOwnedString->data, - content_.asOwnedString->length, JsonString::Copied); + content_.asOwnedString->length); default: return JsonString(); } } +#if ARDUINOJSON_USE_EXTENSIONS + const VariantExtension* getExtension(const ResourceManager* resources) const; +#endif VariantData* getElement(size_t index, const ResourceManager* resources) const { return ArrayData::getElement(asArray(), index, resources); @@ -2325,30 +2369,41 @@ class VariantData { return obj->getOrAddMember(key, resources); } bool isArray() const { - return (flags_ & VALUE_IS_ARRAY) != 0; + return type_ == VariantType::Array; } bool isBoolean() const { - return type() == VALUE_IS_BOOLEAN; + return type_ == VariantType::Boolean; } bool isCollection() const { - return (flags_ & COLLECTION_MASK) != 0; + return type_ & VariantTypeBits::CollectionMask; } bool isFloat() const { - return (flags_ & NUMBER_BIT) != 0; + return type_ & VariantTypeBits::NumberBit; } template - bool isInteger() const { - switch (type()) { - case VALUE_IS_UNSIGNED_INTEGER: - return canConvertNumber(content_.asUnsignedInteger); - case VALUE_IS_SIGNED_INTEGER: - return canConvertNumber(content_.asSignedInteger); + bool isInteger(const ResourceManager* resources) const { +#if ARDUINOJSON_USE_LONG_LONG + auto extension = getExtension(resources); +#else + (void)resources; // silence warning +#endif + switch (type_) { + case VariantType::Uint32: + return canConvertNumber(content_.asUint32); + case VariantType::Int32: + return canConvertNumber(content_.asInt32); +#if ARDUINOJSON_USE_LONG_LONG + case VariantType::Uint64: + return canConvertNumber(extension->asUint64); + case VariantType::Int64: + return canConvertNumber(extension->asInt64); +#endif default: return false; } } bool isNull() const { - return type() == VALUE_IS_NULL; + return type_ == VariantType::Null; } static bool isNull(const VariantData* var) { if (!var) @@ -2356,10 +2411,11 @@ class VariantData { return var->isNull(); } bool isObject() const { - return (flags_ & VALUE_IS_OBJECT) != 0; + return type_ == VariantType::Object; } bool isString() const { - return type() == VALUE_IS_LINKED_STRING || type() == VALUE_IS_OWNED_STRING; + return type_ == VariantType::LinkedString || + type_ == VariantType::OwnedString; } size_t nesting(const ResourceManager* resources) const { auto collection = asCollection(); @@ -2394,153 +2450,210 @@ class VariantData { return; var->removeMember(key, resources); } - void reset() { - flags_ = VALUE_IS_NULL; + void reset() { // TODO: remove + type_ = VariantType::Null; } void setBoolean(bool value) { - setType(VALUE_IS_BOOLEAN); + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Boolean; content_.asBoolean = value; } - void setBoolean(bool value, ResourceManager* resources) { - release(resources); - setBoolean(value); - } - void setFloat(JsonFloat value) { - setType(VALUE_IS_FLOAT); + template + enable_if_t setFloat(T value, ResourceManager*) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Float; content_.asFloat = value; - } - void setFloat(JsonFloat value, ResourceManager* resources) { - release(resources); - setFloat(value); + return true; } template - typename enable_if::value>::type setInteger(T value) { - setType(VALUE_IS_SIGNED_INTEGER); - content_.asSignedInteger = value; - } + enable_if_t setFloat(T value, ResourceManager*); template - typename enable_if::value>::type setInteger(T value) { - setType(VALUE_IS_UNSIGNED_INTEGER); - content_.asUnsignedInteger = static_cast(value); - } + enable_if_t::value, bool> setInteger(T value, + ResourceManager* resources); template - void setInteger(T value, ResourceManager* resources) { - release(resources); - setInteger(value); - } - void setNull() { - setType(VALUE_IS_NULL); - } - void setNull(ResourceManager* resources) { - release(resources); - setNull(); - } - static void setNull(VariantData* var, ResourceManager* resources) { - if (!var) - return; - var->setNull(resources); - } + enable_if_t::value, bool> setInteger( + T value, ResourceManager* resources); void setRawString(StringNode* s) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first ARDUINOJSON_ASSERT(s); - setType(VALUE_IS_RAW_STRING); + type_ = VariantType::RawString; content_.asOwnedString = s; } template - void setRawString(SerializedValue value, ResourceManager* resources) { - release(resources); - auto dup = resources->saveString(adaptString(value.data(), value.size())); - if (dup) - setRawString(dup); - else - setNull(); - } + void setRawString(SerializedValue value, ResourceManager* resources); template static void setRawString(VariantData* var, SerializedValue value, ResourceManager* resources) { if (!var) return; + var->clear(resources); var->setRawString(value, resources); } template - void setString(TAdaptedString value, ResourceManager* resources) { - setNull(resources); - if (value.isNull()) - return; - if (value.isLinked()) { - setLinkedString(value.data()); - return; - } - auto dup = resources->saveString(value); - if (dup) - setOwnedString(dup); + bool setString(TAdaptedString value, ResourceManager* resources); + bool setString(StringNode* s, ResourceManager*) { + setOwnedString(s); + return true; } template static void setString(VariantData* var, TAdaptedString value, ResourceManager* resources) { if (!var) return; + var->clear(resources); var->setString(value, resources); } void setLinkedString(const char* s) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first ARDUINOJSON_ASSERT(s); - setType(VALUE_IS_LINKED_STRING); + type_ = VariantType::LinkedString; content_.asLinkedString = s; } void setOwnedString(StringNode* s) { + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first ARDUINOJSON_ASSERT(s); - setType(VALUE_IS_OWNED_STRING); + type_ = VariantType::OwnedString; content_.asOwnedString = s; } size_t size(const ResourceManager* resources) const { - return isCollection() ? content_.asCollection.size(resources) : 0; + if (isObject()) + return content_.asObject.size(resources); + if (isArray()) + return content_.asArray.size(resources); + return 0; } static size_t size(const VariantData* var, const ResourceManager* resources) { return var != 0 ? var->size(resources) : 0; } ArrayData& toArray() { - setType(VALUE_IS_ARRAY); + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Array; new (&content_.asArray) ArrayData(); return content_.asArray; } - ArrayData& toArray(ResourceManager* resources) { - release(resources); - return toArray(); - } static ArrayData* toArray(VariantData* var, ResourceManager* resources) { if (!var) return 0; - return &var->toArray(resources); + var->clear(resources); + return &var->toArray(); } ObjectData& toObject() { - setType(VALUE_IS_OBJECT); + ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first + type_ = VariantType::Object; new (&content_.asObject) ObjectData(); return content_.asObject; } - ObjectData& toObject(ResourceManager* resources) { - release(resources); - return toObject(); - } static ObjectData* toObject(VariantData* var, ResourceManager* resources) { if (!var) return 0; - return &var->toObject(resources); + var->clear(resources); + return &var->toObject(); } - uint8_t type() const { - return flags_ & VALUE_MASK; + VariantType type() const { + return type_; } - private: - void release(ResourceManager* resources) { - if (flags_ & OWNED_VALUE_BIT) - resources->dereferenceString(content_.asOwnedString->data); - auto collection = asCollection(); - if (collection) - collection->clear(resources); + void clear(ResourceManager* resources); + static void clear(VariantData* var, ResourceManager* resources) { + if (!var) + return; + var->clear(resources); + } +}; +class VariantData; +class VariantWithId; +class ResourceManager { + union SlotData { + VariantData variant; +#if ARDUINOJSON_USE_EXTENSIONS + VariantExtension extension; +#endif + }; + public: + constexpr static size_t slotSize = sizeof(SlotData); + ResourceManager(Allocator* allocator = DefaultAllocator::instance()) + : allocator_(allocator), overflowed_(false) {} + ~ResourceManager() { + stringPool_.clear(allocator_); + variantPools_.clear(allocator_); + } + ResourceManager(const ResourceManager&) = delete; + ResourceManager& operator=(const ResourceManager& src) = delete; + friend void swap(ResourceManager& a, ResourceManager& b) { + swap(a.stringPool_, b.stringPool_); + swap(a.variantPools_, b.variantPools_); + swap_(a.allocator_, b.allocator_); + swap_(a.overflowed_, b.overflowed_); + } + Allocator* allocator() const { + return allocator_; + } + size_t size() const { + return variantPools_.size() + stringPool_.size(); + } + bool overflowed() const { + return overflowed_; + } + Slot allocVariant(); + void freeVariant(Slot slot); + VariantData* getVariant(SlotId id) const; +#if ARDUINOJSON_USE_EXTENSIONS + Slot allocExtension(); + void freeExtension(SlotId slot); + VariantExtension* getExtension(SlotId id) const; +#endif + template + StringNode* saveString(TAdaptedString str) { + if (str.isNull()) + return 0; + auto node = stringPool_.add(str, allocator_); + if (!node) + overflowed_ = true; + return node; + } + void saveString(StringNode* node) { + stringPool_.add(node); + } + template + StringNode* getString(const TAdaptedString& str) const { + return stringPool_.get(str); + } + StringNode* createString(size_t length) { + auto node = StringNode::create(length, allocator_); + if (!node) + overflowed_ = true; + return node; + } + StringNode* resizeString(StringNode* node, size_t length) { + node = StringNode::resize(node, length, allocator_); + if (!node) + overflowed_ = true; + return node; + } + void destroyString(StringNode* node) { + StringNode::destroy(node, allocator_); + } + void dereferenceString(const char* s) { + stringPool_.dereference(s, allocator_); + } + void clear() { + variantPools_.clear(allocator_); + overflowed_ = false; + stringPool_.clear(allocator_); } - void setType(uint8_t t) { - flags_ &= OWNED_KEY_BIT; - flags_ |= t; + void shrinkToFit() { + variantPools_.shrinkToFit(allocator_); } + private: + Allocator* allocator_; + bool overflowed_; + StringPool stringPool_; + MemoryPoolList variantPools_; }; +template +struct IsString : false_type {}; +template +struct IsString::AdaptedString>> + : true_type {}; ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE class JsonArray; @@ -2552,15 +2665,15 @@ template struct VariantTo {}; template <> struct VariantTo { - typedef JsonArray type; + using type = JsonArray; }; template <> struct VariantTo { - typedef JsonObject type; + using type = JsonObject; }; template <> struct VariantTo { - typedef JsonVariant type; + using type = JsonVariant; }; class VariantAttorney { public: @@ -2598,31 +2711,31 @@ CompareResult arithmeticCompare(const T& lhs, const T& rhs) { template CompareResult arithmeticCompare( const T1& lhs, const T2& rhs, - typename enable_if::value && is_integral::value && - sizeof(T1) < sizeof(T2)>::type* = 0) { + enable_if_t::value && is_integral::value && + sizeof(T1) < sizeof(T2)>* = 0) { return arithmeticCompare(static_cast(lhs), rhs); } template CompareResult arithmeticCompare( const T1& lhs, const T2& rhs, - typename enable_if::value && is_integral::value && - sizeof(T2) < sizeof(T1)>::type* = 0) { + enable_if_t::value && is_integral::value && + sizeof(T2) < sizeof(T1)>* = 0) { return arithmeticCompare(lhs, static_cast(rhs)); } template CompareResult arithmeticCompare( const T1& lhs, const T2& rhs, - typename enable_if::value && is_integral::value && - is_signed::value == is_signed::value && - sizeof(T2) == sizeof(T1)>::type* = 0) { + enable_if_t::value && is_integral::value && + is_signed::value == is_signed::value && + sizeof(T2) == sizeof(T1)>* = 0) { return arithmeticCompare(lhs, static_cast(rhs)); } template CompareResult arithmeticCompare( const T1& lhs, const T2& rhs, - typename enable_if::value && is_integral::value && - is_unsigned::value && is_signed::value && - sizeof(T2) == sizeof(T1)>::type* = 0) { + enable_if_t::value && is_integral::value && + is_unsigned::value && is_signed::value && + sizeof(T2) == sizeof(T1)>* = 0) { if (rhs < 0) return COMPARE_RESULT_GREATER; return arithmeticCompare(lhs, static_cast(rhs)); @@ -2630,9 +2743,9 @@ CompareResult arithmeticCompare( template CompareResult arithmeticCompare( const T1& lhs, const T2& rhs, - typename enable_if::value && is_integral::value && - is_signed::value && is_unsigned::value && - sizeof(T2) == sizeof(T1)>::type* = 0) { + enable_if_t::value && is_integral::value && + is_signed::value && is_unsigned::value && + sizeof(T2) == sizeof(T1)>* = 0) { if (lhs < 0) return COMPARE_RESULT_LESS; return arithmeticCompare(static_cast(lhs), rhs); @@ -2640,35 +2753,31 @@ CompareResult arithmeticCompare( template CompareResult arithmeticCompare( const T1& lhs, const T2& rhs, - typename enable_if::value || - is_floating_point::value>::type* = 0) { + enable_if_t::value || is_floating_point::value>* = + 0) { return arithmeticCompare(static_cast(lhs), static_cast(rhs)); } template CompareResult arithmeticCompareNegateLeft( - JsonUInt, const T2&, - typename enable_if::value>::type* = 0) { + JsonUInt, const T2&, enable_if_t::value>* = 0) { return COMPARE_RESULT_LESS; } template CompareResult arithmeticCompareNegateLeft( - JsonUInt lhs, const T2& rhs, - typename enable_if::value>::type* = 0) { + JsonUInt lhs, const T2& rhs, enable_if_t::value>* = 0) { if (rhs > 0) return COMPARE_RESULT_LESS; return arithmeticCompare(-rhs, static_cast(lhs)); } template CompareResult arithmeticCompareNegateRight( - const T1&, JsonUInt, - typename enable_if::value>::type* = 0) { + const T1&, JsonUInt, enable_if_t::value>* = 0) { return COMPARE_RESULT_GREATER; } template CompareResult arithmeticCompareNegateRight( - const T1& lhs, JsonUInt rhs, - typename enable_if::value>::type* = 0) { + const T1& lhs, JsonUInt rhs, enable_if_t::value>* = 0) { if (lhs > 0) return COMPARE_RESULT_GREATER; return arithmeticCompare(static_cast(rhs), -lhs); @@ -2687,10 +2796,9 @@ CompareResult compare(JsonVariantConst lhs, struct VariantOperatorTag {}; template struct VariantOperators : VariantOperatorTag { - template - friend - typename enable_if::value && !is_array::value, T>::type - operator|(const TVariant& variant, const T& defaultValue) { + template ::value && !is_array::value, int> = 0> + friend T operator|(const TVariant& variant, const T& defaultValue) { if (variant.template is()) return variant.template as(); else @@ -2704,119 +2812,113 @@ struct VariantOperators : VariantOperatorTag { return defaultValue; } template - friend typename enable_if::value, JsonVariantConst>::type - operator|(const TVariant& variant, T defaultValue) { + friend enable_if_t::value, JsonVariantConst> operator|( + const TVariant& variant, const T& defaultValue) { if (variant) return variant; else return defaultValue; } template - friend bool operator==(T* lhs, TVariant rhs) { + friend bool operator==(T* lhs, const TVariant& rhs) { return compare(rhs, lhs) == COMPARE_RESULT_EQUAL; } template - friend bool operator==(const T& lhs, TVariant rhs) { + friend bool operator==(const T& lhs, const TVariant& rhs) { return compare(rhs, lhs) == COMPARE_RESULT_EQUAL; } template - friend bool operator==(TVariant lhs, T* rhs) { + friend bool operator==(const TVariant& lhs, T* rhs) { return compare(lhs, rhs) == COMPARE_RESULT_EQUAL; } - template - friend - typename enable_if::value, bool>::type - operator==(TVariant lhs, const T& rhs) { + template ::value, int> = 0> + friend bool operator==(const TVariant& lhs, const T& rhs) { return compare(lhs, rhs) == COMPARE_RESULT_EQUAL; } template - friend bool operator!=(T* lhs, TVariant rhs) { + friend bool operator!=(T* lhs, const TVariant& rhs) { return compare(rhs, lhs) != COMPARE_RESULT_EQUAL; } template - friend bool operator!=(const T& lhs, TVariant rhs) { + friend bool operator!=(const T& lhs, const TVariant& rhs) { return compare(rhs, lhs) != COMPARE_RESULT_EQUAL; } template - friend bool operator!=(TVariant lhs, T* rhs) { + friend bool operator!=(const TVariant& lhs, T* rhs) { return compare(lhs, rhs) != COMPARE_RESULT_EQUAL; } - template - friend - typename enable_if::value, bool>::type - operator!=(TVariant lhs, const T& rhs) { + template ::value, int> = 0> + friend bool operator!=(TVariant lhs, const T& rhs) { return compare(lhs, rhs) != COMPARE_RESULT_EQUAL; } template - friend bool operator<(T* lhs, TVariant rhs) { + friend bool operator<(T* lhs, const TVariant& rhs) { return compare(rhs, lhs) == COMPARE_RESULT_GREATER; } template - friend bool operator<(const T& lhs, TVariant rhs) { + friend bool operator<(const T& lhs, const TVariant& rhs) { return compare(rhs, lhs) == COMPARE_RESULT_GREATER; } template - friend bool operator<(TVariant lhs, T* rhs) { + friend bool operator<(const TVariant& lhs, T* rhs) { return compare(lhs, rhs) == COMPARE_RESULT_LESS; } - template - friend - typename enable_if::value, bool>::type - operator<(TVariant lhs, const T& rhs) { + template ::value, int> = 0> + friend bool operator<(TVariant lhs, const T& rhs) { return compare(lhs, rhs) == COMPARE_RESULT_LESS; } template - friend bool operator<=(T* lhs, TVariant rhs) { + friend bool operator<=(T* lhs, const TVariant& rhs) { return (compare(rhs, lhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; } template - friend bool operator<=(const T& lhs, TVariant rhs) { + friend bool operator<=(const T& lhs, const TVariant& rhs) { return (compare(rhs, lhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; } template - friend bool operator<=(TVariant lhs, T* rhs) { + friend bool operator<=(const TVariant& lhs, T* rhs) { return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; } - template - friend - typename enable_if::value, bool>::type - operator<=(TVariant lhs, const T& rhs) { + template ::value, int> = 0> + friend bool operator<=(TVariant lhs, const T& rhs) { return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; } template - friend bool operator>(T* lhs, TVariant rhs) { + friend bool operator>(T* lhs, const TVariant& rhs) { return compare(rhs, lhs) == COMPARE_RESULT_LESS; } template - friend bool operator>(const T& lhs, TVariant rhs) { + friend bool operator>(const T& lhs, const TVariant& rhs) { return compare(rhs, lhs) == COMPARE_RESULT_LESS; } template - friend bool operator>(TVariant lhs, T* rhs) { + friend bool operator>(const TVariant& lhs, T* rhs) { return compare(lhs, rhs) == COMPARE_RESULT_GREATER; } - template - friend - typename enable_if::value, bool>::type - operator>(TVariant lhs, const T& rhs) { + template ::value, int> = 0> + friend bool operator>(TVariant lhs, const T& rhs) { return compare(lhs, rhs) == COMPARE_RESULT_GREATER; } template - friend bool operator>=(T* lhs, TVariant rhs) { + friend bool operator>=(T* lhs, const TVariant& rhs) { return (compare(rhs, lhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; } template - friend bool operator>=(const T& lhs, TVariant rhs) { + friend bool operator>=(const T& lhs, const TVariant& rhs) { return (compare(rhs, lhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; } template - friend bool operator>=(TVariant lhs, T* rhs) { + friend bool operator>=(const TVariant& lhs, T* rhs) { return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; } - template - friend - typename enable_if::value, bool>::type - operator>=(TVariant lhs, const T& rhs) { + template ::value, int> = 0> + friend bool operator>=(const TVariant& lhs, const T& rhs) { return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; } }; @@ -2827,6 +2929,11 @@ class JsonObject; class JsonVariantConst : public detail::VariantTag, public detail::VariantOperators { friend class detail::VariantAttorney; + template + using ConversionSupported = + detail::is_same::fromJson)>::arg1_type, + JsonVariantConst>; public: JsonVariantConst() : data_(nullptr), resources_(nullptr) {} explicit JsonVariantConst(const detail::VariantData* data, @@ -2844,56 +2951,81 @@ class JsonVariantConst : public detail::VariantTag, size_t size() const { return detail::VariantData::size(data_, resources_); } - template - typename detail::enable_if::value && - !detail::is_same::value, - T>::type - as() const { + template ::value, int> = 0> + T as() const { return Converter::fromJson(*this); } - template - typename detail::enable_if::value && - !detail::is_same::value, - bool>::type - is() const { + template ::value, int> = 0> + detail::InvalidConversion as() const; + template ::value, int> = 0> + bool is() const { return Converter::checkJson(*this); } + template ::value, int> = 0> + bool is() const { + return false; + } template operator T() const { return as(); } - JsonVariantConst operator[](size_t index) const { + template ::value, int> = 0> + JsonVariantConst operator[](T index) const { return JsonVariantConst( - detail::VariantData::getElement(data_, index, resources_), resources_); + detail::VariantData::getElement(data_, size_t(index), resources_), + resources_); } - template - typename detail::enable_if::value, - JsonVariantConst>::type - operator[](const TString& key) const { + template ::value, int> = 0> + JsonVariantConst operator[](const TString& key) const { return JsonVariantConst(detail::VariantData::getMember( data_, detail::adaptString(key), resources_), resources_); } - template - typename detail::enable_if::value, - JsonVariantConst>::type - operator[](TChar* key) const { + template ::value && + !detail::is_const::value, + int> = 0> + JsonVariantConst operator[](TChar* key) const { return JsonVariantConst(detail::VariantData::getMember( data_, detail::adaptString(key), resources_), resources_); } - template - typename detail::enable_if::value, bool>::type - containsKey(const TString& key) const { + template ::value, int> = 0> + JsonVariantConst operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + else + return operator[](key.template as()); + } + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use var[key].is() instead") + bool containsKey(const TString& key) const { return detail::VariantData::getMember(getData(), detail::adaptString(key), resources_) != 0; } - template - typename detail::enable_if::value, bool>::type - containsKey(TChar* key) const { + template ::value && + !detail::is_const::value, + int> = 0> + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + bool containsKey(TChar* key) const { return detail::VariantData::getMember(getData(), detail::adaptString(key), resources_) != 0; } + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use var[key].is() instead") + bool containsKey(const TVariant& key) const { + return containsKey(key.template as()); + } ARDUINOJSON_DEPRECATED("always returns zero") size_t memoryUsage() const { return 0; @@ -2921,7 +3053,7 @@ class VariantRefBase : public VariantTag { friend class VariantAttorney; public: void clear() const { - VariantData::setNull(getOrCreateData(), getResourceManager()); + VariantData::clear(getOrCreateData(), getResourceManager()); } bool isNull() const { return VariantData::isNull(getData()); @@ -2930,88 +3062,97 @@ class VariantRefBase : public VariantTag { return !getData(); } template - typename enable_if::value, T>::type as() - const { - return Converter::fromJson(getVariantConst()); - } - template - typename enable_if::value, T>::type as() const; - template ::value>::type> + T as() const; + template ::value, int> = 0> operator T() const { return as(); } + template ::value, int> = 0> + JsonArray to() const; + template ::value, int> = 0> + JsonObject to() const; + template ::value, int> = 0> + JsonVariant to() const; template - typename enable_if::value, JsonArray>::type to() const; - template - typename enable_if::value, JsonObject>::type to() - const; - template - typename enable_if::value, JsonVariant>::type to() - const; - template - FORCE_INLINE - typename enable_if::value, bool>::type - is() const; + FORCE_INLINE bool is() const; template - FORCE_INLINE - typename enable_if::value, bool>::type - is() const { - return Converter::checkJson(getVariantConst()); + bool set(const T& value) const { + using TypeForConverter = conditional_t::value, T, + remove_cv_t>>; + return doSet>(value); + } + template ::value, int> = 0> + bool set(T* value) const { + return doSet>(value); } - template - bool set(const T& value) const; - template - bool set(T* value) const; size_t size() const { return VariantData::size(getData(), getResourceManager()); } size_t nesting() const { return VariantData::nesting(getData(), getResourceManager()); } - template - typename enable_if::value, T>::type add() const { + template ::value, int> = 0> + T add() const { return add().template to(); } - template - typename enable_if::value, T>::type add() const; + template ::value, int> = 0> + T add() const; template bool add(const T& value) const { - return add().set(value); + return detail::VariantData::addValue(getOrCreateData(), value, + getResourceManager()); } - template + template ::value, int> = 0> bool add(T* value) const { - return add().set(value); + return detail::VariantData::addValue(getOrCreateData(), value, + getResourceManager()); } void remove(size_t index) const { VariantData::removeElement(getData(), index, getResourceManager()); } - template - typename enable_if::value>::type remove(TChar* key) const { + template ::value, int> = 0> + void remove(TChar* key) const { VariantData::removeMember(getData(), adaptString(key), getResourceManager()); } - template - typename enable_if::value>::type remove( - const TString& key) const { + template ::value, int> = 0> + void remove(const TString& key) const { VariantData::removeMember(getData(), adaptString(key), getResourceManager()); } + template ::value, int> = 0> + void remove(const TVariant& key) const { + if (key.template is()) + remove(key.template as()); + else + remove(key.template as()); + } ElementProxy operator[](size_t index) const; - template - typename enable_if::value, bool>::type containsKey( + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + bool containsKey(const TString& key) const; + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + bool containsKey(TChar* key) const; + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + bool containsKey(const TVariant& key) const; + template ::value, int> = 0> + FORCE_INLINE MemberProxy> operator[]( const TString& key) const; - template - typename enable_if::value, bool>::type containsKey( + template < + typename TChar, + enable_if_t::value && !is_const::value, int> = 0> + FORCE_INLINE MemberProxy> operator[]( TChar* key) const; - template - FORCE_INLINE typename enable_if::value, - MemberProxy>::type - operator[](const TString& key) const; - template - FORCE_INLINE typename enable_if::value, - MemberProxy>::type - operator[](TChar* key) const; + template ::value, int> = 0> + JsonVariantConst operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + else + return operator[](key.template as()); + } ARDUINOJSON_DEPRECATED("use add() instead") JsonVariant add() const; ARDUINOJSON_DEPRECATED("use add() instead") @@ -3058,17 +3199,41 @@ class VariantRefBase : public VariantTag { FORCE_INLINE ArduinoJson::JsonVariantConst getVariantConst() const { return ArduinoJson::JsonVariantConst(getData(), getResourceManager()); } + template + FORCE_INLINE enable_if_t::value, T> getVariant() + const { + return getVariantConst(); + } + template + FORCE_INLINE enable_if_t::value, T> getVariant() + const { + return getVariant(); + } + template + bool doSet(const T& value) const { + return doSet( + value, is_same::return_type, + bool>{}); + } + template + bool doSet(const T& value, false_type) const; + template + bool doSet(const T& value, true_type) const; ArduinoJson::JsonVariant getOrCreateVariant() const; }; template class ElementProxy : public VariantRefBase>, public VariantOperators> { friend class VariantAttorney; + friend class VariantRefBase>; + template + friend class MemberProxy; + template + friend class ElementProxy; public: ElementProxy(TUpstream upstream, size_t index) : upstream_(upstream), index_(index) {} - ElementProxy(const ElementProxy& src) - : upstream_(src.upstream_), index_(src.index_) {} ElementProxy& operator=(const ElementProxy& src) { this->set(src); return *this; @@ -3084,6 +3249,8 @@ class ElementProxy : public VariantRefBase>, return *this; } private: + ElementProxy(const ElementProxy& src) // Error here? See https://arduinojson.org/v7/proxy-non-copyable/ + : upstream_(src.upstream_), index_(src.index_) {} ResourceManager* getResourceManager() const { return VariantAttorney::getResourceManager(upstream_); } @@ -3135,15 +3302,10 @@ struct Converter : private detail::VariantAttorney { static JsonVariant fromJson(JsonVariant src) { return src; } - static detail::InvalidConversion fromJson( - JsonVariantConst); static bool checkJson(JsonVariant src) { auto data = getData(src); return !!data; } - static bool checkJson(JsonVariantConst) { - return false; - } }; template <> struct Converter : private detail::VariantAttorney { @@ -3230,7 +3392,7 @@ class JsonArrayConst : public detail::VariantOperators { friend class JsonArray; friend class detail::VariantAttorney; public: - typedef JsonArrayConstIterator iterator; + using iterator = JsonArrayConstIterator; iterator begin() const { if (!data_) return iterator(); @@ -3239,13 +3401,24 @@ class JsonArrayConst : public detail::VariantOperators { iterator end() const { return iterator(); } - JsonArrayConst() : data_(0) {} + JsonArrayConst() : data_(0), resources_(0) {} JsonArrayConst(const detail::ArrayData* data, const detail::ResourceManager* resources) : data_(data), resources_(resources) {} - JsonVariantConst operator[](size_t index) const { + template ::value, int> = 0> + JsonVariantConst operator[](T index) const { return JsonVariantConst( - detail::ArrayData::getElement(data_, index, resources_), resources_); + detail::ArrayData::getElement(data_, size_t(index), resources_), + resources_); + } + template ::value, int> = 0> + JsonVariantConst operator[](const TVariant& variant) const { + if (variant.template is()) + return operator[](variant.template as()); + else + return JsonVariantConst(); } operator JsonVariantConst() const { return JsonVariantConst(getData(), resources_); @@ -3295,7 +3468,7 @@ class JsonObject; class JsonArray : public detail::VariantOperators { friend class detail::VariantAttorney; public: - typedef JsonArrayIterator iterator; + using iterator = JsonArrayIterator; JsonArray() : data_(0), resources_(0) {} JsonArray(detail::ArrayData* data, detail::ResourceManager* resources) : data_(data), resources_(resources) {} @@ -3307,24 +3480,25 @@ class JsonArray : public detail::VariantOperators { operator JsonArrayConst() const { return JsonArrayConst(data_, resources_); } - template - typename detail::enable_if::value, T>::type - add() const { + template ::value, int> = 0> + T add() const { return add().to(); } - template - typename detail::enable_if::value, T>::type - add() const { + template ::value, int> = 0> + JsonVariant add() const { return JsonVariant(detail::ArrayData::addElement(data_, resources_), resources_); } template bool add(const T& value) const { - return add().set(value); + return detail::ArrayData::addValue(data_, value, resources_); } - template + template ::value, int> = 0> bool add(T* value) const { - return add().set(value); + return detail::ArrayData::addValue(data_, value, resources_); } iterator begin() const { if (!data_) @@ -3350,11 +3524,27 @@ class JsonArray : public detail::VariantOperators { void remove(size_t index) const { detail::ArrayData::removeElement(data_, index, resources_); } + template ::value, int> = 0> + void remove(const TVariant& variant) const { + if (variant.template is()) + remove(variant.template as()); + } void clear() const { detail::ArrayData::clear(data_, resources_); } - detail::ElementProxy operator[](size_t index) const { - return {*this, index}; + template ::value, int> = 0> + detail::ElementProxy operator[](T index) const { + return {*this, size_t(index)}; + } + template ::value, int> = 0> + detail::ElementProxy operator[](const TVariant& variant) const { + if (variant.template is()) + return {*this, variant.template as()}; + else + return {*this, size_t(-1)}; } operator JsonVariantConst() const { return JsonVariantConst(collectionToVariant(data_), resources_); @@ -3401,42 +3591,42 @@ class JsonArray : public detail::VariantOperators { class JsonPair { public: JsonPair(detail::ObjectData::iterator iterator, - detail::ResourceManager* resources) - : iterator_(iterator), resources_(resources) {} + detail::ResourceManager* resources) { + if (!iterator.done()) { + key_ = iterator->asString(); + iterator.next(resources); + value_ = JsonVariant(iterator.data(), resources); + } + } JsonString key() const { - if (!iterator_.done()) - return JsonString(iterator_.key(), iterator_.ownsKey() - ? JsonString::Copied - : JsonString::Linked); - else - return JsonString(); + return key_; } JsonVariant value() { - return JsonVariant(iterator_.data(), resources_); + return value_; } private: - detail::ObjectData::iterator iterator_; - detail::ResourceManager* resources_; + JsonString key_; + JsonVariant value_; }; class JsonPairConst { public: JsonPairConst(detail::ObjectData::iterator iterator, - const detail::ResourceManager* resources) - : iterator_(iterator), resources_(resources) {} + const detail::ResourceManager* resources) { + if (!iterator.done()) { + key_ = iterator->asString(); + iterator.next(resources); + value_ = JsonVariantConst(iterator.data(), resources); + } + } JsonString key() const { - if (!iterator_.done()) - return JsonString(iterator_.key(), iterator_.ownsKey() - ? JsonString::Copied - : JsonString::Linked); - else - return JsonString(); + return key_; } JsonVariantConst value() const { - return JsonVariantConst(iterator_.data(), resources_); + return value_; } private: - detail::ObjectData::iterator iterator_; - const detail::ResourceManager* resources_; + JsonString key_; + JsonVariantConst value_; }; class JsonObjectIterator { friend class JsonObject; @@ -3458,7 +3648,8 @@ class JsonObjectIterator { return iterator_ != other.iterator_; } JsonObjectIterator& operator++() { - iterator_.next(resources_); + iterator_.next(resources_); // key + iterator_.next(resources_); // value return *this; } private: @@ -3485,7 +3676,8 @@ class JsonObjectConstIterator { return iterator_ != other.iterator_; } JsonObjectConstIterator& operator++() { - iterator_.next(resources_); + iterator_.next(resources_); // key + iterator_.next(resources_); // value return *this; } private: @@ -3496,8 +3688,8 @@ class JsonObjectConst : public detail::VariantOperators { friend class JsonObject; friend class detail::VariantAttorney; public: - typedef JsonObjectConstIterator iterator; - JsonObjectConst() : data_(0) {} + using iterator = JsonObjectConstIterator; + JsonObjectConst() : data_(0), resources_(0) {} JsonObjectConst(const detail::ObjectData* data, const detail::ResourceManager* resources) : data_(data), resources_(resources) {} @@ -3524,32 +3716,49 @@ class JsonObjectConst : public detail::VariantOperators { iterator end() const { return iterator(); } - template + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") bool containsKey(const TString& key) const { return detail::ObjectData::getMember(data_, detail::adaptString(key), resources_) != 0; } template + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") bool containsKey(TChar* key) const { return detail::ObjectData::getMember(data_, detail::adaptString(key), resources_) != 0; } - template - typename detail::enable_if::value, - JsonVariantConst>::type - operator[](const TString& key) const { + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + bool containsKey(const TVariant& key) const { + return containsKey(key.template as()); + } + template ::value, int> = 0> + JsonVariantConst operator[](const TString& key) const { return JsonVariantConst(detail::ObjectData::getMember( data_, detail::adaptString(key), resources_), resources_); } - template - typename detail::enable_if::value, - JsonVariantConst>::type - operator[](TChar* key) const { + template ::value && + !detail::is_const::value, + int> = 0> + JsonVariantConst operator[](TChar* key) const { return JsonVariantConst(detail::ObjectData::getMember( data_, detail::adaptString(key), resources_), resources_); } + template ::value, int> = 0> + JsonVariantConst operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + else + return JsonVariantConst(); + } ARDUINOJSON_DEPRECATED("always returns zero") size_t memoryUsage() const { return 0; @@ -3579,16 +3788,19 @@ inline bool operator==(JsonObjectConst lhs, JsonObjectConst rhs) { } ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE -template +template class MemberProxy - : public VariantRefBase>, - public VariantOperators> { + : public VariantRefBase>, + public VariantOperators> { friend class VariantAttorney; + friend class VariantRefBase>; + template + friend class MemberProxy; + template + friend class ElementProxy; public: - MemberProxy(TUpstream upstream, TStringRef key) + MemberProxy(TUpstream upstream, AdaptedString key) : upstream_(upstream), key_(key) {} - MemberProxy(const MemberProxy& src) - : upstream_(src.upstream_), key_(src.key_) {} MemberProxy& operator=(const MemberProxy& src) { this->set(src); return *this; @@ -3598,30 +3810,32 @@ class MemberProxy this->set(src); return *this; } - template + template ::value, int> = 0> MemberProxy& operator=(T* src) { this->set(src); return *this; } private: + MemberProxy(const MemberProxy& src) // Error here? See https://arduinojson.org/v7/proxy-non-copyable/ + : upstream_(src.upstream_), key_(src.key_) {} ResourceManager* getResourceManager() const { return VariantAttorney::getResourceManager(upstream_); } VariantData* getData() const { return VariantData::getMember( - VariantAttorney::getData(upstream_), adaptString(key_), + VariantAttorney::getData(upstream_), key_, VariantAttorney::getResourceManager(upstream_)); } VariantData* getOrCreateData() const { auto data = VariantAttorney::getOrCreateData(upstream_); if (!data) return nullptr; - return data->getOrAddMember(adaptString(key_), + return data->getOrAddMember(key_, VariantAttorney::getResourceManager(upstream_)); } private: TUpstream upstream_; - TStringRef key_; + AdaptedString key_; }; ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE @@ -3629,7 +3843,7 @@ class JsonArray; class JsonObject : public detail::VariantOperators { friend class detail::VariantAttorney; public: - typedef JsonObjectIterator iterator; + using iterator = JsonObjectIterator; JsonObject() : data_(0), resources_(0) {} JsonObject(detail::ObjectData* data, detail::ResourceManager* resource) : data_(data), resources_(resource) {} @@ -3677,43 +3891,68 @@ class JsonObject : public detail::VariantOperators { } return true; } - template - typename detail::enable_if::value, - detail::MemberProxy>::type - operator[](const TString& key) const { - return {*this, key}; - } - template - typename detail::enable_if::value, - detail::MemberProxy>::type - operator[](TChar* key) const { - return {*this, key}; + template ::value, int> = 0> + detail::MemberProxy> operator[]( + const TString& key) const { + return {*this, detail::adaptString(key)}; + } + template ::value && + !detail::is_const::value, + int> = 0> + detail::MemberProxy> operator[]( + TChar* key) const { + return {*this, detail::adaptString(key)}; + } + template ::value, int> = 0> + detail::MemberProxy> operator[]( + const TVariant& key) const { + return {*this, detail::adaptString(key.template as())}; } FORCE_INLINE void remove(iterator it) const { detail::ObjectData::remove(data_, it.iterator_, resources_); } - template - FORCE_INLINE void remove(const TString& key) const { + template ::value, int> = 0> + void remove(const TString& key) const { detail::ObjectData::removeMember(data_, detail::adaptString(key), resources_); } + template ::value, int> = 0> + void remove(const TVariant& key) const { + if (key.template is()) + remove(key.template as()); + } template FORCE_INLINE void remove(TChar* key) const { detail::ObjectData::removeMember(data_, detail::adaptString(key), resources_); } - template - typename detail::enable_if::value, bool>::type - containsKey(const TString& key) const { + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + bool containsKey(const TString& key) const { return detail::ObjectData::getMember(data_, detail::adaptString(key), resources_) != 0; } - template - typename detail::enable_if::value, bool>::type - containsKey(TChar* key) const { + template ::value && + !detail::is_const::value, + int> = 0> + ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") + bool containsKey(TChar* key) const { return detail::ObjectData::getMember(data_, detail::adaptString(key), resources_) != 0; } + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use obj[key].is() instead") + bool containsKey(const TVariant& key) const { + return containsKey(key.template as()); + } template ARDUINOJSON_DEPRECATED("use obj[key].to() instead") JsonArray createNestedArray(TChar* key) const { @@ -3763,16 +4002,15 @@ class JsonDocument : public detail::VariantOperators { : JsonDocument(detail::DefaultAllocator::instance()) { swap(*this, src); } - template + template ::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value, + int> = 0> JsonDocument(const T& src, - Allocator* alloc = detail::DefaultAllocator::instance(), - typename detail::enable_if< - detail::is_same::value || - detail::is_same::value || - detail::is_same::value || - detail::is_same::value || - detail::is_same::value || - detail::is_same::value>::type* = 0) + Allocator* alloc = detail::DefaultAllocator::instance()) : JsonDocument(alloc) { set(src); } @@ -3826,10 +4064,15 @@ class JsonDocument : public detail::VariantOperators { bool set(const JsonDocument& src) { return to().set(src.as()); } - template - typename detail::enable_if::value, - bool>::type - set(const T& src) { + template < + typename T, + detail::enable_if_t::value, int> = 0> + bool set(const T& src) { + return to().set(src); + } + template ::value, int> = 0> + bool set(TChar* src) { return to().set(src); } template @@ -3838,78 +4081,114 @@ class JsonDocument : public detail::VariantOperators { return getVariant().template to(); } template + ARDUINOJSON_DEPRECATED("use doc[\"key\"].is() instead") bool containsKey(TChar* key) const { return data_.getMember(detail::adaptString(key), &resources_) != 0; } - template + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use doc[key].is() instead") bool containsKey(const TString& key) const { return data_.getMember(detail::adaptString(key), &resources_) != 0; } - template - typename detail::enable_if::value, - detail::MemberProxy>::type - operator[](const TString& key) { - return {*this, key}; + template ::value, int> = 0> + ARDUINOJSON_DEPRECATED("use doc[key].is() instead") + bool containsKey(const TVariant& key) const { + return containsKey(key.template as()); } - template - typename detail::enable_if::value, - detail::MemberProxy>::type - operator[](TChar* key) { - return {*this, key}; + template ::value, int> = 0> + detail::MemberProxy> operator[]( + const TString& key) { + return {*this, detail::adaptString(key)}; } - template - typename detail::enable_if::value, - JsonVariantConst>::type - operator[](const TString& key) const { + template ::value && + !detail::is_const::value, + int> = 0> + detail::MemberProxy> operator[]( + TChar* key) { + return {*this, detail::adaptString(key)}; + } + template ::value, int> = 0> + JsonVariantConst operator[](const TString& key) const { return JsonVariantConst( data_.getMember(detail::adaptString(key), &resources_), &resources_); } - template - typename detail::enable_if::value, - JsonVariantConst>::type - operator[](TChar* key) const { + template ::value && + !detail::is_const::value, + int> = 0> + JsonVariantConst operator[](TChar* key) const { return JsonVariantConst( data_.getMember(detail::adaptString(key), &resources_), &resources_); } - detail::ElementProxy operator[](size_t index) { - return {*this, index}; + template ::value, int> = 0> + detail::ElementProxy operator[](T index) { + return {*this, size_t(index)}; } JsonVariantConst operator[](size_t index) const { return JsonVariantConst(data_.getElement(index, &resources_), &resources_); } - template - typename detail::enable_if::value, T>::type - add() { + template ::value, int> = 0> + JsonVariantConst operator[](const TVariant& key) const { + if (key.template is()) + return operator[](key.template as()); + if (key.template is()) + return operator[](key.template as()); + return {}; + } + template ::value, int> = 0> + T add() { return add().to(); } - template - typename detail::enable_if::value, T>::type - add() { + template ::value, int> = 0> + JsonVariant add() { return JsonVariant(data_.addElement(&resources_), &resources_); } template bool add(const TValue& value) { - return add().set(value); + return data_.addValue(value, &resources_); } - template + template ::value, int> = 0> bool add(TChar* value) { - return add().set(value); + return data_.addValue(value, &resources_); } - void remove(size_t index) { - detail::VariantData::removeElement(getData(), index, getResourceManager()); - } - template - typename detail::enable_if::value>::type remove( - TChar* key) { + template ::value, int> = 0> + void remove(T index) { + detail::VariantData::removeElement(getData(), size_t(index), + getResourceManager()); + } + template ::value && + !detail::is_const::value, + int> = 0> + void remove(TChar* key) { detail::VariantData::removeMember(getData(), detail::adaptString(key), getResourceManager()); } - template - typename detail::enable_if::value>::type remove( - const TString& key) { + template ::value, int> = 0> + void remove(const TString& key) { detail::VariantData::removeMember(getData(), detail::adaptString(key), getResourceManager()); } + template ::value, int> = 0> + void remove(const TVariant& key) { + if (key.template is()) + remove(key.template as()); + if (key.template is()) + remove(key.template as()); + } operator JsonVariant() { return getVariant(); } @@ -3985,7 +4264,7 @@ ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template struct VariantDataVisitor { - typedef TResult result_type; + using result_type = TResult; template TResult visit(const T&) { return TResult(); @@ -3993,7 +4272,7 @@ struct VariantDataVisitor { }; template struct JsonVariantVisitor { - typedef TResult result_type; + using result_type = TResult; template TResult visit(const T&) { return TResult(); @@ -4027,14 +4306,13 @@ typename TVisitor::result_type accept(JsonVariantConst variant, return visit.visit(nullptr); auto resources = VariantAttorney::getResourceManager(variant); VisitorAdapter adapter(visit, resources); - return data->accept(adapter); + return data->accept(adapter, resources); } struct ComparerBase : JsonVariantVisitor {}; template struct Comparer; template -struct Comparer::value>::type> - : ComparerBase { +struct Comparer::value>> : ComparerBase { T rhs; // TODO: store adapted string? explicit Comparer(T value) : rhs(value) {} CompareResult visit(JsonString lhs) { @@ -4055,24 +4333,23 @@ struct Comparer::value>::type> using ComparerBase::visit; }; template -struct Comparer::value || - is_floating_point::value>::type> +struct Comparer< + T, enable_if_t::value || is_floating_point::value>> : ComparerBase { T rhs; explicit Comparer(T value) : rhs(value) {} - CompareResult visit(JsonFloat lhs) { - return arithmeticCompare(lhs, rhs); - } - CompareResult visit(JsonInteger lhs) { - return arithmeticCompare(lhs, rhs); - } - CompareResult visit(JsonUInt lhs) { + template + enable_if_t::value || is_integral::value, + CompareResult> + visit(const U& lhs) { return arithmeticCompare(lhs, rhs); } - CompareResult visit(bool lhs) { - return visit(static_cast(lhs)); + template + enable_if_t::value && !is_integral::value, + CompareResult> + visit(const U& lhs) { + return ComparerBase::visit(lhs); } - using ComparerBase::visit; }; struct NullComparer : ComparerBase { CompareResult visit(nullptr_t) { @@ -4175,8 +4452,8 @@ struct VariantComparer : ComparerBase { } }; template -struct Comparer::value>::type> +struct Comparer< + T, enable_if_t::value>> : VariantComparer { explicit Comparer(const T& value) : VariantComparer(static_cast(value)) {} @@ -4195,6 +4472,13 @@ inline ArrayData::iterator ArrayData::at( } return it; } +inline VariantData* ArrayData::addElement(ResourceManager* resources) { + auto slot = resources->allocVariant(); + if (!slot) + return nullptr; + CollectionData::appendOne(slot, resources); + return slot.ptr(); +} inline VariantData* ArrayData::getOrAddElement(size_t index, ResourceManager* resources) { auto it = createIterator(resources); @@ -4220,23 +4504,39 @@ inline VariantData* ArrayData::getElement( inline void ArrayData::removeElement(size_t index, ResourceManager* resources) { remove(at(index, resources), resources); } +template +inline bool ArrayData::addValue(const T& value, ResourceManager* resources) { + ARDUINOJSON_ASSERT(resources != nullptr); + auto slot = resources->allocVariant(); + if (!slot) + return false; + JsonVariant variant(slot.ptr(), resources); + if (!variant.set(value)) { + resources->freeVariant(slot); + return false; + } + CollectionData::appendOne(slot, resources); + return true; +} +constexpr size_t sizeofArray(size_t n) { + return n * ResourceManager::slotSize; +} ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE -template -inline typename detail::enable_if::value, bool>::type -copyArray(const T& src, JsonVariant dst) { +template ::value, int> = 0> +inline bool copyArray(const T& src, JsonVariant dst) { return dst.set(src); } -template -inline typename detail::enable_if< - !detail::is_base_of::value, bool>::type -copyArray(T (&src)[N], const TDestination& dst) { +template ::value, int> = 0> +inline bool copyArray(T (&src)[N], const TDestination& dst) { return copyArray(src, N, dst); } -template -inline typename detail::enable_if< - !detail::is_base_of::value, bool>::type -copyArray(const T* src, size_t len, const TDestination& dst) { +template ::value, int> = 0> +inline bool copyArray(const T* src, size_t len, const TDestination& dst) { bool ok = true; for (size_t i = 0; i < len; i++) { ok &= copyArray(src[i], dst.template add()); @@ -4255,9 +4555,8 @@ template inline bool copyArray(const T* src, size_t len, JsonDocument& dst) { return copyArray(src, len, dst.to()); } -template -inline typename detail::enable_if::value, size_t>::type -copyArray(JsonVariantConst src, T& dst) { +template ::value, int> = 0> +inline size_t copyArray(JsonVariantConst src, T& dst) { dst = src.as(); return 1; } @@ -4283,12 +4582,12 @@ inline size_t copyArray(JsonVariantConst src, char (&dst)[N]) { dst[len] = 0; return 1; } -template -inline typename detail::enable_if< - detail::is_array::value && - detail::is_base_of::value, - size_t>::type -copyArray(const TSource& src, T& dst) { +template < + typename TSource, typename T, + detail::enable_if_t::value && + detail::is_base_of::value, + int> = 0> +inline size_t copyArray(const TSource& src, T& dst) { return copyArray(src.template as(), dst); } ARDUINOJSON_END_PUBLIC_NAMESPACE @@ -4329,75 +4628,70 @@ inline T* addPadding(T* p) { size_t address = addPadding(reinterpret_cast(p)); return reinterpret_cast(address); } -inline CollectionIterator::CollectionIterator(VariantSlot* slot, SlotId slotId) +inline CollectionIterator::CollectionIterator(VariantData* slot, SlotId slotId) : slot_(slot), currentId_(slotId) { nextId_ = slot_ ? slot_->next() : NULL_SLOT; } -inline const char* CollectionIterator::key() const { - ARDUINOJSON_ASSERT(slot_ != nullptr); - return slot_->key(); -} -inline void CollectionIterator::setKey(const char* s) { - ARDUINOJSON_ASSERT(slot_ != nullptr); - ARDUINOJSON_ASSERT(s != nullptr); - return slot_->setKey(s); -} -inline void CollectionIterator::setKey(StringNode* s) { - ARDUINOJSON_ASSERT(slot_ != nullptr); - ARDUINOJSON_ASSERT(s != nullptr); - return slot_->setKey(s); -} -inline bool CollectionIterator::ownsKey() const { - ARDUINOJSON_ASSERT(slot_ != nullptr); - return slot_->ownsKey(); -} inline void CollectionIterator::next(const ResourceManager* resources) { ARDUINOJSON_ASSERT(currentId_ != NULL_SLOT); - slot_ = resources->getSlot(nextId_); + slot_ = resources->getVariant(nextId_); currentId_ = nextId_; if (slot_) nextId_ = slot_->next(); } -inline CollectionData::iterator CollectionData::addSlot( - ResourceManager* resources) { - auto slot = resources->allocSlot(); - if (!slot) - return {}; +inline CollectionData::iterator CollectionData::createIterator( + const ResourceManager* resources) const { + return iterator(resources->getVariant(head_), head_); +} +inline void CollectionData::appendOne(Slot slot, + const ResourceManager* resources) { if (tail_ != NULL_SLOT) { - auto tail = resources->getSlot(tail_); + auto tail = resources->getVariant(tail_); tail->setNext(slot.id()); tail_ = slot.id(); } else { head_ = slot.id(); tail_ = slot.id(); } - return iterator(slot, slot.id()); +} +inline void CollectionData::appendPair(Slot key, + Slot value, + const ResourceManager* resources) { + key->setNext(value.id()); + if (tail_ != NULL_SLOT) { + auto tail = resources->getVariant(tail_); + tail->setNext(key.id()); + tail_ = value.id(); + } else { + head_ = key.id(); + tail_ = value.id(); + } } inline void CollectionData::clear(ResourceManager* resources) { auto next = head_; while (next != NULL_SLOT) { auto currId = next; - auto slot = resources->getSlot(next); + auto slot = resources->getVariant(next); next = slot->next(); - releaseSlot(SlotWithId(slot, currId), resources); + resources->freeVariant({slot, currId}); } head_ = NULL_SLOT; tail_ = NULL_SLOT; } -inline SlotWithId CollectionData::getPreviousSlot( - VariantSlot* target, const ResourceManager* resources) const { - auto prev = SlotWithId(); +inline Slot CollectionData::getPreviousSlot( + VariantData* target, const ResourceManager* resources) const { + auto prev = Slot(); auto currentId = head_; while (currentId != NULL_SLOT) { - auto currentSlot = resources->getSlot(currentId); + auto currentSlot = resources->getVariant(currentId); if (currentSlot == target) - return prev; - prev = SlotWithId(currentSlot, currentId); + break; + prev = Slot(currentSlot, currentId); currentId = currentSlot->next(); } - return SlotWithId(); + return prev; } -inline void CollectionData::remove(iterator it, ResourceManager* resources) { +inline void CollectionData::removeOne(iterator it, ResourceManager* resources) { if (it.done()) return; auto curr = it.slot_; @@ -4409,7 +4703,18 @@ inline void CollectionData::remove(iterator it, ResourceManager* resources) { head_ = next; if (next == NULL_SLOT) tail_ = prev.id(); - releaseSlot({it.slot_, it.currentId_}, resources); + resources->freeVariant({it.slot_, it.currentId_}); +} +inline void CollectionData::removePair(ObjectData::iterator it, + ResourceManager* resources) { + if (it.done()) + return; + auto keySlot = it.slot_; + auto valueId = it.nextId_; + auto valueSlot = resources->getVariant(valueId); + keySlot->setNext(valueSlot->next()); + resources->freeVariant({valueSlot, valueId}); + removeOne(it, resources); } inline size_t CollectionData::nesting(const ResourceManager* resources) const { size_t maxChildNesting = 0; @@ -4426,82 +4731,53 @@ inline size_t CollectionData::size(const ResourceManager* resources) const { count++; return count; } -inline void CollectionData::releaseSlot(SlotWithId slot, - ResourceManager* resources) { - if (slot->ownsKey()) - resources->dereferenceString(slot->key()); - slot->data()->setNull(resources); - resources->freeSlot(slot); -} -inline void VariantPool::create(SlotCount cap, Allocator* allocator) { - ARDUINOJSON_ASSERT(cap > 0); - slots_ = - reinterpret_cast(allocator->allocate(slotsToBytes(cap))); - capacity_ = slots_ ? cap : 0; - usage_ = 0; -} -inline void VariantPool::destroy(Allocator* allocator) { - if (slots_) - allocator->deallocate(slots_); - slots_ = nullptr; - capacity_ = 0; - usage_ = 0; -} -inline void VariantPool::shrinkToFit(Allocator* allocator) { - auto newSlots = reinterpret_cast( - allocator->reallocate(slots_, slotsToBytes(usage_))); - if (newSlots) { - slots_ = newSlots; - capacity_ = usage_; - } -} -inline SlotWithId VariantPool::allocSlot() { - if (!slots_) - return {}; - if (usage_ >= capacity_) +inline Slot ResourceManager::allocVariant() { + auto p = variantPools_.allocSlot(allocator_); + if (!p) { + overflowed_ = true; return {}; - auto index = usage_++; - auto slot = &slots_[index]; - return {new (slot) VariantSlot, SlotId(index)}; -} -inline VariantSlot* VariantPool::getSlot(SlotId id) const { - ARDUINOJSON_ASSERT(id < usage_); - return &slots_[id]; -} -inline SlotCount VariantPool::usage() const { - return usage_; + } + return {new (&p->variant) VariantData, p.id()}; } -inline void VariantPool::clear() { - usage_ = 0; +inline void ResourceManager::freeVariant(Slot variant) { + variant->clear(this); + variantPools_.freeSlot({alias_cast(variant.ptr()), variant.id()}); } -inline SlotCount VariantPool::bytesToSlots(size_t n) { - return static_cast(n / sizeof(VariantSlot)); +inline VariantData* ResourceManager::getVariant(SlotId id) const { + return reinterpret_cast(variantPools_.getSlot(id)); } -inline size_t VariantPool::slotsToBytes(SlotCount n) { - return n * sizeof(VariantSlot); +#if ARDUINOJSON_USE_EXTENSIONS +inline Slot ResourceManager::allocExtension() { + auto p = variantPools_.allocSlot(allocator_); + if (!p) { + overflowed_ = true; + return {}; + } + return {&p->extension, p.id()}; } -inline SlotWithId VariantPoolList::allocFromFreeList() { - ARDUINOJSON_ASSERT(freeList_ != NULL_SLOT); - auto id = freeList_; - auto slot = getSlot(freeList_); - freeList_ = slot->next(); - return {new (slot) VariantSlot, id}; +inline void ResourceManager::freeExtension(SlotId id) { + auto p = getExtension(id); + variantPools_.freeSlot({reinterpret_cast(p), id}); } -inline void VariantPoolList::freeSlot(SlotWithId slot) { - slot->setNext(freeList_); - freeList_ = slot.id(); +inline VariantExtension* ResourceManager::getExtension(SlotId id) const { + return &variantPools_.getSlot(id)->extension; } +#endif template inline VariantData* ObjectData::getMember( TAdaptedString key, const ResourceManager* resources) const { - return findKey(key, resources).data(); + auto it = findKey(key, resources); + if (it.done()) + return nullptr; + it.next(resources); + return it.data(); } template VariantData* ObjectData::getOrAddMember(TAdaptedString key, ResourceManager* resources) { - auto it = findKey(key, resources); - if (!it.done()) - return it.data(); + auto data = getMember(key, resources); + if (data) + return data; return addMember(key, resources); } template @@ -4509,9 +4785,11 @@ inline ObjectData::iterator ObjectData::findKey( TAdaptedString key, const ResourceManager* resources) const { if (key.isNull()) return iterator(); + bool isKey = true; for (auto it = createIterator(resources); !it.done(); it.next(resources)) { - if (stringEquals(key, adaptString(it.key()))) + if (isKey && stringEquals(key, adaptString(it->asString()))) return it; + isKey = !isKey; } return iterator(); } @@ -4520,6 +4798,23 @@ inline void ObjectData::removeMember(TAdaptedString key, ResourceManager* resources) { remove(findKey(key, resources), resources); } +template +inline VariantData* ObjectData::addMember(TAdaptedString key, + ResourceManager* resources) { + auto keySlot = resources->allocVariant(); + if (!keySlot) + return nullptr; + auto valueSlot = resources->allocVariant(); + if (!valueSlot) + return nullptr; + if (!keySlot->setString(key, resources)) + return nullptr; + CollectionData::appendPair(keySlot, valueSlot, resources); + return valueSlot.ptr(); +} +constexpr size_t sizeofObject(size_t n) { + return 2 * n * ResourceManager::slotSize; +} class EscapeSequence { public: static char escapeChar(char c) { @@ -4540,68 +4835,72 @@ class EscapeSequence { } } private: - static const char* escapeTable(bool excludeSolidus) { - return &"//\"\"\\\\b\bf\fn\nr\rt\t"[excludeSolidus ? 2 : 0]; + static const char* escapeTable(bool isSerializing) { + return &"//''\"\"\\\\b\bf\fn\nr\rt\t"[isSerializing ? 4 : 0]; } }; -template struct FloatParts { uint32_t integral; uint32_t decimal; int16_t exponent; int8_t decimalPlaces; - FloatParts(TFloat value) { - uint32_t maxDecimalPart = sizeof(TFloat) >= 8 ? 1000000000 : 1000000; - decimalPlaces = sizeof(TFloat) >= 8 ? 9 : 6; - exponent = normalize(value); - integral = uint32_t(value); - for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) { - maxDecimalPart /= 10; - decimalPlaces--; - } - TFloat remainder = (value - TFloat(integral)) * TFloat(maxDecimalPart); - decimal = uint32_t(remainder); - remainder = remainder - TFloat(decimal); - decimal += uint32_t(remainder * 2); - if (decimal >= maxDecimalPart) { - decimal = 0; - integral++; - if (exponent && integral >= 10) { - exponent++; - integral = 1; +}; +template +inline int16_t normalize(TFloat& value) { + using traits = FloatTraits; + int16_t powersOf10 = 0; + int8_t index = sizeof(TFloat) == 8 ? 8 : 5; + int bit = 1 << index; + if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) { + for (; index >= 0; index--) { + if (value >= traits::positiveBinaryPowersOfTen()[index]) { + value *= traits::negativeBinaryPowersOfTen()[index]; + powersOf10 = int16_t(powersOf10 + bit); } - } - while (decimal % 10 == 0 && decimalPlaces > 0) { - decimal /= 10; - decimalPlaces--; + bit >>= 1; } } - static int16_t normalize(TFloat& value) { - typedef FloatTraits traits; - int16_t powersOf10 = 0; - int8_t index = sizeof(TFloat) == 8 ? 8 : 5; - int bit = 1 << index; - if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) { - for (; index >= 0; index--) { - if (value >= traits::positiveBinaryPowersOfTen()[index]) { - value *= traits::negativeBinaryPowersOfTen()[index]; - powersOf10 = int16_t(powersOf10 + bit); - } - bit >>= 1; + if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) { + for (; index >= 0; index--) { + if (value < traits::negativeBinaryPowersOfTen()[index] * 10) { + value *= traits::positiveBinaryPowersOfTen()[index]; + powersOf10 = int16_t(powersOf10 - bit); } + bit >>= 1; } - if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) { - for (; index >= 0; index--) { - if (value < traits::negativeBinaryPowersOfTen()[index] * 10) { - value *= traits::positiveBinaryPowersOfTen()[index]; - powersOf10 = int16_t(powersOf10 - bit); - } - bit >>= 1; - } + } + return powersOf10; +} +constexpr uint32_t pow10(int exponent) { + return (exponent == 0) ? 1 : 10 * pow10(exponent - 1); +} +inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) { + uint32_t maxDecimalPart = pow10(decimalPlaces); + int16_t exponent = normalize(value); + uint32_t integral = uint32_t(value); + for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) { + maxDecimalPart /= 10; + decimalPlaces--; + } + JsonFloat remainder = + (value - JsonFloat(integral)) * JsonFloat(maxDecimalPart); + uint32_t decimal = uint32_t(remainder); + remainder = remainder - JsonFloat(decimal); + decimal += uint32_t(remainder * 2); + if (decimal >= maxDecimalPart) { + decimal = 0; + integral++; + if (exponent && integral >= 10) { + exponent++; + integral = 1; } - return powersOf10; } -}; + while (decimal % 10 == 0 && decimalPlaces > 0) { + decimal /= 10; + decimalPlaces--; + } + return {integral, decimal, exponent, decimalPlaces}; +} template class CountingDecorator { public: @@ -4660,6 +4959,9 @@ class TextFormatter { } template void writeFloat(T value) { + writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 6); + } + void writeFloat(JsonFloat value, int8_t decimalPlaces) { if (isnan(value)) return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null"); #if ARDUINOJSON_ENABLE_INFINITY @@ -4677,7 +4979,7 @@ class TextFormatter { value = -value; } #endif - FloatParts parts(value); + auto parts = decomposeFloat(value, decimalPlaces); writeInteger(parts.integral); if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces); @@ -4687,8 +4989,8 @@ class TextFormatter { } } template - typename enable_if::value>::type writeInteger(T value) { - typedef typename make_unsigned::type unsigned_type; + enable_if_t::value> writeInteger(T value) { + using unsigned_type = make_unsigned_t; unsigned_type unsigned_value; if (value < 0) { writeRaw('-'); @@ -4699,7 +5001,7 @@ class TextFormatter { writeInteger(unsigned_value); } template - typename enable_if::value>::type writeInteger(T value) { + enable_if_t::value> writeInteger(T value) { char buffer[22]; char* end = buffer + sizeof(buffer); char* begin = end; @@ -4752,9 +5054,10 @@ class DummyWriter { template