mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 09:49:32 +02:00
Compare commits
176 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f3a6157080 | ||
![]() |
867512eecd | ||
![]() |
f9109d0348 | ||
![]() |
9943406836 | ||
![]() |
18ee9d6c27 | ||
![]() |
31ea1f0928 | ||
![]() |
dad97b36e1 | ||
![]() |
19a1634f4e | ||
![]() |
aa375dc36a | ||
![]() |
a91f8ab4d4 | ||
![]() |
7fbc9ffcc6 | ||
![]() |
579e0e0bcc | ||
![]() |
b26c451eaf | ||
![]() |
ee9e78e80b | ||
![]() |
706b4a7cea | ||
![]() |
f5ba607fa6 | ||
![]() |
572867b7ad | ||
![]() |
47e715ffe5 | ||
![]() |
c445bd1869 | ||
![]() |
28c0267cae | ||
![]() |
4058050423 | ||
![]() |
04d9a36292 | ||
![]() |
29129037b0 | ||
![]() |
c6b7ff82c0 | ||
![]() |
95ee6ff9ae | ||
![]() |
8cc10a1b71 | ||
![]() |
3ef3279527 | ||
![]() |
f48b4235c1 | ||
![]() |
cdf6314bff | ||
![]() |
48411680c6 | ||
![]() |
132029169d | ||
![]() |
483d4300b1 | ||
![]() |
397e8d03a1 | ||
![]() |
d336b75f56 | ||
![]() |
a7af7cf938 | ||
![]() |
2ba937bd32 | ||
![]() |
48d416b5c4 | ||
![]() |
ed2ebb00fd | ||
![]() |
0458267634 | ||
![]() |
a0de6b092b | ||
![]() |
0f2cbe8f04 | ||
![]() |
983105ab6a | ||
![]() |
5aa657e0b5 | ||
![]() |
69ae0385fb | ||
![]() |
a8d74f5885 | ||
![]() |
20b6ea5e85 | ||
![]() |
b8bbb02b20 | ||
![]() |
4896f3061a | ||
![]() |
0aaab6d4b7 | ||
![]() |
a12ea4b112 | ||
![]() |
e023962bfc | ||
![]() |
4df42e10b4 | ||
![]() |
ee4981b6e1 | ||
![]() |
172e260167 | ||
![]() |
fd52c0e5e7 | ||
![]() |
6982476e89 | ||
![]() |
24c1ce73ae | ||
![]() |
d8530a2b8b | ||
![]() |
7a5cd36e6d | ||
![]() |
3bfc350b65 | ||
![]() |
b52bc7bfd4 | ||
![]() |
89abc6a964 | ||
![]() |
45643237e4 | ||
![]() |
e66161176b | ||
![]() |
269c655dc5 | ||
![]() |
15143d1384 | ||
![]() |
0244468624 | ||
![]() |
3ead4d12d4 | ||
![]() |
5277665dd1 | ||
![]() |
79964a0601 | ||
![]() |
e11843e4a0 | ||
![]() |
df52d067e7 | ||
![]() |
1ea663c0b9 | ||
![]() |
73c6821a9a | ||
![]() |
26126bae1a | ||
![]() |
980e450871 | ||
![]() |
c7bb82c1de | ||
![]() |
a287d0e208 | ||
![]() |
50d794d0de | ||
![]() |
332e982bed | ||
![]() |
9ee0dffb33 | ||
![]() |
6094a2c85b | ||
![]() |
b5d14edb94 | ||
![]() |
00a820f007 | ||
![]() |
34466de3a7 | ||
![]() |
bf14553d77 | ||
![]() |
bc2e49fb5d | ||
![]() |
fdc1fb61ba | ||
![]() |
2546b6da21 | ||
![]() |
7178e0376e | ||
![]() |
5510d3aeb5 | ||
![]() |
9554cbf808 | ||
![]() |
082c005a20 | ||
![]() |
5b7491c7a7 | ||
![]() |
018cd4ed52 | ||
![]() |
0aad11d9bc | ||
![]() |
29e6f52c4c | ||
![]() |
25393106b8 | ||
![]() |
fd2e5f2e52 | ||
![]() |
b33f42c9c5 | ||
![]() |
5246cd34c9 | ||
![]() |
a336ac73fb | ||
![]() |
a2a00a488f | ||
![]() |
c7b6d0adee | ||
![]() |
9fe83fb131 | ||
![]() |
62d0d59e74 | ||
![]() |
2e859a1ee6 | ||
![]() |
9b6f5409c2 | ||
![]() |
57c65e2eeb | ||
![]() |
8ba6a04d61 | ||
![]() |
47ceaf9a7b | ||
![]() |
521ab481d7 | ||
![]() |
ac153ec901 | ||
![]() |
fc109cd954 | ||
![]() |
5f73a4c32b | ||
![]() |
f609427e00 | ||
![]() |
edb69472c3 | ||
![]() |
d8d64ee16c | ||
![]() |
db9537a34b | ||
![]() |
577a353285 | ||
![]() |
42353efdff | ||
![]() |
454a4565c0 | ||
![]() |
bf7d10c825 | ||
![]() |
5ae8155866 | ||
![]() |
8354e554e4 | ||
![]() |
eb44ea7f42 | ||
![]() |
96b3293023 | ||
![]() |
69b0d23b45 | ||
![]() |
d481947e34 | ||
![]() |
b5ebcdbcd7 | ||
![]() |
c6ad1f8afe | ||
![]() |
354926a6b3 | ||
![]() |
3767c698bd | ||
![]() |
b115abf081 | ||
![]() |
036f9aa4b9 | ||
![]() |
5555b38fea | ||
![]() |
7bbbf846d8 | ||
![]() |
5b277d633b | ||
![]() |
5bb12915fe | ||
![]() |
93b30241da | ||
![]() |
30a7b9d90e | ||
![]() |
e02cb61efc | ||
![]() |
c01291d15f | ||
![]() |
3c4880e783 | ||
![]() |
3172c32cc7 | ||
![]() |
7d8accb95d | ||
![]() |
fd9d1ec714 | ||
![]() |
7f8f48756d | ||
![]() |
b205b63af3 | ||
![]() |
64a3d5f5aa | ||
![]() |
a40de5ecee | ||
![]() |
99b79eab7e | ||
![]() |
9662054a4f | ||
![]() |
29aecc577b | ||
![]() |
cfdc1a3130 | ||
![]() |
b40387817d | ||
![]() |
7903805373 | ||
![]() |
282b27ff5d | ||
![]() |
1b012fb2d0 | ||
![]() |
54f9cb49f3 | ||
![]() |
b17c9ad106 | ||
![]() |
3764ab4bf9 | ||
![]() |
c1f3187306 | ||
![]() |
a8532b6f78 | ||
![]() |
a9185be603 | ||
![]() |
91bbb25270 | ||
![]() |
2ef8055535 | ||
![]() |
8fe21fddb7 | ||
![]() |
5770df46e4 | ||
![]() |
bd0923cab3 | ||
![]() |
3597a99a08 | ||
![]() |
805d506062 | ||
![]() |
e38b6286c7 | ||
![]() |
805b3e10d2 | ||
![]() |
08b6c81e67 | ||
![]() |
0617bf4b8c |
93 changed files with 2170 additions and 1058 deletions
|
@ -38,7 +38,7 @@ jobs:
|
||||||
~/.platformio/.cache
|
~/.platformio/.cache
|
||||||
key: ${{ runner.os }}-pio
|
key: ${{ runner.os }}-pio
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
- name: Install PlatformIO Core
|
- name: Install PlatformIO Core
|
||||||
|
|
|
@ -38,7 +38,7 @@ jobs:
|
||||||
~/.platformio/.cache
|
~/.platformio/.cache
|
||||||
key: ${{ runner.os }}-pio
|
key: ${{ runner.os }}-pio
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
- name: Install PlatformIO Core
|
- name: Install PlatformIO Core
|
||||||
|
|
|
@ -38,7 +38,7 @@ jobs:
|
||||||
~/.platformio/.cache
|
~/.platformio/.cache
|
||||||
key: ${{ runner.os }}-pio
|
key: ${{ runner.os }}-pio
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
- name: Install PlatformIO Core
|
- name: Install PlatformIO Core
|
||||||
|
|
8
.github/workflows/release-assets.yml
vendored
8
.github/workflows/release-assets.yml
vendored
|
@ -41,16 +41,12 @@ jobs:
|
||||||
~/.platformio/.cache
|
~/.platformio/.cache
|
||||||
key: ${{ runner.os }}-pio
|
key: ${{ runner.os }}-pio
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
- name: Install PlatformIO Core
|
- name: Install PlatformIO Core
|
||||||
run: pip install --upgrade platformio
|
run: pip install --upgrade platformio
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
- name: 🛠 Build ota image for Lilygo T-CAN
|
- name: 🛠 Build ota image for Lilygo T-CAN
|
||||||
run: |
|
run: |
|
||||||
pio run -e lilygo_330
|
pio run -e lilygo_330
|
||||||
|
@ -68,7 +64,7 @@ jobs:
|
||||||
|
|
||||||
- name: 🛠 Build factory image for Lilygo 2-CAN
|
- name: 🛠 Build factory image for Lilygo 2-CAN
|
||||||
run: |
|
run: |
|
||||||
esptool --chip esp32 merge-bin -o .pio/build/lilygo_2CAN_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 4MB 0x1000 .pio/build/lilygo_2CAN_330/bootloader.bin 0x8000 .pio/build/lilygo_2CAN_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_2CAN_330/firmware.bin
|
esptool --chip esp32s3 merge-bin -o .pio/build/lilygo_2CAN_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 16MB 0x0000 .pio/build/lilygo_2CAN_330/bootloader.bin 0x8000 .pio/build/lilygo_2CAN_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_2CAN_330/firmware.bin
|
||||||
mv .pio/build/lilygo_2CAN_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-2CAN.factory.bin
|
mv .pio/build/lilygo_2CAN_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-2CAN.factory.bin
|
||||||
|
|
||||||
- name: 🛠 Build ota image for Stark
|
- name: 🛠 Build ota image for Stark
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
### Contributing to the Battery-Emulator project
|
### Contributing to the Battery-Emulator project
|
||||||
|
|
||||||
What can I do?
|
What can I do? 🦸
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
**"Help - I want to contribute something, but I don't know what?"**
|
**"Help - I want to contribute something, but I don't know what?"**
|
||||||
|
@ -15,10 +15,66 @@ You're in luck. There's various sources to contribute:
|
||||||
- [Discussion page](https://github.com/dalathegreat/Battery-Emulator/discussions)
|
- [Discussion page](https://github.com/dalathegreat/Battery-Emulator/discussions)
|
||||||
- [Discord server](https://www.patreon.com/dala)
|
- [Discord server](https://www.patreon.com/dala)
|
||||||
|
|
||||||
## Notes on embedded system
|
## Notes on embedded system 🕙
|
||||||
The Battery-Emulator is a real-time control system, which performs lots of time critical operations. Some operations, like contactor control, need to complete within 10 milliseconds periodically. The resources of the ESP32 microcontroller is limited, so keeping track of CPU and memory usage is essential. Keep this in mind when coding for the system! Performance profiling the system can be done by enabling the "Enable performance profiling:" option in the webserver
|
The Battery-Emulator is a real-time control system, which performs lots of time critical operations. Some operations, like contactor control, need to complete within 10 milliseconds periodically. The resources of the ESP32 microcontroller is limited, so keeping track of CPU and memory usage is essential. Keep this in mind when coding for the system! Performance profiling the system can be done by enabling the "Enable performance profiling:" option in the webserver
|
||||||
|
|
||||||
## Code formatting
|
## Setting up the compilation environment (VScode + PlatformIO) 💻
|
||||||
|
|
||||||
|
This project uses the PlatformIO extension within Visual Studio Code for development and uploading. It handles all the complex toolchains and library management for you.
|
||||||
|
### 1. Installing VSCode
|
||||||
|
- Download the stable build of Visual Studio Code for your operating system (Windows, macOS, or Linux) from the official website: https://code.visualstudio.com/
|
||||||
|
- Run the installer and follow the setup instructions.
|
||||||
|
- (Recommended) Launch VSCode after installation.
|
||||||
|
|
||||||
|
### 2. Installing the PlatformIO IDE Plugin
|
||||||
|
|
||||||
|
PlatformIO is an extension that adds all the necessary functionality to VSCode.
|
||||||
|
- Inside VSCode, open the Extensions view by:
|
||||||
|
- Clicking the Extensions icon in the Activity Bar on the left side.
|
||||||
|
- Or using the keyboard shortcut: Ctrl+Shift+X (Windows/Linux) or Cmd+Shift+X (macOS).
|
||||||
|
- In the extensions search bar, type: PlatformIO IDE.
|
||||||
|
- Find the extension published by PlatformIO and click the Install button.
|
||||||
|
- Wait for the installation to complete. This may take a few minutes as PlatformIO downloads and installs its core tools in the background. VSCode might require a reload once finished.
|
||||||
|
|
||||||
|
### 3. Opening the Project and Building
|
||||||
|
- Clone the repository to your local machine using Git.
|
||||||
|
- In VSCode:
|
||||||
|
- Go to File > Open Folder...
|
||||||
|
- Navigate to and select the root folder of the cloned project (the folder containing the platformio.ini file).
|
||||||
|
- Click Open.
|
||||||
|
- PlatformIO will automatically recognize the project structure and begin indexing the code. You'll see the PlatformIO icon (a grey alien) appear in the Activity Bar on the left.
|
||||||
|
|
||||||
|
- To verify everything is set up correctly, build/compile the project:
|
||||||
|
- Click the PlatformIO icon in the Activity Bar to open the PIO Home screen.
|
||||||
|
- Go to Quick Access > PIO > Build.
|
||||||
|
- Alternatively, you can use the checkmark icon in the blue status bar at the bottom of the VSCode window, or the keyboard shortcut Ctrl+Alt+B (Windows/Linux) / Cmd+Alt+B (macOS).
|
||||||
|
- The build process will start. You can monitor the output in the integrated terminal. A successful build will end with ===== [SUCCESS] Took X.XX seconds =====.
|
||||||
|
|
||||||
|
### 4. Uploading Code to Board via USB
|
||||||
|
|
||||||
|
- Connect your Battery-Emulator hardware to your computer using a USB cable.
|
||||||
|
- Select the right board type (Stark, LilyGo)
|
||||||
|
- At the bottom left of VScode, click the Env to bring up a menu of boards. Select the board you are using
|
||||||
|
<img width="396" height="95" alt="image" src="https://github.com/user-attachments/assets/23b73442-5016-4ff1-be78-13e3c41772d5" />
|
||||||
|
<img width="679" height="177" alt="image" src="https://github.com/user-attachments/assets/2fad40a6-f388-4cd6-8e08-844602bb0442" />
|
||||||
|
|
||||||
|
- Ensure the correct upload port is set in the platformio.ini file (it's often auto-detected, but you may need to set it manually. See Troubleshooting below).
|
||||||
|
- Upload the code:
|
||||||
|
- Click the PlatformIO icon in the Activity Bar.
|
||||||
|
- Go to Quick Access > PIO > Upload.
|
||||||
|
- Alternatively, use the right-arrow icon (→) in the blue status bar at the bottom of the VSCode window, or the keyboard shortcut Ctrl+Alt+U (Windows/Linux) / Cmd+Alt+U (macOS).
|
||||||
|
- The upload process will begin. The board may reset automatically. A successful upload will end with ===== [SUCCESS] Took X.XX seconds =====.
|
||||||
|
|
||||||
|
### ⚠️ Troubleshooting & Tips
|
||||||
|
|
||||||
|
#### "Upload port not found" or wrong port errors:
|
||||||
|
|
||||||
|
- Find the correct port:
|
||||||
|
- Windows: Check Device Manager under "Ports (COM & LPT)". It's usually COM3, COM4, etc.
|
||||||
|
- macOS/Linux: Run ls /dev/tty.* or ls /dev/ttyUSB* in a terminal. It's often /dev/tty.usbserial-XXX or /dev/ttyUSB0.
|
||||||
|
- Add the line upload_port = COM4 (replace COM4 with your port) to your platformio.ini file in the [env:...] section.
|
||||||
|
|
||||||
|
## Code formatting 📜
|
||||||
The project enforces a specific code formatting in the workflows. To get your code formatted properly, it is easiest to use a pre-commit hook before pushing the code to a pull request.
|
The project enforces a specific code formatting in the workflows. To get your code formatted properly, it is easiest to use a pre-commit hook before pushing the code to a pull request.
|
||||||
|
|
||||||
Before you begin, make sure you have installed Python on the system!
|
Before you begin, make sure you have installed Python on the system!
|
||||||
|
@ -39,7 +95,7 @@ Or force it to check all files with
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
```
|
```
|
||||||
|
|
||||||
## Local Unit test run
|
## Local Unit test run 🧪
|
||||||
The Unit tests run gtest. Here is how to install this on Debian/Ubuntu and run it locally
|
The Unit tests run gtest. Here is how to install this on Debian/Ubuntu and run it locally
|
||||||
```
|
```
|
||||||
sudo apt-get install libgtest-dev
|
sudo apt-get install libgtest-dev
|
||||||
|
|
54
README.md
54
README.md
|
@ -33,52 +33,18 @@ At the same time, EV manufacturers have been putting high capacity battery packs
|
||||||
|
|
||||||
For examples showing wiring, see each battery type's own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200)
|
For examples showing wiring, see each battery type's 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 💻
|
## How to install the software 💻
|
||||||
|
|
||||||
Start by watching this [quickstart guide](https://www.youtube.com/watch?v=hcl2GdHc0Y0)
|
Start by watching this [quickstart guide](https://www.youtube.com/watch?v=sR3t7j0R9Z0)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=hcl2GdHc0Y0)
|
[](https://www.youtube.com/watch?v=sR3t7j0R9Z0)
|
||||||
|
|
||||||
1. Download the Arduino IDE: https://www.arduino.cc/en/software
|
1. Open the [webinstaller page](https://dalathegreat.github.io/BE-Web-Installer/)
|
||||||
2. Open the Arduino IDE.
|
2. Follow the instructions on that page to install the software
|
||||||
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.
|
3. After successful installation, connect to the wireless network (Battery-Emulator , password: 123456789)
|
||||||
4. Click `Tools` menu -> `Board: "...."` -> `Boards Manager...`, install the `esp32` package by `Espressif Systems` (not `Arduino ESP32 Boards`), then press `Close`.
|
4. Go to setup page and configure component selection
|
||||||
|
5. (OPTIONAL, connect the board to your home Wifi)
|
||||||
**NOTE: The ESP32 version depends on which release of Battery-Emulator you are running! See the [Release Notes](https://github.com/dalathegreat/Battery-Emulator/releases) for more info on which version to use with the current version (Suggested ESP32 version X.Y.Z)**
|
6. Connect your battery and inverter to the board and you are done! 🔋⚡
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
5. The Arduino board should be set to `ESP32 Dev Module` and `Partition Scheme` to `Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)` (under `Tools` -> `Board` -> `ESP32 Arduino`) with the following settings:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
6. Select which battery type you will use, along with other optional settings. This is done in the `USER_SETTINGS.h` file.
|
|
||||||
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
|
|
||||||
|
|
||||||
NOTE: If you see garbled messages on the serial console, change the serial console to match the baud rate to the code, currently 115200.
|
|
||||||
|
|
||||||
NOTE: If you see the error `Sketch too big` then check you set the Partition Scheme above correctly.
|
|
||||||
|
|
||||||
This video explains all the above mentioned steps:
|
|
||||||
<https://youtu.be/_mH2AjnAjDk>
|
|
||||||
|
|
||||||
|
|
||||||
### Linux Development Environment Setup
|
|
||||||
In addition to the steps above, ESP32 requires a dependency for a Python module, pyserial install using the cli.\
|
|
||||||
```python3 -m pip install pyserial```
|
|
||||||
|
|
||||||
If you're using Ubuntu , use apt to manage the dependencies of arduino:\
|
|
||||||
pyserial install: ```sudo apt install python3-serial```
|
|
||||||
|
|
||||||
Arduino AppImage must be set as executable after downloading to run correctly\
|
|
||||||
example: ```chmod 775 arduino-ide_2.3.3_Linux_64bit.AppImage```
|
|
||||||
|
|
||||||
Also you might need to install FUSE to run appimages
|
|
||||||
```sudo apt install libfuse2```
|
|
||||||
|
|
||||||
## Dependencies 📖
|
## Dependencies 📖
|
||||||
This code uses the following excellent libraries:
|
This code uses the following excellent libraries:
|
||||||
|
@ -88,7 +54,7 @@ This code uses the following excellent libraries:
|
||||||
- [eModbus/eModbus](https://github.com/eModbus/eModbus) MIT-License
|
- [eModbus/eModbus](https://github.com/eModbus/eModbus) MIT-License
|
||||||
- [ESP32Async/AsyncTCP](https://github.com/ESP32Async/AsyncTCP) LGPL-3.0 license
|
- [ESP32Async/AsyncTCP](https://github.com/ESP32Async/AsyncTCP) LGPL-3.0 license
|
||||||
- [ESP32Async/ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) LGPL-3.0 license
|
- [ESP32Async/ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) LGPL-3.0 license
|
||||||
- [miwagner/ESP32-Arduino-CAN](https://github.com/miwagner/ESP32-Arduino-CAN/) MIT-License
|
- [pierremolinaro/acan-esp32](https://github.com/pierremolinaro/acan-esp32) MIT-License
|
||||||
- [pierremolinaro/acan2515](https://github.com/pierremolinaro/acan2515) MIT-License
|
- [pierremolinaro/acan2515](https://github.com/pierremolinaro/acan2515) MIT-License
|
||||||
- [pierremolinaro/acan2517FD](https://github.com/pierremolinaro/acan2517FD) MIT-License
|
- [pierremolinaro/acan2517FD](https://github.com/pierremolinaro/acan2517FD) MIT-License
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// The current software version, shown on webserver
|
// The current software version, shown on webserver
|
||||||
const char* version_number = "9.0.RC5";
|
const char* version_number = "9.2.dev";
|
||||||
|
|
||||||
// Interval timers
|
// Interval timers
|
||||||
volatile unsigned long currentMillis = 0;
|
volatile unsigned long currentMillis = 0;
|
||||||
|
@ -67,7 +67,18 @@ void register_transmitter(Transmitter* transmitter) {
|
||||||
void init_serial() {
|
void init_serial() {
|
||||||
// Init Serial monitor
|
// Init Serial monitor
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
#if HW_LILYGO2CAN
|
||||||
|
// Wait up to 100ms for Serial to be available. On the ESP32S3 Serial is
|
||||||
|
// provided by the USB controller, so will only work if the board is connected
|
||||||
|
// to a computer.
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
if (Serial)
|
||||||
|
break;
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
#else
|
||||||
while (!Serial) {}
|
while (!Serial) {}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void connectivity_loop(void*) {
|
void connectivity_loop(void*) {
|
||||||
|
@ -75,9 +86,7 @@ void connectivity_loop(void*) {
|
||||||
// Init wifi
|
// Init wifi
|
||||||
init_WiFi();
|
init_WiFi();
|
||||||
|
|
||||||
if (webserver_enabled) {
|
|
||||||
init_webserver();
|
init_webserver();
|
||||||
}
|
|
||||||
|
|
||||||
if (mdns_enabled) {
|
if (mdns_enabled) {
|
||||||
init_mDNS();
|
init_mDNS();
|
||||||
|
@ -87,9 +96,7 @@ void connectivity_loop(void*) {
|
||||||
START_TIME_MEASUREMENT(wifi);
|
START_TIME_MEASUREMENT(wifi);
|
||||||
wifi_monitor();
|
wifi_monitor();
|
||||||
|
|
||||||
if (webserver_enabled) {
|
|
||||||
ota_monitor();
|
ota_monitor();
|
||||||
}
|
|
||||||
|
|
||||||
END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us);
|
END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us);
|
||||||
|
|
||||||
|
@ -377,11 +384,9 @@ void core_loop(void*) {
|
||||||
|
|
||||||
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
|
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
|
||||||
|
|
||||||
if (webserver_enabled) {
|
|
||||||
START_TIME_MEASUREMENT(ota);
|
START_TIME_MEASUREMENT(ota);
|
||||||
ElegantOTA.loop();
|
ElegantOTA.loop();
|
||||||
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
|
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
|
||||||
}
|
|
||||||
|
|
||||||
// Process
|
// Process
|
||||||
currentMillis = millis();
|
currentMillis = millis();
|
||||||
|
@ -474,6 +479,18 @@ void core_loop(void*) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mqtt_loop(void*) {
|
||||||
|
esp_task_wdt_add(NULL); // Register this task with WDT
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
START_TIME_MEASUREMENT(mqtt);
|
||||||
|
mqtt_client_loop();
|
||||||
|
END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us);
|
||||||
|
esp_task_wdt_reset(); // Reset watchdog
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialization
|
// Initialization
|
||||||
void setup() {
|
void setup() {
|
||||||
init_hal();
|
init_hal();
|
||||||
|
@ -481,7 +498,7 @@ void setup() {
|
||||||
init_serial();
|
init_serial();
|
||||||
|
|
||||||
// We print this after setting up serial, so that is also printed if configured to do so
|
// We print this after setting up serial, so that is also printed if configured to do so
|
||||||
logging.printf("Battery emulator %s build " __DATE__ " " __TIME__ "\n", version_number);
|
DEBUG_PRINTF("Battery emulator %s build " __DATE__ " " __TIME__ "\n", version_number);
|
||||||
|
|
||||||
init_events();
|
init_events();
|
||||||
|
|
||||||
|
@ -492,43 +509,28 @@ void setup() {
|
||||||
&connectivity_loop_task, esp32hal->WIFICORE());
|
&connectivity_loop_task, esp32hal->WIFICORE());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!led_init()) {
|
led_init();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (datalayer.system.info.CAN_SD_logging_active || datalayer.system.info.SD_logging_active) {
|
if (datalayer.system.info.CAN_SD_logging_active || datalayer.system.info.SD_logging_active) {
|
||||||
xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
|
xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
|
||||||
&logging_loop_task, esp32hal->WIFICORE());
|
&logging_loop_task, esp32hal->WIFICORE());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!init_contactors()) {
|
init_contactors();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!init_precharge_control()) {
|
init_precharge_control();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_charger();
|
setup_charger();
|
||||||
|
setup_inverter();
|
||||||
if (!setup_inverter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setup_battery();
|
setup_battery();
|
||||||
setup_can_shunt();
|
setup_can_shunt();
|
||||||
|
|
||||||
// Init CAN only after any CAN receivers have had a chance to register.
|
// Init CAN only after any CAN receivers have had a chance to register.
|
||||||
if (!init_CAN()) {
|
init_CAN();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!init_rs485()) {
|
init_rs485();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!init_equipment_stop_button()) {
|
init_equipment_stop_button();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BOOT button at runtime is used as an input for various things
|
// BOOT button at runtime is used as an input for various things
|
||||||
pinMode(0, INPUT_PULLUP);
|
pinMode(0, INPUT_PULLUP);
|
||||||
|
@ -557,9 +559,7 @@ void setup() {
|
||||||
// Start tasks
|
// Start tasks
|
||||||
|
|
||||||
if (mqtt_enabled) {
|
if (mqtt_enabled) {
|
||||||
if (!init_mqtt()) {
|
init_mqtt();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
|
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
|
||||||
esp32hal->WIFICORE());
|
esp32hal->WIFICORE());
|
||||||
|
@ -568,20 +568,8 @@ void setup() {
|
||||||
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task,
|
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task,
|
||||||
esp32hal->CORE_FUNCTION_CORE());
|
esp32hal->CORE_FUNCTION_CORE());
|
||||||
|
|
||||||
DEBUG_PRINTF("setup() complete\n");
|
DEBUG_PRINTF("Setup complete!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop empty, all functionality runs in tasks
|
// Loop empty, all functionality runs in tasks
|
||||||
void loop() {}
|
void loop() {}
|
||||||
|
|
||||||
void mqtt_loop(void*) {
|
|
||||||
esp_task_wdt_add(NULL); // Register this task with WDT
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
START_TIME_MEASUREMENT(mqtt);
|
|
||||||
mqtt_loop();
|
|
||||||
END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us);
|
|
||||||
esp_task_wdt_reset(); // Reset watchdog
|
|
||||||
delay(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ std::vector<BatteryType> supported_battery_types() {
|
||||||
|
|
||||||
const char* name_for_chemistry(battery_chemistry_enum chem) {
|
const char* name_for_chemistry(battery_chemistry_enum chem) {
|
||||||
switch (chem) {
|
switch (chem) {
|
||||||
|
case battery_chemistry_enum::Autodetect:
|
||||||
|
return "Autodetect";
|
||||||
case battery_chemistry_enum::LFP:
|
case battery_chemistry_enum::LFP:
|
||||||
return "LFP";
|
return "LFP";
|
||||||
case battery_chemistry_enum::NCA:
|
case battery_chemistry_enum::NCA:
|
||||||
|
@ -116,6 +118,8 @@ const char* name_for_battery_type(BatteryType type) {
|
||||||
return RenaultZoeGen1Battery::Name;
|
return RenaultZoeGen1Battery::Name;
|
||||||
case BatteryType::RenaultZoe2:
|
case BatteryType::RenaultZoe2:
|
||||||
return RenaultZoeGen2Battery::Name;
|
return RenaultZoeGen2Battery::Name;
|
||||||
|
case BatteryType::RivianBattery:
|
||||||
|
return RivianBattery::Name;
|
||||||
case BatteryType::SamsungSdiLv:
|
case BatteryType::SamsungSdiLv:
|
||||||
return SamsungSdiLVBattery::Name;
|
return SamsungSdiLVBattery::Name;
|
||||||
case BatteryType::SantaFePhev:
|
case BatteryType::SantaFePhev:
|
||||||
|
@ -137,11 +141,7 @@ const char* name_for_battery_type(BatteryType type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LFP_CHEMISTRY
|
|
||||||
const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::LFP;
|
|
||||||
#else
|
|
||||||
const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::NMC;
|
const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::NMC;
|
||||||
#endif
|
|
||||||
|
|
||||||
battery_chemistry_enum user_selected_battery_chemistry = battery_chemistry_default;
|
battery_chemistry_enum user_selected_battery_chemistry = battery_chemistry_default;
|
||||||
|
|
||||||
|
@ -216,6 +216,8 @@ Battery* create_battery(BatteryType type) {
|
||||||
return new RenaultZoeGen1Battery();
|
return new RenaultZoeGen1Battery();
|
||||||
case BatteryType::RenaultZoe2:
|
case BatteryType::RenaultZoe2:
|
||||||
return new RenaultZoeGen2Battery();
|
return new RenaultZoeGen2Battery();
|
||||||
|
case BatteryType::RivianBattery:
|
||||||
|
return new RivianBattery();
|
||||||
case BatteryType::SamsungSdiLv:
|
case BatteryType::SamsungSdiLv:
|
||||||
return new SamsungSdiLVBattery();
|
return new SamsungSdiLVBattery();
|
||||||
case BatteryType::SantaFePhev:
|
case BatteryType::SantaFePhev:
|
||||||
|
@ -268,9 +270,15 @@ void setup_battery() {
|
||||||
case BatteryType::RenaultZoe1:
|
case BatteryType::RenaultZoe1:
|
||||||
battery2 = new RenaultZoeGen1Battery(&datalayer.battery2, nullptr, can_config.battery_double);
|
battery2 = new RenaultZoeGen1Battery(&datalayer.battery2, nullptr, can_config.battery_double);
|
||||||
break;
|
break;
|
||||||
|
case BatteryType::RenaultZoe2:
|
||||||
|
battery2 = new RenaultZoeGen2Battery(&datalayer.battery2, nullptr, can_config.battery_double);
|
||||||
|
break;
|
||||||
case BatteryType::TestFake:
|
case BatteryType::TestFake:
|
||||||
battery2 = new TestFakeBattery(&datalayer.battery2, can_config.battery_double);
|
battery2 = new TestFakeBattery(&datalayer.battery2, can_config.battery_double);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
DEBUG_PRINTF("User tried enabling double battery on non-supported integration!\n");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (battery2) {
|
if (battery2) {
|
||||||
|
@ -288,6 +296,8 @@ bool user_selected_tesla_GTW_rightHandDrive = true;
|
||||||
uint16_t user_selected_tesla_GTW_mapRegion = 2;
|
uint16_t user_selected_tesla_GTW_mapRegion = 2;
|
||||||
uint16_t user_selected_tesla_GTW_chassisType = 2;
|
uint16_t user_selected_tesla_GTW_chassisType = 2;
|
||||||
uint16_t user_selected_tesla_GTW_packEnergy = 1;
|
uint16_t user_selected_tesla_GTW_packEnergy = 1;
|
||||||
|
/* User-selected EGMP+others settings */
|
||||||
|
bool user_selected_use_estimated_SOC = false;
|
||||||
|
|
||||||
// Use 0V for user selected cell/pack voltage defaults (On boot will be replaced with saved values from NVM)
|
// Use 0V for user selected cell/pack voltage defaults (On boot will be replaced with saved values from NVM)
|
||||||
uint16_t user_selected_max_pack_voltage_dV = 0;
|
uint16_t user_selected_max_pack_voltage_dV = 0;
|
||||||
|
|
|
@ -44,6 +44,7 @@ void setup_can_shunt();
|
||||||
#include "RENAULT-TWIZY.h"
|
#include "RENAULT-TWIZY.h"
|
||||||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||||
|
#include "RIVIAN-BATTERY.h"
|
||||||
#include "RJXZS-BMS.h"
|
#include "RJXZS-BMS.h"
|
||||||
#include "SAMSUNG-SDI-LV-BATTERY.h"
|
#include "SAMSUNG-SDI-LV-BATTERY.h"
|
||||||
#include "SANTA-FE-PHEV-BATTERY.h"
|
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||||
|
@ -55,12 +56,13 @@ void setup_can_shunt();
|
||||||
#include "VOLVO-SPA-HYBRID-BATTERY.h"
|
#include "VOLVO-SPA-HYBRID-BATTERY.h"
|
||||||
|
|
||||||
void setup_battery(void);
|
void setup_battery(void);
|
||||||
|
Battery* create_battery(BatteryType type);
|
||||||
|
|
||||||
extern uint16_t user_selected_max_pack_voltage_dV;
|
extern uint16_t user_selected_max_pack_voltage_dV;
|
||||||
extern uint16_t user_selected_min_pack_voltage_dV;
|
extern uint16_t user_selected_min_pack_voltage_dV;
|
||||||
extern uint16_t user_selected_max_cell_voltage_mV;
|
extern uint16_t user_selected_max_cell_voltage_mV;
|
||||||
extern uint16_t user_selected_min_cell_voltage_mV;
|
extern uint16_t user_selected_min_cell_voltage_mV;
|
||||||
|
extern bool user_selected_use_estimated_SOC;
|
||||||
extern bool user_selected_LEAF_interlock_mandatory;
|
extern bool user_selected_LEAF_interlock_mandatory;
|
||||||
extern bool user_selected_tesla_digital_HVIL;
|
extern bool user_selected_tesla_digital_HVIL;
|
||||||
extern uint16_t user_selected_tesla_GTW_country;
|
extern uint16_t user_selected_tesla_GTW_country;
|
||||||
|
|
|
@ -60,7 +60,7 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
|
||||||
|
|
||||||
datalayer.battery.status.soh_pptt = min_soh_state;
|
datalayer.battery.status.soh_pptt = min_soh_state;
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
//datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping
|
//datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping
|
||||||
|
|
||||||
|
@ -70,10 +70,10 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
|
||||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
MAX_CHARGE_POWER_ALLOWED_W *
|
datalayer.battery.status.override_charge_power_W *
|
||||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
|
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
|
||||||
|
|
|
@ -48,8 +48,6 @@ class BmwIXBattery : public CanBattery {
|
||||||
static const int MAX_CELL_DEVIATION_MV = 250;
|
static const int MAX_CELL_DEVIATION_MV = 250;
|
||||||
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
|
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
|
||||||
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
|
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
|
||||||
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
||||||
static const int RAMPDOWN_SOC =
|
static const int RAMPDOWN_SOC =
|
||||||
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
|
|
@ -177,12 +177,15 @@ void BmwPhevBattery::wake_battery_via_canbus() {
|
||||||
// Followed by a Recessive interval of at least ~3 µs (min) and at most ~10 µs (max)
|
// Followed by a Recessive interval of at least ~3 µs (min) and at most ~10 µs (max)
|
||||||
// Then a second dominant pulse of similar timing.
|
// Then a second dominant pulse of similar timing.
|
||||||
|
|
||||||
auto original_speed = change_can_speed(CAN_Speed::CAN_SPEED_100KBPS);
|
change_can_speed(CAN_Speed::CAN_SPEED_100KBPS);
|
||||||
|
|
||||||
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST);
|
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST);
|
||||||
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST);
|
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST);
|
||||||
|
|
||||||
change_can_speed(original_speed);
|
// FIXME: This might not wait for the above frames to send before it changes
|
||||||
|
// the speed back. A state-machine controlled delay is likely necessary.
|
||||||
|
|
||||||
|
reset_can_speed();
|
||||||
|
|
||||||
logging.println("Sent magic wakeup packet to SME at 100kbps...");
|
logging.println("Sent magic wakeup packet to SME at 100kbps...");
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,14 +96,14 @@ void BoltAmperaBattery::update_values() { //This function maps all the values f
|
||||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
MAX_CHARGE_POWER_ALLOWED_W *
|
datalayer.battery.status.override_charge_power_W *
|
||||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discharge power is also set in .h file (TODO: Remove this estimation when real value has been found)
|
// Discharge power is also set in .h file (TODO: Remove this estimation when real value has been found)
|
||||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = temperature_lowest_C * 10;
|
datalayer.battery.status.temperature_min_dC = temperature_lowest_C * 10;
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@ class BoltAmperaBattery : public CanBattery {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BoltAmperaHtmlRenderer renderer;
|
BoltAmperaHtmlRenderer renderer;
|
||||||
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
||||||
static const int RAMPDOWN_SOC =
|
static const int RAMPDOWN_SOC =
|
||||||
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
|
|
@ -47,6 +47,7 @@ enum class BatteryType {
|
||||||
HyundaiIoniq28 = 39,
|
HyundaiIoniq28 = 39,
|
||||||
Kia64FD = 40,
|
Kia64FD = 40,
|
||||||
RelionBattery = 41,
|
RelionBattery = 41,
|
||||||
|
RivianBattery = 42,
|
||||||
Highest
|
Highest
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,11 @@ void CellPowerBms::update_values() {
|
||||||
|
|
||||||
datalayer.battery.status.current_dA = battery_pack_current_dA;
|
datalayer.battery.status.current_dA = battery_pack_current_dA;
|
||||||
|
|
||||||
datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN?
|
datalayer.battery.status.max_charge_power_W =
|
||||||
|
datalayer.battery.status.override_charge_power_W; //TODO, is this available via CAN?
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN?
|
datalayer.battery.status.max_discharge_power_W =
|
||||||
|
datalayer.battery.status.override_discharge_power_W; //TODO, is this available via CAN?
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10);
|
datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
#include "CanBattery.h"
|
#include "CanBattery.h"
|
||||||
|
|
||||||
CanBattery::CanBattery(CAN_Speed speed) {
|
CanBattery::CanBattery(CAN_Speed speed) : CanBattery(can_config.battery, speed) {}
|
||||||
can_interface = can_config.battery;
|
|
||||||
|
CanBattery::CanBattery(CAN_Interface interface, CAN_Speed speed) {
|
||||||
|
can_interface = interface;
|
||||||
|
initial_speed = speed;
|
||||||
register_transmitter(this);
|
register_transmitter(this);
|
||||||
register_can_receiver(this, can_interface, speed);
|
register_can_receiver(this, can_interface, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAN_Speed CanBattery::change_can_speed(CAN_Speed speed) {
|
bool CanBattery::change_can_speed(CAN_Speed speed) {
|
||||||
return ::change_can_speed(can_interface, speed);
|
return ::change_can_speed(can_interface, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CanBattery::reset_can_speed() {
|
||||||
|
::change_can_speed(can_interface, initial_speed);
|
||||||
|
}
|
||||||
|
|
|
@ -22,16 +22,13 @@ class CanBattery : public Battery, Transmitter, CanReceiver {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CAN_Interface can_interface;
|
CAN_Interface can_interface;
|
||||||
|
CAN_Speed initial_speed;
|
||||||
|
|
||||||
CanBattery(CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS);
|
CanBattery(CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS);
|
||||||
|
CanBattery(CAN_Interface interface, CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS);
|
||||||
|
|
||||||
CanBattery(CAN_Interface interface, CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS) {
|
bool change_can_speed(CAN_Speed speed);
|
||||||
can_interface = interface;
|
void reset_can_speed();
|
||||||
register_transmitter(this);
|
|
||||||
register_can_receiver(this, can_interface, speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
CAN_Speed change_can_speed(CAN_Speed speed);
|
|
||||||
|
|
||||||
void transmit_can_frame(const CAN_frame* frame) { transmit_can_frame_to_interface(frame, can_interface); }
|
void transmit_can_frame(const CAN_frame* frame) { transmit_can_frame_to_interface(frame, can_interface); }
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,13 +5,33 @@
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
|
|
||||||
/* TODO:
|
/* TODO:
|
||||||
This integration is still ongoing. Here is what still needs to be done in order to use this battery type
|
This integration is still ongoing. The same integration can be used on multiple variants of the Stellantis platforms
|
||||||
- Disable the isolation resistance requirement that opens contactors after 30s under load. Factory mode?
|
- eCMP: Disable the isolation resistance requirement that opens contactors after 30s under load. Factory mode?
|
||||||
|
|
||||||
|
- MysteryVan: Map more values from constantly transmitted instead of PID
|
||||||
|
- ADD CAN sending towards the battery (CAN-logs of full vehicle wanted!)
|
||||||
|
- Following CAN messages need to be sent towards it:
|
||||||
|
- VCU: 4C9 , 565 , 398, 448, 458, 4F1 , 342, 3E2 , 402 , 422 , 482 4D1
|
||||||
|
- CMM: 478 , 558, 1A8, 4B8 1F8 498 4E8
|
||||||
|
- OBC: 531 441 541 551 3C1
|
||||||
|
- BSIInfo_382
|
||||||
|
- VCU_BSI_Wakeup_27A
|
||||||
|
- V2_BSI_552
|
||||||
|
- CRASH_4C8
|
||||||
|
- EVSE plug in (optional): 108, 109, 119
|
||||||
|
- CRASH_4C8
|
||||||
|
- CRNT_SENS_095
|
||||||
|
- MCU 526
|
||||||
|
- JDD 55F NEW
|
||||||
|
|
||||||
|
- STLA medium: Everything missing
|
||||||
|
- ADD CAN sending towards the battery (CAN-logs of full vehicle wanted!)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
void EcmpBattery::update_values() {
|
void EcmpBattery::update_values() {
|
||||||
|
|
||||||
|
if (!MysteryVan) { //Normal eCMP platform
|
||||||
datalayer.battery.status.real_soc = battery_soc * 10;
|
datalayer.battery.status.real_soc = battery_soc * 10;
|
||||||
|
|
||||||
datalayer.battery.status.soh_pptt;
|
datalayer.battery.status.soh_pptt;
|
||||||
|
@ -23,6 +43,9 @@ void EcmpBattery::update_values() {
|
||||||
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
|
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.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||||
|
|
||||||
|
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||||
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
datalayer.battery.status.max_charge_power_W = battery_AllowedMaxChargeCurrent * battery_voltage;
|
datalayer.battery.status.max_charge_power_W = battery_AllowedMaxChargeCurrent * battery_voltage;
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = battery_AllowedMaxDischargeCurrent * battery_voltage;
|
datalayer.battery.status.max_discharge_power_W = battery_AllowedMaxDischargeCurrent * battery_voltage;
|
||||||
|
@ -50,6 +73,50 @@ void EcmpBattery::update_values() {
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value;
|
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.cell_max_voltage_mV = max_cell_mv_value;
|
||||||
|
} else { //Some variant of the 50/75kWh battery that is not using the eCMP CAN mappings.
|
||||||
|
// For these batteries we need to use the OBD2 PID polled values
|
||||||
|
|
||||||
|
if (pid_energy_capacity != NOT_SAMPLED_YET) {
|
||||||
|
datalayer.battery.status.remaining_capacity_Wh = pid_energy_capacity;
|
||||||
|
// calculate SOC based on datalayer.battery.info.total_capacity_Wh and remaining_capacity_Wh
|
||||||
|
datalayer.battery.status.real_soc = (uint16_t)(((float)datalayer.battery.status.remaining_capacity_Wh /
|
||||||
|
datalayer.battery.info.total_capacity_Wh) *
|
||||||
|
10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
datalayer.battery.status.soh_pptt;
|
||||||
|
|
||||||
|
if (pid_pack_voltage != NOT_SAMPLED_YET) {
|
||||||
|
datalayer.battery.status.voltage_dV = pid_pack_voltage + 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid_current != NOT_SAMPLED_YET) {
|
||||||
|
datalayer.battery.status.current_dA = (int16_t)(pid_current / 100);
|
||||||
|
|
||||||
|
datalayer.battery.status.active_power_W =
|
||||||
|
(uint16_t)((pid_current / 1000.0f) * (datalayer.battery.status.voltage_dV / 10.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid_max_charge_10s != NOT_SAMPLED_YET) {
|
||||||
|
datalayer.battery.status.max_charge_power_W = pid_max_charge_10s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid_max_discharge_10s != NOT_SAMPLED_YET) {
|
||||||
|
datalayer.battery.status.max_discharge_power_W = pid_max_discharge_10s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pid_highest_temperature != NOT_SAMPLED_YET) && (pid_lowest_temperature != NOT_SAMPLED_YET)) {
|
||||||
|
datalayer.battery.status.temperature_max_dC = pid_highest_temperature * 10;
|
||||||
|
datalayer.battery.status.temperature_min_dC = pid_lowest_temperature * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pid_high_cell_voltage != NOT_SAMPLED_YET) && (pid_low_cell_voltage != NOT_SAMPLED_YET)) {
|
||||||
|
datalayer.battery.status.cell_max_voltage_mV = pid_high_cell_voltage;
|
||||||
|
datalayer.battery.status.cell_min_voltage_mV = pid_low_cell_voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
datalayer.battery.info.number_of_cells = NUMBER_OF_CELL_MEASUREMENTS_IN_BATTERY; //50/75kWh sends valid cellcount
|
||||||
|
}
|
||||||
|
|
||||||
// Update extended datalayer (More Battery Info page)
|
// Update extended datalayer (More Battery Info page)
|
||||||
datalayer_extended.stellantisECMP.MainConnectorState = battery_MainConnectorState;
|
datalayer_extended.stellantisECMP.MainConnectorState = battery_MainConnectorState;
|
||||||
|
@ -124,6 +191,28 @@ void EcmpBattery::update_values() {
|
||||||
datalayer_extended.stellantisECMP.pid_contactor_closing_counter = pid_contactor_closing_counter;
|
datalayer_extended.stellantisECMP.pid_contactor_closing_counter = pid_contactor_closing_counter;
|
||||||
datalayer_extended.stellantisECMP.pid_date_of_manufacture = pid_date_of_manufacture;
|
datalayer_extended.stellantisECMP.pid_date_of_manufacture = pid_date_of_manufacture;
|
||||||
datalayer_extended.stellantisECMP.pid_SOH_cell_1 = pid_SOH_cell_1;
|
datalayer_extended.stellantisECMP.pid_SOH_cell_1 = pid_SOH_cell_1;
|
||||||
|
// Update extended datalayer for MysteryVan
|
||||||
|
datalayer_extended.stellantisECMP.MysteryVan = MysteryVan;
|
||||||
|
datalayer_extended.stellantisECMP.CONTACTORS_STATE = CONTACTORS_STATE;
|
||||||
|
datalayer_extended.stellantisECMP.CrashMemorized = HV_BATT_CRASH_MEMORIZED;
|
||||||
|
datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON = CONTACTOR_OPENING_REASON;
|
||||||
|
datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE = TBMU_FAULT_TYPE;
|
||||||
|
datalayer_extended.stellantisECMP.HV_BATT_FC_INSU_MINUS_RES = HV_BATT_FC_INSU_MINUS_RES;
|
||||||
|
datalayer_extended.stellantisECMP.HV_BATT_FC_INSU_PLUS_RES = HV_BATT_FC_INSU_PLUS_RES;
|
||||||
|
datalayer_extended.stellantisECMP.HV_BATT_FC_VHL_INSU_PLUS_RES = HV_BATT_FC_VHL_INSU_PLUS_RES;
|
||||||
|
datalayer_extended.stellantisECMP.HV_BATT_ONLY_INSU_MINUS_RES = HV_BATT_ONLY_INSU_MINUS_RES;
|
||||||
|
datalayer_extended.stellantisECMP.HV_BATT_ONLY_INSU_MINUS_RES = HV_BATT_ONLY_INSU_MINUS_RES;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_CELL_POOR_CONSIST = ALERT_CELL_POOR_CONSIST;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_OVERCHARGE = ALERT_OVERCHARGE;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_BATT = ALERT_BATT;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_LOW_SOC = ALERT_LOW_SOC;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_HIGH_SOC = ALERT_HIGH_SOC;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_SOC_JUMP = ALERT_SOC_JUMP;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_TEMP_DIFF = ALERT_TEMP_DIFF;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_HIGH_TEMP = ALERT_HIGH_TEMP;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_OVERVOLTAGE = ALERT_OVERVOLTAGE;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_CELL_OVERVOLTAGE = ALERT_CELL_OVERVOLTAGE;
|
||||||
|
datalayer_extended.stellantisECMP.ALERT_CELL_UNDERVOLTAGE = ALERT_CELL_UNDERVOLTAGE;
|
||||||
|
|
||||||
if (battery_InterlockOpen) {
|
if (battery_InterlockOpen) {
|
||||||
set_event(EVENT_HVIL_FAILURE, 0);
|
set_event(EVENT_HVIL_FAILURE, 0);
|
||||||
|
@ -145,9 +234,223 @@ void EcmpBattery::update_values() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x125: //Common
|
case 0x2D4: //MysteryVan 50/75kWh platform (TBMU 100ms periodic)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
MysteryVan = true;
|
||||||
|
SOE_MAX_CURRENT_TEMP = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; // (Wh, 0-200000)
|
||||||
|
FRONT_MACHINE_POWER_LIMIT = (rx_frame.data.u8[4] << 6) | ((rx_frame.data.u8[5] & 0xFC) >> 2); // (W 0-1000000)
|
||||||
|
REAR_MACHINE_POWER_LIMIT = ((rx_frame.data.u8[5] & 0x03) << 12) | (rx_frame.data.u8[6] << 4) |
|
||||||
|
((rx_frame.data.u8[7] & 0xF0) >> 4); // (W 0-1000000)
|
||||||
|
break;
|
||||||
|
case 0x3B4: //MysteryVan 50/75kWh platform (TBMU 100ms periodic)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
EVSE_INSTANT_DC_HV_CURRENT =
|
||||||
|
((rx_frame.data.u8[2] & 0x03) << 12) | (rx_frame.data.u8[3] << 2) | ((rx_frame.data.u8[4] & 0xC0) >> 6);
|
||||||
|
EVSE_STATE = ((rx_frame.data.u8[4] & 0x38) >> 3); /*Enumeration below
|
||||||
|
000: NOT CONNECTED
|
||||||
|
001: CONNECTED
|
||||||
|
010: INITIALISATION
|
||||||
|
011: READY
|
||||||
|
100: PRECHARGE IN PROGRESS
|
||||||
|
101: TRANSFER IN PROGRESS
|
||||||
|
110: NOT READY
|
||||||
|
111: Reserved */
|
||||||
|
HV_BATT_SOE_HD = ((rx_frame.data.u8[4] & 0x03) << 12) | (rx_frame.data.u8[5] << 4) |
|
||||||
|
((rx_frame.data.u8[6] & 0xF0) >> 4); // (Wh, 0-200000)
|
||||||
|
HV_BATT_SOE_MAX = ((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]; // (Wh, 0-200000)
|
||||||
|
CHECKSUM_FRAME_3B4 = (rx_frame.data.u8[0] & 0xF0) >> 4;
|
||||||
|
COUNTER_3B4 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x2F4: //MysteryVan 50/75kWh platform (Event triggered when charging)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
//TBMU_EVSE_DC_MES_VOLTAGE = (rx_frame.data.u8[0] << 6) | (rx_frame.data.u8[1] >> 2); //V 0-1000 //Fastcharger info, not needed for BE
|
||||||
|
//TBMU_EVSE_DC_MIN_VOLTAGE = ((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]; //V 0-1000 //Fastcharger info, not needed for BE
|
||||||
|
//TBMU_EVSE_DC_MES_CURRENT = (rx_frame.data.u8[3] << 4) | ((rx_frame.data.u8[4] & 0xF0) >> 4); //A -2000 - 2000 //Fastcharger info, not needed for BE
|
||||||
|
//TBMU_EVSE_CHRG_REQ = (rx_frame.data.u8[4] & 0x0C) >> 2; //00 No request, 01 Stop request //Fastcharger info, not needed for BE
|
||||||
|
//HV_STORAGE_MAX_I = ((rx_frame.data.u8[4] & 0x03) << 12) | (rx_frame.data.u8[5] << 2) | //Fastcharger info, not needed for BE
|
||||||
|
//((rx_frame.data.u8[6] & 0xC0) >> 6); //A -2000 - 2000
|
||||||
|
//TBMU_EVSE_DC_MAX_POWER = ((rx_frame.data.u8[6] & 0x3F) << 8) | rx_frame.data.u8[7]; //W -1000000 - 0 //Fastcharger info, not needed for BE
|
||||||
|
break;
|
||||||
|
case 0x3F4: //MysteryVan 50/75kWh platform (Temperature sensors)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
switch (((rx_frame.data.u8[0] & 0xE0) >> 5)) //Mux resides in top 3 bits of frame0
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
BMS_PROBETEMP[0] = (rx_frame.data.u8[1] - 40);
|
||||||
|
BMS_PROBETEMP[1] = (rx_frame.data.u8[2] - 40);
|
||||||
|
BMS_PROBETEMP[2] = (rx_frame.data.u8[3] - 40);
|
||||||
|
BMS_PROBETEMP[3] = (rx_frame.data.u8[4] - 40);
|
||||||
|
BMS_PROBETEMP[4] = (rx_frame.data.u8[5] - 40);
|
||||||
|
BMS_PROBETEMP[5] = (rx_frame.data.u8[6] - 40);
|
||||||
|
BMS_PROBETEMP[6] = (rx_frame.data.u8[7] - 40);
|
||||||
|
break;
|
||||||
|
default: //There are in total 64 temperature measurements in the BMS. We do not need to sample them all.
|
||||||
|
break; //For future, we could read them all if we want to.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x554: //MysteryVan 50/75kWh platform (Discharge/Charge limits)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_PEAK_DISCH_POWER_HD = (rx_frame.data.u8[1] << 6) | (rx_frame.data.u8[2] >> 2); //0-1000000 W
|
||||||
|
HV_BATT_PEAK_CH_POWER_HD = ((rx_frame.data.u8[2] & 0x03) << 12) | (rx_frame.data.u8[3] << 4) |
|
||||||
|
((rx_frame.data.u8[4] & 0xF0) >> 4); // -1000000 - 0 W
|
||||||
|
HV_BATT_NOM_CH_POWER_HD = ((rx_frame.data.u8[4] & 0x0F) << 12) | (rx_frame.data.u8[5] << 6) |
|
||||||
|
((rx_frame.data.u8[6] & 0xC0) >> 6); // -1000000 - 0 W
|
||||||
|
MAX_ALLOW_CHRG_CURRENT = ((rx_frame.data.u8[6] & 0x3F) << 8) | rx_frame.data.u8[7];
|
||||||
|
CHECKSUM_FRAME_554 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xE
|
||||||
|
COUNTER_554 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x373: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
REQ_CLEAR_DTC_TBMU = ((rx_frame.data.u8[3] & 0x40) >> 7);
|
||||||
|
TBCU_48V_WAKEUP = (rx_frame.data.u8[3] >> 7);
|
||||||
|
HV_BATT_MAX_REAL_CURR = (rx_frame.data.u8[5] << 7) | (rx_frame.data.u8[6] >> 1); //A -2000 - 2000 0.1 scaling
|
||||||
|
TBMU_FAULT_TYPE = (rx_frame.data.u8[7] & 0xE0) >> 5;
|
||||||
|
/*000: No fault
|
||||||
|
001: FirstLevelFault: Warning Lamp
|
||||||
|
010: SecondLevelFault: Stop Lamp
|
||||||
|
011: ThirdLevelFault: Stop Lamp + contactor opening (EPS shutdown)
|
||||||
|
100: FourthLevelFault: Stop Lamp + Active Discharge
|
||||||
|
101: Inhibition of powertrain activation
|
||||||
|
110: Reserved
|
||||||
|
111: Invalid*/
|
||||||
|
HV_BATT_REAL_VOLT_HD = ((rx_frame.data.u8[3] & 0x3F) << 8) | (rx_frame.data.u8[4]); //V 0-1000 * 0.1 scaling
|
||||||
|
HV_BATT_REAL_CURR_HD = (rx_frame.data.u8[1] << 8) | (rx_frame.data.u8[2]); //A -2000 - 2000 0.1 scaling
|
||||||
|
CHECKSUM_FRAME_373 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xD
|
||||||
|
COUNTER_373 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x4F4: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_CRASH_MEMORIZED = ((rx_frame.data.u8[2] & 0x08) >> 3);
|
||||||
|
HV_BATT_COLD_CRANK_ACK = ((rx_frame.data.u8[2] & 0x04) >> 2);
|
||||||
|
HV_BATT_CHARGE_NEEDED_STATE = ((rx_frame.data.u8[2] & 0x02) >> 1);
|
||||||
|
HV_BATT_NOM_CH_VOLTAGE = ((rx_frame.data.u8[2] & 0x01) << 8) | (rx_frame.data.u8[3]); //V 0 - 500
|
||||||
|
HV_BATT_NOM_CH_CURRENT = rx_frame.data.u8[4]; // -120 - 0 0.5scaling
|
||||||
|
HV_BATT_GENERATED_HEAT_RATE = (rx_frame.data.u8[5] << 1) | (rx_frame.data.u8[6] >> 7); //W 0-50000
|
||||||
|
REQ_MIL_LAMP_CONTINOUS = (rx_frame.data.u8[7] & 0x04) >> 2;
|
||||||
|
REQ_BLINK_STOP_AND_SERVICE_LAMP = (rx_frame.data.u8[7] & 0x02) >> 1;
|
||||||
|
CMD_RESET_MIL = (rx_frame.data.u8[7] & 0x01);
|
||||||
|
HV_BATT_SOC = (rx_frame.data.u8[1] << 2) | (rx_frame.data.u8[2] >> 6);
|
||||||
|
CONTACTORS_STATE =
|
||||||
|
(rx_frame.data.u8[2] & 0x30) >> 4; //00 : contactor open 01 : pre-load contactor 10 : contactor close
|
||||||
|
HV_BATT_DISCONT_WARNING_OPEN = (rx_frame.data.u8[7] & 0x08) >> 3;
|
||||||
|
CHECKSUM_FRAME_4F4 = (rx_frame.data.u8[0] & 0xF0) >> 4;
|
||||||
|
COUNTER_4F4 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x414: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_REAL_POWER_HD = (rx_frame.data.u8[1] << 7) | (rx_frame.data.u8[2] >> 1);
|
||||||
|
MAX_ALLOW_CHRG_POWER =
|
||||||
|
((rx_frame.data.u8[2] & 0x01) << 13) | (rx_frame.data.u8[3] << 5) | ((rx_frame.data.u8[4] & 0xF8) >> 3);
|
||||||
|
MAX_ALLOW_DISCHRG_POWER =
|
||||||
|
((rx_frame.data.u8[5] & 0x07) << 11) | (rx_frame.data.u8[6] << 3) | ((rx_frame.data.u8[7] & 0xE0) >> 5);
|
||||||
|
CHECKSUM_FRAME_414 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0x9
|
||||||
|
COUNTER_414 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x353: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_COP_VOLTAGE =
|
||||||
|
(rx_frame.data.u8[1] << 5) | (rx_frame.data.u8[2] >> 3); //Real voltage HV battery (dV, 0-5000)
|
||||||
|
HV_BATT_COP_CURRENT =
|
||||||
|
(rx_frame.data.u8[3] << 5) | (rx_frame.data.u8[4] >> 3); //High resolution battery current (dA, -4000 - 4000)
|
||||||
|
CHECKSUM_FRAME_353 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xB
|
||||||
|
COUNTER_353 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x474: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
BMS_DC_RELAY_MES_EVSE_VOLTAGE = (rx_frame.data.u8[1] << 6) | (rx_frame.data.u8[2] >> 2); //V 0-1000
|
||||||
|
FAST_CHARGE_CONTACTOR_STATE = (rx_frame.data.u8[2] & 0x03);
|
||||||
|
/*00: Contactors Opened
|
||||||
|
01: Contactors Closed
|
||||||
|
10: No Request
|
||||||
|
11: WELDING TEST*/
|
||||||
|
BMS_FASTCHARGE_STATUS = (rx_frame.data.u8[4] & 0x03);
|
||||||
|
/*00 : not charging
|
||||||
|
01 : charging
|
||||||
|
10 : charging fault
|
||||||
|
11 : charging finished*/
|
||||||
|
CHECKSUM_FRAME_474 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0xF
|
||||||
|
COUNTER_474 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x574: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_FC_INSU_MINUS_RES = (rx_frame.data.u8[0] << 5) | (rx_frame.data.u8[1] >> 3); //kOhm (0-60000)
|
||||||
|
HV_BATT_FC_VHL_INSU_PLUS_RES =
|
||||||
|
((rx_frame.data.u8[1] & 0x07) << 10) | (rx_frame.data.u8[2] << 2) | ((rx_frame.data.u8[3] & 0xC0) >> 6);
|
||||||
|
HV_BATT_FC_INSU_PLUS_RES = (rx_frame.data.u8[5] << 4) | (rx_frame.data.u8[6] >> 4);
|
||||||
|
HV_BATT_ONLY_INSU_MINUS_RES = ((rx_frame.data.u8[3] & 0x3F) << 7) | (rx_frame.data.u8[4] >> 1);
|
||||||
|
break;
|
||||||
|
case 0x583: //MysteryVan 50/75kWh platform (CAN-FD also?)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
ALERT_OVERCHARGE = (rx_frame.data.u8[4] & 0x20) >> 5;
|
||||||
|
NUMBER_PROBE_TEMP_MAX = rx_frame.data.u8[0];
|
||||||
|
NUMBER_PROBE_TEMP_MIN = rx_frame.data.u8[1];
|
||||||
|
TEMPERATURE_MINIMUM_C = rx_frame.data.u8[2] - 40;
|
||||||
|
ALERT_BATT = (rx_frame.data.u8[3] & 0x80) >> 7;
|
||||||
|
ALERT_TEMP_DIFF = (rx_frame.data.u8[3] & 0x40) >> 6;
|
||||||
|
ALERT_HIGH_TEMP = (rx_frame.data.u8[3] & 0x20) >> 5;
|
||||||
|
ALERT_OVERVOLTAGE = (rx_frame.data.u8[3] & 0x10) >> 4;
|
||||||
|
ALERT_LOW_SOC = (rx_frame.data.u8[3] & 0x08) >> 3;
|
||||||
|
ALERT_HIGH_SOC = (rx_frame.data.u8[3] & 0x04) >> 2;
|
||||||
|
ALERT_CELL_OVERVOLTAGE = (rx_frame.data.u8[3] & 0x02) >> 1;
|
||||||
|
ALERT_CELL_UNDERVOLTAGE = (rx_frame.data.u8[3] & 0x01);
|
||||||
|
ALERT_SOC_JUMP = (rx_frame.data.u8[4] & 0x80) >> 7;
|
||||||
|
ALERT_CELL_POOR_CONSIST = (rx_frame.data.u8[4] & 0x40) >> 6;
|
||||||
|
CONTACTOR_OPENING_REASON = (rx_frame.data.u8[4] & 0x1C) >> 2;
|
||||||
|
/*
|
||||||
|
000 : Not error
|
||||||
|
001 : Crash
|
||||||
|
010 : 12V supply source undervoltage
|
||||||
|
011 : 12V supply source overvoltage
|
||||||
|
100 : Battery temperature
|
||||||
|
101 : interlock line open
|
||||||
|
110 : e-Service plug disconnected
|
||||||
|
111 : Not valid
|
||||||
|
*/
|
||||||
|
NUMBER_OF_TEMPERATURE_SENSORS_IN_BATTERY = rx_frame.data.u8[5];
|
||||||
|
NUMBER_OF_CELL_MEASUREMENTS_IN_BATTERY = rx_frame.data.u8[6];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x314: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
MIN_ALLOW_DISCHRG_VOLTAGE = (rx_frame.data.u8[1] << 3) | (rx_frame.data.u8[2] >> 5); //V (0-1000)
|
||||||
|
//EVSE_DC_MAX_CURRENT = ((rx_frame.data.u8[2] & 0x1F) << 5) | (rx_frame.data.u8[3] >> 3); //Fastcharger info, not needed for BE
|
||||||
|
//TBMU_EVSE_DC_MAX_VOLTAGE //Fastcharger info, not needed for BE
|
||||||
|
//TBMU_MAX_CHRG_SCKT_TEMP //Fastcharger info, not needed for BE
|
||||||
|
//DC_CHARGE_MODE_AVAIL //Fastcharger info, not needed for BE
|
||||||
|
//BIDIR_V2HG_MODE_AVAIL //Fastcharger info, not needed for BE
|
||||||
|
//TBMU_CHRG_CONN_CONF //Fastcharger info, not needed for BE
|
||||||
|
//EVSE_GRID_FAULT //Fastcharger info, not needed for BE
|
||||||
|
CHECKSUM_FRAME_314 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0x8
|
||||||
|
COUNTER_314 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x254: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
//HV_BATT_SOE_MAX_HR = frame6 & frame7 //Only on FD-CAN variant of the message. FD has length 7, non-fd 5
|
||||||
|
HV_BATT_NOMINAL_DISCH_CURR_HD = (rx_frame.data.u8[0] << 7) | (rx_frame.data.u8[1] >> 1); //dA (0-20000)
|
||||||
|
HV_BATT_PEAK_DISCH_CURR_HD = (rx_frame.data.u8[2] << 7) | (rx_frame.data.u8[3] >> 1); //dA (0-20000)
|
||||||
|
HV_BATT_STABLE_DISCH_CURR_HD = (rx_frame.data.u8[4] << 7) | (rx_frame.data.u8[5] >> 1); //dA (0-20000)
|
||||||
|
break;
|
||||||
|
case 0x2B4: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_NOMINAL_CHARGE_CURR_HD = (rx_frame.data.u8[0] << 7) | (rx_frame.data.u8[1] >> 1);
|
||||||
|
HV_BATT_PEAK_CHARGE_CURR_HD = (rx_frame.data.u8[2] << 7) | (rx_frame.data.u8[3] >> 1);
|
||||||
|
HV_BATT_STABLE_CHARGE_CURR_HD = (rx_frame.data.u8[4] << 7) | (rx_frame.data.u8[5] >> 1);
|
||||||
|
break;
|
||||||
|
case 0x4D4: //MysteryVan 50/75kWh platform
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
HV_BATT_STABLE_CHARGE_POWER_HD = (rx_frame.data.u8[0] << 6) | (rx_frame.data.u8[1] >> 2);
|
||||||
|
HV_BATT_STABLE_DISCH_POWER_HD =
|
||||||
|
((rx_frame.data.u8[2] & 0x03) << 12) | (rx_frame.data.u8[3] << 4) | ((rx_frame.data.u8[4] & 0xF0) >> 4);
|
||||||
|
HV_BATT_NOMINAL_DISCH_POWER_HD =
|
||||||
|
((rx_frame.data.u8[4] & 0x0F) << 10) | (rx_frame.data.u8[5] << 2) | ((rx_frame.data.u8[6] & 0xC0) >> 6);
|
||||||
|
MAX_ALLOW_DISCHRG_CURRENT = ((rx_frame.data.u8[6] & 0x3F) << 5) | (rx_frame.data.u8[7] >> 3);
|
||||||
|
RC01_PERM_SYNTH_TBMU = (rx_frame.data.u8[7] & 0x04) >> 2; //TBMU Readiness Code synthesis
|
||||||
|
CHECKSUM_FRAME_4D4 = (rx_frame.data.u8[0] & 0xF0) >> 4; //Frame checksum 0x5
|
||||||
|
COUNTER_4D4 = (rx_frame.data.u8[0] & 0x0F);
|
||||||
|
break;
|
||||||
|
case 0x125: //Common eCMP
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
battery_soc = (rx_frame.data.u8[0] << 2) |
|
battery_soc = (rx_frame.data.u8[0] << 2) |
|
||||||
(rx_frame.data.u8[1] >> 6); // Byte1, bit 7 length 10 (0x3FE when abnormal) (0-1000 ppt)
|
(rx_frame.data.u8[1] >> 6); // Byte1, bit 7 length 10 (0x3FE when abnormal) (0-1000 ppt)
|
||||||
battery_MainConnectorState = ((rx_frame.data.u8[2] & 0x18) >>
|
battery_MainConnectorState = ((rx_frame.data.u8[2] & 0x18) >>
|
||||||
|
@ -157,6 +460,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
battery_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]) - 600; // TODO: Test
|
battery_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[5]) - 600; // TODO: Test
|
||||||
break;
|
break;
|
||||||
case 0x127: //DFM specific
|
case 0x127: //DFM specific
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
battery_AllowedMaxChargeCurrent =
|
battery_AllowedMaxChargeCurrent =
|
||||||
(rx_frame.data.u8[0] << 2) |
|
(rx_frame.data.u8[0] << 2) |
|
||||||
((rx_frame.data.u8[1] & 0xC0) >> 6); //Byte 1, bit 7, length 10 (0-600A) [0x3FF if invalid]
|
((rx_frame.data.u8[1] & 0xC0) >> 6); //Byte 1, bit 7, length 10 (0-600A) [0x3FF if invalid]
|
||||||
|
@ -165,12 +469,15 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
(rx_frame.data.u8[3] >> 4); //Byte 2, bit 5, length 10 (0-600A) [0x3FF if invalid]
|
(rx_frame.data.u8[3] >> 4); //Byte 2, bit 5, length 10 (0-600A) [0x3FF if invalid]
|
||||||
break;
|
break;
|
||||||
case 0x129: //PSA specific
|
case 0x129: //PSA specific
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
break;
|
break;
|
||||||
case 0x31B:
|
case 0x31B:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
battery_InterlockOpen = ((rx_frame.data.u8[1] & 0x10) >> 4); //Best guess, seems to work?
|
battery_InterlockOpen = ((rx_frame.data.u8[1] & 0x10) >> 4); //Best guess, seems to work?
|
||||||
//TODO: frame7 contains checksum, we can use this to check for CAN message corruption
|
//TODO: frame7 contains checksum, we can use this to check for CAN message corruption
|
||||||
break;
|
break;
|
||||||
case 0x358: //Common
|
case 0x358: //Common
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
battery_highestTemperature = rx_frame.data.u8[6] - 40;
|
battery_highestTemperature = rx_frame.data.u8[6] - 40;
|
||||||
battery_lowestTemperature = rx_frame.data.u8[7] - 40;
|
battery_lowestTemperature = rx_frame.data.u8[7] - 40;
|
||||||
break;
|
break;
|
||||||
|
@ -185,11 +492,13 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
case 0x494:
|
case 0x494:
|
||||||
break;
|
break;
|
||||||
case 0x594:
|
case 0x594:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
battery_insulation_failure_diag = ((rx_frame.data.u8[6] & 0xE0) >> 5); //Unsure if this is right position
|
battery_insulation_failure_diag = ((rx_frame.data.u8[6] & 0xE0) >> 5); //Unsure if this is right position
|
||||||
//byte pos 6, bit pos 7, signal lenth 3
|
//byte pos 6, bit pos 7, signal lenth 3
|
||||||
//0 = no failure, 1 = symmetric failure, 4 = invalid value , forbidden value 5-7
|
//0 = no failure, 1 = symmetric failure, 4 = invalid value , forbidden value 5-7
|
||||||
break;
|
break;
|
||||||
case 0x6D0: //Common
|
case 0x6D0: //Common
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
battery_insulationResistanceKOhm =
|
battery_insulationResistanceKOhm =
|
||||||
(rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; //Byte 2, bit 7, length 16 (0-60000 kOhm)
|
(rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; //Byte 2, bit 7, length 16 (0-60000 kOhm)
|
||||||
break;
|
break;
|
||||||
|
@ -198,6 +507,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
case 0x6D2:
|
case 0x6D2:
|
||||||
break;
|
break;
|
||||||
case 0x6D3:
|
case 0x6D3:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
cellvoltages[0] = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1];
|
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[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[2] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
@ -374,6 +684,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages, 108 * sizeof(uint16_t));
|
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages, 108 * sizeof(uint16_t));
|
||||||
break;
|
break;
|
||||||
case 0x694: // Poll reply
|
case 0x694: // Poll reply
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
// Handle user requested functionality first if ongoing
|
// Handle user requested functionality first if ongoing
|
||||||
if (datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring) {
|
if (datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ class EcmpBattery : public CanBattery {
|
||||||
static const int COMPLETED_STATE = 0;
|
static const int COMPLETED_STATE = 0;
|
||||||
bool battery_RelayOpenRequest = false;
|
bool battery_RelayOpenRequest = false;
|
||||||
bool battery_InterlockOpen = false;
|
bool battery_InterlockOpen = false;
|
||||||
|
bool MysteryVan = false;
|
||||||
uint8_t ContactorResetStatemachine = 0;
|
uint8_t ContactorResetStatemachine = 0;
|
||||||
uint8_t CollisionResetStatemachine = 0;
|
uint8_t CollisionResetStatemachine = 0;
|
||||||
uint8_t IsolationResetStatemachine = 0;
|
uint8_t IsolationResetStatemachine = 0;
|
||||||
|
@ -80,8 +81,8 @@ class EcmpBattery : public CanBattery {
|
||||||
uint8_t pid_coldest_module = NOT_SAMPLED_YET;
|
uint8_t pid_coldest_module = NOT_SAMPLED_YET;
|
||||||
uint8_t pid_lowest_temperature = NOT_SAMPLED_YET;
|
uint8_t pid_lowest_temperature = NOT_SAMPLED_YET;
|
||||||
uint8_t pid_average_temperature = NOT_SAMPLED_YET;
|
uint8_t pid_average_temperature = NOT_SAMPLED_YET;
|
||||||
uint8_t pid_highest_temperature = NOT_SAMPLED_YET;
|
int8_t pid_highest_temperature = NOT_SAMPLED_YET;
|
||||||
uint8_t pid_hottest_module = NOT_SAMPLED_YET;
|
int8_t pid_hottest_module = NOT_SAMPLED_YET;
|
||||||
uint16_t pid_avg_cell_voltage = NOT_SAMPLED_YET;
|
uint16_t pid_avg_cell_voltage = NOT_SAMPLED_YET;
|
||||||
int32_t pid_current = NOT_SAMPLED_YET;
|
int32_t pid_current = NOT_SAMPLED_YET;
|
||||||
uint32_t pid_insulation_res_neg = NOT_SAMPLED_YET;
|
uint32_t pid_insulation_res_neg = NOT_SAMPLED_YET;
|
||||||
|
@ -132,7 +133,68 @@ class EcmpBattery : public CanBattery {
|
||||||
uint32_t pid_date_of_manufacture = NOT_SAMPLED_YET;
|
uint32_t pid_date_of_manufacture = NOT_SAMPLED_YET;
|
||||||
uint16_t pid_SOH_cell_1 = NOT_SAMPLED_YET;
|
uint16_t pid_SOH_cell_1 = NOT_SAMPLED_YET;
|
||||||
|
|
||||||
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
//MysteryVan platform (allcaps to make code easer to co-exist)
|
||||||
|
uint16_t SOE_MAX_CURRENT_TEMP = 0;
|
||||||
|
uint16_t FRONT_MACHINE_POWER_LIMIT = 0;
|
||||||
|
uint16_t REAR_MACHINE_POWER_LIMIT = 0;
|
||||||
|
uint16_t EVSE_INSTANT_DC_HV_CURRENT = 0;
|
||||||
|
uint8_t EVSE_STATE = 0;
|
||||||
|
uint16_t HV_BATT_SOE_HD = 0;
|
||||||
|
uint16_t HV_BATT_SOE_MAX = 0;
|
||||||
|
uint8_t CHECKSUM_FRAME_314, CHECKSUM_FRAME_3B4, CHECKSUM_FRAME_554, CHECKSUM_FRAME_373, CHECKSUM_FRAME_4F4,
|
||||||
|
CHECKSUM_FRAME_414, CHECKSUM_FRAME_353, CHECKSUM_FRAME_474, CHECKSUM_FRAME_4D4 = 0;
|
||||||
|
uint16_t HV_STORAGE_MAX_I = 0;
|
||||||
|
int8_t BMS_PROBETEMP[7] = {0};
|
||||||
|
uint8_t COUNTER_314, COUNTER_554, COUNTER_373, COUNTER_3B4, COUNTER_4F4, COUNTER_414, COUNTER_353, COUNTER_474,
|
||||||
|
COUNTER_4D4 = 0;
|
||||||
|
uint16_t HV_BATT_PEAK_DISCH_POWER_HD = 0;
|
||||||
|
uint16_t HV_BATT_PEAK_CH_POWER_HD = 0;
|
||||||
|
uint16_t HV_BATT_NOM_CH_POWER_HD = 0;
|
||||||
|
uint16_t MAX_ALLOW_CHRG_CURRENT = 0;
|
||||||
|
int16_t HV_BATT_REAL_CURR_HD = 0;
|
||||||
|
uint16_t HV_BATT_REAL_VOLT_HD = 0;
|
||||||
|
uint8_t TBMU_FAULT_TYPE = 0;
|
||||||
|
int16_t HV_BATT_MAX_REAL_CURR = 0;
|
||||||
|
bool TBCU_48V_WAKEUP = false;
|
||||||
|
bool REQ_CLEAR_DTC_TBMU = false;
|
||||||
|
bool HV_BATT_DISCONT_WARNING_OPEN = false;
|
||||||
|
uint8_t CONTACTORS_STATE = 0;
|
||||||
|
uint16_t HV_BATT_SOC = 0;
|
||||||
|
bool CMD_RESET_MIL = 0;
|
||||||
|
bool REQ_BLINK_STOP_AND_SERVICE_LAMP = false;
|
||||||
|
bool REQ_MIL_LAMP_CONTINOUS = false;
|
||||||
|
uint16_t HV_BATT_GENERATED_HEAT_RATE = 0;
|
||||||
|
bool HV_BATT_CRASH_MEMORIZED = false;
|
||||||
|
bool HV_BATT_COLD_CRANK_ACK = false;
|
||||||
|
bool HV_BATT_CHARGE_NEEDED_STATE = false;
|
||||||
|
uint8_t HV_BATT_NOM_CH_CURRENT = 0;
|
||||||
|
uint16_t HV_BATT_NOM_CH_VOLTAGE = 0;
|
||||||
|
uint16_t HV_BATT_REAL_POWER_HD = 0;
|
||||||
|
uint16_t MAX_ALLOW_CHRG_POWER = 0;
|
||||||
|
uint16_t MAX_ALLOW_DISCHRG_POWER = 0;
|
||||||
|
bool ALERT_CELL_POOR_CONSIST, ALERT_OVERCHARGE, ALERT_BATT, ALERT_LOW_SOC, ALERT_HIGH_SOC, ALERT_SOC_JUMP,
|
||||||
|
ALERT_TEMP_DIFF, ALERT_HIGH_TEMP, ALERT_OVERVOLTAGE, ALERT_CELL_OVERVOLTAGE, ALERT_CELL_UNDERVOLTAGE = false;
|
||||||
|
uint8_t NUMBER_PROBE_TEMP_MAX, NUMBER_PROBE_TEMP_MIN = 0;
|
||||||
|
int8_t TEMPERATURE_MINIMUM_C = 0;
|
||||||
|
uint8_t CONTACTOR_OPENING_REASON = 0;
|
||||||
|
uint8_t NUMBER_OF_TEMPERATURE_SENSORS_IN_BATTERY, NUMBER_OF_CELL_MEASUREMENTS_IN_BATTERY = 0;
|
||||||
|
uint16_t HV_BATT_COP_VOLTAGE = 0;
|
||||||
|
int16_t HV_BATT_COP_CURRENT = 0;
|
||||||
|
uint16_t BMS_DC_RELAY_MES_EVSE_VOLTAGE = 0;
|
||||||
|
uint8_t FAST_CHARGE_CONTACTOR_STATE = 0;
|
||||||
|
uint8_t BMS_FASTCHARGE_STATUS = 0;
|
||||||
|
uint16_t HV_BATT_FC_INSU_MINUS_RES, HV_BATT_FC_INSU_PLUS_RES, HV_BATT_FC_VHL_INSU_PLUS_RES,
|
||||||
|
HV_BATT_ONLY_INSU_MINUS_RES = 0;
|
||||||
|
uint16_t MIN_ALLOW_DISCHRG_VOLTAGE = 0;
|
||||||
|
uint16_t HV_BATT_NOMINAL_DISCH_CURR_HD, HV_BATT_PEAK_DISCH_CURR_HD, HV_BATT_STABLE_DISCH_CURR_HD = 0;
|
||||||
|
uint16_t HV_BATT_NOMINAL_CHARGE_CURR_HD, HV_BATT_PEAK_CHARGE_CURR_HD, HV_BATT_STABLE_CHARGE_CURR_HD = 0;
|
||||||
|
bool RC01_PERM_SYNTH_TBMU = false;
|
||||||
|
uint16_t HV_BATT_STABLE_CHARGE_POWER_HD = 0;
|
||||||
|
uint16_t HV_BATT_STABLE_DISCH_POWER_HD = 0;
|
||||||
|
uint16_t HV_BATT_NOMINAL_DISCH_POWER_HD = 0;
|
||||||
|
uint16_t MAX_ALLOW_DISCHRG_CURRENT = 0;
|
||||||
|
|
||||||
|
unsigned long previousMillis10 = 0; //- will store last time a 10ms CAN Message was sent
|
||||||
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
|
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
|
||||||
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
|
unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
|
||||||
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||||
|
|
|
@ -383,6 +383,132 @@ class EcmpHtmlRenderer : public BatteryHtmlRenderer {
|
||||||
: String(datalayer_extended.stellantisECMP.pid_SOH_cell_1)) +
|
: String(datalayer_extended.stellantisECMP.pid_SOH_cell_1)) +
|
||||||
"</h4>";
|
"</h4>";
|
||||||
|
|
||||||
|
if (datalayer_extended.stellantisECMP.MysteryVan) {
|
||||||
|
content += "<h3>MysteryVan platform detected!</h3>";
|
||||||
|
content += "<h4>Contactor State: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.CONTACTORS_STATE == 0) {
|
||||||
|
content += "Open";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTORS_STATE == 1) {
|
||||||
|
content += "Precharge";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTORS_STATE == 2) {
|
||||||
|
content += "Closed";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
|
content += "<h4>Crash Memorized: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.CrashMemorized) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Contactor Opening Reason: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 0) {
|
||||||
|
content += "No error";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 1) {
|
||||||
|
content += "Crash!";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 2) {
|
||||||
|
content += "12V supply source undervoltage";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 3) {
|
||||||
|
content += "12V supply source overvoltage";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 4) {
|
||||||
|
content += "Battery temperature";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 5) {
|
||||||
|
content += "Interlock line open";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.CONTACTOR_OPENING_REASON == 6) {
|
||||||
|
content += "e-Service plug disconnected";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
|
content += "<h4>Battery fault type: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 0) {
|
||||||
|
content += "No fault";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 1) {
|
||||||
|
content += "FirstLevelFault: Warning Lamp";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 2) {
|
||||||
|
content += "SecondLevelFault: Stop Lamp";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 3) {
|
||||||
|
content += "ThirdLevelFault: Stop Lamp + contactor opening (EPS shutdown)";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 4) {
|
||||||
|
content += "FourthLevelFault: Stop Lamp + Active Discharge";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 5) {
|
||||||
|
content += "Inhibition of powertrain activation";
|
||||||
|
} else if (datalayer_extended.stellantisECMP.TBMU_FAULT_TYPE == 6) {
|
||||||
|
content += "Reserved";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
|
content += "<h4>FC insulation minus resistance " +
|
||||||
|
String(datalayer_extended.stellantisECMP.HV_BATT_FC_INSU_MINUS_RES) + " kOhm</h4>";
|
||||||
|
content += "<h4>FC insulation plus resistance " +
|
||||||
|
String(datalayer_extended.stellantisECMP.HV_BATT_FC_INSU_PLUS_RES) + " kOhm</h4>";
|
||||||
|
content += "<h4>FC vehicle insulation plus resistance " +
|
||||||
|
String(datalayer_extended.stellantisECMP.HV_BATT_FC_VHL_INSU_PLUS_RES) + " kOhm</h4>";
|
||||||
|
content += "<h4>FC vehicle insulation plus resistance " +
|
||||||
|
String(datalayer_extended.stellantisECMP.HV_BATT_ONLY_INSU_MINUS_RES) + " kOhm</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Battery: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_BATT) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Low SOC: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_LOW_SOC) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert High SOC: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_HIGH_SOC) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert SOC Jump: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_SOC_JUMP) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Overcharge: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_OVERCHARGE) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Temp Diff: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_TEMP_DIFF) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Temp High: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_HIGH_TEMP) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Overvoltage: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_OVERVOLTAGE) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Cell Overvoltage: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_CELL_OVERVOLTAGE) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Cell Undervoltage: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_CELL_UNDERVOLTAGE) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
|
content += "<h4>Alert Cell Poor Consistency: ";
|
||||||
|
if (datalayer_extended.stellantisECMP.ALERT_CELL_POOR_CONSIST) {
|
||||||
|
content += "Yes</h4>";
|
||||||
|
} else {
|
||||||
|
content += "No</h4>";
|
||||||
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,9 +20,11 @@ void ImievCZeroIonBattery::
|
||||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
//We do not know the max charge/discharge power is sent by the battery. We hardcode value for now.
|
//We do not know the max charge/discharge power is sent by the battery. We hardcode value for now.
|
||||||
datalayer.battery.status.max_charge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
datalayer.battery.status.max_charge_power_W =
|
||||||
|
datalayer.battery.status.override_charge_power_W; //TODO: Fix when CAN is decoded
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
datalayer.battery.status.max_discharge_power_W =
|
||||||
|
datalayer.battery.status.override_discharge_power_W; //TODO: Fix when CAN is decoded
|
||||||
|
|
||||||
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
|
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
|
||||||
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
|
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
|
||||||
|
|
|
@ -72,7 +72,7 @@ uint16_t KiaEGmpBattery::selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
|
||||||
|
|
||||||
void KiaEGmpBattery::set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell) {
|
void KiaEGmpBattery::set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell) {
|
||||||
for (size_t i = 0; i < length; i++) {
|
for (size_t i = 0; i < length; i++) {
|
||||||
if ((rx_frame.data.u8[start + i] * 20) > 1000) {
|
if ((rx_frame.data.u8[start + i] * 20) > 2600) {
|
||||||
datalayer.battery.status.cell_voltages_mV[startCell + i] = (rx_frame.data.u8[start + i] * 20);
|
datalayer.battery.status.cell_voltages_mV[startCell + i] = (rx_frame.data.u8[start + i] * 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,16 +114,17 @@ uint8_t KiaEGmpBattery::calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t
|
||||||
|
|
||||||
void KiaEGmpBattery::update_values() {
|
void KiaEGmpBattery::update_values() {
|
||||||
|
|
||||||
#ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE
|
if (user_selected_use_estimated_SOC) {
|
||||||
// Use the simplified pack-based SOC estimation with proper compensation
|
// Use the simplified pack-based SOC estimation with proper compensation
|
||||||
datalayer.battery.status.real_soc = estimateSOC(batteryVoltage, datalayer.battery.info.number_of_cells, batteryAmps);
|
datalayer.battery.status.real_soc =
|
||||||
|
estimateSOC(batteryVoltage, datalayer.battery.info.number_of_cells, batteryAmps);
|
||||||
|
|
||||||
// For comparison or fallback, we can still calculate from min/max cell voltages
|
// For comparison or fallback, we can still calculate from min/max cell voltages
|
||||||
SOC_estimated_lowest = estimateSOCFromCell(CellVoltMin_mV);
|
SOC_estimated_lowest = estimateSOCFromCell(CellVoltMin_mV);
|
||||||
SOC_estimated_highest = estimateSOCFromCell(CellVoltMax_mV);
|
SOC_estimated_highest = estimateSOCFromCell(CellVoltMax_mV);
|
||||||
#else
|
} else {
|
||||||
datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
|
datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
|
||||||
#endif
|
}
|
||||||
|
|
||||||
datalayer.battery.status.soh_pptt = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00%
|
datalayer.battery.status.soh_pptt = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00%
|
||||||
|
|
||||||
|
@ -143,12 +144,12 @@ void KiaEGmpBattery::update_values() {
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
||||||
//The allowed discharge power is not available. We hardcode this value for now
|
//The allowed discharge power is not available. We hardcode this value for now
|
||||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
|
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include "CanBattery.h"
|
#include "CanBattery.h"
|
||||||
#include "KIA-E-GMP-HTML.h"
|
#include "KIA-E-GMP-HTML.h"
|
||||||
|
|
||||||
#define ESTIMATE_SOC_FROM_CELLVOLTAGE
|
extern bool user_selected_use_estimated_SOC;
|
||||||
|
|
||||||
class KiaEGmpBattery : public CanBattery {
|
class KiaEGmpBattery : public CanBattery {
|
||||||
public:
|
public:
|
||||||
|
@ -37,8 +37,6 @@ class KiaEGmpBattery : public CanBattery {
|
||||||
static const int MAX_CELL_DEVIATION_MV = 150;
|
static const int MAX_CELL_DEVIATION_MV = 150;
|
||||||
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
|
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
|
||||||
static const int MIN_CELL_VOLTAGE_MV = 2950; //Battery is put into emergency stop if one cell goes below this value
|
static const int MIN_CELL_VOLTAGE_MV = 2950; //Battery is put into emergency stop if one cell goes below this value
|
||||||
static const int MAXCHARGEPOWERALLOWED = 10000;
|
|
||||||
static const int MAXDISCHARGEPOWERALLOWED = 10000;
|
|
||||||
static const int RAMPDOWN_SOC = 9000; // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
static const int RAMPDOWN_SOC = 9000; // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
|
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ void KiaHyundaiHybridBattery::
|
||||||
}
|
}
|
||||||
|
|
||||||
void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x5F1:
|
case 0x5F1:
|
||||||
break;
|
break;
|
||||||
|
@ -53,6 +52,8 @@ void KiaHyundaiHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
case 0x588:
|
case 0x588:
|
||||||
break;
|
break;
|
||||||
case 0x5AE:
|
case 0x5AE:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
interlock_missing = (bool)(rx_frame.data.u8[1] & 0x02) >> 1;
|
interlock_missing = (bool)(rx_frame.data.u8[1] & 0x02) >> 1;
|
||||||
break;
|
break;
|
||||||
case 0x5AF:
|
case 0x5AF:
|
||||||
|
@ -200,15 +201,21 @@ void KiaHyundaiHybridBattery::transmit_can(unsigned long currentMillis) {
|
||||||
}
|
}
|
||||||
poll_data_pid++;
|
poll_data_pid++;
|
||||||
if (poll_data_pid == 1) {
|
if (poll_data_pid == 1) {
|
||||||
transmit_can_frame(&KIA_7E4_id1);
|
KIA_7E4.data.u8[2] = 0x01;
|
||||||
|
KIA_7E4.data.u8[3] = 0x00;
|
||||||
|
transmit_can_frame(&KIA_7E4);
|
||||||
} else if (poll_data_pid == 2) {
|
} else if (poll_data_pid == 2) {
|
||||||
transmit_can_frame(&KIA_7E4_id2);
|
KIA_7E4.data.u8[2] = 0x02;
|
||||||
|
transmit_can_frame(&KIA_7E4);
|
||||||
} else if (poll_data_pid == 3) {
|
} else if (poll_data_pid == 3) {
|
||||||
transmit_can_frame(&KIA_7E4_id3);
|
KIA_7E4.data.u8[2] = 0x03;
|
||||||
|
transmit_can_frame(&KIA_7E4);
|
||||||
} else if (poll_data_pid == 4) {
|
} else if (poll_data_pid == 4) {
|
||||||
|
//Group 4 not polled
|
||||||
} else if (poll_data_pid == 5) {
|
} else if (poll_data_pid == 5) {
|
||||||
transmit_can_frame(&KIA_7E4_id5);
|
KIA_7E4.data.u8[2] = 0x05;
|
||||||
|
KIA_7E4.data.u8[3] = 0x04;
|
||||||
|
transmit_can_frame(&KIA_7E4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,26 +34,11 @@ class KiaHyundaiHybridBattery : public CanBattery {
|
||||||
uint16_t min_cell_voltage_mv = 3700;
|
uint16_t min_cell_voltage_mv = 3700;
|
||||||
uint16_t max_cell_voltage_mv = 3700;
|
uint16_t max_cell_voltage_mv = 3700;
|
||||||
|
|
||||||
CAN_frame KIA_7E4_id1 = {.FD = false,
|
CAN_frame KIA_7E4 = {.FD = false,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x7E4,
|
.ID = 0x7E4,
|
||||||
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
CAN_frame KIA_7E4_id2 = {.FD = false,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x7E4,
|
|
||||||
.data = {0x02, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame KIA_7E4_id3 = {.FD = false,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x7E4,
|
|
||||||
.data = {0x02, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame KIA_7E4_id5 = {.FD = false,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x7E4,
|
|
||||||
.data = {0x02, 0x21, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame KIA_7E4_ack = {.FD = false,
|
CAN_frame KIA_7E4_ack = {.FD = false,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
|
|
|
@ -332,6 +332,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x17F0007B: // BMS 500ms
|
case 0x17F0007B: // BMS 500ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x17F0007B;
|
can_msg_received |= RX_0x17F0007B;
|
||||||
component_protection_active = (rx_frame.data.u8[0] & 0x01);
|
component_protection_active = (rx_frame.data.u8[0] & 0x01);
|
||||||
shutdown_active = ((rx_frame.data.u8[0] & 0x02) >> 1);
|
shutdown_active = ((rx_frame.data.u8[0] & 0x02) >> 1);
|
||||||
|
@ -347,11 +348,13 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
case 0x17FE007B: // BMS - Offboard tester diag response
|
case 0x17FE007B: // BMS - Offboard tester diag response
|
||||||
break;
|
break;
|
||||||
case 0x1B00007B: // BMS - 200ms
|
case 0x1B00007B: // BMS - 200ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
wakeup_type =
|
wakeup_type =
|
||||||
((rx_frame.data.u8[1] & 0x10) >> 4); //0 passive, SG has not woken up, 1 active, SG has woken up the network
|
((rx_frame.data.u8[1] & 0x10) >> 4); //0 passive, SG has not woken up, 1 active, SG has woken up the network
|
||||||
instrumentation_cluster_request = ((rx_frame.data.u8[1] & 0x40) >> 6); //True/false
|
instrumentation_cluster_request = ((rx_frame.data.u8[1] & 0x40) >> 6); //True/false
|
||||||
break;
|
break;
|
||||||
case 0x12DD54D0: // BMS Limits 100ms
|
case 0x12DD54D0: // BMS Limits 100ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x12DD54D0;
|
can_msg_received |= RX_0x12DD54D0;
|
||||||
max_discharge_power_watt =
|
max_discharge_power_watt =
|
||||||
((rx_frame.data.u8[6] & 0x07) << 10) | (rx_frame.data.u8[5] << 2) | (rx_frame.data.u8[4] & 0xC0) >> 6; //*100
|
((rx_frame.data.u8[6] & 0x07) << 10) | (rx_frame.data.u8[5] << 2) | (rx_frame.data.u8[4] & 0xC0) >> 6; //*100
|
||||||
|
@ -361,6 +364,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
max_charge_current_amp = ((rx_frame.data.u8[4] & 0x3F) << 7) | (rx_frame.data.u8[3] >> 1); //*0.2
|
max_charge_current_amp = ((rx_frame.data.u8[4] & 0x3F) << 7) | (rx_frame.data.u8[3] >> 1); //*0.2
|
||||||
break;
|
break;
|
||||||
case 0x12DD54D1: // BMS 100ms
|
case 0x12DD54D1: // BMS 100ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x12DD54D1;
|
can_msg_received |= RX_0x12DD54D1;
|
||||||
if (rx_frame.data.u8[6] != 0xFE || rx_frame.data.u8[7] != 0xFF) { // Init state, values below invalid
|
if (rx_frame.data.u8[6] != 0xFE || rx_frame.data.u8[7] != 0xFF) { // Init state, values below invalid
|
||||||
battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05
|
battery_SOC = ((rx_frame.data.u8[3] & 0x0F) << 7) | (rx_frame.data.u8[2] >> 1); //*0.05
|
||||||
|
@ -372,6 +376,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
warning_support = (rx_frame.data.u8[1] & 0x70) >> 4;
|
warning_support = (rx_frame.data.u8[1] & 0x70) >> 4;
|
||||||
break;
|
break;
|
||||||
case 0x12DD54D2: // BMS 100ms
|
case 0x12DD54D2: // BMS 100ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x12DD54D2;
|
can_msg_received |= RX_0x12DD54D2;
|
||||||
battery_heating_active = (rx_frame.data.u8[4] & 0x40) >> 6;
|
battery_heating_active = (rx_frame.data.u8[4] & 0x40) >> 6;
|
||||||
heating_request = (rx_frame.data.u8[5] & 0xE0) >> 5;
|
heating_request = (rx_frame.data.u8[5] & 0xE0) >> 5;
|
||||||
|
@ -380,6 +385,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
power_battery_heating_req_watt = rx_frame.data.u8[7];
|
power_battery_heating_req_watt = rx_frame.data.u8[7];
|
||||||
break;
|
break;
|
||||||
case 0x1A555550: // BMS 500ms
|
case 0x1A555550: // BMS 500ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x1A555550;
|
can_msg_received |= RX_0x1A555550;
|
||||||
balancing_active = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
balancing_active = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
||||||
charging_active = (rx_frame.data.u8[2] & 0x01);
|
charging_active = (rx_frame.data.u8[2] & 0x01);
|
||||||
|
@ -389,6 +395,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
isolation_resistance_kOhm = (((rx_frame.data.u8[3] & 0x1F) << 7) | rx_frame.data.u8[2] >> 1); //*5
|
isolation_resistance_kOhm = (((rx_frame.data.u8[3] & 0x1F) << 7) | rx_frame.data.u8[2] >> 1); //*5
|
||||||
break;
|
break;
|
||||||
case 0x1A555551: // BMS 500ms
|
case 0x1A555551: // BMS 500ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x1A555551;
|
can_msg_received |= RX_0x1A555551;
|
||||||
battery_heating_installed = (rx_frame.data.u8[1] & 0x20) >> 5;
|
battery_heating_installed = (rx_frame.data.u8[1] & 0x20) >> 5;
|
||||||
error_NT_circuit = (rx_frame.data.u8[1] & 0x40) >> 6;
|
error_NT_circuit = (rx_frame.data.u8[1] & 0x40) >> 6;
|
||||||
|
@ -402,6 +409,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40
|
return_temperature_C = rx_frame.data.u8[7]; //*0,5 -40
|
||||||
break;
|
break;
|
||||||
case 0x1A5555B2: // BMS
|
case 0x1A5555B2: // BMS
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x1A5555B2;
|
can_msg_received |= RX_0x1A5555B2;
|
||||||
performance_index_discharge_peak_temperature_percentage =
|
performance_index_discharge_peak_temperature_percentage =
|
||||||
(((rx_frame.data.u8[3] & 0x07) << 6) | rx_frame.data.u8[2] >> 2); //*0.2
|
(((rx_frame.data.u8[3] & 0x07) << 6) | rx_frame.data.u8[2] >> 2); //*0.2
|
||||||
|
@ -411,6 +419,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
temperature_status_charge = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7);
|
temperature_status_charge = (((rx_frame.data.u8[2] & 0x03) << 1) | rx_frame.data.u8[1] >> 7);
|
||||||
break;
|
break;
|
||||||
case 0x16A954A6: // BMS
|
case 0x16A954A6: // BMS
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
can_msg_received |= RX_0x16A954A6;
|
can_msg_received |= RX_0x16A954A6;
|
||||||
BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
BMS_16A954A6_counter = (rx_frame.data.u8[1] & 0x0F); // Can be used to check CAN signal integrity later on
|
||||||
isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5;
|
isolation_fault = (rx_frame.data.u8[2] & 0xE0) >> 5;
|
||||||
|
@ -425,10 +434,12 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x16A954F8: // BMS
|
case 0x16A954F8: // BMS
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
predicted_power_dyn_standard_watt = ((rx_frame.data.u8[6] << 1) | rx_frame.data.u8[5] >> 7); //*50
|
predicted_power_dyn_standard_watt = ((rx_frame.data.u8[6] << 1) | rx_frame.data.u8[5] >> 7); //*50
|
||||||
predicted_time_dyn_standard_minutes = rx_frame.data.u8[7];
|
predicted_time_dyn_standard_minutes = rx_frame.data.u8[7];
|
||||||
break;
|
break;
|
||||||
case 0x16A954E8: // BMS Temperature and cellvoltages - 180ms
|
case 0x16A954E8: // BMS Temperature and cellvoltages - 180ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
mux = (rx_frame.data.u8[0] & 0x0F);
|
mux = (rx_frame.data.u8[0] & 0x0F);
|
||||||
switch (mux) {
|
switch (mux) {
|
||||||
case 0: // Temperatures 1-56. Value is 0xFD if sensor not present
|
case 0: // Temperatures 1-56. Value is 0xFD if sensor not present
|
||||||
|
@ -1260,12 +1271,11 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x18DAF105:
|
case 0x18DAF105:
|
||||||
handle_obd_frame(rx_frame);
|
handle_obd_frame(rx_frame, can_interface);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
if (can_msg_received == 0xFFFF && nof_cells_determined) {
|
if (can_msg_received == 0xFFFF && nof_cells_determined) {
|
||||||
if (datalayer.battery.status.real_bms_status == BMS_DISCONNECTED)
|
if (datalayer.battery.status.real_bms_status == BMS_DISCONNECTED)
|
||||||
datalayer.battery.status.real_bms_status = BMS_STANDBY;
|
datalayer.battery.status.real_bms_status = BMS_STANDBY;
|
||||||
|
|
|
@ -34,7 +34,6 @@ void Mg5Battery::
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void Mg5Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x171: //Following messages were detected on a MG5 battery BMS
|
case 0x171: //Following messages were detected on a MG5 battery BMS
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "../devboard/utils/logging.h"
|
#include "../devboard/utils/logging.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
MG HS PHEV 16.6kWh battery integration
|
MG HS PHEV 16.6kWh battery integration
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ void PylonBattery::update_values() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PylonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void PylonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x7310:
|
case 0x7310:
|
||||||
case 0x7311:
|
case 0x7311:
|
||||||
|
@ -55,6 +54,7 @@ void PylonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
break;
|
break;
|
||||||
case 0x4210:
|
case 0x4210:
|
||||||
case 0x4211:
|
case 0x4211:
|
||||||
|
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
voltage_dV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
voltage_dV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||||
current_dA = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 30000;
|
current_dA = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 30000;
|
||||||
SOC = rx_frame.data.u8[6];
|
SOC = rx_frame.data.u8[6];
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "../communication/can/comm_can.h"
|
#include "../communication/can/comm_can.h"
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
- LOG files from vehicle needed to determine CAN content needed to send towards battery!
|
- LOG files from vehicle needed to determine CAN content needed to send towards battery!
|
||||||
- BCCM_PMZ_A (0x18B 50ms)
|
- BCCM_PMZ_A (0x18B 50ms)
|
||||||
|
@ -76,7 +77,6 @@ void RangeRoverPhevBattery::update_values() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RangeRoverPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void RangeRoverPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x080: // 15ms
|
case 0x080: // 15ms
|
||||||
StatusCAT5BPOChg = (rx_frame.data.u8[0] & 0x01);
|
StatusCAT5BPOChg = (rx_frame.data.u8[0] & 0x01);
|
||||||
|
@ -104,6 +104,7 @@ void RangeRoverPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
DischargeContPwrLmt = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
DischargeContPwrLmt = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||||
break;
|
break;
|
||||||
case 0x102: // 20ms
|
case 0x102: // 20ms
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
PwrGpCS = rx_frame.data.u8[0];
|
PwrGpCS = rx_frame.data.u8[0];
|
||||||
PwrGpCounter = (rx_frame.data.u8[1] & 0x3C) >> 2;
|
PwrGpCounter = (rx_frame.data.u8[1] & 0x3C) >> 2;
|
||||||
VoltageExt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]);
|
VoltageExt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]);
|
||||||
|
|
|
@ -52,9 +52,10 @@ void RenaultTwizyBattery::update_values() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenaultTwizyBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void RenaultTwizyBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x155:
|
case 0x155:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
// max charge power is in steps of 300W from 0 to 7
|
// max charge power is in steps of 300W from 0 to 7
|
||||||
max_charge_power = (uint16_t)rx_frame.data.u8[0] * 300;
|
max_charge_power = (uint16_t)rx_frame.data.u8[0] * 300;
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,10 @@ class RenaultZoeGen1Battery : public CanBattery {
|
||||||
private:
|
private:
|
||||||
RenaultZoeGen1HtmlRenderer renderer;
|
RenaultZoeGen1HtmlRenderer renderer;
|
||||||
|
|
||||||
static const int MAX_PACK_VOLTAGE_DV = 4200; //5000 = 500.0V
|
static const int MAX_PACK_VOLTAGE_DV = 4040; //5000 = 500.0V
|
||||||
static const int MIN_PACK_VOLTAGE_DV = 3000;
|
static const int MIN_PACK_VOLTAGE_DV = 3000;
|
||||||
static const int MAX_CELL_DEVIATION_MV = 150;
|
static const int MAX_CELL_DEVIATION_MV = 150;
|
||||||
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
|
static const int MAX_CELL_VOLTAGE_MV = 4220; //Battery is put into emergency stop if one cell goes over this value
|
||||||
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
|
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
|
||||||
|
|
||||||
DATALAYER_BATTERY_TYPE* datalayer_battery;
|
DATALAYER_BATTERY_TYPE* datalayer_battery;
|
||||||
|
|
159
Software/src/battery/RIVIAN-BATTERY.cpp
Normal file
159
Software/src/battery/RIVIAN-BATTERY.cpp
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
#include "RIVIAN-BATTERY.h"
|
||||||
|
|
||||||
|
#include "../battery/BATTERIES.h"
|
||||||
|
#include "../communication/can/comm_can.h"
|
||||||
|
#include "../datalayer/datalayer.h"
|
||||||
|
#include "../devboard/utils/events.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initial support for Rivian BIG battery (135kWh)
|
||||||
|
The battery has 3x CAN channels
|
||||||
|
- Failover CAN (CAN-FD) lots of content, not required for operation. Has all cellvoltages!
|
||||||
|
- Platform CAN (500kbps) with all the control messages needed to control the battery <- This is the one we want
|
||||||
|
- Battery CAN (500kbps) lots of content, not required for operation
|
||||||
|
*/
|
||||||
|
|
||||||
|
void RivianBattery::update_values() {
|
||||||
|
|
||||||
|
datalayer.battery.status.real_soc = battery_SOC;
|
||||||
|
|
||||||
|
datalayer.battery.status.soh_pptt;
|
||||||
|
|
||||||
|
datalayer.battery.status.voltage_dV = battery_voltage;
|
||||||
|
datalayer.battery.status.current_dA = ((int16_t)battery_current / 10.0 - 3200) * 10;
|
||||||
|
|
||||||
|
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||||
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
|
datalayer.battery.status.max_charge_power_W = ((battery_voltage / 10) * battery_charge_limit_amp);
|
||||||
|
datalayer.battery.status.max_discharge_power_W = ((battery_voltage / 10) * battery_discharge_limit_amp);
|
||||||
|
|
||||||
|
//datalayer.battery.status.cell_min_voltage_mV = 3700; //TODO: Take from failover CAN?
|
||||||
|
//datalayer.battery.status.cell_max_voltage_mV = 3700; //TODO: Take from failover CAN?
|
||||||
|
|
||||||
|
datalayer.battery.status.temperature_min_dC = battery_min_temperature * 10;
|
||||||
|
datalayer.battery.status.temperature_max_dC = battery_max_temperature * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RivianBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
|
switch (rx_frame.ID) {
|
||||||
|
case 0x160: //Current [Platform CAN]+
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
battery_current = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||||
|
break;
|
||||||
|
case 0x151: //Celltemps (requires other CAN channel)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
break;
|
||||||
|
case 0x120: //Voltages [Platform CAN]+
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
battery_voltage = (((rx_frame.data.u8[7] & 0x1F) << 8) | rx_frame.data.u8[6]);
|
||||||
|
break;
|
||||||
|
case 0x25A: //SOC and kWh [Platform CAN]+
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
//battery_SOC = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]);
|
||||||
|
kWh_available_max =
|
||||||
|
(((rx_frame.data.u8[3] & 0x03) << 14) | (rx_frame.data.u8[2] << 6) | (rx_frame.data.u8[1] >> 2)) / 200;
|
||||||
|
kWh_available_total =
|
||||||
|
(((rx_frame.data.u8[5] & 0x03) << 14) | (rx_frame.data.u8[4] << 6) | (rx_frame.data.u8[3] >> 2)) / 200;
|
||||||
|
break;
|
||||||
|
case 0x405: //State [Platform CAN]+
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
BMS_state = (rx_frame.data.u8[0] & 0x03);
|
||||||
|
break;
|
||||||
|
case 0x100: //Discharge/Charge speed
|
||||||
|
battery_charge_limit_amp =
|
||||||
|
(((rx_frame.data.u8[3] & 0x0F) << 8) | (rx_frame.data.u8[2] << 4) | (rx_frame.data.u8[1] >> 4)) / 20;
|
||||||
|
battery_discharge_limit_amp =
|
||||||
|
(((rx_frame.data.u8[5] & 0x0F) << 8) | (rx_frame.data.u8[4] << 4) | (rx_frame.data.u8[3] >> 4)) / 20;
|
||||||
|
break;
|
||||||
|
case 0x153: //Temperatures
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
battery_max_temperature = (rx_frame.data.u8[5] / 2) - 40;
|
||||||
|
battery_min_temperature = (rx_frame.data.u8[6] / 2) - 40;
|
||||||
|
break;
|
||||||
|
case 0x55B: //Temperatures
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
battery_SOC = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RivianBattery::transmit_can(unsigned long currentMillis) {
|
||||||
|
// Send 500ms CAN Message, too fast and the pack can't change states (pre-charge is built in, seems we can't change state during pre-charge)
|
||||||
|
// 100ms seems to draw too much current for a 5A supply during contactor pull in
|
||||||
|
if (currentMillis - previousMillis10 >= (INTERVAL_200_MS)) {
|
||||||
|
previousMillis10 = currentMillis;
|
||||||
|
|
||||||
|
//If we want to close contactors, and preconditions are met
|
||||||
|
if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) {
|
||||||
|
//Standby -> Ready Mode
|
||||||
|
if (BMS_state == STANDBY || BMS_state == SLEEP) {
|
||||||
|
RIVIAN_150.data.u8[0] = 0x03;
|
||||||
|
RIVIAN_150.data.u8[2] = 0x01;
|
||||||
|
RIVIAN_420.data.u8[0] = 0x02;
|
||||||
|
RIVIAN_200.data.u8[0] = 0x08;
|
||||||
|
|
||||||
|
transmit_can_frame(&RIVIAN_150);
|
||||||
|
transmit_can_frame(&RIVIAN_420);
|
||||||
|
transmit_can_frame(&RIVIAN_41F);
|
||||||
|
transmit_can_frame(&RIVIAN_207);
|
||||||
|
transmit_can_frame(&RIVIAN_200);
|
||||||
|
}
|
||||||
|
//Ready mode -> Go Mode
|
||||||
|
|
||||||
|
if (BMS_state == READY) {
|
||||||
|
RIVIAN_150.data.u8[0] = 0x3E;
|
||||||
|
RIVIAN_150.data.u8[2] = 0x03;
|
||||||
|
RIVIAN_420.data.u8[0] = 0x03;
|
||||||
|
|
||||||
|
transmit_can_frame(&RIVIAN_150);
|
||||||
|
transmit_can_frame(&RIVIAN_420);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { //If we want to open contactors, transition the other way
|
||||||
|
//Go mode -> Ready Mode
|
||||||
|
|
||||||
|
if (BMS_state == GO) {
|
||||||
|
RIVIAN_150.data.u8[0] = 0x03;
|
||||||
|
RIVIAN_150.data.u8[2] = 0x01;
|
||||||
|
|
||||||
|
transmit_can_frame(&RIVIAN_150);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BMS_state == READY) {
|
||||||
|
RIVIAN_150.data.u8[0] = 0x03;
|
||||||
|
RIVIAN_150.data.u8[2] = 0x01;
|
||||||
|
RIVIAN_420.data.u8[0] = 0x01;
|
||||||
|
RIVIAN_200.data.u8[0] = 0x10;
|
||||||
|
|
||||||
|
transmit_can_frame(&RIVIAN_245);
|
||||||
|
transmit_can_frame(&RIVIAN_150);
|
||||||
|
transmit_can_frame(&RIVIAN_420);
|
||||||
|
transmit_can_frame(&RIVIAN_41F);
|
||||||
|
transmit_can_frame(&RIVIAN_200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//disabled this because the battery didn't like it so fast (slowed to 100ms) and because the battery couldn't change states fast enough
|
||||||
|
//as much as I don't like the "free-running" aspect of it, checking the BMS_state and acting on it should fix issues caused by that.
|
||||||
|
//transmit_can_frame(&RIVIAN_150);
|
||||||
|
//transmit_can_frame(&RIVIAN_420);
|
||||||
|
//transmit_can_frame(&RIVIAN_41F);
|
||||||
|
//transmit_can_frame(&RIVIAN_207);
|
||||||
|
//transmit_can_frame(&RIVIAN_200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RivianBattery::setup(void) { // Performs one time setup at startup
|
||||||
|
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||||
|
datalayer.system.info.battery_protocol[63] = '\0';
|
||||||
|
datalayer.battery.info.number_of_cells = 108;
|
||||||
|
datalayer.battery.info.total_capacity_Wh = 135000;
|
||||||
|
datalayer.battery.info.chemistry = NMC;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||||
|
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||||
|
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||||
|
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||||
|
}
|
60
Software/src/battery/RIVIAN-BATTERY.h
Normal file
60
Software/src/battery/RIVIAN-BATTERY.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#ifndef RIVIAN_BATTERY_H
|
||||||
|
#define RIVIAN_BATTERY_H
|
||||||
|
#include "CanBattery.h"
|
||||||
|
|
||||||
|
class RivianBattery : public CanBattery {
|
||||||
|
public:
|
||||||
|
virtual void setup(void);
|
||||||
|
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||||
|
virtual void update_values();
|
||||||
|
virtual void transmit_can(unsigned long currentMillis);
|
||||||
|
static constexpr const char* Name = "Rivian R1T large 135kWh battery";
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const int MAX_PACK_VOLTAGE_DV = 4480;
|
||||||
|
static const int MIN_PACK_VOLTAGE_DV = 2920;
|
||||||
|
static const int MAX_CELL_DEVIATION_MV = 150;
|
||||||
|
static const int MAX_CELL_VOLTAGE_MV = 4200; //Battery is put into emergency stop if one cell goes over this value
|
||||||
|
static const int MIN_CELL_VOLTAGE_MV = 3300; //Battery is put into emergency stop if one cell goes below this value
|
||||||
|
|
||||||
|
uint8_t BMS_state = 0;
|
||||||
|
uint16_t battery_voltage = 3700;
|
||||||
|
uint16_t battery_SOC = 5000;
|
||||||
|
int32_t battery_current = 32000;
|
||||||
|
uint16_t kWh_available_total = 135;
|
||||||
|
uint16_t kWh_available_max = 135;
|
||||||
|
int16_t battery_min_temperature = 0;
|
||||||
|
int16_t battery_max_temperature = 0;
|
||||||
|
uint16_t battery_discharge_limit_amp = 0;
|
||||||
|
uint16_t battery_charge_limit_amp = 0;
|
||||||
|
static const uint8_t SLEEP = 0;
|
||||||
|
static const uint8_t STANDBY = 1;
|
||||||
|
static const uint8_t READY = 2;
|
||||||
|
static const uint8_t GO = 3;
|
||||||
|
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
||||||
|
|
||||||
|
CAN_frame RIVIAN_150 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 6,
|
||||||
|
.ID = 0x150,
|
||||||
|
.data = {0x03, 0x00, 0x01, 0x00, 0x01, 0x00}};
|
||||||
|
CAN_frame RIVIAN_420 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x420,
|
||||||
|
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame RIVIAN_41F = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x41F, .data = {0x62, 0x10, 0x00}};
|
||||||
|
CAN_frame RIVIAN_245 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 6,
|
||||||
|
.ID = 0x245,
|
||||||
|
.data = {0x10, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame RIVIAN_200 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x200, .data = {0x08}};
|
||||||
|
CAN_frame RIVIAN_207 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 1,
|
||||||
|
.ID = 0x207,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00}};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -33,20 +33,20 @@ void RjxzsBms::update_values() {
|
||||||
datalayer.battery.status.current_dA = total_current;
|
datalayer.battery.status.current_dA = total_current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge power is set in .h file
|
// Charge power is manually set
|
||||||
if (datalayer.battery.status.real_soc > 9900) {
|
if (datalayer.battery.status.real_soc > 9900) {
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
|
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
|
||||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
MAX_CHARGE_POWER_ALLOWED_W *
|
datalayer.battery.status.override_charge_power_W *
|
||||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discharge power is also set in .h file
|
// Discharge power is manually set
|
||||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
uint16_t temperatures[] = {
|
uint16_t temperatures[] = {
|
||||||
module_1_temperature, module_2_temperature, module_3_temperature, module_4_temperature,
|
module_1_temperature, module_2_temperature, module_3_temperature, module_4_temperature,
|
||||||
|
@ -140,262 +140,18 @@ void RjxzsBms::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
charging_active = false;
|
charging_active = false;
|
||||||
discharging_active = true;
|
discharging_active = true;
|
||||||
}
|
}
|
||||||
} else if (mux == 0x07) { // Cellvoltages 1-3
|
} else if (mux >= 0x07 && mux <= 0x46) {
|
||||||
cellvoltages[0] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
// Cell voltages 1-192 (3 per message, 0x07=1-3, 0x08=4-6, ..., 0x46=190-192)
|
||||||
cellvoltages[1] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
int cell_index = (mux - 0x07) * 3;
|
||||||
cellvoltages[2] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
for (int i = 0; i < 3; i++) {
|
||||||
} else if (mux == 0x08) { // Cellvoltages 4-6
|
if (cell_index + i >= MAX_AMOUNT_CELLS) {
|
||||||
cellvoltages[3] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
break;
|
||||||
cellvoltages[4] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
}
|
||||||
cellvoltages[5] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
cellvoltages[cell_index + i] = (rx_frame.data.u8[1 + i * 2] << 8) | rx_frame.data.u8[2 + i * 2];
|
||||||
} else if (mux == 0x09) { // Cellvoltages 7-9
|
}
|
||||||
cellvoltages[6] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
if (cell_index + 2 >= populated_cellvoltages) {
|
||||||
cellvoltages[7] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
populated_cellvoltages = cell_index + 2 + 1;
|
||||||
cellvoltages[8] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
}
|
||||||
} else if (mux == 0x0A) { // Cellvoltages 10-12
|
|
||||||
cellvoltages[9] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[10] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[11] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x0B) { // Cellvoltages 13-15
|
|
||||||
cellvoltages[12] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[13] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[14] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x0C) { // Cellvoltages 16-18
|
|
||||||
cellvoltages[15] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[16] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[17] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x0D) { // Cellvoltages 19-21
|
|
||||||
cellvoltages[18] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[19] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[20] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x0E) { // Cellvoltages 22-24
|
|
||||||
cellvoltages[21] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[22] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[23] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x0F) { // Cellvoltages 25-27
|
|
||||||
cellvoltages[24] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[25] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[26] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x10) { // Cellvoltages 28-30
|
|
||||||
cellvoltages[27] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[28] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[29] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x11) { // Cellvoltages 31-33
|
|
||||||
cellvoltages[30] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
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];
|
|
||||||
} else if (mux == 0x12) { // Cellvoltages 34-36
|
|
||||||
cellvoltages[33] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[34] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[35] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x13) { // Cellvoltages 37-39
|
|
||||||
cellvoltages[36] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[37] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[38] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x14) { // Cellvoltages 40-42
|
|
||||||
cellvoltages[39] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[40] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[41] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x15) { // Cellvoltages 43-45
|
|
||||||
cellvoltages[42] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[43] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[44] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x16) { // Cellvoltages 46-48
|
|
||||||
cellvoltages[45] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[46] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[47] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x17) { // Cellvoltages 49-51
|
|
||||||
cellvoltages[48] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[49] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[50] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x18) { // Cellvoltages 52-54
|
|
||||||
cellvoltages[51] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[52] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[53] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x19) { // Cellvoltages 55-57
|
|
||||||
cellvoltages[54] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[55] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[56] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x1A) { // Cellvoltages 58-60
|
|
||||||
cellvoltages[57] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[58] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[59] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x1B) { // Cellvoltages 61-63
|
|
||||||
cellvoltages[60] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[61] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[62] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x1C) { // Cellvoltages 64-66
|
|
||||||
cellvoltages[63] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[64] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[65] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x1D) { // Cellvoltages 67-69
|
|
||||||
cellvoltages[66] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[67] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[68] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x1E) { // Cellvoltages 70-72
|
|
||||||
cellvoltages[69] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[70] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[71] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x1F) { // Cellvoltages 73-75
|
|
||||||
cellvoltages[72] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[73] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[74] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x20) { // Cellvoltages 76-78
|
|
||||||
cellvoltages[75] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[76] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[77] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x21) { // Cellvoltages 79-81
|
|
||||||
cellvoltages[78] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[79] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[80] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x22) { // Cellvoltages 82-84
|
|
||||||
cellvoltages[81] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[82] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[83] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x23) { // Cellvoltages 85-87
|
|
||||||
cellvoltages[84] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[85] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[86] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x24) { // Cellvoltages 88-90
|
|
||||||
cellvoltages[87] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[88] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[89] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x25) { // Cellvoltages 91-93
|
|
||||||
cellvoltages[90] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[91] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[92] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x26) { // Cellvoltages 94-96
|
|
||||||
cellvoltages[93] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[94] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[95] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x27) { // Cellvoltages 97-99
|
|
||||||
cellvoltages[96] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[97] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[98] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x28) { // Cellvoltages 100-102
|
|
||||||
cellvoltages[99] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[100] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[101] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x29) { // Cellvoltages 103-105
|
|
||||||
cellvoltages[102] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[103] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[104] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x2A) { // Cellvoltages 106-108
|
|
||||||
cellvoltages[105] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[106] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[107] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x2B) { // Cellvoltages 109-111
|
|
||||||
cellvoltages[108] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[109] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[110] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x2C) { // Cellvoltages 112-114
|
|
||||||
cellvoltages[111] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[112] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[113] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x2D) { // Cellvoltages 115-117
|
|
||||||
cellvoltages[114] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[115] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[116] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x2E) { // Cellvoltages 118-120
|
|
||||||
cellvoltages[117] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[118] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[119] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x2F) { // Cellvoltages 121-123
|
|
||||||
cellvoltages[120] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[121] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[122] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x30) { // Cellvoltages 124-126
|
|
||||||
cellvoltages[123] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[124] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[125] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x31) { // Cellvoltages 127-129
|
|
||||||
cellvoltages[126] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[127] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[128] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x32) { // Cellvoltages 130-132
|
|
||||||
cellvoltages[129] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[130] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[131] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x33) { // Cellvoltages 133-135
|
|
||||||
cellvoltages[132] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[133] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[134] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x34) { // Cellvoltages 136-138
|
|
||||||
cellvoltages[135] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[136] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[137] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x35) { // Cellvoltages 139-141
|
|
||||||
cellvoltages[138] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[139] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[140] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x36) { // Cellvoltages 142-144
|
|
||||||
cellvoltages[141] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[142] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[143] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x37) { // Cellvoltages 145-147
|
|
||||||
cellvoltages[144] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[145] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[146] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x38) { // Cellvoltages 148-150
|
|
||||||
cellvoltages[147] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[148] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[149] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x39) { // Cellvoltages 151-153
|
|
||||||
cellvoltages[150] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[151] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[152] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x3A) { // Cellvoltages 154-156
|
|
||||||
cellvoltages[153] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[154] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[155] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x3B) { // Cellvoltages 157-159
|
|
||||||
cellvoltages[156] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[157] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[158] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x3C) { // Cellvoltages 160-162
|
|
||||||
cellvoltages[159] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[160] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[161] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x3D) { // Cellvoltages 163-165
|
|
||||||
cellvoltages[162] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[163] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[164] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x3E) { // Cellvoltages 166-167
|
|
||||||
cellvoltages[165] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[166] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[167] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x3F) { // Cellvoltages 169-171
|
|
||||||
cellvoltages[168] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[169] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[170] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x40) { // Cellvoltages 172-174
|
|
||||||
cellvoltages[171] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[172] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[173] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x41) { // Cellvoltages 175-177
|
|
||||||
cellvoltages[174] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[175] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[176] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x42) { // Cellvoltages 178-180
|
|
||||||
cellvoltages[177] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[178] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[179] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x43) { // Cellvoltages 181-183
|
|
||||||
cellvoltages[180] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[181] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[182] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x44) { // Cellvoltages 184-186
|
|
||||||
cellvoltages[183] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[184] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[185] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x45) { // Cellvoltages 187-189
|
|
||||||
cellvoltages[186] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[187] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[188] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x46) { // Cellvoltages 190-192
|
|
||||||
cellvoltages[189] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
|
|
||||||
cellvoltages[190] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
|
||||||
cellvoltages[191] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
|
|
||||||
} else if (mux == 0x47) {
|
} else if (mux == 0x47) {
|
||||||
temperature_below_zero_mod1_4 = rx_frame.data.u8[2];
|
temperature_below_zero_mod1_4 = rx_frame.data.u8[2];
|
||||||
module_1_temperature = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
module_1_temperature = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
||||||
|
|
|
@ -15,9 +15,6 @@ class RjxzsBms : public CanBattery {
|
||||||
static constexpr const char* Name = "RJXZS BMS, DIY battery";
|
static constexpr const char* Name = "RJXZS BMS, DIY battery";
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* Tweak these according to your battery build */
|
|
||||||
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 5000;
|
|
||||||
static const int MAX_CHARGE_POWER_ALLOWED_W = 5000;
|
|
||||||
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
||||||
static const int RAMPDOWN_SOC =
|
static const int RAMPDOWN_SOC =
|
||||||
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "../communication/can/comm_can.h"
|
#include "../communication/can/comm_can.h"
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
|
|
||||||
/* Credits go to maciek16c for these findings!
|
/* Credits go to maciek16c for these findings!
|
||||||
https://github.com/maciek16c/hyundai-santa-fe-phev-battery
|
https://github.com/maciek16c/hyundai-santa-fe-phev-battery
|
||||||
https://openinverter.org/forum/viewtopic.php?p=62256
|
https://openinverter.org/forum/viewtopic.php?p=62256
|
||||||
|
|
|
@ -40,9 +40,10 @@ void SimpBmsBattery::update_values() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpBmsBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void SimpBmsBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x355:
|
case 0x355:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
SOC = (rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0];
|
SOC = (rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0];
|
||||||
SOH = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
|
SOH = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
|
||||||
|
|
||||||
|
|
|
@ -113,33 +113,6 @@ inline const char* getHvilStatusState(int index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char* getBMSState(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "STANDBY";
|
|
||||||
case 1:
|
|
||||||
return "DRIVE";
|
|
||||||
case 2:
|
|
||||||
return "SUPPORT";
|
|
||||||
case 3:
|
|
||||||
return "CHARGE";
|
|
||||||
case 4:
|
|
||||||
return "FEIM";
|
|
||||||
case 5:
|
|
||||||
return "CLEAR_FAULT";
|
|
||||||
case 6:
|
|
||||||
return "FAULT";
|
|
||||||
case 7:
|
|
||||||
return "WELD";
|
|
||||||
case 8:
|
|
||||||
return "TEST";
|
|
||||||
case 9:
|
|
||||||
return "SNA";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getBMSContactorState(int index) {
|
inline const char* getBMSContactorState(int index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -161,174 +134,10 @@ inline const char* getBMSContactorState(int index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char* getBMSHvState(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "DOWN";
|
|
||||||
case 1:
|
|
||||||
return "COMING_UP";
|
|
||||||
case 2:
|
|
||||||
return "GOING_DOWN";
|
|
||||||
case 3:
|
|
||||||
return "UP_FOR_DRIVE";
|
|
||||||
case 4:
|
|
||||||
return "UP_FOR_CHARGE";
|
|
||||||
case 5:
|
|
||||||
return "UP_FOR_DC_CHARGE";
|
|
||||||
case 6:
|
|
||||||
return "UP";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getBMSUiChargeStatus(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "DISCONNECTED";
|
|
||||||
case 1:
|
|
||||||
return "NO_POWER";
|
|
||||||
case 2:
|
|
||||||
return "ABOUT_TO_CHARGE";
|
|
||||||
case 3:
|
|
||||||
return "CHARGING";
|
|
||||||
case 4:
|
|
||||||
return "CHARGE_COMPLETE";
|
|
||||||
case 5:
|
|
||||||
return "CHARGE_STOPPED";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getPCS_DcdcStatus(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "IDLE";
|
|
||||||
case 1:
|
|
||||||
return "ACTIVE";
|
|
||||||
case 2:
|
|
||||||
return "FAULTED";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getPCS_DcdcMainState(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "STANDBY";
|
|
||||||
case 1:
|
|
||||||
return "12V_SUPPORT_ACTIVE";
|
|
||||||
case 2:
|
|
||||||
return "PRECHARGE_STARTUP";
|
|
||||||
case 3:
|
|
||||||
return "PRECHARGE_ACTIVE";
|
|
||||||
case 4:
|
|
||||||
return "DIS_HVBUS_ACTIVE";
|
|
||||||
case 5:
|
|
||||||
return "SHUTDOWN";
|
|
||||||
case 6:
|
|
||||||
return "FAULTED";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getPCS_DcdcSubState(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "PWR_UP_INIT";
|
|
||||||
case 1:
|
|
||||||
return "STANDBY";
|
|
||||||
case 2:
|
|
||||||
return "12V_SUPPORT_ACTIVE";
|
|
||||||
case 3:
|
|
||||||
return "DIS_HVBUS";
|
|
||||||
case 4:
|
|
||||||
return "PCHG_FAST_DIS_HVBUS";
|
|
||||||
case 5:
|
|
||||||
return "PCHG_SLOW_DIS_HVBUS";
|
|
||||||
case 6:
|
|
||||||
return "PCHG_DWELL_CHARGE";
|
|
||||||
case 7:
|
|
||||||
return "PCHG_DWELL_WAIT";
|
|
||||||
case 8:
|
|
||||||
return "PCHG_DI_RECOVERY_WAIT";
|
|
||||||
case 9:
|
|
||||||
return "PCHG_ACTIVE";
|
|
||||||
case 10:
|
|
||||||
return "PCHG_FLT_FAST_DIS_HVBUS";
|
|
||||||
case 11:
|
|
||||||
return "SHUTDOWN";
|
|
||||||
case 12:
|
|
||||||
return "12V_SUPPORT_FAULTED";
|
|
||||||
case 13:
|
|
||||||
return "DIS_HVBUS_FAULTED";
|
|
||||||
case 14:
|
|
||||||
return "PCHG_FAULTED";
|
|
||||||
case 15:
|
|
||||||
return "CLEAR_FAULTS";
|
|
||||||
case 16:
|
|
||||||
return "FAULTED";
|
|
||||||
case 17:
|
|
||||||
return "NUM";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getBMSPowerLimitState(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "NOT_CALCULATED_FOR_DRIVE";
|
|
||||||
case 1:
|
|
||||||
return "CALCULATED_FOR_DRIVE";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getHVPStatus(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "INVALID";
|
|
||||||
case 1:
|
|
||||||
return "NOT_AVAILABLE";
|
|
||||||
case 2:
|
|
||||||
return "STALE";
|
|
||||||
case 3:
|
|
||||||
return "VALID";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getHVPContactor(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return "NOT_ACTIVE";
|
|
||||||
case 1:
|
|
||||||
return "ACTIVE";
|
|
||||||
case 2:
|
|
||||||
return "COMPLETED";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getFalseTrue(bool value) {
|
|
||||||
return value ? "True" : "False";
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* getNoYes(bool value) {
|
inline const char* getNoYes(bool value) {
|
||||||
return value ? "Yes" : "No";
|
return value ? "Yes" : "No";
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char* getFault(bool value) {
|
|
||||||
return value ? "ACTIVE" : "NOT_ACTIVE";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp DLC to 0–8 bytes for classic CAN
|
// Clamp DLC to 0–8 bytes for classic CAN
|
||||||
inline int getDataLen(uint8_t dlc) {
|
inline int getDataLen(uint8_t dlc) {
|
||||||
return std::min<int>(dlc, 8);
|
return std::min<int>(dlc, 8);
|
||||||
|
@ -627,8 +436,8 @@ void TeslaBattery::
|
||||||
// Define the allowed discharge power
|
// Define the allowed discharge power
|
||||||
datalayer.battery.status.max_discharge_power_W = (battery_max_discharge_current * (battery_volts / 10));
|
datalayer.battery.status.max_discharge_power_W = (battery_max_discharge_current * (battery_volts / 10));
|
||||||
// Cap the allowed discharge power if higher than the maximum discharge power allowed
|
// Cap the allowed discharge power if higher than the maximum discharge power allowed
|
||||||
if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
|
if (datalayer.battery.status.max_discharge_power_W > datalayer.battery.status.override_discharge_power_W) {
|
||||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
//The allowed charge power behaves strangely. We instead estimate this value
|
//The allowed charge power behaves strangely. We instead estimate this value
|
||||||
|
@ -649,7 +458,7 @@ void TeslaBattery::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = battery_min_temp;
|
datalayer.battery.status.temperature_min_dC = battery_min_temp;
|
||||||
|
@ -660,8 +469,6 @@ void TeslaBattery::
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV = battery_cell_min_v;
|
datalayer.battery.status.cell_min_voltage_mV = battery_cell_min_v;
|
||||||
|
|
||||||
battery_cell_deviation_mV = (battery_cell_max_v - battery_cell_min_v);
|
|
||||||
|
|
||||||
/* Value mapping is completed. Start to check all safeties */
|
/* Value mapping is completed. Start to check all safeties */
|
||||||
|
|
||||||
//INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
|
//INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
|
||||||
|
@ -676,11 +483,24 @@ void TeslaBattery::
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_BATTERY_FUSE);
|
clear_event(EVENT_BATTERY_FUSE);
|
||||||
}
|
}
|
||||||
|
// Raise any Tesla BMS events in BE
|
||||||
|
// Events: Informational
|
||||||
|
if (BMS_a145_SW_SOC_Change) { // BMS has newly recalibrated pack SOC
|
||||||
|
set_event_latched(EVENT_BATTERY_SOC_RECALIBRATION, 0); // Latcched as BMS_a145 can be active for a while
|
||||||
|
} else if (!BMS_a145_SW_SOC_Change) {
|
||||||
|
clear_event(EVENT_BATTERY_SOC_RECALIBRATION);
|
||||||
|
}
|
||||||
|
// Events: Warning
|
||||||
|
if (BMS_contactorState == 5) { // BMS has detected welded contactor(s)
|
||||||
|
set_event_latched(EVENT_CONTACTOR_WELDED, 0);
|
||||||
|
} else if (BMS_contactorState != 5) {
|
||||||
|
clear_event(EVENT_CONTACTOR_WELDED);
|
||||||
|
}
|
||||||
|
|
||||||
if (user_selected_tesla_GTW_chassisType > 1) { //{{0, "Model S"}, {1, "Model X"}, {2, "Model 3"}, {3, "Model Y"}};
|
if (user_selected_tesla_GTW_chassisType > 1) { //{{0, "Model S"}, {1, "Model X"}, {2, "Model 3"}, {3, "Model Y"}};
|
||||||
// Autodetect algoritm for chemistry on 3/Y packs.
|
// Autodetect algorithm for chemistry on 3/Y packs.
|
||||||
// NCM/A batteries have 96s, LFP has 102-108s
|
// NCM/A batteries have 96s, LFP has 102-108s
|
||||||
// Drawback with this check is that it takes 3-5minutes before all cells have been counted!
|
// Drawback with this check is that it takes 3-5 minutes before all cells have been counted!
|
||||||
if (datalayer.battery.info.number_of_cells > 101) {
|
if (datalayer.battery.info.number_of_cells > 101) {
|
||||||
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
|
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
|
||||||
}
|
}
|
||||||
|
@ -721,23 +541,28 @@ void TeslaBattery::
|
||||||
//Start the BMS ECU reset statemachine, only if contactors are OPEN and BMS ECU allows it
|
//Start the BMS ECU reset statemachine, only if contactors are OPEN and BMS ECU allows it
|
||||||
stateMachineBMSReset = 0;
|
stateMachineBMSReset = 0;
|
||||||
datalayer.battery.settings.user_requests_tesla_bms_reset = false;
|
datalayer.battery.settings.user_requests_tesla_bms_reset = false;
|
||||||
logging.println("BMS reset requested");
|
logging.println("INFO: BMS reset requested");
|
||||||
} else {
|
} else {
|
||||||
logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it");
|
logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it");
|
||||||
stateMachineBMSReset = 0xFF;
|
stateMachineBMSReset = 0xFF;
|
||||||
datalayer.battery.settings.user_requests_tesla_bms_reset = false;
|
datalayer.battery.settings.user_requests_tesla_bms_reset = false;
|
||||||
|
set_event(EVENT_BMS_RESET_REQ_FAIL, 0);
|
||||||
|
clear_event(EVENT_BMS_RESET_REQ_FAIL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (datalayer.battery.settings.user_requests_tesla_soc_reset) {
|
if (datalayer.battery.settings.user_requests_tesla_soc_reset) {
|
||||||
if (datalayer.battery.status.real_soc < 1500 || datalayer.battery.status.real_soc > 9000) {
|
if ((datalayer.battery.status.real_soc < 1500 || datalayer.battery.status.real_soc > 9000) &&
|
||||||
//Start the SOC reset statemachine, only if SOC < 15% or > 90%
|
battery_contactor == 1) {
|
||||||
|
//Start the SOC reset statemachine, only if SOC less than 15% or greater than 90%, and contactors open
|
||||||
stateMachineSOCReset = 0;
|
stateMachineSOCReset = 0;
|
||||||
datalayer.battery.settings.user_requests_tesla_soc_reset = false;
|
datalayer.battery.settings.user_requests_tesla_soc_reset = false;
|
||||||
logging.println("SOC reset requested");
|
logging.println("INFO: SOC reset requested");
|
||||||
} else {
|
} else {
|
||||||
logging.println("ERROR: SOC reset failed due to SOC not being less than 15 or greater than 90");
|
logging.println("ERROR: SOC reset failed, SOC not < 15 or > 90, or contactors not open");
|
||||||
stateMachineSOCReset = 0xFF;
|
stateMachineSOCReset = 0xFF;
|
||||||
datalayer.battery.settings.user_requests_tesla_soc_reset = false;
|
datalayer.battery.settings.user_requests_tesla_soc_reset = false;
|
||||||
|
set_event(EVENT_BATTERY_SOC_RESET_FAIL, 0);
|
||||||
|
clear_event(EVENT_BATTERY_SOC_RESET_FAIL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -972,42 +797,42 @@ void TeslaBattery::
|
||||||
datalayer_extended.tesla.HVP_shuntBarTempStatus = HVP_shuntBarTempStatus;
|
datalayer_extended.tesla.HVP_shuntBarTempStatus = HVP_shuntBarTempStatus;
|
||||||
datalayer_extended.tesla.HVP_shuntAsicTempStatus = HVP_shuntAsicTempStatus;
|
datalayer_extended.tesla.HVP_shuntAsicTempStatus = HVP_shuntAsicTempStatus;
|
||||||
|
|
||||||
//Safety checks for CAN message sesnding
|
//Safety checks for CAN message sending
|
||||||
if ((datalayer.system.status.inverter_allows_contactor_closing == true) &&
|
if ((datalayer.system.status.inverter_allows_contactor_closing == true) &&
|
||||||
(datalayer.battery.status.bms_status != FAULT) && (!datalayer.system.settings.equipment_stop_active)) {
|
(datalayer.battery.status.bms_status != FAULT) && (!datalayer.system.settings.equipment_stop_active)) {
|
||||||
// Carry on: 0x221 DRIVE state & reset power down timer
|
// Carry on: 0x221 DRIVE state & reset power down timer
|
||||||
vehicleState = 1;
|
vehicleState = CAR_DRIVE;
|
||||||
powerDownTimer = 180; //0x221 50ms cyclic, 20 calls/second
|
powerDownSeconds = 9;
|
||||||
} else {
|
} else {
|
||||||
// Faulted state, or inverter blocks contactor closing
|
// Faulted state, or inverter blocks contactor closing
|
||||||
// Shut down: 0x221 ACCESSORY state for 3 seconds, followed by GOING_DOWN, then OFF
|
// Shut down: 0x221 ACCESSORY state for 3 seconds, followed by GOING_DOWN, then OFF
|
||||||
if (powerDownTimer <= 180 && powerDownTimer > 120) {
|
if (powerDownSeconds <= 9 && powerDownSeconds > 6) {
|
||||||
vehicleState = 2; //ACCESSORY
|
vehicleState = ACCESSORY;
|
||||||
powerDownTimer--;
|
powerDownSeconds--;
|
||||||
}
|
}
|
||||||
if (powerDownTimer <= 120 && powerDownTimer > 60) {
|
if (powerDownSeconds <= 6 && powerDownSeconds > 3) {
|
||||||
vehicleState = 3; //GOING_DOWN
|
vehicleState = GOING_DOWN;
|
||||||
powerDownTimer--;
|
powerDownSeconds--;
|
||||||
}
|
}
|
||||||
if (powerDownTimer <= 60 && powerDownTimer > 0) {
|
if (powerDownSeconds <= 3 && powerDownSeconds > 0) {
|
||||||
vehicleState = 0; //OFF
|
vehicleState = CAR_OFF;
|
||||||
powerDownTimer--;
|
powerDownSeconds--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printFaultCodesIfActive();
|
printFaultCodesIfActive();
|
||||||
logging.printf("BMS Contactors State: ");
|
logging.printf("Contactor State: ");
|
||||||
logging.printf(getBMSContactorState(battery_contactor)); // Display what state the BMS thinks the contactors are in
|
logging.printf(getBMSContactorState(battery_contactor)); // Display what state the BMS thinks the contactors are in
|
||||||
logging.printf(", HVIL: ");
|
logging.printf(" HVIL: ");
|
||||||
logging.printf(getHvilStatusState(battery_hvil_status));
|
logging.printf(getHvilStatusState(battery_hvil_status));
|
||||||
logging.printf(", NegativeState: ");
|
logging.printf(" NegState: ");
|
||||||
logging.printf(getContactorState(battery_packContNegativeState));
|
logging.printf(getContactorState(battery_packContNegativeState));
|
||||||
logging.printf(", PositiveState: ");
|
logging.printf(" PosState: ");
|
||||||
logging.println(getContactorState(battery_packContPositiveState));
|
logging.println(getContactorState(battery_packContPositiveState));
|
||||||
logging.printf("HVP Contactors setState: ");
|
logging.printf("Cont. setState: ");
|
||||||
logging.printf(
|
logging.printf(
|
||||||
getContactorText(battery_packContactorSetState)); // Display what state the HVP has set the contactors to be in
|
getContactorText(battery_packContactorSetState)); // Display what state the HVP has set the contactors to be in
|
||||||
logging.printf(", Closing blocked: ");
|
logging.printf(" Closing blocked: ");
|
||||||
logging.printf(getNoYes(battery_packCtrsClosingBlocked));
|
logging.printf(getNoYes(battery_packCtrsClosingBlocked));
|
||||||
if (battery_packContactorSetState == 5) {
|
if (battery_packContactorSetState == 5) {
|
||||||
logging.printf(" (already CLOSED)");
|
logging.printf(" (already CLOSED)");
|
||||||
|
@ -1015,43 +840,8 @@ void TeslaBattery::
|
||||||
logging.printf(", Pyrotest: ");
|
logging.printf(", Pyrotest: ");
|
||||||
logging.println(getNoYes(battery_pyroTestInProgress));
|
logging.println(getNoYes(battery_pyroTestInProgress));
|
||||||
|
|
||||||
logging.printf("Battery values: ");
|
logging.printf("HV: %.2f V, 12V: %.2f V, 12V current: %.2f A.\n", (battery_dcdcHvBusVolt * 0.146484),
|
||||||
logging.printf("Real SOC: ");
|
(battery_dcdcLvBusVolt * 0.0390625), (battery_dcdcLvOutputCurrent * 0.1));
|
||||||
logging.print(battery_soc_ui / 10.0, 1);
|
|
||||||
logging.printf(", Battery voltage: ");
|
|
||||||
logging.print(battery_volts / 10.0, 1);
|
|
||||||
logging.printf("V");
|
|
||||||
logging.printf(", Battery HV current: ");
|
|
||||||
logging.print(battery_amps / 10.0, 1);
|
|
||||||
logging.printf("A");
|
|
||||||
logging.printf(", Fully charged?: ");
|
|
||||||
if (battery_full_charge_complete)
|
|
||||||
logging.printf("YES, ");
|
|
||||||
else
|
|
||||||
logging.printf("NO, ");
|
|
||||||
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
|
|
||||||
logging.printf("LFP chemistry detected!");
|
|
||||||
}
|
|
||||||
logging.println("");
|
|
||||||
logging.printf("Cellstats, Max: ");
|
|
||||||
logging.print(battery_cell_max_v);
|
|
||||||
logging.printf("mV (cell ");
|
|
||||||
logging.print(battery_BrickVoltageMaxNum);
|
|
||||||
logging.printf("), Min: ");
|
|
||||||
logging.print(battery_cell_min_v);
|
|
||||||
logging.printf("mV (cell ");
|
|
||||||
logging.print(battery_BrickVoltageMinNum);
|
|
||||||
logging.printf("), Imbalance: ");
|
|
||||||
logging.print(battery_cell_deviation_mV);
|
|
||||||
logging.println("mV.");
|
|
||||||
|
|
||||||
logging.printf("High Voltage Output Pins: %.2f V, Low Voltage: %.2f V, DC/DC 12V current: %.2f A.\n",
|
|
||||||
(battery_dcdcHvBusVolt * 0.146484), (battery_dcdcLvBusVolt * 0.0390625),
|
|
||||||
(battery_dcdcLvOutputCurrent * 0.1));
|
|
||||||
|
|
||||||
logging.printf("PCS_ambientTemp: %.2f°C, DCDC_Temp: %.2f°C, ChgPhA: %.2f°C, ChgPhB: %.2f°C, ChgPhC: %.2f°C.\n",
|
|
||||||
PCS_ambientTemp * 0.1 + 40, PCS_dcdcTemp * 0.1 + 40, PCS_chgPhATemp * 0.1 + 40,
|
|
||||||
PCS_chgPhBTemp * 0.1 + 40, PCS_chgPhCTemp * 0.1 + 40);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
|
@ -1895,17 +1685,17 @@ void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
break;
|
break;
|
||||||
case 0x612: // CAN UDSs for BMS
|
case 0x612: // CAN UDS responses for BMS
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
//BMS Query
|
//BMS Query
|
||||||
if (stateMachineBMSQuery != 0xFF && stateMachineBMSReset == 0xFF) {
|
if (stateMachineBMSQuery != 0xFF && stateMachineBMSReset == 0xFF && stateMachineSOCReset == 0xFF) {
|
||||||
if (memcmp(rx_frame.data.u8, "\x02\x50\x03\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
|
if (memcmp(rx_frame.data.u8, "\x02\x50\x03\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
|
||||||
//Received initial response, proceed to actual query
|
//Received initial response, proceed to actual query
|
||||||
logging.println("CAN UDS: Received BMS query initial handshake reply");
|
logging.println("CAN UDS: Received BMS query initial handshake reply");
|
||||||
stateMachineBMSQuery = 1;
|
stateMachineBMSQuery = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (memcmp(&rx_frame.data.u8[0], "\x10", 1) == 0) {
|
if (rx_frame.data.u8[0] == 0x10) {
|
||||||
//Received first data frame
|
//Received first data frame
|
||||||
battery_partNumber[0] = rx_frame.data.u8[5];
|
battery_partNumber[0] = rx_frame.data.u8[5];
|
||||||
battery_partNumber[1] = rx_frame.data.u8[6];
|
battery_partNumber[1] = rx_frame.data.u8[6];
|
||||||
|
@ -1914,7 +1704,7 @@ void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
stateMachineBMSQuery = 2;
|
stateMachineBMSQuery = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (memcmp(&rx_frame.data.u8[0], "\x21", 1) == 0) {
|
if (rx_frame.data.u8[0] == 0x21) {
|
||||||
//Second part of part number after flow control
|
//Second part of part number after flow control
|
||||||
battery_partNumber[3] = rx_frame.data.u8[1];
|
battery_partNumber[3] = rx_frame.data.u8[1];
|
||||||
battery_partNumber[4] = rx_frame.data.u8[2];
|
battery_partNumber[4] = rx_frame.data.u8[2];
|
||||||
|
@ -1926,7 +1716,7 @@ void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
logging.println("CAN UDS: Received BMS query second data frame");
|
logging.println("CAN UDS: Received BMS query second data frame");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (memcmp(&rx_frame.data.u8[0], "\x22", 1) == 0) {
|
if (rx_frame.data.u8[0] == 0x22) {
|
||||||
//Final part of part number
|
//Final part of part number
|
||||||
battery_partNumber[10] = rx_frame.data.u8[1];
|
battery_partNumber[10] = rx_frame.data.u8[1];
|
||||||
battery_partNumber[11] = rx_frame.data.u8[2];
|
battery_partNumber[11] = rx_frame.data.u8[2];
|
||||||
|
@ -1941,15 +1731,28 @@ void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//BMS Reset
|
//BMS ECU responses
|
||||||
if (stateMachineBMSQuery == 0xFF) { // Make sure this is reset request not query
|
|
||||||
if (memcmp(rx_frame.data.u8, "\x02\x67\x06\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
|
if (memcmp(rx_frame.data.u8, "\x02\x67\x06\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
|
||||||
logging.println("CAN UDS: ECU unlocked");
|
logging.println("CAN UDS: BMS ECU unlocked");
|
||||||
} else if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) {
|
|
||||||
logging.println("CAN UDS: ECU reset request successful but ECU busy, response pending");
|
|
||||||
} else if (memcmp(rx_frame.data.u8, "\x02\x51\x01\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
|
|
||||||
logging.println("CAN UDS: ECU reset positive response, 1 second downtime");
|
|
||||||
}
|
}
|
||||||
|
if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) {
|
||||||
|
logging.println("CAN UDS: BMS ECU reset request successful but ECU busy, response pending");
|
||||||
|
}
|
||||||
|
if (memcmp(rx_frame.data.u8, "\x02\x51\x01\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
|
||||||
|
logging.println("CAN UDS: BMS ECU reset positive response, 1 second downtime");
|
||||||
|
set_event(EVENT_BMS_RESET_REQ_SUCCESS, 0);
|
||||||
|
clear_event(EVENT_BMS_RESET_REQ_SUCCESS);
|
||||||
|
}
|
||||||
|
if (memcmp(rx_frame.data.u8, "\x05\x71\x01\x04\x07\x01\xAA\xAA", 8) == 0) {
|
||||||
|
logging.println("CAN UDS: BMS SOC reset accepted, resetting BMS ECU");
|
||||||
|
set_event(EVENT_BATTERY_SOC_RESET_SUCCESS, 0);
|
||||||
|
clear_event(EVENT_BATTERY_SOC_RESET_SUCCESS);
|
||||||
|
stateMachineBMSReset = 6; // BMS ECU already unlocked etc. so we jump straight to reset
|
||||||
|
}
|
||||||
|
if (memcmp(rx_frame.data.u8, "\x05\x71\x01\x04\x07\x00\xAA\xAA", 8) == 0) {
|
||||||
|
logging.println("CAN UDS: BMS SOC reset failed");
|
||||||
|
set_event(EVENT_BATTERY_SOC_RESET_FAIL, 0);
|
||||||
|
clear_event(EVENT_BATTERY_SOC_RESET_FAIL);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1985,41 +1788,26 @@ CAN_frame can_msg_118[] = {
|
||||||
{.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6F, 0x8E, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}},
|
{.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x6F, 0x8E, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}},
|
||||||
{.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x70, 0x8F, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}};
|
{.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x118, .data = {0x70, 0x8F, 0x30, 0x10, 0x00, 0x08, 0x00, 0x80}}};
|
||||||
|
|
||||||
unsigned long lastSend1CF = 0;
|
|
||||||
unsigned long lastSend118 = 0;
|
|
||||||
|
|
||||||
int index_1CF = 0;
|
|
||||||
int index_118 = 0;
|
|
||||||
|
|
||||||
void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
||||||
|
|
||||||
if (user_selected_tesla_digital_HVIL) { //Special S/X? mode for 2024+ batteries
|
|
||||||
if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) {
|
|
||||||
if (currentMillis - lastSend1CF >= 10) {
|
|
||||||
transmit_can_frame(&can_msg_1CF[index_1CF]);
|
|
||||||
|
|
||||||
index_1CF = (index_1CF + 1) % 8;
|
|
||||||
lastSend1CF = currentMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentMillis - lastSend118 >= 10) {
|
|
||||||
transmit_can_frame(&can_msg_118[index_118]);
|
|
||||||
|
|
||||||
index_118 = (index_118 + 1) % 16;
|
|
||||||
lastSend118 = currentMillis;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
index_1CF = 0;
|
|
||||||
index_118 = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Send 10ms messages
|
//Send 10ms messages
|
||||||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||||
previousMillis10 = currentMillis;
|
previousMillis10 = currentMillis;
|
||||||
|
|
||||||
|
if (user_selected_tesla_digital_HVIL) { //Special Digital HVIL mode for S/X 2024+ batteries
|
||||||
|
if ((datalayer.system.status.inverter_allows_contactor_closing) &&
|
||||||
|
(datalayer.battery.status.bms_status != FAULT)) {
|
||||||
|
transmit_can_frame(&can_msg_1CF[index_1CF]);
|
||||||
|
index_1CF = (index_1CF + 1) % 8;
|
||||||
|
transmit_can_frame(&can_msg_118[index_118]);
|
||||||
|
index_118 = (index_118 + 1) % 16;
|
||||||
|
}
|
||||||
|
} else { //Normal handling of 118 message (Non digital HVIL version)
|
||||||
//0x118 DI_systemStatus
|
//0x118 DI_systemStatus
|
||||||
transmit_can_frame(&TESLA_118);
|
transmit_can_frame(&TESLA_118);
|
||||||
|
index_1CF = 0; //Stop broadcasting Digital HVIL 1CF and 118 to keep contactors open
|
||||||
|
index_118 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
//0x2E1 VCFRONT_status
|
//0x2E1 VCFRONT_status
|
||||||
switch (muxNumber_TESLA_2E1) {
|
switch (muxNumber_TESLA_2E1) {
|
||||||
|
@ -2059,7 +1847,7 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
||||||
previousMillis50 = currentMillis;
|
previousMillis50 = currentMillis;
|
||||||
|
|
||||||
//0x221 VCFRONT_LVPowerState
|
//0x221 VCFRONT_LVPowerState
|
||||||
if (vehicleState == 1) { // Drive
|
if (vehicleState == CAR_DRIVE) {
|
||||||
switch (muxNumber_TESLA_221) {
|
switch (muxNumber_TESLA_221) {
|
||||||
case 0:
|
case 0:
|
||||||
generateMuxFrameCounterChecksum(TESLA_221_DRIVE_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
generateMuxFrameCounterChecksum(TESLA_221_DRIVE_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
||||||
|
@ -2074,10 +1862,8 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//Generate next new frame
|
|
||||||
frameCounter_TESLA_221 = (frameCounter_TESLA_221 + 1) % 16;
|
|
||||||
}
|
}
|
||||||
if (vehicleState == 2) { // Accessory
|
if (vehicleState == ACCESSORY) {
|
||||||
switch (muxNumber_TESLA_221) {
|
switch (muxNumber_TESLA_221) {
|
||||||
case 0:
|
case 0:
|
||||||
generateMuxFrameCounterChecksum(TESLA_221_ACCESSORY_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
generateMuxFrameCounterChecksum(TESLA_221_ACCESSORY_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
||||||
|
@ -2092,10 +1878,8 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//Generate next new frame
|
|
||||||
frameCounter_TESLA_221 = (frameCounter_TESLA_221 + 1) % 16;
|
|
||||||
}
|
}
|
||||||
if (vehicleState == 3) { // Going down
|
if (vehicleState == GOING_DOWN) {
|
||||||
switch (muxNumber_TESLA_221) {
|
switch (muxNumber_TESLA_221) {
|
||||||
case 0:
|
case 0:
|
||||||
generateMuxFrameCounterChecksum(TESLA_221_GOING_DOWN_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
generateMuxFrameCounterChecksum(TESLA_221_GOING_DOWN_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
||||||
|
@ -2110,10 +1894,8 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//Generate next new frame
|
|
||||||
frameCounter_TESLA_221 = (frameCounter_TESLA_221 + 1) % 16;
|
|
||||||
}
|
}
|
||||||
if (vehicleState == 0) { // Off
|
if (vehicleState == CAR_OFF) {
|
||||||
switch (muxNumber_TESLA_221) {
|
switch (muxNumber_TESLA_221) {
|
||||||
case 0:
|
case 0:
|
||||||
generateMuxFrameCounterChecksum(TESLA_221_OFF_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
generateMuxFrameCounterChecksum(TESLA_221_OFF_Mux0, frameCounter_TESLA_221, 52, 4, 56, 8);
|
||||||
|
@ -2128,23 +1910,13 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//Generate next new frame
|
//Generate next new frame
|
||||||
frameCounter_TESLA_221 = (frameCounter_TESLA_221 + 1) % 16;
|
frameCounter_TESLA_221 = (frameCounter_TESLA_221 + 1) % 16;
|
||||||
}
|
|
||||||
|
|
||||||
//0x3C2 VCLEFT_switchStatus
|
//0x3C2 VCLEFT_switchStatus
|
||||||
switch (muxNumber_TESLA_3C2) {
|
transmit_can_frame(muxNumber_TESLA_3C2 == 0 ? &TESLA_3C2_Mux0 : &TESLA_3C2_Mux1);
|
||||||
case 0:
|
muxNumber_TESLA_3C2 = !muxNumber_TESLA_3C2; // Flip between sending Mux0 and Mux1 on each pass
|
||||||
transmit_can_frame(&TESLA_3C2_Mux0);
|
|
||||||
muxNumber_TESLA_3C2++;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
transmit_can_frame(&TESLA_3C2_Mux1);
|
|
||||||
muxNumber_TESLA_3C2 = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//0x39D IBST_status
|
//0x39D IBST_status
|
||||||
transmit_can_frame(&TESLA_39D);
|
transmit_can_frame(&TESLA_39D);
|
||||||
|
@ -2567,7 +2339,7 @@ void TeslaBattery::printFaultCodesIfActive() {
|
||||||
printDebugIfActive(BMS_a139_SW_DC_Link_V_Irrational, "ERROR: BMS_a139_SW_DC_Link_V_Irrational");
|
printDebugIfActive(BMS_a139_SW_DC_Link_V_Irrational, "ERROR: BMS_a139_SW_DC_Link_V_Irrational");
|
||||||
printDebugIfActive(BMS_a141_SW_BMB_Status_Warning, "ERROR: BMS_a141_SW_BMB_Status_Warning");
|
printDebugIfActive(BMS_a141_SW_BMB_Status_Warning, "ERROR: BMS_a141_SW_BMB_Status_Warning");
|
||||||
printDebugIfActive(BMS_a144_Hvp_Config_Mismatch, "ERROR: BMS_a144_Hvp_Config_Mismatch");
|
printDebugIfActive(BMS_a144_Hvp_Config_Mismatch, "ERROR: BMS_a144_Hvp_Config_Mismatch");
|
||||||
printDebugIfActive(BMS_a145_SW_SOC_Change, "ERROR: BMS_a145_SW_SOC_Change");
|
printDebugIfActive(BMS_a145_SW_SOC_Change, "INFO: BMS_a145_SW_SOC_Change");
|
||||||
printDebugIfActive(BMS_a146_SW_Brick_Overdischarged, "ERROR: BMS_a146_SW_Brick_Overdischarged");
|
printDebugIfActive(BMS_a146_SW_Brick_Overdischarged, "ERROR: BMS_a146_SW_Brick_Overdischarged");
|
||||||
printDebugIfActive(BMS_a149_SW_Missing_Config_Block, "ERROR: BMS_a149_SW_Missing_Config_Block");
|
printDebugIfActive(BMS_a149_SW_Missing_Config_Block, "ERROR: BMS_a149_SW_Missing_Config_Block");
|
||||||
printDebugIfActive(BMS_a151_SW_external_isolation, "ERROR: BMS_a151_SW_external_isolation");
|
printDebugIfActive(BMS_a151_SW_external_isolation, "ERROR: BMS_a151_SW_external_isolation");
|
||||||
|
@ -2601,7 +2373,7 @@ void TeslaModel3YBattery::setup(void) { // Performs one time setup at startup
|
||||||
//0x7FF GTW CAN frame values
|
//0x7FF GTW CAN frame values
|
||||||
//Mux1
|
//Mux1
|
||||||
write_signal_value(&TESLA_7FF_Mux1, 16, 16, user_selected_tesla_GTW_country, false);
|
write_signal_value(&TESLA_7FF_Mux1, 16, 16, user_selected_tesla_GTW_country, false);
|
||||||
write_signal_value(&TESLA_7FF_Mux1, 11, 1, user_selected_tesla_GTW_country, false);
|
write_signal_value(&TESLA_7FF_Mux1, 11, 1, user_selected_tesla_GTW_rightHandDrive, false);
|
||||||
//Mux3
|
//Mux3
|
||||||
write_signal_value(&TESLA_7FF_Mux3, 8, 4, user_selected_tesla_GTW_mapRegion, false);
|
write_signal_value(&TESLA_7FF_Mux3, 8, 4, user_selected_tesla_GTW_mapRegion, false);
|
||||||
write_signal_value(&TESLA_7FF_Mux3, 18, 3, user_selected_tesla_GTW_chassisType, false);
|
write_signal_value(&TESLA_7FF_Mux3, 18, 3, user_selected_tesla_GTW_chassisType, false);
|
||||||
|
|
|
@ -39,15 +39,6 @@ class TeslaBattery : public CanBattery {
|
||||||
TeslaHtmlRenderer renderer;
|
TeslaHtmlRenderer renderer;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* Modify these if needed */
|
|
||||||
static const int MAXCHARGEPOWERALLOWED =
|
|
||||||
15000; // 15000W we use a define since the value supplied by Tesla is always 0
|
|
||||||
static const int MAXDISCHARGEPOWERALLOWED =
|
|
||||||
60000; // 60000W we use a define since the value supplied by Tesla is always 0
|
|
||||||
|
|
||||||
// Set this to true to try to close contactors/full startup even with no inverter defined/connected
|
|
||||||
bool batteryTestOverride = false;
|
|
||||||
|
|
||||||
/* Do not change anything below this line! */
|
/* Do not change anything below this line! */
|
||||||
static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
|
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
|
||||||
|
@ -87,7 +78,11 @@ class TeslaBattery : public CanBattery {
|
||||||
uint8_t muxNumber_TESLA_221 = 0;
|
uint8_t muxNumber_TESLA_221 = 0;
|
||||||
uint8_t frameCounter_TESLA_221 = 15; // Start at 15 for Mux 0
|
uint8_t frameCounter_TESLA_221 = 15; // Start at 15 for Mux 0
|
||||||
uint8_t vehicleState = 1; // "OFF": 0, "DRIVE": 1, "ACCESSORY": 2, "GOING_DOWN": 3
|
uint8_t vehicleState = 1; // "OFF": 0, "DRIVE": 1, "ACCESSORY": 2, "GOING_DOWN": 3
|
||||||
uint16_t powerDownTimer = 180; // Car power down (i.e. contactor open) tracking timer, 3 seconds per sendingState
|
static const uint8_t CAR_OFF = 0;
|
||||||
|
static const uint8_t CAR_DRIVE = 1;
|
||||||
|
static const uint8_t ACCESSORY = 2;
|
||||||
|
static const uint8_t GOING_DOWN = 3;
|
||||||
|
uint8_t powerDownSeconds = 9; // Car power down (i.e. contactor open) tracking timer, 3 seconds per sendingState
|
||||||
//0x2E1 VCFRONT_status, 6 mux tracker
|
//0x2E1 VCFRONT_status, 6 mux tracker
|
||||||
uint8_t muxNumber_TESLA_2E1 = 0;
|
uint8_t muxNumber_TESLA_2E1 = 0;
|
||||||
//0x334 UI
|
//0x334 UI
|
||||||
|
@ -466,15 +461,14 @@ class TeslaBattery : public CanBattery {
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x610,
|
.ID = 0x610,
|
||||||
.data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Define initial UDS request
|
.data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Define initial UDS request
|
||||||
|
uint8_t index_1CF = 0;
|
||||||
|
uint8_t index_118 = 0;
|
||||||
uint8_t stateMachineClearIsolationFault = 0xFF;
|
uint8_t stateMachineClearIsolationFault = 0xFF;
|
||||||
uint8_t stateMachineBMSReset = 0xFF;
|
uint8_t stateMachineBMSReset = 0xFF;
|
||||||
uint8_t stateMachineSOCReset = 0xFF;
|
uint8_t stateMachineSOCReset = 0xFF;
|
||||||
uint8_t stateMachineBMSQuery = 0xFF;
|
uint8_t stateMachineBMSQuery = 0xFF;
|
||||||
uint16_t sendContactorClosingMessagesStill = 300;
|
|
||||||
uint16_t battery_cell_max_v = 3300;
|
uint16_t battery_cell_max_v = 3300;
|
||||||
uint16_t battery_cell_min_v = 3300;
|
uint16_t battery_cell_min_v = 3300;
|
||||||
uint16_t battery_cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
|
||||||
bool cellvoltagesRead = false;
|
bool cellvoltagesRead = false;
|
||||||
//0x3d2: 978 BMS_kwhCounter
|
//0x3d2: 978 BMS_kwhCounter
|
||||||
uint32_t battery_total_discharge = 0;
|
uint32_t battery_total_discharge = 0;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "../devboard/utils/logging.h"
|
#include "../devboard/utils/logging.h"
|
||||||
|
|
||||||
void VolvoSpaBattery::
|
void VolvoSpaBattery::
|
||||||
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
||||||
|
|
||||||
|
@ -85,12 +86,20 @@ void VolvoSpaBattery::
|
||||||
datalayer.battery.info.total_capacity_Wh = 69511;
|
datalayer.battery.info.total_capacity_Wh = 69511;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check safeties
|
||||||
|
if (datalayer_extended.VolvoPolestar.BECMsupplyVoltage < 10700) { //10.7V,
|
||||||
|
//If 12V voltage goes under this, latch battery OFF to prevent contactors from swinging between on/off
|
||||||
|
set_event(EVENT_12V_LOW, (datalayer_extended.VolvoPolestar.BECMsupplyVoltage / 100));
|
||||||
|
set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x3A:
|
case 0x3A:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
||||||
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
|
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class VolvoSpaHtmlRenderer : public BatteryHtmlRenderer {
|
||||||
content += "</h4><h4>BECM reported number of DTCs: " + String(datalayer_extended.VolvoPolestar.DTCcount) + "</h4>";
|
content += "</h4><h4>BECM reported number of DTCs: " + String(datalayer_extended.VolvoPolestar.DTCcount) + "</h4>";
|
||||||
content += "<h4>BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms / 10.0) + " %</h4>";
|
content += "<h4>BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms / 10.0) + " %</h4>";
|
||||||
content += "<h4>Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc / 10.0) + " %</h4>";
|
content += "<h4>Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc / 10.0) + " %</h4>";
|
||||||
content += "<h4>Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10.0) + " %</h4>";
|
content += "<h4>Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 100.0) + " %</h4>";
|
||||||
content += "<h4>BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms / 100.0) + " %</h4>";
|
content += "<h4>BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms / 100.0) + " %</h4>";
|
||||||
content += "<h4>BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV</h4>";
|
content += "<h4>BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV</h4>";
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "../devboard/utils/logging.h"
|
#include "../devboard/utils/logging.h"
|
||||||
|
|
||||||
void VolvoSpaHybridBattery::
|
void VolvoSpaHybridBattery::
|
||||||
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
|
||||||
uint8_t cnt = 0;
|
uint8_t cnt = 0;
|
||||||
|
@ -73,9 +74,9 @@ void VolvoSpaHybridBattery::
|
||||||
}
|
}
|
||||||
|
|
||||||
void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
void VolvoSpaHybridBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x3A:
|
case 0x3A:
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
||||||
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
|
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#include "comm_can.h"
|
#include "comm_can.h"
|
||||||
#include <algorithm>
|
|
||||||
#include <map>
|
|
||||||
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
|
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
|
||||||
#include "../../lib/pierremolinaro-acan-esp32/ACAN_ESP32.h"
|
#include "../../lib/pierremolinaro-acan-esp32/ACAN_ESP32.h"
|
||||||
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
|
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
|
||||||
|
@ -11,6 +9,11 @@
|
||||||
#include "src/devboard/sdcard/sdcard.h"
|
#include "src/devboard/sdcard/sdcard.h"
|
||||||
#include "src/devboard/utils/logging.h"
|
#include "src/devboard/utils/logging.h"
|
||||||
|
|
||||||
|
#include <esp_private/periph_ctrl.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
volatile CAN_Configuration can_config = {.battery = CAN_NATIVE,
|
volatile CAN_Configuration can_config = {.battery = CAN_NATIVE,
|
||||||
.inverter = CAN_NATIVE,
|
.inverter = CAN_NATIVE,
|
||||||
.battery_double = CAN_ADDON_MCP2515,
|
.battery_double = CAN_ADDON_MCP2515,
|
||||||
|
@ -35,17 +38,22 @@ void register_can_receiver(CanReceiver* receiver, CAN_Interface interface, CAN_S
|
||||||
DEBUG_PRINTF("CAN receiver registered, total: %d\n", can_receivers.size());
|
DEBUG_PRINTF("CAN receiver registered, total: %d\n", can_receivers.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
ACAN_ESP32_Settings* settingsespcan;
|
uint32_t init_native_can(CAN_Speed speed, gpio_num_t tx_pin, gpio_num_t rx_pin);
|
||||||
|
|
||||||
|
ACAN_ESP32_Settings* settingsespcan = nullptr;
|
||||||
|
|
||||||
static uint32_t QUARTZ_FREQUENCY;
|
static uint32_t QUARTZ_FREQUENCY;
|
||||||
SPIClass SPI2515;
|
SPIClass SPI2515;
|
||||||
uint8_t user_selected_can_addon_crystal_frequency_mhz = 0;
|
uint8_t user_selected_can_addon_crystal_frequency_mhz = 0;
|
||||||
|
|
||||||
ACAN2515* can2515;
|
ACAN2515* can2515;
|
||||||
ACAN2515Settings* settings2515;
|
ACAN2515Settings* settings2515;
|
||||||
|
|
||||||
static ACAN2515_Buffer16 gBuffer;
|
static ACAN2515_Buffer16 gBuffer;
|
||||||
|
|
||||||
|
static ACAN2517FDSettings::Oscillator quartz_fd_frequency;
|
||||||
SPIClass SPI2517;
|
SPIClass SPI2517;
|
||||||
|
uint8_t user_selected_canfd_addon_crystal_frequency_mhz = 0;
|
||||||
ACAN2517FD* canfd;
|
ACAN2517FD* canfd;
|
||||||
ACAN2517FDSettings* settings2517;
|
ACAN2517FDSettings* settings2517;
|
||||||
bool use_canfd_as_can = false;
|
bool use_canfd_as_can = false;
|
||||||
|
@ -61,6 +69,14 @@ bool init_CAN() {
|
||||||
QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL;
|
QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user_selected_canfd_addon_crystal_frequency_mhz == 20) {
|
||||||
|
quartz_fd_frequency = ACAN2517FDSettings::OSC_20MHz;
|
||||||
|
} else if (user_selected_canfd_addon_crystal_frequency_mhz == 40) {
|
||||||
|
quartz_fd_frequency = ACAN2517FDSettings::OSC_40MHz;
|
||||||
|
} else { // Default to 40MHz incase value invalid/not set
|
||||||
|
quartz_fd_frequency = ACAN2517FDSettings::OSC_40MHz;
|
||||||
|
}
|
||||||
|
|
||||||
auto nativeIt = can_receivers.find(CAN_NATIVE);
|
auto nativeIt = can_receivers.find(CAN_NATIVE);
|
||||||
if (nativeIt != can_receivers.end()) {
|
if (nativeIt != can_receivers.end()) {
|
||||||
auto se_pin = esp32hal->CAN_SE_PIN();
|
auto se_pin = esp32hal->CAN_SE_PIN();
|
||||||
|
@ -79,12 +95,7 @@ bool init_CAN() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsespcan = new ACAN_ESP32_Settings((int)nativeIt->second.speed * 1000UL);
|
const uint32_t errorCode = init_native_can(nativeIt->second.speed, tx_pin, rx_pin);
|
||||||
settingsespcan->mRequestedCANMode = ACAN_ESP32_Settings::NormalMode;
|
|
||||||
settingsespcan->mTxPin = tx_pin;
|
|
||||||
settingsespcan->mRxPin = rx_pin;
|
|
||||||
|
|
||||||
const uint32_t errorCode = ACAN_ESP32::can.begin(*settingsespcan);
|
|
||||||
if (errorCode == 0) {
|
if (errorCode == 0) {
|
||||||
native_can_initialized = true;
|
native_can_initialized = true;
|
||||||
logging.println("Native Can ok");
|
logging.println("Native Can ok");
|
||||||
|
@ -181,9 +192,8 @@ bool init_CAN() {
|
||||||
logging.println("CAN FD add-on (ESP32+MCP2517) selected");
|
logging.println("CAN FD add-on (ESP32+MCP2517) selected");
|
||||||
SPI2517.begin(sck_pin, sdo_pin, sdi_pin);
|
SPI2517.begin(sck_pin, sdo_pin, sdi_pin);
|
||||||
auto bitRate = (int)speed * 1000UL;
|
auto bitRate = (int)speed * 1000UL;
|
||||||
settings2517 = new ACAN2517FDSettings(
|
settings2517 = new ACAN2517FDSettings(quartz_fd_frequency, bitRate, DataBitRateFactor::x4);
|
||||||
CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, bitRate,
|
// Arbitration bit rate: 250/500 kbit/s, data bit rate: 1/2 Mbit/s
|
||||||
DataBitRateFactor::x4); // Arbitration bit rate: 250/500 kbit/s, data bit rate: 1/2 Mbit/s
|
|
||||||
|
|
||||||
// ListenOnly / Normal20B / NormalFDs
|
// ListenOnly / Normal20B / NormalFDs
|
||||||
settings2517->mRequestedMode = use_canfd_as_can ? ACAN2517FDSettings::Normal20B : ACAN2517FDSettings::NormalFD;
|
settings2517->mRequestedMode = use_canfd_as_can ? ACAN2517FDSettings::Normal20B : ACAN2517FDSettings::NormalFD;
|
||||||
|
@ -218,11 +228,11 @@ bool init_CAN() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, int interface) {
|
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, CAN_Interface interface) {
|
||||||
if (!allowed_to_send_CAN) {
|
if (!allowed_to_send_CAN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
print_can_frame(*tx_frame, frameDirection(MSG_TX));
|
print_can_frame(*tx_frame, interface, frameDirection(MSG_TX));
|
||||||
|
|
||||||
if (datalayer.system.info.CAN_SD_logging_active) {
|
if (datalayer.system.info.CAN_SD_logging_active) {
|
||||||
add_can_frame_to_buffer(*tx_frame, frameDirection(MSG_TX));
|
add_can_frame_to_buffer(*tx_frame, frameDirection(MSG_TX));
|
||||||
|
@ -357,13 +367,20 @@ void receive_frame_canfd_addon() { // This section checks if we have a complete
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support functions
|
// Support functions
|
||||||
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
|
void print_can_frame(CAN_frame frame, CAN_Interface interface, frameDirection msgDir) {
|
||||||
|
|
||||||
if (datalayer.system.info.CAN_usb_logging_active) {
|
if (datalayer.system.info.CAN_usb_logging_active) {
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
Serial.print("(");
|
Serial.print("(");
|
||||||
Serial.print(millis() / 1000.0);
|
Serial.print(millis() / 1000.0);
|
||||||
(msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 ");
|
if (msgDir == MSG_RX) {
|
||||||
|
Serial.print(") RX");
|
||||||
|
Serial.print((int)(interface * 2));
|
||||||
|
} else {
|
||||||
|
Serial.print(") TX");
|
||||||
|
Serial.print((int)(interface * 2) + 1);
|
||||||
|
}
|
||||||
|
Serial.print(" ");
|
||||||
Serial.print(frame.ID, HEX);
|
Serial.print(frame.ID, HEX);
|
||||||
Serial.print(" [");
|
Serial.print(" [");
|
||||||
Serial.print(frame.DLC);
|
Serial.print(frame.DLC);
|
||||||
|
@ -378,7 +395,7 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
|
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
|
||||||
dump_can_frame(frame, msgDir);
|
dump_can_frame(frame, interface, msgDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +403,7 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface) {
|
||||||
if (interface !=
|
if (interface !=
|
||||||
CANFD_NATIVE) { //Avoid printing twice due to receive_frame_canfd_addon sending to both FD interfaces
|
CANFD_NATIVE) { //Avoid printing twice due to receive_frame_canfd_addon sending to both FD interfaces
|
||||||
//TODO: This check can be removed later when refactored to use inline functions for logging
|
//TODO: This check can be removed later when refactored to use inline functions for logging
|
||||||
print_can_frame(*rx_frame, frameDirection(MSG_RX));
|
print_can_frame(*rx_frame, interface, frameDirection(MSG_RX));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datalayer.system.info.CAN_SD_logging_active) {
|
if (datalayer.system.info.CAN_SD_logging_active) {
|
||||||
|
@ -406,7 +423,7 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dump_can_frame(CAN_frame& frame, frameDirection msgDir) {
|
void dump_can_frame(CAN_frame& frame, CAN_Interface interface, frameDirection msgDir) {
|
||||||
char* message_string = datalayer.system.info.logged_can_messages;
|
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
|
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);
|
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
|
||||||
|
@ -420,8 +437,9 @@ void dump_can_frame(CAN_frame& frame, frameDirection msgDir) {
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
|
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
|
||||||
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.
|
// Add direction. Multiplying the interface by two 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");
|
offset += snprintf(message_string + offset, message_string_size - offset, "%s%d ", (msgDir == MSG_RX) ? "RX" : "TX",
|
||||||
|
(int)(interface * 2) + (msgDir == MSG_RX ? 0 : 1));
|
||||||
|
|
||||||
// Add ID and DLC
|
// Add ID and DLC
|
||||||
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
|
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
|
||||||
|
@ -472,11 +490,41 @@ void restart_can() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CAN_Speed change_can_speed(CAN_Interface interface, CAN_Speed speed) {
|
// Initialize the native CAN interface with the given speed and pins.
|
||||||
auto oldSpeed = (CAN_Speed)settingsespcan->mDesiredBitRate;
|
// This can be called repeatedly to change the interface speed (as some
|
||||||
if (interface == CAN_Interface::CAN_NATIVE) {
|
// batteries require).
|
||||||
settingsespcan->mDesiredBitRate = (int)speed;
|
uint32_t init_native_can(CAN_Speed speed, gpio_num_t tx_pin, gpio_num_t rx_pin) {
|
||||||
ACAN_ESP32::can.begin(*settingsespcan);
|
|
||||||
|
// TODO: check whether this is necessary? It seems to help with
|
||||||
|
// reinitialization.
|
||||||
|
periph_module_reset(PERIPH_TWAI_MODULE);
|
||||||
|
|
||||||
|
if (settingsespcan != nullptr) {
|
||||||
|
delete settingsespcan;
|
||||||
}
|
}
|
||||||
return speed;
|
|
||||||
|
// Create a new settings object (as it does the bitrate calcs in the constructor)
|
||||||
|
settingsespcan = new ACAN_ESP32_Settings((int)speed * 1000UL);
|
||||||
|
settingsespcan->mRequestedCANMode = ACAN_ESP32_Settings::NormalMode;
|
||||||
|
settingsespcan->mTxPin = tx_pin;
|
||||||
|
settingsespcan->mRxPin = rx_pin;
|
||||||
|
|
||||||
|
// (Re)start the CAN interface
|
||||||
|
return ACAN_ESP32::can.begin(*settingsespcan);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the speed of the given CAN interface. Returns true if successful.
|
||||||
|
bool change_can_speed(CAN_Interface interface, CAN_Speed speed) {
|
||||||
|
if (interface == CAN_Interface::CAN_NATIVE && settingsespcan != nullptr) {
|
||||||
|
// Reinitialize the native CAN interface with the new speed
|
||||||
|
const uint32_t errorCode = init_native_can(speed, settingsespcan->mTxPin, settingsespcan->mRxPin);
|
||||||
|
if (errorCode != 0) {
|
||||||
|
logging.print("Error Native Can: 0x");
|
||||||
|
logging.println(errorCode, HEX);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
|
|
||||||
extern bool use_canfd_as_can;
|
extern bool use_canfd_as_can;
|
||||||
extern uint8_t user_selected_can_addon_crystal_frequency_mhz;
|
extern uint8_t user_selected_can_addon_crystal_frequency_mhz;
|
||||||
|
extern uint8_t user_selected_canfd_addon_crystal_frequency_mhz;
|
||||||
|
|
||||||
void dump_can_frame(CAN_frame& frame, frameDirection msgDir);
|
void dump_can_frame(CAN_frame& frame, CAN_Interface interface, frameDirection msgDir);
|
||||||
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, int interface);
|
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, CAN_Interface interface);
|
||||||
|
|
||||||
//These defines are not used if user updates values via Settings page
|
//These defines are not used if user updates values via Settings page
|
||||||
#define CRYSTAL_FREQUENCY_MHZ 8
|
#define CRYSTAL_FREQUENCY_MHZ 8
|
||||||
|
@ -93,7 +94,7 @@ void receive_frame_canfd_addon();
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
void print_can_frame(CAN_frame frame, frameDirection msgDir);
|
void print_can_frame(CAN_frame frame, CAN_Interface interface, frameDirection msgDir);
|
||||||
|
|
||||||
// Stop/pause CAN communication for all interfaces
|
// Stop/pause CAN communication for all interfaces
|
||||||
void stop_can();
|
void stop_can();
|
||||||
|
@ -101,7 +102,7 @@ void stop_can();
|
||||||
// Restart CAN communication for all interfaces
|
// Restart CAN communication for all interfaces
|
||||||
void restart_can();
|
void restart_can();
|
||||||
|
|
||||||
// Change the speed of the CAN interface and return the old speed.
|
// Change the speed of the CAN interface. Returns true if successful.
|
||||||
CAN_Speed change_can_speed(CAN_Interface interface, CAN_Speed speed);
|
bool change_can_speed(CAN_Interface interface, CAN_Speed speed);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -23,7 +23,7 @@ void show_dtc(uint8_t byte0, uint8_t byte1) {
|
||||||
logging.printf("%c%d\n", letter, ((byte0 & 0x3F) << 8) | byte1);
|
logging.printf("%c%d\n", letter, ((byte0 & 0x3F) << 8) | byte1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_obd_frame(CAN_frame& rx_frame) {
|
void handle_obd_frame(CAN_frame& rx_frame, CAN_Interface interface) {
|
||||||
if (rx_frame.data.u8[1] == 0x7F) {
|
if (rx_frame.data.u8[1] == 0x7F) {
|
||||||
const char* error_str = "?";
|
const char* error_str = "?";
|
||||||
switch (rx_frame.data.u8[3]) { // See https://automotive.wiki/index.php/ISO_14229
|
switch (rx_frame.data.u8[3]) { // See https://automotive.wiki/index.php/ISO_14229
|
||||||
|
@ -105,10 +105,10 @@ void handle_obd_frame(CAN_frame& rx_frame) {
|
||||||
logging.printf("ODBx reply frame received:\n");
|
logging.printf("ODBx reply frame received:\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dump_can_frame(rx_frame, MSG_RX);
|
dump_can_frame(rx_frame, interface, MSG_RX);
|
||||||
}
|
}
|
||||||
|
|
||||||
void transmit_obd_can_frame(unsigned int address, int interface, bool canFD) {
|
void transmit_obd_can_frame(unsigned int address, CAN_Interface interface, bool canFD) {
|
||||||
static CAN_frame OBD_frame;
|
static CAN_frame OBD_frame;
|
||||||
OBD_frame.FD = canFD;
|
OBD_frame.FD = canFD;
|
||||||
OBD_frame.ID = address;
|
OBD_frame.ID = address;
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
#include "comm_can.h"
|
#include "comm_can.h"
|
||||||
|
|
||||||
void handle_obd_frame(CAN_frame& rx_frame);
|
void handle_obd_frame(CAN_frame& rx_frame, CAN_Interface interface);
|
||||||
|
|
||||||
void transmit_obd_can_frame(unsigned int address, int interface, bool canFD);
|
void transmit_obd_can_frame(unsigned int address, CAN_Interface interface, bool canFD);
|
||||||
|
|
||||||
#endif // _OBD_H_
|
#endif // _OBD_H_
|
||||||
|
|
|
@ -74,6 +74,7 @@ bool init_contactors() {
|
||||||
auto precPin = esp32hal->PRECHARGE_PIN();
|
auto precPin = esp32hal->PRECHARGE_PIN();
|
||||||
|
|
||||||
if (!esp32hal->alloc_pins(contactors, posPin, negPin, precPin)) {
|
if (!esp32hal->alloc_pins(contactors, posPin, negPin, precPin)) {
|
||||||
|
DEBUG_PRINTF("GPIO controlled contactor setup failed\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ bool init_contactors() {
|
||||||
if (contactor_control_enabled_double_battery) {
|
if (contactor_control_enabled_double_battery) {
|
||||||
auto second_contactors = esp32hal->SECOND_BATTERY_CONTACTORS_PIN();
|
auto second_contactors = esp32hal->SECOND_BATTERY_CONTACTORS_PIN();
|
||||||
if (!esp32hal->alloc_pins(contactors, second_contactors)) {
|
if (!esp32hal->alloc_pins(contactors, second_contactors)) {
|
||||||
|
DEBUG_PRINTF("Secondary battery contactor control setup failed\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +110,7 @@ bool init_contactors() {
|
||||||
if (periodic_bms_reset || remote_bms_reset || esp32hal->always_enable_bms_power()) {
|
if (periodic_bms_reset || remote_bms_reset || esp32hal->always_enable_bms_power()) {
|
||||||
auto pin = esp32hal->BMS_POWER();
|
auto pin = esp32hal->BMS_POWER();
|
||||||
if (!esp32hal->alloc_pins("BMS power", pin)) {
|
if (!esp32hal->alloc_pins("BMS power", pin)) {
|
||||||
|
DEBUG_PRINTF("BMS power setup failed\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pinMode(pin, OUTPUT);
|
pinMode(pin, OUTPUT);
|
||||||
|
|
|
@ -102,7 +102,9 @@ void init_stored_settings() {
|
||||||
user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0);
|
user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0);
|
||||||
user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false);
|
user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false);
|
||||||
user_selected_can_addon_crystal_frequency_mhz = settings.getUInt("CANFREQ", 8);
|
user_selected_can_addon_crystal_frequency_mhz = settings.getUInt("CANFREQ", 8);
|
||||||
|
user_selected_canfd_addon_crystal_frequency_mhz = settings.getUInt("CANFDFREQ", 40);
|
||||||
user_selected_LEAF_interlock_mandatory = settings.getBool("INTERLOCKREQ", false);
|
user_selected_LEAF_interlock_mandatory = settings.getBool("INTERLOCKREQ", false);
|
||||||
|
user_selected_use_estimated_SOC = settings.getBool("SOCESTIMATED", false);
|
||||||
user_selected_tesla_digital_HVIL = settings.getBool("DIGITALHVIL", false);
|
user_selected_tesla_digital_HVIL = settings.getBool("DIGITALHVIL", false);
|
||||||
user_selected_tesla_GTW_country = settings.getUInt("GTWCOUNTRY", 0);
|
user_selected_tesla_GTW_country = settings.getUInt("GTWCOUNTRY", 0);
|
||||||
user_selected_tesla_GTW_rightHandDrive = settings.getBool("GTWRHD", false);
|
user_selected_tesla_GTW_rightHandDrive = settings.getBool("GTWRHD", false);
|
||||||
|
@ -156,9 +158,14 @@ void init_stored_settings() {
|
||||||
datalayer.system.info.SD_logging_active = settings.getBool("SDLOGENABLED", false);
|
datalayer.system.info.SD_logging_active = settings.getBool("SDLOGENABLED", false);
|
||||||
datalayer.battery.status.led_mode = (led_mode_enum)settings.getUInt("LEDMODE", false);
|
datalayer.battery.status.led_mode = (led_mode_enum)settings.getUInt("LEDMODE", false);
|
||||||
|
|
||||||
|
//Some early integrations need manually set allowed charge/discharge power
|
||||||
|
datalayer.battery.status.override_charge_power_W = settings.getUInt("CHGPOWER", 1000);
|
||||||
|
datalayer.battery.status.override_discharge_power_W = settings.getUInt("DCHGPOWER", 1000);
|
||||||
|
|
||||||
// WIFI AP is enabled by default unless disabled in the settings
|
// WIFI AP is enabled by default unless disabled in the settings
|
||||||
wifiap_enabled = settings.getBool("WIFIAPENABLED", true);
|
wifiap_enabled = settings.getBool("WIFIAPENABLED", true);
|
||||||
wifi_channel = settings.getUInt("WIFICHANNEL", 2000);
|
wifi_channel = settings.getUInt("WIFICHANNEL", 0);
|
||||||
|
ssidAP = settings.getString("APNAME", "BatteryEmulator").c_str();
|
||||||
passwordAP = settings.getString("APPASSWORD", "123456789").c_str();
|
passwordAP = settings.getString("APPASSWORD", "123456789").c_str();
|
||||||
mqtt_enabled = settings.getBool("MQTTENABLED", false);
|
mqtt_enabled = settings.getBool("MQTTENABLED", false);
|
||||||
mqtt_timeout_ms = settings.getUInt("MQTTTIMEOUT", 2000);
|
mqtt_timeout_ms = settings.getUInt("MQTTTIMEOUT", 2000);
|
||||||
|
@ -202,9 +209,11 @@ void store_settings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.putString("SSID", String(ssid.c_str()))) {
|
if (!settings.putString("SSID", String(ssid.c_str()))) {
|
||||||
|
if (ssid != "")
|
||||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 1);
|
set_event(EVENT_PERSISTENT_SAVE_INFO, 1);
|
||||||
}
|
}
|
||||||
if (!settings.putString("PASSWORD", String(password.c_str()))) {
|
if (!settings.putString("PASSWORD", String(password.c_str()))) {
|
||||||
|
if (password != "")
|
||||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 2);
|
set_event(EVENT_PERSISTENT_SAVE_INFO, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,13 +31,11 @@ bool init_precharge_control() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup PWM Channel Frequency and Resolution
|
|
||||||
logging.printf("Precharge control initialised\n");
|
|
||||||
|
|
||||||
auto hia4v1_pin = esp32hal->HIA4V1_PIN();
|
auto hia4v1_pin = esp32hal->HIA4V1_PIN();
|
||||||
auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_PIN();
|
auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_PIN();
|
||||||
|
|
||||||
if (!esp32hal->alloc_pins("Precharge control", hia4v1_pin, inverter_disconnect_contactor_pin)) {
|
if (!esp32hal->alloc_pins("Precharge control", hia4v1_pin, inverter_disconnect_contactor_pin)) {
|
||||||
|
DEBUG_PRINTF("Precharge control setup failed\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +44,7 @@ bool init_precharge_control() {
|
||||||
pinMode(inverter_disconnect_contactor_pin, OUTPUT);
|
pinMode(inverter_disconnect_contactor_pin, OUTPUT);
|
||||||
digitalWrite(inverter_disconnect_contactor_pin, LOW);
|
digitalWrite(inverter_disconnect_contactor_pin, LOW);
|
||||||
|
|
||||||
|
DEBUG_PRINTF("Precharge control setup successful\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ bool init_rs485() {
|
||||||
auto pin_5v_en = esp32hal->PIN_5V_EN();
|
auto pin_5v_en = esp32hal->PIN_5V_EN();
|
||||||
|
|
||||||
if (!esp32hal->alloc_pins_ignore_unused("RS485", en_pin, se_pin, pin_5v_en)) {
|
if (!esp32hal->alloc_pins_ignore_unused("RS485", en_pin, se_pin, pin_5v_en)) {
|
||||||
return false;
|
DEBUG_PRINTF("Modbus failed to allocate pins\n");
|
||||||
|
return true; //Early return, we do not set the pins
|
||||||
}
|
}
|
||||||
|
|
||||||
if (en_pin != GPIO_NUM_NC) {
|
if (en_pin != GPIO_NUM_NC) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*
|
*
|
||||||
* @param[in] void
|
* @param[in] void
|
||||||
*
|
*
|
||||||
* @return true if init was successful, false otherwise.
|
* @return Safe to call even if rs485 is not used
|
||||||
*/
|
*/
|
||||||
bool init_rs485();
|
bool init_rs485();
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,13 @@ struct DATALAYER_BATTERY_STATUS_TYPE {
|
||||||
/** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
|
/** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
|
||||||
int16_t current_dA;
|
int16_t current_dA;
|
||||||
|
|
||||||
|
/* Some early integrations do not support reading allowed charge power from battery
|
||||||
|
On these integrations we need to have the user specify what limits the battery can take */
|
||||||
|
/** Overriden allowed battery discharge power in Watts. Set by user */
|
||||||
|
uint32_t override_discharge_power_W = 0;
|
||||||
|
/** Overriden allowed battery charge power in Watts. Set by user */
|
||||||
|
uint32_t override_charge_power_W = 0;
|
||||||
|
|
||||||
/** uint16_t */
|
/** uint16_t */
|
||||||
/** State of health in integer-percent x 100. 9900 = 99.00% */
|
/** State of health in integer-percent x 100. 9900 = 99.00% */
|
||||||
uint16_t soh_pptt = 9900;
|
uint16_t soh_pptt = 9900;
|
||||||
|
@ -121,7 +128,7 @@ struct DATALAYER_BATTERY_SETTINGS_TYPE {
|
||||||
/** Minimum percentage setting. Set this value to the lowest real SOC
|
/** Minimum percentage setting. Set this value to the lowest real SOC
|
||||||
* you want the inverter to be able to use. At this real SOC, the inverter
|
* you want the inverter to be able to use. At this real SOC, the inverter
|
||||||
* will "see" 0% , Example 2000 = 20.0%*/
|
* will "see" 0% , Example 2000 = 20.0%*/
|
||||||
uint16_t min_percentage = 2000;
|
int16_t min_percentage = 2000;
|
||||||
/** Maximum percentage setting. Set this value to the highest real SOC
|
/** Maximum percentage setting. Set this value to the highest real SOC
|
||||||
* you want the inverter to be able to use. At this real SOC, the inverter
|
* you want the inverter to be able to use. At this real SOC, the inverter
|
||||||
* will "see" 100% Example 8000 = 80.0%*/
|
* will "see" 100% Example 8000 = 80.0%*/
|
||||||
|
|
|
@ -235,6 +235,17 @@ struct DATALAYER_INFO_CMFAEV {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DATALAYER_INFO_ECMP {
|
struct DATALAYER_INFO_ECMP {
|
||||||
|
//mysteryvan parameters
|
||||||
|
bool MysteryVan = false;
|
||||||
|
bool CrashMemorized = false;
|
||||||
|
uint8_t CONTACTOR_OPENING_REASON = 0;
|
||||||
|
uint8_t TBMU_FAULT_TYPE = 0;
|
||||||
|
uint8_t CONTACTORS_STATE = 0;
|
||||||
|
uint16_t HV_BATT_FC_INSU_MINUS_RES, HV_BATT_FC_INSU_PLUS_RES, HV_BATT_FC_VHL_INSU_PLUS_RES,
|
||||||
|
HV_BATT_ONLY_INSU_MINUS_RES = 0;
|
||||||
|
bool ALERT_CELL_POOR_CONSIST, ALERT_OVERCHARGE, ALERT_BATT, ALERT_LOW_SOC, ALERT_HIGH_SOC, ALERT_SOC_JUMP,
|
||||||
|
ALERT_TEMP_DIFF, ALERT_HIGH_TEMP, ALERT_OVERVOLTAGE, ALERT_CELL_OVERVOLTAGE, ALERT_CELL_UNDERVOLTAGE = false;
|
||||||
|
//ecmp below
|
||||||
uint8_t MainConnectorState = 0;
|
uint8_t MainConnectorState = 0;
|
||||||
uint16_t InsulationResistance = 0;
|
uint16_t InsulationResistance = 0;
|
||||||
uint8_t InsulationDiag = 0;
|
uint8_t InsulationDiag = 0;
|
||||||
|
@ -766,7 +777,7 @@ struct DATALAYER_INFO_VOLVO_POLESTAR {
|
||||||
uint16_t soc_calc = 0;
|
uint16_t soc_calc = 0;
|
||||||
uint16_t soc_rescaled = 0;
|
uint16_t soc_rescaled = 0;
|
||||||
uint16_t soh_bms = 0;
|
uint16_t soh_bms = 0;
|
||||||
uint16_t BECMsupplyVoltage = 0;
|
uint16_t BECMsupplyVoltage = 12000;
|
||||||
|
|
||||||
uint16_t BECMBatteryVoltage = 0;
|
uint16_t BECMBatteryVoltage = 0;
|
||||||
int16_t BECMBatteryCurrent = 0;
|
int16_t BECMBatteryCurrent = 0;
|
||||||
|
|
|
@ -21,6 +21,9 @@ class StarkHal : public Esp32Hal {
|
||||||
public:
|
public:
|
||||||
const char* name() { return "Stark CMR Module"; }
|
const char* name() { return "Stark CMR Module"; }
|
||||||
|
|
||||||
|
//Always enable BMS power on Stark CMR, it does not collide with any pin definitions
|
||||||
|
virtual bool always_enable_bms_power() { return true; }
|
||||||
|
|
||||||
// Not needed, GPIO 16 has hardware pullup for PSRAM compatibility
|
// Not needed, GPIO 16 has hardware pullup for PSRAM compatibility
|
||||||
virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_NC; }
|
virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_NC; }
|
||||||
|
|
||||||
|
|
|
@ -675,7 +675,7 @@ bool init_mqtt(void) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mqtt_loop(void) {
|
void mqtt_client_loop(void) {
|
||||||
// Only attempt to publish/reconnect MQTT if Wi-Fi is connectedand checkTimmer is elapsed
|
// Only attempt to publish/reconnect MQTT if Wi-Fi is connectedand checkTimmer is elapsed
|
||||||
if (check_global_timer.elapsed() && WiFi.status() == WL_CONNECTED) {
|
if (check_global_timer.elapsed() && WiFi.status() == WL_CONNECTED) {
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ extern const char* ha_device_id;
|
||||||
extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
|
extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
|
||||||
|
|
||||||
bool init_mqtt(void);
|
bool init_mqtt(void);
|
||||||
void mqtt_loop(void);
|
void mqtt_client_loop(void);
|
||||||
bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain);
|
bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -67,6 +67,9 @@ void init_events(void) {
|
||||||
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_BATTERY_VALUE_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_BATTERY_VALUE_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING;
|
||||||
|
events.entries[EVENT_BATTERY_SOC_RECALIBRATION].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_BATTERY_SOC_RESET_SUCCESS].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_BATTERY_SOC_RESET_FAIL].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR;
|
||||||
|
@ -124,6 +127,8 @@ void init_events(void) {
|
||||||
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_PERIODIC_BMS_RESET].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_PERIODIC_BMS_RESET].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_BMS_RESET_REQ_SUCCESS].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_BMS_RESET_REQ_FAIL].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_GPIO_CONFLICT].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_GPIO_CONFLICT].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_GPIO_NOT_DEFINED].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_GPIO_NOT_DEFINED].level = EVENT_LEVEL_ERROR;
|
||||||
|
@ -244,6 +249,12 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
return "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:
|
case EVENT_BATTERY_ISOLATION:
|
||||||
return "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_BATTERY_SOC_RECALIBRATION:
|
||||||
|
return "The BMS updated the HV battery State of Charge (SOC) by more than 3pct based on SocByOcv.";
|
||||||
|
case EVENT_BATTERY_SOC_RESET_SUCCESS:
|
||||||
|
return "SOC reset routine was successful.";
|
||||||
|
case EVENT_BATTERY_SOC_RESET_FAIL:
|
||||||
|
return "SOC reset routine failed - check SOC is < 15 or > 90, and contactors are open.";
|
||||||
case EVENT_VOLTAGE_DIFFERENCE:
|
case EVENT_VOLTAGE_DIFFERENCE:
|
||||||
return "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:
|
case EVENT_SOH_DIFFERENCE:
|
||||||
|
@ -361,7 +372,11 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
case EVENT_SD_INIT_FAILED:
|
case EVENT_SD_INIT_FAILED:
|
||||||
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
|
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
|
||||||
case EVENT_PERIODIC_BMS_RESET:
|
case EVENT_PERIODIC_BMS_RESET:
|
||||||
return "BMS Reset Event Completed.";
|
return "BMS reset event completed.";
|
||||||
|
case EVENT_BMS_RESET_REQ_SUCCESS:
|
||||||
|
return "BMS reset request completed successfully.";
|
||||||
|
case EVENT_BMS_RESET_REQ_FAIL:
|
||||||
|
return "BMS reset request failed - check contactors are open.";
|
||||||
case EVENT_GPIO_CONFLICT:
|
case EVENT_GPIO_CONFLICT:
|
||||||
return "GPIO Pin Conflict: The pin used by '" + esp32hal->failed_allocator() + "' is already allocated by '" +
|
return "GPIO Pin Conflict: The pin used by '" + esp32hal->failed_allocator() + "' is already allocated by '" +
|
||||||
esp32hal->conflicting_allocator() + "'. Please check your configuration and assign different pins.";
|
esp32hal->conflicting_allocator() + "'. Please check your configuration and assign different pins.";
|
||||||
|
|
|
@ -49,6 +49,9 @@
|
||||||
XX(EVENT_BATTERY_ISOLATION) \
|
XX(EVENT_BATTERY_ISOLATION) \
|
||||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||||
XX(EVENT_BATTERY_WARMED_UP) \
|
XX(EVENT_BATTERY_WARMED_UP) \
|
||||||
|
XX(EVENT_BATTERY_SOC_RECALIBRATION) \
|
||||||
|
XX(EVENT_BATTERY_SOC_RESET_SUCCESS) \
|
||||||
|
XX(EVENT_BATTERY_SOC_RESET_FAIL) \
|
||||||
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
||||||
XX(EVENT_SOH_DIFFERENCE) \
|
XX(EVENT_SOH_DIFFERENCE) \
|
||||||
XX(EVENT_SOH_LOW) \
|
XX(EVENT_SOH_LOW) \
|
||||||
|
@ -107,6 +110,8 @@
|
||||||
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
||||||
XX(EVENT_SD_INIT_FAILED) \
|
XX(EVENT_SD_INIT_FAILED) \
|
||||||
XX(EVENT_PERIODIC_BMS_RESET) \
|
XX(EVENT_PERIODIC_BMS_RESET) \
|
||||||
|
XX(EVENT_BMS_RESET_REQ_SUCCESS) \
|
||||||
|
XX(EVENT_BMS_RESET_REQ_FAIL) \
|
||||||
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
|
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
|
||||||
XX(EVENT_GPIO_NOT_DEFINED) \
|
XX(EVENT_GPIO_NOT_DEFINED) \
|
||||||
XX(EVENT_GPIO_CONFLICT) \
|
XX(EVENT_GPIO_CONFLICT) \
|
||||||
|
|
|
@ -20,6 +20,7 @@ static LED* led;
|
||||||
|
|
||||||
bool led_init(void) {
|
bool led_init(void) {
|
||||||
if (!esp32hal->alloc_pins("LED", esp32hal->LED_PIN())) {
|
if (!esp32hal->alloc_pins("LED", esp32hal->LED_PIN())) {
|
||||||
|
DEBUG_PRINTF("LED setup failed\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,10 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
||||||
return settings.getBool("DBLBTR") ? "checked" : "";
|
return settings.getBool("DBLBTR") ? "checked" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (var == "SOCESTIMATED") {
|
||||||
|
return settings.getBool("SOCESTIMATED") ? "checked" : "";
|
||||||
|
}
|
||||||
|
|
||||||
if (var == "CNTCTRL") {
|
if (var == "CNTCTRL") {
|
||||||
return settings.getBool("CNTCTRL") ? "checked" : "";
|
return settings.getBool("CNTCTRL") ? "checked" : "";
|
||||||
}
|
}
|
||||||
|
@ -291,6 +295,14 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
||||||
return settings.getBool("WIFIAPENABLED", wifiap_enabled) ? "checked" : "";
|
return settings.getBool("WIFIAPENABLED", wifiap_enabled) ? "checked" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (var == "APPASSWORD") {
|
||||||
|
return settings.getString("APPASSWORD", "123456789");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (var == "APNAME") {
|
||||||
|
return settings.getString("APNAME", "BatteryEmulator");
|
||||||
|
}
|
||||||
|
|
||||||
if (var == "STATICIP") {
|
if (var == "STATICIP") {
|
||||||
return settings.getBool("STATICIP") ? "checked" : "";
|
return settings.getBool("STATICIP") ? "checked" : "";
|
||||||
}
|
}
|
||||||
|
@ -299,6 +311,14 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
||||||
return String(settings.getUInt("WIFICHANNEL", 0));
|
return String(settings.getUInt("WIFICHANNEL", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (var == "CHGPOWER") {
|
||||||
|
return String(settings.getUInt("CHGPOWER", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (var == "DCHGPOWER") {
|
||||||
|
return String(settings.getUInt("DCHGPOWER", 0));
|
||||||
|
}
|
||||||
|
|
||||||
if (var == "LOCALIP1") {
|
if (var == "LOCALIP1") {
|
||||||
return String(settings.getUInt("LOCALIP1", 0));
|
return String(settings.getUInt("LOCALIP1", 0));
|
||||||
}
|
}
|
||||||
|
@ -629,6 +649,10 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
||||||
return String(settings.getUInt("CANFREQ", 8));
|
return String(settings.getUInt("CANFREQ", 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (var == "CANFDFREQ") {
|
||||||
|
return String(settings.getUInt("CANFDFREQ", 40));
|
||||||
|
}
|
||||||
|
|
||||||
if (var == "PRECHGMS") {
|
if (var == "PRECHGMS") {
|
||||||
return String(settings.getUInt("PRECHGMS", 100));
|
return String(settings.getUInt("PRECHGMS", 100));
|
||||||
}
|
}
|
||||||
|
@ -726,7 +750,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
|
|
||||||
function editError(){alert('Invalid input');}
|
function editError(){alert('Invalid input');}
|
||||||
|
|
||||||
function editSSID(){var value=prompt('Enter new SSID:');if(value!==null){var xhr=new
|
function editSSID(){var value=prompt('Which SSID to connect to. Enter new SSID:');if(value!==null){var xhr=new
|
||||||
XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updateSSID?value='+encodeURIComponent(value),true);xhr.send();}}
|
XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updateSSID?value='+encodeURIComponent(value),true);xhr.send();}}
|
||||||
|
|
||||||
function editPassword(){var value=prompt('Enter new password:');if(value!==null){var xhr=new
|
function editPassword(){var value=prompt('Enter new password:');if(value!==null){var xhr=new
|
||||||
|
@ -848,6 +872,21 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
background-color: #3a4b54; /* Slightly lighter than main background */
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 20px; /* Less rounded than 50px for a more card-like feel */
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.settings-card h3 {
|
||||||
|
color: #fff;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #4d5f69;
|
||||||
|
}
|
||||||
|
|
||||||
form .if-battery, form .if-inverter, form .if-charger, form .if-shunt { display: contents; }
|
form .if-battery, form .if-inverter, form .if-charger, form .if-shunt { display: contents; }
|
||||||
form[data-battery="0"] .if-battery { display: none; }
|
form[data-battery="0"] .if-battery { display: none; }
|
||||||
form[data-inverter="0"] .if-inverter { display: none; }
|
form[data-inverter="0"] .if-inverter { display: none; }
|
||||||
|
@ -869,6 +908,23 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form .if-estimated { display: none; } /* Integrations with manually set charge/discharge power */
|
||||||
|
form[data-battery="3"] .if-estimated,
|
||||||
|
form[data-battery="4"] .if-estimated,
|
||||||
|
form[data-battery="6"] .if-estimated,
|
||||||
|
form[data-battery="14"] .if-estimated,
|
||||||
|
form[data-battery="16"] .if-estimated,
|
||||||
|
form[data-battery="24"] .if-estimated,
|
||||||
|
form[data-battery="32"] .if-estimated,
|
||||||
|
form[data-battery="33"] .if-estimated {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .if-socestimated { display: none; } /* Integrations where you can turn on SOC estimation */
|
||||||
|
form[data-battery="16"] .if-socestimated {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
form .if-dblbtr { display: none; }
|
form .if-dblbtr { display: none; }
|
||||||
form[data-dblbtr="true"] .if-dblbtr {
|
form[data-dblbtr="true"] .if-dblbtr {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
@ -925,6 +981,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
#define SETTINGS_HTML_BODY \
|
#define SETTINGS_HTML_BODY \
|
||||||
R"rawliteral(
|
R"rawliteral(
|
||||||
<button onclick='goToMainPage()'>Back to main page</button>
|
<button onclick='goToMainPage()'>Back to main page</button>
|
||||||
|
<button onclick="askFactoryReset()">Factory reset</button>
|
||||||
|
|
||||||
<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>
|
<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>
|
||||||
<h4 style='color: white;'>SSID: <span id='SSID'>%SSID%</span><button onclick='editSSID()'>Edit</button></h4>
|
<h4 style='color: white;'>SSID: <span id='SSID'>%SSID%</span><button onclick='editSSID()'>Edit</button></h4>
|
||||||
|
@ -932,7 +989,15 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style='background-color: #404E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>
|
<div style='background-color: #404E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>
|
||||||
<form action='saveSettings' method='post' style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
<form action='saveSettings' method='post'>
|
||||||
|
|
||||||
|
<div style='grid-column: span 2; text-align: center; padding-top: 10px;' class="%SAVEDCLASS%">
|
||||||
|
<p>Settings saved. Reboot to take the new settings into use.<p> <button type='button' onclick='askReboot()'>Reboot</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3>Battery config</h3>
|
||||||
|
<div style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
||||||
|
|
||||||
<label for='battery'>Battery: </label>
|
<label for='battery'>Battery: </label>
|
||||||
<select name='battery' id='battery'>
|
<select name='battery' id='battery'>
|
||||||
|
@ -963,8 +1028,26 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="if-estimated">
|
||||||
|
<label>Manual charging power, watt: </label>
|
||||||
|
<input type='number' name='CHGPOWER' value="%CHGPOWER%"
|
||||||
|
min="0" max="65000" step="1"
|
||||||
|
title="Continous max charge power. Used since CAN data not valid for this integration. Do not set too high!" />
|
||||||
|
|
||||||
|
<label>Manual discharge power, watt: </label>
|
||||||
|
<input type='number' name='DCHGPOWER' value="%DCHGPOWER%"
|
||||||
|
min="0" max="65000" step="1"
|
||||||
|
title="Continous max discharge power. Used since CAN data not valid for this integration. Do not set too high!" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="if-socestimated">
|
||||||
|
<label>Use estimated SOC: </label>
|
||||||
|
<input type='checkbox' name='SOCESTIMATED' value='on' %SOCESTIMATED%
|
||||||
|
title="Switch to estimated State of Charge when accurate SOC data is not available from the battery" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="if-battery">
|
<div class="if-battery">
|
||||||
<label for='BATTCOMM'>Battery comm I/F: </label><select name='BATTCOMM' id='BATTCOMM'>
|
<label for='BATTCOMM'>Battery interface: </label><select name='BATTCOMM' id='BATTCOMM'>
|
||||||
%BATTCOMM%
|
%BATTCOMM%
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
@ -975,68 +1058,97 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
|
|
||||||
<div class="if-cbms">
|
<div class="if-cbms">
|
||||||
<label>Battery max design voltage (V): </label>
|
<label>Battery max design voltage (V): </label>
|
||||||
<input name='BATTPVMAX' pattern="^[0-9]+(\.[0-9]+)?$" type='text' value='%BATTPVMAX%' />
|
<input name='BATTPVMAX' pattern="[0-9]+(\.[0-9]+)?" type='text' value='%BATTPVMAX%'
|
||||||
|
title="Maximum safe voltage for the entire battery pack in volts. Used as charge target and protection limits." />
|
||||||
|
|
||||||
<label>Battery min design voltage (V): </label>
|
<label>Battery min design voltage (V): </label>
|
||||||
<input name='BATTPVMIN' pattern="^[0-9]+(\.[0-9]+)?$" type='text' value='%BATTPVMIN%' />
|
<input name='BATTPVMIN' pattern="[0-9]+(\.[0-9]+)?" type='text' value='%BATTPVMIN%'
|
||||||
|
title="Minimum safe voltage for the entire battery pack in volts. Further discharge not possible below this limit." />
|
||||||
|
|
||||||
<label>Cell max design voltage (mV): </label>
|
<label>Cell max design voltage (mV): </label>
|
||||||
<input name='BATTCVMAX' pattern="^[0-9]+$" type='text' value='%BATTCVMAX%' />
|
<input name='BATTCVMAX' pattern="[0-9]+" type='text' value='%BATTCVMAX%'
|
||||||
|
title="Maximum voltage per individual cell in millivolts. Charging stops if one cell reaches this voltage." />
|
||||||
|
|
||||||
<label>Cell min design voltage (mV): </label>
|
<label>Cell min design voltage (mV): </label>
|
||||||
<input name='BATTCVMIN' pattern="^[0-9]+$" type='text' value='%BATTCVMIN%' />
|
<input name='BATTCVMIN' pattern="[0-9]+$" type='text' value='%BATTCVMIN%'
|
||||||
|
title="Minimum voltage per individual cell in millivolts. Discharge stops if one cell drops to this voltage." />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label>Double battery: </label>
|
||||||
|
<input type='checkbox' name='DBLBTR' value='on' %DBLBTR%
|
||||||
|
title="Enable this option if you intend to run two batteries in parallel" />
|
||||||
|
|
||||||
|
<div class="if-dblbtr">
|
||||||
|
<label>Battery 2 interface: </label>
|
||||||
|
<select name='BATT2COMM'>
|
||||||
|
%BATT2COMM%
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3>Inverter config</h3>
|
||||||
|
<div style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
||||||
|
|
||||||
<label>Inverter protocol: </label><select name='inverter'>
|
<label>Inverter protocol: </label><select name='inverter'>
|
||||||
%INVTYPE%
|
%INVTYPE%
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div class="if-inverter">
|
<div class="if-inverter">
|
||||||
<label>Inverter comm I/F: </label><select name='INVCOMM'>
|
<label>Inverter interface: </label><select name='INVCOMM'>
|
||||||
%INVCOMM%
|
%INVCOMM%
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="if-sofar">
|
<div class="if-sofar">
|
||||||
<label>Sofar Battery ID (0-15): </label>
|
<label>Sofar Battery ID (0-15): </label>
|
||||||
<input name='SOFAR_ID' type='text' value="%SOFAR_ID%" pattern="^[0-9]{1,2}$" />
|
<input name='SOFAR_ID' type='text' value="%SOFAR_ID%" pattern="[0-9]{1,2}" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="if-pylonish">
|
<div class="if-pylonish">
|
||||||
<label>Reported cell count (0 for default): </label>
|
<label>Reported cell count (0 for default): </label>
|
||||||
<input name='INVCELLS' type='text' value="%INVCELLS%" pattern="^[0-9]+$" />
|
<input name='INVCELLS' type='text' value="%INVCELLS%" pattern="[0-9]+" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="if-pylonish if-solax">
|
<div class="if-pylonish if-solax">
|
||||||
<label>Reported module count (0 for default): </label>
|
<label>Reported module count (0 for default): </label>
|
||||||
<input name='INVMODULES' type='text' value="%INVMODULES%" pattern="^[0-9]+$" />
|
<input name='INVMODULES' type='text' value="%INVMODULES%" pattern="[0-9]+" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="if-pylonish">
|
<div class="if-pylonish">
|
||||||
<label>Reported cells per module (0 for default): </label>
|
<label>Reported cells per module (0 for default): </label>
|
||||||
<input name='INVCELLSPER' type='text' value="%INVCELLSPER%" pattern="^[0-9]+$" />
|
<input name='INVCELLSPER' type='text' value="%INVCELLSPER%" pattern="[0-9]+" />
|
||||||
|
|
||||||
<label>Reported voltage level (0 for default): </label>
|
<label>Reported voltage level (0 for default): </label>
|
||||||
<input name='INVVLEVEL' type='text' value="%INVVLEVEL%" pattern="^[0-9]+$" />
|
<input name='INVVLEVEL' type='text' value="%INVVLEVEL%" pattern="[0-9]+" />
|
||||||
|
|
||||||
<label>Reported Ah capacity (0 for default): </label>
|
<label>Reported Ah capacity (0 for default): </label>
|
||||||
<input name='INVCAPACITY' type='text' value="%INVCAPACITY%" pattern="^[0-9]+$" />
|
<input name='INVCAPACITY' type='text' value="%INVCAPACITY%" pattern="[0-9]+" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="if-solax">
|
<div class="if-solax">
|
||||||
<label>Reported battery type (in decimal): </label>
|
<label>Reported battery type (in decimal): </label>
|
||||||
<input name='INVBTYPE' type='text' value="%INVBTYPE%" pattern="^[0-9]+$" />
|
<input name='INVBTYPE' type='text' value="%INVBTYPE%" pattern="[0-9]+" />
|
||||||
|
|
||||||
<label>Inverter should ignore contactors: </label>
|
<label>Inverter should ignore contactors: </label>
|
||||||
<input type='checkbox' name='INVICNT' value='on' %INVICNT% />
|
<input type='checkbox' name='INVICNT' value='on' %INVICNT% />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3>Optional components config</h3>
|
||||||
|
<div style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
||||||
|
|
||||||
<label>Charger: </label><select name='charger'>
|
<label>Charger: </label><select name='charger'>
|
||||||
%CHGTYPE%
|
%CHGTYPE%
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div class="if-charger">
|
<div class="if-charger">
|
||||||
<label>Charger comm I/F: </label><select name='CHGCOMM'>
|
<label>Charger interface: </label><select name='CHGCOMM'>
|
||||||
%CHGCOMM%
|
%CHGCOMM%
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1046,30 +1158,38 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div class="if-shunt">
|
<div class="if-shunt">
|
||||||
<label>Shunt comm I/F: </label><select name='SHUNTCOMM'>
|
<label>Shunt interface: </label><select name='SHUNTCOMM'>
|
||||||
%SHUNTCOMM%
|
%SHUNTCOMM%
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Can-addon frequency Mhz: </label>
|
</div>
|
||||||
<input name='CANFREQ' type='text' value="%CANFREQ%" pattern="^[0-9]+$" />
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3>Hardware config</h3>
|
||||||
|
<div style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
||||||
|
|
||||||
|
<label>Use CanFD as classic CAN: </label>
|
||||||
|
<input type='checkbox' name='CANFDASCAN' value='on' %CANFDASCAN%
|
||||||
|
title="When enabled, CAN-FD channel will operate as normal 500kbps CAN" />
|
||||||
|
|
||||||
|
<label>CAN addon crystal (Mhz): </label>
|
||||||
|
<input type='number' name='CANFREQ' value="%CANFREQ%"
|
||||||
|
min="0" max="1000" step="1"
|
||||||
|
title="Configure this if you are using a custom add-on CAN board. Integers only" />
|
||||||
|
|
||||||
|
<label>CAN-FD-addon crystal (Mhz): </label>
|
||||||
|
<input type='number' name='CANFDFREQ' value="%CANFDFREQ%"
|
||||||
|
min="0" max="1000" step="1"
|
||||||
|
title="Configure this if you are using a custom add-on CAN board. Integers only" />
|
||||||
|
|
||||||
<label>Equipment stop button: </label><select name='EQSTOP'>
|
<label>Equipment stop button: </label><select name='EQSTOP'>
|
||||||
%EQSTOP%
|
%EQSTOP%
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label>Use CanFD as classic CAN: </label>
|
|
||||||
<input type='checkbox' name='CANFDASCAN' value='on' %CANFDASCAN% />
|
|
||||||
|
|
||||||
<label>Double battery: </label>
|
|
||||||
<input type='checkbox' name='DBLBTR' value='on' %DBLBTR% />
|
|
||||||
|
|
||||||
<div class="if-dblbtr">
|
<div class="if-dblbtr">
|
||||||
<label>Battery 2 comm I/F: </label>
|
<label>Double-Battery Contactor control via GPIO: </label>
|
||||||
<select name='BATT2COMM'>
|
|
||||||
%BATT2COMM%
|
|
||||||
</select>
|
|
||||||
<label>Contactor control via GPIO double battery: </label>
|
|
||||||
<input type='checkbox' name='CNTCTRLDBL' value='on' %CNTCTRLDBL% />
|
<input type='checkbox' name='CNTCTRLDBL' value='on' %CNTCTRLDBL% />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1078,46 +1198,76 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
|
|
||||||
<div class="if-cntctrl">
|
<div class="if-cntctrl">
|
||||||
<label>Precharge time ms: </label>
|
<label>Precharge time ms: </label>
|
||||||
<input name='PRECHGMS' type='text' value="%PRECHGMS%" pattern="^[0-9]+$" />
|
<input type='number' name='PRECHGMS' value="%PRECHGMS%"
|
||||||
|
min="1" max="65000" step="1"
|
||||||
|
title="Time in milliseconds the precharge should be active" />
|
||||||
|
|
||||||
<label>PWM contactor control: </label>
|
<label>PWM contactor control: </label>
|
||||||
<input type='checkbox' name='PWMCNTCTRL' value='on' %PWMCNTCTRL% />
|
<input type='checkbox' name='PWMCNTCTRL' value='on' %PWMCNTCTRL% />
|
||||||
|
|
||||||
<div class="if-pwmcntctrl">
|
<div class="if-pwmcntctrl">
|
||||||
<label>PWM Frequency Hz: </label>
|
<label>PWM Frequency Hz: </label>
|
||||||
<input name='PWMFREQ' type='text' value="%PWMFREQ%" pattern="^[0-9]+$" />
|
<input name='PWMFREQ' type='text' value="%PWMFREQ%"
|
||||||
|
min="1" max="65000" step="1"
|
||||||
|
title="Frequency in Hz used for PWM" />
|
||||||
|
|
||||||
<label>PWM Hold 0-1023: </label>
|
<label>PWM Hold 1-1023: </label>
|
||||||
<input name='PWMHOLD' type='text' value="%PWMHOLD%" pattern="^[0-9]+$" />
|
<input type='number' name='PWMHOLD' value="%PWMHOLD%"
|
||||||
|
min="1" max="1023" step="1"
|
||||||
|
title="1-1023 , lower value = lower power consumption" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Periodic BMS reset: </label>
|
<label>Periodic BMS reset every 24h: </label>
|
||||||
<input type='checkbox' name='PERBMSRESET' value='on' %PERBMSRESET% />
|
<input type='checkbox' name='PERBMSRESET' value='on' %PERBMSRESET% />
|
||||||
|
|
||||||
<label>Remote BMS reset: </label>
|
|
||||||
<input type='checkbox' name='REMBMSRESET' value='on' %REMBMSRESET% />
|
|
||||||
|
|
||||||
<label>External precharge via HIA4V1: </label>
|
<label>External precharge via HIA4V1: </label>
|
||||||
<input type='checkbox' name='EXTPRECHARGE' value='on' %EXTPRECHARGE% />
|
<input type='checkbox' name='EXTPRECHARGE' value='on' %EXTPRECHARGE% />
|
||||||
|
|
||||||
<div class="if-extprecharge">
|
<div class="if-extprecharge">
|
||||||
<label>Precharge, maximum ms before fault: </label>
|
<label>Precharge, maximum ms before fault: </label>
|
||||||
<input name='MAXPRETIME' type='text' value="%MAXPRETIME%" pattern="^[0-9]+$" />
|
<input name='MAXPRETIME' type='text' value="%MAXPRETIME%" pattern="[0-9]+" />
|
||||||
|
|
||||||
<label>Normally Open inverter disconnect contactor: </label>
|
<label>Normally Open (NO) inverter disconnect contactor: </label>
|
||||||
<input type='checkbox' name='NOINVDISC' value='on' %NOINVDISC% />
|
<input type='checkbox' name='NOINVDISC' value='on' %NOINVDISC% />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label for='LEDMODE'>Status LED pattern: </label><select name='LEDMODE' id='LEDMODE'>
|
||||||
|
%LEDMODE%
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3>Connectivity settings</h3>
|
||||||
|
<div style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
||||||
|
|
||||||
<label>Broadcast Wifi access point: </label>
|
<label>Broadcast Wifi access point: </label>
|
||||||
<input type='checkbox' name='WIFIAPENABLED' value='on' %WIFIAPENABLED% />
|
<input type='checkbox' name='WIFIAPENABLED' value='on' %WIFIAPENABLED% />
|
||||||
|
|
||||||
|
<label>Access point name: </label>
|
||||||
|
<input type='text' name='APNAME' value="%APNAME%"
|
||||||
|
pattern="[A-Za-z0-9!#*-]{8,63}"
|
||||||
|
title="Name must be 8-63 characters long and may only contain letters, numbers and some special characters: !#*-"
|
||||||
|
required />
|
||||||
|
|
||||||
|
<label>Access point password: </label>
|
||||||
|
<input type='text' name='APPASSWORD' value="%APPASSWORD%"
|
||||||
|
pattern="[A-Za-z0-9!#*-]{8,63}"
|
||||||
|
title="Password must be 8-63 characters long and may only contain letters, numbers and some special characters: !#*-"
|
||||||
|
required />
|
||||||
|
|
||||||
<label>Wifi channel 0-14: </label>
|
<label>Wifi channel 0-14: </label>
|
||||||
<input name='WIFICHANNEL' type='text' value="%WIFICHANNEL%" pattern="^[0-9]+$" />
|
<input type='number' name='WIFICHANNEL' value="%WIFICHANNEL%"
|
||||||
|
min="0" max="14" step="1"
|
||||||
|
title="Force specific channel. Set to 0 for autodetect" required />
|
||||||
|
|
||||||
<label>Custom Wifi hostname: </label>
|
<label>Custom Wifi hostname: </label>
|
||||||
<input type='text' name='HOSTNAME' value="%HOSTNAME%" />
|
<input type='text' name='HOSTNAME' value="%HOSTNAME%"
|
||||||
|
pattern="[A-Za-z0-9!#*-]+"
|
||||||
|
title="Optional: Hostname may only contain letters, numbers and some special characters: !#*-" />
|
||||||
|
|
||||||
<label>Use static IP address: </label>
|
<label>Use static IP address: </label>
|
||||||
<input type='checkbox' name='STATICIP' value='on' %STATICIP% />
|
<input type='checkbox' name='STATICIP' value='on' %STATICIP% />
|
||||||
|
@ -1149,38 +1299,31 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Enable performance profiling: </label>
|
|
||||||
<input type='checkbox' name='PERFPROFILE' value='on' %PERFPROFILE% />
|
|
||||||
|
|
||||||
<label>Enable CAN logging via USB serial: </label>
|
|
||||||
<input type='checkbox' name='CANLOGUSB' value='on' %CANLOGUSB% />
|
|
||||||
|
|
||||||
<label>Enable logging via USB serial: </label>
|
|
||||||
<input type='checkbox' name='USBENABLED' value='on' %USBENABLED% />
|
|
||||||
|
|
||||||
<label>Enable logging via Webserver: </label>
|
|
||||||
<input type='checkbox' name='WEBENABLED' value='on' %WEBENABLED% />
|
|
||||||
|
|
||||||
<label>Enable CAN logging via SD card: </label>
|
|
||||||
<input type='checkbox' name='CANLOGSD' value='on' %CANLOGSD% />
|
|
||||||
|
|
||||||
<label>Enable logging via SD card: </label>
|
|
||||||
<input type='checkbox' name='SDLOGENABLED' value='on' %SDLOGENABLED% />
|
|
||||||
|
|
||||||
<label for='LEDMODE'>Status LED pattern: </label><select name='LEDMODE' id='LEDMODE'>
|
|
||||||
%LEDMODE%
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label>Enable MQTT: </label>
|
<label>Enable MQTT: </label>
|
||||||
<input type='checkbox' name='MQTTENABLED' value='on' %MQTTENABLED% />
|
<input type='checkbox' name='MQTTENABLED' value='on' %MQTTENABLED% />
|
||||||
|
|
||||||
<div class='if-mqtt'>
|
<div class='if-mqtt'>
|
||||||
<label>MQTT server: </label><input type='text' name='MQTTSERVER' value="%MQTTSERVER%" />
|
<label>MQTT server: </label>
|
||||||
<label>MQTT port: </label><input type='text' name='MQTTPORT' value="%MQTTPORT%" />
|
<input type='text' name='MQTTSERVER' value="%MQTTSERVER%"
|
||||||
<label>MQTT user: </label><input type='text' name='MQTTUSER' value="%MQTTUSER%" />
|
pattern="[A-Za-z0-9.-]+"
|
||||||
<label>MQTT password: </label><input type='password' name='MQTTPASSWORD' value="%MQTTPASSWORD%" />
|
title="Hostname (letters, numbers, dots, hyphens)" />
|
||||||
<label>MQTT timeout ms: </label><input name='MQTTTIMEOUT' type='text' value="%MQTTTIMEOUT%" pattern="^[0-9]+$" />
|
<label>MQTT port: </label>
|
||||||
|
<input type='number' name='MQTTPORT' value="%MQTTPORT%"
|
||||||
|
min="1" max="65535" step="1"
|
||||||
|
title="Port number (1-65535)" />
|
||||||
|
<label>MQTT user: </label><input type='text' name='MQTTUSER' value="%MQTTUSER%"
|
||||||
|
pattern="[A-Za-z0-9!#*-]+"
|
||||||
|
title="MQTT username can only contain letters, numbers and some special characters: !#*-" />
|
||||||
|
<label>MQTT password: </label><input type='password' name='MQTTPASSWORD' value="%MQTTPASSWORD%"
|
||||||
|
pattern="[A-Za-z0-9!#*-]+"
|
||||||
|
title="MQTT password can only contain letters, numbers and some special characters: !#*-" />
|
||||||
|
<label>MQTT timeout ms: </label>
|
||||||
|
<input name='MQTTTIMEOUT' type='number' value="%MQTTTIMEOUT%"
|
||||||
|
min="1" max="60000" step="1"
|
||||||
|
title="Timeout in milliseconds (1-60000)" />
|
||||||
<label>Send all cellvoltages via MQTT: </label><input type='checkbox' name='MQTTCELLV' value='on' %MQTTCELLV% />
|
<label>Send all cellvoltages via MQTT: </label><input type='checkbox' name='MQTTCELLV' value='on' %MQTTCELLV% />
|
||||||
|
<label>Remote BMS reset via MQTT allowed: </label>
|
||||||
|
<input type='checkbox' name='REMBMSRESET' value='on' %REMBMSRESET% />
|
||||||
<label>Customized MQTT topics: </label>
|
<label>Customized MQTT topics: </label>
|
||||||
<input type='checkbox' name='MQTTTOPICS' value='on' %MQTTTOPICS% />
|
<input type='checkbox' name='MQTTTOPICS' value='on' %MQTTTOPICS% />
|
||||||
|
|
||||||
|
@ -1198,6 +1341,58 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3>Debug options</h3>
|
||||||
|
<div style='display: grid; grid-template-columns: 1fr 1.5fr; gap: 10px; align-items: center;'>
|
||||||
|
|
||||||
|
<label>Enable performance profiling on main page: </label>
|
||||||
|
<input type='checkbox' name='PERFPROFILE' value='on' %PERFPROFILE%
|
||||||
|
title="For developers. Enable this to get detailed performance metrics on the front page" />
|
||||||
|
|
||||||
|
<label>Enable CAN message logging via USB serial: </label>
|
||||||
|
<input type='checkbox' name='CANLOGUSB' value='on' %CANLOGUSB%
|
||||||
|
title="WARNING: Causes performance issues. Enable this to get incoming/outgoing CAN messages logged via USB cable. Avoid if possible" />
|
||||||
|
<script> //Make sure user only uses one general logging method, improves performance
|
||||||
|
function handleCheckboxSelection(clickedCheckbox) {
|
||||||
|
const usbCheckbox = document.querySelector('input[name="USBENABLED"]');
|
||||||
|
const webCheckbox = document.querySelector('input[name="WEBENABLED"]');
|
||||||
|
|
||||||
|
if (clickedCheckbox.checked) {
|
||||||
|
// If the clicked checkbox is being checked, uncheck the other one
|
||||||
|
if (clickedCheckbox.name === 'USBENABLED') {
|
||||||
|
webCheckbox.checked = false;
|
||||||
|
} else {
|
||||||
|
usbCheckbox.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If unchecking, do nothing (allow both to be unchecked)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label>Enable general logging via USB serial: </label>
|
||||||
|
<input type='checkbox' name='USBENABLED' value='on' %USBENABLED%
|
||||||
|
onclick="handleCheckboxSelection(this)"
|
||||||
|
title="WARNING: Causes performance issues. Enable this to get general logging via USB cable. Avoid if possible" />
|
||||||
|
|
||||||
|
<label>Enable general logging via Webserver: </label>
|
||||||
|
<input type='checkbox' name='WEBENABLED' value='on' %WEBENABLED%
|
||||||
|
onclick="handleCheckboxSelection(this)"
|
||||||
|
title="Enable this if you want general logging available in the Webserver" />
|
||||||
|
|
||||||
|
<label>Enable CAN message logging via SD card: </label>
|
||||||
|
<input type='checkbox' name='CANLOGSD' value='on' %CANLOGSD%
|
||||||
|
title="Enable this if you want incoming/outgoing CAN messages to be stored to an SD card. Only works on select hardware with SD-card slot" />
|
||||||
|
|
||||||
|
<label>Enable general logging via SD card: </label>
|
||||||
|
<input type='checkbox' name='SDLOGENABLED' value='on' %SDLOGENABLED%
|
||||||
|
title="Enable this if you want general logging to be stored to an SD card. Only works on select hardware with SD-card slot" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style='grid-column: span 2; text-align: center; padding-top: 10px;'><button type='submit'>Save</button></div>
|
<div style='grid-column: span 2; text-align: center; padding-top: 10px;'><button type='submit'>Save</button></div>
|
||||||
|
|
||||||
<div style='grid-column: span 2; text-align: center; padding-top: 10px;' class="%SAVEDCLASS%">
|
<div style='grid-column: span 2; text-align: center; padding-top: 10px;' class="%SAVEDCLASS%">
|
||||||
|
@ -1286,8 +1481,6 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onclick="askFactoryReset()">Factory reset</button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)rawliteral"
|
)rawliteral"
|
||||||
|
|
|
@ -23,9 +23,6 @@
|
||||||
extern std::string http_username;
|
extern std::string http_username;
|
||||||
extern std::string http_password;
|
extern std::string http_password;
|
||||||
|
|
||||||
bool webserver_enabled =
|
|
||||||
true; // Global flag to enable or disable the webserver //Old method to disable was with #ifdef WEBSERVER
|
|
||||||
|
|
||||||
bool webserver_auth = false;
|
bool webserver_auth = false;
|
||||||
|
|
||||||
// Create AsyncWebServer object on port 80
|
// Create AsyncWebServer object on port 80
|
||||||
|
@ -157,7 +154,7 @@ void canReplayTask(void* param) {
|
||||||
(datalayer.system.info.can_replay_interface == CANFD_ADDON_MCP2518);
|
(datalayer.system.info.can_replay_interface == CANFD_ADDON_MCP2518);
|
||||||
currentFrame.ext_ID = (currentFrame.ID > 0x7F0);
|
currentFrame.ext_ID = (currentFrame.ID > 0x7F0);
|
||||||
|
|
||||||
transmit_can_frame_to_interface(¤tFrame, datalayer.system.info.can_replay_interface);
|
transmit_can_frame_to_interface(¤tFrame, (CAN_Interface)datalayer.system.info.can_replay_interface);
|
||||||
}
|
}
|
||||||
} while (datalayer.system.info.loop_playback);
|
} while (datalayer.system.info.loop_playback);
|
||||||
|
|
||||||
|
@ -400,7 +397,7 @@ void init_webserver() {
|
||||||
"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "SDLOGENABLED", "STATICIP",
|
"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "SDLOGENABLED", "STATICIP",
|
||||||
"REMBMSRESET", "EXTPRECHARGE", "USBENABLED", "CANLOGUSB", "WEBENABLED", "CANFDASCAN", "CANLOGSD",
|
"REMBMSRESET", "EXTPRECHARGE", "USBENABLED", "CANLOGUSB", "WEBENABLED", "CANFDASCAN", "CANLOGSD",
|
||||||
"WIFIAPENABLED", "MQTTENABLED", "NOINVDISC", "HADISC", "MQTTTOPICS", "MQTTCELLV", "INVICNT",
|
"WIFIAPENABLED", "MQTTENABLED", "NOINVDISC", "HADISC", "MQTTTOPICS", "MQTTCELLV", "INVICNT",
|
||||||
"GTWRHD", "DIGITALHVIL", "PERFPROFILE", "INTERLOCKREQ",
|
"GTWRHD", "DIGITALHVIL", "PERFPROFILE", "INTERLOCKREQ", "SOCESTIMATED",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the form POST from UI to save settings of the common image
|
// Handles the form POST from UI to save settings of the common image
|
||||||
|
@ -467,6 +464,12 @@ void init_webserver() {
|
||||||
} else if (p->name() == "WIFICHANNEL") {
|
} else if (p->name() == "WIFICHANNEL") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("WIFICHANNEL", type);
|
settings.saveUInt("WIFICHANNEL", type);
|
||||||
|
} else if (p->name() == "DCHGPOWER") {
|
||||||
|
auto type = atoi(p->value().c_str());
|
||||||
|
settings.saveUInt("DCHGPOWER", type);
|
||||||
|
} else if (p->name() == "CHGPOWER") {
|
||||||
|
auto type = atoi(p->value().c_str());
|
||||||
|
settings.saveUInt("CHGPOWER", type);
|
||||||
} else if (p->name() == "LOCALIP1") {
|
} else if (p->name() == "LOCALIP1") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("LOCALIP1", type);
|
settings.saveUInt("LOCALIP1", type);
|
||||||
|
@ -503,6 +506,10 @@ void init_webserver() {
|
||||||
} else if (p->name() == "SUBNET4") {
|
} else if (p->name() == "SUBNET4") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("SUBNET4", type);
|
settings.saveUInt("SUBNET4", type);
|
||||||
|
} else if (p->name() == "APNAME") {
|
||||||
|
settings.saveString("APNAME", p->value().c_str());
|
||||||
|
} else if (p->name() == "APPASSWORD") {
|
||||||
|
settings.saveString("APPASSWORD", p->value().c_str());
|
||||||
} else if (p->name() == "HOSTNAME") {
|
} else if (p->name() == "HOSTNAME") {
|
||||||
settings.saveString("HOSTNAME", p->value().c_str());
|
settings.saveString("HOSTNAME", p->value().c_str());
|
||||||
} else if (p->name() == "MQTTSERVER") {
|
} else if (p->name() == "MQTTSERVER") {
|
||||||
|
@ -517,7 +524,8 @@ void init_webserver() {
|
||||||
} else if (p->name() == "MQTTTOPIC") {
|
} else if (p->name() == "MQTTTOPIC") {
|
||||||
settings.saveString("MQTTTOPIC", p->value().c_str());
|
settings.saveString("MQTTTOPIC", p->value().c_str());
|
||||||
} else if (p->name() == "MQTTTIMEOUT") {
|
} else if (p->name() == "MQTTTIMEOUT") {
|
||||||
settings.saveString("MQTTTIMEOUT", p->value().c_str());
|
auto port = atoi(p->value().c_str());
|
||||||
|
settings.saveUInt("MQTTTIMEOUT", port);
|
||||||
} else if (p->name() == "MQTTOBJIDPREFIX") {
|
} else if (p->name() == "MQTTOBJIDPREFIX") {
|
||||||
settings.saveString("MQTTOBJIDPREFIX", p->value().c_str());
|
settings.saveString("MQTTOBJIDPREFIX", p->value().c_str());
|
||||||
} else if (p->name() == "MQTTDEVICENAME") {
|
} else if (p->name() == "MQTTDEVICENAME") {
|
||||||
|
@ -548,6 +556,9 @@ void init_webserver() {
|
||||||
} else if (p->name() == "CANFREQ") {
|
} else if (p->name() == "CANFREQ") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("CANFREQ", type);
|
settings.saveUInt("CANFREQ", type);
|
||||||
|
} else if (p->name() == "CANFDFREQ") {
|
||||||
|
auto type = atoi(p->value().c_str());
|
||||||
|
settings.saveUInt("CANFDFREQ", type);
|
||||||
} else if (p->name() == "PRECHGMS") {
|
} else if (p->name() == "PRECHGMS") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("PRECHGMS", type);
|
settings.saveUInt("PRECHGMS", type);
|
||||||
|
@ -610,7 +621,7 @@ void init_webserver() {
|
||||||
def_route_with_auth("/updatePassword", server, HTTP_GET, [](AsyncWebServerRequest* request) {
|
def_route_with_auth("/updatePassword", server, HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
if (request->hasParam("value")) {
|
if (request->hasParam("value")) {
|
||||||
String value = request->getParam("value")->value();
|
String value = request->getParam("value")->value();
|
||||||
if (value.length() > 8) { // Check if password is within the allowable length
|
if (value.length() >= 8) { // Password must be 8 characters or longer
|
||||||
password = value.c_str();
|
password = value.c_str();
|
||||||
store_settings();
|
store_settings();
|
||||||
request->send(200, "text/plain", "Updated successfully");
|
request->send(200, "text/plain", "Updated successfully");
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
|
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
|
||||||
#include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
#include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||||
|
|
||||||
extern bool webserver_enabled;
|
|
||||||
|
|
||||||
extern const char* version_number; // The current software version, shown on webserver
|
extern const char* version_number; // The current software version, shown on webserver
|
||||||
|
|
||||||
// Common charger parameters
|
// Common charger parameters
|
||||||
|
|
|
@ -54,7 +54,7 @@ static uint16_t current_check_interval = WIFI_CHECK_INTERVAL;
|
||||||
static bool connected_once = false;
|
static bool connected_once = false;
|
||||||
|
|
||||||
void init_WiFi() {
|
void init_WiFi() {
|
||||||
DEBUG_PRINTF("init_Wifi enabled=%d, apå=%d, ssid=%s, password=%s\n", wifi_enabled, wifiap_enabled, ssid.c_str(),
|
DEBUG_PRINTF("init_Wifi enabled=%d, ap=%d, ssid=%s, password=%s\n", wifi_enabled, wifiap_enabled, ssid.c_str(),
|
||||||
password.c_str());
|
password.c_str());
|
||||||
|
|
||||||
if (!custom_hostname.empty()) {
|
if (!custom_hostname.empty()) {
|
||||||
|
@ -108,7 +108,7 @@ void wifi_monitor() {
|
||||||
if ((hasConnectedBefore && (currentMillis - lastWiFiCheck > current_check_interval)) ||
|
if ((hasConnectedBefore && (currentMillis - lastWiFiCheck > current_check_interval)) ||
|
||||||
(!hasConnectedBefore && (currentMillis - lastWiFiCheck > INIT_WIFI_FULL_RECONNECT_INTERVAL))) {
|
(!hasConnectedBefore && (currentMillis - lastWiFiCheck > INIT_WIFI_FULL_RECONNECT_INTERVAL))) {
|
||||||
|
|
||||||
DEBUG_PRINTF("Time to monitor Wi-Fi status: %d, %d, %d, %d, %d\n", hasConnectedBefore, currentMillis, lastWiFiCheck,
|
DEBUG_PRINTF("Wi-Fi status: %d, %d, %d, %d, %d\n", hasConnectedBefore, currentMillis, lastWiFiCheck,
|
||||||
current_check_interval, INIT_WIFI_FULL_RECONNECT_INTERVAL);
|
current_check_interval, INIT_WIFI_FULL_RECONNECT_INTERVAL);
|
||||||
|
|
||||||
lastWiFiCheck = currentMillis;
|
lastWiFiCheck = currentMillis;
|
||||||
|
@ -240,7 +240,6 @@ void init_mDNS() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_WiFi_AP() {
|
void init_WiFi_AP() {
|
||||||
ssidAP = std::string("BatteryEmulator") + WiFi.macAddress().c_str();
|
|
||||||
|
|
||||||
DEBUG_PRINTF("Creating Access Point: %s\n", ssidAP.c_str());
|
DEBUG_PRINTF("Creating Access Point: %s\n", ssidAP.c_str());
|
||||||
DEBUG_PRINTF("With password: %s\n", passwordAP.c_str());
|
DEBUG_PRINTF("With password: %s\n", passwordAP.c_str());
|
||||||
|
|
|
@ -139,11 +139,10 @@ void AforeCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x305: // Every 1s from inverter
|
case 0x305: // Every 1s from inverter
|
||||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||||
char0 = rx_frame.data.u8[0]; // A
|
for (uint8_t i = 0; i < 5; i++) {
|
||||||
char1 = rx_frame.data.u8[0]; // F
|
datalayer.system.info.inverter_brand[i] = rx_frame.data.u8[i];
|
||||||
char2 = rx_frame.data.u8[0]; // O
|
}
|
||||||
char3 = rx_frame.data.u8[0]; // R
|
datalayer.system.info.inverter_brand[7] = '\0';
|
||||||
char4 = rx_frame.data.u8[0]; // E
|
|
||||||
inverter_status = rx_frame.data.u8[7];
|
inverter_status = rx_frame.data.u8[7];
|
||||||
time_to_send_info = true;
|
time_to_send_info = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -16,11 +16,6 @@ class AforeCanInverter : public CanInverterProtocol {
|
||||||
uint8_t inverter_status =
|
uint8_t inverter_status =
|
||||||
0; //0 = init, 1 = standby, 2 = starting, 3 = grid connected, 4 off-grid, 5 diesel generator, 6 grid connected, but disconnected, 7off grid and disconnected, 8 = power failure processing, 9 = power off, 10 = Failure
|
0; //0 = init, 1 = standby, 2 = starting, 3 = grid connected, 4 off-grid, 5 diesel generator, 6 grid connected, but disconnected, 7off grid and disconnected, 8 = power failure processing, 9 = power off, 10 = Failure
|
||||||
bool time_to_send_info = false;
|
bool time_to_send_info = false;
|
||||||
uint8_t char0 = 0;
|
|
||||||
uint8_t char1 = 0;
|
|
||||||
uint8_t char2 = 0;
|
|
||||||
uint8_t char3 = 0;
|
|
||||||
uint8_t char4 = 0;
|
|
||||||
//Actual content messages
|
//Actual content messages
|
||||||
CAN_frame AFORE_350 = {.FD = false, // Operation information
|
CAN_frame AFORE_350 = {.FD = false, // Operation information
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
|
|
|
@ -69,6 +69,10 @@ void BydCanInverter::
|
||||||
BYD_150.data.u8[6] = (fully_charged_capacity_ah >> 8);
|
BYD_150.data.u8[6] = (fully_charged_capacity_ah >> 8);
|
||||||
BYD_150.data.u8[7] = (fully_charged_capacity_ah & 0x00FF);
|
BYD_150.data.u8[7] = (fully_charged_capacity_ah & 0x00FF);
|
||||||
|
|
||||||
|
//Alarms
|
||||||
|
//TODO: BYD Alarms are not implemented yet. Investigation needed on the bits in this message
|
||||||
|
//BYD_190.data.u8[0] =
|
||||||
|
|
||||||
//Voltage (ex 370.0)
|
//Voltage (ex 370.0)
|
||||||
BYD_1D0.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
|
BYD_1D0.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
|
||||||
BYD_1D0.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
BYD_1D0.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||||
|
@ -144,21 +148,21 @@ void BydCanInverter::transmit_can(unsigned long currentMillis) {
|
||||||
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
||||||
previousMillis2s = currentMillis;
|
previousMillis2s = currentMillis;
|
||||||
|
|
||||||
transmit_can_frame(&BYD_110);
|
transmit_can_frame(&BYD_110); //Send Limits
|
||||||
}
|
}
|
||||||
// Send 10s CAN Message
|
// Send 10s CAN Message
|
||||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||||
previousMillis10s = currentMillis;
|
previousMillis10s = currentMillis;
|
||||||
|
|
||||||
transmit_can_frame(&BYD_150);
|
transmit_can_frame(&BYD_150); //Send States
|
||||||
transmit_can_frame(&BYD_1D0);
|
transmit_can_frame(&BYD_1D0); //Send Battery Info
|
||||||
transmit_can_frame(&BYD_210);
|
transmit_can_frame(&BYD_210); //Send Cell Info
|
||||||
}
|
}
|
||||||
//Send 60s message
|
//Send 60s message
|
||||||
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
||||||
previousMillis60s = currentMillis;
|
previousMillis60s = currentMillis;
|
||||||
|
|
||||||
transmit_can_frame(&BYD_190);
|
transmit_can_frame(&BYD_190); //Send Alarm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +170,12 @@ void BydCanInverter::send_initial_data() {
|
||||||
transmit_can_frame(&BYD_250);
|
transmit_can_frame(&BYD_250);
|
||||||
transmit_can_frame(&BYD_290);
|
transmit_can_frame(&BYD_290);
|
||||||
transmit_can_frame(&BYD_2D0);
|
transmit_can_frame(&BYD_2D0);
|
||||||
transmit_can_frame(&BYD_3D0_0);
|
BYD_3D0.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}; //Battery
|
||||||
transmit_can_frame(&BYD_3D0_1);
|
transmit_can_frame(&BYD_3D0);
|
||||||
transmit_can_frame(&BYD_3D0_2);
|
BYD_3D0.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}; //-Box Pr
|
||||||
transmit_can_frame(&BYD_3D0_3);
|
transmit_can_frame(&BYD_3D0);
|
||||||
|
BYD_3D0.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}; //emium H
|
||||||
|
transmit_can_frame(&BYD_3D0);
|
||||||
|
BYD_3D0.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}; //VS
|
||||||
|
transmit_can_frame(&BYD_3D0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,48 +40,33 @@ class BydCanInverter : public CanInverterProtocol {
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x2D0,
|
.ID = 0x2D0,
|
||||||
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //BYD
|
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //BYD
|
||||||
CAN_frame BYD_3D0_0 = {.FD = false,
|
CAN_frame BYD_3D0 = {.FD = false,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x3D0,
|
.ID = 0x3D0,
|
||||||
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //Battery
|
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //Battery
|
||||||
CAN_frame BYD_3D0_1 = {.FD = false,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3D0,
|
|
||||||
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
|
|
||||||
CAN_frame BYD_3D0_2 = {.FD = false,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3D0,
|
|
||||||
.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
|
|
||||||
CAN_frame BYD_3D0_3 = {.FD = false,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3D0,
|
|
||||||
.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
|
|
||||||
//Actual content messages
|
//Actual content messages
|
||||||
CAN_frame BYD_110 = {.FD = false,
|
CAN_frame BYD_110 = {.FD = false, //Limits
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x110,
|
.ID = 0x110,
|
||||||
.data = {0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
.data = {0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
CAN_frame BYD_150 = {.FD = false,
|
CAN_frame BYD_150 = {.FD = false, //States
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x150,
|
.ID = 0x150,
|
||||||
.data = {0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00}};
|
.data = {0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00}};
|
||||||
CAN_frame BYD_190 = {.FD = false,
|
CAN_frame BYD_190 = {.FD = false, //Alarm
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x190,
|
.ID = 0x190,
|
||||||
.data = {0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
.data = {0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
CAN_frame BYD_1D0 = {.FD = false,
|
CAN_frame BYD_1D0 = {.FD = false, //Battery Info
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x1D0,
|
.ID = 0x1D0,
|
||||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x08}};
|
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x08}};
|
||||||
CAN_frame BYD_210 = {.FD = false,
|
CAN_frame BYD_210 = {.FD = false, //Cell info
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x210,
|
.ID = 0x210,
|
||||||
|
|
|
@ -136,8 +136,8 @@ void KostalInverterProtocol::update_values() {
|
||||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 18); // Last current
|
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 18); // Last current
|
||||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s)
|
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s)
|
||||||
|
|
||||||
// Close contactors after 20 battery info frames requested
|
// Close contactors after 7 battery info frames requested
|
||||||
if (f2_startup_count > 20) {
|
if (f2_startup_count > 7) {
|
||||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||||
dbg_message("inverter_allows_contactor_closing -> true (info frame)");
|
dbg_message("inverter_allows_contactor_closing -> true (info frame)");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ class SmaBydHInverter : public SmaInverterBase {
|
||||||
void update_values();
|
void update_values();
|
||||||
void transmit_can(unsigned long currentMillis);
|
void transmit_can(unsigned long currentMillis);
|
||||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||||
static constexpr const char* Name = "BYD over SMA CAN";
|
static constexpr const char* Name = "SMA compatible BYD H";
|
||||||
|
|
||||||
virtual bool controls_contactor() { return true; }
|
virtual bool controls_contactor() { return true; }
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class SmaBydHvsInverter : public SmaInverterBase {
|
||||||
void update_values();
|
void update_values();
|
||||||
void transmit_can(unsigned long currentMillis);
|
void transmit_can(unsigned long currentMillis);
|
||||||
void map_can_frame_to_variable(CAN_frame rx_frame);
|
void map_can_frame_to_variable(CAN_frame rx_frame);
|
||||||
static constexpr const char* Name = "BYD Battery-Box HVS over SMA CAN";
|
static constexpr const char* Name = "SMA compatible BYD Battery-Box HVS";
|
||||||
|
|
||||||
virtual bool controls_contactor() { return true; }
|
virtual bool controls_contactor() { return true; }
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,17 @@ void SmaTripowerInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SmaTripowerInverter::pushFrame(CAN_frame* frame, std::function<void(void)> callback) {
|
||||||
|
if (listLength >= 20) {
|
||||||
|
return; //TODO: scream.
|
||||||
|
}
|
||||||
|
framesToSend[listLength] = {
|
||||||
|
.frame = frame,
|
||||||
|
.callback = callback,
|
||||||
|
};
|
||||||
|
listLength++;
|
||||||
|
}
|
||||||
|
|
||||||
void SmaTripowerInverter::transmit_can(unsigned long currentMillis) {
|
void SmaTripowerInverter::transmit_can(unsigned long currentMillis) {
|
||||||
|
|
||||||
// Send CAN Message only if we're enabled by inverter
|
// Send CAN Message only if we're enabled by inverter
|
||||||
|
@ -144,6 +155,18 @@ void SmaTripowerInverter::transmit_can(unsigned long currentMillis) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (listLength > 0 && currentMillis - previousMillis250ms >= INTERVAL_250_MS) {
|
||||||
|
previousMillis250ms = currentMillis;
|
||||||
|
// Send next frame.
|
||||||
|
Frame frame = framesToSend[0];
|
||||||
|
transmit_can_frame(frame.frame);
|
||||||
|
frame.callback();
|
||||||
|
for (int i = 0; i < listLength - 1; i++) {
|
||||||
|
framesToSend[i] = framesToSend[i + 1];
|
||||||
|
}
|
||||||
|
listLength--;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pairing_completed) {
|
if (!pairing_completed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -151,19 +174,19 @@ void SmaTripowerInverter::transmit_can(unsigned long currentMillis) {
|
||||||
// Send CAN Message every 2s
|
// Send CAN Message every 2s
|
||||||
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
||||||
previousMillis2s = currentMillis;
|
previousMillis2s = currentMillis;
|
||||||
transmit_can_frame(&SMA_358);
|
pushFrame(&SMA_358);
|
||||||
}
|
}
|
||||||
// Send CAN Message every 10s
|
// Send CAN Message every 10s
|
||||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||||
previousMillis10s = currentMillis;
|
previousMillis10s = currentMillis;
|
||||||
transmit_can_frame(&SMA_518);
|
pushFrame(&SMA_518);
|
||||||
transmit_can_frame(&SMA_4D8);
|
pushFrame(&SMA_4D8);
|
||||||
transmit_can_frame(&SMA_3D8);
|
pushFrame(&SMA_3D8);
|
||||||
}
|
}
|
||||||
// Send CAN Message every 60s (potentially SMA_458 is not required for stable operation)
|
// Send CAN Message every 60s (potentially SMA_458 is not required for stable operation)
|
||||||
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
||||||
previousMillis60s = currentMillis;
|
previousMillis60s = currentMillis;
|
||||||
transmit_can_frame(&SMA_458);
|
pushFrame(&SMA_458);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,17 +195,18 @@ void SmaTripowerInverter::completePairing() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SmaTripowerInverter::transmit_can_init() {
|
void SmaTripowerInverter::transmit_can_init() {
|
||||||
|
listLength = 0; // clear all frames
|
||||||
|
|
||||||
transmit_can_frame(&SMA_558); //Pairing start - Vendor
|
pushFrame(&SMA_558); //Pairing start - Vendor
|
||||||
transmit_can_frame(&SMA_598); //Serial
|
pushFrame(&SMA_598); //Serial
|
||||||
transmit_can_frame(&SMA_5D8); //BYD
|
pushFrame(&SMA_5D8); //BYD
|
||||||
transmit_can_frame(&SMA_618_0); //BATTERY
|
pushFrame(&SMA_618_0); //BATTERY
|
||||||
transmit_can_frame(&SMA_618_1); //-Box Pr
|
pushFrame(&SMA_618_1); //-Box Pr
|
||||||
transmit_can_frame(&SMA_618_2); //emium H
|
pushFrame(&SMA_618_2); //emium H
|
||||||
transmit_can_frame(&SMA_618_3); //VS
|
pushFrame(&SMA_618_3); //VS
|
||||||
transmit_can_frame(&SMA_358);
|
pushFrame(&SMA_358);
|
||||||
transmit_can_frame(&SMA_3D8);
|
pushFrame(&SMA_3D8);
|
||||||
transmit_can_frame(&SMA_458);
|
pushFrame(&SMA_458);
|
||||||
transmit_can_frame(&SMA_4D8);
|
pushFrame(&SMA_4D8);
|
||||||
transmit_can_frame(&SMA_518);
|
pushFrame(&SMA_518, [this]() { this->completePairing(); });
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include "../devboard/hal/hal.h"
|
#include "../devboard/hal/hal.h"
|
||||||
#include "SmaInverterBase.h"
|
#include "SmaInverterBase.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class SmaTripowerInverter : public SmaInverterBase {
|
class SmaTripowerInverter : public SmaInverterBase {
|
||||||
public:
|
public:
|
||||||
const char* name() override { return Name; }
|
const char* name() override { return Name; }
|
||||||
|
@ -20,6 +22,7 @@ class SmaTripowerInverter : public SmaInverterBase {
|
||||||
const int THIRTY_MINUTES = 1200;
|
const int THIRTY_MINUTES = 1200;
|
||||||
|
|
||||||
void transmit_can_init();
|
void transmit_can_init();
|
||||||
|
void pushFrame(CAN_frame* frame, std::function<void(void)> callback = []() {});
|
||||||
void completePairing();
|
void completePairing();
|
||||||
|
|
||||||
unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send
|
unsigned long previousMillis250ms = 0; // will store last time a 250ms CAN Message was send
|
||||||
|
@ -28,6 +31,14 @@ class SmaTripowerInverter : public SmaInverterBase {
|
||||||
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
||||||
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
|
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CAN_frame* frame;
|
||||||
|
std::function<void(void)> callback;
|
||||||
|
} Frame;
|
||||||
|
|
||||||
|
unsigned short listLength = 0;
|
||||||
|
Frame framesToSend[20];
|
||||||
|
|
||||||
uint32_t inverter_time = 0;
|
uint32_t inverter_time = 0;
|
||||||
uint16_t inverter_voltage = 0;
|
uint16_t inverter_voltage = 0;
|
||||||
int16_t inverter_current = 0;
|
int16_t inverter_current = 0;
|
||||||
|
|
|
@ -292,8 +292,5 @@ bool SofarInverter::setup() { // Performs one time setup at startup over CAN bu
|
||||||
init_frame(SOFAR_783, 0x783);
|
init_frame(SOFAR_783, 0x783);
|
||||||
init_frame(SOFAR_784, 0x784);
|
init_frame(SOFAR_784, 0x784);
|
||||||
|
|
||||||
snprintf(datalayer.system.info.inverter_brand, sizeof(datalayer.system.info.inverter_brand), "%s",
|
|
||||||
datalayer.battery.settings.sofar_user_specified_battery_id);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,6 @@
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
extern const uint8_t ELEGANT_HTML[41354];
|
extern const uint8_t ELEGANT_HTML[10615];
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -101,7 +101,7 @@ class ACAN_ESP32_Settings {
|
||||||
// Transmit buffer sizes
|
// Transmit buffer sizes
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
public: uint16_t mDriverTransmitBufferSize = 16 ;
|
public: uint16_t mDriverTransmitBufferSize = 32 ;
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
// Compute actual bit rate
|
// Compute actual bit rate
|
||||||
|
|
23
Software/src/lib/update_ota_html_gzip.py
Normal file
23
Software/src/lib/update_ota_html_gzip.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
libpath = Path("ayushsharma82-ElegantOTA")
|
||||||
|
gzipped=libpath/"CurrentPlainHTML.txt.gz"
|
||||||
|
header=libpath/"src/elop.h"
|
||||||
|
cpp=libpath/"src/elop.cpp"
|
||||||
|
if not gzipped.exists():
|
||||||
|
print(f"Please create {gzipped.resolve()} to replace OTA file.")
|
||||||
|
print(f"Example: zopfli -v --i10000 {libpath.resolve()}/CurrentPlainHTML.txt")
|
||||||
|
raise SystemExit(1)
|
||||||
|
gzipbytes=gzipped.read_bytes()
|
||||||
|
intlist = [int(one) for one in gzipbytes]
|
||||||
|
content = json.dumps(intlist).replace("[","{").replace("]","}").replace(" ", "")
|
||||||
|
headertext = header.read_text()
|
||||||
|
header.write_text(headertext[:1+headertext.find("[")]+str(len(gzipbytes))+headertext[headertext.find("]"):])
|
||||||
|
cpptext = cpp.read_text()
|
||||||
|
first_bracket = cpptext.find("[")
|
||||||
|
second_bracket = cpptext.find("]")
|
||||||
|
corrected_bytes = cpptext[:1+first_bracket]+str(len(gzipbytes))+cpptext[second_bracket:]
|
||||||
|
cppout = corrected_bytes[:corrected_bytes.find("{")]+content+corrected_bytes[corrected_bytes.find(";"):]+"\n"
|
||||||
|
cpp.write_text(cppout)
|
||||||
|
print("File content updated from", gzipped.resolve())
|
||||||
|
print("Bytes fixed:", cpptext[1+first_bracket:second_bracket], "to", len(gzipbytes))
|
|
@ -26,6 +26,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = default, time, log2file
|
monitor_filters = default, time, log2file
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
board_build.f_flash = 80000000
|
||||||
|
board_build.arduino.memory_type = qio_qspi
|
||||||
board_build.partitions = min_spiffs.csv
|
board_build.partitions = min_spiffs.csv
|
||||||
framework = arduino
|
framework = arduino
|
||||||
build_flags = -I include -DHW_LILYGO
|
build_flags = -I include -DHW_LILYGO
|
||||||
|
@ -36,6 +39,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = default, time, log2file, esp32_exception_decoder
|
monitor_filters = default, time, log2file, esp32_exception_decoder
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
board_build.f_flash = 80000000
|
||||||
|
board_build.arduino.memory_type = qio_qspi
|
||||||
board_build.partitions = min_spiffs.csv
|
board_build.partitions = min_spiffs.csv
|
||||||
framework = arduino
|
framework = arduino
|
||||||
build_flags = -I include -DHW_STARK
|
build_flags = -I include -DHW_STARK
|
||||||
|
|
|
@ -63,13 +63,19 @@ include_directories("${source_dir}/googletest/include"
|
||||||
include_directories(emul)
|
include_directories(emul)
|
||||||
|
|
||||||
# For eModBus
|
# For eModBus
|
||||||
add_compile_definitions(ESP32 HW_LILYGO NISSAN_LEAF_BATTERY)
|
add_compile_definitions(ESP32 HW_LILYGO COMMON_IMAGE)
|
||||||
|
|
||||||
# add the executable
|
# add the executable
|
||||||
add_executable(tests
|
add_executable(tests
|
||||||
tests.cpp
|
tests.cpp
|
||||||
safety_tests.cpp
|
safety_tests.cpp
|
||||||
battery/NissanLeafTest.cpp
|
battery/NissanLeafTest.cpp
|
||||||
|
battery/still_alive_tests.cpp
|
||||||
|
can_log_based/canlog_safety_tests.cpp
|
||||||
|
utils/utils.cpp
|
||||||
|
../Software/src/communication/can/obd.cpp
|
||||||
|
../Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp
|
||||||
|
../Software/src/communication/rs485/comm_rs485.cpp
|
||||||
../Software/src/devboard/safety/safety.cpp
|
../Software/src/devboard/safety/safety.cpp
|
||||||
../Software/src/devboard/hal/hal.cpp
|
../Software/src/devboard/hal/hal.cpp
|
||||||
../Software/src/devboard/utils/events.cpp
|
../Software/src/devboard/utils/events.cpp
|
||||||
|
@ -122,6 +128,7 @@ add_executable(tests
|
||||||
../Software/src/battery/RENAULT-TWIZY.cpp
|
../Software/src/battery/RENAULT-TWIZY.cpp
|
||||||
../Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
|
../Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
|
||||||
../Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
|
../Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
|
||||||
|
../Software/src/battery/RIVIAN-BATTERY.cpp
|
||||||
../Software/src/battery/RJXZS-BMS.cpp
|
../Software/src/battery/RJXZS-BMS.cpp
|
||||||
../Software/src/battery/SAMSUNG-SDI-LV-BATTERY.cpp
|
../Software/src/battery/SAMSUNG-SDI-LV-BATTERY.cpp
|
||||||
../Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp
|
../Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp
|
||||||
|
|
105
test/battery/still_alive_tests.cpp
Normal file
105
test/battery/still_alive_tests.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "../utils/utils.h"
|
||||||
|
|
||||||
|
#include "../../Software/src/battery/BATTERIES.h"
|
||||||
|
#include "../../Software/src/devboard/utils/events.h"
|
||||||
|
|
||||||
|
class BatteryTestFixture : public testing::Test {
|
||||||
|
public:
|
||||||
|
BatteryTestFixture(BatteryType type) : type(type) {}
|
||||||
|
// Optional:
|
||||||
|
// static void SetUpTestSuite() { ... }
|
||||||
|
// static void TearDownTestSuite() { ... }
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
// Reset the datalayer and events before each test
|
||||||
|
datalayer = DataLayer();
|
||||||
|
reset_all_events();
|
||||||
|
if (battery) {
|
||||||
|
delete battery;
|
||||||
|
battery = nullptr;
|
||||||
|
}
|
||||||
|
init_hal();
|
||||||
|
|
||||||
|
user_selected_battery_type = type;
|
||||||
|
setup_battery();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
if (battery) {
|
||||||
|
delete battery;
|
||||||
|
battery = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
BatteryType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the CAN aliveness timeout isn't being renewed by bogus CAN frames
|
||||||
|
class TestNotStillAlive : public BatteryTestFixture {
|
||||||
|
public:
|
||||||
|
explicit TestNotStillAlive(BatteryType type) : BatteryTestFixture(type) {}
|
||||||
|
void TestBody() override {
|
||||||
|
// check if battery is a CanBattery subclass
|
||||||
|
auto* battery = dynamic_cast<CanBattery*>(::battery);
|
||||||
|
if (battery == nullptr) {
|
||||||
|
GTEST_SKIP() << "Battery is not a CanBattery subclass";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the still-alive counter to 0 (ie, not alive)
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = 0;
|
||||||
|
|
||||||
|
// A random fake CAN frame
|
||||||
|
CAN_frame frame = {
|
||||||
|
.ID = 0x7ff,
|
||||||
|
.data = {.u8 = {0x00, 0x64, 0x00, 0x64, 0x0F, 0xA0, 0x27, 0x10}},
|
||||||
|
};
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
battery->handle_incoming_can_frame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check it's still not alive (ie, the CAN frame handling didn't renew the counter)
|
||||||
|
EXPECT_EQ(datalayer.battery.status.CAN_battery_still_alive, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsValidCanBattery(BatteryType type) {
|
||||||
|
if (type == BatteryType::TestFake) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need some minimal setup or the battery constructors may segfault
|
||||||
|
datalayer = DataLayer();
|
||||||
|
// This leaks memory (but not much...)
|
||||||
|
init_hal();
|
||||||
|
|
||||||
|
auto* tmp_battery = create_battery(type);
|
||||||
|
if (tmp_battery == nullptr) {
|
||||||
|
// Not a valid battery type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* as_can_battery = dynamic_cast<CanBattery*>(tmp_battery);
|
||||||
|
if (as_can_battery == nullptr) {
|
||||||
|
// Failed to cast to CanBattery, so it's not a CAN battery
|
||||||
|
delete tmp_battery;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete tmp_battery;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterStillAliveTests() {
|
||||||
|
for (int i = 0; i < (int)BatteryType::Highest; i++) {
|
||||||
|
if (!IsValidCanBattery((BatteryType)i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string test_name = ("TestNotStillAlive" + snake_case_to_camel_case(name_for_battery_type((BatteryType)i)));
|
||||||
|
testing::RegisterTest("StillAliveTests", test_name.c_str(), nullptr, nullptr, __FILE__, __LINE__,
|
||||||
|
[=]() -> BatteryTestFixture* { return new TestNotStillAlive((BatteryType)i); });
|
||||||
|
}
|
||||||
|
}
|
5
test/can_log_based/can_logs/24_RjxzsBms_ov_cov.txt
Normal file
5
test/can_log_based/can_logs/24_RjxzsBms_ov_cov.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# 65278V pack (overvoltage)
|
||||||
|
(123.893) RX0 f5 [8] 03 fe fe 00 00 00 00 00
|
||||||
|
|
||||||
|
# 5V max cell (cell overvoltage)
|
||||||
|
(124.893) RX0 f5 [8] 51 01 01 01 01 13 88 00
|
6
test/can_log_based/can_logs/37_MgHsPhev_base.txt
Normal file
6
test/can_log_based/can_logs/37_MgHsPhev_base.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
(84215.921) RX0 295 [8] d5 1d 00 01 a6 4e 20 01
|
||||||
|
(84215.922) RX0 171 [8] 00 34 b5 75 4d 03 78 c2
|
||||||
|
(84215.923) RX0 172 [8] 3b e0 d2 3f 20 dd 00 00
|
||||||
|
(84216.021) RX0 173 [8] 00 00 c8 c8 0e 2d 0e 29
|
||||||
|
(84216.027) RX0 2a2 [8] 78 77 04 c7 40 76 00 6a
|
||||||
|
(84216.028) RX0 3ac [8] 01 a6 01 2f 05 1d 4e 20
|
5
test/can_log_based/can_logs/37_MgHsPhev_ov_cov.txt
Normal file
5
test/can_log_based/can_logs/37_MgHsPhev_ov_cov.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# 450V pack (overvoltage)
|
||||||
|
(13148.893) RX0 3ac [8] 01 d4 01 6f 07 08 4d d0
|
||||||
|
|
||||||
|
# 5V max cell (cell overvoltage)
|
||||||
|
(38245.429) RX0 173 [8] 00 00 c8 c3 13 88 0e 18
|
11
test/can_log_based/can_logs/5_BydAtto3_base.txt
Normal file
11
test/can_log_based/can_logs/5_BydAtto3_base.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# voltage
|
||||||
|
(0.001) RX0 7ef [8] 00 00 00 08 81 34 ee ee
|
||||||
|
# lowest cell
|
||||||
|
(0.002) RX0 7ef [8] 00 00 00 2f 30 ee ee ee
|
||||||
|
# highest cell
|
||||||
|
(0.002) RX0 7ef [8] 00 00 00 31 40 ee ee ee
|
||||||
|
|
||||||
|
# indicate the pack is still alive
|
||||||
|
(0.003) RX0 244 [8] 00 00 00 00 00 00 00 00
|
||||||
|
|
||||||
|
# this is enough to pass (all the other params have defaults)
|
201
test/can_log_based/canlog_safety_tests.cpp
Normal file
201
test/can_log_based/canlog_safety_tests.cpp
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "../utils/utils.h"
|
||||||
|
|
||||||
|
#include "../../Software/src/battery/BATTERIES.h"
|
||||||
|
#include "../../Software/src/devboard/utils/events.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
// These tests replay CAN logs against individual batteries to check that they
|
||||||
|
// are correctly parsed, and that safety mechanisms work.
|
||||||
|
|
||||||
|
// The base class for our tests
|
||||||
|
class CanLogTestFixture : public testing::Test {
|
||||||
|
public:
|
||||||
|
CanLogTestFixture(fs::path path) : path_(std::move(path)) {}
|
||||||
|
// Optional:
|
||||||
|
// static void SetUpTestSuite() { ... }
|
||||||
|
// static void TearDownTestSuite() { ... }
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
// Reset the datalayer and events before each test
|
||||||
|
datalayer = DataLayer();
|
||||||
|
reset_all_events();
|
||||||
|
if (battery) {
|
||||||
|
delete battery;
|
||||||
|
battery = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume a 90s NMC pack for custom-BMS batteries
|
||||||
|
user_selected_max_pack_voltage_dV = 378 + 10;
|
||||||
|
user_selected_min_pack_voltage_dV = 261 - 10;
|
||||||
|
user_selected_max_cell_voltage_mV = 4200 + 20;
|
||||||
|
user_selected_min_cell_voltage_mV = 2900 - 20;
|
||||||
|
|
||||||
|
// Extract battery type from log filename
|
||||||
|
std::string filename = path_.filename().string();
|
||||||
|
std::string batteryId = filename.substr(0, filename.find('_'));
|
||||||
|
user_selected_battery_type = (BatteryType)std::stoi(batteryId);
|
||||||
|
setup_battery();
|
||||||
|
|
||||||
|
// Initialize datalayer to invalid values
|
||||||
|
datalayer.battery.status.voltage_dV = 0;
|
||||||
|
datalayer.battery.status.current_dA = INT16_MIN;
|
||||||
|
datalayer.battery.status.cell_min_voltage_mV = 0;
|
||||||
|
datalayer.battery.status.cell_max_voltage_mV = 0;
|
||||||
|
datalayer.battery.status.real_soc = UINT16_MAX;
|
||||||
|
datalayer.battery.status.temperature_max_dC = INT16_MIN;
|
||||||
|
datalayer.battery.status.temperature_min_dC = INT16_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
if (battery) {
|
||||||
|
delete battery;
|
||||||
|
battery = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessLog() {
|
||||||
|
std::vector<CAN_frame> parsedMessages = parse_can_log_file(path_);
|
||||||
|
|
||||||
|
for (const auto& msg : parsedMessages) {
|
||||||
|
dynamic_cast<CanBattery*>(battery)->handle_incoming_can_frame(msg);
|
||||||
|
dynamic_cast<CanBattery*>(battery)->update_values();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_machineryprotection();
|
||||||
|
|
||||||
|
// When debugging, uncomment this to see the parsed values
|
||||||
|
// PrintValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintValues() {
|
||||||
|
std::cout << "Battery voltage: " << (datalayer.battery.status.voltage_dV / 10.0) << " V" << std::endl;
|
||||||
|
std::cout << "Battery current: " << (datalayer.battery.status.current_dA / 10.0) << " A" << std::endl;
|
||||||
|
std::cout << "Battery cell min voltage: " << datalayer.battery.status.cell_min_voltage_mV << " mV" << std::endl;
|
||||||
|
std::cout << "Battery cell max voltage: " << datalayer.battery.status.cell_max_voltage_mV << " mV" << std::endl;
|
||||||
|
std::cout << "Battery real SoC: " << (datalayer.battery.status.real_soc / 100.0) << " %" << std::endl;
|
||||||
|
std::cout << "Battery temperature max: " << (datalayer.battery.status.temperature_max_dC / 10.0) << " C"
|
||||||
|
<< std::endl;
|
||||||
|
std::cout << "Battery temperature min: " << (datalayer.battery.status.temperature_min_dC / 10.0) << " C"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
fs::path path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the parsed logs populate the minimum required datalayer values for
|
||||||
|
// Battery Emulator to function.
|
||||||
|
class BaseValuesPresentTest : public CanLogTestFixture {
|
||||||
|
public:
|
||||||
|
explicit BaseValuesPresentTest(fs::path path) : CanLogTestFixture(path) {}
|
||||||
|
void TestBody() override {
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive = 10;
|
||||||
|
|
||||||
|
ProcessLog();
|
||||||
|
|
||||||
|
EXPECT_GT(datalayer.battery.status.CAN_battery_still_alive, 10);
|
||||||
|
EXPECT_NE(datalayer.battery.status.voltage_dV, 0);
|
||||||
|
// TODO: Current isn't actually a requirement? check power instead?
|
||||||
|
//EXPECT_NE(datalayer.battery.status.current_dA, INT16_MIN);
|
||||||
|
EXPECT_NE(datalayer.battery.status.cell_min_voltage_mV, 0);
|
||||||
|
EXPECT_NE(datalayer.battery.status.cell_max_voltage_mV, 0);
|
||||||
|
EXPECT_NE(datalayer.battery.status.real_soc, UINT16_MAX);
|
||||||
|
EXPECT_NE(datalayer.battery.status.temperature_max_dC, INT16_MIN);
|
||||||
|
EXPECT_NE(datalayer.battery.status.temperature_min_dC, INT16_MIN);
|
||||||
|
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_BATTERY_OVERVOLTAGE)->occurences, 0);
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_BATTERY_UNDERVOLTAGE)->occurences, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the parsed logs correctly trigger an overvoltage event.
|
||||||
|
class OverVoltageTest : public CanLogTestFixture {
|
||||||
|
public:
|
||||||
|
explicit OverVoltageTest(fs::path path) : CanLogTestFixture(path) {}
|
||||||
|
void TestBody() override {
|
||||||
|
ProcessLog();
|
||||||
|
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_BATTERY_OVERVOLTAGE)->occurences, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the parsed logs correctly trigger a cell overvoltage event.
|
||||||
|
class CellOverVoltageTest : public CanLogTestFixture {
|
||||||
|
public:
|
||||||
|
explicit CellOverVoltageTest(fs::path path) : CanLogTestFixture(path) {}
|
||||||
|
void TestBody() override {
|
||||||
|
ProcessLog();
|
||||||
|
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_CELL_OVER_VOLTAGE)->occurences, 1);
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_CELL_CRITICAL_OVER_VOLTAGE)->occurences, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the parsed logs correctly trigger a cell undervoltage event.
|
||||||
|
class CellUnderVoltageTest : public CanLogTestFixture {
|
||||||
|
public:
|
||||||
|
explicit CellUnderVoltageTest(fs::path path) : CanLogTestFixture(path) {}
|
||||||
|
void TestBody() override {
|
||||||
|
ProcessLog();
|
||||||
|
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_CELL_UNDER_VOLTAGE)->occurences, 1);
|
||||||
|
EXPECT_EQ(get_event_pointer(EVENT_CELL_CRITICAL_UNDER_VOLTAGE)->occurences, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RegisterCanLogTests() {
|
||||||
|
// The logs should be named as follows:
|
||||||
|
//
|
||||||
|
// <battery_type>_<battery class name>_<flag1>_<flag2...>.txt
|
||||||
|
//
|
||||||
|
// where:
|
||||||
|
// battery_type is the integer in the BatteryType enum
|
||||||
|
// flag1/flag2... are flags that indicate which tests to run:
|
||||||
|
// base: test that the minimmum required values are populated (and no events triggered)
|
||||||
|
// ov: test that an overvoltage event is triggered
|
||||||
|
// cov: test that normal and critical cell overvoltage events are triggered
|
||||||
|
// cuv: test that normal and critical cell undervoltage events are triggered
|
||||||
|
|
||||||
|
std::string directoryPath = "../can_log_based/can_logs";
|
||||||
|
|
||||||
|
for (const auto& entry : fs::directory_iterator(directoryPath)) {
|
||||||
|
if (!entry.is_regular_file() || entry.path().extension().string() != ".txt") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bits = split(entry.path().stem(), '_');
|
||||||
|
auto has_flag = [&bits](const std::string& flag) -> bool {
|
||||||
|
return std::find(bits.begin() + 2, bits.end(), flag) != bits.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (has_flag("base")) {
|
||||||
|
testing::RegisterTest("CanLogSafetyTests",
|
||||||
|
("TestBaseValuesPresent" + snake_case_to_camel_case(entry.path().stem().string())).c_str(),
|
||||||
|
nullptr, nullptr, __FILE__, __LINE__,
|
||||||
|
[=]() -> CanLogTestFixture* { return new BaseValuesPresentTest(entry.path()); });
|
||||||
|
}
|
||||||
|
if (has_flag("ov")) {
|
||||||
|
testing::RegisterTest("CanLogSafetyTests",
|
||||||
|
("TestOverVoltage" + snake_case_to_camel_case(entry.path().stem().string())).c_str(),
|
||||||
|
nullptr, nullptr, __FILE__, __LINE__,
|
||||||
|
[=]() -> CanLogTestFixture* { return new OverVoltageTest(entry.path()); });
|
||||||
|
}
|
||||||
|
if (has_flag("cov")) {
|
||||||
|
testing::RegisterTest("CanLogSafetyTests",
|
||||||
|
("TestCellOverVoltage" + snake_case_to_camel_case(entry.path().stem().string())).c_str(),
|
||||||
|
nullptr, nullptr, __FILE__, __LINE__,
|
||||||
|
[=]() -> CanLogTestFixture* { return new CellOverVoltageTest(entry.path()); });
|
||||||
|
}
|
||||||
|
if (has_flag("cuv")) {
|
||||||
|
testing::RegisterTest("CanLogSafetyTests",
|
||||||
|
("TestCellUnderVoltage" + snake_case_to_camel_case(entry.path().stem().string())).c_str(),
|
||||||
|
nullptr, nullptr, __FILE__, __LINE__,
|
||||||
|
[=]() -> CanLogTestFixture* { return new CellUnderVoltageTest(entry.path()); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ int digitalRead(uint8_t pin) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
void digitalWrite(uint8_t pin, uint8_t val) {}
|
void digitalWrite(uint8_t pin, uint8_t val) {}
|
||||||
|
|
||||||
unsigned long micros() {
|
unsigned long micros() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -24,22 +25,11 @@ int max(int a, int b) {
|
||||||
return (a > b) ? a : b;
|
return (a > b) ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock implementation for OBD
|
bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, int8_t channel) {
|
||||||
#include "../../Software/src/communication/can/obd.h"
|
return true;
|
||||||
void handle_obd_frame(CAN_frame& frame) {
|
|
||||||
(void)frame;
|
|
||||||
}
|
}
|
||||||
void transmit_obd_can_frame(unsigned int address, int interface, bool canFD) {
|
bool ledcWrite(uint8_t pin, uint32_t duty) {
|
||||||
(void)interface;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
void start_bms_reset() {}
|
|
||||||
|
|
||||||
#include "../../Software/src/communication/rs485/comm_rs485.h"
|
|
||||||
|
|
||||||
// Mock implementation
|
|
||||||
void register_receiver(Rs485Receiver* receiver) {
|
|
||||||
(void)receiver; // Silence unused parameter warning
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ESPClass ESP;
|
ESPClass ESP;
|
||||||
|
|
|
@ -121,6 +121,9 @@ void delay(unsigned long ms);
|
||||||
void delayMicroseconds(unsigned long us);
|
void delayMicroseconds(unsigned long us);
|
||||||
int max(int a, int b);
|
int max(int a, int b);
|
||||||
|
|
||||||
|
bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, int8_t channel);
|
||||||
|
bool ledcWrite(uint8_t pin, uint32_t duty);
|
||||||
|
|
||||||
class ESPClass {
|
class ESPClass {
|
||||||
public:
|
public:
|
||||||
size_t getFlashChipSize() {
|
size_t getFlashChipSize() {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#include "../../Software/src/communication/Transmitter.h"
|
#include "../../Software/src/communication/Transmitter.h"
|
||||||
#include "../../Software/src/communication/can/comm_can.h"
|
#include "../../Software/src/communication/can/comm_can.h"
|
||||||
|
|
||||||
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, int interface) {}
|
void transmit_can_frame_to_interface(const CAN_frame* tx_frame, CAN_Interface interface) {}
|
||||||
|
|
||||||
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface, CAN_Speed speed) {}
|
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface, CAN_Speed speed) {}
|
||||||
|
|
||||||
CAN_Speed change_can_speed(CAN_Interface interface, CAN_Speed speed) {
|
bool change_can_speed(CAN_Interface interface, CAN_Speed speed) {
|
||||||
return CAN_Speed::CAN_SPEED_500KBPS;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop_can() {}
|
void stop_can() {}
|
||||||
|
@ -18,3 +18,5 @@ char const* getCANInterfaceName(CAN_Interface) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void register_transmitter(Transmitter* transmitter) {}
|
void register_transmitter(Transmitter* transmitter) {}
|
||||||
|
|
||||||
|
void dump_can_frame(CAN_frame& frame, CAN_Interface interface, frameDirection msgDir) {}
|
||||||
|
|
|
@ -5,8 +5,13 @@
|
||||||
#include "../Software/src/devboard/safety/safety.h"
|
#include "../Software/src/devboard/safety/safety.h"
|
||||||
#include "../Software/src/devboard/utils/events.h"
|
#include "../Software/src/devboard/utils/events.h"
|
||||||
|
|
||||||
|
void RegisterCanLogTests(void);
|
||||||
|
void RegisterStillAliveTests(void);
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
testing::InitGoogleTest(&argc, argv);
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
RegisterCanLogTests();
|
||||||
|
RegisterStillAliveTests();
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
130
test/utils/utils.cpp
Normal file
130
test/utils/utils.cpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
bool ends_with(const std::string& str, const std::string& suffix) {
|
||||||
|
return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> split(const std::string& text, char sep) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::size_t start = 0, end = 0;
|
||||||
|
while ((end = text.find(sep, start)) != std::string::npos) {
|
||||||
|
tokens.push_back(text.substr(start, end - start));
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
tokens.push_back(text.substr(start));
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_frame(const CAN_frame& frame) {
|
||||||
|
std::cout << "ID: " << std::hex << frame.ID << ", DLC: " << (int)frame.DLC << ", Data: ";
|
||||||
|
for (int i = 0; i < frame.DLC; ++i) {
|
||||||
|
std::cout << std::hex << (int)frame.data.u8[i] << " ";
|
||||||
|
}
|
||||||
|
std::cout << std::dec << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string snake_case_to_camel_case(const std::string& str) {
|
||||||
|
std::string result;
|
||||||
|
bool toUpper = false;
|
||||||
|
for (char ch : str) {
|
||||||
|
if (ch == '_') {
|
||||||
|
toUpper = true;
|
||||||
|
} else if (ch < '0' || (ch > '9' && ch < 'A') || (ch > 'Z' && ch < 'a') || ch > 'z') {
|
||||||
|
// skip non-alphanumeric characters
|
||||||
|
toUpper = true;
|
||||||
|
} else {
|
||||||
|
if (toUpper) {
|
||||||
|
result += toupper(ch);
|
||||||
|
toUpper = false;
|
||||||
|
} else {
|
||||||
|
result += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAN_frame parse_can_log_line(const std::string& logLine) {
|
||||||
|
std::stringstream ss(logLine);
|
||||||
|
CAN_frame frame = {};
|
||||||
|
char dummy;
|
||||||
|
|
||||||
|
double timestamp;
|
||||||
|
std::string interfaceName;
|
||||||
|
|
||||||
|
// timestamp and interface name are parsed but not used
|
||||||
|
ss >> dummy >> timestamp >> dummy;
|
||||||
|
ss >> interfaceName;
|
||||||
|
|
||||||
|
// parse hexadecimal CAN ID
|
||||||
|
ss >> std::hex >> frame.ID;
|
||||||
|
if (ss.fail()) {
|
||||||
|
throw std::runtime_error("Invalid format: Failed to parse CAN ID.");
|
||||||
|
}
|
||||||
|
// check whether the ID is in the extended range
|
||||||
|
frame.ext_ID = (frame.ID > 0x7FF);
|
||||||
|
|
||||||
|
// parse the data length
|
||||||
|
int dlc_val;
|
||||||
|
ss >> dummy; // Consume '['
|
||||||
|
if (ss.fail() || dummy != '[') {
|
||||||
|
throw std::runtime_error("Invalid format: Missing opening bracket for data length.");
|
||||||
|
}
|
||||||
|
ss >> dlc_val;
|
||||||
|
frame.DLC = static_cast<uint8_t>(dlc_val);
|
||||||
|
ss >> dummy; // Consume ']'
|
||||||
|
if (ss.fail() || dummy != ']') {
|
||||||
|
throw std::runtime_error("Invalid format: Missing closing bracket for data length.");
|
||||||
|
}
|
||||||
|
// crudely assume CAN FD if DLC > 8
|
||||||
|
frame.FD = (frame.DLC > 8);
|
||||||
|
|
||||||
|
// parse the actual data bytes
|
||||||
|
unsigned int byte;
|
||||||
|
for (int i = 0; i < frame.DLC; ++i) {
|
||||||
|
ss >> std::hex >> byte;
|
||||||
|
if (ss.fail()) {
|
||||||
|
throw std::runtime_error("Fewer data bytes than specified by data length.");
|
||||||
|
}
|
||||||
|
frame.data.u8[i] = static_cast<uint8_t>(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CAN_frame> parse_can_log_file(const fs::path& filePath) {
|
||||||
|
std::ifstream logFile(filePath);
|
||||||
|
if (!logFile.is_open()) {
|
||||||
|
std::cerr << "Error: Could not open file " << filePath << std::endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CAN_frame> frames;
|
||||||
|
std::string line;
|
||||||
|
int lineNumber = 0;
|
||||||
|
|
||||||
|
// read the file line by line
|
||||||
|
while (std::getline(logFile, line)) {
|
||||||
|
lineNumber++;
|
||||||
|
if (line.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line[0] == '#' || line[0] == ';') {
|
||||||
|
continue; // skip comment lines
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
frames.push_back(parse_can_log_line(line));
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
std::cerr << "Warning: Skipping malformed line " << lineNumber << " in " << filePath.filename()
|
||||||
|
<< ". Reason: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
12
test/utils/utils.h
Normal file
12
test/utils/utils.h
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#include "../../Software/src/devboard/utils/types.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
bool ends_with(const std::string& str, const std::string& suffix);
|
||||||
|
std::vector<std::string> split(const std::string& text, char sep);
|
||||||
|
std::string snake_case_to_camel_case(const std::string& str);
|
||||||
|
|
||||||
|
std::vector<CAN_frame> parse_can_log_file(const fs::path& filePath);
|
Loading…
Add table
Add a link
Reference in a new issue