Merge branch 'main' into feature/sungrow-can

This commit is contained in:
Daniel Öster 2025-01-13 09:57:13 +02:00
commit 700dad23e5
250 changed files with 23705 additions and 15734 deletions

View file

@ -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`

View file

@ -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.
@ -54,6 +70,7 @@ 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
@ -62,6 +79,10 @@ jobs:
# 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
@ -70,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.
@ -86,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

View file

@ -0,0 +1,120 @@
# 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_HV_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

View file

@ -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,18 +54,6 @@ jobs:
#- 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
- NISSAN_LEAF_BATTERY
- PYLON_BATTERY
- RJXZS_BMS
@ -57,6 +63,7 @@ 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
@ -67,17 +74,21 @@ jobs:
- BYD_CAN
- BYD_KOSTAL_RS485
- BYD_MODBUS
- BYD_SMA
- FOXESS_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
# 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
@ -87,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.
@ -103,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

View file

@ -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

View file

@ -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.
@ -40,12 +58,14 @@ jobs:
- BYD_CAN
- BYD_KOSTAL_RS485
- BYD_MODBUS
- BYD_SMA
- FOXESS_CAN
- GROWATT_HV_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
@ -53,6 +73,10 @@ jobs:
- SUNGROW_CAN
- SERIAL_LINK_TRANSMITTER
- 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
@ -61,6 +85,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.
@ -77,4 +105,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

3
.gitignore vendored
View file

@ -22,3 +22,6 @@ compile.bat
# Ignore binary files
*.bin
# Ignore secret file
USER_SECRETS.h

View file

@ -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
@ -83,8 +85,8 @@ This code uses the following excellent libraries:
- [eModbus/eModbus](https://github.com/eModbus/eModbus) MIT-License
- [knolleary/pubsubclient](https://github.com/knolleary/pubsubclient) MIT-License
- [mackelec/SerialDataLink](https://github.com/mackelec/SerialDataLink)
- [me-no-dev/AsyncTCP](https://github.com/me-no-dev/AsyncTCP) LGPL-3.0 license
- [me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
- [mathieucarbou/AsyncTCPsock](https://github.com/mathieucarbou/AsyncTCPSock) LGPL-3.0 license
- [mathieucarbou/ESPAsyncWebServer](https://github.com/mathieucarbou/ESPAsyncWebServer) LGPL-3.0 license
- [miwagner/ESP32-Arduino-CAN](https://github.com/miwagner/ESP32-Arduino-CAN/) MIT-License
- [pierremolinaro/acan2515](https://github.com/pierremolinaro/acan2515) MIT-License
- [pierremolinaro/acan2517FD](https://github.com/pierremolinaro/acan2517FD) MIT-License

View file

@ -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 = "8.0.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 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
// 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,389 +310,9 @@ void init_serial() {
while (!Serial) {}
#ifdef DEBUG_VIA_USB
Serial.println("__ OK __");
#endif
#endif // DEBUG_VIA_USB
}
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
}
#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) {
#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
}
}
#ifdef CAN_FD
// Functions
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
int count = 0;
while (canfd.available() && count++ < 16) {
canfd.receive(frame);
CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
rx_frame.DLC = frame.len;
memcpy(rx_frame.data.u8, frame.data, MIN(rx_frame.DLC, 64));
//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) {
@ -773,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,6 +355,12 @@ void update_calculated_values() {
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
#ifdef DOUBLE_BATTERY
/* Calculate active power based on voltage and current for battery 2*/
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
#endif // DOUBLE_BATTERY
if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
*
@ -950,10 +410,6 @@ void update_calculated_values() {
}
#ifdef DOUBLE_BATTERY
/* Calculate active power based on voltage and current*/
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
// Calculate the scaled remaining capacity in Wh
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) {
calc_max_capacity =
@ -969,7 +425,7 @@ void update_calculated_values() {
} 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;
@ -998,65 +454,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
@ -1135,92 +545,3 @@ void check_reset_reason() {
break;
}
}
void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
print_can_frame(*tx_frame, frameDirection(MSG_TX));
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;
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 // 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) {
print_can_frame(*rx_frame, frameDirection(MSG_RX));
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
}
}

View file

@ -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

View file

@ -1,5 +1,6 @@
#include "USER_SETTINGS.h"
#include <string>
#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
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;
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);
IPAddress gateway(192, 168, 10, 1);
IPAddress subnet(255, 255, 255, 0);
#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.

View file

@ -11,6 +11,7 @@
/* 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
@ -29,24 +30,28 @@
//#define RENAULT_TWIZY_BATTERY
//#define RENAULT_ZOE_GEN1_BATTERY
//#define RENAULT_ZOE_GEN2_BATTERY
//#define SONO_BATTERY
//#define SANTA_FE_PHEV_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_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 BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
//#define GROWATT_HV_CAN //Enable this line to emulate a "Growatt High Voltage v1.10 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
@ -54,46 +59,53 @@
//#define SUNGROW_CAN //Enable this line to emulate a "Sungrow SBR064" 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. \
@ -105,9 +117,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
@ -125,19 +134,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;
@ -161,9 +177,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

View file

@ -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
@ -104,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

View file

@ -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 };
@ -195,6 +196,7 @@ static uint8_t BMW_1D0_counter = 0;
static uint8_t BMW_13E_counter = 0;
static uint8_t BMW_380_counter = 0;
static uint32_t BMW_328_counter = 0;
static bool battery_awake = false;
static bool battery2_awake = false;
static bool battery_info_available = false;
@ -204,6 +206,7 @@ static bool CRCCheckPassedPreviously = false;
static bool skipCRCCheck_battery2 = false;
static bool CRCCheckPassedPreviously_battery2 = false;
static uint16_t cellvoltage_temp_mV = 0;
static uint32_t battery_serial_number = 0;
static uint32_t battery_available_power_shortterm_charge = 0;
static uint32_t battery_available_power_shortterm_discharge = 0;
@ -269,9 +272,9 @@ 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 uint16_t cellvoltage2_temp_mV = 0;
static uint32_t battery2_serial_number = 0;
static uint32_t battery2_available_power_shortterm_charge = 0;
static uint32_t battery2_available_power_shortterm_discharge = 0;
@ -337,7 +340,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 +394,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 +427,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 +510,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 +640,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 +653,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)) {
@ -657,8 +665,14 @@ void receive_can_battery(CAN_frame rx_frame) {
switch (cmdState) {
case CELL_VOLTAGE_MINMAX:
if (next_data >= 4) {
datalayer.battery.status.cell_min_voltage_mV = (message_data[0] << 8 | message_data[1]);
datalayer.battery.status.cell_max_voltage_mV = (message_data[2] << 8 | message_data[3]);
cellvoltage_temp_mV = (message_data[0] << 8 | message_data[1]);
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_temp_mV;
}
cellvoltage_temp_mV = (message_data[2] << 8 | message_data[3]);
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_temp_mV;
}
}
break;
case SOH:
@ -681,7 +695,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 +790,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[6] = ((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 +803,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 +824,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)) {
@ -832,8 +852,14 @@ void receive_can_battery2(CAN_frame rx_frame) {
switch (cmdState) {
case CELL_VOLTAGE_MINMAX:
if (next_data >= 4) {
datalayer.battery2.status.cell_min_voltage_mV = (message_data[0] << 8 | message_data[1]);
datalayer.battery2.status.cell_max_voltage_mV = (message_data[2] << 8 | message_data[3]);
cellvoltage2_temp_mV = (message_data[0] << 8 | message_data[1]);
if (cellvoltage2_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery2.status.cell_min_voltage_mV = cellvoltage2_temp_mV;
}
cellvoltage2_temp_mV = (message_data[2] << 8 | message_data[3]);
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery2.status.cell_max_voltage_mV = cellvoltage2_temp_mV;
}
}
break;
case SOH:
@ -856,7 +882,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 +913,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 +931,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 +945,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 +959,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 +999,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 +1040,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;
@ -1040,21 +1066,24 @@ void send_can_battery() {
current_cell_polled++;
if (current_cell_polled > 96) {
datalayer.battery.info.number_of_cells = 97;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = 97;
#endif
cmdState = CELL_VOLTAGE_CELLNO_LAST;
} else {
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 +1096,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 +1123,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 +1163,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

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,146 @@
#ifndef BOLT_AMPERA_BATTERY_H
#define BOLT_AMPERA_BATTERY_H
#include <Arduino.h>
#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

View file

@ -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;

View file

@ -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

View file

@ -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);
*/
}
}

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,308 @@
#include "../include.h"
#ifdef STELLANTIS_ECMP_BATTERY
#include <algorithm> // 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<uint16_t>::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<uint16_t>::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

View file

@ -0,0 +1,12 @@
#ifndef STELLANTIS_ECMP_BATTERY_H
#define STELLANTIS_ECMP_BATTERY_H
#include <Arduino.h>
#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

View file

@ -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) {

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

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

View file

@ -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

View file

@ -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 */
// 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;
#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);
//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);
}
}

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -618,7 +618,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.meb.rt_battery_unathorized = realtime_warning_battery_unathorized;
}
void receive_can_battery(CAN_frame rx_frame) {
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;
@ -994,7 +994,7 @@ void receive_can_battery(CAN_frame rx_frame) {
break;
case 0x1C42007B: // Reply from battery
if (rx_frame.data.u8[0] == 0x10) { //PID header
transmit_can(&MEB_ACK_FRAME, can_config.battery);
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];
@ -1453,7 +1453,7 @@ void receive_can_battery(CAN_frame rx_frame) {
}
}
void send_can_battery() {
void transmit_can_battery() {
unsigned long currentMillis = millis();
// Send 10ms CAN Message
if (currentMillis > last_can_msg_timestamp + 500) {
@ -1474,7 +1474,7 @@ void send_can_battery() {
counter_10ms = (counter_10ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3..
transmit_can(&MEB_0FC, can_config.battery); // Required for contactor closing
transmit_can_frame(&MEB_0FC, can_config.battery); // Required for contactor closing
}
// Send 20ms CAN Message
if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) {
@ -1485,7 +1485,7 @@ void send_can_battery() {
counter_20ms = (counter_20ms + 1) % 16; //Goes from 0-1-2-3...15-0-1-2-3..
transmit_can(&MEB_0FD, can_config.battery); // Required for contactor closing
transmit_can_frame(&MEB_0FD, can_config.battery); // Required for contactor closing
}
// Send 40ms CAN Message
if (currentMillis - previousMillis40ms >= INTERVAL_40_MS) {
@ -1502,7 +1502,7 @@ void send_can_battery() {
}
toggle = !toggle; // Flip the toggle each time the code block is executed
transmit_can(&MEB_040, can_config.battery); // Airbag message - Needed for contactor closing
transmit_can_frame(&MEB_040, can_config.battery); // Airbag message - Needed for contactor closing
}
// Send 50ms CAN Message
if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) {
@ -1518,7 +1518,7 @@ void send_can_battery() {
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(&MEB_0C0, can_config.battery); // Needed for contactor closing
transmit_can_frame(&MEB_0C0, can_config.battery); // Needed for contactor closing
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
@ -1560,11 +1560,11 @@ void send_can_battery() {
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(&MEB_503, can_config.battery);
transmit_can(&MEB_272, can_config.battery);
transmit_can(&MEB_3C0, can_config.battery);
transmit_can(&MEB_3BE, can_config.battery);
transmit_can(&MEB_14C, can_config.battery);
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) {
@ -1574,11 +1574,11 @@ void send_can_battery() {
//TODO: MEB_1B0000B9 & MEB_1B000010 & MEB_1B000046 has CAN sleep commands, static OK?
transmit_can(&MEB_5E1, can_config.battery);
transmit_can(&MEB_153, can_config.battery);
transmit_can(&MEB_1B0000B9, can_config.battery);
transmit_can(&MEB_1B000010, can_config.battery);
transmit_can(&MEB_1B000046, can_config.battery);
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:
@ -2076,7 +2076,7 @@ void send_can_battery() {
break;
}
if (first_can_msg > 0 && currentMillis > first_can_msg + 2000) {
transmit_can(&MEB_POLLING_FRAME, can_config.battery);
transmit_can_frame(&MEB_POLLING_FRAME, can_config.battery);
}
}
@ -2084,11 +2084,11 @@ void send_can_battery() {
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
previousMillis500ms = currentMillis;
transmit_can(&MEB_16A954B4, can_config.battery); //eTM, Cooling valves and pumps for BMS
transmit_can(&MEB_569, can_config.battery); // Battery heating requests
transmit_can(&MEB_1A55552B, can_config.battery); //Climate, heatpump and priorities
transmit_can(&MEB_1A555548, can_config.battery); //ORU, OTA update message for reserving battery
transmit_can(&MEB_16A954FB, can_config.battery); //Climate, request to BMS for starting preconditioning
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
@ -2109,12 +2109,12 @@ void send_can_battery() {
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(&MEB_6B2, can_config.battery); // Diagnostics - Needed for contactor closing
transmit_can(&MEB_641, can_config.battery); // Motor - OBD
transmit_can(&MEB_5F5, can_config.battery); // Loading profile
transmit_can(&MEB_585, can_config.battery); // Systeminfo
transmit_can(&MEB_1A5555A6, can_config.battery); // Temperature QBit
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
}
}

View file

@ -133,6 +133,6 @@
#define PID_CELLVOLTAGE_CELL_108 0x1EAB
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif

View file

@ -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);
}
}

View file

@ -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

View file

@ -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,6 +329,9 @@ 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;
@ -496,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)) {
@ -598,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];
}
if (battery2_group_7bb == 1) //High precision SOC, Current, voltages etc.
transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
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
@ -631,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;
@ -674,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];
@ -738,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)) {
@ -845,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!
@ -860,20 +863,18 @@ void receive_can_battery(CAN_frame rx_frame) {
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
if (group_7bb == 1) //High precision SOC, Current, voltages etc.
//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];
}
transmit_can_frame(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group
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
@ -891,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;
@ -934,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];
@ -991,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();
@ -1029,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) {
@ -1125,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
@ -1152,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;
@ -1176,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...
@ -1190,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
}
@ -1258,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;
@ -1278,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;

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
#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);
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);
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);
}
}

View file

@ -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

View file

@ -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
}

View file

@ -1,6 +1,5 @@
#include "../include.h"
#ifdef RENAULT_ZOE_GEN1_BATTERY
#include <algorithm> // For std::min and std::max
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "RENAULT-ZOE-GEN1-BATTERY.h"
@ -9,20 +8,23 @@
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)
- 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 +79,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<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = 5000; //TODO: Take from CAN
datalayer.battery.status.max_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 +132,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<uint32_t>((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 +194,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];
@ -376,7 +396,7 @@ void receive_can_battery(CAN_frame rx_frame) {
case 0x29:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[30] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[21] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[31] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[32] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
@ -465,7 +485,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 +494,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 +533,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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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,37 +371,280 @@ 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<uint32_t>(
(static_cast<double>(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

View file

@ -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

View file

@ -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);
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);
Serial.println("");
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

View file

@ -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<uint32_t>(
(static_cast<double>(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

View file

@ -0,0 +1,17 @@
#ifndef SONO_BATTERY_H
#define SONO_BATTERY_H
#include <Arduino.h>
#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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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/mathieucarbou-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

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,8 @@ 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;

View file

@ -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,7 +226,12 @@ 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;
@ -195,10 +247,20 @@ typedef struct {
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_full_charge_complete = 0;
uint8_t battery_fully_charged = 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;
@ -212,9 +274,131 @@ typedef struct {
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;
@ -350,6 +534,36 @@ typedef struct {
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*/
@ -398,13 +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;
};

View file

@ -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

View file

@ -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

View file

@ -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__

View file

@ -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

View file

@ -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__

View file

@ -2,8 +2,10 @@
#include <Arduino.h>
#include <WiFi.h>
#include <freertos/FreeRTOS.h>
#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<EventData> 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

View file

@ -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];

View file

@ -8,6 +8,7 @@ static bool battery_full_event_fired = false;
static bool battery_empty_event_fired = false;
#define MAX_SOH_DEVIATION_PPTT 2500
#define CELL_CRITICAL_MV 100 // If cells go this much outside design voltage, shut battery down!
//battery pause status begin
bool emulator_pause_request_ON = false;
@ -54,13 +55,24 @@ void update_machineryprotection() {
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
}
// Cell overvoltage, critical latching error without automatic reset. Requires user action.
// Cell overvoltage, further charging not possible. Battery might be imbalanced.
if (datalayer.battery.status.cell_max_voltage_mV >= datalayer.battery.info.max_cell_voltage_mV) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
datalayer.battery.status.max_charge_power_W = 0;
}
// Cell undervoltage, critical latching error without automatic reset. Requires user action.
// Cell CRITICAL overvoltage, critical latching error without automatic reset. Requires user action to inspect battery.
if (datalayer.battery.status.cell_max_voltage_mV >= (datalayer.battery.info.max_cell_voltage_mV + CELL_CRITICAL_MV)) {
set_event(EVENT_CELL_CRITICAL_OVER_VOLTAGE, 0);
}
// Cell undervoltage. Further discharge not possible. Battery might be imbalanced.
if (datalayer.battery.status.cell_min_voltage_mV <= datalayer.battery.info.min_cell_voltage_mV) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
datalayer.battery.status.max_discharge_power_W = 0;
}
//Cell CRITICAL undervoltage. critical latching error without automatic reset. Requires user action to inspect battery.
if (datalayer.battery.status.cell_min_voltage_mV <= (datalayer.battery.info.min_cell_voltage_mV - CELL_CRITICAL_MV)) {
set_event(EVENT_CELL_CRITICAL_UNDER_VOLTAGE, 0);
}
// Battery is fully charged. Dont allow any more power into it
@ -111,7 +123,8 @@ void update_machineryprotection() {
#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 +250,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 +315,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) {

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,32 @@
#ifndef SDCARD_H
#define SDCARD_H
#include <SD_MMC.h>
#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

View file

@ -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,8 +130,8 @@ 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;
@ -159,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;
@ -181,8 +173,10 @@ void init_events(void) {
events.entries[EVENT_INTERFACE_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_MODBUS_INVERTER_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_ERROR_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CELL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_OVER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_CRITICAL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_CRITICAL_OVER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_UNDER_VOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CELL_OVER_VOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CELL_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_UNKNOWN_EVENT_SET].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_OTA_UPDATE].level = EVENT_LEVEL_UPDATE;
@ -191,6 +185,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;
@ -263,9 +258,9 @@ 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.";
@ -280,84 +275,92 @@ 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_CRITICAL_UNDER_VOLTAGE:
return "CELL VOLTAGE CRITICALLY LOW! Not possible to continue. Inspect battery!";
case EVENT_CELL_UNDER_VOLTAGE:
return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
return "Cell undervoltage. Further discharge not possible. Check balancing of cells";
case EVENT_CELL_OVER_VOLTAGE:
return "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
return "Cell overvoltage. Further charging not possible. Check balancing of cells";
case EVENT_CELL_CRITICAL_OVER_VOLTAGE:
return "CELL VOLTAGE CRITICALLY HIGH! Not possible to continue. Inspect battery!";
case EVENT_CELL_DEVIATION_HIGH:
return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!";
return "Large cell voltage deviation! Check balancing of cells";
case EVENT_UNKNOWN_EVENT_SET:
return "An unknown event was set! Review your code!";
case EVENT_DUMMY_INFO:
@ -368,6 +371,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:
@ -381,54 +386,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 "";
}
@ -468,6 +473,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
@ -481,10 +490,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) {
@ -558,8 +563,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;
@ -568,8 +573,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;
}
@ -585,28 +590,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);
}
}

Some files were not shown because too many files have changed in this diff Show more