Compare commits

...

419 commits

Author SHA1 Message Date
Daniel Öster
d8153c48bb
Update CONTRIBUTING.md 2025-10-03 13:52:14 +03:00
Daniel Öster
f3a6157080
Merge pull request #1592 from dalathegreat/improvement/stark-BMS-on-by-default
Stark CMR: Make BMS power on by default
2025-10-02 14:27:47 +03:00
Daniel Öster
867512eecd Make Stark BMS power on by default 2025-10-01 21:13:41 +03:00
Daniel Öster
f9109d0348
Merge pull request #1587 from dalathegreat/bugfix/passwords-dashes
Bugfix: Allow dash as special character in usernames/passwords
2025-10-01 20:08:27 +03:00
Daniel Öster
9943406836 Make Wifi use actual set SSID for AP 2025-09-30 23:29:08 +03:00
Daniel Öster
18ee9d6c27 Allow dash as special character in usernames/passwords 2025-09-30 22:16:05 +03:00
Daniel Öster
31ea1f0928
Update Software.cpp 2025-09-30 14:43:14 +03:00
Daniel Öster
dad97b36e1
Update Software.cpp 2025-09-30 14:37:22 +03:00
Daniel Öster
19a1634f4e
Merge pull request #1580 from jonny5532/fix/settings-validation
Fix hostname/MQTT settings validation
2025-09-28 14:08:04 +03:00
Jonny
aa375dc36a Fix broken hostname/MQTT settings validation 2025-09-28 09:05:30 +01:00
Jonny
a91f8ab4d4 Remove redundant ^$ on settings input patterns 2025-09-28 08:59:38 +01:00
Daniel Öster
7fbc9ffcc6
Merge pull request #1575 from greenoem/tesla-feature-events
Feature: Tesla - add initial/basic events
2025-09-27 23:15:05 +03:00
Daniel Öster
579e0e0bcc
Update Software.cpp 2025-09-27 22:09:42 +03:00
James Brookes
b26c451eaf Fix % in SOC_RECALIBRATION string 2025-09-27 19:27:15 +01:00
James Brookes
ee9e78e80b Merge branch 'tesla-feature-events' of https://github.com/greenoem/Battery-Emulator into tesla-feature-events 2025-09-27 18:04:10 +01:00
James Brookes
706b4a7cea Add contactor weld warning event, change events to native latch type 2025-09-27 18:03:56 +01:00
greenoem
f5ba607fa6
Merge branch 'dalathegreat:main' into tesla-feature-events 2025-09-26 21:21:46 +01:00
Daniel Öster
572867b7ad
Update Software.cpp 2025-09-26 21:33:04 +03:00
Daniel Öster
47e715ffe5
Merge pull request #1572 from dalathegreat/bugfix/chars-non-ascii
Improvement: Add more input field validation on Settings page
2025-09-26 21:31:38 +03:00
Daniel Öster
c445bd1869 Move factory reset to top of page, tweak password 2025-09-26 21:27:19 +03:00
Daniel Öster
28c0267cae Improve password/username entering 2025-09-26 19:29:50 +03:00
Daniel Öster
4058050423 Add more tooltips 2025-09-26 13:45:39 +03:00
James Brookes
04d9a36292 Add event and logging text for SOC Reset 2025-09-26 10:38:56 +01:00
Daniel Öster
29129037b0 Add more input field validation on Settings page 2025-09-25 23:44:46 +03:00
James Brookes
c6b7ff82c0 Alter BMS_a145_SW_SOC_Change event to once only 2025-09-25 19:09:46 +01:00
James Brookes
95ee6ff9ae Add events for BMS_a145_SW_SOC_Change, BMS reset and SOC reset 2025-09-25 19:03:54 +01:00
Daniel Öster
8cc10a1b71
Merge pull request #1568 from dalathegreat/bugfix/tesla-task-overrun-logging
Performance: Reduce likelyhood of TASK_OVERRUN when using Tesla
2025-09-25 16:04:01 +03:00
Daniel Öster
3ef3279527 Remove unused codeblocks 2025-09-25 15:49:50 +03:00
Daniel Öster
f48b4235c1 Simplify DigitalHVIL sending to increase performance 2025-09-25 15:30:52 +03:00
Daniel Öster
cdf6314bff
Merge pull request #1567 from dalathegreat/bugfix/egmp-cellvoltage-count
Kia E-GMP: Update valid cellvoltage filter
2025-09-25 14:36:39 +03:00
Daniel Öster
48411680c6 Raise limit further 2025-09-25 12:04:36 +03:00
Daniel Öster
132029169d Raise limit to 2500 for filtering 2025-09-25 11:09:56 +03:00
Daniel Öster
483d4300b1 Make sure user only enables one general logging method at once 2025-09-25 00:00:59 +03:00
Daniel Öster
397e8d03a1 Remove/shorten periodically logged items 2025-09-25 00:00:35 +03:00
Daniel Öster
d336b75f56 Update valid cellvoltage filter 2025-09-24 22:54:53 +03:00
Daniel Öster
a7af7cf938
Merge pull request #1563 from greenoem/tesla-fix-gtw
Tesla: Fix GTW 0x7FF frame incorrect value written
2025-09-23 20:32:10 +03:00
James Brookes
2ba937bd32 Fix incorrect user_selected_tesla_GTW_country value written to 7FF user_selected_tesla_GTW_rightHandDrive bit 2025-09-23 18:10:24 +01:00
Daniel Öster
48d416b5c4
Update Software.cpp 2025-09-23 10:28:46 +03:00
Daniel Öster
ed2ebb00fd
Update Software.cpp 2025-09-23 10:01:21 +03:00
Daniel Öster
0458267634
Merge pull request #1560 from dalathegreat/improvement/enable-qio-stark
Enable QIO and 80MHZ flash on Stark CMR
2025-09-23 09:43:21 +03:00
Daniel Öster
a0de6b092b Enable QIO and 80MHZ flash on Stark CMR 2025-09-23 09:38:28 +03:00
Daniel Öster
0f2cbe8f04
Merge pull request #1559 from jonny5532/feature/enable-qio
Enable QIO and 80mhz flash on Lilygo T-CAN485 build (50-80% more perf)
2025-09-23 09:32:08 +03:00
Jonny
983105ab6a Enable QIO and 80mhz flash on Lilygo T-CAN485 build (50-80% perf improvement) 2025-09-23 07:15:11 +01:00
Daniel Öster
5aa657e0b5
Merge pull request #1557 from dalathegreat/improvement/wifi-name-access
Improvement: Make AP name configurable
2025-09-23 09:12:33 +03:00
Daniel Öster
69ae0385fb Make AP name configurable 2025-09-22 15:36:41 +03:00
Daniel Öster
a8d74f5885
Merge pull request #1535 from korhojoa/zopfli
Repackage OTA html file with zopfli
2025-09-22 15:16:21 +03:00
Daniel Öster
20b6ea5e85
Merge pull request #1531 from dalathegreat/bugfix/reduce-flash
Improvement: Reduce flash usage
2025-09-22 14:53:54 +03:00
Daniel Öster
b8bbb02b20
Merge pull request #1540 from dalathegreat/bugfix/zoe1-voltage-limits
Bugfix: Tweak voltage limits for Zoe1
2025-09-22 12:29:44 +03:00
Daniel Öster
4896f3061a
Merge pull request #1491 from dalathegreat/improvement/stellantis-can-mappings
Improvement: Stellantis CAN mappings
2025-09-22 10:34:44 +03:00
Daniel Öster
0aaab6d4b7
Merge pull request #1548 from dalathegreat/improvement/volvo-low-12V
Volvo SPA: Add low 12V voltage safety
2025-09-22 10:34:22 +03:00
Daniel Öster
a12ea4b112
Update README.md 2025-09-21 21:02:45 +03:00
Daniel Öster
e023962bfc
Merge pull request #1555 from dalathegreat/improvement/SMA-naming
Rename protocol to make it more clear it is SMA
2025-09-20 23:56:23 +03:00
Daniel Öster
4df42e10b4 Rename protocol to make it more clear it is SMA 2025-09-20 23:49:47 +03:00
Daniel Öster
ee4981b6e1
Merge pull request #1554 from mbuhansen/main
Kostal revert startup counter.
2025-09-20 22:34:04 +03:00
mbuhansen
172e260167
Update GPIO pin assignments for contactor handling 2025-09-20 21:20:41 +02:00
mbuhansen
fd52c0e5e7
Uncomment contactor welded event checks 2025-09-20 21:19:08 +02:00
mbuhansen
6982476e89
Change contactor closing condition from 20 to 7 frames 2025-09-20 21:14:57 +02:00
mbuhansen
24c1ce73ae
Merge branch 'dalathegreat:main' into main 2025-09-20 21:13:04 +02:00
Daniel Öster
d8530a2b8b
Update README.md 2025-09-20 13:45:30 +03:00
Daniel Öster
7a5cd36e6d
Update README.md 2025-09-20 13:37:02 +03:00
Daniel Öster
3bfc350b65
Merge pull request #1546 from dalathegreat/bugfix/egmp-estimated-soc-configurable
Kia eGMP: Add configurable option for estimated SOC
2025-09-19 10:03:42 +03:00
Daniel Öster
b52bc7bfd4
Merge pull request #1545 from dalathegreat/bugfix/tesla-contactor-opening
Tesla: Make contactor opening take 9s instead of 60s
2025-09-19 10:03:29 +03:00
mbuhansen
89abc6a964
Update GPIO pin assignments for contactors 2025-09-19 08:45:19 +02:00
mbuhansen
45643237e4
Comment out contactor welded event checks 2025-09-19 08:42:19 +02:00
Daniel Öster
e66161176b Fix scaling on Rescaled SOC on More Battery Info page 2025-09-18 22:24:32 +03:00
Daniel Öster
269c655dc5 Add low 12V voltage safety to Volvo 2025-09-18 10:48:56 +03:00
Daniel Öster
15143d1384 Add configurable option for estimated SOC 2025-09-17 23:23:55 +03:00
Daniel Öster
0244468624 Make contactor opening take 9s instead of 60s 2025-09-17 23:06:30 +03:00
Daniel Öster
3ead4d12d4
Merge pull request #1543 from jonny5532/fix/allow-8char-wifi-passwords
Fix validation so that 8 character WiFi passwords work
2025-09-16 19:39:05 +03:00
Jonny
5277665dd1 Fix validation so that 8 character WiFi passwords work 2025-09-16 16:14:20 +01:00
Daniel Öster
79964a0601 Remove old method to disable webserver to save flash 2025-09-16 14:44:30 +03:00
Daniel Öster
e11843e4a0 Merge branch 'main' into bugfix/reduce-flash 2025-09-16 14:41:12 +03:00
Daniel Öster
df52d067e7
Update Software.cpp 2025-09-16 13:36:42 +03:00
Daniel Öster
1ea663c0b9
Merge pull request #1541 from jonny5532/fix/dont-error-if-ssid-pw-blank
Stop erroneous events when saving an empty SSID/pw
2025-09-15 23:11:37 +03:00
Jonny
73c6821a9a Stop erroneous events when saving an empty SSID/pw 2025-09-15 20:23:35 +01:00
Daniel Öster
26126bae1a Tweak voltage limits for Zoe1 2025-09-15 14:42:54 +03:00
Daniel Öster
980e450871
Update Software.cpp 2025-09-15 13:25:57 +03:00
Daniel Öster
c7bb82c1de
Merge pull request #1537 from jonny5532/fix/revert-sma-tripower-changes
Restore previous SMA TRIPOWER behaviour to fix regression/issues
2025-09-14 23:22:27 +03:00
Daniel Öster
a287d0e208
Merge pull request #1538 from dalathegreat/improvement/webpage-layout
Improvement: Refactor Settings page with cards
2025-09-14 23:21:37 +03:00
Jonny
50d794d0de Restore previous SMA TRIPOWER behaviour to fix regression and potential issues 2025-09-14 21:16:48 +01:00
Daniel Öster
332e982bed Move save button, update naming 2025-09-14 23:12:32 +03:00
Daniel Öster
9ee0dffb33 Refactor Settings page with cards 2025-09-14 22:45:03 +03:00
korhojoa
6094a2c85b Add tool to easily replace OTA file 2025-09-13 17:28:56 +03:00
korhojoa
b5d14edb94 Repackage OTA gzip with zopfli 2025-09-13 17:28:47 +03:00
Daniel Öster
00a820f007
Merge pull request #1526 from jonny5532/fix/t-2can-dio-16mb
Switch T-2CAN back to dio, and try 16mb
2025-09-13 12:24:58 +03:00
Daniel Öster
34466de3a7
Merge pull request #1528 from jonny5532/feature/log-can-interfaces
Include CAN interface number in logs (with distinct TX/RX)
2025-09-13 12:22:08 +03:00
Daniel Öster
bf14553d77 Add notes on BYD messages 2025-09-13 10:53:11 +03:00
Daniel Öster
bc2e49fb5d
Merge pull request #1529 from jonny5532/feature/fix-serialless-on-esp32s3
Wait at most 100ms for Serial in init_serial on 2CAN so it will boot without USB
2025-09-12 22:40:53 +03:00
Daniel Öster
fdc1fb61ba Improve Afore writing of name 2025-09-12 22:39:01 +03:00
Daniel Öster
2546b6da21 Reduce CAN templates in BYD-CAN 2025-09-12 22:32:03 +03:00
Daniel Öster
7178e0376e Reduce CAN messages used by Kia PHEV 2025-09-12 22:19:44 +03:00
Jonny
5510d3aeb5 Wait at most 100ms for Serial in init_serial on 2CAN so it will boot without USB 2025-09-12 20:07:25 +01:00
Jonny
9554cbf808 Include CAN interface number in logs (with distinct TX/RX) 2025-09-12 15:31:44 +01:00
Daniel Öster
082c005a20
Merge pull request #1524 from dalathegreat/improvement/save-flash-ota-array
Improvement: Optimize OTA Flash usage
2025-09-12 14:04:17 +03:00
Jonny
5b7491c7a7 Switch T-2CAN back to dio, and try 16mb 2025-09-12 09:01:28 +01:00
Daniel Öster
018cd4ed52 Allocate less memory for ELEGANT_HTLM array 2025-09-11 23:48:55 +03:00
Daniel Öster
0aad11d9bc Add contactor state to more battery info 2025-09-11 23:27:15 +03:00
Daniel Öster
29e6f52c4c Add alerts to more battery info page 2025-09-11 23:05:06 +03:00
Daniel Öster
25393106b8 Merge branch 'main' into improvement/stellantis-can-mappings 2025-09-11 21:32:22 +03:00
Daniel Öster
fd2e5f2e52 Add start for MysteryVan More Info page 2025-09-11 21:30:56 +03:00
Daniel Öster
b33f42c9c5
Update Software.cpp 2025-09-11 20:36:48 +03:00
Daniel Öster
5246cd34c9
Update Software.cpp 2025-09-11 20:32:11 +03:00
Daniel Öster
a336ac73fb
Merge pull request #1522 from jonny5532/fix/change-2can-bootloader-offset
Change bootloader offset (and flash config) for T-2CAN
2025-09-11 20:30:55 +03:00
Daniel Öster
a2a00a488f
Update CONTRIBUTING.md 2025-09-11 18:38:29 +03:00
Jonny
c7b6d0adee Change bootloader offset (and flash config) for T-2CAN 2025-09-11 16:36:20 +01:00
Daniel Öster
9fe83fb131
Merge pull request #1517 from dalathegreat/feature/new-rivian-battery
New Battery! 🔋 Add support for Rivian batteries  🔋
2025-09-10 22:32:32 +03:00
Daniel Öster
62d0d59e74 Simplify CAN writing 2025-09-10 21:13:38 +03:00
Daniel Öster
2e859a1ee6 Correct shifting of amp limits 2025-09-10 21:01:13 +03:00
Daniel Öster
9b6f5409c2
Merge pull request #1494 from jonny5532/fix/change-can-speed
Fix multiple bugs with native change_can_speed operation to fix BMW PHEV
2025-09-10 20:49:25 +03:00
Daniel Öster
57c65e2eeb
Merge pull request #1518 from jonny5532/fix/exp32s3-factory-build
Use ths correct chip ID for the T-2CAN factory image
2025-09-09 23:25:06 +03:00
Jonny
8ba6a04d61 Use ths correct chip ID for the T-2CAN factory image 2025-09-09 21:16:52 +01:00
Daniel Öster
47ceaf9a7b
Merge pull request #1516 from dalathegreat/bugfix/double-renault50-bug
Bugfix: Double Renault Zoe2 not initializing
2025-09-09 22:28:18 +03:00
Daniel Öster
521ab481d7 Add Rivian to tests 2025-09-09 21:25:20 +03:00
Daniel Öster
ac153ec901 Simplify capacity calculation and initial values 2025-09-09 21:15:35 +03:00
Daniel Öster
fc109cd954 Add Rivian battery support 2025-09-09 21:08:07 +03:00
Daniel Öster
5f73a4c32b Update version number 2025-09-09 20:46:31 +03:00
Daniel Öster
f609427e00 Make Zoe2 double-battery possible 2025-09-09 20:45:44 +03:00
Daniel Öster
edb69472c3
Merge pull request #1514 from dalathegreat/bugfix/AP-password
Bugfix: Add AP password to settings page
2025-09-09 00:02:13 +03:00
Daniel Öster
d8d64ee16c Add APpassword to settings page 2025-09-08 23:21:17 +03:00
Jonny
db9537a34b Fix native CAN speed changing using new library, with new CanBattery API (currently only used by BMW PHEV). 2025-09-08 17:39:03 +01:00
Daniel Öster
577a353285
Update Software.cpp 2025-09-08 19:17:35 +03:00
Daniel Öster
42353efdff
Merge pull request #1512 from dalathegreat/improvement/configurable-fd-frequency
Improvement: Make CAN-FD frequency configurable
2025-09-08 19:17:11 +03:00
Daniel Öster
454a4565c0 Make CAN-FD frequence configurable 2025-09-08 19:10:24 +03:00
Daniel Öster
bf7d10c825
Merge pull request #1510 from jonny5532/feature/battery-can-aliveness-test
Tweak CAN still-alive tests to more reliably construct batteries
2025-09-08 18:18:39 +03:00
Jonny
5ae8155866 Tweak CAN still-alive tests to more reliably construct batteries (and change name) 2025-09-08 10:34:49 +01:00
Daniel Öster
8354e554e4
Merge pull request #1509 from jonny5532/feature/battery-can-aliveness-test
Add tests to make sure batteries aren't renewing liveness on bogus CAN frames
2025-09-08 12:30:56 +03:00
Daniel Öster
eb44ea7f42
Merge pull request #1508 from dalathegreat/dependabot/github_actions/actions/setup-python-6
Bump actions/setup-python from 5 to 6
2025-09-08 11:44:58 +03:00
Jonny
96b3293023 Add tests to make sure batteries aren't renewing liveness on bogus CAN frames 2025-09-08 09:36:53 +01:00
Daniel Öster
69b0d23b45
Merge pull request #1506 from jonny5532/feature/can-log-aliveness-test
Make CAN log base tests check for aliveness
2025-09-08 11:36:13 +03:00
Daniel Öster
d481947e34
Merge pull request #1505 from jonny5532/tidy/parameterised-tests
Tidy up parameterised test names
2025-09-08 11:35:22 +03:00
Daniel Öster
b5ebcdbcd7
Merge pull request #1504 from jonny5532/feature/fix-can-still-alive
Change several batteries to only flag CAN aliveness on relevant msgs
2025-09-08 11:34:47 +03:00
Daniel Öster
c6ad1f8afe
Merge pull request #1503 from dalathegreat/bugfix/logging-unreliable
Bugfix: Make setup() unable to return early
2025-09-08 11:33:56 +03:00
Daniel Öster
354926a6b3
Merge pull request #1507 from jonny5532/fix/rjxzs-cell-count
Fix cellvoltages index on RJXZS population
2025-09-08 11:33:31 +03:00
dependabot[bot]
3767c698bd
Bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 01:07:30 +00:00
Jonny
b115abf081 Fix cellvoltages index on RJXZS population 2025-09-07 23:51:21 +01:00
Jonny
036f9aa4b9 Make the CAN log tests check for aliveness 2025-09-07 22:46:11 +01:00
Jonny
5555b38fea Tidy up parameterised test names 2025-09-07 22:45:12 +01:00
Jonny
7bbbf846d8 Change several batteries to only flag CAN aliveness on relevant msg reception 2025-09-07 22:38:50 +01:00
Daniel Öster
5b277d633b Make setup() unable to return early 2025-09-08 00:10:07 +03:00
Daniel Öster
5bb12915fe
Merge pull request #1502 from dalathegreat/bugfix/precharge-crash
MEB: Fix CAN still alive handler
2025-09-07 23:54:43 +03:00
Daniel Öster
93b30241da Move stillalive handler to actual messages 2025-09-07 20:44:33 +03:00
Daniel Öster
30a7b9d90e Add notes on CAN sending 2025-09-07 20:14:38 +03:00
Daniel Öster
e02cb61efc Add final CAN mappings for MysteryVan 2025-09-07 18:55:00 +03:00
Daniel Öster
c01291d15f
Update Software.cpp 2025-09-07 13:22:54 +03:00
jonny5532
3c4880e783
Merge pull request #1443 from jonny5532/feature/can-log-based-testing-rebase
CAN log based testing
2025-09-07 11:07:35 +01:00
Daniel Öster
3172c32cc7
Merge pull request #1496 from jonny5532/fix/rjxzs-cell-count
Fix RJXZS cell count population (and tidy)
2025-09-07 12:06:00 +03:00
Jonny
7d8accb95d Change can log test flags 2025-09-07 09:40:17 +01:00
Jonny
fd9d1ec714 Add CAN log replay based battery tests 2025-09-07 08:58:30 +01:00
Jonny
7f8f48756d Tidy up and add more test stubs 2025-09-07 08:58:30 +01:00
Jonny
b205b63af3 Tidy some battery files 2025-09-07 08:56:17 +01:00
Daniel Öster
64a3d5f5aa Add more CAN mappings 2025-09-07 00:01:35 +03:00
Daniel Öster
a40de5ecee
Merge pull request #1495 from jonny5532/fix/remove-duplicate-CanBattery-constructor
Remove the duplicate CanBattery constructor.
2025-09-06 23:23:44 +03:00
Daniel Öster
99b79eab7e
Merge pull request #1498 from dalathegreat/bugfix/sofar-crash-common
Sofar: Crashfix, remove faulty print causing crash
2025-09-06 21:35:29 +03:00
Daniel Öster
9662054a4f
Merge pull request #1489 from dalathegreat/improvement/egmp-charge-settings
Improvement: Add estimated charge setting for Kia eGMP
2025-09-06 21:31:39 +03:00
Daniel Öster
29aecc577b Remove faulty print causing crash 2025-09-06 21:24:17 +03:00
Daniel Öster
cfdc1a3130 Improve mappings 2025-09-06 21:21:53 +03:00
Jonny
b40387817d Fix RJXZS cell count population (and tidy) 2025-09-06 14:00:12 +01:00
Jonny
7903805373 Remove the duplicate CanBattery constructor. 2025-09-06 12:10:17 +01:00
Daniel Öster
282b27ff5d
Merge pull request #1492 from dalathegreat/bugfix/scaled-soc-negative-regression
Bugfix: Fix underflow bug when scaling SOC
2025-09-05 10:16:15 +03:00
Daniel Öster
1b012fb2d0
Fix underflow bug when scaling
Accidentally changed datatype during refactoring
2025-09-04 23:27:18 +03:00
Daniel Öster
54f9cb49f3 Add mappings for 2D4, 3B4 and 2F4 2025-09-04 23:21:26 +03:00
Daniel Öster
b17c9ad106
Update Software.cpp 2025-09-04 22:37:06 +03:00
Daniel Öster
3764ab4bf9 Add estimated charge setting for Kia eGMP 2025-09-04 22:34:38 +03:00
Daniel Öster
c1f3187306
Update Software.cpp 2025-09-04 20:36:12 +03:00
Daniel Öster
a8532b6f78
Merge pull request #1487 from dalathegreat/improvement/estimated-charge
Feature: Add manual override charge/discharge limits
2025-09-04 20:34:33 +03:00
Daniel Öster
a9185be603 Add manual override charge(discharge limits 2025-09-04 20:26:17 +03:00
Daniel Öster
91bbb25270
Merge pull request #1484 from dalathegreat/feature/ecmp-75kWh
Stellantis: Add initial support for 50/75kWh vans
2025-09-04 15:51:33 +03:00
Daniel Öster
2ef8055535
Merge pull request #1485 from No-Signal/bugfix/mqtt_loop_fix
Fixing mqtt_loop setup regression bug
2025-09-04 15:05:25 +03:00
Matt Holmes
8fe21fddb7 Fixing mqtt_loop setup regression bug 2025-09-04 10:35:32 +01:00
Daniel Öster
5770df46e4 Add offset to voltage value to make it more realistic 2025-09-04 09:28:07 +03:00
Daniel Öster
bd0923cab3 Only update temp and cellv when both min/max are available 2025-09-03 23:55:35 +03:00
Daniel Öster
3597a99a08 Fix float calculation for SOC 2025-09-03 23:52:32 +03:00
Daniel Öster
805d506062 Fix SOC and capacity for mystery van 2025-09-03 23:39:50 +03:00
Daniel Öster
e38b6286c7 Add initial support for 50/75kWh vans 2025-09-03 23:25:30 +03:00
Daniel Öster
805b3e10d2
Merge pull request #1483 from dalathegreat/bugfix/i3-dropping-messages
Bugfix: Increase CAN TX buffer to avoid dropping messages
2025-09-03 22:10:30 +03:00
Daniel Öster
08b6c81e67 Increase CAN TX buffer 2025-09-03 13:02:08 +03:00
Daniel Öster
0617bf4b8c
Update release-assets.yml 2025-09-02 23:59:44 +03:00
Daniel Öster
21870349d4
Update Software.cpp 2025-09-02 23:40:32 +03:00
Daniel Öster
eba1b640cf
Merge pull request #1481 from dalathegreat/feature/configurable-static-IP
Improvement: Add configurable static IP
2025-09-02 23:40:09 +03:00
Daniel Öster
90d3332143 Add configurable static IP 2025-09-02 23:34:58 +03:00
Daniel Öster
63100d2a37
Merge pull request #1478 from dalathegreat/improvement/moar-common-image
Improvement: Remove USER_SETTINGS entirely
2025-09-02 22:04:22 +03:00
Daniel Öster
57009c6f72 Merge branch 'improvement/moar-common-image' of github.com:dalathegreat/Battery-Emulator into improvement/moar-common-image 2025-09-02 22:00:08 +03:00
Daniel Öster
778d9b8585 Wifi unit test wrap 2025-09-02 22:00:00 +03:00
pre-commit-ci[bot]
630c67a482 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-09-02 18:57:25 +00:00
Daniel Öster
081d96e055 Fix unit tests after removal of USER_SETTINGS 2025-09-02 21:56:30 +03:00
Daniel Öster
53d40cbe51 Remove USER_SETTINGS for good 2025-09-02 21:18:15 +03:00
Daniel Öster
da560e1629 Remove more USER_SETTINGS 2025-09-02 21:06:18 +03:00
Daniel Öster
bb21b5d8e1 Remove unused ifdefs 2025-09-02 20:51:53 +03:00
Daniel Öster
f03324585b Move more USER_SETTINGS out 2025-09-02 20:42:48 +03:00
Daniel Öster
bf6e4c8a0f Merge branch 'improvement/moar-common-image' of github.com:dalathegreat/Battery-Emulator into improvement/moar-common-image 2025-09-02 20:13:59 +03:00
Daniel Öster
21eda56c9e Make Wifi channel configurable 2025-09-02 20:12:42 +03:00
pre-commit-ci[bot]
58af5b658b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-09-02 16:42:38 +00:00
Daniel Öster
224c33ec1d Make more settings configurable 2025-09-02 19:42:26 +03:00
Daniel Öster
3e56549498 Merge branch 'main' into improvement/moar-common-image 2025-09-02 19:03:29 +03:00
Daniel Öster
77b1844151
Merge pull request #1480 from dalathegreat/bugfix/foxess-can-crash
Restore CAN-FD library to v2.1.4
2025-09-02 18:28:33 +03:00
Daniel Öster
a0b0fbd5d6 Restore CAN-FD library to v2.1.4 2025-09-02 18:27:37 +03:00
Daniel Öster
fb62b2cdb1 Progress 2025-09-02 18:13:24 +03:00
Daniel Öster
1cc4a021c2 Convert .ino to .cpp file 2025-09-01 23:50:17 +03:00
Daniel Öster
aba193ca85 Remove more USER_SETTINGS 2025-09-01 23:41:18 +03:00
Daniel Öster
7328f7b99b Remove even more USER_SETTINGS 2025-09-01 23:22:34 +03:00
Daniel Öster
688ba0388c Remove more USER_SETTINGS 2025-09-01 23:12:21 +03:00
Daniel Öster
dabbcd8bcd Make LEAF interlock setting configurable 2025-09-01 22:03:00 +03:00
Daniel Öster
955688fec0
Merge pull request #1476 from dalathegreat/improvement/even-more-common-image-settings
Improvement: Remove old method to compile, all is now Common Image
2025-09-01 21:34:44 +03:00
Daniel Öster
6f76bdfeb0 Restore HEX printing 2025-09-01 21:30:48 +03:00
Daniel Öster
4d0777ce52
Merge pull request #1477 from dalathegreat/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-09-01 21:27:48 +03:00
Daniel Öster
12cc542a42 Make tests pass 2025-09-01 21:27:09 +03:00
pre-commit-ci[bot]
5c3aaf0a22 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-09-01 16:31:01 +00:00
pre-commit-ci[bot]
cf68489bf8
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-clang-format: v20.1.8 → v21.1.0](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.8...v21.1.0)
2025-09-01 16:29:53 +00:00
Daniel Öster
024ff58965 Remove secrets copy from tests 2025-09-01 15:34:28 +03:00
Daniel Öster
21b2b06dab Remove USER_SECRETS 2025-09-01 15:23:02 +03:00
Daniel Öster
7ee0eb5e88 Remove old method to compile, all is now Common Image 2025-09-01 15:03:56 +03:00
Daniel Öster
ee4b14c770
Merge pull request #1464 from dalathegreat/bugfix/ioniq28-PID
Ioniq 28: Correct SOH reading. Add isolation resistance to More Battery Info
2025-09-01 14:18:44 +03:00
Daniel Öster
7cc5d368d1
Merge pull request #1475 from dalathegreat/improvement/even-more-webserver-settings
Improvement: Move Contactor settings to common-image
2025-09-01 13:54:16 +03:00
Daniel Öster
9031d3e5cf Move more settings to common-image 2025-09-01 13:25:59 +03:00
Daniel Öster
6253cd678b
Merge pull request #1474 from dalathegreat/improvement/more-settings-webserver
Improvement: More settings webserver
2025-09-01 11:58:58 +03:00
Daniel Öster
7ded4f1662 Remove user voltage set ability from USER_SETTINGS, now common 2025-08-31 23:52:44 +03:00
Daniel Öster
13df59f806 Make performance profiling configurable from webserver 2025-08-31 22:54:57 +03:00
Daniel Öster
dc4aa95109 Remove broken NTP BMS reset functionality 2025-08-31 21:57:41 +03:00
Daniel Öster
d38a8d6655
Update Software.ino 2025-08-31 17:50:51 +03:00
Daniel Öster
36493a89cc
Update README.md 2025-08-31 17:50:26 +03:00
Daniel Öster
6c25a669ec
Update Software.ino 2025-08-31 17:20:28 +03:00
Daniel Öster
d750201795 Merge branch 'main' into bugfix/ioniq28-PID 2025-08-31 17:19:06 +03:00
Daniel Öster
62f23bd2db
Merge pull request #1472 from dalathegreat/bugfix/crash-on-boot
Common Image: Fix crash for some integrations
2025-08-31 17:18:40 +03:00
Daniel Öster
b0330dc5f5 Fix crash on Common Image for MEB 2025-08-31 17:10:02 +03:00
Daniel Öster
bdcf4e788c Fix crash on Common Image for Ioniq 28 2025-08-31 17:02:06 +03:00
Daniel Öster
a6f406a928 Fix crash on Common Image for Geely Geometry C 2025-08-31 16:48:07 +03:00
Daniel Öster
f27015d5bd
Merge pull request #1471 from dalathegreat/feature/build-common-image-2CAN
Feature: Build common image on release for LilyGo T-2CAN
2025-08-31 15:50:43 +03:00
Daniel Öster
e5678d2406 Add workflow for T-2CAN 2025-08-31 15:45:15 +03:00
Daniel Öster
d7190d770b Remove obsolete compile all hardware workflow 2025-08-31 15:37:39 +03:00
Daniel Öster
21a2c2daf3
Merge pull request #1470 from dalathegreat/bugfix/tests-not-passing
Bugfix: Make tests pass after 2_CAN board merge
2025-08-31 15:20:25 +03:00
Daniel Öster
90884998a1 Move boards folder to HAL 2025-08-31 15:13:08 +03:00
Daniel Öster
7c72526946 Make tests pass 2025-08-31 15:08:45 +03:00
Daniel Öster
0e904f9465
Merge pull request #1384 from No-Signal/feature/lilygo_t_2can
Hardware: Add support for Lilygo T_2CAN board
2025-08-31 14:56:55 +03:00
Daniel Öster
3d6333f4ef
Merge pull request #1469 from dalathegreat/bugfix/zoe2-double-balancing
Renault Zoe2: Make secondary battery write correctly to balancing status
2025-08-31 14:46:49 +03:00
Daniel Öster
f0cdc4919a
Merge pull request #1437 from dalathegreat/library/acanfd-update
Library 📜 Update ACAN2517FD to version 2.1.16
2025-08-31 14:46:20 +03:00
Daniel Öster
a7f9d32c3a Merge branch 'main' into library/acanfd-update 2025-08-31 14:21:49 +03:00
Daniel Öster
4574b7317c Make secondary battery write correctly to balancing status 2025-08-31 14:16:52 +03:00
Daniel Öster
c3caad4da7
Merge branch 'main' into feature/lilygo_t_2can 2025-08-31 14:11:21 +03:00
Daniel Öster
083432da78 Make LED pattern configurable in Webserver 2025-08-31 14:07:57 +03:00
Daniel Öster
fba4534ce4
Merge pull request #1468 from dalathegreat/improvement/debug-at-runtime
Improvement: Debug Web/USB as configurable setting
2025-08-31 13:13:28 +03:00
Daniel Öster
268910ef63 Add mock print implementation for unit tests 2025-08-31 13:07:17 +03:00
Daniel Öster
01aa926f26 Update gitignore for local test execution 2025-08-31 11:48:21 +03:00
Daniel Öster
d0f74934af
Update CONTRIBUTING.md with Unit tests 2025-08-31 11:44:37 +03:00
Daniel Öster
825db3567f Fix unit test to work with logging 2025-08-31 11:37:39 +03:00
Daniel Öster
8af86d9f29 Add logging to tests 2025-08-31 11:31:45 +03:00
Daniel Öster
e7cf83e387
Merge branch 'main' into feature/lilygo_t_2can 2025-08-31 11:22:04 +03:00
Daniel Öster
c252bf32cc
Merge pull request #1448 from mbuhansen/main
Kostal inverter add contactor sequence,
2025-08-30 23:48:35 +03:00
Daniel Öster
d1d1ab9f5f Merge branch 'main' into bugfix/ioniq28-PID 2025-08-30 23:46:48 +03:00
Daniel Öster
bcfc745ea6
Merge pull request #1442 from dalathegreat/feature/RELION-LV-protocol
New Battery 🔋 Add support for RELi³ON LV battery protocol
2025-08-30 23:42:55 +03:00
Daniel Öster
5d0486c87c Merge branch 'main' into improvement/debug-at-runtime 2025-08-30 23:26:28 +03:00
Daniel Öster
d2098e1de5 Merge branch 'main' into improvement/debug-at-runtime 2025-08-30 23:24:47 +03:00
Daniel Öster
6c8326fce1 Make SD card configurable via webserver 2025-08-30 23:22:22 +03:00
Daniel Öster
1c9497e316
Merge pull request #1466 from dalathegreat/improvement/tesla-settings-webserver
Tesla: Configurable GTW settings via Webserver
2025-08-30 22:31:12 +03:00
Daniel Öster
195755b21e Merge branch 'main' into bugfix/ioniq28-PID 2025-08-30 22:24:10 +03:00
Daniel Öster
01f03d6726
Merge pull request #1439 from dalathegreat/bugfix/precharge-osc
Precharge: Avoid oscillation in precharge pin incase it fails/timeouts
2025-08-30 22:10:45 +03:00
Daniel Öster
94eeccb017 Make USB_CAN_DEBUG toogglable via webserver 2025-08-30 13:05:05 +03:00
Daniel Öster
127094f9fa Fix unit test failing 2025-08-30 00:30:49 +03:00
Daniel Öster
5df7071999 Fix unit test failing 2025-08-30 00:29:22 +03:00
Daniel Öster
aa9a4d429e Make USB/WEB debug configurable 2025-08-30 00:22:43 +03:00
Daniel Öster
a1e5bc57d0 Remove ifdef from logging 2025-08-29 23:16:36 +03:00
Daniel Öster
e299b867c9 Remove all ifdefs from Tesla code 2025-08-29 21:38:40 +03:00
Daniel Öster
1ec6af8ea0 Add configurable Tesla options 2025-08-29 20:56:29 +03:00
mbuhansen
91273c7763
Fix contactor test timer start variable assignment 2025-08-29 19:15:17 +02:00
Daniel Öster
681b915faa
Merge pull request #1456 from greenoem/tesla-feature-socreset
Tesla: Add SOC reset feature
2025-08-29 19:44:19 +03:00
Daniel Öster
2b42641c6b Correct SOH reading. Add all PIDs as comments. Add isolation resistance 2025-08-28 23:10:12 +03:00
Daniel Öster
daabd2ba80
Update Software.ino 2025-08-27 21:28:02 +03:00
pre-commit-ci[bot]
9774e591da [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-08-27 18:16:40 +00:00
Matt Holmes
5fae1c645d Fixing unit tests and formatting 2025-08-27 19:14:54 +01:00
Matt Holmes
588c78f1d6 Adding the ability to set default configuration for specific hardware such as a 16mhz crystal for the Lilygo T-2CAN 2025-08-27 18:45:25 +01:00
Matt Holmes
4e0ea84fad Adding ability to configure Can Addon frequency in common image 2025-08-27 18:45:25 +01:00
Matt Holmes
eb047badfd Updating native can library to ACAN_ESP32 2025-08-27 18:45:25 +01:00
Matt Holmes
62486d9bf1 Initial version of Lilygo T_2CAN board 2025-08-27 18:45:25 +01:00
Daniel Öster
dd21ff6ce1
Merge branch 'main' into feature/RELION-LV-protocol 2025-08-26 21:48:34 +03:00
Daniel Öster
50f4227266
Merge pull request #1452 from dalathegreat/feature/kia64-FD
New battery 🔋 Add support for Kia 64kWh FD battery
2025-08-26 21:47:03 +03:00
Daniel Öster
cd30370c85
Update Software.ino 2025-08-26 21:45:20 +03:00
Daniel Öster
94cd2a3309
Merge pull request #1444 from freddanastrom/imporvement/BYD-Atto3-send-voltage
BYD Atto 3: Send correct data in 441 message
2025-08-26 16:09:49 +03:00
Daniel Öster
4a24f3558f
Merge pull request #1425 from jonny5532/feature/more-inverter-settings
Make more inverter settings configurable
2025-08-25 20:01:37 +03:00
Daniel Öster
baca9f5ca1
Merge pull request #1461 from dalathegreat/bugfix/volvo-contactor-state
Volvo SPA: Swap precharge/positive state
2025-08-25 20:01:03 +03:00
Daniel Öster
7adee39d82 Swap precharge/positive state 2025-08-25 19:28:33 +03:00
Daniel Öster
18b24c9ee8 Add cellvoltages to CAN reading 2025-08-25 15:56:55 +03:00
Daniel Öster
e89d69ba3b Merge branch 'main' into feature/RELION-LV-protocol 2025-08-25 15:17:44 +03:00
Daniel Öster
837407514d
Merge pull request #1446 from dalathegreat/bugfix/volvo-HVIL
Volvo SPA: Add HVIL status to More Battery Info page
2025-08-24 22:51:20 +03:00
Daniel Öster
e944951c55 Remove HVIL Event since it was misleading on some packs 2025-08-24 22:41:50 +03:00
James Brookes
dfa289ce56 Fix copy paste omissions 2025-08-24 15:19:25 +01:00
James Brookes
0961aef9aa Pre-commit changes 2025-08-24 15:09:40 +01:00
James Brookes
38e900bed7 Add pack SOC reset feature, fix BMS reset bug 2025-08-24 15:05:44 +01:00
Daniel Öster
26eed03397 Merge branch 'main' into bugfix/precharge-osc 2025-08-24 15:09:10 +03:00
Daniel Öster
3087ff9caf
Merge pull request #1433 from dalathegreat/bugfix/double-UI-bugs
Improvement: UI visualization of contactor states
2025-08-23 21:17:01 +03:00
pre-commit-ci[bot]
68da99c4a0 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-08-23 12:36:02 +00:00
freddanastrom
100e7e6eff Using a more robust way to check that voltage is available from BMS 2025-08-23 14:33:51 +02:00
Daniel Öster
817d0ed7e6
Merge pull request #1454 from dalathegreat/feature/stark-hw-autodetect
Hardware: Add autodetection method for Stark V1 / V2
2025-08-22 22:57:14 +03:00
pre-commit-ci[bot]
36a6dd049b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-08-22 18:44:38 +00:00
Jaakko Haakana
8494463926 Add mock ESP class 2025-08-22 21:44:24 +03:00
Daniel Öster
0737c7b6b9
Merge pull request #1450 from dalathegreat/feature/mqtt_status_info
Adding event_level and event_level_color to mqtt information
2025-08-21 23:37:39 +03:00
Daniel Öster
39ef78fd4a Add autodetection method for Stark V1 / V2 2025-08-21 23:24:55 +03:00
pre-commit-ci[bot]
2cf46fede4 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-08-21 17:17:22 +00:00
Matt Holmes
cd7e0b51ea Fixing typo and removing unused led_color enum 2025-08-21 18:13:17 +01:00
Daniel Öster
2888402e5d Move variables to private section 2025-08-21 20:06:48 +03:00
Daniel Öster
3853236023 Add support for 64kWh FD Kia battery 2025-08-21 19:59:16 +03:00
Matt Holmes
d4f0e188fe Refactoring led handler, webserver and mqtt to all use a common BE status enum rather than relying on duplicatitng logic or using led color 2025-08-21 17:33:15 +01:00
Matt Holmes
980d914ffd Adding event_level and event_level_color to mqtt information 2025-08-21 08:58:55 +01:00
mbuhansen
b8059de996
Update KOSTAL-RS485.cpp 2025-08-20 21:15:06 +02:00
mbuhansen
56bfcbe664
Update KOSTAL-RS485.cpp 2025-08-20 21:01:54 +02:00
Daniel Öster
2bad768263 Add Event for HVIL not seated 2025-08-20 21:22:58 +03:00
Daniel Öster
d86a9bd364 Add HVIL status to webserver 2025-08-20 21:13:45 +03:00
mbuhansen
93556780eb
Update KOSTAL-RS485.cpp 2025-08-20 16:49:21 +02:00
mbuhansen
6645f3c1f3
Update KOSTAL-RS485.h 2025-08-20 16:38:02 +02:00
mbuhansen
c988245d5f
Update KOSTAL-RS485.cpp
Add contactor test cycle.
2025-08-20 16:37:09 +02:00
pre-commit-ci[bot]
b349482f20 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-08-20 10:27:02 +00:00
Daniel Öster
37f9d3e8f8 Add Relion battery protocol 2025-08-20 12:58:45 +03:00
Fredrik
0f647978fa Send correct data in 441 message 2025-08-20 11:23:22 +02:00
Daniel Öster
d20712f301 Update event for precharge failure 2025-08-19 21:30:18 +03:00
Daniel Öster
d715d169fe Avoid oscillation in precharge incase it fails/timeoutus 2025-08-19 21:27:29 +03:00
Daniel Öster
de393d4ec0 Modify library with BE specific tweaks 2025-08-19 09:56:01 +03:00
Daniel Öster
43fdb4a2f3 Modify library with BE specific tweaks 2025-08-19 09:53:20 +03:00
Daniel Öster
297c56b289 Update acanfd library to v2.1.16 2025-08-19 09:42:24 +03:00
Daniel Öster
b2553fcabe Update acanfd library to v2.1.16 2025-08-19 09:40:08 +03:00
Daniel Öster
10a60abd1a
Merge pull request #1436 from freddanastrom/improvement/BYD-Atto3-default-soc-method
BYD Atto 3: Default SOC method changed to measured
2025-08-19 09:38:30 +03:00
Fredrik
291f858488 Default SOC method changed to measured 2025-08-19 07:47:34 +02:00
Jonny
a718d99672 Make Solax use type settings in COMMON_IMAGE mode, add ignore contactors feature 2025-08-18 20:15:47 +01:00
Jonny
efd6ca1349 Make Pylon/Solxpow/Ferroamp inverters use the new settings in COMMON_IMAGE mode 2025-08-18 20:15:45 +01:00
Jonny
2f1ff9950c Add configurable cell/module/etc settings for Pylonish/Solax inverters to use 2025-08-18 20:05:10 +01:00
Jonny
9816417b21 Pin Inverter enum values so they won't change in future 2025-08-18 20:05:10 +01:00
Jonny
7a2e2519cd Move SOFAR_ID to new setting mechanism. 2025-08-18 20:05:10 +01:00
Daniel Öster
ce1f8955e9
Merge pull request #1424 from jonny5532/feature/move-custom-bms-voltage-limits-to-settings
Make custom-BMS voltage limits configurable via settings
2025-08-18 21:54:04 +03:00
Jonny
2a8f8713e7 Make new voltage limit settings actually save 2025-08-18 18:57:28 +01:00
Jonny
0e871e477a Make reboots reliably reload the page 2025-08-18 18:57:28 +01:00
Jonny
0d8bc06f04 Make custom-BMS voltage limits configurable via settings 2025-08-18 18:57:24 +01:00
Daniel Öster
689a0fb55c
Merge pull request #1430 from dalathegreat/feature/raw-temp-readings-LEAF
Nissan LEAF: Add all temperature measurements to More Battery Info page
2025-08-18 20:52:20 +03:00
Daniel Öster
bd72fe9153
Merge pull request #1426 from dalathegreat/bugfix/LED-S3
Improvement: Make LED handler compatible with ESP-IDF v5.x
2025-08-18 20:50:46 +03:00
Daniel Öster
1ff3ff43e4 Make the ESP-IDF v5.x RMT driver execute faster 2025-08-18 20:24:48 +03:00
Daniel Öster
552c304be4
Merge pull request #1434 from dalathegreat/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-18 19:08:33 +03:00
dependabot[bot]
df70353608
Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 01:42:22 +00:00
Daniel Öster
f8138b04eb Merge branch 'main' into bugfix/LED-S3 2025-08-17 22:48:09 +03:00
Daniel Öster
24abab7746 Fix startup voltage not triggering events 2025-08-17 21:59:21 +03:00
Daniel Öster
2f6b63afe8 Add check if bat2 is configured 2025-08-17 21:53:44 +03:00
Daniel Öster
5888457926 Improve secondary battery visualization 2025-08-17 21:43:43 +03:00
Daniel Öster
1645c5b337 Further improve dialogue boxes and texts 2025-08-17 21:24:55 +03:00
Daniel Öster
8444855f24 Add new box for contactor status 2025-08-17 20:02:11 +03:00
Daniel Öster
be96cd98ef Hide temp3 if not on ZE0 2025-08-17 14:29:58 +03:00
Daniel Öster
ba39ba49d9 Merge branch 'main' into feature/raw-temp-readings-LEAF 2025-08-17 14:25:13 +03:00
Daniel Öster
98ba854ae8 Add decimals to temp value 2025-08-17 14:23:12 +03:00
Daniel Öster
0fb9078a1e
Merge pull request #1431 from dalathegreat/bugfix/cpu-tempwarning
Improvement: Update CPU temperature setpoint
2025-08-17 12:58:45 +03:00
Daniel Öster
9462d1e30b
Merge pull request #1428 from dalathegreat/improvement/LED-CPU
Improvement: Optimize LED library for maximum performance
2025-08-17 12:58:34 +03:00
Daniel Öster
2536639272 Increase setpoint in tests 2025-08-17 12:43:23 +03:00
Daniel Öster
38c88e78f3
Update CPU temperature setpoint
Raised the CPU temperature warning from 80 to 87deg to cut down on false positive warnings
2025-08-17 12:40:15 +03:00
Daniel Öster
ec9ac18fa4 Add 4 temperature measurements to More Battery Info page 2025-08-17 11:02:24 +03:00
Daniel Öster
ce7547f71e Optimize Neopixel library for maximum performance 2025-08-17 00:19:45 +03:00
Daniel Öster
77706ce200 Optimize Adafruit library for maximum CPU usage reduction 2025-08-17 00:14:19 +03:00
Daniel Öster
6136c92996 Further improve implementation 2025-08-16 18:16:13 +03:00
Daniel Öster
f4418672ba Working implementationN 2025-08-16 18:11:52 +03:00
Daniel Öster
9a898b72e2 Update text for cellvoltages not read 2025-08-16 16:02:30 +03:00
Daniel Öster
dd1ee5139d Make RJXZS protocol handle reboots gracefully with default values 2025-08-16 13:32:18 +03:00
Daniel Öster
555cc16a11 Make cellmonitor page show amount of detected cells 2025-08-16 13:31:54 +03:00
Daniel Öster
fd0b72af1e
Merge pull request #1423 from jonny5532/feature/remove-cell-count-from-custom-bms
Remove hardcoded cell counts from custom-BMS batteries
2025-08-16 12:23:07 +03:00
Daniel Öster
a7897f30e6
Update Software.ino 2025-08-16 12:17:28 +03:00
Jonny
ca33040cfe Remove hardcoded cell counts from custom-BMS batteries 2025-08-15 22:17:09 +01:00
Daniel Öster
6e6e9dea49
Update Software.ino 2025-08-15 20:34:01 +03:00
Daniel Öster
7f54b2a6f2
Merge pull request #1417 from dalathegreat/cleanup/uptime
Cleanup 🧹 Remove obsolete uptime library
2025-08-15 20:33:18 +03:00
Daniel Öster
6c0675c85a
Merge pull request #1408 from dalathegreat/bugfix/sofar-fix
Sofar CAN: Reduced CAN sending rate, improved inverter response
2025-08-15 20:32:26 +03:00
Daniel Öster
ca1bb08973
Merge pull request #1400 from dalathegreat/bugfix/pairing-success-rate-SMA
SMA (All protocols): Improve pairing success rate
2025-08-15 20:31:09 +03:00
Daniel Öster
73834cceeb
Merge pull request #1378 from jonny5532/feature/modbus-inverter-ram-usage
Make Modbus inverters allocate their own register memory as required
2025-08-15 20:30:36 +03:00
Daniel Öster
dc01dea9b2 Remove static, adjust datatypes 2025-08-15 18:52:58 +03:00
Daniel Öster
d4ccb4e797
Merge pull request #1418 from jonny5532/feature/revert-asynctcp-taskyield
Revert AsyncTCP extra taskYIELD (unnecessary)
2025-08-15 16:29:12 +03:00
Jonny
71b552a6f6 Revert AsyncTCP extra taskYIELD (unnecessary) 2025-08-15 08:57:52 +01:00
Daniel Öster
7fa65629e7
Merge pull request #1416 from dalathegreat/bugfix/nvrol-stop-streaming
Bugfix Zoe2: Improve NVROL streaming
2025-08-14 23:27:30 +03:00
Daniel Öster
102c359f5d Improve calculation 2025-08-14 21:46:32 +03:00
Daniel Öster
5d6c845573 Simplify uptime calculation with millis64 2025-08-14 21:37:33 +03:00
Daniel Öster
cf3db85d20
Merge pull request #1396 from jonny5532/fix/sort-out-watchdogs
Readd watchdog (re)initialization code, disable idle timeouts, make AsyncTCP yield (discuss!)
2025-08-14 19:37:44 +03:00
Daniel Öster
7c4586e602 Add 100ms message 0x375 status 2025-08-14 16:44:40 +03:00
Daniel Öster
02739cc32f Add vehicle ID and Boost time CAN sending 2025-08-14 16:28:18 +03:00
Daniel Öster
64ad1fc472 Switch to periodically sent cellvoltages for faster read 2025-08-14 16:14:56 +03:00
Daniel Öster
a8914f559b
Merge pull request #1413 from dalathegreat/library/esp32asyncwebserver380
Library 📜 Update ESP32AsyncWebServer to v3.8.0
2025-08-14 16:04:10 +03:00
Daniel Öster
7dcc30211d
Merge pull request #1415 from jonny5532/feature/web-installer2
Keep factory images out of release assets
2025-08-14 16:04:00 +03:00
Daniel Öster
0dad293d96 Add corrupted CAN message detection 2025-08-14 15:56:51 +03:00
Daniel Öster
2dff7c62af Add CRC handling. Add 0x0EE pedal position message 2025-08-14 14:59:42 +03:00
Daniel Öster
3fff2bde2b Add Interlock event incase connectors not seated 2025-08-14 12:58:36 +03:00
Daniel Öster
a0c1a2f8fd Implement still alive CAN, speedup voltage reading 2025-08-14 12:52:14 +03:00
Daniel Öster
a890447efa Improve NVROL streaming 2025-08-14 11:50:28 +03:00
Jonny
a0320a7341 Keep factory images out of release assets 2025-08-14 09:37:55 +01:00
Daniel Öster
aaada1b660
Merge pull request #1412 from jonny5532/feature/web-installer-integration
Add factory builds and push to web installer
2025-08-13 22:58:59 +03:00
Daniel Öster
146420b91b Update ESP32AsyncWebServer to v3.8.0 2025-08-13 18:04:18 +03:00
Daniel Öster
9ce4f98e19
Merge pull request #1403 from dalathegreat/bugfix/ZE1-LEAF-CAN
Nissan LEAF: Add more ZE1 CAN message sending
2025-08-13 17:44:27 +03:00
Daniel Öster
c92a99590c
Merge pull request #1402 from jonny5532/feature/constexpr-can-messages-main
Add static constexpr to lots of static battery CAN_frames to save RAM
2025-08-13 17:40:30 +03:00
Jonny
8c813984b0 Add factory builds and push to web installer 2025-08-13 14:55:21 +01:00
Daniel Öster
e328751ecf
Merge pull request #1411 from dalathegreat/bugfix/phev-mg-voltages
Improvement: Add voltage limits to RangeRoverPHEV
2025-08-13 16:19:59 +03:00
Daniel Öster
161e33ad97 Add voltage limits to RangeRoverPHEV 2025-08-13 15:57:39 +03:00
Daniel Öster
17226d94ed
Merge pull request #1405 from dalathegreat/bugfix/ix-startup-error
BMW iX, i4‐i7: Reduce startup events
2025-08-13 13:57:09 +03:00
Daniel Öster
1f24a38507
Merge pull request #1409 from dalathegreat/dependabot/github_actions/softprops/action-gh-release-2
Bump softprops/action-gh-release from 1 to 2
2025-08-12 10:53:14 +03:00
dependabot[bot]
463f36b6e0
Bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 01:48:08 +00:00
Daniel Öster
1625e34ed4 Reduced CAN sending rate, improved inverter response 2025-08-10 22:44:42 +03:00
Daniel Öster
de16386e94
Merge pull request #1407 from dalathegreat/feature/extend-capacity
Increase max capacity from 120kWh to 400kWh
2025-08-10 22:14:27 +03:00
Daniel Öster
d03050963a Increase max capacity from 120kWh to 400kWh 2025-08-10 21:42:01 +03:00
Daniel Öster
91a32e60c2
Merge pull request #1399 from dalathegreat/feature/sol-ark-lv
New inverter protocol  Sol-Ark LV CAN
2025-08-09 14:26:38 +03:00
Daniel Öster
fb1b7194ba
Merge pull request #1392 from dalathegreat/bugfix/volvo-robustness
Volvo SPA: Improve robustness of reading of values
2025-08-09 14:26:21 +03:00
Daniel Öster
d7e73794f9 Add event for low 12V voltage 2025-08-08 15:07:01 +03:00
Daniel Öster
7becd8c2b2 Fix datatypes and startup voltage value 2025-08-08 15:04:41 +03:00
Daniel Öster
c617499da8 Add HX to More Battery Info Page 2025-08-08 11:29:48 +03:00
Daniel Öster
5d23a91a62
Merge pull request #1404 from kyberias/update-secret-name
Back to using GITHUB_TOKEN but with more permissions
2025-08-08 10:09:35 +03:00
Jaakko Haakana
3d65864765 Back to using GITHUB_TOKEN but with more permissions 2025-08-08 00:36:01 +03:00
Daniel Öster
9035e0418d
Merge pull request #1401 from kyberias/update-secret-name
Update secret name
2025-08-07 23:51:30 +03:00
Daniel Öster
d31fa7f118 Add more ZE1 CAN message sending 2025-08-07 23:42:10 +03:00
Jonny
ab3d63ab18 Add static constexpr to lots of static battery CAN_frames to save RAM 2025-08-07 21:31:11 +01:00
Jaakko Haakana
bea141a070 Update secret name 2025-08-07 23:14:49 +03:00
Daniel Öster
d0c8886250 Improve pairing success rate 2025-08-07 20:49:27 +03:00
Daniel Öster
16ec6791e3 Finalize SolArkLV protocol 2025-08-07 19:24:56 +03:00
Daniel Öster
83d2b2a754 Add configuration option for Sol-Ark 2025-08-07 19:21:53 +03:00
Daniel Öster
a27f9c45af
Merge pull request #1397 from kyberias/fix-dbl-cont
Fix double battery contactor control settings
2025-08-07 19:13:22 +03:00
Jaakko Haakana
4469d07b82 Fix double battery contactor control settings 2025-08-07 15:57:51 +03:00
Jonny
de770144a2 Make AsyncTCP yield each loop, to let other same-prio tasks get a turn 2025-08-07 09:48:06 +01:00
Jonny
5f23463914 Readd watchdog (re)initialization, disable idle watchdogs 2025-08-07 09:48:03 +01:00
Jonny
55d8a24f55 Add more Arduino/FreeRTOS test stubs so that eModbus builds in ESP32 mode. 2025-08-06 20:28:55 +01:00
Jonny
9eac0a90b5 Refactor Modbus inverter handling into superclass, use std::map instead of static array 2025-08-06 20:25:57 +01:00
Jonny
b2e9515d44 Fix invalid C++ in eModbus (0xff in a char[]) 2025-08-06 20:23:31 +01:00
Daniel Öster
859ec81a65
Merge pull request #1394 from kyberias/release-wf
Add release assets workflow
2025-08-06 20:49:36 +03:00
Daniel Öster
675f1e9771
Update Software.ino 2025-08-06 15:47:26 +03:00
Daniel Öster
5c306230fc Improve robustness of reading of values 2025-08-06 15:35:32 +03:00
Daniel Öster
2d9660e6c5 Add Sol-ark files 2025-08-06 15:31:45 +03:00
Jaakko Haakana
273a6f4d34 Add release assets workflow 2025-08-05 22:11:39 +03:00
272 changed files with 10613 additions and 8089 deletions

View file

@ -16,13 +16,11 @@
### Settings ### Settings
<!-- Please fill in the settings used below from USER_SETTINGS.h, as it will help with diagnosis. --> <!-- Please attach screenshot of configuration for easier debugging, as it will help with diagnosis. -->
- Software version: `` - Software version: X.Y.Z
- Battery used: `` - Battery used: [Nissan LEAF]
- Inverter communication protocol: `` - Inverter communication protocol: [BYD Modbus]
- Hardware used for Battery-Emulator: `HW_LILYGO, HW_STARK, Custom` - Hardware used for Battery-Emulator: [LilyGo T-CAN/Stark CMR/LilyGo T-2CAN]
- CONTACTOR_CONTROL: `yes/no` - GPIO controlled contactors: [yes/no]
- CAN_ADDON: `yes/no` - MQTT: [yes/no]
- WEBSERVER: `yes/no`
- MQTT: `yes/no`

View file

@ -20,10 +20,10 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Version and settings (please complete the following information):** **Version and settings (please complete the following information):**
- Please attach your USER_SETTINGS files for easier debugging - Please attach screenshot of configuration for easier debugging
- Software version: X.Y.Z - Software version: X.Y.Z
- Battery used: [NISSAN_LEAF] - Battery used: [Nissan LEAF]
- Inverter communication protocol: [BYD_MODBUS] - Inverter communication protocol: [BYD Modbus]
- Hardware used for Battery-Emulator: [HW_LILYGO/HW_STARK/HW_DEVKIT] - Hardware used for Battery-Emulator: [LilyGo T-CAN/Stark CMR/LilyGo T-2CAN]
- CONTACTOR_CONTROL: [yes/no] - GPIO controlled contactors: [yes/no]
- MQTT: [yes/no] - MQTT: [yes/no]

View file

@ -1,93 +0,0 @@
# This is the name of the workflow, visible on GitHub UI.
name: 🤖 Compile All Hardware
# Here we tell GitHub when to run the workflow.
on:
# The workflow is run when a commit is pushed or for a
# Pull Request.
- push
- pull_request
# This is the list of jobs that will be run concurrently.
jobs:
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
# This is the name of the job.
build-hardware:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
strategy:
# The matrix will produce one job for each combination of parameters.
matrix:
# This is the development board hardware for which the code will be compiled.
# FBQN stands for "fully qualified board name", and is used by Arduino to define the hardware to compile for.
fqbn:
- esp32:esp32:esp32
# further ESP32 chips
#- esp32:esp32:esp32c3
#- esp32:esp32:esp32c2
#- esp32:esp32:esp32c6
#- esp32:esp32:esp32h2
#- esp32:esp32:esp32s3
# These are the batteries for which the code will be compiled.
battery:
- NISSAN_LEAF_BATTERY
# These are the emulated inverter communication protocols for which the code will be compiled.
inverter:
- BYD_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
- HW_STARK
- HW_3LB
- HW_DEVKIT
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest
# This is the list of steps this job will run.
steps:
# First we clone the repo using the `checkout` action.
- name: Checkout
uses: actions/checkout@v4
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# We use the `arduino/setup-arduino-cli` action to install and
# configure the Arduino CLI on the system.
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
# We then install the platform.
- name: Install platform
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32
# Finally, we compile the sketch, using the FQBN that was set
# in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix.
- name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -0,0 +1,54 @@
name: 🔋 Compile Common Image for Lilygo 2CAN
on:
# The workflow is run when a commit is pushed or for a
# Pull Request.
- push
- pull_request
# This is the list of jobs that will be run concurrently.
jobs:
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
build-common-image:
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
name: Checkout code
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: Build image for Lilygo
run: pio run -e lilygo_2CAN_330
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: battery-emulator-lilygo.bin
path: .pio/build/lilygo_2CAN_330/firmware.bin

View file

@ -1,4 +1,4 @@
name: 🔋 Compile Common Image for Lilygo name: 🔋 Compile Common Image for Lilygo T-CAN
on: on:
# The workflow is run when a commit is pushed or for a # The workflow is run when a commit is pushed or for a
@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
name: Checkout code name: Checkout code
- uses: actions/cache@v4 - uses: actions/cache@v4
@ -38,16 +38,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 image for Lilygo - name: Build image for Lilygo
run: pio run -e lilygo_330 run: pio run -e lilygo_330

View file

@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
name: Checkout code name: Checkout code
- uses: actions/cache@v4 - uses: actions/cache@v4
@ -38,16 +38,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 image for Stark CMR - name: Build image for Stark CMR
run: pio run -e stark_330 run: pio run -e stark_330

102
.github/workflows/release-assets.yml vendored Normal file
View file

@ -0,0 +1,102 @@
name: Release Assets
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v1.0.0)'
required: true
jobs:
build-and-upload:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Set release tag
id: vars
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout code at tag
uses: actions/checkout@v5
with:
ref: refs/tags/${{ steps.vars.outputs.tag }}
- name: Build artifacts
run: |
mkdir output
echo "Built for tag ${{ steps.vars.outputs.tag }}" > output/info.txt
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: 🛠 Build ota image for Lilygo T-CAN
run: |
pio run -e lilygo_330
cp .pio/build/lilygo_330/firmware.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-CAN485.ota.bin
- name: 🛠 Build factory image for Lilygo T-CAN
run: |
esptool --chip esp32 merge-bin -o .pio/build/lilygo_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 4MB 0x1000 .pio/build/lilygo_330/bootloader.bin 0x8000 .pio/build/lilygo_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_330/firmware.bin
mv .pio/build/lilygo_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-CAN485.factory.bin
- name: 🛠 Build ota image for Lilygo 2-CAN
run: |
pio run -e lilygo_2CAN_330
cp .pio/build/lilygo_2CAN_330/firmware.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-2CAN.ota.bin
- name: 🛠 Build factory image for Lilygo 2-CAN
run: |
esptool --chip esp32s3 merge-bin -o .pio/build/lilygo_2CAN_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 16MB 0x0000 .pio/build/lilygo_2CAN_330/bootloader.bin 0x8000 .pio/build/lilygo_2CAN_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lilygo_2CAN_330/firmware.bin
mv .pio/build/lilygo_2CAN_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_LilygoT-2CAN.factory.bin
- name: 🛠 Build ota image for Stark
run: |
pio run -e stark_330
cp .pio/build/stark_330/firmware.bin output/BE_${{ steps.vars.outputs.tag }}_StarkCMR.ota.bin
- name: 🛠 Build factory image for Stark
run: |
esptool --chip esp32 merge-bin -o .pio/build/stark_330/factory.bin --flash-mode dio --flash-freq 40m --flash-size 4MB 0x1000 .pio/build/stark_330/bootloader.bin 0x8000 .pio/build/stark_330/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/stark_330/firmware.bin
mv .pio/build/stark_330/factory.bin output/BE_${{ steps.vars.outputs.tag }}_StarkCMR.factory.bin
- name: 🌐 Deploy to Web Installer repo
env:
WEB_INSTALLER_PUSH_TOKEN: ${{ secrets.WEB_INSTALLER_PUSH_TOKEN }}
REPO_OWNER: ${{ github.repository_owner }}
run: |
git clone https://$WEB_INSTALLER_PUSH_TOKEN@github.com/$REPO_OWNER/BE-Web-Installer web-installer
cd web-installer
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
mkdir -p images/${{ steps.vars.outputs.tag }}
cp ../output/*.factory.bin images/${{ steps.vars.outputs.tag }}/
git add images
git commit -m "Deploy from GitHub Actions"
git push origin main
- name: 📡 Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.vars.outputs.tag }}
files: |
output/*.ota.bin
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Run pre-commit - name: Run pre-commit
uses: pre-commit/action@v3.0.1 uses: pre-commit/action@v3.0.1

View file

@ -9,11 +9,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Configure and build with CMake - name: Configure and build with CMake
run: | run: |
cp Software/USER_SECRETS.TEMPLATE.h Software/USER_SECRETS.h
cd test cd test
mkdir build mkdir build
cd build cd build

9
.gitignore vendored
View file

@ -4,6 +4,15 @@
# Ignore any files in any build folder # Ignore any files in any build folder
*build/ *build/
# Ignore any generated test files from running locally
test/CMakeCache.txt
test/CMakeFiles/
test/CTestTestfile.cmake
test/Makefile
test/cmake_install.cmake
test/gtest/
test/tests[1]_include.cmake
# Ignore .exe (unit tests) # Ignore .exe (unit tests)
*.exe *.exe
**/.DS_Store **/.DS_Store

View file

@ -10,7 +10,7 @@ ci:
repos: repos:
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v20.1.8 rev: v21.1.0
hooks: hooks:
- id: clang-format - id: clang-format
args: [-Werror] # change formatting warnings to errors, hook includes -i (Inplace edit) by default args: [-Werror] # change formatting warnings to errors, hook includes -i (Inplace edit) by default

View file

@ -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 FUNCTION_TIME_MEASUREMENT option in the USER_SETTINGS.h file 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!
@ -38,3 +94,32 @@ Or force it to check all files with
``` ```
pre-commit run --all-files pre-commit run --all-files
``` ```
## Local Unit test run 🧪
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 cmake
```
Navigate to Battery-Emulator/test folder
```
sudo cmake CMakeLists.txt
sudo make
```
## Downloading a pull request build to test locally 🛜
If you want to test a pull request, you can download the precompiled binaries from the build system. To do this,start by clicking on the "**Checks**" tab
<img width="779" height="312" alt="image" src="https://github.com/user-attachments/assets/fc7783c1-ba61-440e-ab09-b53d2b49f1bb" />
Then select which hardware you need the binaries for. Currently we build for these hardwares:
- LilyGo T-CAN485
- Stark CMR
- LilyGo T-2CAN
<img width="647" height="476" alt="image" src="https://github.com/user-attachments/assets/fbb97719-0155-4792-9d91-c51e6052fa57" />
After selecting the hardware you need, click the "**Upload Artifact**", and there will be a download link. Download the file, and [OTA Update](https://github.com/dalathegreat/Battery-Emulator/wiki/OTA-Update) your device with this file!
<img width="1714" height="697" alt="image" src="https://github.com/user-attachments/assets/2e17f90f-cc7d-4265-b7bc-7aa5cc6b6ec8" />

View file

@ -2,7 +2,7 @@
![GitHub release (with filter)](https://img.shields.io/github/v/release/dalathegreat/BYD-Battery-Emulator-For-Gen24?color=%23008000) ![GitHub release (with filter)](https://img.shields.io/github/v/release/dalathegreat/BYD-Battery-Emulator-For-Gen24?color=%23008000)
![GitHub Repo stars](https://img.shields.io/github/stars/dalathegreat/Battery-Emulator?style=flat&color=%23128512) ![GitHub Repo stars](https://img.shields.io/github/stars/dalathegreat/Battery-Emulator?style=flat&color=%23128512)
![GitHub forks](https://img.shields.io/github/forks/dalathegreat/Battery-Emulator?style=flat&color=%23128512) ![GitHub forks](https://img.shields.io/github/forks/dalathegreat/Battery-Emulator?style=flat&color=%23128512)
![GitHub actions](https://img.shields.io/github/actions/workflow/status/dalathegreat/BYD-Battery-Emulator-For-Gen24/compile-common-image-lilygo.yml?color=0E810E) ![GitHub actions](https://img.shields.io/github/actions/workflow/status/dalathegreat/BYD-Battery-Emulator-For-Gen24/compile-common-image-lilygo-TCAN.yml?color=0E810E)
![Static Badge](https://img.shields.io/badge/made-with_love-blue?color=%23008000) ![Static Badge](https://img.shields.io/badge/made-with_love-blue?color=%23008000)
## What is Battery Emulator? ## What is Battery Emulator?
@ -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)
[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/hcl2GdHc0Y0/0.jpg)](https://www.youtube.com/watch?v=hcl2GdHc0Y0) [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/sR3t7j0R9Z0/0.jpg)](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! 🔋⚡
![bild](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/6a2414b1-f2ca-4746-8e8d-9afd78bd9252)
5. The Arduino board should be set to `ESP32 Dev Module` and `Partition Scheme` to `Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)` (under `Tools` -> `Board` -> `ESP32 Arduino`) with the following settings:
![ArduinoSettings](https://github.com/user-attachments/assets/74d36b07-cca4-4bf1-9eaf-1e7fa4e1effe)
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,10 +54,9 @@ 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
- [YiannisBourkelis/Uptime-Library](https://github.com/YiannisBourkelis/Uptime-Library) GPL-3.0 license
It is also based on the information found in the following excellent repositories/websites: It is also based on the information found in the following excellent repositories/websites:
- https://gitlab.com/pelle8/inverter_resources //new url - https://gitlab.com/pelle8/inverter_resources //new url

View file

@ -1,8 +1,5 @@
/* Do not change any code below this line unless you are sure what you are doing */ #include <Arduino.h>
/* Only change battery specific settings in "USER_SETTINGS.h" */
#include "HardwareSerial.h" #include "HardwareSerial.h"
#include "USER_SECRETS.h"
#include "USER_SETTINGS.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "esp_timer.h" #include "esp_timer.h"
@ -26,31 +23,28 @@
#include "src/devboard/utils/logging.h" #include "src/devboard/utils/logging.h"
#include "src/devboard/utils/time_meas.h" #include "src/devboard/utils/time_meas.h"
#include "src/devboard/utils/timer.h" #include "src/devboard/utils/timer.h"
#include "src/devboard/utils/types.h"
#include "src/devboard/utils/value_mapping.h" #include "src/devboard/utils/value_mapping.h"
#include "src/devboard/webserver/webserver.h" #include "src/devboard/webserver/webserver.h"
#include "src/devboard/wifi/wifi.h" #include "src/devboard/wifi/wifi.h"
#include "src/inverter/INVERTERS.h" #include "src/inverter/INVERTERS.h"
#if !defined(HW_LILYGO) && !defined(HW_STARK) && !defined(HW_3LB) && !defined(HW_DEVKIT) #if !defined(HW_LILYGO) && !defined(HW_LILYGO2CAN) && !defined(HW_STARK) && !defined(HW_3LB) && !defined(HW_DEVKIT)
#error You must select a target hardware in the USER_SETTINGS.h file! #error You must select a target hardware!
#endif #endif
#ifdef PERIODIC_BMS_RESET_AT
#include "src/devboard/utils/ntp_time.h"
#endif
volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver // The current software version, shown on webserver
const char* version_number = "9.0.RC1"; const char* version_number = "9.2.dev";
// Interval timers // Interval timers
volatile unsigned long currentMillis = 0; volatile unsigned long currentMillis = 0;
unsigned long previousMillis10ms = 0; unsigned long previousMillis10ms = 0;
unsigned long previousMillisUpdateVal = 0; unsigned long previousMillisUpdateVal = 0;
#ifdef FUNCTION_TIME_MEASUREMENT
// Task time measurement for debugging // Task time measurement for debugging
MyTimer core_task_timer_10s(INTERVAL_10_S); MyTimer core_task_timer_10s(INTERVAL_10_S);
#endif uint64_t start_time_10ms = 0;
uint64_t start_time_values = 0;
uint64_t start_time_cantx = 0;
TaskHandle_t main_loop_task; TaskHandle_t main_loop_task;
TaskHandle_t connectivity_loop_task; TaskHandle_t connectivity_loop_task;
TaskHandle_t logging_loop_task; TaskHandle_t logging_loop_task;
@ -58,129 +52,41 @@ TaskHandle_t mqtt_loop_task;
Logging logging; Logging logging;
// Initialization std::string mqtt_user; //TODO, move?
void setup() { std::string mqtt_password; //TODO, move?
init_hal(); std::string http_username; //TODO, move?
std::string http_password; //TODO, move?
init_serial(); static std::list<Transmitter*> transmitters;
void register_transmitter(Transmitter* transmitter) {
// We print this after setting up serial, such that is also printed to serial with DEBUG_VIA_USB set. transmitters.push_back(transmitter);
logging.printf("Battery emulator %s build " __DATE__ " " __TIME__ "\n", version_number); DEBUG_PRINTF("transmitter registered, total: %d\n", transmitters.size());
init_events();
init_stored_settings();
if (wifi_enabled) {
xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
&connectivity_loop_task, esp32hal->WIFICORE());
}
if (!led_init()) {
return;
}
#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD)
xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
&logging_loop_task, esp32hal->WIFICORE());
#endif
if (!init_contactors()) {
return;
}
if (!init_precharge_control()) {
return;
}
setup_charger();
if (!setup_inverter()) {
return;
}
setup_battery();
setup_can_shunt();
// Init CAN only after any CAN receivers have had a chance to register.
if (!init_CAN()) {
return;
}
if (!init_rs485()) {
return;
}
if (!init_equipment_stop_button()) {
return;
}
// BOOT button at runtime is used as an input for various things
pinMode(0, INPUT_PULLUP);
check_reset_reason();
// Initialize Task Watchdog for subscribed tasks
esp_task_wdt_config_t wdt_config = {
.timeout_ms = INTERVAL_5_S, // If task hangs for longer than this, reboot
.idle_core_mask =
(uint32_t)(1 << esp32hal->CORE_FUNCTION_CORE()) | (uint32_t)(1 << esp32hal->WIFICORE()), // Watch both cores
.trigger_panic = true // Enable panic reset on timeout
};
// Start tasks
if (mqtt_enabled) {
if (!init_mqtt()) {
return;
}
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
esp32hal->WIFICORE());
}
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task,
esp32hal->CORE_FUNCTION_CORE());
#ifdef PERIODIC_BMS_RESET_AT
bmsResetTimeOffset = getTimeOffsetfromNowUntil(PERIODIC_BMS_RESET_AT);
if (bmsResetTimeOffset == 0) {
set_event(EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED, 0);
} else {
set_event(EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS, 0);
}
#endif
DEBUG_PRINTF("setup() complete\n");
} }
// Loop empty, all functionality runs in tasks // Initialization functions
void loop() {} void init_serial() {
// Init Serial monitor
#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) Serial.begin(115200);
void logging_loop(void*) { #if HW_LILYGO2CAN
// Wait up to 100ms for Serial to be available. On the ESP32S3 Serial is
init_logging_buffers(); // provided by the USB controller, so will only work if the board is connected
init_sdcard(); // to a computer.
for (int i = 0; i < 10; i++) {
while (true) { if (Serial)
#ifdef LOG_TO_SD break;
write_log_to_sdcard(); delay(10);
#endif
#ifdef LOG_CAN_TO_SD
write_can_frame_to_sdcard();
#endif
} }
} #else
while (!Serial) {}
#endif #endif
}
void connectivity_loop(void*) { void connectivity_loop(void*) {
esp_task_wdt_add(NULL); // Register this task with WDT esp_task_wdt_add(NULL); // Register this task with WDT
// 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();
@ -190,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);
@ -201,149 +105,20 @@ void connectivity_loop(void*) {
} }
} }
void mqtt_loop(void*) { void logging_loop(void*) {
esp_task_wdt_add(NULL); // Register this task with WDT
init_logging_buffers();
init_sdcard();
while (true) { while (true) {
START_TIME_MEASUREMENT(mqtt); if (datalayer.system.info.SD_logging_active) {
mqtt_loop(); write_log_to_sdcard();
END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us);
esp_task_wdt_reset(); // Reset watchdog
delay(1);
}
}
static std::list<Transmitter*> transmitters;
void register_transmitter(Transmitter* transmitter) {
transmitters.push_back(transmitter);
DEBUG_PRINTF("transmitter registered, total: %d\n", transmitters.size());
}
void core_loop(void*) {
esp_task_wdt_add(NULL); // Register this task with WDT
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1); // Convert 1ms to ticks
while (true) {
START_TIME_MEASUREMENT(all);
START_TIME_MEASUREMENT(comm);
monitor_equipment_stop_button();
// Input, Runs as fast as possible
receive_can(); // Receive CAN messages
receive_rs485(); // Process serial2 RS485 interface
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
if (webserver_enabled) {
START_TIME_MEASUREMENT(ota);
ElegantOTA.loop();
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
} }
// Process if (datalayer.system.info.CAN_SD_logging_active) {
currentMillis = millis(); write_can_frame_to_sdcard();
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) &&
(milliseconds(currentMillis) > esp32hal->BOOTUP_TIME())) {
set_event(EVENT_TASK_OVERRUN, (currentMillis - previousMillis10ms));
} }
previousMillis10ms = currentMillis;
#ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(time_10ms);
#endif
led_exe();
handle_contactors(); // Take care of startup precharge/contactor closing
#ifdef PRECHARGE_CONTROL
handle_precharge_control(currentMillis);
#endif // PRECHARGE_CONTROL
#ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
#endif
} }
if (currentMillis - previousMillisUpdateVal >= INTERVAL_1_S) {
previousMillisUpdateVal = currentMillis; // Order matters on the update_loop!
#ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(time_values);
#endif
update_pause_state(); // Check if we are OK to send CAN or need to pause
// Fetch battery values
if (battery) {
battery->update_values();
}
if (battery2) {
battery2->update_values();
check_interconnect_available();
}
update_calculated_values();
update_machineryprotection(); // Check safeties
// Update values heading towards inverter
if (inverter) {
inverter->update_values();
}
#ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us);
#endif
}
#ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(cantx);
#endif
// Let all transmitter objects send their messages
for (auto& transmitter : transmitters) {
transmitter->transmit(currentMillis);
}
#ifdef FUNCTION_TIME_MEASUREMENT
END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us);
#endif
#ifdef FUNCTION_TIME_MEASUREMENT
if (datalayer.system.status.core_task_10s_max_us > datalayer.system.status.core_task_max_us) {
// Update worst case total time
datalayer.system.status.core_task_max_us = datalayer.system.status.core_task_10s_max_us;
// Record snapshots of task times
datalayer.system.status.time_snap_comm_us = datalayer.system.status.time_comm_us;
datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us;
datalayer.system.status.time_snap_values_us = datalayer.system.status.time_values_us;
datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_us;
datalayer.system.status.time_snap_ota_us = datalayer.system.status.time_ota_us;
}
datalayer.system.status.core_task_max_us =
MAX(datalayer.system.status.core_task_10s_max_us, datalayer.system.status.core_task_max_us);
if (core_task_timer_10s.elapsed()) {
datalayer.system.status.time_ota_us = 0;
datalayer.system.status.time_comm_us = 0;
datalayer.system.status.time_10ms_us = 0;
datalayer.system.status.time_values_us = 0;
datalayer.system.status.time_cantx_us = 0;
datalayer.system.status.core_task_10s_max_us = 0;
datalayer.system.status.wifi_task_10s_max_us = 0;
datalayer.system.status.mqtt_task_10s_max_us = 0;
}
#endif // FUNCTION_TIME_MEASUREMENT
esp_task_wdt_reset(); // Reset watchdog to prevent reset
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
// Initialization functions
void init_serial() {
// Init Serial monitor
Serial.begin(115200);
while (!Serial) {}
#ifdef DEBUG_VIA_USB
Serial.println("__ OK __");
#endif // DEBUG_VIA_USB
} }
void check_interconnect_available() { void check_interconnect_available() {
@ -590,3 +365,211 @@ void check_reset_reason() {
break; break;
} }
} }
void core_loop(void*) {
esp_task_wdt_add(NULL); // Register this task with WDT
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1); // Convert 1ms to ticks
while (true) {
START_TIME_MEASUREMENT(all);
START_TIME_MEASUREMENT(comm);
monitor_equipment_stop_button();
// Input, Runs as fast as possible
receive_can(); // Receive CAN messages
receive_rs485(); // Process serial2 RS485 interface
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
START_TIME_MEASUREMENT(ota);
ElegantOTA.loop();
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
// Process
currentMillis = millis();
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) &&
(milliseconds(currentMillis) > esp32hal->BOOTUP_TIME())) {
set_event(EVENT_TASK_OVERRUN, (currentMillis - previousMillis10ms));
}
previousMillis10ms = currentMillis;
if (datalayer.system.info.performance_measurement_active) {
START_TIME_MEASUREMENT(10ms);
}
led_exe();
handle_contactors(); // Take care of startup precharge/contactor closing
if (precharge_control_enabled) {
handle_precharge_control(currentMillis); //Drive the hia4v1 via PWM
}
if (datalayer.system.info.performance_measurement_active) {
END_TIME_MEASUREMENT_MAX(10ms, datalayer.system.status.time_10ms_us);
}
}
if (currentMillis - previousMillisUpdateVal >= INTERVAL_1_S) {
previousMillisUpdateVal = currentMillis; // Order matters on the update_loop!
if (datalayer.system.info.performance_measurement_active) {
START_TIME_MEASUREMENT(values);
}
update_pause_state(); // Check if we are OK to send CAN or need to pause
// Fetch battery values
if (battery) {
battery->update_values();
}
if (battery2) {
battery2->update_values();
check_interconnect_available();
}
update_calculated_values();
update_machineryprotection(); // Check safeties
// Update values heading towards inverter
if (inverter) {
inverter->update_values();
}
if (datalayer.system.info.performance_measurement_active) {
END_TIME_MEASUREMENT_MAX(values, datalayer.system.status.time_values_us);
}
}
if (datalayer.system.info.performance_measurement_active) {
START_TIME_MEASUREMENT(cantx);
}
// Let all transmitter objects send their messages
for (auto& transmitter : transmitters) {
transmitter->transmit(currentMillis);
}
if (datalayer.system.info.performance_measurement_active) {
END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us);
if (datalayer.system.status.core_task_10s_max_us > datalayer.system.status.core_task_max_us) {
// Update worst case total time
datalayer.system.status.core_task_max_us = datalayer.system.status.core_task_10s_max_us;
// Record snapshots of task times
datalayer.system.status.time_snap_comm_us = datalayer.system.status.time_comm_us;
datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us;
datalayer.system.status.time_snap_values_us = datalayer.system.status.time_values_us;
datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_us;
datalayer.system.status.time_snap_ota_us = datalayer.system.status.time_ota_us;
}
datalayer.system.status.core_task_max_us =
MAX(datalayer.system.status.core_task_10s_max_us, datalayer.system.status.core_task_max_us);
if (core_task_timer_10s.elapsed()) {
datalayer.system.status.time_ota_us = 0;
datalayer.system.status.time_comm_us = 0;
datalayer.system.status.time_10ms_us = 0;
datalayer.system.status.time_values_us = 0;
datalayer.system.status.time_cantx_us = 0;
datalayer.system.status.core_task_10s_max_us = 0;
datalayer.system.status.wifi_task_10s_max_us = 0;
datalayer.system.status.mqtt_task_10s_max_us = 0;
}
}
esp_task_wdt_reset(); // Reset watchdog to prevent reset
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
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
void setup() {
init_hal();
init_serial();
// We print this after setting up serial, so that is also printed if configured to do so
DEBUG_PRINTF("Battery emulator %s build " __DATE__ " " __TIME__ "\n", version_number);
init_events();
init_stored_settings();
if (wifi_enabled) {
xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
&connectivity_loop_task, esp32hal->WIFICORE());
}
led_init();
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,
&logging_loop_task, esp32hal->WIFICORE());
}
init_contactors();
init_precharge_control();
setup_charger();
setup_inverter();
setup_battery();
setup_can_shunt();
// Init CAN only after any CAN receivers have had a chance to register.
init_CAN();
init_rs485();
init_equipment_stop_button();
// BOOT button at runtime is used as an input for various things
pinMode(0, INPUT_PULLUP);
check_reset_reason();
// Initialize Task Watchdog for subscribed tasks
esp_task_wdt_config_t wdt_config = {// 5s should be enough for the connectivity tasks (which are all contending
// for the same core) to yield to each other and reset their watchdogs.
.timeout_ms = INTERVAL_5_S,
// We don't benefit from idle task watchdogs, our critical loops have their
// own. The idle watchdogs can cause nuisance reboots under heavy load.
.idle_core_mask = 0,
// Panic (and reboot) on timeout
.trigger_panic = true};
#ifdef CONFIG_ESP_TASK_WDT
// ESP-IDF will have already initialized it, so reconfigure.
// Arduino and PlatformIO have different watchdog defaults, so we reconfigure
// for consistency.
esp_task_wdt_reconfigure(&wdt_config);
#else
// Otherwise initialize it for the first time.
esp_task_wdt_init(&wdt_config);
#endif
// Start tasks
if (mqtt_enabled) {
init_mqtt();
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
esp32hal->WIFICORE());
}
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task,
esp32hal->CORE_FUNCTION_CORE());
DEBUG_PRINTF("Setup complete!\n");
}
// Loop empty, all functionality runs in tasks
void loop() {}

View file

@ -1,22 +0,0 @@
/* This file should be renamed to USER_SECRETS.h to be able to use the software!
It contains all the credentials that should never be made public */
#ifndef COMMON_IMAGE
//Password to the access point generated by the Battery-Emulator
#define AP_PASSWORD "123456789" // Minimum of 8 characters; set to blank if you want the access point to be open
//Name and password of Wifi network you want the emulator to connect to
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID" // Maximum of 63 characters
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // Minimum of 8 characters
//Set WEBSERVER_AUTH_REQUIRED to true to require a password when accessing the webserver homepage. Improves cybersecurity.
#define WEBSERVER_AUTH_REQUIRED false
#define HTTP_USERNAME "admin" // Username for webserver authentication
#define HTTP_PASSWORD "admin" // Password for webserver authentication
//MQTT credentials
#define MQTT_SERVER "192.168.xxx.yyy" // MQTT server address
#define MQTT_PORT 1883 // MQTT server port
#define MQTT_USER "" // MQTT username, leave blank for no authentication
#define MQTT_PASSWORD "" // MQTT password, leave blank for no authentication
#endif

View file

@ -1,81 +0,0 @@
#include "USER_SETTINGS.h"
#include <string>
#include "USER_SECRETS.h"
#include "src/devboard/hal/hal.h"
/* This file contains all the battery settings and limits */
/* They can be defined here, or later on in the WebUI */
/* Most important is to select which CAN interface each component is connected to */
/*
CAN_NATIVE = Native CAN port on the LilyGo & Stark hardware
CANFD_NATIVE = Native CANFD port on the Stark CMR hardware
CAN_ADDON_MCP2515 = Add-on CAN MCP2515 connected to GPIO pins
CANFD_ADDON_MCP2518 = Add-on CAN-FD MCP2518 connected to GPIO pins
*/
volatile CAN_Configuration can_config = {
.battery = CAN_NATIVE, // Which CAN is your battery connected to?
.inverter = CAN_NATIVE, // Which CAN is your inverter connected to? (No need to configure incase you use RS485)
.battery_double = CAN_ADDON_MCP2515, // (OPTIONAL) Which CAN is your second battery connected to?
.charger = CAN_NATIVE, // (OPTIONAL) Which CAN is your charger connected to?
.shunt = CAN_NATIVE // (OPTIONAL) Which CAN is your shunt connected to?
};
#ifdef COMMON_IMAGE
std::string ssid;
std::string password;
std::string passwordAP;
#else
std::string ssid = WIFI_SSID; // Set in USER_SECRETS.h
std::string password = WIFI_PASSWORD; // Set in USER_SECRETS.h
std::string passwordAP = AP_PASSWORD; // Set in USER_SECRETS.h
#endif
const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection
#ifdef COMMON_IMAGE
std::string http_username;
std::string http_password;
#else
std::string http_username = HTTP_USERNAME; // Set in USER_SECRETS.h
std::string http_password = HTTP_PASSWORD; // Set in USER_SECRETS.h
#endif
// Set your Static IP address. Only used incase WIFICONFIG is set in USER_SETTINGS.h
IPAddress local_IP(192, 168, 10, 150);
IPAddress gateway(192, 168, 10, 1);
IPAddress subnet(255, 255, 255, 0);
// MQTT
#ifdef COMMON_IMAGE
std::string mqtt_user;
std::string mqtt_password;
#else
std::string mqtt_user = MQTT_USER; // Set in USER_SECRETS.h
std::string mqtt_password = MQTT_PASSWORD; // Set in USER_SECRETS.h
#endif
const char* mqtt_topic_name =
"BE"; // Custom MQTT topic name. Previously, the name was automatically set to "battery-emulator_esp32-XXXXXX"
const char* mqtt_object_id_prefix =
"be_"; // Custom prefix for MQTT object ID. Previously, the prefix was automatically set to "esp32-XXXXXX_"
const char* mqtt_device_name =
"Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX"
const char* ha_device_id =
"battery-emulator"; // Custom device ID in Home Assistant. Previously, the ID was always "battery-emulator"
/* Charger settings (Optional, when using generator charging) */
volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cell charging of a 96s pack
volatile float CHARGER_MAX_HV = 420; // Max permissible output (VDC) of charger
volatile float CHARGER_MIN_HV = 200; // Min permissible output (VDC) of charger
volatile float CHARGER_MAX_POWER = 3300; // Max power capable of charger, as a ceiling for validating config
volatile float CHARGER_MAX_A = 11.5; // Max current output (amps) of charger
volatile float CHARGER_END_A = 1.0; // Current at which charging is considered complete
#ifdef PERIODIC_BMS_RESET_AT
// A list of rules for your zone can be obtained from https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h
const char* time_zone =
"GMT0BST,M3.5.0/1,M10.5.0"; // TimeZone rule for Europe/London including daylight adjustment rules (optional)
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
#endif

View file

@ -1,228 +0,0 @@
#ifndef __USER_SETTINGS_H__
#define __USER_SETTINGS_H__
#include <WiFi.h>
#include <stdint.h>
#include "src/devboard/utils/types.h"
/* This file contains all the battery/inverter protocol settings Battery-Emulator software */
/* To switch between batteries/inverters, uncomment a line to enable, comment out to disable. */
/* There are also some options for battery limits and extra functionality */
/* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/
/* Select battery used */
//#define BMW_I3_BATTERY
//#define BMW_IX_BATTERY
//#define BMW_PHEV_BATTERY
//#define BOLT_AMPERA_BATTERY
//#define BYD_ATTO_3_BATTERY
//#define FOXESS_BATTERY
//#define CELLPOWER_BMS
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
//#define GEELY_GEOMETRY_C_BATTERY
//#define HYUNDAI_IONIQ_28_BATTERY
//#define CMFA_EV_BATTERY
//#define IMIEV_CZERO_ION_BATTERY
//#define JAGUAR_IPACE_BATTERY
//#define KIA_E_GMP_BATTERY
//#define KIA_HYUNDAI_64_BATTERY
//#define KIA_HYUNDAI_HYBRID_BATTERY
//#define MEB_BATTERY
//#define MG_5_BATTERY
//#define MG_HS_PHEV_BATTERY
//#define NISSAN_LEAF_BATTERY
//#define ORION_BMS
//#define PYLON_BATTERY
//#define DALY_BMS
//#define RJXZS_BMS
//#define RANGE_ROVER_PHEV_BATTERY
//#define RENAULT_KANGOO_BATTERY
//#define RENAULT_TWIZY_BATTERY
//#define RENAULT_ZOE_GEN1_BATTERY
//#define RENAULT_ZOE_GEN2_BATTERY
//#define SONO_BATTERY
//#define SAMSUNG_SDI_LV_BATTERY
//#define SANTA_FE_PHEV_BATTERY
//#define SIMPBMS_BATTERY
//#define STELLANTIS_ECMP_BATTERY
//#define TESLA_MODEL_3Y_BATTERY
//#define TESLA_MODEL_SX_BATTERY
//#define VOLVO_SPA_BATTERY
//#define VOLVO_SPA_HYBRID_BATTERY
//#define TEST_FAKE_BATTERY
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires separate CAN setup)
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/Battery-Emulator/wiki */
//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus
//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
//#define BYD_CAN_DEYE //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus, with Deye specific fixes
//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
//#define FERROAMP_CAN //Enable this line to emulate a "Pylon 4x96V Force H2" over CAN Bus
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
//#define GROWATT_HV_CAN //Enable this line to emulate a "Growatt High Voltage v1.10 battery" over CAN bus
//#define GROWATT_LV_CAN //Enable this line to emulate a "48V Growatt Low Voltage battery" over CAN bus
//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus
//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus
//#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus
//#define SMA_BYD_H_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" (SMA compatible) over CAN bus
//#define SMA_BYD_HVS_CAN //Enable this line to emulate a "BYD Battery-Box HVS 10.2KW battery" (SMA compatible) over CAN bus
//#define SMA_LV_CAN //Enable this line to emulate a "SMA Sunny Island 48V battery" over CAN bus
//#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
//#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
//#define SOLXPOW_CAN //Enable this line to emulate a "Solxpow compatible battery" over CAN bus
//#define SUNGROW_CAN //Enable this line to emulate a "Sungrow SBR064" over CAN bus
/* Select hardware used for Battery-Emulator */
//#define HW_LILYGO
//#define HW_STARK
//#define HW_3LB
//#define HW_DEVKIT
/* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */
#define PRECHARGE_TIME_MS 500 //Precharge time in milliseconds. Modify to suit your inverter (See wiki for more info)
//#define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins)
//#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins)
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled.
//#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting!
//#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF
//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF
// PERIODIC_BMS_RESET_AT Uses NTP server, internet required. In 24 Hour format WITHOUT leading 0. e.g 0230 should be 230. Time Zone is set in USER_SETTINGS.cpp
//#define PERIODIC_BMS_RESET_AT 525
/* Shunt/Contactor settings (Optional) */
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
/* Select charger used (Optional) */
//#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function.
//#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging
/* Automatic Precharge settings (Optional) If you have a battery that expects an external voltage applied before opening contactors (within the battery), configure this section */
//#define PRECHARGE_CONTROL //Enable this line to control a modified HIA4V1 via PWM on the HIA4V1_PIN (see Wiki and HAL for pin definition)
//#define INVERTER_DISCONNECT_CONTACTOR_IS_NORMALLY_OPEN //Enable this line if you use a normally open contactor instead of normally closed
/* Other options */
//#define EQUIPMENT_STOP_BUTTON // Enable this to allow an equipment stop button connected to the Battery-Emulator to disengage the battery
//#define LFP_CHEMISTRY //Tesla specific setting, enable this line to startup in LFP mode
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
//#define LOG_TO_SD //Enable this line to log diagnostic data to SD card (WARNING, raises CPU load, do not use for production)
//#define LOG_CAN_TO_SD //Enable this line to log incoming/outgoing CAN & CAN-FD messages to SD card (WARNING, raises CPU load, do not use for production)
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
//#define DEBUG_VIA_WEB //Enable this line to log diagnostic data while program runs, which can be viewed via webpage (WARNING, slightly raises CPU load, do not use for production)
//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production)
/* CAN options */
//#define CAN_ADDON //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery)
#define CRYSTAL_FREQUENCY_MHZ 8 //CAN_ADDON option, what is your MCP2515 add-on boards crystal frequency?
//#define CANFD_ADDON //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board
#define CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ \
ACAN2517FDSettings::OSC_40MHz //CANFD_ADDON option, what is your MCP2518 add-on boards crystal frequency?
//#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN
/* Connectivity options */
#define WIFI
//#define WIFICONFIG //Enable this line to set a static IP address / gateway /subnet mask for the device. see USER_SETTINGS.cpp for the settings
//#define CUSTOM_HOSTNAME \
"battery-emulator" //Enable this line to use a custom hostname for the device, if disabled the default naming format 'esp32-XXXXXX' will be used.
#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings.
#define WIFIAP //When enabled, the emulator will broadcast its own access point Wifi. Can be used at the same time as a normal Wifi connection to a router.
#define MDNSRESPONDER //Enable this line to enable MDNS, allows battery monitor te be found by .local address. Requires WEBSERVER to be enabled.
#define LOAD_SAVED_SETTINGS_ON_BOOT // Enable this line to read settings stored via the webserver on boot (overrides Wifi credentials set here)
//#define FUNCTION_TIME_MEASUREMENT // Enable this to record execution times and present them in the web UI (WARNING, raises CPU load, do not use for production)
/* MQTT options */
// #define MQTT // Enable this line to enable MQTT
#define MQTT_QOS 0 // MQTT Quality of Service (0, 1, or 2)
#define MQTT_PUBLISH_CELL_VOLTAGES // Enable this line to publish cell voltages to MQTT
#define MQTT_TIMEOUT 2000 // MQTT timeout in milliseconds
#define MQTT_MANUAL_TOPIC_OBJECT_NAME
// Enable MQTT_MANUAL_TOPIC_OBJECT_NAME to use custom MQTT topic, object ID prefix, and device name.
// WARNING: If this is not defined, the previous default naming format 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used.
// This naming convention was in place until version 7.5.0. Users should check the version from which they are updating, as this change
// may break compatibility with previous versions of MQTT naming. Please refer to USER_SETTINGS.cpp for configuration options.
/* Home Assistant options */
#define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required
/* Battery settings */
// Predefined total energy capacity of the battery in Watt-hours (updates automatically from battery data when available)
#define BATTERY_WH_MAX 30000
// Increases battery life. If true will rescale SOC between the configured min/max-percentage
#define BATTERY_USE_SCALED_SOC true
// 8000 = 80.0% , Max percentage the battery will charge to (Inverter gets 100% when reached)
#define BATTERY_MAXPERCENTAGE 8000
// 2000 = 20.0% , Min percentage the battery will discharge to (Inverter gets 0% when reached)
#define BATTERY_MINPERCENTAGE 2000
// 500 = 50.0 °C , Max temperature (Will produce a battery overheat event if above)
#define BATTERY_MAXTEMPERATURE 500
// -250 = -25.0 °C , Min temperature (Will produce a battery frozen event if below)
#define BATTERY_MINTEMPERATURE -250
// 150 = 15.0 °C , Max difference between min and max temperature (Will produce a battery temperature deviation event if greater)
#define BATTERY_MAX_TEMPERATURE_DEVIATION 150
// 300 = 30.0A , Max charge in Amp (Some inverters needs to be limited)
#define BATTERY_MAX_CHARGE_AMP 300
// 300 = 30.0A , Max discharge in Amp (Some inverters needs to be limited)
#define BATTERY_MAX_DISCHARGE_AMP 300
// Enable this to manually set voltage limits on how much battery can be discharged/charged. Normally not used.
#define BATTERY_USE_VOLTAGE_LIMITS false
// 5000 = 500.0V , Target charge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true
#define BATTERY_MAX_CHARGE_VOLTAGE 5000
// 3000 = 300.0V, Target discharge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true
#define BATTERY_MAX_DISCHARGE_VOLTAGE 3000
/* LED settings. Optional customization for how the blinking pattern on the LED should behave.
* CLASSIC - Slow up/down ramp. If CLASSIC, then a ramp up and ramp down will finish in LED_PERIOD_MS milliseconds
* FLOW - Ramp up/down depending on flow of energy
* HEARTBEAT - Heartbeat-like LED pattern that reacts to the system state with color and BPM
*/
#define LED_MODE CLASSIC
#define LED_PERIOD_MS 3000
/* Do not change any code below this line */
/* Only change battery specific settings above and in "USER_SETTINGS.cpp" */
typedef struct {
CAN_Interface battery;
CAN_Interface inverter;
CAN_Interface battery_double;
CAN_Interface charger;
CAN_Interface shunt;
} CAN_Configuration;
extern volatile CAN_Configuration can_config;
extern volatile uint8_t AccessPointEnabled;
extern const uint8_t wifi_channel;
extern volatile float CHARGER_SET_HV;
extern volatile float CHARGER_MAX_HV;
extern volatile float CHARGER_MIN_HV;
extern volatile float CHARGER_MAX_POWER;
extern volatile float CHARGER_MAX_A;
extern volatile float CHARGER_END_A;
extern volatile unsigned long long bmsResetTimeOffset;
#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h"
// Equipment stop button behavior. Use NC button for safety reasons.
//LATCHING_SWITCH - Normally closed (NC), latching switch. When pressed it activates e-stop
//MOMENTARY_SWITCH - Short press to activate e-stop, long 15s press to deactivate. E-stop is persistent between reboots
#ifdef EQUIPMENT_STOP_BUTTON
const STOP_BUTTON_BEHAVIOR stop_button_default_behavior = STOP_BUTTON_BEHAVIOR::MOMENTARY_SWITCH;
#else
const STOP_BUTTON_BEHAVIOR stop_button_default_behavior = STOP_BUTTON_BEHAVIOR::NOT_CONNECTED;
#endif
#ifdef WIFICONFIG
extern IPAddress local_IP;
extern IPAddress gateway;
extern IPAddress subnet;
#endif
#if defined(DEBUG_VIA_USB) || defined(DEBUG_VIA_WEB) || defined(LOG_TO_SD)
#define DEBUG_LOG
#endif
#if defined(MEB_BATTERY)
#define PRECHARGE_CONTROL
#endif
#endif // __USER_SETTINGS_H__

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "RS485Battery.h" #include "RS485Battery.h"
#if !defined(COMMON_IMAGE) && !defined(SELECTED_BATTERY_CLASS)
#error No battery selected! Choose one from the USER_SETTINGS.h file or build COMMON_IMAGE.
#endif
Battery* battery = nullptr; Battery* battery = nullptr;
Battery* battery2 = nullptr; Battery* battery2 = nullptr;
@ -22,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:
@ -90,6 +88,8 @@ const char* name_for_battery_type(BatteryType type) {
return KiaEGmpBattery::Name; return KiaEGmpBattery::Name;
case BatteryType::KiaHyundai64: case BatteryType::KiaHyundai64:
return KiaHyundai64Battery::Name; return KiaHyundai64Battery::Name;
case BatteryType::Kia64FD:
return Kia64FDBattery::Name;
case BatteryType::KiaHyundaiHybrid: case BatteryType::KiaHyundaiHybrid:
return KiaHyundaiHybridBattery::Name; return KiaHyundaiHybridBattery::Name;
case BatteryType::Meb: case BatteryType::Meb:
@ -108,6 +108,8 @@ const char* name_for_battery_type(BatteryType type) {
return RjxzsBms::Name; return RjxzsBms::Name;
case BatteryType::RangeRoverPhev: case BatteryType::RangeRoverPhev:
return RangeRoverPhevBattery::Name; return RangeRoverPhevBattery::Name;
case BatteryType::RelionBattery:
return RelionBattery::Name;
case BatteryType::RenaultKangoo: case BatteryType::RenaultKangoo:
return RenaultKangooBattery::Name; return RenaultKangooBattery::Name;
case BatteryType::RenaultTwizy: case BatteryType::RenaultTwizy:
@ -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,19 +141,10 @@ 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;
#ifdef COMMON_IMAGE
#ifdef SELECTED_BATTERY_CLASS
#error "Compile time SELECTED_BATTERY_CLASS should not be defined with COMMON_IMAGE"
#endif
BatteryType user_selected_battery_type = BatteryType::NissanLeaf; BatteryType user_selected_battery_type = BatteryType::NissanLeaf;
bool user_selected_second_battery = false; bool user_selected_second_battery = false;
@ -187,6 +182,8 @@ Battery* create_battery(BatteryType type) {
return new ImievCZeroIonBattery(); return new ImievCZeroIonBattery();
case BatteryType::JaguarIpace: case BatteryType::JaguarIpace:
return new JaguarIpaceBattery(); return new JaguarIpaceBattery();
case BatteryType::Kia64FD:
return new Kia64FDBattery();
case BatteryType::KiaEGmp: case BatteryType::KiaEGmp:
return new KiaEGmpBattery(); return new KiaEGmpBattery();
case BatteryType::KiaHyundai64: case BatteryType::KiaHyundai64:
@ -209,6 +206,8 @@ Battery* create_battery(BatteryType type) {
return new RjxzsBms(); return new RjxzsBms();
case BatteryType::RangeRoverPhev: case BatteryType::RangeRoverPhev:
return new RangeRoverPhevBattery(); return new RangeRoverPhevBattery();
case BatteryType::RelionBattery:
return new RelionBattery();
case BatteryType::RenaultKangoo: case BatteryType::RenaultKangoo:
return new RenaultKangooBattery(); return new RenaultKangooBattery();
case BatteryType::RenaultTwizy: case BatteryType::RenaultTwizy:
@ -217,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:
@ -269,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) {
@ -279,36 +286,21 @@ void setup_battery() {
} }
} }
} }
#else // Battery selection has been made at build-time
void setup_battery() { /* User-selected Nissan LEAF settings */
// Instantiate the battery only once just in case this function gets called multiple times. bool user_selected_LEAF_interlock_mandatory = false;
if (battery == nullptr) { /* User-selected Tesla settings */
#ifdef TESLA_MODEL_3Y_BATTERY bool user_selected_tesla_digital_HVIL = false;
battery = new SELECTED_BATTERY_CLASS(user_selected_battery_chemistry); uint16_t user_selected_tesla_GTW_country = 17477;
#else bool user_selected_tesla_GTW_rightHandDrive = true;
battery = new SELECTED_BATTERY_CLASS(); uint16_t user_selected_tesla_GTW_mapRegion = 2;
#endif uint16_t user_selected_tesla_GTW_chassisType = 2;
} uint16_t user_selected_tesla_GTW_packEnergy = 1;
battery->setup(); /* User-selected EGMP+others settings */
bool user_selected_use_estimated_SOC = false;
#ifdef DOUBLE_BATTERY // Use 0V for user selected cell/pack voltage defaults (On boot will be replaced with saved values from NVM)
if (battery2 == nullptr) { uint16_t user_selected_max_pack_voltage_dV = 0;
#if defined(BMW_I3_BATTERY) uint16_t user_selected_min_pack_voltage_dV = 0;
battery2 = uint16_t user_selected_max_cell_voltage_mV = 0;
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allowed_contactor_closing, uint16_t user_selected_min_cell_voltage_mV = 0;
can_config.battery_double, esp32hal->WUP_PIN2());
#elif defined(KIA_HYUNDAI_64_BATTERY)
battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2,
&datalayer.system.status.battery2_allowed_contactor_closing,
can_config.battery_double);
#elif defined(SANTA_FE_PHEV_BATTERY) || defined(TEST_FAKE_BATTERY)
battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, can_config.battery_double);
#else
battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, nullptr, can_config.battery_double);
#endif
}
battery2->setup();
#endif
}
#endif

View file

@ -1,6 +1,5 @@
#ifndef BATTERIES_H #ifndef BATTERIES_H
#define BATTERIES_H #define BATTERIES_H
#include "../../USER_SETTINGS.h"
#include "Shunt.h" #include "Shunt.h"
class Battery; class Battery;
@ -29,6 +28,7 @@ void setup_can_shunt();
#include "HYUNDAI-IONIQ-28-BATTERY.h" #include "HYUNDAI-IONIQ-28-BATTERY.h"
#include "IMIEV-CZERO-ION-BATTERY.h" #include "IMIEV-CZERO-ION-BATTERY.h"
#include "JAGUAR-IPACE-BATTERY.h" #include "JAGUAR-IPACE-BATTERY.h"
#include "KIA-64FD-BATTERY.h"
#include "KIA-E-GMP-BATTERY.h" #include "KIA-E-GMP-BATTERY.h"
#include "KIA-HYUNDAI-64-BATTERY.h" #include "KIA-HYUNDAI-64-BATTERY.h"
#include "KIA-HYUNDAI-HYBRID-BATTERY.h" #include "KIA-HYUNDAI-HYBRID-BATTERY.h"
@ -39,10 +39,12 @@ void setup_can_shunt();
#include "ORION-BMS.h" #include "ORION-BMS.h"
#include "PYLON-BATTERY.h" #include "PYLON-BATTERY.h"
#include "RANGE-ROVER-PHEV-BATTERY.h" #include "RANGE-ROVER-PHEV-BATTERY.h"
#include "RELION-LV-BATTERY.h"
#include "RENAULT-KANGOO-BATTERY.h" #include "RENAULT-KANGOO-BATTERY.h"
#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"
@ -54,5 +56,19 @@ 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_min_pack_voltage_dV;
extern uint16_t user_selected_max_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_tesla_digital_HVIL;
extern uint16_t user_selected_tesla_GTW_country;
extern bool user_selected_tesla_GTW_rightHandDrive;
extern uint16_t user_selected_tesla_GTW_mapRegion;
extern uint16_t user_selected_tesla_GTW_chassisType;
extern uint16_t user_selected_tesla_GTW_packEnergy;
#endif #endif

View file

@ -6,10 +6,6 @@
#include "BMW-I3-HTML.h" #include "BMW-I3-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef BMW_I3_BATTERY
#define SELECTED_BATTERY_CLASS BmwI3Battery
#endif
class BmwI3Battery : public CanBattery { class BmwI3Battery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.
@ -137,7 +133,7 @@ class BmwI3Battery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x13E, .ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}}; .data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
CAN_frame BMW_192 = {.FD = false, static constexpr CAN_frame BMW_192 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x192, .ID = 0x192,
@ -152,8 +148,8 @@ class BmwI3Battery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x1D0, .ID = 0x1D0,
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}}; .data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}}; static constexpr CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}};
CAN_frame BMW_2E2 = {.FD = false, static constexpr CAN_frame BMW_2E2 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x2E2, .ID = 0x2E2,
@ -168,17 +164,17 @@ class BmwI3Battery : public CanBattery {
.DLC = 6, .DLC = 6,
.ID = 0x328, .ID = 0x328,
.data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}}; .data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
CAN_frame BMW_37B = {.FD = false, static constexpr CAN_frame BMW_37B = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x37B, .ID = 0x37B,
.data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}}; .data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
CAN_frame BMW_380 = {.FD = false, static constexpr CAN_frame BMW_380 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 7, .DLC = 7,
.ID = 0x380, .ID = 0x380,
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}}; .data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
CAN_frame BMW_3A0 = {.FD = false, static constexpr CAN_frame BMW_3A0 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x3A0, .ID = 0x3A0,
@ -193,13 +189,13 @@ class BmwI3Battery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x3C5, .ID = 0x3C5,
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}}; .data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
CAN_frame BMW_3CA = {.FD = false, static constexpr CAN_frame BMW_3CA = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x3CA, .ID = 0x3CA,
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}}; .data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}}; static constexpr CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}};
CAN_frame BMW_3E4 = {.FD = false, static constexpr CAN_frame BMW_3E4 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x3E4, .ID = 0x3E4,
@ -216,62 +212,79 @@ class BmwI3Battery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x3F9, .ID = 0x3F9,
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}}; .data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
CAN_frame BMW_3FB = {.FD = false, static constexpr CAN_frame BMW_3FB = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x3FB, .ID = 0x3FB,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}}; .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}}; CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}};
CAN_frame BMW_418 = {.FD = false, static constexpr CAN_frame BMW_418 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x418, .ID = 0x418,
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}}; .data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
CAN_frame BMW_41D = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x41D, .data = {0xFF, 0xF7, 0x7F, 0xFF}}; static constexpr CAN_frame BMW_41D = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x41D,
.data = {0xFF, 0xF7, 0x7F, 0xFF}};
CAN_frame BMW_433 = {.FD = false, CAN_frame BMW_433 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 4, .DLC = 4,
.ID = 0x433, .ID = 0x433,
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification .data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
CAN_frame BMW_512 = {.FD = false, static constexpr CAN_frame BMW_512 = {
.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x512, .ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
CAN_frame BMW_592_0 = {.FD = false, static constexpr CAN_frame BMW_592_0 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x592, .ID = 0x592,
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}}; .data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
CAN_frame BMW_592_1 = {.FD = false, static constexpr CAN_frame BMW_592_1 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x592, .ID = 0x592,
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}}; .data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BMW_5F8 = {.FD = false, static constexpr CAN_frame BMW_5F8 = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x5F8, .ID = 0x5F8,
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}}; .data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
CAN_frame BMW_6F1_CELL = {.FD = false, static constexpr CAN_frame BMW_6F1_CELL = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F1, .ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xBF}}; .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};
CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}}; static constexpr CAN_frame BMW_6F1_SOH = {.FD = false,
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}}; .ext_ID = false,
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false, .DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0x63, 0x35}};
static constexpr CAN_frame BMW_6F1_SOC = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
static constexpr CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F1, .ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; .data = {0x07, 0x03, 0x22, 0xDF, 0xA0}};
CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}}; static constexpr CAN_frame BMW_6F1_CONTINUE = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F1,
.data = {0x07, 0x30, 0x00, 0x02}};
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false, CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 7, .DLC = 7,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}}; .data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false, static constexpr CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x6F4, .ID = 0x6F4,

View file

@ -37,7 +37,7 @@ uint8_t BmwIXBattery::increment_alive_counter(uint8_t counter) {
return counter; return counter;
} }
static byte increment_C0_counter(byte counter) { static uint8_t increment_C0_counter(uint8_t counter) {
counter++; counter++;
// Reset to 0xF0 if it exceeds 0xFE // Reset to 0xF0 if it exceeds 0xFE
if (counter > 0xFE) { if (counter > 0xFE) {
@ -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;
@ -95,6 +95,10 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive
} }
if (terminal30_12v_voltage < 1100) { //11.000V
set_event(EVENT_12V_LOW, terminal30_12v_voltage);
}
datalayer.battery.info.max_design_voltage_dV = max_design_voltage; datalayer.battery.info.max_design_voltage_dV = max_design_voltage;
datalayer.battery.info.min_design_voltage_dV = min_design_voltage; datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
@ -303,9 +307,7 @@ void BmwIXBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 || if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 ||
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot
#ifdef DEBUG_LOG
logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
#endif // DEBUG_LOG
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type //set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET); transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET);
} else { //Only ingest values if they are not the 10V Error state } else { //Only ingest values if they are not the 10V Error state
@ -374,15 +376,11 @@ void BmwIXBattery::transmit_can(unsigned long currentMillis) {
// Detect edge // Detect edge
if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) { if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) {
// Rising edge detected // Rising edge detected
#ifdef DEBUG_LOG
logging.println("Rising edge detected. Resetting 10ms counter."); logging.println("Rising edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter counter_10ms = 0; // reset counter
} else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) { } else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) {
// Dropping edge detected // Dropping edge detected
#ifdef DEBUG_LOG
logging.println("Dropping edge detected. Resetting 10ms counter."); logging.println("Dropping edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter counter_10ms = 0; // reset counter
} }
ContactorCloseRequest.previous = ContactorCloseRequest.present; ContactorCloseRequest.previous = ContactorCloseRequest.present;
@ -461,12 +459,12 @@ void BmwIXBattery::setup(void) { // Performs one time setup at startup
void BmwIXBattery::HandleIncomingUserRequest(void) { void BmwIXBattery::HandleIncomingUserRequest(void) {
// Debug user request to open or close the contactors // Debug user request to open or close the contactors
#ifdef DEBUG_LOG if (userRequestContactorClose) {
logging.print("User request: contactor close: "); logging.printf("User request: contactor close");
logging.print(userRequestContactorClose); }
logging.print(" User request: contactor open: "); if (userRequestContactorOpen) {
logging.println(userRequestContactorOpen); logging.printf("User request: contactor open");
#endif // DEBUG_LOG }
if ((userRequestContactorClose == false) && (userRequestContactorOpen == false)) { if ((userRequestContactorClose == false) && (userRequestContactorOpen == false)) {
// do nothing // do nothing
} else if ((userRequestContactorClose == true) && (userRequestContactorOpen == false)) { } else if ((userRequestContactorClose == true) && (userRequestContactorOpen == false)) {
@ -483,11 +481,9 @@ void BmwIXBattery::HandleIncomingUserRequest(void) {
// set user request to false // set user request to false
userRequestContactorClose = false; userRequestContactorClose = false;
userRequestContactorOpen = false; userRequestContactorOpen = false;
// print error, as both these flags shall not be true at the same time // print error, as both these flags shall not be true at the same time
#ifdef DEBUG_LOG
logging.println( logging.println(
"Error: user requested contactors to close and open at the same time. Contactors have been opened."); "Error: user requested contactors to close and open at the same time. Contactors have been opened.");
#endif // DEBUG_LOG
} }
} }
@ -495,16 +491,12 @@ void BmwIXBattery::HandleIncomingInverterRequest(void) {
InverterContactorCloseRequest.present = datalayer.system.status.inverter_allows_contactor_closing; InverterContactorCloseRequest.present = datalayer.system.status.inverter_allows_contactor_closing;
// Detect edge // Detect edge
if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) { if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) {
// Rising edge detected // Rising edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to close contactors"); logging.println("Inverter requests to close contactors");
#endif // DEBUG_LOG
BmwIxCloseContactors(); BmwIxCloseContactors();
} else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) { } else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) {
// Falling edge detected // Falling edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to open contactors"); logging.println("Inverter requests to open contactors");
#endif // DEBUG_LOG
BmwIxOpenContactors(); BmwIxOpenContactors();
} // else: do nothing } // else: do nothing
@ -513,16 +505,12 @@ void BmwIXBattery::HandleIncomingInverterRequest(void) {
} }
void BmwIXBattery::BmwIxCloseContactors(void) { void BmwIXBattery::BmwIxCloseContactors(void) {
#ifdef DEBUG_LOG
logging.println("Closing contactors"); logging.println("Closing contactors");
#endif // DEBUG_LOG
contactorCloseReq = true; contactorCloseReq = true;
} }
void BmwIXBattery::BmwIxOpenContactors(void) { void BmwIXBattery::BmwIxOpenContactors(void) {
#ifdef DEBUG_LOG
logging.println("Opening contactors"); logging.println("Opening contactors");
#endif // DEBUG_LOG
contactorCloseReq = false; contactorCloseReq = false;
counter_100ms = 0; // reset counter, such that keep contactors closed message sequence starts from the beginning counter_100ms = 0; // reset counter, such that keep contactors closed message sequence starts from the beginning
} }
@ -548,46 +536,34 @@ void BmwIXBattery::HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
if (counter_10ms == 0) { if (counter_10ms == 0) {
// @0 ms // @0 ms
transmit_can_frame(&BMWiX_510); transmit_can_frame(&BMWiX_510);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 1/6"); logging.println("Transmitted 0x510 - 1/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 5) { } else if (counter_10ms == 5) {
// @50 ms // @50 ms
transmit_can_frame(&BMWiX_276); transmit_can_frame(&BMWiX_276);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x276 - 2/6"); logging.println("Transmitted 0x276 - 2/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 10) { } else if (counter_10ms == 10) {
// @100 ms // @100 ms
BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed
transmit_can_frame(&BMWiX_510); transmit_can_frame(&BMWiX_510);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 3/6"); logging.println("Transmitted 0x510 - 3/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 20) { } else if (counter_10ms == 20) {
// @200 ms // @200 ms
BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed
BMWiX_510.data.u8[5] = 0x80; // needed to close contactors BMWiX_510.data.u8[5] = 0x80; // needed to close contactors
transmit_can_frame(&BMWiX_510); transmit_can_frame(&BMWiX_510);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 4/6"); logging.println("Transmitted 0x510 - 4/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 30) { } else if (counter_10ms == 30) {
// @300 ms // @300 ms
BMWiX_16E.data.u8[0] = 0x6A; BMWiX_16E.data.u8[0] = 0x6A;
BMWiX_16E.data.u8[1] = 0xAD; BMWiX_16E.data.u8[1] = 0xAD;
transmit_can_frame(&BMWiX_16E); transmit_can_frame(&BMWiX_16E);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 5/6"); logging.println("Transmitted 0x16E - 5/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 50) { } else if (counter_10ms == 50) {
// @500 ms // @500 ms
BMWiX_16E.data.u8[0] = 0x03; BMWiX_16E.data.u8[0] = 0x03;
BMWiX_16E.data.u8[1] = 0xA9; BMWiX_16E.data.u8[1] = 0xA9;
transmit_can_frame(&BMWiX_16E); transmit_can_frame(&BMWiX_16E);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 6/6"); logging.println("Transmitted 0x16E - 6/6");
#endif // DEBUG_LOG
ContactorState.closed = true; ContactorState.closed = true;
ContactorState.open = false; ContactorState.open = false;
} }
@ -609,9 +585,7 @@ void BmwIXBattery::BmwIxKeepContactorsClosed(uint8_t counter_100ms) {
0xC9, 0x3A, 0xF7}; // Explicit declaration, to prevent modification by other functions 0xC9, 0x3A, 0xF7}; // Explicit declaration, to prevent modification by other functions
if (counter_100ms == 0) { if (counter_100ms == 0) {
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages started"); logging.println("Sending keep contactors closed messages started");
#endif // DEBUG_LOG
// @0 ms // @0 ms
transmit_can_frame(&BMWiX_510); transmit_can_frame(&BMWiX_510);
} else if (counter_100ms == 7) { } else if (counter_100ms == 7) {
@ -627,9 +601,7 @@ void BmwIXBattery::BmwIxKeepContactorsClosed(uint8_t counter_100ms) {
BMWiX_16E.data.u8[0] = 0x02; BMWiX_16E.data.u8[0] = 0x02;
BMWiX_16E.data.u8[1] = 0xA7; BMWiX_16E.data.u8[1] = 0xA7;
transmit_can_frame(&BMWiX_16E); transmit_can_frame(&BMWiX_16E);
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages finished"); logging.println("Sending keep contactors closed messages finished");
#endif // DEBUG_LOG
} else if (counter_100ms == 140) { } else if (counter_100ms == 140) {
// @14000 ms // @14000 ms
// reset counter (outside of this function) // reset counter (outside of this function)

View file

@ -4,10 +4,6 @@
#include "BMW-IX-HTML.h" #include "BMW-IX-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef BMW_IX_BATTERY
#define SELECTED_BATTERY_CLASS BmwIXBattery
#endif
class BmwIXBattery : public CanBattery { class BmwIXBattery : public CanBattery {
public: public:
BmwIXBattery() : renderer(*this) {} BmwIXBattery() : renderer(*this) {}
@ -52,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%
@ -150,7 +144,7 @@ TODO:
*/ */
//Vehicle CAN START //Vehicle CAN START
CAN_frame BMWiX_125 = { static constexpr CAN_frame BMWiX_125 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 20, .DLC = 20,
@ -178,14 +172,14 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
0x3A, // 0x3A to close contactors, 0x33 to open contactors 0x3A, // 0x3A to close contactors, 0x33 to open contactors
0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output. 0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output.
CAN_frame BMWiX_188 = { static constexpr CAN_frame BMWiX_188 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x188, .ID = 0x188,
.data = {0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF}}; // CCU output - values while driving .data = {0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF}}; // CCU output - values while driving
CAN_frame BMWiX_1EA = { static constexpr CAN_frame BMWiX_1EA = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -193,7 +187,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
//.data = {TODO:km_least_significant, TODO:, TODO:, TODO:, TODO:km_most_significant, 0xFF, TODO:, TODO:} //.data = {TODO:km_least_significant, TODO:, TODO:, TODO:, TODO:km_most_significant, 0xFF, TODO:, TODO:}
}; // KOMBI output - kilometerstand }; // KOMBI output - kilometerstand
CAN_frame BMWiX_1FC = { static constexpr CAN_frame BMWiX_1FC = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -201,7 +195,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.data = {0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xC0, .data = {0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xC0,
0x00}}; // FIXME:(add transmitter node) output - heat management engine control - values 0x00}}; // FIXME:(add transmitter node) output - heat management engine control - values
CAN_frame BMWiX_21D = { static constexpr CAN_frame BMWiX_21D = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -209,14 +203,15 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
// .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:} // .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:}
}; // FIXME:(add transmitter node) output - request heating and air conditioning system 1 }; // FIXME:(add transmitter node) output - request heating and air conditioning system 1
CAN_frame BMWiX_276 = {.FD = true, static constexpr CAN_frame BMWiX_276 = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x276, .ID = 0x276,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, .data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFD}}; // BDC output - vehicle condition. Used for contactor closing 0xFD}}; // BDC output - vehicle condition. Used for contactor closing
CAN_frame BMWiX_2ED = { static constexpr CAN_frame BMWiX_2ED = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -225,14 +220,14 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
0x75, 0x75,
0x75}}; // FIXME:(add transmitter node) output - ambient temperature (values seen in logs vary between 0x72 and 0x79) 0x75}}; // FIXME:(add transmitter node) output - ambient temperature (values seen in logs vary between 0x72 and 0x79)
CAN_frame BMWiX_2F1 = { static constexpr CAN_frame BMWiX_2F1 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x2F1, .ID = 0x2F1,
.data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC output - values - varies at startup .data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC output - values - varies at startup
CAN_frame BMWiX_340 = { static constexpr CAN_frame BMWiX_340 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 12, .DLC = 12,
@ -240,7 +235,7 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
// .data = {TODO:, TODO:, TODO:, 0xFF, TODO:, TODO:, 0x00, 0x00, TODO:, TODO:, TODO:, 0xFF, 0xFF, } // .data = {TODO:, TODO:, TODO:, 0xFF, TODO:, TODO:, 0x00, 0x00, TODO:, TODO:, TODO:, 0xFF, 0xFF, }
}; // CCU output }; // CCU output
CAN_frame BMWiX_380 = { static constexpr CAN_frame BMWiX_380 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 7, .DLC = 7,
@ -256,7 +251,7 @@ CAN_frame BMWiX_439 = {.FD = true,
.data = {0xFF, 0x3F, 0xFF, 0xF3}}; // 1000ms BDC output .data = {0xFF, 0x3F, 0xFF, 0xF3}}; // 1000ms BDC output
*/ */
CAN_frame BMWiX_442 = { static constexpr CAN_frame BMWiX_442 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
@ -289,7 +284,7 @@ CAN_frame BMWiX_49C = {.FD = true,
0xFF}}; // 1000ms SME output - Suspected keep alive CONFIRM NEEDED 0xFF}}; // 1000ms SME output - Suspected keep alive CONFIRM NEEDED
*/ */
CAN_frame BMWiX_4EB = { static constexpr CAN_frame BMWiX_4EB = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -297,7 +292,7 @@ CAN_frame BMWiX_49C = {.FD = true,
// .data = {TODO:, TODO:, TODO: 0xE0 or 0xE5 (while driving), 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // .data = {TODO:, TODO:, TODO: 0xE0 or 0xE5 (while driving), 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // BDC output - RSU condition }; // BDC output - RSU condition
CAN_frame BMWiX_4F8 = { static constexpr CAN_frame BMWiX_4F8 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -318,7 +313,7 @@ CAN_frame BMWiX_49C = {.FD = true,
0x01, 0x01,
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing 0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
CAN_frame BMWiX_6D = { static constexpr CAN_frame BMWiX_6D = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
@ -327,7 +322,7 @@ CAN_frame BMWiX_49C = {.FD = true,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC output - [0] [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static 0xFF}}; // 1000ms BDC output - [0] [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
CAN_frame BMWiX_C0 = { static constexpr CAN_frame BMWiX_C0 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 2, .DLC = 2,
@ -338,87 +333,91 @@ CAN_frame BMWiX_49C = {.FD = true,
//Vehicle CAN END //Vehicle CAN END
//Request Data CAN START //Request Data CAN START
CAN_frame BMWiX_6F4 = { static constexpr CAN_frame BMWiX_6F4 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value .data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value
CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = { static constexpr CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 4, .DLC = 4,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode .data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode
CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 4, .DLC = 4,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME .data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME
CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures .data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures
CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC% .data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC%
CAN_frame BMWiX_6F4_REQUEST_CAPACITY = { static constexpr CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, .data = {0x07, 0x03, 0x22, 0xE5,
0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = { static constexpr CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid .data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = { static constexpr CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor) .data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor)
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = { static constexpr CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor) .data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor)
CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = { static constexpr CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge .data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge
CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = { static constexpr CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages .data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages
CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = { static constexpr CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply) .data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply)
CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO .data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO
CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_SOH = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request .data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request
CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = { static constexpr CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
@ -426,82 +425,84 @@ CAN_frame BMWiX_49C = {.FD = true,
.data = { .data = {
0x07, 0x03, 0x22, 0xE5, 0x07, 0x03, 0x22, 0xE5,
0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate 0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate
CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status .data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status
CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_UPTIME = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status .data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status
CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State .data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State
CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true, static constexpr CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {
.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data .data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data
CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = { static constexpr CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps .data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps
CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = { static constexpr CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier .data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = { static constexpr CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = { static constexpr CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed .data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = { static constexpr CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command? .data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START2 = { static constexpr CAN_frame BMWiX_6F4_REQUEST_BALANCING_START2 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 6, .DLC = 6,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0xF4, 0x04, 0x31, 0x01, 0xAE, 0x77}}; // Request Balancing command? .data = {0xF4, 0x04, 0x31, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = { static constexpr CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits .data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits
CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true, static constexpr CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 4, .DLC = 4,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x30, 0x00, 0x02}}; .data = {0x07, 0x30, 0x00, 0x02}};
//Action Requests: //Action Requests:
CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true, static constexpr CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true, static constexpr CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6F4, .ID = 0x6F4,
@ -509,7 +510,7 @@ CAN_frame BMWiX_49C = {.FD = true,
//Request Data CAN End //Request Data CAN End
//Setup UDS values to poll for //Setup UDS values to poll for
CAN_frame* UDS_REQUESTS100MS[17] = {&BMWiX_6F4_REQUEST_CELL_TEMP, static constexpr const CAN_frame* UDS_REQUESTS100MS[17] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
&BMWiX_6F4_REQUEST_SOC, &BMWiX_6F4_REQUEST_SOC,
&BMWiX_6F4_REQUEST_CAPACITY, &BMWiX_6F4_REQUEST_CAPACITY,
&BMWiX_6F4_REQUEST_MINMAXCELLV, &BMWiX_6F4_REQUEST_MINMAXCELLV,
@ -532,25 +533,25 @@ CAN_frame BMWiX_49C = {.FD = true,
bool battery_info_available = false; bool battery_info_available = false;
uint32_t battery_serial_number = 0; uint32_t battery_serial_number = 0;
int32_t battery_current = 0; int32_t battery_current = 0;
int16_t battery_voltage = 370; //Startup with valid values - needs fixing in future uint16_t battery_voltage = 3700; //Startup with valid values - needs fixing in future
int16_t terminal30_12v_voltage = 0; uint16_t terminal30_12v_voltage = 1200;
int16_t battery_voltage_after_contactor = 0; uint16_t battery_voltage_after_contactor = 0;
int16_t min_soc_state = 5000; uint16_t min_soc_state = 5000;
int16_t avg_soc_state = 5000; uint16_t avg_soc_state = 5000;
int16_t max_soc_state = 5000; uint16_t max_soc_state = 5000;
int16_t min_soh_state = 9900; // Uses E5 45, also available in 78 73 uint16_t min_soh_state = 9900; // Uses E5 45, also available in 78 73
int16_t avg_soh_state = 9900; // Uses E5 45, also available in 78 73 uint16_t avg_soh_state = 9900; // Uses E5 45, also available in 78 73
int16_t max_soh_state = 9900; // Uses E5 45, also available in 78 73 uint16_t max_soh_state = 9900; // Uses E5 45, also available in 78 73
uint16_t max_design_voltage = 0; uint16_t max_design_voltage = 0;
uint16_t min_design_voltage = 0; uint16_t min_design_voltage = 0;
int32_t remaining_capacity = 0; uint32_t remaining_capacity = 0;
int32_t max_capacity = 0; uint32_t max_capacity = 0;
int16_t min_battery_temperature = 0; int16_t min_battery_temperature = 0;
int16_t avg_battery_temperature = 0; int16_t avg_battery_temperature = 0;
int16_t max_battery_temperature = 0; int16_t max_battery_temperature = 0;
int16_t main_contactor_temperature = 0; int16_t main_contactor_temperature = 0;
int16_t min_cell_voltage = 3700; //Startup with valid values - needs fixing in future uint16_t min_cell_voltage = 3700; //Startup with valid values - needs fixing in future
int16_t max_cell_voltage = 3700; //Startup with valid values - needs fixing in future uint16_t max_cell_voltage = 3700; //Startup with valid values - needs fixing in future
unsigned long min_cell_voltage_lastchanged = 0; unsigned long min_cell_voltage_lastchanged = 0;
unsigned long max_cell_voltage_lastchanged = 0; unsigned long max_cell_voltage_lastchanged = 0;
unsigned min_cell_voltage_lastreceived = 0; unsigned min_cell_voltage_lastreceived = 0;

View file

@ -1,5 +1,6 @@
#include "BMW-PHEV-BATTERY.h" #include "BMW-PHEV-BATTERY.h"
#include <Arduino.h> #include <Arduino.h>
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
@ -122,9 +123,7 @@ bool BmwPhevBattery::storeUDSPayload(const uint8_t* payload, uint8_t length) {
if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) { if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) {
// Overflow => abort // Overflow => abort
gUDSContext.UDS_inProgress = false; gUDSContext.UDS_inProgress = false;
#ifdef DEBUG_LOG
logging.println("UDS Payload Overflow"); logging.println("UDS Payload Overflow");
#endif // DEBUG_LOG
return false; return false;
} }
memcpy(&gUDSContext.UDS_buffer[gUDSContext.UDS_bytesReceived], payload, length); memcpy(&gUDSContext.UDS_buffer[gUDSContext.UDS_bytesReceived], payload, length);
@ -134,9 +133,7 @@ bool BmwPhevBattery::storeUDSPayload(const uint8_t* payload, uint8_t length) {
// If weve reached or exceeded the expected length, mark complete // If weve reached or exceeded the expected length, mark complete
if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) { if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) {
gUDSContext.UDS_inProgress = false; gUDSContext.UDS_inProgress = false;
// #ifdef DEBUG_LOG
// logging.println("Recived all expected UDS bytes"); // logging.println("Recived all expected UDS bytes");
// #endif // DEBUG_LOG
} }
return true; return true;
} }
@ -153,15 +150,6 @@ uint8_t BmwPhevBattery::increment_alive_counter(uint8_t counter) {
return counter; return counter;
} }
static byte increment_0C0_counter(byte counter) {
counter++;
// Reset to 0xF0 if it exceeds 0xFE
if (counter > 0xFE) {
counter = 0xF0;
}
return counter;
}
void BmwPhevBattery::processCellVoltages() { void BmwPhevBattery::processCellVoltages() {
const int startByte = 3; // Start reading at byte 3 const int startByte = 3; // Start reading at byte 3
const int numVoltages = 96; // Number of cell voltage values to process const int numVoltages = 96; // Number of cell voltage values to process
@ -189,16 +177,17 @@ 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();
#ifdef DEBUG_LOG
logging.println("Sent magic wakeup packet to SME at 100kbps..."); logging.println("Sent magic wakeup packet to SME at 100kbps...");
#endif
} }
void BmwPhevBattery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer void BmwPhevBattery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
@ -246,9 +235,7 @@ void BmwPhevBattery::update_values() { //This function maps all the values fetc
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
set_event(EVENT_STALE_VALUE, 0); set_event(EVENT_STALE_VALUE, 0);
#ifdef DEBUG_LOG
logging.println("Stale Min/Max voltage values detected during charge/discharge sending - 9999mV..."); logging.println("Stale Min/Max voltage values detected during charge/discharge sending - 9999mV...");
#endif // DEBUG_LOG
} else { } else {
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive
@ -398,11 +385,7 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
rx_frame.data.u8[4] == rx_frame.data.u8[4] ==
0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01
balancing_status = (rx_frame.data.u8[6]); balancing_status = (rx_frame.data.u8[6]);
// #ifdef DEBUG_LOG //logging.println("Balancing Status received");
// logging.println("Balancing Status received");
// #endif // DEBUG_LOG
} }
break; break;
@ -481,7 +464,7 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
#endif // DEBUG_LOG && UDS_LOG #endif // DEBUG_LOG && UDS_LOG
transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME); transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME);
gUDSContext.receivedInBatch = 0; // Reset batch count gUDSContext.receivedInBatch = 0; // Reset batch count
Serial.println("Sent FC for next batch of 3 frames."); logging.println("Sent FC for next batch of 3 frames.");
} }
break; break;
@ -525,20 +508,18 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_current = ((int32_t)((gUDSContext.UDS_buffer[3] << 24) | (gUDSContext.UDS_buffer[4] << 16) | battery_current = ((int32_t)((gUDSContext.UDS_buffer[3] << 24) | (gUDSContext.UDS_buffer[4] << 16) |
(gUDSContext.UDS_buffer[5] << 8) | gUDSContext.UDS_buffer[6])) * (gUDSContext.UDS_buffer[5] << 8) | gUDSContext.UDS_buffer[6])) *
0.1; 0.1;
#ifdef DEBUG_LOG logging.printf("Received current/amps measurement data: ");
logging.print("Received current/amps measurement data: ");
logging.print(battery_current); logging.print(battery_current);
logging.print(" - "); logging.printf(" - ");
for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) {
// Optional leading zero for single-digit hex // Optional leading zero for single-digit hex
if (gUDSContext.UDS_buffer[i] < 0x10) { if (gUDSContext.UDS_buffer[i] < 0x10) {
logging.print("0"); logging.printf("0");
} }
logging.print(gUDSContext.UDS_buffer[i], HEX); logging.print(gUDSContext.UDS_buffer[i], HEX);
logging.print(" "); logging.printf(" ");
} }
logging.println(); // new line at the end logging.println(""); // new line at the end
#endif // DEBUG_LOG
} }
//Cell Min/Max //Cell Min/Max
@ -553,19 +534,17 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
min_cell_voltage = (gUDSContext.UDS_buffer[9] << 8 | gUDSContext.UDS_buffer[10]) / 10; min_cell_voltage = (gUDSContext.UDS_buffer[9] << 8 | gUDSContext.UDS_buffer[10]) / 10;
max_cell_voltage = (gUDSContext.UDS_buffer[11] << 8 | gUDSContext.UDS_buffer[12]) / 10; max_cell_voltage = (gUDSContext.UDS_buffer[11] << 8 | gUDSContext.UDS_buffer[12]) / 10;
} else { } else {
#ifdef DEBUG_LOG
logging.println("Cell Min Max Invalid 65535 or 0..."); logging.println("Cell Min Max Invalid 65535 or 0...");
logging.print("Received data: "); logging.printf("Received data: ");
for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) {
// Optional leading zero for single-digit hex // Optional leading zero for single-digit hex
if (gUDSContext.UDS_buffer[i] < 0x10) { if (gUDSContext.UDS_buffer[i] < 0x10) {
logging.print("0"); logging.printf("0");
} }
logging.print(gUDSContext.UDS_buffer[i], HEX); logging.print(gUDSContext.UDS_buffer[i], HEX);
logging.print(" "); logging.printf(" ");
} }
logging.println(); // new line at the end logging.println(); // new line at the end
#endif // DEBUG_LOG
} }
} }
@ -618,16 +597,12 @@ void BmwPhevBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
if (rx_frame.data.u8[6] > 0 && rx_frame.data.u8[6] < 255) { if (rx_frame.data.u8[6] > 0 && rx_frame.data.u8[6] < 255) {
battery_temperature_min = (rx_frame.data.u8[6] - 50); battery_temperature_min = (rx_frame.data.u8[6] - 50);
} else { } else {
#ifdef DEBUG_LOG
logging.println("Pre parsed Cell Temp Min is Invalid "); logging.println("Pre parsed Cell Temp Min is Invalid ");
#endif
} }
if (rx_frame.data.u8[7] > 0 && rx_frame.data.u8[7] < 255) { if (rx_frame.data.u8[7] > 0 && rx_frame.data.u8[7] < 255) {
battery_temperature_max = (rx_frame.data.u8[7] - 50); battery_temperature_max = (rx_frame.data.u8[7] - 50);
} else { } else {
#ifdef DEBUG_LOG
logging.println("Pre parsed Cell Temp Max is Invalid "); logging.println("Pre parsed Cell Temp Max is Invalid ");
#endif
} }
break; break;
@ -698,7 +673,7 @@ void BmwPhevBattery::transmit_can(unsigned long currentMillis) {
} }
void BmwPhevBattery::setup(void) { // Performs one time setup at startup void BmwPhevBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
//Wakeup the SME //Wakeup the SME
wake_battery_via_canbus(); wake_battery_via_canbus();

View file

@ -3,10 +3,6 @@
#include "BMW-PHEV-HTML.h" #include "BMW-PHEV-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef BMW_PHEV_BATTERY
#define SELECTED_BATTERY_CLASS BmwPhevBattery
#endif
class BmwPhevBattery : public CanBattery { class BmwPhevBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -14,6 +10,8 @@ class BmwPhevBattery : public CanBattery {
virtual void update_values(); virtual void update_values();
virtual void transmit_can(unsigned long currentMillis); virtual void transmit_can(unsigned long currentMillis);
static constexpr const char* Name = "BMW PHEV Battery";
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private: private:

View file

@ -2,6 +2,7 @@
#include <Arduino.h> #include <Arduino.h>
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/logging.h"
uint8_t reverse_bits(uint8_t byte) { uint8_t reverse_bits(uint8_t byte) {
uint8_t reversed = 0; uint8_t reversed = 0;
@ -122,9 +123,7 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
SBOX_100.data.u8[0] = 0x86; // Precharge relay only SBOX_100.data.u8[0] = 0x86; // Precharge relay only
prechargeStartTime = currentMillis; prechargeStartTime = currentMillis;
contactorStatus = NEGATIVE; contactorStatus = NEGATIVE;
#ifdef DEBUG_VIA_USB logging.println("S-BOX Precharge relay engaged");
Serial.println("S-BOX Precharge relay engaged");
#endif
break; break;
case NEGATIVE: case NEGATIVE:
if (currentMillis - prechargeStartTime >= CONTACTOR_CONTROL_T1) { if (currentMillis - prechargeStartTime >= CONTACTOR_CONTROL_T1) {
@ -132,9 +131,7 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
negativeStartTime = currentMillis; negativeStartTime = currentMillis;
contactorStatus = POSITIVE; contactorStatus = POSITIVE;
datalayer.shunt.precharging = true; datalayer.shunt.precharging = true;
#ifdef DEBUG_VIA_USB logging.println("S-BOX Negative relay engaged");
Serial.println("S-BOX Negative relay engaged");
#endif
} }
break; break;
case POSITIVE: case POSITIVE:
@ -145,18 +142,14 @@ void BmwSbox::transmit_can(unsigned long currentMillis) {
positiveStartTime = currentMillis; positiveStartTime = currentMillis;
contactorStatus = PRECHARGE_OFF; contactorStatus = PRECHARGE_OFF;
datalayer.shunt.precharging = false; datalayer.shunt.precharging = false;
#ifdef DEBUG_VIA_USB logging.println("S-BOX Positive relay engaged");
Serial.println("S-BOX Positive relay engaged");
#endif
} }
break; break;
case PRECHARGE_OFF: case PRECHARGE_OFF:
if (currentMillis - positiveStartTime >= CONTACTOR_CONTROL_T3) { if (currentMillis - positiveStartTime >= CONTACTOR_CONTROL_T3) {
SBOX_100.data.u8[0] = 0x6A; // Negative + Positive SBOX_100.data.u8[0] = 0x6A; // Negative + Positive
contactorStatus = COMPLETED; contactorStatus = COMPLETED;
#ifdef DEBUG_VIA_USB logging.println("S-BOX Precharge relay released");
Serial.println("S-BOX Precharge relay released");
#endif
datalayer.shunt.contactors_engaged = true; datalayer.shunt.contactors_engaged = true;
} }
break; break;

View file

@ -1,4 +1,5 @@
#include "BOLT-AMPERA-BATTERY.h" #include "BOLT-AMPERA-BATTERY.h"
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
@ -95,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;

View file

@ -3,10 +3,6 @@
#include "BOLT-AMPERA-HTML.h" #include "BOLT-AMPERA-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef BOLT_AMPERA_BATTERY
#define SELECTED_BATTERY_CLASS BoltAmperaBattery
#endif
class BoltAmperaBattery : public CanBattery { class BoltAmperaBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -20,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%

View file

@ -1,15 +1,10 @@
#include "BYD-ATTO-3-BATTERY.h" #include "BYD-ATTO-3-BATTERY.h"
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
/* Notes
SOC% by default is now ESTIMATED.
If you have a crash-locked pack, See the Wiki for more info on how to attempt an unlock
After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the BYD-ATTO-3-BATTERY.h file
*/
#define POLL_FOR_BATTERY_VOLTAGE 0x0008 #define POLL_FOR_BATTERY_VOLTAGE 0x0008
#define POLL_FOR_BATTERY_CURRENT 0x0009 #define POLL_FOR_BATTERY_CURRENT 0x0009
#define POLL_FOR_LOWEST_TEMP_CELL 0x002f #define POLL_FOR_LOWEST_TEMP_CELL 0x002f
@ -142,6 +137,16 @@ uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation fu
return 0; // Default return for safety, should never reach here return 0; // Default return for safety, should never reach here
} }
uint8_t compute441Checksum(const uint8_t* u8) // Computes the 441 checksum byte
{
int sum = 0;
for (int i = 0; i < 7; ++i) {
sum += u8[i];
}
uint8_t lsb = static_cast<uint8_t>(sum & 0xFF);
return static_cast<uint8_t>(~lsb & 0xFF);
}
void BydAttoBattery:: void BydAttoBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
@ -389,6 +394,7 @@ void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0]; battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7 //battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
BMS_voltage_available = true;
break; break;
case 0x445: case 0x445:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -538,10 +544,17 @@ void BydAttoBattery::transmit_can(unsigned long currentMillis) {
} }
if (counter_100ms > 3) { if (counter_100ms > 3) {
ATTO_3_441.data.u8[4] = 0x9D; if (BMS_voltage_available) { // Transmit battery voltage back to BMS when confirmed it's available, this closes the contactors
ATTO_3_441.data.u8[5] = 0x01; ATTO_3_441.data.u8[4] = (uint8_t)(battery_voltage - 1);
ATTO_3_441.data.u8[5] = ((battery_voltage - 1) >> 8);
ATTO_3_441.data.u8[6] = 0xFF; ATTO_3_441.data.u8[6] = 0xFF;
ATTO_3_441.data.u8[7] = 0xF5; ATTO_3_441.data.u8[7] = compute441Checksum(ATTO_3_441.data.u8);
} else {
ATTO_3_441.data.u8[4] = 0x0C;
ATTO_3_441.data.u8[5] = 0x00;
ATTO_3_441.data.u8[6] = 0xFF;
ATTO_3_441.data.u8[7] = 0x87;
}
} }
transmit_can_frame(&ATTO_3_441); transmit_can_frame(&ATTO_3_441);

View file

@ -7,8 +7,12 @@
#include "BYD-ATTO-3-HTML.h" #include "BYD-ATTO-3-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \ /* Notes
// Comment out this only if you know your BMS is unlocked and able to send SOC% SOC% by default is now MEASURED by BMS.
If you have a crash-locked pack, See the Wiki for more info on how to attempt an unlock.
Remove the comment from "USE_ESTIMATED_SOC" below if you still decide to use a locked battery and want to use estimated SOC.
*/
//#define USE_ESTIMATED_SOC
//Uncomment and configure this line, if you want to filter out a broken temperature sensor (1-10) //Uncomment and configure this line, if you want to filter out a broken temperature sensor (1-10)
//Make sure you understand risks associated with disabling. Values can be read via "More Battery info" //Make sure you understand risks associated with disabling. Values can be read via "More Battery info"
@ -20,9 +24,6 @@ static const int RAMPDOWN_POWER_ALLOWED =
10000; // Power to start ramp down from, set a lower value to limit the power even further as SOC decreases 10000; // Power to start ramp down from, set a lower value to limit the power even further as SOC decreases
/* Do not modify the rows below */ /* Do not modify the rows below */
#ifdef BYD_ATTO_3_BATTERY
#define SELECTED_BATTERY_CLASS BydAttoBattery
#endif
class BydAttoBattery : public CanBattery { class BydAttoBattery : public CanBattery {
public: public:
@ -94,6 +95,7 @@ class BydAttoBattery : public CanBattery {
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
bool SOC_method = false; bool SOC_method = false;
bool BMS_voltage_available = false;
uint8_t counter_50ms = 0; uint8_t counter_50ms = 0;
uint8_t counter_100ms = 0; uint8_t counter_100ms = 0;
uint8_t frame6_counter = 0xB; uint8_t frame6_counter = 0xB;

View file

@ -45,6 +45,9 @@ enum class BatteryType {
MgHsPhev = 37, MgHsPhev = 37,
SamsungSdiLv = 38, SamsungSdiLv = 38,
HyundaiIoniq28 = 39, HyundaiIoniq28 = 39,
Kia64FD = 40,
RelionBattery = 41,
RivianBattery = 42,
Highest Highest
}; };
@ -73,6 +76,7 @@ class Battery {
virtual bool supports_clear_isolation() { return false; } virtual bool supports_clear_isolation() { return false; }
virtual bool supports_reset_BMS() { return false; } virtual bool supports_reset_BMS() { return false; }
virtual bool supports_reset_SOC() { return false; }
virtual bool supports_reset_crash() { return false; } virtual bool supports_reset_crash() { return false; }
virtual bool supports_reset_NVROL() { return false; } virtual bool supports_reset_NVROL() { return false; }
virtual bool supports_reset_DTC() { return false; } virtual bool supports_reset_DTC() { return false; }
@ -91,6 +95,7 @@ class Battery {
virtual void clear_isolation() {} virtual void clear_isolation() {}
virtual void reset_BMS() {} virtual void reset_BMS() {}
virtual void reset_SOC() {}
virtual void reset_crash() {} virtual void reset_crash() {}
virtual void reset_contactor() {} virtual void reset_contactor() {}
virtual void reset_NVROL() {} virtual void reset_NVROL() {}

View file

@ -1,4 +1,5 @@
#include "CELLPOWER-BMS.h" #include "CELLPOWER-BMS.h"
#include "../battery/BATTERIES.h"
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage #include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
@ -19,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);
@ -231,8 +234,8 @@ void CellPowerBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
} }

View file

@ -3,10 +3,6 @@
#include "CELLPOWER-HTML.h" #include "CELLPOWER-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef CELLPOWER_BMS
#define SELECTED_BATTERY_CLASS CellPowerBms
#endif
class CellPowerBms : public CanBattery { class CellPowerBms : public CanBattery {
public: public:
CellPowerBms() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {} CellPowerBms() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {}
@ -22,11 +18,6 @@ class CellPowerBms : public CanBattery {
private: private:
CellpowerHtmlRenderer renderer; CellpowerHtmlRenderer renderer;
/* Tweak these according to your battery build */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent

View file

@ -116,15 +116,13 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest; x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage; x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
#ifdef DEBUG_LOG
//Note on p131 //Note on p131
uint8_t chargingrate = 0; uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) { if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100; chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
logging.print("Charge Rate (kW): "); logging.printf("Charge Rate (kW): ");
logging.println(chargingrate); logging.println(chargingrate);
} }
#endif
//Table A.26—Charge control termination command patterns -- should echo x108 handling //Table A.26—Charge control termination command patterns -- should echo x108 handling
@ -136,41 +134,31 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
*/ */
if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) || if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) ||
(x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) { (x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) {
#ifdef DEBUG_LOG
logging.println("Inconsistent charge/discharge state."); logging.println("Inconsistent charge/discharge state.");
#endif
CHADEMO_Status = CHADEMO_FAULT; CHADEMO_Status = CHADEMO_FAULT;
return; return;
} }
if (x102_chg_session.f.fault.FaultBatteryOverVoltage) { if (x102_chg_session.f.fault.FaultBatteryOverVoltage) {
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery over voltage."); logging.println("Vehicle indicates fault, battery over voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
return; return;
} }
if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) { if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) {
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery under voltage."); logging.println("Vehicle indicates fault, battery under voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
return; return;
} }
if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) { if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) {
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?"); logging.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
return; return;
} }
if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) { if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) {
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?"); logging.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
return; return;
} }
@ -183,18 +171,14 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
//FIXME condition nesting or more stanzas needed here for clear determination of cessation reason //FIXME condition nesting or more stanzas needed here for clear determination of cessation reason
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) { if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) {
#ifdef DEBUG_LOG
logging.println("State of charge ceiling reached or charging interrupted, stop charging"); logging.println("State of charge ceiling reached or charging interrupted, stop charging");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
return; return;
} }
if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) { if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) {
CHADEMO_Status = CHADEMO_EV_ALLOWED; CHADEMO_Status = CHADEMO_EV_ALLOWED;
#ifdef DEBUG_LOG
logging.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()"); logging.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
#endif
return; return;
} }
@ -203,23 +187,17 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
// consider relocating // consider relocating
if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 && if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 &&
newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) { newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) {
#ifdef DEBUG_LOG
logging.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()"); logging.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
#endif
CHADEMO_Status = CHADEMO_EVSE_START; CHADEMO_Status = CHADEMO_EVSE_START;
return; return;
} }
if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) { if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) {
#ifdef DEBUG_LOG
logging.println("updating vehicle request in process_vehicle_charging_session()"); logging.println("updating vehicle request in process_vehicle_charging_session()");
#endif
return; return;
} }
#ifdef DEBUG_LOG
logging.println("UNHANDLED CHADEMO STATE, try unplugging chademo cable, reboot emulator, and retry!"); logging.println("UNHANDLED CHADEMO STATE, try unplugging chademo cable, reboot emulator, and retry!");
#endif
return; return;
} }
@ -231,8 +209,7 @@ void ChademoBattery::process_vehicle_charging_limits(CAN_frame rx_frame) {
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6]; x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7]; x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
#ifdef DEBUG_LOG /* unsigned long currentMillis = millis();
/* unsigned long currentMillis = millis();
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis; previousMillis5000 = currentMillis;
logging.println("x200 Max remaining capacity for charging/discharging:"); logging.println("x200 Max remaining capacity for charging/discharging:");
@ -240,16 +217,13 @@ void ChademoBattery::process_vehicle_charging_limits(CAN_frame rx_frame) {
logging.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging); logging.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
} }
*/ */
#endif
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) { if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
#ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping."); logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
logging.print("Measured: "); logging.printf("Measured: ");
logging.print(get_measured_voltage()); logging.print(get_measured_voltage());
logging.print("Minimum voltage: "); logging.printf("Minimum voltage: ");
logging.print(x200_discharge_limits.MinimumDischargeVoltage); logging.print(x200_discharge_limits.MinimumDischargeVoltage);
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
} }
} }
@ -264,15 +238,13 @@ void ChademoBattery::process_vehicle_discharge_estimate(CAN_frame rx_frame) {
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]); x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]);
#ifdef DEBUG_LOG
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis; previousMillis5000 = currentMillis;
logging.print("x201 availabile vehicle energy, completion time: "); logging.printf("x201 availabile vehicle energy, completion time: ");
logging.println(x201_discharge_estimate.AvailableVehicleEnergy); logging.println(x201_discharge_estimate.AvailableVehicleEnergy);
logging.print("x201 approx vehicle completion time: "); logging.printf("x201 approx vehicle completion time: ");
logging.println(x201_discharge_estimate.ApproxDischargeCompletionTime); logging.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
} }
#endif
} }
void ChademoBattery::process_vehicle_dynamic_control(CAN_frame rx_frame) { void ChademoBattery::process_vehicle_dynamic_control(CAN_frame rx_frame) {
@ -615,10 +587,8 @@ void ChademoBattery::transmit_can(unsigned long currentMillis) {
// TODO need an update_evse_dynamic_control(..) function above before we send 118 // TODO need an update_evse_dynamic_control(..) function above before we send 118
// 110.0.0 // 110.0.0
if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles? if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles?
#ifdef DEBUG_LOG
//FIXME REMOVE //FIXME REMOVE
logging.println("REMOVE: proto 2.0"); logging.println("REMOVE: proto 2.0");
#endif
transmit_can_frame(&CHADEMO_118); transmit_can_frame(&CHADEMO_118);
} }
} }
@ -656,16 +626,12 @@ void ChademoBattery::handle_chademo_sequence() {
/* ------------------- State override conditions checks ------------------- */ /* ------------------- State override conditions checks ------------------- */
/* ------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------ */
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) { if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) {
#ifdef DEBUG_LOG
logging.println("Vehicle is not parked, abort."); logging.println("Vehicle is not parked, abort.");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
} }
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) { if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) {
#ifdef DEBUG_LOG
logging.println("Vehicle charge/discharge permission ended, stop."); logging.println("Vehicle charge/discharge permission ended, stop.");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
} }
@ -678,25 +644,19 @@ void ChademoBattery::handle_chademo_sequence() {
plug_inserted = digitalRead(pin7); plug_inserted = digitalRead(pin7);
if (!plug_inserted) { if (!plug_inserted) {
#ifdef DEBUG_LOG // Commented unless needed for debug
// Commented unless needed for debug // logging.println("CHADEMO plug is not inserted.");
// logging.println("CHADEMO plug is not inserted.");
#endif
return; return;
} }
CHADEMO_Status = CHADEMO_CONNECTED; CHADEMO_Status = CHADEMO_CONNECTED;
#ifdef DEBUG_LOG
logging.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization."); logging.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
#endif
break; break;
case CHADEMO_CONNECTED: case CHADEMO_CONNECTED:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
//logging.println("CHADEMO_CONNECTED State"); //logging.println("CHADEMO_CONNECTED State");
#endif
/* plug_inserted is .. essentially a volatile of sorts, so verify */ /* plug_inserted is .. essentially a volatile of sorts, so verify */
if (plug_inserted) { if (plug_inserted) {
/* If connection is detectable, jumpstart handshake by /* If connection is detectable, jumpstart handshake by
@ -729,17 +689,13 @@ void ChademoBattery::handle_chademo_sequence() {
/* Vehicle and EVSE dance */ /* Vehicle and EVSE dance */
//TODO if pin 4 / j goes high, //TODO if pin 4 / j goes high,
#ifdef DEBUG_LOG // Commented unless needed for debug
// Commented unless needed for debug // logging.println("CHADEMO_NEGOTIATE State");
// logging.println("CHADEMO_NEGOTIATE State");
#endif
x109_evse_state.s.status.ChgDischStopControl = 1; x109_evse_state.s.status.ChgDischStopControl = 1;
break; break;
case CHADEMO_EV_ALLOWED: case CHADEMO_EV_ALLOWED:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_EV_ALLOWED State"); logging.println("CHADEMO_EV_ALLOWED State");
#endif
// If we are in this state, vehicle_permission was already set to true...but re-verify // If we are in this state, vehicle_permission was already set to true...but re-verify
// that pin 4 (j) reads high // that pin 4 (j) reads high
if (vehicle_permission) { if (vehicle_permission) {
@ -754,10 +710,8 @@ void ChademoBattery::handle_chademo_sequence() {
} }
break; break;
case CHADEMO_EVSE_PREPARE: case CHADEMO_EVSE_PREPARE:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_EVSE_PREPARE State"); logging.println("CHADEMO_EVSE_PREPARE State");
#endif
/* TODO voltage check of output < 20v /* TODO voltage check of output < 20v
* insulation test hypothetically happens here before triggering PIN 10 high * insulation test hypothetically happens here before triggering PIN 10 high
* see Table A.28Requirements for the insulation test for output DC circuit * see Table A.28Requirements for the insulation test for output DC circuit
@ -790,19 +744,15 @@ void ChademoBattery::handle_chademo_sequence() {
//state changes to CHADEMO_EVSE_START only upon receipt of charging session request //state changes to CHADEMO_EVSE_START only upon receipt of charging session request
break; break;
case CHADEMO_EVSE_START: case CHADEMO_EVSE_START:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_EVSE_START State"); logging.println("CHADEMO_EVSE_START State");
#endif
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
x109_evse_state.s.status.ChgDischStopControl = 1; x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0; x109_evse_state.s.status.EVSE_status = 0;
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED; CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
#ifdef DEBUG_LOG
logging.println("Initiating contactors"); logging.println("Initiating contactors");
#endif
/* break rather than fall through because contactors are not instantaneous; /* break rather than fall through because contactors are not instantaneous;
* worth giving it a cycle to finish * worth giving it a cycle to finish
@ -810,18 +760,14 @@ void ChademoBattery::handle_chademo_sequence() {
break; break;
case CHADEMO_EVSE_CONTACTORS_ENABLED: case CHADEMO_EVSE_CONTACTORS_ENABLED:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_EVSE_CONTACTORS State"); logging.println("CHADEMO_EVSE_CONTACTORS State");
#endif
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */ /* check whether contactors ready, because externally dependent upon inverter allow during discharge */
if (contactors_ready) { if (contactors_ready) {
#ifdef DEBUG_LOG
logging.println("Contactors ready"); logging.println("Contactors ready");
logging.print("Voltage: "); logging.printf("Voltage: ");
logging.println(get_measured_voltage()); logging.println(get_measured_voltage());
#endif
/* transition to POWERFLOW state if discharge compatible on both sides */ /* transition to POWERFLOW state if discharge compatible on both sides */
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible && if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
(EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL)) { (EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL)) {
@ -840,10 +786,8 @@ void ChademoBattery::handle_chademo_sequence() {
/* break or fall through ? TODO */ /* break or fall through ? TODO */
break; break;
case CHADEMO_POWERFLOW: case CHADEMO_POWERFLOW:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_POWERFLOW State"); logging.println("CHADEMO_POWERFLOW State");
#endif
/* POWERFLOW for charging, discharging, and bidirectional */ /* POWERFLOW for charging, discharging, and bidirectional */
/* Interpretation */ /* Interpretation */
if (x102_chg_session.s.status.StatusVehicleShifterPosition) { if (x102_chg_session.s.status.StatusVehicleShifterPosition) {
@ -860,9 +804,7 @@ void ChademoBattery::handle_chademo_sequence() {
} }
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage) { if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage) {
#ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping."); logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
} }
@ -871,10 +813,8 @@ void ChademoBattery::handle_chademo_sequence() {
x109_evse_state.s.status.EVSE_status = 1; x109_evse_state.s.status.EVSE_status = 1;
break; break;
case CHADEMO_STOP: case CHADEMO_STOP:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_STOP State"); logging.println("CHADEMO_STOP State");
#endif
/* back to CHADEMO_IDLE after teardown */ /* back to CHADEMO_IDLE after teardown */
x109_evse_state.s.status.ChgDischStopControl = 1; x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0; x109_evse_state.s.status.EVSE_status = 0;
@ -899,17 +839,13 @@ void ChademoBattery::handle_chademo_sequence() {
break; break;
case CHADEMO_FAULT: case CHADEMO_FAULT:
#ifdef DEBUG_LOG
// Commented unless needed for debug // Commented unless needed for debug
logging.println("CHADEMO_FAULT State"); logging.println("CHADEMO_FAULT State");
#endif
/* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */ /* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */
x109_evse_state.s.status.EVSE_error = 1; x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.ChgDischError = 1; x109_evse_state.s.status.ChgDischError = 1;
x109_evse_state.s.status.ChgDischStopControl = 1; x109_evse_state.s.status.ChgDischStopControl = 1;
#ifdef DEBUG_LOG
logging.println("CHADEMO fault encountered, tearing down to make safe"); logging.println("CHADEMO fault encountered, tearing down to make safe");
#endif
digitalWrite(pin10, LOW); digitalWrite(pin10, LOW);
digitalWrite(pin2, LOW); digitalWrite(pin2, LOW);
evse_permission = false; evse_permission = false;
@ -919,9 +855,7 @@ void ChademoBattery::handle_chademo_sequence() {
break; break;
default: default:
#ifdef DEBUG_LOG
logging.println("UNHANDLED CHADEMO_STATE, setting FAULT"); logging.println("UNHANDLED CHADEMO_STATE, setting FAULT");
#endif
CHADEMO_Status = CHADEMO_FAULT; CHADEMO_Status = CHADEMO_FAULT;
break; break;
} }

View file

@ -7,13 +7,6 @@
#include "CHADEMO-BATTERY-HTML.h" #include "CHADEMO-BATTERY-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef CHADEMO_BATTERY
#define SELECTED_BATTERY_CLASS ChademoBattery
//Contactor control is required for CHADEMO support
#define CONTACTOR_CONTROL
#endif
class ChademoBattery : public CanBattery { class ChademoBattery : public CanBattery {
public: public:
ChademoBattery() { ChademoBattery() {

View file

@ -22,7 +22,6 @@
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "CHADEMO-BATTERY.h" #include "CHADEMO-BATTERY.h"
#include "src/devboard/utils/logging.h"
/* Initial frames received from ISA shunts provide invalid during initialization */ /* Initial frames received from ISA shunts provide invalid during initialization */
static int framecount = 0; static int framecount = 0;
@ -87,17 +86,6 @@ void ISA_handleFrame(CAN_frame* frame) {
case 0x510: case 0x510:
case 0x511: case 0x511:
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(frame->ID, HEX);
logging.print(" ");
logging.print(frame->DLC);
logging.print(" ");
for (int i = 0; i < frame->DLC; ++i) {
logging.print(frame->data.u8[i], HEX);
logging.print(" ");
}
logging.println("");
break; break;
case 0x521: case 0x521:
@ -245,7 +233,7 @@ void ISA_initialize() {
ISA_STOP(); ISA_STOP();
delay(500); delay(500);
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
logging.print("ISA Initialization "); logging.printf("ISA Initialization ");
logging.println(i); logging.println(i);
outframe.data.u8[0] = (0x20 + i); outframe.data.u8[0] = (0x20 + i);
@ -382,7 +370,7 @@ void ISA_initCurrent() {
} }
void ISA_getCONFIG(uint8_t i) { void ISA_getCONFIG(uint8_t i) {
logging.print("ISA Get Config "); logging.printf("ISA Get Config ");
logging.println(i); logging.println(i);
outframe.data.u8[0] = (0x60 + i); outframe.data.u8[0] = (0x60 + i);
@ -398,7 +386,7 @@ void ISA_getCONFIG(uint8_t i) {
} }
void ISA_getCAN_ID(uint8_t i) { void ISA_getCAN_ID(uint8_t i) {
logging.print("ISA Get CAN ID "); logging.printf("ISA Get CAN ID ");
logging.println(i); logging.println(i);
outframe.data.u8[0] = (0x50 + i); outframe.data.u8[0] = (0x50 + i);
@ -418,8 +406,8 @@ void ISA_getCAN_ID(uint8_t i) {
} }
void ISA_getINFO(uint8_t i) { void ISA_getINFO(uint8_t i) {
logging.print("ISA Get INFO "); logging.printf("ISA Get INFO ");
logging.println(i, HEX); logging.println(i);
outframe.data.u8[0] = (0x70 + i); outframe.data.u8[0] = (0x70 + i);
outframe.data.u8[1] = 0x00; outframe.data.u8[1] = 0x00;

View file

@ -1,4 +1,5 @@
#include "CMFA-EV-BATTERY.h" #include "CMFA-EV-BATTERY.h"
#include <cstring> //unit tests memcpy
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"

View file

@ -4,10 +4,6 @@
#include "CMFA-EV-HTML.h" #include "CMFA-EV-HTML.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef CMFA_EV_BATTERY
#define SELECTED_BATTERY_CLASS CmfaEvBattery
#endif
class CmfaEvBattery : public CanBattery { class CmfaEvBattery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.

View file

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

View file

@ -3,7 +3,6 @@
#include "Battery.h" #include "Battery.h"
#include "../../USER_SETTINGS.h"
#include "../../src/communication/Transmitter.h" #include "../../src/communication/Transmitter.h"
#include "../../src/communication/can/CanReceiver.h" #include "../../src/communication/can/CanReceiver.h"
#include "../../src/communication/can/comm_can.h" #include "../../src/communication/can/comm_can.h"
@ -23,18 +22,15 @@ 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(CAN_frame* frame) { transmit_can_frame_to_interface(frame, can_interface); }
}; };
#endif #endif

View file

@ -1,6 +1,7 @@
#include "DALY-BMS.h" #include "DALY-BMS.h"
#include <Arduino.h> #include <Arduino.h>
#include <cstdint> #include <cstdint>
#include "../battery/BATTERIES.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/hal/hal.h" #include "../devboard/hal/hal.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
@ -14,8 +15,9 @@ static int16_t current_dA = 0;
static uint16_t voltage_dV = 0; static uint16_t voltage_dV = 0;
static uint32_t remaining_capacity_mAh = 0; static uint32_t remaining_capacity_mAh = 0;
static uint16_t cellvoltages_mV[48] = {0}; static uint16_t cellvoltages_mV[48] = {0};
static uint16_t cellvoltage_min_mV = 0; static uint16_t cellvoltage_min_mV = 3700;
static uint16_t cellvoltage_max_mV = 0; static uint16_t cellvoltage_max_mV = 0;
static uint16_t cell_count = 0;
static uint16_t SOC = 0; static uint16_t SOC = 0;
static bool has_fault = false; static bool has_fault = false;
@ -25,8 +27,9 @@ void DalyBms::update_values() {
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0)
datalayer.battery.status.remaining_capacity_Wh = (remaining_capacity_mAh * (uint32_t)voltage_dV) / 10000; datalayer.battery.status.remaining_capacity_Wh = (remaining_capacity_mAh * (uint32_t)voltage_dV) / 10000;
datalayer.battery.status.max_charge_power_W = (BATTERY_MAX_CHARGE_AMP * voltage_dV) / 100; datalayer.battery.status.max_charge_power_W = (datalayer.battery.settings.max_user_set_charge_dA * voltage_dV) / 100;
datalayer.battery.status.max_discharge_power_W = (BATTERY_MAX_DISCHARGE_AMP * voltage_dV) / 100; datalayer.battery.status.max_discharge_power_W =
(datalayer.battery.settings.max_user_set_discharge_dA * voltage_dV) / 100;
// limit power when SoC is low or high // limit power when SoC is low or high
uint32_t adaptive_power_limit = 999999; uint32_t adaptive_power_limit = 999999;
@ -54,6 +57,9 @@ void DalyBms::update_values() {
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV; datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV; datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
// Use the received value from the BMS, to avoid needing to configure it
datalayer.battery.info.number_of_cells = cell_count;
datalayer.battery.status.temperature_min_dC = temperature_min_dC; datalayer.battery.status.temperature_min_dC = temperature_min_dC;
datalayer.battery.status.temperature_max_dC = temperature_max_dC; datalayer.battery.status.temperature_max_dC = temperature_max_dC;
@ -63,12 +69,10 @@ void DalyBms::update_values() {
void DalyBms::setup(void) { // Performs one time setup at startup void DalyBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = CELL_COUNT; datalayer.battery.info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
auto rx_pin = esp32hal->RS485_RX_PIN(); auto rx_pin = esp32hal->RS485_RX_PIN();
@ -104,18 +108,16 @@ uint32_t decode_uint32be(uint8_t data[8], uint8_t offset) {
((uint32_t)data[offset + 3]); ((uint32_t)data[offset + 3]);
} }
#ifdef DEBUG_VIA_USB
void dump_buff(const char* msg, uint8_t* buff, uint8_t len) { void dump_buff(const char* msg, uint8_t* buff, uint8_t len) {
Serial.print("[DALY-BMS] "); logging.printf("[DALY-BMS] ");
Serial.print(msg); logging.printf(msg);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
Serial.print(buff[i] >> 4, HEX); logging.print(buff[i] >> 4, HEX);
Serial.print(buff[i] & 0xf, HEX); logging.print(buff[i] & 0xf, HEX);
Serial.print(" "); logging.printf(" ");
} }
Serial.println(); logging.println();
} }
#endif
void decode_packet(uint8_t command, uint8_t data[8]) { void decode_packet(uint8_t command, uint8_t data[8]) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -138,6 +140,7 @@ void decode_packet(uint8_t command, uint8_t data[8]) {
remaining_capacity_mAh = decode_uint32be(data, 4); remaining_capacity_mAh = decode_uint32be(data, 4);
break; break;
case 0x94: case 0x94:
cell_count = data[0];
break; break;
case 0x95: case 0x95:
if (data[0] > 0 && data[0] <= 16) { if (data[0] > 0 && data[0] <= 16) {
@ -175,9 +178,7 @@ void DalyBms::transmit_rs485(unsigned long currentMillis) {
tx_buff[2] = nextCommand; tx_buff[2] = nextCommand;
tx_buff[3] = 8; tx_buff[3] = 8;
tx_buff[12] = calculate_checksum(tx_buff); tx_buff[12] = calculate_checksum(tx_buff);
#ifdef DEBUG_VIA_USB
dump_buff("transmitting: ", tx_buff, 13); dump_buff("transmitting: ", tx_buff, 13);
#endif
Serial2.write(tx_buff, 13); Serial2.write(tx_buff, 13);
nextCommand++; nextCommand++;
if (nextCommand > 0x98) if (nextCommand > 0x98)
@ -197,17 +198,12 @@ void DalyBms::receive() {
if (recv_len > 0 && recv_buff[0] != 0xA5 || recv_len > 1 && recv_buff[1] != 0x01 || if (recv_len > 0 && recv_buff[0] != 0xA5 || recv_len > 1 && recv_buff[1] != 0x01 ||
recv_len > 2 && (recv_buff[2] < 0x90 || recv_buff[2] > 0x98) || recv_len > 3 && recv_buff[3] != 8 || recv_len > 2 && (recv_buff[2] < 0x90 || recv_buff[2] > 0x98) || recv_len > 3 && recv_buff[3] != 8 ||
recv_len > 12 && recv_buff[12] != calculate_checksum(recv_buff)) { recv_len > 12 && recv_buff[12] != calculate_checksum(recv_buff)) {
#ifdef DEBUG_VIA_USB
dump_buff("dropping partial rx: ", recv_buff, recv_len); dump_buff("dropping partial rx: ", recv_buff, recv_len);
#endif
recv_len = 0; recv_len = 0;
} }
if (recv_len > 12) { if (recv_len > 12) {
#ifdef DEBUG_VIA_USB
dump_buff("decoding successfull rx: ", recv_buff, recv_len); dump_buff("decoding successfull rx: ", recv_buff, recv_len);
#endif
decode_packet(recv_buff[2], &recv_buff[4]); decode_packet(recv_buff[2], &recv_buff[4]);
recv_len = 0; recv_len = 0;
lastPacket = millis(); lastPacket = millis();

View file

@ -3,10 +3,6 @@
#include "RS485Battery.h" #include "RS485Battery.h"
#ifdef DALY_BMS
#define SELECTED_BATTERY_CLASS DalyBms
#endif
class DalyBms : public RS485Battery { class DalyBms : public RS485Battery {
public: public:
void setup(); void setup();
@ -17,11 +13,6 @@ class DalyBms : public RS485Battery {
private: private:
/* Tweak these according to your battery build */ /* Tweak these according to your battery build */
static const int CELL_COUNT = 14;
static const int MAX_PACK_VOLTAGE_DV = 580; //580 = 58.0V
static const int MIN_PACK_VOLTAGE_DV = 460; //480 = 48.0V
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 = 3200; //Battery is put into emergency stop if one cell goes below this value
static const int POWER_PER_PERCENT = static const int POWER_PER_PERCENT =
50; // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...) 50; // below 20% and above 80% limit power to 50W * SOC (i.e. 150W at 3%, 500W at 10%, ...)
static const int POWER_PER_DEGREE_C = 60; // max power added/removed per degree above/below 0°C static const int POWER_PER_DEGREE_C = 60; // max power added/removed per degree above/below 0°C

View file

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

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "ECMP-HTML.h" #include "ECMP-HTML.h"
#ifdef STELLANTIS_ECMP_BATTERY
#define SELECTED_BATTERY_CLASS EcmpBattery
#endif
class EcmpBattery : public CanBattery { class EcmpBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -42,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;
@ -84,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;
@ -136,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
@ -220,8 +278,9 @@ class EcmpBattery : public CanBattery {
uint16_t incoming_poll = 0; uint16_t incoming_poll = 0;
CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; //VCU_BCM_Crash 100ms CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; //VCU_BCM_Crash 100ms
CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}}; static constexpr CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}};
CAN_frame ECMP_0A6 = {.FD = false, static constexpr CAN_frame ECMP_0A6 = {
.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 2, .DLC = 2,
.ID = 0x0A6, .ID = 0x0A6,
@ -236,13 +295,17 @@ class EcmpBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x0F2, .ID = 0x0F2,
.data = {0x7D, 0x00, 0x4E, 0x20, 0x00, 0x00, 0x60, 0x0D}}; .data = {0x7D, 0x00, 0x4E, 0x20, 0x00, 0x00, 0x60, 0x0D}};
CAN_frame ECMP_0AE = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x0AE, .data = {0x04, 0x77, 0x7A, 0x5E, 0xDF}}; static constexpr CAN_frame ECMP_0AE = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x0AE,
.data = {0x04, 0x77, 0x7A, 0x5E, 0xDF}};
CAN_frame ECMP_110 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator) CAN_frame ECMP_110 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, // NOTE. Changes on BMS state .ext_ID = false, // NOTE. Changes on BMS state
.DLC = 8, .DLC = 8,
.ID = 0x110, .ID = 0x110,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x87, 0x05}}; .data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x87, 0x05}};
CAN_frame ECMP_111 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_111 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 8, .DLC = 8,
.ID = 0x111, .ID = 0x111,
@ -252,12 +315,12 @@ class EcmpBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x112, .ID = 0x112,
.data = {0x4E, 0x20, 0x00, 0x0F, 0xA0, 0x7D, 0x00, 0x0A}}; .data = {0x4E, 0x20, 0x00, 0x0F, 0xA0, 0x7D, 0x00, 0x0A}};
CAN_frame ECMP_114 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_114 = {.FD = false, //??? 10ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 8, .DLC = 8,
.ID = 0x114, .ID = 0x114,
.data = {0x00, 0x00, 0x00, 0x7D, 0x07, 0xD0, 0x7D, 0x00}}; .data = {0x00, 0x00, 0x00, 0x7D, 0x07, 0xD0, 0x7D, 0x00}};
CAN_frame ECMP_0C5 = {.FD = false, //DC2_0C5 10ms periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_0C5 = {.FD = false, //DC2_0C5 10ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 8, .DLC = 8,
.ID = 0x0C5, .ID = 0x0C5,
@ -267,7 +330,7 @@ class EcmpBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x17B, .ID = 0x17B,
.data = {0x00, 0x00, 0x00, 0x7E, 0x78, 0x00, 0x00, 0x0F}}; // NOTE. Changes on BMS state .data = {0x00, 0x00, 0x00, 0x7E, 0x78, 0x00, 0x00, 0x0F}}; // NOTE. Changes on BMS state
CAN_frame ECMP_230 = {.FD = false, //OBC3_230 50ms periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_230 = {.FD = false, //OBC3_230 50ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 8, .DLC = 8,
.ID = 0x230, .ID = 0x230,
@ -308,12 +371,13 @@ class EcmpBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x372, .ID = 0x372,
.data = {0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // NOTE. Changes on BMS state .data = {0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // NOTE. Changes on BMS state
CAN_frame ECMP_37F = {.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_37F = {
.FD = false, //??? 100ms periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, // Seems to be a bunch of temperature measurements? Static for now .ext_ID = false, // Seems to be a bunch of temperature measurements? Static for now
.DLC = 8, .DLC = 8,
.ID = 0x37F, .ID = 0x37F,
.data = {0x45, 0x49, 0x51, 0x45, 0x45, 0x00, 0x45, 0x45}}; .data = {0x45, 0x49, 0x51, 0x45, 0x45, 0x00, 0x45, 0x45}};
CAN_frame ECMP_382 = { static constexpr CAN_frame ECMP_382 = {
//BSIInfo_382 (VCU) PSA specific 100ms periodic (Perfectly emulated in Battery-Emulator) //BSIInfo_382 (VCU) PSA specific 100ms periodic (Perfectly emulated in Battery-Emulator)
.FD = false, //Same content always, fully static .FD = false, //Same content always, fully static
.ext_ID = false, .ext_ID = false,
@ -335,7 +399,7 @@ class EcmpBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x3A3, .ID = 0x3A3,
.data = {0x4A, 0x4A, 0x40, 0x00, 0x00, 0x08, 0x00, 0x0F}}; .data = {0x4A, 0x4A, 0x40, 0x00, 0x00, 0x08, 0x00, 0x0F}};
CAN_frame ECMP_439 = {.FD = false, //OBC4 1s periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_439 = {.FD = false, //OBC4 1s periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 8, .DLC = 8,
.ID = 0x439, .ID = 0x439,
@ -350,17 +414,17 @@ class EcmpBattery : public CanBattery {
.DLC = 8, //552 seems to be tracking time in byte 0-3 .DLC = 8, //552 seems to be tracking time in byte 0-3
.ID = 0x552, // distance in km in byte 4-6, temporal reset counter in byte 7 .ID = 0x552, // distance in km in byte 4-6, temporal reset counter in byte 7
.data = {0x00, 0x02, 0x95, 0x6D, 0x00, 0xD7, 0xB5, 0xFE}}; .data = {0x00, 0x02, 0x95, 0x6D, 0x00, 0xD7, 0xB5, 0xFE}};
CAN_frame ECMP_55F = {.FD = false, //5s periodic (Perfectly emulated in Battery-Emulator) static constexpr CAN_frame ECMP_55F = {.FD = false, //5s periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 1, .DLC = 1,
.ID = 0x55F, .ID = 0x55F,
.data = {0x82}}; .data = {0x82}};
CAN_frame ECMP_591 = {.FD = false, //1s periodic static constexpr CAN_frame ECMP_591 = {.FD = false, //1s periodic
.ext_ID = false, //Always static in HV mode .ext_ID = false, //Always static in HV mode
.DLC = 8, .DLC = 8,
.ID = 0x591, .ID = 0x591,
.data = {0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; .data = {0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame ECMP_786 = {.FD = false, //1s periodic static constexpr CAN_frame ECMP_786 = {.FD = false, //1s periodic
.ext_ID = false, //Always static in HV mode .ext_ID = false, //Always static in HV mode
.DLC = 8, .DLC = 8,
.ID = 0x786, .ID = 0x786,
@ -371,55 +435,67 @@ class EcmpBattery : public CanBattery {
.ID = 0x794, .ID = 0x794,
.data = {0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // NOTE. Changes on BMS state .data = {0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; // NOTE. Changes on BMS state
CAN_frame ECMP_POLL = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6B4, .data = {0x03, 0x22, 0xD8, 0x66}}; CAN_frame ECMP_POLL = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6B4, .data = {0x03, 0x22, 0xD8, 0x66}};
CAN_frame ECMP_ACK = {.FD = false, //Ack frame static constexpr CAN_frame ECMP_ACK = {.FD = false, //Ack frame
.ext_ID = false, .ext_ID = false,
.DLC = 3, .DLC = 3,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x30, 0x00, 0x00}}; .data = {0x30, 0x00, 0x00}};
CAN_frame ECMP_DIAG_START = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x6B4, .data = {0x02, 0x10, 0x03}}; static constexpr CAN_frame ECMP_DIAG_START = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x6B4,
.data = {0x02, 0x10, 0x03}};
//Start diagnostic session (extended diagnostic session, mode 0x10 with sub-mode 0x03) //Start diagnostic session (extended diagnostic session, mode 0x10 with sub-mode 0x03)
CAN_frame ECMP_CONTACTOR_RESET_START = {.FD = false, static constexpr CAN_frame ECMP_CONTACTOR_RESET_START = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x01, 0xDD, 0x35}}; .data = {0x04, 0x31, 0x01, 0xDD, 0x35}};
CAN_frame ECMP_CONTACTOR_RESET_PROGRESS = {.FD = false, static constexpr CAN_frame ECMP_CONTACTOR_RESET_PROGRESS = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x03, 0xDD, 0x35}}; .data = {0x04, 0x31, 0x03, 0xDD, 0x35}};
CAN_frame ECMP_COLLISION_RESET_START = {.FD = false, static constexpr CAN_frame ECMP_COLLISION_RESET_START = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x01, 0xDF, 0x60}}; .data = {0x04, 0x31, 0x01, 0xDF, 0x60}};
CAN_frame ECMP_COLLISION_RESET_PROGRESS = {.FD = false, static constexpr CAN_frame ECMP_COLLISION_RESET_PROGRESS = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x03, 0xDF, 0x60}}; .data = {0x04, 0x31, 0x03, 0xDF, 0x60}};
CAN_frame ECMP_ISOLATION_RESET_START = {.FD = false, static constexpr CAN_frame ECMP_ISOLATION_RESET_START = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x01, 0xDF, 0x46}}; .data = {0x04, 0x31, 0x01, 0xDF, 0x46}};
CAN_frame ECMP_ISOLATION_RESET_PROGRESS = {.FD = false, static constexpr CAN_frame ECMP_ISOLATION_RESET_PROGRESS = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x03, 0xDF, 0x46}}; .data = {0x04, 0x31, 0x03, 0xDF, 0x46}};
CAN_frame ECMP_RESET_DONE = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x6B4, .data = {0x02, 0x3E, 0x00}}; static constexpr CAN_frame ECMP_RESET_DONE = {.FD = false,
CAN_frame ECMP_FACTORY_MODE_ACTIVATION = {.FD = false, .ext_ID = false,
.DLC = 3,
.ID = 0x6B4,
.data = {0x02, 0x3E, 0x00}};
static constexpr CAN_frame ECMP_FACTORY_MODE_ACTIVATION = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x2E, 0xD9, 0x00, 0x01}}; .data = {0x04, 0x2E, 0xD9, 0x00, 0x01}};
CAN_frame ECMP_DISABLE_ISOLATION_REQ = {.FD = false, static constexpr CAN_frame ECMP_DISABLE_ISOLATION_REQ = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x02, 0xDF, 0xE1}}; .data = {0x04, 0x31, 0x02, 0xDF, 0xE1}};
CAN_frame ECMP_ACK_MESSAGE = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x6B4, .data = {0x02, 0x3E, 0x00}}; static constexpr CAN_frame ECMP_ACK_MESSAGE = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x6B4,
.data = {0x02, 0x3E, 0x00}};
uint8_t data_010_CRC[8] = {0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0xF0, 0xD2}; uint8_t data_010_CRC[8] = {0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0xF0, 0xD2};
uint8_t data_3A2_CRC[16] = {0x0C, 0x1B, 0x2A, 0x39, 0x48, 0x57, uint8_t data_3A2_CRC[16] = {0x0C, 0x1B, 0x2A, 0x39, 0x48, 0x57,
0x66, 0x75, 0x84, 0x93, 0xA2, 0xB1}; // NOTE. Changes on BMS state 0x66, 0x75, 0x84, 0x93, 0xA2, 0xB1}; // NOTE. Changes on BMS state

View file

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

View file

@ -1,4 +1,5 @@
#include "FOXESS-BATTERY.h" #include "FOXESS-BATTERY.h"
#include <cstring> //For unit test
#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"

View file

@ -2,10 +2,6 @@
#define FOXESS_BATTERY_H #define FOXESS_BATTERY_H
#include "CanBattery.h" #include "CanBattery.h"
#ifdef FOXESS_BATTERY
#define SELECTED_BATTERY_CLASS FoxessBattery
#endif
class FoxessBattery : public CanBattery { class FoxessBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,7 +1,8 @@
#include "GEELY-GEOMETRY-C-BATTERY.h" #include "GEELY-GEOMETRY-C-BATTERY.h"
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
/* TODO /* TODO

View file

@ -1,16 +1,25 @@
#ifndef GEELY_GEOMETRY_C_BATTERY_H #ifndef GEELY_GEOMETRY_C_BATTERY_H
#define GEELY_GEOMETRY_C_BATTERY_H #define GEELY_GEOMETRY_C_BATTERY_H
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "CanBattery.h" #include "CanBattery.h"
#include "GEELY-GEOMETRY-C-HTML.h" #include "GEELY-GEOMETRY-C-HTML.h"
#ifdef GEELY_GEOMETRY_C_BATTERY
#define SELECTED_BATTERY_CLASS GeelyGeometryCBattery
#endif
class GeelyGeometryCBattery : public CanBattery { class GeelyGeometryCBattery : public CanBattery {
public: public:
// Use this constructor for the second battery.
GeelyGeometryCBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_GEELY_GEOMETRY_C* extended,
CAN_Interface targetCan)
: CanBattery(targetCan) {
datalayer_battery = datalayer_ptr;
battery_voltage = 0;
}
// Use the default constructor to create the first or single battery.
GeelyGeometryCBattery() {
datalayer_battery = &datalayer.battery;
datalayer_geometryc = &datalayer_extended.geometryC;
}
virtual void setup(void); virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values(); virtual void update_values();
@ -20,6 +29,11 @@ class GeelyGeometryCBattery : public CanBattery {
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private: private:
GeelyGeometryCHtmlRenderer renderer;
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_GEELY_GEOMETRY_C* datalayer_geometryc;
static const int POLL_SOC = 0x4B35; static const int POLL_SOC = 0x4B35;
static const int POLL_CC2_VOLTAGE = 0x4BCF; static const int POLL_CC2_VOLTAGE = 0x4BCF;
static const int POLL_CELL_MAX_VOLTAGE_NUMBER = 0x4B1E; static const int POLL_CELL_MAX_VOLTAGE_NUMBER = 0x4B1E;
@ -41,8 +55,6 @@ class GeelyGeometryCBattery : public CanBattery {
static const int POLL_MULTI_HARDWARE_VERSION = 0x4B6B; static const int POLL_MULTI_HARDWARE_VERSION = 0x4B6B;
static const int POLL_MULTI_SOFTWARE_VERSION = 0x4B6C; static const int POLL_MULTI_SOFTWARE_VERSION = 0x4B6C;
GeelyGeometryCHtmlRenderer renderer;
static const int MAX_PACK_VOLTAGE_70_DV = 4420; //70kWh static const int MAX_PACK_VOLTAGE_70_DV = 4420; //70kWh
static const int MIN_PACK_VOLTAGE_70_DV = 2860; static const int MIN_PACK_VOLTAGE_70_DV = 2860;
static const int MAX_PACK_VOLTAGE_53_DV = 4160; //53kWh static const int MAX_PACK_VOLTAGE_53_DV = 4160; //53kWh
@ -51,9 +63,6 @@ class GeelyGeometryCBattery : public CanBattery {
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_GEELY_GEOMETRY_C* datalayer_geometryc;
CAN_frame GEELY_191 = {.FD = false, //PAS_APA_Status , 10ms CAN_frame GEELY_191 = {.FD = false, //PAS_APA_Status , 10ms
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,

View file

@ -1,8 +1,7 @@
#ifndef _GEELY_GEOMETRY_C_HTML_H #ifndef _GEELY_GEOMETRY_C_HTML_H
#define _GEELY_GEOMETRY_C_HTML_H #define _GEELY_GEOMETRY_C_HTML_H
#include <cstring> #include <cstring> //For unit test
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/webserver/BatteryHtmlRenderer.h" #include "../devboard/webserver/BatteryHtmlRenderer.h"
@ -10,7 +9,6 @@ class GeelyGeometryCHtmlRenderer : public BatteryHtmlRenderer {
public: public:
String get_status_html() { String get_status_html() {
String content; String content;
char readableSerialNumber[29]; // One extra space for null terminator char readableSerialNumber[29]; // One extra space for null terminator
memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber, memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber,
sizeof(datalayer_extended.geometryC.BatterySerialNumber)); sizeof(datalayer_extended.geometryC.BatterySerialNumber));
@ -52,7 +50,6 @@ class GeelyGeometryCHtmlRenderer : public BatteryHtmlRenderer {
"<h4>Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " &deg;C</h4>"; "<h4>Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " &deg;C</h4>";
content += content +=
"<h4>Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " &deg;C</h4>"; "<h4>Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " &deg;C</h4>";
return content; return content;
} }
}; };

View file

@ -5,9 +5,7 @@ String HyundaiIoniq28BatteryHtmlRenderer::get_status_html() {
String content; String content;
content += "<h4>12V voltage: " + String(batt.get_lead_acid_voltage() / 10.0, 1) + "</h4>"; content += "<h4>12V voltage: " + String(batt.get_lead_acid_voltage() / 10.0, 1) + "</h4>";
content += "<h4>Temperature, power relay: " + String(batt.get_power_relay_temperature()) + "</h4>"; content += "<h4>Temperature, power relay: " + String(batt.get_power_relay_temperature()) + "</h4>";
content += "<h4>Battery relay: " + String(batt.get_battery_relay_mode()) + "</h4>";
content += "<h4>Batterymanagement mode: " + String(batt.get_battery_management_mode()) + "</h4>"; content += "<h4>Batterymanagement mode: " + String(batt.get_battery_management_mode()) + "</h4>";
content += "<h4>BMS ignition: " + String(batt.get_battery_ignition_mode()) + "</h4>"; content += "<h4>Isolation resistance: " + String(batt.get_isolation_resistance()) + " kOhm</h4>";
return content; return content;
} }

View file

@ -88,7 +88,11 @@ void HyundaiIoniq28Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
break; break;
case 0x21: //First frame in PID group case 0x21: //First frame in PID group
if (incoming_poll_group == 1) { //21 14 13 24 13 24 04 00 if (incoming_poll_group == 1) { //21 14 13 24 13 24 04 00
batteryRelay = rx_frame.data.u8[7]; //SOC = rx_frame.data.u8[1];
//available_charge_power = 2 & 3
//available_discharge_power = 4 & 5
//status_bits? = 6
//battery_current_highbyte = rx_frame.data.u8[7];
} else if (incoming_poll_group == 2) { //21 AD AD AD AD AD AD AC } else if (incoming_poll_group == 2) { //21 AD AD AD AD AD AD AC
cellvoltages_mv[0] = (rx_frame.data.u8[1] * 20); cellvoltages_mv[0] = (rx_frame.data.u8[1] * 20);
cellvoltages_mv[1] = (rx_frame.data.u8[2] * 20); cellvoltages_mv[1] = (rx_frame.data.u8[2] * 20);
@ -113,11 +117,19 @@ void HyundaiIoniq28Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
cellvoltages_mv[68] = (rx_frame.data.u8[5] * 20); cellvoltages_mv[68] = (rx_frame.data.u8[5] * 20);
cellvoltages_mv[69] = (rx_frame.data.u8[6] * 20); cellvoltages_mv[69] = (rx_frame.data.u8[6] * 20);
cellvoltages_mv[70] = (rx_frame.data.u8[7] * 20); cellvoltages_mv[70] = (rx_frame.data.u8[7] * 20);
} else if (incoming_poll_group == 5) { //21 0 0 0 0 0 0f0f
//battery_module_6_temperature = rx_frame.data.u8[6];
//battery_module_7_temperature = rx_frame.data.u8[7];
} }
break; break;
case 0x22: //Second datarow in PID group case 0x22: //Second datarow in PID group
if (incoming_poll_group == 1) { //22 00 0C FF 17 16 17 17 if (incoming_poll_group == 1) { //22 00 0C FF 17 16 17 17
//battery_current_lowbyte = rx_frame.data.u8[1];
//battery_DC_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
//battery_max_temperature = rx_frame.data.u8[4];
//battery_min_temperature = rx_frame.data.u8[5];
//battery_module_1_temperature = rx_frame.data.u8[6];
//battery_module_2_temperature = rx_frame.data.u8[7];
} else if (incoming_poll_group == 2) { //22 AD AC AC AD AD AD AD } else if (incoming_poll_group == 2) { //22 AD AC AC AD AD AD AD
cellvoltages_mv[7] = (rx_frame.data.u8[1] * 20); cellvoltages_mv[7] = (rx_frame.data.u8[1] * 20);
cellvoltages_mv[8] = (rx_frame.data.u8[2] * 20); cellvoltages_mv[8] = (rx_frame.data.u8[2] * 20);
@ -142,13 +154,25 @@ void HyundaiIoniq28Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
cellvoltages_mv[75] = (rx_frame.data.u8[5] * 20); cellvoltages_mv[75] = (rx_frame.data.u8[5] * 20);
cellvoltages_mv[76] = (rx_frame.data.u8[6] * 20); cellvoltages_mv[76] = (rx_frame.data.u8[6] * 20);
cellvoltages_mv[77] = (rx_frame.data.u8[7] * 20); cellvoltages_mv[77] = (rx_frame.data.u8[7] * 20);
} else if (incoming_poll_group == 5) { //22 10 0d 0c 0e 0d 26 48
//battery_module_8_temperature = rx_frame.data.u8[1];
//battery_module_9_temperature = rx_frame.data.u8[2];
//battery_module_10_temperature = rx_frame.data.u8[3];
//battery_module_11_temperature = rx_frame.data.u8[4];
//battery_module_12_temperature = rx_frame.data.u8[5];
//available_charge_power = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
} else if (incoming_poll_group == 6) { } else if (incoming_poll_group == 6) {
batteryManagementMode = rx_frame.data.u8[5]; batteryManagementMode = rx_frame.data.u8[5];
} }
break; break;
case 0x23: //Third datarow in PID group case 0x23: //Third datarow in PID group
if (incoming_poll_group == 1) { //23 17 17 17 00 17 AD 25 if (incoming_poll_group == 1) { //23 17 17 17 00 17 AD 25
//battery_module_3_temperature = rx_frame.data.u8[1];
//battery_module_4_temperature = rx_frame.data.u8[2];
//battery_module_5_temperature = rx_frame.data.u8[3];
//battery_inlet_temperature = rx_frame.data.u8[5];
CellVoltMax_mV = (rx_frame.data.u8[6] * 20); //(volts *50) *20 =mV CellVoltMax_mV = (rx_frame.data.u8[6] * 20); //(volts *50) *20 =mV
//cellmaxvoltage_number = rx_frame.data.u8[7];
} else if (incoming_poll_group == 2) { //23 AD AD AD AD AB AD AD } else if (incoming_poll_group == 2) { //23 AD AD AD AD AB AD AD
cellvoltages_mv[14] = (rx_frame.data.u8[1] * 20); cellvoltages_mv[14] = (rx_frame.data.u8[1] * 20);
cellvoltages_mv[15] = (rx_frame.data.u8[2] * 20); cellvoltages_mv[15] = (rx_frame.data.u8[2] * 20);
@ -174,12 +198,21 @@ void HyundaiIoniq28Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
cellvoltages_mv[83] = (rx_frame.data.u8[6] * 20); cellvoltages_mv[83] = (rx_frame.data.u8[6] * 20);
cellvoltages_mv[84] = (rx_frame.data.u8[7] * 20); cellvoltages_mv[84] = (rx_frame.data.u8[7] * 20);
} else if (incoming_poll_group == 5) { } else if (incoming_poll_group == 5) {
heatertemp = rx_frame.data.u8[7]; //available_discharge_power = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
//battery_cell_mV_deviation = rx_frame.data.u8[3];
//airbag_h/wire_duty = rx_frame.data.u8[5];
heatertemperature_1 = rx_frame.data.u8[6];
heatertemperature_2 = rx_frame.data.u8[7];
} }
break; break;
case 0x24: //Fourth datarow in PID group case 0x24: //Fourth datarow in PID group
if (incoming_poll_group == 1) { //24 AA 4F 00 00 77 00 14 if (incoming_poll_group == 1) { //24 AA 4F 00 00 77 00 14
CellVoltMin_mV = (rx_frame.data.u8[1] * 20); //(volts *50) *20 =mV CellVoltMin_mV = (rx_frame.data.u8[1] * 20); //(volts *50) *20 =mV
//mincellvoltage_number = rx_frame.data.u8[2];
//fan_status = rx_frame.data.u8[3];
//fan_feedback_signal = rx_frame.data.u8[4];
//aux_battery_voltage = rx_frame.data.u8[5];
//cumulative_charge_current_highbyte = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
} else if (incoming_poll_group == 2) { //24 AD AD AD AD AD AD AB } else if (incoming_poll_group == 2) { //24 AD AD AD AD AD AD AB
cellvoltages_mv[21] = (rx_frame.data.u8[1] * 20); cellvoltages_mv[21] = (rx_frame.data.u8[1] * 20);
cellvoltages_mv[22] = (rx_frame.data.u8[2] * 20); cellvoltages_mv[22] = (rx_frame.data.u8[2] * 20);
@ -204,13 +237,19 @@ void HyundaiIoniq28Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
cellvoltages_mv[89] = (rx_frame.data.u8[5] * 20); cellvoltages_mv[89] = (rx_frame.data.u8[5] * 20);
cellvoltages_mv[90] = (rx_frame.data.u8[6] * 20); cellvoltages_mv[90] = (rx_frame.data.u8[6] * 20);
cellvoltages_mv[91] = (rx_frame.data.u8[7] * 20); cellvoltages_mv[91] = (rx_frame.data.u8[7] * 20);
} else if (incoming_poll_group == 5) { } else if (incoming_poll_group == 5) { //24 3 e8 5 3 e8 m34 6e
batterySOH = ((rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]); batterySOH = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
//max_deterioration_cell_number = rx_frame.data.u8[3]
//min_deterioration = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
//min_deterioration_cell_number = rx_frame.data.u8[6]
//SOC_display = rx_frame.data.u8[7]
} }
break; break;
case 0x25: //Fifth datarow in PID group case 0x25: //Fifth datarow in PID group
if (incoming_poll_group == 1) { //25 5C A9 00 14 5F D3 00 if (incoming_poll_group == 1) { //25 5C A9 00 14 5F D3 00
//cumulative_charge_current_lowbyte = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
//cumulative_discharge_current = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
//cumulative_charge_energy_highbyte = rx_frame.data.u8[7];
} else if (incoming_poll_group == 2) { //25 AD AD AD AD 00 00 00 } else if (incoming_poll_group == 2) { //25 AD AD AD AD 00 00 00
cellvoltages_mv[28] = (rx_frame.data.u8[1] * 20); cellvoltages_mv[28] = (rx_frame.data.u8[1] * 20);
cellvoltages_mv[29] = (rx_frame.data.u8[2] * 20); cellvoltages_mv[29] = (rx_frame.data.u8[2] * 20);
@ -231,19 +270,22 @@ void HyundaiIoniq28Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
break; break;
case 0x26: //Sixth datarow in PID group case 0x26: //Sixth datarow in PID group
if (incoming_poll_group == 1) { //26 07 84 F9 00 07 42 8F if (incoming_poll_group == 1) { //26 07 84 F9 00 07 42 8F
//cumulative_charge_energy_lowbyte = (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
//cumulative_discharge_energy = ((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
} else if (incoming_poll_group == 5) { } else if (incoming_poll_group == 5) {
} }
break; break;
case 0x27: //Seventh datarow in PID group case 0x27: //Seventh datarow in PID group
if (incoming_poll_group == 1) { //27 03 3F A1 EB 00 19 99 if (incoming_poll_group == 1) { //27 03 3F A1 EB 00 19 99
BMS_ign = rx_frame.data.u8[6]; //cumulative_operating_time = ((rx_frame.data.u8[1] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
inverterVoltageFrameHigh = rx_frame.data.u8[7]; //bitfield (41 off, 45 car on) = rx_frame.data.u8[5];
inverterVoltage = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
} }
break; break;
case 0x28: //Eighth datarow in PID group case 0x28: //Eighth datarow in PID group
if (incoming_poll_group == 1) { //28 7F FF 7F FF 03 E8 00 if (incoming_poll_group == 1) { //28 7F FF 7F FF 03 E8 00
inverterVoltage = (inverterVoltageFrameHigh << 8) + rx_frame.data.u8[1]; isolation_resistance = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
} }
break; break;
} }
@ -378,10 +420,6 @@ uint8_t HyundaiIoniq28Battery::get_battery_management_mode() const {
return batteryManagementMode; return batteryManagementMode;
} }
uint8_t HyundaiIoniq28Battery::get_battery_ignition_mode() const { uint16_t HyundaiIoniq28Battery::get_isolation_resistance() const {
return BMS_ign; return isolation_resistance;
}
uint8_t HyundaiIoniq28Battery::get_battery_relay_mode() const {
return batteryRelay;
} }

View file

@ -6,13 +6,10 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "HYUNDAI-IONIQ-28-BATTERY-HTML.h" #include "HYUNDAI-IONIQ-28-BATTERY-HTML.h"
#ifdef HYUNDAI_IONIQ_28_BATTERY
#define SELECTED_BATTERY_CLASS HyundaiIoniq28Battery
#endif
class HyundaiIoniq28Battery : public CanBattery { class HyundaiIoniq28Battery : public CanBattery {
public: public:
HyundaiIoniq28Battery() : renderer(*this) {} // Use the default constructor to create the first or single battery.
HyundaiIoniq28Battery() : renderer(*this) { datalayer_battery = &datalayer.battery; }
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }
@ -25,10 +22,9 @@ class HyundaiIoniq28Battery : public CanBattery {
// Getter methods for HTML renderer // Getter methods for HTML renderer
uint16_t get_lead_acid_voltage() const; uint16_t get_lead_acid_voltage() const;
uint16_t get_isolation_resistance() const;
int16_t get_power_relay_temperature() const; int16_t get_power_relay_temperature() const;
uint8_t get_battery_management_mode() const; uint8_t get_battery_management_mode() const;
uint8_t get_battery_ignition_mode() const;
uint8_t get_battery_relay_mode() const;
private: private:
HyundaiIoniq28BatteryHtmlRenderer renderer; HyundaiIoniq28BatteryHtmlRenderer renderer;
@ -53,18 +49,17 @@ class HyundaiIoniq28Battery : public CanBattery {
uint16_t allowedDischargePower = 0; uint16_t allowedDischargePower = 0;
uint16_t allowedChargePower = 0; uint16_t allowedChargePower = 0;
uint16_t batteryVoltage = 3700; uint16_t batteryVoltage = 3700;
uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0; uint16_t inverterVoltage = 0;
uint16_t isolation_resistance = 1000;
uint16_t cellvoltages_mv[96]; uint16_t cellvoltages_mv[96];
uint16_t leadAcidBatteryVoltage = 120; uint16_t leadAcidBatteryVoltage = 120;
int16_t batteryAmps = 0; int16_t batteryAmps = 0;
int16_t temperatureMax = 0; int16_t temperatureMax = 0;
int16_t temperatureMin = 0; int16_t temperatureMin = 0;
uint8_t batteryManagementMode = 0; uint8_t batteryManagementMode = 0;
uint8_t BMS_ign = 0;
uint8_t batteryRelay = 0;
uint8_t counter_200 = 0; uint8_t counter_200 = 0;
int8_t heatertemp = 0; int8_t heatertemperature_1 = 0;
int8_t heatertemperature_2 = 0;
int8_t powerRelayTemperature = 0; int8_t powerRelayTemperature = 0;
bool startedUp = false; bool startedUp = false;
uint8_t incoming_poll_group = 0xFF; uint8_t incoming_poll_group = 0xFF;

View file

@ -1,4 +1,5 @@
#include "IMIEV-CZERO-ION-BATTERY.h" #include "IMIEV-CZERO-ION-BATTERY.h"
#include <cstring> //for unit tests
#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"
@ -19,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
@ -75,30 +78,9 @@ void ImievCZeroIonBattery::
} }
if (!BMU_Detected) { if (!BMU_Detected) {
#ifdef DEBUG_LOG
logging.println("BMU not detected, check wiring!"); logging.println("BMU not detected, check wiring!");
#endif //TODO: Raise event
} }
#ifdef DEBUG_LOG
logging.println("Battery Values");
logging.print("BMU SOC: ");
logging.print(BMU_SOC);
logging.print(" BMU Current: ");
logging.print(BMU_Current);
logging.print(" BMU Battery Voltage: ");
logging.print(BMU_PackVoltage);
logging.print(" BMU_Power: ");
logging.print(BMU_Power);
logging.print(" Cell max voltage: ");
logging.print(max_volt_cel);
logging.print(" Cell min voltage: ");
logging.print(min_volt_cel);
logging.print(" Cell max temp: ");
logging.print(max_temp_cel);
logging.print(" Cell min temp: ");
logging.println(min_temp_cel);
#endif
} }
void ImievCZeroIonBattery::handle_incoming_can_frame(CAN_frame rx_frame) { void ImievCZeroIonBattery::handle_incoming_can_frame(CAN_frame rx_frame) {

View file

@ -2,10 +2,6 @@
#define IMIEV_CZERO_ION_BATTERY_H #define IMIEV_CZERO_ION_BATTERY_H
#include "CanBattery.h" #include "CanBattery.h"
#ifdef IMIEV_CZERO_ION_BATTERY
#define SELECTED_BATTERY_CLASS ImievCZeroIonBattery
#endif
class ImievCZeroIonBattery : public CanBattery { class ImievCZeroIonBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,4 +1,5 @@
#include "JAGUAR-IPACE-BATTERY.h" #include "JAGUAR-IPACE-BATTERY.h"
#include <cstring> //for unit tests
#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"
@ -57,9 +58,9 @@ CAN_frame ipace_keep_alive = {.FD = false,
.data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/ .data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/
static void print_units(const char* header, int value, const char* units) { static void print_units(const char* header, int value, const char* units) {
logging.print(header); logging.printf(header);
logging.print(value); logging.printf("%d", value);
logging.print(units); logging.printf(units);
} }
void JaguarIpaceBattery::update_values() { void JaguarIpaceBattery::update_values() {
@ -102,21 +103,6 @@ void JaguarIpaceBattery::update_values() {
} else { } else {
clear_event(EVENT_BATTERY_ISOLATION); clear_event(EVENT_BATTERY_ISOLATION);
} }
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_LOG
logging.println("Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
print_units(", Max discharge power: ", datalayer.battery.status.max_discharge_power_W, "W ");
print_units(", Max charge power: ", datalayer.battery.status.max_charge_power_W, "W ");
print_units(", Max temp: ", (datalayer.battery.status.temperature_max_dC * 0.1), "°C ");
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
logging.println("");
#endif
} }
void JaguarIpaceBattery::handle_incoming_can_frame(CAN_frame rx_frame) { void JaguarIpaceBattery::handle_incoming_can_frame(CAN_frame rx_frame) {

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#ifdef JAGUAR_IPACE_BATTERY
#define SELECTED_BATTERY_CLASS JaguarIpaceBattery
#endif
class JaguarIpaceBattery : public CanBattery { class JaguarIpaceBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -0,0 +1,423 @@
#include "KIA-64FD-BATTERY.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "../devboard/utils/logging.h"
#include "../system_settings.h"
// Function to estimate SOC based on cell voltage
uint16_t Kia64FDBattery::estimateSOCFromCell(uint16_t cellVoltage) {
if (cellVoltage >= voltage[0]) {
return SOC[0];
}
if (cellVoltage <= voltage[numPoints - 1]) {
return SOC[numPoints - 1];
}
for (int i = 1; i < numPoints; ++i) {
if (cellVoltage >= voltage[i]) {
// Cast to float for proper division
float t = (float)(cellVoltage - voltage[i]) / (float)(voltage[i - 1] - voltage[i]);
// Calculate interpolated SOC value
uint16_t socDiff = SOC[i - 1] - SOC[i];
uint16_t interpolatedValue = SOC[i] + (uint16_t)(t * socDiff);
return interpolatedValue;
}
}
return 0; // Default return for safety, should never reach here
}
// Simplified version of the pack-based SOC estimation with compensation
uint16_t Kia64FDBattery::estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps) {
if (cellCount == 0) {
return 0;
}
// Convert pack voltage (decivolts) to millivolts
uint32_t packVoltageMv = packVoltage * 100;
// Apply internal resistance compensation
// Current is in deciamps (-150 = -15.0A, 150 = 15.0A)
// Resistance is in milliohms
int32_t voltageDrop = (currentAmps * PACK_INTERNAL_RESISTANCE_MOHM) / 10;
// Compensate the pack voltage (add the voltage drop)
uint32_t compensatedPackVoltageMv = packVoltageMv + voltageDrop;
// Calculate average cell voltage in millivolts
uint16_t avgCellVoltage = compensatedPackVoltageMv / cellCount;
// Use the cell voltage lookup table to estimate SOC
return estimateSOCFromCell(avgCellVoltage);
}
// Fix: Change parameter types to uint16_t to match SOC values
uint16_t Kia64FDBattery::selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
if (SOC_low == 0 || SOC_high == 0) {
return 0; // If either value is 0, return 0
}
if (SOC_low == 10000 || SOC_high == 10000) {
return 10000; // If either value is 100%, return 100%
}
return (SOC_low < SOC_high) ? SOC_low : SOC_high; // Otherwise, return the lowest value
}
void write_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell) {
for (size_t i = 0; i < length; i++) {
if ((rx_frame.data.u8[start + i] * 20) > 1000) {
datalayer.battery.status.cell_voltages_mV[startCell + i] = (rx_frame.data.u8[start + i] * 20);
}
}
}
uint8_t Kia64FDBattery::calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
crc = crc8_table[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256];
}
return crc;
}
void Kia64FDBattery::update_values() {
#ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE
// Use the simplified pack-based SOC estimation with proper compensation
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
SOC_estimated_lowest = estimateSOCFromCell(CellVoltMin_mV);
SOC_estimated_highest = estimateSOCFromCell(CellVoltMax_mV);
#else
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.voltage_dV = batteryVoltage; //value is *10 (3700 = 370.0)
datalayer.battery.status.current_dA = -batteryAmps; //value is *10 (150 = 15.0)
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 = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
//The allowed charge power is not available. We estimate this value for now
if (datalayer.battery.status.real_soc > 9900) {
datalayer.battery.status.max_charge_power_W = 0;
} else if (datalayer.battery.status.real_soc >
RAMPDOWN_SOC) { // When real SOC is between 90-99%, ramp the value between Max<->0
datalayer.battery.status.max_charge_power_W =
RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
} else { // No limits, max charging power allowed
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
}
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
//The allowed discharge power is not available. We hardcode this value for now
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV;
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
if (leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
}
void Kia64FDBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
startedUp = true;
switch (rx_frame.ID) {
case 0x055:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x150:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x1F5:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x215:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x21A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x235:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x245:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x25A:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x275:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x2FA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x325:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x330:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x335:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x360:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x365:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x3BA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x3F5:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EC:
// print_canfd_frame(frame);
switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header"
// logging.println ("Send ack");
poll_data_pid = rx_frame.data.u8[4];
// if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can_frame(&KIA64FD_ack); //Send ack to BMS if the same frame is sent as polled
// }
break;
case 0x21: //First frame in PID group
if (poll_data_pid == 1) {
allowedChargePower = ((rx_frame.data.u8[3] << 8) + rx_frame.data.u8[4]);
allowedDischargePower = ((rx_frame.data.u8[5] << 8) + rx_frame.data.u8[6]);
SOC_BMS = rx_frame.data.u8[2] * 5; //100% = 200 ( 200 * 5 = 1000 )
} else if (poll_data_pid == 2) {
// set cell voltages data, start bite, data length from start, start cell
write_cell_voltages(rx_frame, 2, 6, 0);
} else if (poll_data_pid == 3) {
write_cell_voltages(rx_frame, 2, 6, 32);
} else if (poll_data_pid == 4) {
write_cell_voltages(rx_frame, 2, 6, 64);
} else if (poll_data_pid == 0x0A) {
write_cell_voltages(rx_frame, 2, 6, 96);
} else if (poll_data_pid == 0x0B) {
write_cell_voltages(rx_frame, 2, 6, 128);
} else if (poll_data_pid == 0x0C) {
write_cell_voltages(rx_frame, 2, 6, 160);
}
break;
case 0x22: //Second datarow in PID group
if (poll_data_pid == 1) {
batteryVoltage = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[4];
batteryAmps = (rx_frame.data.u8[1] << 8) + rx_frame.data.u8[2];
temperatureMax = rx_frame.data.u8[5];
temperatureMin = rx_frame.data.u8[6];
// temp1 = rx_frame.data.u8[7];
} else if (poll_data_pid == 2) {
write_cell_voltages(rx_frame, 1, 7, 6);
} else if (poll_data_pid == 3) {
write_cell_voltages(rx_frame, 1, 7, 38);
} else if (poll_data_pid == 4) {
write_cell_voltages(rx_frame, 1, 7, 70);
} else if (poll_data_pid == 0x0A) {
write_cell_voltages(rx_frame, 1, 7, 102);
} else if (poll_data_pid == 0x0B) {
write_cell_voltages(rx_frame, 1, 7, 134);
} else if (poll_data_pid == 0x0C) {
write_cell_voltages(rx_frame, 1, 7, 166);
} else if (poll_data_pid == 6) {
batteryManagementMode = rx_frame.data.u8[5];
}
break;
case 0x23: //Third datarow in PID group
if (poll_data_pid == 1) {
temperature_water_inlet = rx_frame.data.u8[6];
CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
// temp2 = rx_frame.data.u8[1];
// temp3 = rx_frame.data.u8[2];
// temp4 = rx_frame.data.u8[3];
} else if (poll_data_pid == 2) {
write_cell_voltages(rx_frame, 1, 7, 13);
} else if (poll_data_pid == 3) {
write_cell_voltages(rx_frame, 1, 7, 45);
} else if (poll_data_pid == 4) {
write_cell_voltages(rx_frame, 1, 7, 77);
} else if (poll_data_pid == 0x0A) {
write_cell_voltages(rx_frame, 1, 7, 109);
} else if (poll_data_pid == 0x0B) {
write_cell_voltages(rx_frame, 1, 7, 141);
} else if (poll_data_pid == 0x0C) {
write_cell_voltages(rx_frame, 1, 7, 173);
} else if (poll_data_pid == 5) {
// ac = rx_frame.data.u8[3];
// Vdiff = rx_frame.data.u8[4];
// airbag = rx_frame.data.u8[6];
heatertemp = rx_frame.data.u8[7];
}
break;
case 0x24: //Fourth datarow in PID group
if (poll_data_pid == 1) {
CellVmaxNo = rx_frame.data.u8[1];
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
CellVminNo = rx_frame.data.u8[3];
// fanMod = rx_frame.data.u8[4];
// fanSpeed = rx_frame.data.u8[5];
leadAcidBatteryVoltage = rx_frame.data.u8[6]; //12v Battery Volts
//cumulative_charge_current[0] = rx_frame.data.u8[7];
} else if (poll_data_pid == 2) {
write_cell_voltages(rx_frame, 1, 7, 20);
} else if (poll_data_pid == 3) {
write_cell_voltages(rx_frame, 1, 7, 52);
} else if (poll_data_pid == 4) {
write_cell_voltages(rx_frame, 1, 7, 84);
} else if (poll_data_pid == 0x0A) {
write_cell_voltages(rx_frame, 1, 7, 116);
} else if (poll_data_pid == 0x0B) {
write_cell_voltages(rx_frame, 1, 7, 148);
} else if (poll_data_pid == 0x0C) {
write_cell_voltages(rx_frame, 1, 7, 180);
} else if (poll_data_pid == 5) {
batterySOH = ((rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]);
// maxDetCell = rx_frame.data.u8[4];
// minDet = (rx_frame.data.u8[5] << 8) + rx_frame.data.u8[6];
// minDetCell = rx_frame.data.u8[7];
}
break;
case 0x25: //Fifth datarow in PID group
if (poll_data_pid == 1) {
//cumulative_charge_current[1] = rx_frame.data.u8[1];
//cumulative_charge_current[2] = rx_frame.data.u8[2];
//cumulative_charge_current[3] = rx_frame.data.u8[3];
//cumulative_discharge_current[0] = rx_frame.data.u8[4];
//cumulative_discharge_current[1] = rx_frame.data.u8[5];
//cumulative_discharge_current[2] = rx_frame.data.u8[6];
//cumulative_discharge_current[3] = rx_frame.data.u8[7];
//set_cumulative_charge_current();
//set_cumulative_discharge_current();
} else if (poll_data_pid == 2) {
write_cell_voltages(rx_frame, 1, 5, 27);
} else if (poll_data_pid == 3) {
write_cell_voltages(rx_frame, 1, 5, 59);
} else if (poll_data_pid == 4) {
write_cell_voltages(rx_frame, 1, 5, 91);
} else if (poll_data_pid == 0x0A) {
write_cell_voltages(rx_frame, 1, 5, 123);
} else if (poll_data_pid == 0x0B) {
write_cell_voltages(rx_frame, 1, 5, 155);
} else if (poll_data_pid == 0x0C) {
write_cell_voltages(rx_frame, 1, 5, 187);
//set_cell_count();
} else if (poll_data_pid == 5) {
// datalayer.battery.info.number_of_cells = 98;
SOC_Display = rx_frame.data.u8[1] * 5;
}
break;
case 0x26: //Sixth datarow in PID group
if (poll_data_pid == 1) {
//cumulative_energy_charged[0] = rx_frame.data.u8[1];
// cumulative_energy_charged[1] = rx_frame.data.u8[2];
//cumulative_energy_charged[2] = rx_frame.data.u8[3];
//cumulative_energy_charged[3] = rx_frame.data.u8[4];
//cumulative_energy_discharged[0] = rx_frame.data.u8[5];
//cumulative_energy_discharged[1] = rx_frame.data.u8[6];
//cumulative_energy_discharged[2] = rx_frame.data.u8[7];
// set_cumulative_energy_charged();
}
break;
case 0x27: //Seventh datarow in PID group
if (poll_data_pid == 1) {
//cumulative_energy_discharged[3] = rx_frame.data.u8[1];
//opTimeBytes[0] = rx_frame.data.u8[2];
//opTimeBytes[1] = rx_frame.data.u8[3];
//opTimeBytes[2] = rx_frame.data.u8[4];
//opTimeBytes[3] = rx_frame.data.u8[5];
BMS_ign = rx_frame.data.u8[6];
inverterVoltageFrameHigh = rx_frame.data.u8[7]; // BMS Capacitoir
// set_cumulative_energy_discharged();
// set_opTime();
}
break;
case 0x28: //Eighth datarow in PID group
if (poll_data_pid == 1) {
inverterVoltage = (inverterVoltageFrameHigh << 8) + rx_frame.data.u8[1]; // BMS Capacitoir
}
break;
}
break;
default:
break;
}
}
void Kia64FDBattery::transmit_can(unsigned long currentMillis) {
if (startedUp) {
//Send Contactor closing message loop
// Check if we still have messages to send
if (messageIndex < sizeof(messageDelays) / sizeof(messageDelays[0])) {
// Check if it's time to send the next message
if (currentMillis - startMillis >= messageDelays[messageIndex]) {
// Transmit the current message
transmit_can_frame(messages[messageIndex]);
// Move to the next message
messageIndex++;
}
}
if (messageIndex >= 63) {
startMillis = currentMillis; // Start over!
messageIndex = 0;
}
//Send 200ms CANFD message
if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) {
previousMillis200ms = currentMillis;
KIA64FD_7E4.data.u8[3] = KIA_7E4_COUNTER;
if (ok_start_polling_battery) {
transmit_can_frame(&KIA64FD_7E4);
}
KIA_7E4_COUNTER++;
if (KIA_7E4_COUNTER > 0x0D) { // gets up to 0x010C before repeating
KIA_7E4_COUNTER = 0x01;
}
}
//Send 10s CANFD message
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
previousMillis10s = currentMillis;
ok_start_polling_battery = true;
}
}
}
void Kia64FDBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}

View file

@ -0,0 +1,631 @@
#ifndef KIA_64_FD_BATTERY_H
#define KIA_64_FD_BATTERY_H
#include <Arduino.h>
#include "CanBattery.h"
#define ESTIMATE_SOC_FROM_CELLVOLTAGE
class Kia64FDBattery : 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 = "Kia 64kWh FD battery";
private:
uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps);
uint16_t estimateSOCFromCell(uint16_t cellVoltage);
uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value);
uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high);
static const int MAX_PACK_VOLTAGE_DV = 4032; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 2400;
static const int MAX_CELL_DEVIATION_MV = 150;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2950; //Battery is put into emergency stop if one cell goes below this value
static const int MAXCHARGEPOWERALLOWED = 10000;
static const int MAXDISCHARGEPOWERALLOWED = 10000;
static const int RAMPDOWN_SOC = 9000; // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
// Used for SoC compensation - Define internal resistance value in milliohms for the entire pack
// How to calculate: voltage_drop_under_known_load [Volts] / load [Amps] = Resistance
static const int PACK_INTERNAL_RESISTANCE_MOHM = 200; // 200 milliohms for the whole pack
unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0;
uint16_t soc_calculated = 0;
uint16_t SOC_BMS = 0;
uint16_t SOC_Display = 0;
uint16_t SOC_estimated_lowest = 0;
uint16_t SOC_estimated_highest = 0;
uint16_t batterySOH = 1000;
uint16_t CellVoltMax_mV = 3700;
uint16_t CellVoltMin_mV = 3700;
uint16_t batteryVoltage = 0;
int16_t leadAcidBatteryVoltage = 120;
int16_t batteryAmps = 0;
int16_t temperatureMax = 0;
int16_t temperatureMin = 0;
int16_t allowedDischargePower = 0;
int16_t allowedChargePower = 0;
int16_t poll_data_pid = 0;
uint8_t CellVmaxNo = 0;
uint8_t CellVminNo = 0;
uint8_t batteryManagementMode = 0;
uint8_t BMS_ign = 0;
bool startedUp = false;
bool ok_start_polling_battery = false;
uint8_t KIA_7E4_COUNTER = 0x01;
int8_t temperature_water_inlet = 0;
int8_t heatertemp = 0;
unsigned long startMillis = 0;
uint8_t messageIndex = 0;
const unsigned char crc8_table[256] = {
// CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
// Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100;
const uint16_t SOC[101] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9100, 9000, 8900, 8800, 8700, 8600,
8500, 8400, 8300, 8200, 8100, 8000, 7900, 7800, 7700, 7600, 7500, 7400, 7300, 7200, 7100,
7000, 6900, 6800, 6700, 6600, 6500, 6400, 6300, 6200, 6100, 6000, 5900, 5800, 5700, 5600,
5500, 5400, 5300, 5200, 5100, 5000, 4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100,
4000, 3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100,
1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0};
const uint16_t voltage[101] = {
4200, 4173, 4148, 4124, 4102, 4080, 4060, 4041, 4023, 4007, 3993, 3980, 3969, 3959, 3953, 3950, 3941,
3932, 3924, 3915, 3907, 3898, 3890, 3881, 3872, 3864, 3855, 3847, 3838, 3830, 3821, 3812, 3804, 3795,
3787, 3778, 3770, 3761, 3752, 3744, 3735, 3727, 3718, 3710, 3701, 3692, 3684, 3675, 3667, 3658, 3650,
3641, 3632, 3624, 3615, 3607, 3598, 3590, 3581, 3572, 3564, 3555, 3547, 3538, 3530, 3521, 3512, 3504,
3495, 3487, 3478, 3470, 3461, 3452, 3444, 3435, 3427, 3418, 3410, 3401, 3392, 3384, 3375, 3367, 3358,
3350, 3338, 3325, 3313, 3299, 3285, 3271, 3255, 3239, 3221, 3202, 3180, 3156, 3127, 3090, 3000};
/* These messages are needed for contactor closing */
uint8_t messageDelays[63] = {0, 0, 5, 10, 10, 15, 19, 19, 20, 20, 25, 30, 30, 35, 40, 40,
45, 49, 49, 50, 50, 52, 53, 53, 54, 55, 60, 60, 65, 67, 67, 70,
70, 75, 77, 77, 80, 80, 85, 90, 90, 95, 100, 100, 105, 110, 110, 115,
119, 119, 120, 120, 125, 130, 130, 135, 140, 140, 145, 149, 149, 150, 150};
static constexpr CAN_frame message_1 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x62, 0x36, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_2 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xd4, 0x1b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_3 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x24, 0x9b, 0x7b, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_4 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x24, 0x6f, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_5 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x92, 0x42, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_6 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xd7, 0x05, 0x7c, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_7 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x30A,
.data = {0xb1, 0xe0, 0x26, 0x08, 0x54, 0x01, 0x04, 0x15, 0x00, 0x1a, 0x76, 0x00, 0x25, 0x01, 0x10, 0x27,
0x4f, 0x06, 0x18, 0x04, 0x33, 0x15, 0x34, 0x28, 0x00, 0x00, 0x10, 0x06, 0x21, 0x00, 0x4b, 0x06}};
static constexpr CAN_frame message_8 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x320,
.data = {0xc6, 0xab, 0x26, 0x41, 0x00, 0x00, 0x01, 0x3c, 0xac, 0x0d, 0x40, 0x20, 0x05, 0xc8, 0xa0, 0x03,
0x40, 0x20, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_9 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xee, 0x84, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_10 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x58, 0xa9, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_11 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x91, 0x5c, 0x7d, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_12 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xa8, 0xdd, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_13 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x1e, 0xf0, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_14 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x5b, 0xb7, 0x7e, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_15 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xec, 0x6d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_16 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x5a, 0x40, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_17 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x1d, 0xee, 0x7f, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_18 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2B5,
.data = {0xbd, 0xb2, 0x42, 0x00, 0x00, 0x00, 0x00, 0x80, 0x59, 0x00, 0x2b, 0x00, 0x00, 0x04, 0x00, 0x00,
0xfa, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_19 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E0,
.data = {0xc1, 0xf2, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x70, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_20 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xaa, 0x34, 0x91, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_21 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x1c, 0x19, 0x91, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_22 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2D5,
.data = {0x79, 0xfb, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_23 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2EA,
.data = {0x6e, 0xbb, 0xa0, 0x0d, 0x04, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_24 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x306,
.data = {0x00, 0x00, 0x00, 0xd2, 0x06, 0x92, 0x05, 0x34, 0x07, 0x8e, 0x08, 0x73, 0x05, 0x80, 0x05, 0x83,
0x05, 0x73, 0x05, 0x80, 0x05, 0xed, 0x01, 0xdd, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_25 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x308,
.data = {0xbe, 0x84, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x75, 0x6c, 0x86, 0x0d, 0xfb, 0xdf, 0x03, 0x36, 0xc3, 0x86, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_26 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x6b, 0xa2, 0x80, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_27 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x60, 0xdf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_28 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xd6, 0xf2, 0x92, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_29 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x2d, 0xfb, 0x81, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_30 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x33A,
.data = {0x1a, 0x23, 0x26, 0x10, 0x27, 0x4f, 0x06, 0x00, 0xf8, 0x1b, 0x19, 0x04, 0x30, 0x01, 0x00, 0x06,
0x00, 0x00, 0x00, 0x2e, 0x2d, 0x81, 0x25, 0x20, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_31 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x350,
.data = {0x26, 0x82, 0x26, 0xf4, 0x01, 0x00, 0x00, 0x50, 0x90, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_32 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x26, 0x86, 0x93, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_33 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x90, 0xab, 0x93, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_34 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xe7, 0x10, 0x82, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_35 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E5,
.data = {0x69, 0x8a, 0x3f, 0x01, 0x00, 0x00, 0x00, 0x15, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_36 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x3B5,
.data = {0xa3, 0xc8, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x36, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_37 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xd5, 0x18, 0x94, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_38 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x63, 0x35, 0x94, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_39 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xa1, 0x49, 0x83, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_40 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x93, 0x41, 0x95, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_41 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x25, 0x6c, 0x95, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_42 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x52, 0xd7, 0x84, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_43 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x59, 0xaa, 0x96, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_44 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xef, 0x87, 0x96, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_45 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x14, 0x8e, 0x85, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_46 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x1f, 0xf3, 0x97, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_47 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xa9, 0xde, 0x97, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_48 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xde, 0x65, 0x86, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_49 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x30A,
.data = {0xd3, 0x11, 0x27, 0x08, 0x54, 0x01, 0x04, 0x15, 0x00, 0x1a, 0x76, 0x00, 0x25, 0x01, 0x10, 0x27,
0x4f, 0x06, 0x19, 0x04, 0x33, 0x15, 0x34, 0x28, 0x00, 0x00, 0x10, 0x06, 0x21, 0x00, 0x4b, 0x06}};
static constexpr CAN_frame message_50 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x320,
.data = {0x80, 0xf2, 0x27, 0x41, 0x00, 0x00, 0x01, 0x3c, 0xac, 0x0d, 0x40, 0x20, 0x05, 0xc8, 0xa0, 0x03,
0x40, 0x20, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_51 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x9e, 0x87, 0x98, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_52 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x28, 0xaa, 0x98, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_53 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x98, 0x3c, 0x87, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_54 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xd8, 0xde, 0x99, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_55 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x6e, 0xf3, 0x99, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_56 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x19, 0x48, 0x88, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_57 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x12, 0x35, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_58 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xa4, 0x18, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_59 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x5f, 0x11, 0x89, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_60 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2B5,
.data = {0xfb, 0xeb, 0x43, 0x00, 0x00, 0x00, 0x00, 0x80, 0x59, 0x00, 0x2b, 0x00, 0x00, 0x04, 0x00, 0x00,
0xfa, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_61 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2C0,
.data = {0xcc, 0xcd, 0xa2, 0x21, 0x00, 0xa1, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_62 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E0,
.data = {0x87, 0xab, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x70, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_63 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x54, 0x6c, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
const CAN_frame* messages[64] = {
&message_1, &message_2, &message_3, &message_4, &message_5, &message_6, &message_7, &message_8,
&message_9, &message_10, &message_11, &message_12, &message_13, &message_14, &message_15, &message_16,
&message_17, &message_18, &message_19, &message_20, &message_21, &message_22, &message_23, &message_24,
&message_25, &message_26, &message_27, &message_28, &message_29, &message_30, &message_31, &message_32,
&message_33, &message_34, &message_35, &message_36, &message_37, &message_38, &message_39, &message_40,
&message_41, &message_42, &message_43, &message_44, &message_45, &message_46, &message_47, &message_48,
&message_49, &message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
/* PID polling messages */
CAN_frame KIA64FD_7E4 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 01
CAN_frame KIA64FD_ack = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
};
#endif

View file

@ -6,45 +6,8 @@
#include "../devboard/utils/logging.h" #include "../devboard/utils/logging.h"
#include "../system_settings.h" #include "../system_settings.h"
const unsigned char crc8_table[256] =
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0,
0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0,
0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23,
0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40,
0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B,
0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B,
0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8,
0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D,
0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC,
0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B,
0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C,
0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB,
0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47,
0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
0xE3, 0xFE, 0xD9, 0xC4};
// Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100;
const uint16_t SOC[] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9100, 9000, 8900, 8800, 8700, 8600,
8500, 8400, 8300, 8200, 8100, 8000, 7900, 7800, 7700, 7600, 7500, 7400, 7300, 7200, 7100,
7000, 6900, 6800, 6700, 6600, 6500, 6400, 6300, 6200, 6100, 6000, 5900, 5800, 5700, 5600,
5500, 5400, 5300, 5200, 5100, 5000, 4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100,
4000, 3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100,
1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0};
const uint16_t voltage[] = {4200, 4173, 4148, 4124, 4102, 4080, 4060, 4041, 4023, 4007, 3993, 3980, 3969, 3959, 3953,
3950, 3941, 3932, 3924, 3915, 3907, 3898, 3890, 3881, 3872, 3864, 3855, 3847, 3838, 3830,
3821, 3812, 3804, 3795, 3787, 3778, 3770, 3761, 3752, 3744, 3735, 3727, 3718, 3710, 3701,
3692, 3684, 3675, 3667, 3658, 3650, 3641, 3632, 3624, 3615, 3607, 3598, 3590, 3581, 3572,
3564, 3555, 3547, 3538, 3530, 3521, 3512, 3504, 3495, 3487, 3478, 3470, 3461, 3452, 3444,
3435, 3427, 3418, 3410, 3401, 3392, 3384, 3375, 3367, 3358, 3350, 3338, 3325, 3313, 3299,
3285, 3271, 3255, 3239, 3221, 3202, 3180, 3156, 3127, 3090, 3000};
// Function to estimate SOC based on cell voltage // Function to estimate SOC based on cell voltage
uint16_t estimateSOCFromCell(uint16_t cellVoltage) { uint16_t KiaEGmpBattery::estimateSOCFromCell(uint16_t cellVoltage) {
if (cellVoltage >= voltage[0]) { if (cellVoltage >= voltage[0]) {
return SOC[0]; return SOC[0];
} }
@ -92,26 +55,12 @@ uint16_t KiaEGmpBattery::estimateSOC(uint16_t packVoltage, uint16_t cellCount, i
// Calculate average cell voltage in millivolts // Calculate average cell voltage in millivolts
uint16_t avgCellVoltage = compensatedPackVoltageMv / cellCount; uint16_t avgCellVoltage = compensatedPackVoltageMv / cellCount;
#ifdef DEBUG_LOG
logging.print("Pack: ");
logging.print(packVoltage / 10.0);
logging.print("V, Current: ");
logging.print(currentAmps / 10.0);
logging.print("A, Drop: ");
logging.print(voltageDrop / 1000.0);
logging.print("V, Comp Pack: ");
logging.print(compensatedPackVoltageMv / 1000.0);
logging.print("V, Avg Cell: ");
logging.print(avgCellVoltage);
logging.println("mV");
#endif
// Use the cell voltage lookup table to estimate SOC // Use the cell voltage lookup table to estimate SOC
return estimateSOCFromCell(avgCellVoltage); return estimateSOCFromCell(avgCellVoltage);
} }
// Fix: Change parameter types to uint16_t to match SOC values // Fix: Change parameter types to uint16_t to match SOC values
uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) { uint16_t KiaEGmpBattery::selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
if (SOC_low == 0 || SOC_high == 0) { if (SOC_low == 0 || SOC_high == 0) {
return 0; // If either value is 0, return 0 return 0; // If either value is 0, return 0
} }
@ -121,536 +70,9 @@ uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
return (SOC_low < SOC_high) ? SOC_low : SOC_high; // Otherwise, return the lowest value return (SOC_low < SOC_high) ? SOC_low : SOC_high; // Otherwise, return the lowest value
} }
/* These messages are needed for contactor closing */ void KiaEGmpBattery::set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell) {
unsigned long startMillis = 0;
uint8_t messageIndex = 0;
uint8_t messageDelays[63] = {0, 0, 5, 10, 10, 15, 19, 19, 20, 20, 25, 30, 30, 35, 40, 40,
45, 49, 49, 50, 50, 52, 53, 53, 54, 55, 60, 60, 65, 67, 67, 70,
70, 75, 77, 77, 80, 80, 85, 90, 90, 95, 100, 100, 105, 110, 110, 115,
119, 119, 120, 120, 125, 130, 130, 135, 140, 140, 145, 149, 149, 150, 150};
CAN_frame message_1 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x62, 0x36, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_2 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xd4, 0x1b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_3 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x24, 0x9b, 0x7b, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_4 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x24, 0x6f, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_5 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x92, 0x42, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_6 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xd7, 0x05, 0x7c, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_7 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x30A,
.data = {0xb1, 0xe0, 0x26, 0x08, 0x54, 0x01, 0x04, 0x15, 0x00, 0x1a, 0x76, 0x00, 0x25, 0x01, 0x10, 0x27,
0x4f, 0x06, 0x18, 0x04, 0x33, 0x15, 0x34, 0x28, 0x00, 0x00, 0x10, 0x06, 0x21, 0x00, 0x4b, 0x06}};
CAN_frame message_8 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x320,
.data = {0xc6, 0xab, 0x26, 0x41, 0x00, 0x00, 0x01, 0x3c, 0xac, 0x0d, 0x40, 0x20, 0x05, 0xc8, 0xa0, 0x03,
0x40, 0x20, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_9 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xee, 0x84, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_10 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x58, 0xa9, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_11 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x91, 0x5c, 0x7d, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_12 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xa8, 0xdd, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_13 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x1e, 0xf0, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_14 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x5b, 0xb7, 0x7e, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_15 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xec, 0x6d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_16 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x5a, 0x40, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_17 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x1d, 0xee, 0x7f, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_18 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2B5,
.data = {0xbd, 0xb2, 0x42, 0x00, 0x00, 0x00, 0x00, 0x80, 0x59, 0x00, 0x2b, 0x00, 0x00, 0x04, 0x00, 0x00,
0xfa, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_19 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E0,
.data = {0xc1, 0xf2, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x70, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_20 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xaa, 0x34, 0x91, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_21 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x1c, 0x19, 0x91, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_22 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2D5,
.data = {0x79, 0xfb, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_23 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2EA,
.data = {0x6e, 0xbb, 0xa0, 0x0d, 0x04, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_24 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x306,
.data = {0x00, 0x00, 0x00, 0xd2, 0x06, 0x92, 0x05, 0x34, 0x07, 0x8e, 0x08, 0x73, 0x05, 0x80, 0x05, 0x83,
0x05, 0x73, 0x05, 0x80, 0x05, 0xed, 0x01, 0xdd, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_25 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x308,
.data = {0xbe, 0x84, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x75, 0x6c, 0x86, 0x0d, 0xfb, 0xdf, 0x03, 0x36, 0xc3, 0x86, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_26 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x6b, 0xa2, 0x80, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_27 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x60, 0xdf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_28 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xd6, 0xf2, 0x92, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_29 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x2d, 0xfb, 0x81, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_30 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x33A,
.data = {0x1a, 0x23, 0x26, 0x10, 0x27, 0x4f, 0x06, 0x00, 0xf8, 0x1b, 0x19, 0x04, 0x30, 0x01, 0x00, 0x06,
0x00, 0x00, 0x00, 0x2e, 0x2d, 0x81, 0x25, 0x20, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_31 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x350,
.data = {0x26, 0x82, 0x26, 0xf4, 0x01, 0x00, 0x00, 0x50, 0x90, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_32 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x26, 0x86, 0x93, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_33 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x90, 0xab, 0x93, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_34 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xe7, 0x10, 0x82, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_35 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E5,
.data = {0x69, 0x8a, 0x3f, 0x01, 0x00, 0x00, 0x00, 0x15, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_36 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x3B5,
.data = {0xa3, 0xc8, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x36, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_37 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xd5, 0x18, 0x94, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_38 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x63, 0x35, 0x94, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_39 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xa1, 0x49, 0x83, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_40 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x93, 0x41, 0x95, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_41 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x25, 0x6c, 0x95, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_42 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x52, 0xd7, 0x84, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_43 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x59, 0xaa, 0x96, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_44 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xef, 0x87, 0x96, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_45 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x14, 0x8e, 0x85, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_46 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x1f, 0xf3, 0x97, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_47 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xa9, 0xde, 0x97, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_48 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xde, 0x65, 0x86, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_49 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x30A,
.data = {0xd3, 0x11, 0x27, 0x08, 0x54, 0x01, 0x04, 0x15, 0x00, 0x1a, 0x76, 0x00, 0x25, 0x01, 0x10, 0x27,
0x4f, 0x06, 0x19, 0x04, 0x33, 0x15, 0x34, 0x28, 0x00, 0x00, 0x10, 0x06, 0x21, 0x00, 0x4b, 0x06}};
CAN_frame message_50 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x320,
.data = {0x80, 0xf2, 0x27, 0x41, 0x00, 0x00, 0x01, 0x3c, 0xac, 0x0d, 0x40, 0x20, 0x05, 0xc8, 0xa0, 0x03,
0x40, 0x20, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_51 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x9e, 0x87, 0x98, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_52 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x28, 0xaa, 0x98, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_53 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x98, 0x3c, 0x87, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_54 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xd8, 0xde, 0x99, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_55 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x6e, 0xf3, 0x99, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_56 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x19, 0x48, 0x88, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_57 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x12, 0x35, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_58 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xa4, 0x18, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_59 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x5f, 0x11, 0x89, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
CAN_frame message_60 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2B5,
.data = {0xfb, 0xeb, 0x43, 0x00, 0x00, 0x00, 0x00, 0x80, 0x59, 0x00, 0x2b, 0x00, 0x00, 0x04, 0x00, 0x00,
0xfa, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_61 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2C0,
.data = {0xcc, 0xcd, 0xa2, 0x21, 0x00, 0xa1, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_62 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E0,
.data = {0x87, 0xab, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x70, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame message_63 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x54, 0x6c, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame* messages[] = {&message_1, &message_2, &message_3, &message_4, &message_5, &message_6, &message_7,
&message_8, &message_9, &message_10, &message_11, &message_12, &message_13, &message_14,
&message_15, &message_16, &message_17, &message_18, &message_19, &message_20, &message_21,
&message_22, &message_23, &message_24, &message_25, &message_26, &message_27, &message_28,
&message_29, &message_30, &message_31, &message_32, &message_33, &message_34, &message_35,
&message_36, &message_37, &message_38, &message_39, &message_40, &message_41, &message_42,
&message_43, &message_44, &message_45, &message_46, &message_47, &message_48, &message_49,
&message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
/* PID polling messages */
CAN_frame EGMP_7E4 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 01
CAN_frame EGMP_7E4_ack = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
void 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);
} }
} }
@ -682,7 +104,7 @@ void KiaEGmpBattery::set_voltage_minmax_limits() {
} }
} }
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) { uint8_t KiaEGmpBattery::calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value; uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
crc = crc8_table[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256]; crc = crc8_table[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) % 256];
@ -690,19 +112,19 @@ static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_
return crc; return crc;
} }
void KiaEGmpBattery:: void KiaEGmpBattery::update_values() {
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
#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%
@ -722,12 +144,12 @@ void KiaEGmpBattery::
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
@ -749,67 +171,6 @@ void KiaEGmpBattery::
if (leadAcidBatteryVoltage < 110) { if (leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
} }
/* Safeties verified. Perform USB serial printout if configured to do so */
#ifdef DEBUG_LOG
logging.println(); //sepatator
logging.println("Values from battery: ");
logging.print("SOC BMS: ");
logging.print((uint16_t)SOC_BMS / 10.0, 1);
logging.print("% | SOC Display: ");
logging.print((uint16_t)SOC_Display / 10.0, 1);
logging.print("% | SOH ");
logging.print((uint16_t)batterySOH / 10.0, 1);
logging.println("%");
logging.print((int16_t)batteryAmps / 10.0, 1);
logging.print(" Amps | ");
logging.print((uint16_t)batteryVoltage / 10.0, 1);
logging.print(" Volts | ");
logging.print((int16_t)datalayer.battery.status.active_power_W);
logging.println(" Watts");
logging.print("Allowed Charge ");
logging.print((uint16_t)allowedChargePower * 10);
logging.print(" W | Allowed Discharge ");
logging.print((uint16_t)allowedDischargePower * 10);
logging.println(" W");
logging.print("MaxCellVolt ");
logging.print(CellVoltMax_mV);
logging.print(" mV No ");
logging.print(CellVmaxNo);
logging.print(" | MinCellVolt ");
logging.print(CellVoltMin_mV);
logging.print(" mV No ");
logging.println(CellVminNo);
logging.print("TempHi ");
logging.print((int16_t)temperatureMax);
logging.print("°C TempLo ");
logging.print((int16_t)temperatureMin);
logging.print("°C WaterInlet ");
logging.print((int8_t)temperature_water_inlet);
logging.print("°C PowerRelay ");
logging.print((int8_t)powerRelayTemperature * 2);
logging.println("°C");
logging.print("Aux12volt: ");
logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
logging.println("V | ");
logging.print("BmsManagementMode ");
logging.print((uint8_t)batteryManagementMode, BIN);
if (bitRead((uint8_t)BMS_ign, 2) == 1) {
logging.print(" | BmsIgnition ON");
} else {
logging.print(" | BmsIgnition OFF");
}
if (bitRead((uint8_t)batteryRelay, 0) == 1) {
logging.print(" | PowerRelay ON");
} else {
logging.print(" | PowerRelay OFF");
}
logging.print(" | Inverter ");
logging.print(inverterVoltage);
logging.println(" Volts");
#endif
} }
// Getter implementations for HTML renderer // Getter implementations for HTML renderer
@ -890,14 +251,10 @@ void KiaEGmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x7EC: case 0x7EC:
// print_canfd_frame(frame);
switch (rx_frame.data.u8[0]) { switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header" case 0x10: //"PID Header"
// logging.println ("Send ack");
poll_data_pid = rx_frame.data.u8[4]; poll_data_pid = rx_frame.data.u8[4];
// if (rx_frame.data.u8[4] == poll_data_pid) { transmit_can_frame(&EGMP_7E4_ack); //Send ack to BMS
transmit_can_frame(&EGMP_7E4_ack); //Send ack to BMS if the same frame is sent as polled
// }
break; break;
case 0x21: //First frame in PID group case 0x21: //First frame in PID group
if (poll_data_pid == 1) { if (poll_data_pid == 1) {

View file

@ -3,11 +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;
#ifdef KIA_E_GMP_BATTERY
#define SELECTED_BATTERY_CLASS KiaEGmpBattery
#endif
class KiaEGmpBattery : public CanBattery { class KiaEGmpBattery : public CanBattery {
public: public:
@ -30,6 +26,10 @@ class KiaEGmpBattery : public CanBattery {
private: private:
KiaEGMPHtmlRenderer renderer; KiaEGMPHtmlRenderer renderer;
uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps); uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps);
uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high);
uint16_t estimateSOCFromCell(uint16_t cellVoltage);
uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value);
void set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell);
void set_voltage_minmax_limits(); void set_voltage_minmax_limits();
static const int MAX_PACK_VOLTAGE_DV = 8064; //5000 = 500.0V static const int MAX_PACK_VOLTAGE_DV = 8064; //5000 = 500.0V
@ -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
@ -81,9 +79,510 @@ class KiaEGmpBattery : public CanBattery {
int8_t powerRelayTemperature = 10; int8_t powerRelayTemperature = 10;
int8_t heatertemp = 20; int8_t heatertemp = 20;
bool set_voltage_limits = false; bool set_voltage_limits = false;
uint8_t ticks_200ms_counter = 0;
uint8_t EGMP_1CF_counter = 0; const unsigned char crc8_table[256] = {
uint8_t EGMP_3XF_counter = 0; // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
// Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100;
const uint16_t SOC[101] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9100, 9000, 8900, 8800, 8700, 8600,
8500, 8400, 8300, 8200, 8100, 8000, 7900, 7800, 7700, 7600, 7500, 7400, 7300, 7200, 7100,
7000, 6900, 6800, 6700, 6600, 6500, 6400, 6300, 6200, 6100, 6000, 5900, 5800, 5700, 5600,
5500, 5400, 5300, 5200, 5100, 5000, 4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100,
4000, 3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100,
1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0};
const uint16_t voltage[101] = {
4200, 4173, 4148, 4124, 4102, 4080, 4060, 4041, 4023, 4007, 3993, 3980, 3969, 3959, 3953, 3950, 3941,
3932, 3924, 3915, 3907, 3898, 3890, 3881, 3872, 3864, 3855, 3847, 3838, 3830, 3821, 3812, 3804, 3795,
3787, 3778, 3770, 3761, 3752, 3744, 3735, 3727, 3718, 3710, 3701, 3692, 3684, 3675, 3667, 3658, 3650,
3641, 3632, 3624, 3615, 3607, 3598, 3590, 3581, 3572, 3564, 3555, 3547, 3538, 3530, 3521, 3512, 3504,
3495, 3487, 3478, 3470, 3461, 3452, 3444, 3435, 3427, 3418, 3410, 3401, 3392, 3384, 3375, 3367, 3358,
3350, 3338, 3325, 3313, 3299, 3285, 3271, 3255, 3239, 3221, 3202, 3180, 3156, 3127, 3090, 3000};
/* These messages are needed for contactor closing */
unsigned long startMillis = 0;
uint8_t messageIndex = 0;
uint8_t messageDelays[63] = {0, 0, 5, 10, 10, 15, 19, 19, 20, 20, 25, 30, 30, 35, 40, 40,
45, 49, 49, 50, 50, 52, 53, 53, 54, 55, 60, 60, 65, 67, 67, 70,
70, 75, 77, 77, 80, 80, 85, 90, 90, 95, 100, 100, 105, 110, 110, 115,
119, 119, 120, 120, 125, 130, 130, 135, 140, 140, 145, 149, 149, 150, 150};
static constexpr CAN_frame message_1 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x62, 0x36, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_2 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xd4, 0x1b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_3 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x24, 0x9b, 0x7b, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_4 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x24, 0x6f, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_5 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x92, 0x42, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_6 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xd7, 0x05, 0x7c, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_7 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x30A,
.data = {0xb1, 0xe0, 0x26, 0x08, 0x54, 0x01, 0x04, 0x15, 0x00, 0x1a, 0x76, 0x00, 0x25, 0x01, 0x10, 0x27,
0x4f, 0x06, 0x18, 0x04, 0x33, 0x15, 0x34, 0x28, 0x00, 0x00, 0x10, 0x06, 0x21, 0x00, 0x4b, 0x06}};
static constexpr CAN_frame message_8 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x320,
.data = {0xc6, 0xab, 0x26, 0x41, 0x00, 0x00, 0x01, 0x3c, 0xac, 0x0d, 0x40, 0x20, 0x05, 0xc8, 0xa0, 0x03,
0x40, 0x20, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_9 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xee, 0x84, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_10 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x58, 0xa9, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_11 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x91, 0x5c, 0x7d, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_12 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xa8, 0xdd, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_13 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x1e, 0xf0, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_14 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x5b, 0xb7, 0x7e, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_15 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xec, 0x6d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_16 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x5a, 0x40, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_17 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x1d, 0xee, 0x7f, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_18 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2B5,
.data = {0xbd, 0xb2, 0x42, 0x00, 0x00, 0x00, 0x00, 0x80, 0x59, 0x00, 0x2b, 0x00, 0x00, 0x04, 0x00, 0x00,
0xfa, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_19 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E0,
.data = {0xc1, 0xf2, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x70, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_20 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xaa, 0x34, 0x91, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_21 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x1c, 0x19, 0x91, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_22 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2D5,
.data = {0x79, 0xfb, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_23 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2EA,
.data = {0x6e, 0xbb, 0xa0, 0x0d, 0x04, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_24 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x306,
.data = {0x00, 0x00, 0x00, 0xd2, 0x06, 0x92, 0x05, 0x34, 0x07, 0x8e, 0x08, 0x73, 0x05, 0x80, 0x05, 0x83,
0x05, 0x73, 0x05, 0x80, 0x05, 0xed, 0x01, 0xdd, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_25 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x308,
.data = {0xbe, 0x84, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x75, 0x6c, 0x86, 0x0d, 0xfb, 0xdf, 0x03, 0x36, 0xc3, 0x86, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_26 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x6b, 0xa2, 0x80, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_27 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x60, 0xdf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_28 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xd6, 0xf2, 0x92, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_29 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x2d, 0xfb, 0x81, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_30 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x33A,
.data = {0x1a, 0x23, 0x26, 0x10, 0x27, 0x4f, 0x06, 0x00, 0xf8, 0x1b, 0x19, 0x04, 0x30, 0x01, 0x00, 0x06,
0x00, 0x00, 0x00, 0x2e, 0x2d, 0x81, 0x25, 0x20, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_31 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x350,
.data = {0x26, 0x82, 0x26, 0xf4, 0x01, 0x00, 0x00, 0x50, 0x90, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_32 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x26, 0x86, 0x93, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_33 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x90, 0xab, 0x93, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_34 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xe7, 0x10, 0x82, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_35 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E5,
.data = {0x69, 0x8a, 0x3f, 0x01, 0x00, 0x00, 0x00, 0x15, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_36 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x3B5,
.data = {0xa3, 0xc8, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x36, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_37 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xd5, 0x18, 0x94, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_38 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x63, 0x35, 0x94, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_39 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xa1, 0x49, 0x83, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_40 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x93, 0x41, 0x95, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_41 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x25, 0x6c, 0x95, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_42 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x52, 0xd7, 0x84, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_43 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x59, 0xaa, 0x96, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_44 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xef, 0x87, 0x96, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_45 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x14, 0x8e, 0x85, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_46 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x1f, 0xf3, 0x97, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_47 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xa9, 0xde, 0x97, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_48 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0xde, 0x65, 0x86, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_49 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x30A,
.data = {0xd3, 0x11, 0x27, 0x08, 0x54, 0x01, 0x04, 0x15, 0x00, 0x1a, 0x76, 0x00, 0x25, 0x01, 0x10, 0x27,
0x4f, 0x06, 0x19, 0x04, 0x33, 0x15, 0x34, 0x28, 0x00, 0x00, 0x10, 0x06, 0x21, 0x00, 0x4b, 0x06}};
static constexpr CAN_frame message_50 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x320,
.data = {0x80, 0xf2, 0x27, 0x41, 0x00, 0x00, 0x01, 0x3c, 0xac, 0x0d, 0x40, 0x20, 0x05, 0xc8, 0xa0, 0x03,
0x40, 0x20, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_51 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x9e, 0x87, 0x98, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_52 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x28, 0xaa, 0x98, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_53 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x98, 0x3c, 0x87, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_54 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0xd8, 0xde, 0x99, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_55 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0x6e, 0xf3, 0x99, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_56 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x19, 0x48, 0x88, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_57 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x12, 0x35, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_58 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x120,
.data = {0xa4, 0x18, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x37, 0x35, 0x37, 0x37,
0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_59 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x19A,
.data = {0x5f, 0x11, 0x89, 0x55, 0x44, 0x64, 0xd8, 0x1b, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x11, 0x52,
0x00, 0x12, 0x02, 0x64, 0x00, 0x00, 0x00, 0x08, 0x13, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00}};
static constexpr CAN_frame message_60 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2B5,
.data = {0xfb, 0xeb, 0x43, 0x00, 0x00, 0x00, 0x00, 0x80, 0x59, 0x00, 0x2b, 0x00, 0x00, 0x04, 0x00, 0x00,
0xfa, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_61 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2C0,
.data = {0xcc, 0xcd, 0xa2, 0x21, 0x00, 0xa1, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_62 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x2E0,
.data = {0x87, 0xab, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x70, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static constexpr CAN_frame message_63 = {
.FD = true,
.ext_ID = false,
.DLC = 32,
.ID = 0x10A,
.data = {0x54, 0x6c, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0x00, 0x36, 0x39, 0x35, 0x35,
0xc9, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x35, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00}};
const CAN_frame* messages[63] = {
&message_1, &message_2, &message_3, &message_4, &message_5, &message_6, &message_7, &message_8,
&message_9, &message_10, &message_11, &message_12, &message_13, &message_14, &message_15, &message_16,
&message_17, &message_18, &message_19, &message_20, &message_21, &message_22, &message_23, &message_24,
&message_25, &message_26, &message_27, &message_28, &message_29, &message_30, &message_31, &message_32,
&message_33, &message_34, &message_35, &message_36, &message_37, &message_38, &message_39, &message_40,
&message_41, &message_42, &message_43, &message_44, &message_45, &message_46, &message_47, &message_48,
&message_49, &message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
/* PID polling messages */
CAN_frame EGMP_7E4 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 01
static constexpr CAN_frame EGMP_7E4_ack = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
}; };
#endif #endif

View file

@ -1,4 +1,5 @@
#include "KIA-HYUNDAI-64-BATTERY.h" #include "KIA-HYUNDAI-64-BATTERY.h"
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"

View file

@ -5,10 +5,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "KIA-HYUNDAI-64-HTML.h" #include "KIA-HYUNDAI-64-HTML.h"
#ifdef KIA_HYUNDAI_64_BATTERY
#define SELECTED_BATTERY_CLASS KiaHyundai64Battery
#endif
class KiaHyundai64Battery : public CanBattery { class KiaHyundai64Battery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.
@ -70,7 +66,7 @@ class KiaHyundai64Battery : public CanBattery {
uint16_t CellVoltMin_mV = 3700; uint16_t CellVoltMin_mV = 3700;
uint16_t allowedDischargePower = 0; uint16_t allowedDischargePower = 0;
uint16_t allowedChargePower = 0; uint16_t allowedChargePower = 0;
uint16_t batteryVoltage = 0; uint16_t batteryVoltage = 3700;
uint16_t inverterVoltageFrameHigh = 0; uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0; uint16_t inverterVoltage = 0;
uint16_t cellvoltages_mv[98]; uint16_t cellvoltages_mv[98];

View file

@ -1,6 +1,7 @@
#ifndef _KIA_HYUNDAI_64_HTML_H #ifndef _KIA_HYUNDAI_64_HTML_H
#define _KIA_HYUNDAI_64_HTML_H #define _KIA_HYUNDAI_64_HTML_H
#include <cstring> //For unit test
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/webserver/BatteryHtmlRenderer.h" #include "../devboard/webserver/BatteryHtmlRenderer.h"

View file

@ -1,4 +1,5 @@
#include "KIA-HYUNDAI-HYBRID-BATTERY.h" #include "KIA-HYUNDAI-HYBRID-BATTERY.h"
#include <cstring> //For unit test
#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"
@ -43,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;
@ -52,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:
@ -199,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);
} }
} }
} }

View file

@ -2,10 +2,6 @@
#define KIA_HYUNDAI_HYBRID_BATTERY_H #define KIA_HYUNDAI_HYBRID_BATTERY_H
#include "CanBattery.h" #include "CanBattery.h"
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
#define SELECTED_BATTERY_CLASS KiaHyundaiHybridBattery
#endif
class KiaHyundaiHybridBattery : public CanBattery { class KiaHyundaiHybridBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -38,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,

View file

@ -1,6 +1,7 @@
#include "MEB-BATTERY.h" #include "MEB-BATTERY.h"
#include <Arduino.h> #include <Arduino.h>
#include <algorithm> // For std::min and std::max #include <algorithm> // For std::min and std::max
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../communication/can/obd.h" #include "../communication/can/obd.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
@ -171,9 +172,7 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint32_t address) {
magicByte = MB16A954A6[counter]; magicByte = MB16A954A6[counter];
break; break;
default: // this won't lead to correct CRC checksums default: // this won't lead to correct CRC checksums
#ifdef DEBUG_LOG
logging.println("Checksum request unknown"); logging.println("Checksum request unknown");
#endif
magicByte = 0x00; magicByte = 0x00;
break; break;
} }
@ -202,15 +201,15 @@ uint8_t vw_crc_calc(uint8_t* inputBytes, uint8_t length, uint32_t address) {
void MebBattery:: void MebBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100 datalayer_battery->status.real_soc = battery_SOC * 5; //*0.05*100
datalayer.battery.status.voltage_dV = BMS_voltage * 2.5; // *0.25*10 datalayer_battery->status.voltage_dV = BMS_voltage * 2.5; // *0.25*10
datalayer.battery.status.current_dA = (BMS_current - 16300); // 0.1 * 10 datalayer_battery->status.current_dA = (BMS_current - 16300); // 0.1 * 10
if (nof_cells_determined) { if (nof_cells_determined) {
datalayer.battery.info.total_capacity_Wh = datalayer_battery->info.total_capacity_Wh =
((float)datalayer.battery.info.number_of_cells) * 3.67 * ((float)BMS_capacity_ah) * 0.2 * 1.02564; ((float)datalayer_battery->info.number_of_cells) * 3.67 * ((float)BMS_capacity_ah) * 0.2 * 1.02564;
// The factor 1.02564 = 1/0.975 is to correct for bottom 2.5% which is reported by the remaining_capacity_Wh, // The factor 1.02564 = 1/0.975 is to correct for bottom 2.5% which is reported by the remaining_capacity_Wh,
// but which is not actually usable, but if we do not include it, the remaining_capacity_Wh can be larger than // but which is not actually usable, but if we do not include it, the remaining_capacity_Wh can be larger than
// the total_capacity_Wh. // the total_capacity_Wh.
@ -218,32 +217,32 @@ void MebBattery::
// total_capacity_Wh calculated above. // total_capacity_Wh calculated above.
int Wh_max = 61832 * 0.935; // 108 cells int Wh_max = 61832 * 0.935; // 108 cells
if (datalayer.battery.info.number_of_cells <= 84) if (datalayer_battery->info.number_of_cells <= 84)
Wh_max = 48091 * 0.9025; Wh_max = 48091 * 0.9025;
else if (datalayer.battery.info.number_of_cells <= 96) else if (datalayer_battery->info.number_of_cells <= 96)
Wh_max = 82442 * 0.9025; Wh_max = 82442 * 0.9025;
if (BMS_capacity_ah > 0) if (BMS_capacity_ah > 0)
datalayer.battery.status.soh_pptt = 10000 * datalayer.battery.info.total_capacity_Wh / (Wh_max * 1.02564); datalayer_battery->status.soh_pptt = 10000 * datalayer_battery->info.total_capacity_Wh / (Wh_max * 1.02564);
} }
datalayer.battery.status.remaining_capacity_Wh = usable_energy_amount_Wh * 5; datalayer_battery->status.remaining_capacity_Wh = usable_energy_amount_Wh * 5;
datalayer.battery.status.max_charge_power_W = (max_charge_power_watt * 100); datalayer_battery->status.max_charge_power_W = (max_charge_power_watt * 100);
datalayer.battery.status.max_discharge_power_W = (max_discharge_power_watt * 100); datalayer_battery->status.max_discharge_power_W = (max_discharge_power_watt * 100);
//Power in watts, Negative = charging batt //Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W = datalayer_battery->status.active_power_W =
((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.temperature_min_dC = actual_temperature_lowest_C*5 -400; // We use the value below, because it has better accuracy // datalayer.battery.status.temperature_min_dC = actual_temperature_lowest_C*5 -400; // We use the value below, because it has better accuracy
datalayer.battery.status.temperature_min_dC = (battery_min_temp * 10) / 64; datalayer_battery->status.temperature_min_dC = (battery_min_temp * 10) / 64;
// datalayer.battery.status.temperature_max_dC = actual_temperature_highest_C*5 -400; // We use the value below, because it has better accuracy // datalayer.battery.status.temperature_max_dC = actual_temperature_highest_C*5 -400; // We use the value below, because it has better accuracy
datalayer.battery.status.temperature_max_dC = (battery_max_temp * 10) / 64; datalayer_battery->status.temperature_max_dC = (battery_max_temp * 10) / 64;
//Map all cell voltages to the global array //Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_polled, 108 * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_polled, 108 * sizeof(uint16_t));
if (service_disconnect_switch_missing) { if (service_disconnect_switch_missing) {
set_event(EVENT_HVIL_FAILURE, 1); set_event(EVENT_HVIL_FAILURE, 1);
@ -310,9 +309,7 @@ void MebBattery::
void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) { void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
last_can_msg_timestamp = millis(); last_can_msg_timestamp = millis();
if (first_can_msg == 0) { if (first_can_msg == 0) {
#ifdef DEBUG_LOG
logging.printf("MEB: First CAN msg received\n"); logging.printf("MEB: First CAN msg received\n");
#endif
first_can_msg = last_can_msg_timestamp; first_can_msg = last_can_msg_timestamp;
} }
@ -326,9 +323,7 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
if (rx_frame.data.u8[0] != if (rx_frame.data.u8[0] !=
vw_crc_calc(rx_frame.data.u8, rx_frame.DLC, rx_frame.ID)) { //If CRC does not match calc vw_crc_calc(rx_frame.data.u8, rx_frame.DLC, rx_frame.ID)) { //If CRC does not match calc
datalayer.battery.status.CAN_error_counter++; datalayer.battery.status.CAN_error_counter++;
#ifdef DEBUG_LOG
logging.printf("MEB: Msg 0x%04X CRC error\n", rx_frame.ID); logging.printf("MEB: Msg 0x%04X CRC error\n", rx_frame.ID);
#endif
return; return;
} }
default: default:
@ -337,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);
@ -352,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
@ -366,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
@ -377,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;
@ -385,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);
@ -394,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;
@ -407,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
@ -416,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;
@ -430,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
@ -700,29 +706,23 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case 3: // EXTERN CHARGING case 3: // EXTERN CHARGING
case 4: // AC_CHARGING case 4: // AC_CHARGING
case 6: // DC_CHARGING case 6: // DC_CHARGING
#ifdef DEBUG_LOG
if (!datalayer.system.status.battery_allows_contactor_closing) if (!datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB: Contactors closed\n"); logging.printf("MEB: Contactors closed\n");
#endif
if (datalayer.battery.status.real_bms_status != BMS_FAULT) if (datalayer.battery.status.real_bms_status != BMS_FAULT)
datalayer.battery.status.real_bms_status = BMS_ACTIVE; datalayer.battery.status.real_bms_status = BMS_ACTIVE;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
hv_requested = false; hv_requested = false;
break; break;
case 5: // Error case 5: // Error
#ifdef DEBUG_LOG
if (datalayer.system.status.battery_allows_contactor_closing) if (datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB: Contactors opened\n"); logging.printf("MEB: Contactors opened\n");
#endif
datalayer.battery.status.real_bms_status = BMS_FAULT; datalayer.battery.status.real_bms_status = BMS_FAULT;
datalayer.system.status.battery_allows_contactor_closing = false; datalayer.system.status.battery_allows_contactor_closing = false;
hv_requested = false; hv_requested = false;
break; break;
case 7: // Init case 7: // Init
#ifdef DEBUG_LOG
if (datalayer.system.status.battery_allows_contactor_closing) if (datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB: Contactors opened\n"); logging.printf("MEB: Contactors opened\n");
#endif
if (datalayer.battery.status.real_bms_status != BMS_FAULT) if (datalayer.battery.status.real_bms_status != BMS_FAULT)
datalayer.battery.status.real_bms_status = BMS_STANDBY; datalayer.battery.status.real_bms_status = BMS_STANDBY;
datalayer.system.status.battery_allows_contactor_closing = false; datalayer.system.status.battery_allows_contactor_closing = false;
@ -730,10 +730,8 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break; break;
case 2: // BALANCING case 2: // BALANCING
default: default:
#ifdef DEBUG_LOG
if (datalayer.system.status.battery_allows_contactor_closing) if (datalayer.system.status.battery_allows_contactor_closing)
logging.printf("MEB: Contactors opened\n"); logging.printf("MEB: Contactors opened\n");
#endif
if (datalayer.battery.status.real_bms_status != BMS_FAULT) if (datalayer.battery.status.real_bms_status != BMS_FAULT)
datalayer.battery.status.real_bms_status = BMS_STANDBY; datalayer.battery.status.real_bms_status = BMS_STANDBY;
datalayer.system.status.battery_allows_contactor_closing = false; datalayer.system.status.battery_allows_contactor_closing = false;
@ -1273,16 +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:
#ifdef DEBUG_LOG
logging.printf("Unknown CAN frame received:\n");
dump_can_frame(rx_frame, MSG_RX);
#endif
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;
@ -1292,10 +1285,8 @@ void MebBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
void MebBattery::transmit_can(unsigned long currentMillis) { void MebBattery::transmit_can(unsigned long currentMillis) {
if (currentMillis - last_can_msg_timestamp > 500) { if (currentMillis - last_can_msg_timestamp > 500) {
#ifdef DEBUG_LOG
if (first_can_msg) if (first_can_msg)
logging.printf("MEB: No CAN msg received for 500ms\n"); logging.printf("MEB: No CAN msg received for 500ms\n");
#endif
can_msg_received = RX_DEFAULT; can_msg_received = RX_DEFAULT;
first_can_msg = 0; first_can_msg = 0;
if (datalayer.battery.status.real_bms_status != BMS_FAULT) { if (datalayer.battery.status.real_bms_status != BMS_FAULT) {
@ -1373,7 +1364,6 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
((int32_t)datalayer_extended.meb.BMS_voltage_intermediate_dV)) < 200))))) { ((int32_t)datalayer_extended.meb.BMS_voltage_intermediate_dV)) < 200))))) {
hv_requested = true; hv_requested = true;
datalayer.system.settings.start_precharging = false; datalayer.system.settings.start_precharging = false;
#ifdef DEBUG_LOG
if (MEB_503.data.u8[3] == BMS_TARGET_HV_OFF) { if (MEB_503.data.u8[3] == BMS_TARGET_HV_OFF) {
logging.printf("MEB: Requesting HV\n"); logging.printf("MEB: Requesting HV\n");
} }
@ -1385,7 +1375,6 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
logging.printf("MEB: Precharge bit set to inactive\n"); logging.printf("MEB: Precharge bit set to inactive\n");
} }
} }
#endif
MEB_503.data.u8[1] = MEB_503.data.u8[1] =
0x30 | (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00); 0x30 | (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00);
MEB_503.data.u8[3] = BMS_TARGET_AC_CHARGING; MEB_503.data.u8[3] = BMS_TARGET_AC_CHARGING;
@ -1399,7 +1388,6 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
datalayer.system.settings.start_precharging = true; datalayer.system.settings.start_precharging = true;
} }
#ifdef DEBUG_LOG
if (MEB_503.data.u8[3] != BMS_TARGET_HV_OFF) { if (MEB_503.data.u8[3] != BMS_TARGET_HV_OFF) {
logging.printf("MEB: Requesting HV_OFF\n"); logging.printf("MEB: Requesting HV_OFF\n");
} }
@ -1411,7 +1399,6 @@ void MebBattery::transmit_can(unsigned long currentMillis) {
logging.printf("MEB: Precharge bit set to inactive\n"); logging.printf("MEB: Precharge bit set to inactive\n");
} }
} }
#endif
MEB_503.data.u8[1] = MEB_503.data.u8[1] =
0x10 | (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00); 0x10 | (datalayer.system.status.precharge_status == AUTO_PRECHARGE_PRECHARGING ? 0x80 : 0x00);
MEB_503.data.u8[3] = BMS_TARGET_HV_OFF; MEB_503.data.u8[3] = BMS_TARGET_HV_OFF;

View file

@ -3,12 +3,21 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "MEB-HTML.h" #include "MEB-HTML.h"
#ifdef MEB_BATTERY
#define SELECTED_BATTERY_CLASS MebBattery
#endif
class MebBattery : public CanBattery { class MebBattery : public CanBattery {
public: public:
// Use this constructor for the second battery.
MebBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_MEB* extended, CAN_Interface targetCan)
: CanBattery(targetCan) {
datalayer_battery = datalayer_ptr;
BMS_voltage = 0;
}
// Use the default constructor to create the first or single battery.
MebBattery() {
datalayer_battery = &datalayer.battery;
datalayer_meb = &datalayer_extended.meb;
}
virtual void setup(void); virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values(); virtual void update_values();
@ -21,6 +30,10 @@ class MebBattery : public CanBattery {
private: private:
MebHtmlRenderer renderer; MebHtmlRenderer renderer;
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_MEB* datalayer_meb;
static const int MAX_PACK_VOLTAGE_84S_DV = 3528; //5000 = 500.0V static const int MAX_PACK_VOLTAGE_84S_DV = 3528; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_84S_DV = 2520; static const int MIN_PACK_VOLTAGE_84S_DV = 2520;
static const int MAX_PACK_VOLTAGE_96S_DV = 4032; static const int MAX_PACK_VOLTAGE_96S_DV = 4032;
@ -361,7 +374,7 @@ class MebBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x1C40007B, // SOC 02 8C .ID = 0x1C40007B, // SOC 02 8C
.data = {0x03, 0x22, 0x02, 0x8C, 0x55, 0x55, 0x55, 0x55}}; .data = {0x03, 0x22, 0x02, 0x8C, 0x55, 0x55, 0x55, 0x55}};
CAN_frame MEB_ACK_FRAME = {.FD = true, static constexpr CAN_frame MEB_ACK_FRAME = {.FD = true,
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x1C40007B, // Ack .ID = 0x1C40007B, // Ack
@ -392,7 +405,7 @@ class MebBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x6B2, .ID = 0x6B2,
.data = {0x6A, 0xA7, 0x37, 0x80, 0xC9, 0xBD, 0xF6, 0xC2}}; .data = {0x6A, 0xA7, 0x37, 0x80, 0xC9, 0xBD, 0xF6, 0xC2}};
CAN_frame MEB_17FC007B_poll = {.FD = true, // Non period request static constexpr CAN_frame MEB_17FC007B_poll = {.FD = true, // Non period request
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x17FC007B, .ID = 0x17FC007B,
@ -402,14 +415,14 @@ class MebBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x1A5555A6, .ID = 0x1A5555A6,
.data = {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_585 = { static constexpr CAN_frame MEB_585 = {
.FD = true, .FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x585, .ID = 0x585,
.data = {0xCF, 0x38, 0xAF, 0x5B, 0x25, 0x00, 0x00, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log .data = {0xCF, 0x38, 0xAF, 0x5B, 0x25, 0x00, 0x00, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log
// .data = {0xCF, 0x38, 0x20, 0x02, 0x25, 0xF7, 0x30, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log // .data = {0xCF, 0x38, 0x20, 0x02, 0x25, 0xF7, 0x30, 0x00}}; // CF 38 AF 5B 25 00 00 00 in start4.log
CAN_frame MEB_5F5 = {.FD = true, static constexpr CAN_frame MEB_5F5 = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x5F5, .ID = 0x5F5,
@ -429,52 +442,52 @@ class MebBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x0FD, //CRC and counter, otherwise static .ID = 0x0FD, //CRC and counter, otherwise static
.data = {0x5F, 0xD0, 0x1F, 0x81, 0x00, 0x00, 0x00, 0x00}}; .data = {0x5F, 0xD0, 0x1F, 0x81, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_16A954FB = {.FD = true, static constexpr CAN_frame MEB_16A954FB = {.FD = true,
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x16A954FB, .ID = 0x16A954FB,
.data = {0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1A555548 = {.FD = true, static constexpr CAN_frame MEB_1A555548 = {.FD = true,
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x1A555548, .ID = 0x1A555548,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1A55552B = {.FD = true, static constexpr CAN_frame MEB_1A55552B = {.FD = true,
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x1A55552B, .ID = 0x1A55552B,
.data = {0x00, 0x00, 0x00, 0xA0, 0x02, 0x04, 0x00, 0x30}}; .data = {0x00, 0x00, 0x00, 0xA0, 0x02, 0x04, 0x00, 0x30}};
CAN_frame MEB_569 = {.FD = true, static constexpr CAN_frame MEB_569 = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x569, //HVEM .ID = 0x569, //HVEM
.data = {0x00, 0x00, 0x01, 0x3A, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x00, 0x01, 0x3A, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_16A954B4 = {.FD = true, static constexpr CAN_frame MEB_16A954B4 = {.FD = true,
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x16A954B4, //eTM .ID = 0x16A954B4, //eTM
.data = {0xFE, 0xB6, 0x0D, 0x00, 0x00, 0xD5, 0x48, 0xFD}}; .data = {0xFE, 0xB6, 0x0D, 0x00, 0x00, 0xD5, 0x48, 0xFD}};
CAN_frame MEB_1B000046 = {.FD = false, // Not FD static constexpr CAN_frame MEB_1B000046 = {.FD = false, // Not FD
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x1B000046, // Klima .ID = 0x1B000046, // Klima
.data = {0x00, 0x40, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x40, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_1B000010 = {.FD = false, // Not FD static constexpr CAN_frame MEB_1B000010 = {.FD = false, // Not FD
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x1B000010, // Gateway .ID = 0x1B000010, // Gateway
.data = {0x00, 0x50, 0x08, 0x50, 0x01, 0xFF, 0x30, 0x00}}; .data = {0x00, 0x50, 0x08, 0x50, 0x01, 0xFF, 0x30, 0x00}};
CAN_frame MEB_1B0000B9 = {.FD = false, // Not FD static constexpr CAN_frame MEB_1B0000B9 = {.FD = false, // Not FD
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
.ID = 0x1B0000B9, //DC/DC converter .ID = 0x1B0000B9, //DC/DC converter
.data = {0x00, 0x40, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00}}; .data = {0x00, 0x40, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00}};
CAN_frame MEB_153 = {.FD = true, static constexpr CAN_frame MEB_153 = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x153, // content .ID = 0x153, // content
.data = {0x00, 0x00, 0x00, 0xFF, 0xEF, 0xFE, 0xFF, 0xFF}}; .data = {0x00, 0x00, 0x00, 0xFF, 0xEF, 0xFE, 0xFF, 0xFF}};
CAN_frame MEB_5E1 = {.FD = true, static constexpr CAN_frame MEB_5E1 = {.FD = true,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x5E1, // content .ID = 0x5E1, // content

View file

@ -1,4 +1,5 @@
#include "MG-5-BATTERY.h" #include "MG-5-BATTERY.h"
#include <cstring> //For unit test
#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"
@ -33,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
@ -112,7 +112,7 @@ void Mg5Battery::transmit_can(unsigned long currentMillis) {
} }
void Mg5Battery::setup(void) { // Performs one time setup at startup void Mg5Battery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -2,10 +2,6 @@
#define MG_5_BATTERY_H #define MG_5_BATTERY_H
#include "CanBattery.h" #include "CanBattery.h"
#ifdef MG_5_BATTERY
#define SELECTED_BATTERY_CLASS Mg5Battery
#endif
class Mg5Battery : public CanBattery { class Mg5Battery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,4 +1,6 @@
#include "MG-HS-PHEV-BATTERY.h" #include "MG-HS-PHEV-BATTERY.h"
#include <cmath> //For unit test
#include <cstring> //For unit test
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../communication/contactorcontrol/comm_contactorcontrol.h" #include "../communication/contactorcontrol/comm_contactorcontrol.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
@ -14,8 +16,6 @@ changing.
OPTIONAL SETTINGS OPTIONAL SETTINGS
Put these in your USER_SETTINGS.h:
// This will scale the SoC so the batteries top out at 4.2V/cell instead of // This will scale the SoC so the batteries top out at 4.2V/cell instead of
4.1V/cell. The car only seems to use up to 4.1V/cell in service. 4.1V/cell. The car only seems to use up to 4.1V/cell in service.
#define MG_HS_PHEV_USE_FULL_CAPACITY true #define MG_HS_PHEV_USE_FULL_CAPACITY true
@ -147,11 +147,9 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// 15 = isolation fault // 15 = isolation fault
// 0/8 = checking // 0/8 = checking
#ifdef DEBUG_LOG
if (rx_frame.data.u8[1] != previousState) { if (rx_frame.data.u8[1] != previousState) {
logging.printf("MG_HS_PHEV: Battery status changed to %d (%d)\n", rx_frame.data.u8[1], rx_frame.data.u8[0]); logging.printf("MG_HS_PHEV: Battery status changed to %d (%d)\n", rx_frame.data.u8[1], rx_frame.data.u8[0]);
} }
#endif
if (rx_frame.data.u8[1] == 0xf && previousState != 0xf) { if (rx_frame.data.u8[1] == 0xf && previousState != 0xf) {
// Isolation fault, set event // Isolation fault, set event
@ -168,18 +166,14 @@ void MgHsPHEVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
// A weird 'stuck' state where the battery won't reconnect // A weird 'stuck' state where the battery won't reconnect
datalayer.system.status.battery_allows_contactor_closing = false; datalayer.system.status.battery_allows_contactor_closing = false;
if (!datalayer.system.status.BMS_startup_in_progress) { if (!datalayer.system.status.BMS_startup_in_progress) {
#ifdef DEBUG_LOG
logging.printf("MG_HS_PHEV: Stuck, resetting.\n"); logging.printf("MG_HS_PHEV: Stuck, resetting.\n");
#endif
start_bms_reset(); start_bms_reset();
} }
} else if (rx_frame.data.u8[1] == 0xf) { } else if (rx_frame.data.u8[1] == 0xf) {
// A fault state (likely isolation failure) // A fault state (likely isolation failure)
datalayer.system.status.battery_allows_contactor_closing = false; datalayer.system.status.battery_allows_contactor_closing = false;
if (!datalayer.system.status.BMS_startup_in_progress) { if (!datalayer.system.status.BMS_startup_in_progress) {
#ifdef DEBUG_LOG
logging.printf("MG_HS_PHEV: Fault, resetting.\n"); logging.printf("MG_HS_PHEV: Fault, resetting.\n");
#endif
start_bms_reset(); start_bms_reset();
} }
} else { } else {
@ -378,6 +372,5 @@ void MgHsPHEVBattery::setup(void) { // Performs one time setup at startup
datalayer.battery.info.min_design_voltage_dV = MIN_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.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX;
datalayer.battery.info.number_of_cells = 90; datalayer.battery.info.number_of_cells = 90;
} }

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#ifdef MG_HS_PHEV_BATTERY
#define SELECTED_BATTERY_CLASS MgHsPHEVBattery
#endif
class MgHsPHEVBattery : public CanBattery { class MgHsPHEVBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,13 +1,13 @@
#include "NISSAN-LEAF-BATTERY.h" #include "NISSAN-LEAF-BATTERY.h"
#include <cstring> //For unit test
#include "../charger/CHARGERS.h"
#include "../charger/CanCharger.h"
#include "../communication/can/comm_can.h" #include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage #include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "../devboard/utils/logging.h" #include "../devboard/utils/logging.h"
#include "../charger/CHARGERS.h"
#include "../charger/CanCharger.h"
uint16_t Temp_fromRAW_to_F(uint16_t temperature); uint16_t Temp_fromRAW_to_F(uint16_t temperature);
//Cryptographic functions //Cryptographic functions
void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer); void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer);
@ -150,13 +150,14 @@ void NissanLeafBattery::
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ); clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
} }
#ifdef INTERLOCK_REQUIRED if (user_selected_LEAF_interlock_mandatory) {
//If user requires both large 80kW and small 6kW interlock to be seated for operation
if (!battery_Interlock) { if (!battery_Interlock) {
set_event(EVENT_HVIL_FAILURE, 0); set_event(EVENT_HVIL_FAILURE, 0);
} else { } else {
clear_event(EVENT_HVIL_FAILURE); clear_event(EVENT_HVIL_FAILURE);
} }
#endif }
if (battery_HeatExist) { if (battery_HeatExist) {
if (battery_Heating_Stop) { if (battery_Heating_Stop) {
@ -187,6 +188,11 @@ void NissanLeafBattery::
datalayer_nissan->HeatingStop = battery_Heating_Stop; datalayer_nissan->HeatingStop = battery_Heating_Stop;
datalayer_nissan->HeatingStart = battery_Heating_Start; datalayer_nissan->HeatingStart = battery_Heating_Start;
datalayer_nissan->HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request; datalayer_nissan->HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request;
datalayer_nissan->battery_HX = battery_HX;
datalayer_nissan->temperature1 = ((Temp_fromRAW_to_F(battery_temp_raw_1) - 320) * 5) / 9; //Convert from F to C
datalayer_nissan->temperature2 = ((Temp_fromRAW_to_F(battery_temp_raw_2) - 320) * 5) / 9; //Convert from F to C
datalayer_nissan->temperature3 = ((Temp_fromRAW_to_F(battery_temp_raw_3) - 320) * 5) / 9; //Convert from F to C
datalayer_nissan->temperature4 = ((Temp_fromRAW_to_F(battery_temp_raw_4) - 320) * 5) / 9; //Convert from F to C
datalayer_nissan->CryptoChallenge = incomingChallenge; datalayer_nissan->CryptoChallenge = incomingChallenge;
datalayer_nissan->SolvedChallengeMSB = datalayer_nissan->SolvedChallengeMSB =
((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]); ((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]);
@ -296,6 +302,9 @@ void NissanLeafBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
LEAF_battery_Type = AZE0_BATTERY; LEAF_battery_Type = AZE0_BATTERY;
} }
break; break;
case 0x380:
case 0x5EB:
case 0x5BF:
case 0x1ED: case 0x1ED:
case 0x1C2: case 0x1C2:
//ZE1 2018-2023 battery detected! //ZE1 2018-2023 battery detected!
@ -682,6 +691,14 @@ void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
mprun10 = (mprun10 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1... mprun10 = (mprun10 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1...
} }
//Send 40ms message
if (currentMillis - previousMillis40 >= INTERVAL_40_MS) {
previousMillis40 = currentMillis;
if (LEAF_battery_Type == ZE1_BATTERY) {
transmit_can_frame(&LEAF_355);
}
}
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
@ -697,6 +714,22 @@ void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
LEAF_50B.data.u8[6] = 0x00; //Batt_Heater_Mail_Send_NG LEAF_50B.data.u8[6] = 0x00; //Batt_Heater_Mail_Send_NG
} }
//If we are on ZE1 battery, handle some extra 100ms messages
if (LEAF_battery_Type == ZE1_BATTERY) {
counter_3B8 = (counter_3B8 + 1) % 15;
LEAF_3B8.data.u8[2] = counter_3B8; // 0 - 14 (0x00 - 0x0E)
transmit_can_frame(&LEAF_3B8); // Sending 3B8 removes U1000 and P318E DTC
transmit_can_frame(&LEAF_5C5); // Sending 5C5 removes U214E DTC
transmit_can_frame(&LEAF_626); // Sending 625 removes U215B DTC
if (flip_3B8) {
flip_3B8 = 0;
LEAF_3B8.data.u8[1] = 0xC8;
} else {
flip_3B8 = 1;
LEAF_3B8.data.u8[1] = 0xE8;
}
}
// VCM message, containing info if battery should sleep or stay awake // VCM message, containing info if battery should sleep or stay awake
transmit_can_frame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 transmit_can_frame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1
@ -724,6 +757,14 @@ void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1... mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1...
} }
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
if (LEAF_battery_Type == ZE1_BATTERY) {
transmit_can_frame(&LEAF_5EC);
}
}
//Send 10s CAN messages //Send 10s CAN messages
if (currentMillis - previousMillis10s >= INTERVAL_10_S) { if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
previousMillis10s = currentMillis; previousMillis10s = currentMillis;
@ -763,6 +804,8 @@ bool NissanLeafBattery::is_message_corrupt(CAN_frame rx_frame) {
uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrible, but apparently works well uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrible, but apparently works well
if (temperature == 1021) { if (temperature == 1021) {
return 10; return 10;
} else if (temperature == 65535) { //Value unavailable, sensor does not exist
return 718; //0*C final calculation
} else if (temperature >= 589) { } else if (temperature >= 589) {
return static_cast<uint16_t>(1620 - temperature * 1.81); return static_cast<uint16_t>(1620 - temperature * 1.81);
} else if (temperature >= 569) { } else if (temperature >= 569) {

View file

@ -6,9 +6,7 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "NISSAN-LEAF-HTML.h" #include "NISSAN-LEAF-HTML.h"
#ifdef NISSAN_LEAF_BATTERY extern bool user_selected_LEAF_interlock_mandatory;
#define SELECTED_BATTERY_CLASS NissanLeafBattery
#endif
class NissanLeafBattery : public CanBattery { class NissanLeafBattery : public CanBattery {
public: public:
@ -68,15 +66,19 @@ class NissanLeafBattery : public CanBattery {
bool* allows_contactor_closing; bool* allows_contactor_closing;
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis40 = 0; // will store last time a 40ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
uint8_t mprun10 = 0; //counter 0-3 uint8_t mprun10 = 0; //counter 0-3
uint8_t mprun100 = 0; //counter 0-3 uint8_t mprun100 = 0; //counter 0-3
uint8_t counter_3B8 = 0; //counter 0-14
bool flip_3B8 = false;
static const int ZE0_BATTERY = 0; static const uint8_t ZE0_BATTERY = 0;
static const int AZE0_BATTERY = 1; static const uint8_t AZE0_BATTERY = 1;
static const int ZE1_BATTERY = 2; static const uint8_t ZE1_BATTERY = 2;
// These CAN messages need to be sent towards the battery to keep it alive // These CAN messages need to be sent towards the battery to keep it alive
CAN_frame LEAF_1F2 = {.FD = false, CAN_frame LEAF_1F2 = {.FD = false,
@ -99,6 +101,24 @@ class NissanLeafBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x1D4, .ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}}; .data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Extra CAN messages for ZE1 batteries
CAN_frame LEAF_355 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x355,
.data = {0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40, 0x00}};
CAN_frame LEAF_3B8 = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x3B8, .data = {0x7F, 0xE8, 0x01, 0x07, 0xFF}};
CAN_frame LEAF_5C5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5C5,
.data = {0x40, 0x01, 0x2F, 0x5E, 0x00, 0x00, 0x00, 0x00}};
CAN_frame LEAF_5EC = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x5EC, .data = {0x00}};
CAN_frame LEAF_626 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x626,
.data = {0x02, 0x00, 0xff, 0x1d, 0x20, 0x00}};
// Active polling messages // Active polling messages
uint8_t PIDgroups[7] = {0x01, 0x02, 0x04, 0x06, 0x83, 0x84, 0x90}; uint8_t PIDgroups[7] = {0x01, 0x02, 0x04, 0x06, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0; uint8_t PIDindex = 0;
@ -176,11 +196,11 @@ class NissanLeafBattery : public CanBattery {
uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
uint16_t battery_HX = 0; //Internal resistance uint16_t battery_HX = 0; //Internal resistance
uint16_t battery_insulation = 0; //Insulation resistance uint16_t battery_insulation = 0; //Insulation resistance
uint16_t battery_temp_raw_1 = 0; uint16_t battery_temp_raw_1 = 718;
uint8_t battery_temp_raw_2_highnibble = 0; uint8_t battery_temp_raw_2_highnibble = 0;
uint16_t battery_temp_raw_2 = 0; uint16_t battery_temp_raw_2 = 718;
uint16_t battery_temp_raw_3 = 0; uint16_t battery_temp_raw_3 = 718; //This measurement not available on 2013+
uint16_t battery_temp_raw_4 = 0; uint16_t battery_temp_raw_4 = 718;
uint16_t battery_temp_raw_max = 0; uint16_t battery_temp_raw_max = 0;
uint16_t battery_temp_raw_min = 0; uint16_t battery_temp_raw_min = 0;
int16_t battery_temp_polled_max = 0; int16_t battery_temp_polled_max = 0;

View file

@ -40,6 +40,7 @@ class NissanLeafHtmlRenderer : public BatteryHtmlRenderer {
readableBMSID[8] = '\0'; // Null terminate the string readableBMSID[8] = '\0'; // Null terminate the string
content += "<h4>BMS ID: " + String(readableBMSID) + "</h4>"; content += "<h4>BMS ID: " + String(readableBMSID) + "</h4>";
content += "<h4>GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "</h4>"; content += "<h4>GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "</h4>";
content += "<h4>HX: " + String(datalayer_extended.nissanleaf.battery_HX) + "</h4>";
content += "<h4>Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "</h4>"; content += "<h4>Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "</h4>";
content += "<h4>Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "</h4>"; content += "<h4>Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "</h4>";
content += "<h4>Interlock: " + String(datalayer_extended.nissanleaf.Interlock) + "</h4>"; content += "<h4>Interlock: " + String(datalayer_extended.nissanleaf.Interlock) + "</h4>";
@ -53,6 +54,12 @@ class NissanLeafHtmlRenderer : public BatteryHtmlRenderer {
content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>"; content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>";
content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>"; content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>";
content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>"; content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>";
content += "<h4>Temperature 1: " + String(datalayer_extended.nissanleaf.temperature1 / 10.0) + " &deg;C</h4>";
content += "<h4>Temperature 2: " + String(datalayer_extended.nissanleaf.temperature2 / 10.0) + " &deg;C</h4>";
if (datalayer_extended.nissanleaf.LEAF_gen == 0) {
content += "<h4>Temperature 3: " + String(datalayer_extended.nissanleaf.temperature3 / 10.0) + " &deg;C</h4>";
}
content += "<h4>Temperature 4: " + String(datalayer_extended.nissanleaf.temperature4 / 10.0) + " &deg;C</h4>";
content += "<h4>CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "</h4>"; content += "<h4>CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "</h4>";
content += "<h4>SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) + content += "<h4>SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) +
String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "</h4>"; String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "</h4>";

View file

@ -1,4 +1,5 @@
#include "ORION-BMS.h" #include "ORION-BMS.h"
#include "../battery/BATTERIES.h"
#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"
@ -57,8 +58,8 @@ void OrionBms::update_values() {
datalayer.battery.status.cell_min_voltage_mV = Minimum_Cell_Voltage; datalayer.battery.status.cell_min_voltage_mV = Minimum_Cell_Voltage;
//If user did not configure amount of cells correctly in the header file, update the value //Use the reported number of cells to avoid needing to configure it
if ((amount_of_detected_cells > NUMBER_OF_CELLS) && (amount_of_detected_cells < MAX_AMOUNT_CELLS)) { if (amount_of_detected_cells < MAX_AMOUNT_CELLS) {
datalayer.battery.info.number_of_cells = amount_of_detected_cells; datalayer.battery.info.number_of_cells = amount_of_detected_cells;
} }
} }
@ -115,10 +116,9 @@ void OrionBms::transmit_can(unsigned long currentMillis) {
void OrionBms::setup(void) { // Performs one time setup at startup void OrionBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS; datalayer.battery.info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
} }

View file

@ -4,10 +4,6 @@
#include "../system_settings.h" #include "../system_settings.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef ORION_BMS
#define SELECTED_BATTERY_CLASS OrionBms
#endif
class OrionBms : public CanBattery { class OrionBms : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -17,14 +13,6 @@ class OrionBms : public CanBattery {
static constexpr const char* Name = "DIY battery with Orion BMS (Victron setting)"; static constexpr const char* Name = "DIY battery with Orion BMS (Victron setting)";
private: private:
/* Change the following to suit your battery */
static const int NUMBER_OF_CELLS = 96;
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 150;
uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages uint16_t cellvoltages[MAX_AMOUNT_CELLS]; //array with all the cellvoltages
uint16_t Maximum_Cell_Voltage = 3700; uint16_t Maximum_Cell_Voltage = 3700;
uint16_t Minimum_Cell_Voltage = 3700; uint16_t Minimum_Cell_Voltage = 3700;

View file

@ -1,4 +1,5 @@
#include "PYLON-BATTERY.h" #include "PYLON-BATTERY.h"
#include "../battery/BATTERIES.h"
#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"
@ -36,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:
@ -54,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];
@ -131,10 +132,10 @@ void PylonBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63); strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer_battery->info.number_of_cells = 2; datalayer_battery->info.number_of_cells = 2;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer_battery->info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer_battery->info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer_battery->info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer_battery->info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;

View file

@ -4,10 +4,6 @@
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef PYLON_BATTERY
#define SELECTED_BATTERY_CLASS PylonBattery
#endif
class PylonBattery : public CanBattery { class PylonBattery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.
@ -32,11 +28,6 @@ class PylonBattery : public CanBattery {
static constexpr const char* Name = "Pylon compatible battery"; static constexpr const char* Name = "Pylon compatible battery";
private: private:
/* Change the following to suit your battery */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 150; static const int MAX_CELL_DEVIATION_MV = 150;
DATALAYER_BATTERY_TYPE* datalayer_battery; DATALAYER_BATTERY_TYPE* datalayer_battery;

View file

@ -1,4 +1,5 @@
#include "RANGE-ROVER-PHEV-BATTERY.h" #include "RANGE-ROVER-PHEV-BATTERY.h"
#include <cstring> //For unit test
#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"
@ -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]);

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#ifdef RANGE_ROVER_PHEV_BATTERY
#define SELECTED_BATTERY_CLASS RangeRoverPhevBattery
#endif
class RangeRoverPhevBattery : public CanBattery { class RangeRoverPhevBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -17,12 +13,12 @@ class RangeRoverPhevBattery : public CanBattery {
private: private:
/* Change the following to suit your battery */ /* Change the following to suit your battery */
static const int MAX_PACK_VOLTAGE_DV = 5000; //TODO: Configure static const int MAX_PACK_VOLTAGE_DV = 4710;
static const int MIN_PACK_VOLTAGE_DV = 0; //TODO: Configure static const int MIN_PACK_VOLTAGE_DV = 3000;
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 = 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
static const int MAX_CELL_DEVIATION_MV = 150; static const int MAX_CELL_DEVIATION_MV = 150;
;
unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent
//CAN content from battery //CAN content from battery

View file

@ -0,0 +1,155 @@
#include "RELION-LV-BATTERY.h"
#include "../battery/BATTERIES.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
/*CAN Type:CAN2.0(Extended)
BPS:250kbps
Data Length: 8
Data Encoded Format:Motorola*/
void RelionBattery::update_values() {
datalayer.battery.status.real_soc = battery_soc * 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.soh_pptt = battery_soh * 100;
datalayer.battery.status.voltage_dV = battery_total_voltage;
datalayer.battery.status.current_dA = battery_total_current; //Charging negative, discharge positive
datalayer.battery.status.max_charge_power_W =
((battery_total_voltage / 10) * charge_current_A); //90A recommended charge current
datalayer.battery.status.max_discharge_power_W =
((battery_total_voltage / 10) * discharge_current_A); //150A max continous discharge current
datalayer.battery.status.temperature_min_dC = max_cell_temperature * 10;
datalayer.battery.status.temperature_max_dC = max_cell_temperature * 10;
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage;
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage;
}
void RelionBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x02018100: //ID1 (Example frame 10 08 01 F0 00 00 00 00)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_total_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
break;
case 0x02028100: //ID2 (Example frame 00 00 00 63 64 10 00 00)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_total_current = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
system_state = rx_frame.data.u8[2];
battery_soc = rx_frame.data.u8[3];
battery_soh = rx_frame.data.u8[4];
most_serious_fault = rx_frame.data.u8[5];
break;
case 0x02038100: //ID3 (Example frame 0C F9 01 04 0C A7 01 0F)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
max_cell_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case 0x02648100: //Charging limitis
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
charge_current_A = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) - 800;
regen_charge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800;
discharge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800;
break;
case 0x02048100: ///Temperatures min/max 2048100 [8] 47 01 01 47 01 01 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
max_cell_temperature = rx_frame.data.u8[0] - 50;
min_cell_temperature = rx_frame.data.u8[2] - 50;
break;
case 0x02468100: ///Raw temperatures 2468100 [8] 47 47 47 47 47 47 47 47
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02478100: ///? 2478100 [8] 32 32 32 32 32 32 32 32
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
//ID6 = 0x02108100 ~ 0x023F8100****** Cell Voltage 1~192******
case 0x02108100: ///Cellvoltages 1 2108100 [8] 0C F9 0C F8 0C F8 0C F9
datalayer.battery.status.cell_voltages_mV[0] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
datalayer.battery.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
datalayer.battery.status.cell_voltages_mV[2] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
datalayer.battery.status.cell_voltages_mV[3] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02118100: ///Cellvoltages 2 2118100 [8] 0C F8 0C F8 0C F9 0C F8
datalayer.battery.status.cell_voltages_mV[4] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
datalayer.battery.status.cell_voltages_mV[6] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
datalayer.battery.status.cell_voltages_mV[7] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02128100: ///Cellvoltages 3 2128100 [8] 0C F8 0C F8 0C F9 0C F8
datalayer.battery.status.cell_voltages_mV[8] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
datalayer.battery.status.cell_voltages_mV[9] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
datalayer.battery.status.cell_voltages_mV[10] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
datalayer.battery.status.cell_voltages_mV[11] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02138100: ///Cellvoltages 4 2138100 [8] 0C F9 0C CD 0C A7 00 00
datalayer.battery.status.cell_voltages_mV[12] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
datalayer.battery.status.cell_voltages_mV[13] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
datalayer.battery.status.cell_voltages_mV[14] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02058100: ///? 2058100 [8] 00 0C 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02068100: ///? 2068100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02148100: ///? 2148100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02508100: ///? 2508100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02518100: ///? 2518100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02528100: ///? 2528100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02548100: ///? 2548100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x024A8100: ///? 24A8100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02558100: ///? 2558100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02538100: ///? 2538100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x02568100: ///? 2568100 [8] 00 00 00 00 00 00 00 00
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
default:
break;
}
}
void RelionBattery::transmit_can(unsigned long currentMillis) {
// No periodic sending for this protocol
}
void RelionBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.chemistry = LFP;
datalayer.battery.info.number_of_cells = 16;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}

View file

@ -0,0 +1,39 @@
#ifndef RELION_BATTERY_H
#define RELION_BATTERY_H
#include "../system_settings.h"
#include "CanBattery.h"
class RelionBattery : public CanBattery {
public:
RelionBattery() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {}
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 = "Relion LV protocol via 250kbps CAN";
private:
static const int MAX_PACK_VOLTAGE_DV = 584; //58.4V recommended charge voltage. BMS protection steps in at 60.8V
static const int MIN_PACK_VOLTAGE_DV = 440; //44.0V Recommended LV disconnect. BMS protection steps in at 40.0V
static const int MAX_CELL_DEVIATION_MV = 300;
static const int MAX_CELL_VOLTAGE_MV = 3800; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
uint16_t battery_total_voltage = 500;
int16_t battery_total_current = 0;
uint8_t system_state = 0;
uint8_t battery_soc = 50;
uint8_t battery_soh = 99;
uint8_t most_serious_fault = 0;
uint16_t max_cell_voltage = 3300;
uint16_t min_cell_voltage = 3300;
int16_t max_cell_temperature = 0;
int16_t min_cell_temperature = 0;
int16_t charge_current_A = 0;
int16_t regen_charge_current_A = 0;
int16_t discharge_current_A = 0;
};
#endif

View file

@ -51,39 +51,6 @@ void RenaultKangooBattery::
datalayer.battery.status.cell_min_voltage_mV = LB_Cell_Min_Voltage; datalayer.battery.status.cell_min_voltage_mV = LB_Cell_Min_Voltage;
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage; datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
#ifdef DEBUG_LOG
logging.println("Values going to inverter:");
logging.print("SOH%: ");
logging.print(datalayer.battery.status.soh_pptt);
logging.print(", SOC% scaled: ");
logging.print(datalayer.battery.status.reported_soc);
logging.print(", Voltage: ");
logging.print(datalayer.battery.status.voltage_dV);
logging.print(", Max discharge power: ");
logging.print(datalayer.battery.status.max_discharge_power_W);
logging.print(", Max charge power: ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(", Max temp: ");
logging.print(datalayer.battery.status.temperature_max_dC);
logging.print(", Min temp: ");
logging.print(datalayer.battery.status.temperature_min_dC);
logging.print(", BMS Status (3=OK): ");
logging.print(datalayer.battery.status.bms_status);
logging.println("Battery values: ");
logging.print("Real SOC: ");
logging.print(LB_SOC);
logging.print(", Current: ");
logging.print(LB_Current);
logging.print(", kWh remain: ");
logging.print(LB_kWh_Remaining);
logging.print(", max mV: ");
logging.print(LB_Cell_Max_Voltage);
logging.print(", min mV: ");
logging.print(LB_Cell_Min_Voltage);
#endif
} }
void RenaultKangooBattery::handle_incoming_can_frame(CAN_frame rx_frame) { void RenaultKangooBattery::handle_incoming_can_frame(CAN_frame rx_frame) {

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#ifdef RENAULT_KANGOO_BATTERY
#define SELECTED_BATTERY_CLASS RenaultKangooBattery
#endif
class RenaultKangooBattery : public CanBattery { class RenaultKangooBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,5 +1,6 @@
#include "RENAULT-TWIZY.h" #include "RENAULT-TWIZY.h"
#include <cstdint> #include <cstdint>
#include <cstring> //For unit test
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
@ -51,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;

View file

@ -2,10 +2,6 @@
#define RENAULT_TWIZY_BATTERY_H #define RENAULT_TWIZY_BATTERY_H
#include "CanBattery.h" #include "CanBattery.h"
#ifdef RENAULT_TWIZY_BATTERY
#define SELECTED_BATTERY_CLASS RenaultTwizyBattery
#endif
class RenaultTwizyBattery : public CanBattery { class RenaultTwizyBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,10 +1,9 @@
#include "RENAULT-ZOE-GEN1-BATTERY.h" #include "RENAULT-ZOE-GEN1-BATTERY.h"
#include <cstring> //For unit test
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" #include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component /* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp
The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage

View file

@ -4,10 +4,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "RENAULT-ZOE-GEN1-HTML.h" #include "RENAULT-ZOE-GEN1-HTML.h"
#ifdef RENAULT_ZOE_GEN1_BATTERY
#define SELECTED_BATTERY_CLASS RenaultZoeGen1Battery
#endif
class RenaultZoeGen1Battery : public CanBattery { class RenaultZoeGen1Battery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.
@ -38,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;

View file

@ -22,6 +22,19 @@ https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=read
https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2 https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2
*/ */
uint8_t RenaultZoeGen2Battery::calculate_crc_zoe(CAN_frame& rx_frame, uint8_t crc_xor) {
uint8_t crc = 0; //init value 0x00
for (uint8_t j = 0; j < 7; j++) {
crc = crctable[(crc ^ static_cast<uint8_t>(rx_frame.data.u8[j])) & 0xFF];
}
return crc ^ crc_xor;
}
bool RenaultZoeGen2Battery::is_message_corrupt(CAN_frame rx_frame, uint8_t crc_xor) {
uint8_t crc = calculate_crc_zoe(rx_frame, crc_xor);
return crc != rx_frame.data.u8[7];
}
void RenaultZoeGen2Battery::update_values() { void RenaultZoeGen2Battery::update_values() {
datalayer_battery->status.soh_pptt = battery_soh; datalayer_battery->status.soh_pptt = battery_soh;
@ -32,7 +45,7 @@ void RenaultZoeGen2Battery::update_values() {
datalayer_battery->status.real_soc = 0; datalayer_battery->status.real_soc = 0;
} }
datalayer_battery->status.voltage_dV = battery_pack_voltage; datalayer_battery->status.voltage_dV = battery_pack_voltage_periodic_dV;
datalayer_battery->status.current_dA = ((battery_current - 32640) * 0.3125); datalayer_battery->status.current_dA = ((battery_current - 32640) * 0.3125);
@ -50,28 +63,32 @@ void RenaultZoeGen2Battery::update_values() {
datalayer_battery->status.temperature_max_dC = ((battery_max_temp - 640) * 0.625); datalayer_battery->status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
} }
if ((battery_min_cell_voltage != 3700) && (battery_max_cell_voltage != 3700)) { datalayer_battery->status.cell_min_voltage_mV = battery_minimum_cell_voltage_mV;
datalayer_battery->status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563); datalayer_battery->status.cell_max_voltage_mV = battery_maximum_cell_voltage_mV;
datalayer_battery->status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563);
}
if (battery_12v < 11000) { //11.000V if (battery_12v < 11000) { //11.000V
set_event(EVENT_12V_LOW, battery_12v); set_event(EVENT_12V_LOW, battery_12v);
} }
if (battery_interlock != 0xFFFE) {
set_event(EVENT_HVIL_FAILURE, 0);
} else {
clear_event(EVENT_HVIL_FAILURE);
}
// Update webserver datalayer // Update webserver datalayer
datalayer_extended.zoePH2.battery_soc = battery_soc; datalayer_extended.zoePH2.battery_soc = battery_soc;
datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc; datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc;
datalayer_extended.zoePH2.battery_soh = battery_soh; datalayer_extended.zoePH2.battery_soh = battery_soh;
datalayer_extended.zoePH2.battery_pack_voltage = battery_pack_voltage; datalayer_extended.zoePH2.battery_pack_voltage = battery_pack_voltage_polled_dV;
datalayer_extended.zoePH2.battery_max_cell_voltage = battery_max_cell_voltage; datalayer_extended.zoePH2.battery_max_cell_voltage = battery_max_cell_voltage_polled;
datalayer_extended.zoePH2.battery_min_cell_voltage = battery_min_cell_voltage; datalayer_extended.zoePH2.battery_min_cell_voltage = battery_min_cell_voltage_polled;
datalayer_extended.zoePH2.battery_12v = battery_12v; datalayer_extended.zoePH2.battery_12v = battery_12v;
datalayer_extended.zoePH2.battery_avg_temp = battery_avg_temp; datalayer_extended.zoePH2.battery_avg_temp = battery_avg_temp;
datalayer_extended.zoePH2.battery_min_temp = battery_min_temp; datalayer_extended.zoePH2.battery_min_temp = battery_min_temp;
datalayer_extended.zoePH2.battery_max_temp = battery_max_temp; datalayer_extended.zoePH2.battery_max_temp = battery_max_temp;
datalayer_extended.zoePH2.battery_max_power = battery_max_power; datalayer_extended.zoePH2.battery_max_power = battery_max_power;
datalayer_extended.zoePH2.battery_interlock = battery_interlock; datalayer_extended.zoePH2.battery_interlock = battery_interlock_polled;
datalayer_extended.zoePH2.battery_kwh = battery_kwh; datalayer_extended.zoePH2.battery_kwh = battery_kwh;
datalayer_extended.zoePH2.battery_current = battery_current; datalayer_extended.zoePH2.battery_current = battery_current;
datalayer_extended.zoePH2.battery_current_offset = battery_current_offset; datalayer_extended.zoePH2.battery_current_offset = battery_current_offset;
@ -103,9 +120,78 @@ void RenaultZoeGen2Battery::update_values() {
} }
void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) { void RenaultZoeGen2Battery::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 0x0F8:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_interlock = (rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]; //Expected FF FE
battery_pack_voltage_periodic_dV = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) / 8;
//battery_pack_current_periodic_dA = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8; //4-5-6 current related
break;
case 0x381:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//frame0 - 373 Related
//frame1 - 373 Related
//frame2 - Maximum_Available_Power_related
//frame3 - Maximum_Available_Power_related/was charge complete or partial
//frame4 - max power/SOC_related
//frame5-6 - SOC_related
//frame7 - Unknown status
break;
case 0x382:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Frame0-2 Max gen power
//frame6 cooling temp OK
//frame7 max temp OK
break;
case 0x387:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x388: //Blower/Cooling/Maxpower
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x3EF:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x36C:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (is_message_corrupt(rx_frame, 0x01)) {
datalayer_battery->status.CAN_error_counter++;
}
break;
case 0x4DB:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_maximum_cell_voltage_mV = ((rx_frame.data.u8[0] << 4) | (rx_frame.data.u8[1] & 0xF0) >> 4) + 1000;
battery_minimum_cell_voltage_mV = (((rx_frame.data.u8[1] & 0x0F) << 8) | (rx_frame.data.u8[2])) + 1000;
break;
case 0x4AE:
case 0x4AF:
case 0x5A1:
case 0x5AC:
case 0x5AD:
case 0x5B4:
case 0x5B5:
case 0x5B7:
case 0x5C9:
case 0x5CB:
case 0x5CC:
case 0x5D6:
case 0x5D7:
case 0x5D9:
case 0x5DC:
case 0x5DD:
case 0x5EA:
case 0x5ED:
case 0x5F0:
case 0x5F1:
case 0x5F2:
case 0x5F4:
case 0x5F7:
case 0x612:
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x18DAF1DB: // LBC Reply from active polling case 0x18DAF1DB: // LBC Reply from active polling
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x10) { //First frame of a group if (rx_frame.data.u8[0] == 0x10) { //First frame of a group
transmit_can_frame(&ZOE_POLL_FLOW_CONTROL); transmit_can_frame(&ZOE_POLL_FLOW_CONTROL);
@ -128,18 +214,18 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_soh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_soh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case POLL_PACK_VOLTAGE: case POLL_PACK_VOLTAGE:
battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_pack_voltage_polled_dV = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case POLL_MAX_CELL_VOLTAGE: case POLL_MAX_CELL_VOLTAGE:
temporary_variable = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; temporary_variable = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
if (temporary_variable > 500) { //Disregard messages with value unavailable if (temporary_variable > 500) { //Disregard messages with value unavailable
battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_max_cell_voltage_polled = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
} }
break; break;
case POLL_MIN_CELL_VOLTAGE: case POLL_MIN_CELL_VOLTAGE:
temporary_variable = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; temporary_variable = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
if (temporary_variable > 500) { //Disregard messages with value unavailable if (temporary_variable > 500) { //Disregard messages with value unavailable
battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_min_cell_voltage_polled = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
} }
break; break;
case POLL_12V: case POLL_12V:
@ -158,7 +244,7 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_max_power = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_max_power = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case POLL_INTERLOCK: case POLL_INTERLOCK:
battery_interlock = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_interlock_polled = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case POLL_KWH: case POLL_KWH:
battery_kwh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; battery_kwh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
@ -317,7 +403,7 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
battery_balancing_shunts[94] = (rx_frame.data.u8[1] & 0x02) >> 1; battery_balancing_shunts[94] = (rx_frame.data.u8[1] & 0x02) >> 1;
battery_balancing_shunts[95] = (rx_frame.data.u8[1] & 0x01); battery_balancing_shunts[95] = (rx_frame.data.u8[1] & 0x01);
memcpy(datalayer.battery.status.cell_balancing_status, battery_balancing_shunts, 96 * sizeof(bool)); memcpy(datalayer_battery->status.cell_balancing_status, battery_balancing_shunts, 96 * sizeof(bool));
} }
break; break;
case POLL_ENERGY_COMPLETE: case POLL_ENERGY_COMPLETE:
@ -660,28 +746,42 @@ void RenaultZoeGen2Battery::transmit_can(unsigned long currentMillis) {
if (datalayer_extended.zoePH2.UserRequestNVROLReset) { if (datalayer_extended.zoePH2.UserRequestNVROLReset) {
// Send NVROL reset frames // Send NVROL reset frames
transmit_reset_nvrol_frames(); transmit_reset_nvrol_frames();
} else { }
// Send 10ms CAN Message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
counter_10ms = (counter_10ms + 1) % 16;
ZOE_0EE.data.u8[6] = counter_10ms;
ZOE_0EE.data.u8[7] = calculate_crc_zoe(ZOE_0EE, 0xAC);
transmit_can_frame(&ZOE_0EE); //Pedal position
//transmit_can_frame(&ZOE_133); //Vehicle speed (CRC is frame3 B1A670 55 0006FFFF)
}
// Send 100ms CAN Message // Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
/* FIXME: remove if not needed ZOE_373.data.u8[1] = 0x40; //40 vehicle locked, 80 vehicle unlocked
if ((counter_373 / 5) % 2 == 0) { // Alternate every 5 messages between these two
if ((counter_373 / 5) % 2 == 0) { // Alternate every 5 messages between these two patterns
ZOE_373.data.u8[2] = 0xB2; ZOE_373.data.u8[2] = 0xB2;
ZOE_373.data.u8[3] = 0xB2; ZOE_373.data.u8[3] = 0x5D;
} else { } else {
ZOE_373.data.u8[2] = 0x5D; ZOE_373.data.u8[2] = 0x5D;
ZOE_373.data.u8[3] = 0x5D; ZOE_373.data.u8[3] = 0xB2;
} }
counter_373 = (counter_373 + 1) % 10; counter_373 = (counter_373 + 1) % 10;
*/
transmit_can_frame(&ZOE_373); transmit_can_frame(&ZOE_373); //HEVC Wakeup / Sleep message
transmit_can_frame_376(); transmit_can_frame(&ZOE_375); //HEVC Status message
transmit_can_frame_376(); //HEVC Time and Date
} }
// Send 200ms CAN Message // Send 200ms polling CAN Message (Only if not NVROL in progress)
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { if ((currentMillis - previousMillis200 >= INTERVAL_200_MS) && !datalayer_extended.zoePH2.UserRequestNVROLReset) {
previousMillis200 = currentMillis; previousMillis200 = currentMillis;
// Update current poll from the array // Update current poll from the array
@ -699,7 +799,9 @@ void RenaultZoeGen2Battery::transmit_can(unsigned long currentMillis) {
// Time in seconds emulated // Time in seconds emulated
ZOE_376_time_now_s++; // Increment by 1 second ZOE_376_time_now_s++; // Increment by 1 second
}
transmit_can_frame(&ZOE_5F8); //Vehicle ID
transmit_can_frame(&ZOE_6BF); //Total Boost Time
} }
} }
@ -775,9 +877,13 @@ void RenaultZoeGen2Battery::transmit_reset_nvrol_frames(void) {
} }
break; break;
case 4: //Wait 30s case 4: //Wait 30s
//While waiting, stop streaming 0x373 to make battery go to sleep
ZOE_373.data.u8[0] = 0x01;
if ((millis() - startTimeNVROL) > INTERVAL_30_S) { if ((millis() - startTimeNVROL) > INTERVAL_30_S) {
// after sleeping, set the nvrol reset flag to false, to continue normal operation of sending CAN messages // after sleeping, set the nvrol reset flag to false, to continue normal operation of sending CAN messages
datalayer_extended.zoePH2.UserRequestNVROLReset = false; datalayer_extended.zoePH2.UserRequestNVROLReset = false;
// Wake battery back up
ZOE_373.data.u8[0] = 0xC1;
// reset state machine, we are done! // reset state machine, we are done!
NVROLstateMachine = 0; NVROLstateMachine = 0;
} }

View file

@ -4,10 +4,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#include "RENAULT-ZOE-GEN2-HTML.h" #include "RENAULT-ZOE-GEN2-HTML.h"
#ifdef RENAULT_ZOE_GEN2_BATTERY
#define SELECTED_BATTERY_CLASS RenaultZoeGen2Battery
#endif
class RenaultZoeGen2Battery : public CanBattery { class RenaultZoeGen2Battery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.
@ -18,7 +14,7 @@ class RenaultZoeGen2Battery : public CanBattery {
allows_contactor_closing = nullptr; allows_contactor_closing = nullptr;
datalayer_zoePH2 = extended; datalayer_zoePH2 = extended;
battery_pack_voltage = 0; battery_pack_voltage_periodic_dV = 0;
} }
// Use the default constructor to create the first or single battery. // Use the default constructor to create the first or single battery.
@ -39,6 +35,8 @@ class RenaultZoeGen2Battery : public CanBattery {
BatteryHtmlRenderer& get_status_renderer() { return renderer; } BatteryHtmlRenderer& get_status_renderer() { return renderer; }
uint8_t calculate_crc_zoe(CAN_frame& frame, uint8_t crc_xor);
private: private:
RenaultZoeGen2HtmlRenderer renderer; RenaultZoeGen2HtmlRenderer renderer;
@ -48,6 +46,8 @@ class RenaultZoeGen2Battery : public CanBattery {
// If not null, this battery decides when the contactor can be closed and writes the value here. // If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing; bool* allows_contactor_closing;
bool is_message_corrupt(CAN_frame rx_frame, uint8_t crc_xor);
static const int MAX_PACK_VOLTAGE_DV = 4100; //5000 = 500.0V static const int MAX_PACK_VOLTAGE_DV = 4100; //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;
@ -197,15 +197,19 @@ class RenaultZoeGen2Battery : public CanBattery {
uint16_t battery_soc = 0; uint16_t battery_soc = 0;
uint16_t battery_usable_soc = 5000; uint16_t battery_usable_soc = 5000;
uint16_t battery_soh = 10000; uint16_t battery_soh = 10000;
uint16_t battery_pack_voltage = 3700; uint16_t battery_pack_voltage_polled_dV = 3700;
uint16_t battery_max_cell_voltage = 3700; uint16_t battery_pack_voltage_periodic_dV = 3700;
uint16_t battery_min_cell_voltage = 3700; uint16_t battery_minimum_cell_voltage_mV = 3700;
uint16_t battery_maximum_cell_voltage_mV = 3700;
uint16_t battery_max_cell_voltage_polled = 3700;
uint16_t battery_min_cell_voltage_polled = 3700;
uint16_t battery_12v = 12000; uint16_t battery_12v = 12000;
uint16_t battery_avg_temp = 920; uint16_t battery_avg_temp = 920;
uint16_t battery_min_temp = 920; uint16_t battery_min_temp = 920;
uint16_t battery_max_temp = 920; uint16_t battery_max_temp = 920;
uint16_t battery_max_power = 0; uint16_t battery_max_power = 0;
uint16_t battery_interlock = 0; uint16_t battery_interlock = 0xFFFE;
uint16_t battery_interlock_polled = 0;
uint16_t battery_kwh = 0; uint16_t battery_kwh = 0;
int32_t battery_current = 32640; int32_t battery_current = 32640;
uint16_t battery_current_offset = 0; uint16_t battery_current_offset = 0;
@ -241,20 +245,59 @@ class RenaultZoeGen2Battery : public CanBattery {
1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT 1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT
bool battery_balancing_shunts[96]; bool battery_balancing_shunts[96];
CAN_frame ZOE_373 = { const uint8_t crctable[256] = {
0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7,
0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE,
0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2,
0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, 0xFB, 0xE6, 0xC1, 0xDC,
0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78,
0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44,
0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52,
0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8,
0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73,
0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED,
0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8,
0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95,
0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31,
0x2C, 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4};
CAN_frame ZOE_0EE = {//Pedal position
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x0EE,
.data = {0x32, 0x3, 0x20, 0xAA, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ZOE_373 = {//HEVC sender, wakeup message
.FD = false, .FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x373, .ID = 0x373,
.data = {0xC1, 0x40, 0x5D, 0xB2, 0x00, 0x01, 0xff, .data = {0xC1, 0x40, 0x5D, 0xB2, 0x00, 0x01, 0xff, 0xe3}};
0xe3}}; // FIXME: remove if not needed: {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}}; CAN_frame ZOE_375 = {//HEVC status message
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x375,
.data = {0x02, 0x29, 0x00, 0xBF, 0xFE, 0x64, 0x0, 0xff}};
CAN_frame ZOE_376 = { CAN_frame ZOE_376 = {
//HEVC sender
.FD = false, .FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 8, .DLC = 8,
.ID = 0x376, .ID = 0x376,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,
0x00}}; // fill first 6 bytes with 0's. The first 6 bytes are calculated based on the current time. 0x00}}; // fill first 6 bytes with 0's. The first 6 bytes are calculated based on the current time.
CAN_frame ZOE_5F8 = { //Vehicle ID
.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x5F8,
.data = {0x16, 0x44, 0x90, 0x8F}};
CAN_frame ZOE_6BF = {//Total Boost Time
.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x6BF,
.data = {0x00, 0x00, 0x00}};
CAN_frame ZOE_POLL_18DADBF1 = {.FD = false, CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
.ext_ID = true, .ext_ID = true,
.DLC = 8, .DLC = 8,
@ -455,7 +498,8 @@ class RenaultZoeGen2Battery : public CanBattery {
uint8_t poll_index = 0; uint8_t poll_index = 0;
uint16_t currentpoll = POLL_SOC; uint16_t currentpoll = POLL_SOC;
uint16_t reply_poll = 0; uint16_t reply_poll = 0;
uint8_t counter_10ms = 0;
unsigned long previousMillis10 = 0; // will store last time a 10ms 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
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent

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

View 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

View file

@ -1,4 +1,5 @@
#include "RJXZS-BMS.h" #include "RJXZS-BMS.h"
#include "../battery/BATTERIES.h"
#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"
@ -32,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,
@ -72,14 +73,8 @@ void RjxzsBms::update_values() {
datalayer.battery.status.temperature_max_dC = max_temp; datalayer.battery.status.temperature_max_dC = max_temp;
// The cellvoltages[] array can contain 0s inside it //Map all cell voltages to the global array
populated_cellvoltages = 0; memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages, MAX_AMOUNT_CELLS * sizeof(uint16_t));
for (int i = 0; i < MAX_AMOUNT_CELLS; ++i) {
if (cellvoltages[i] > 0) { // We have a measurement available
datalayer.battery.status.cell_voltages_mV[populated_cellvoltages] = cellvoltages[i];
populated_cellvoltages++;
}
}
datalayer.battery.info.number_of_cells = populated_cellvoltages; // 1-192S datalayer.battery.info.number_of_cells = populated_cellvoltages; // 1-192S
@ -145,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];
@ -517,9 +268,9 @@ void RjxzsBms::transmit_can(unsigned long currentMillis) {
void RjxzsBms::setup(void) { // Performs one time setup at startup void RjxzsBms::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
} }

View file

@ -4,10 +4,6 @@
#include "../system_settings.h" #include "../system_settings.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef RJXZS_BMS
#define SELECTED_BATTERY_CLASS RjxzsBms
#endif
class RjxzsBms : public CanBattery { class RjxzsBms : public CanBattery {
public: public:
RjxzsBms() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {} RjxzsBms() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {}
@ -19,14 +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_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 250;
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%
@ -41,7 +29,7 @@ class RjxzsBms : public CanBattery {
uint8_t mux = 0; uint8_t mux = 0;
bool setup_completed = false; bool setup_completed = false;
uint16_t total_voltage = 0; uint16_t total_voltage = 3700;
int16_t total_current = 0; int16_t total_current = 0;
uint16_t total_power = 0; uint16_t total_power = 0;
uint16_t battery_usage_capacity = 0; uint16_t battery_usage_capacity = 0;
@ -83,9 +71,9 @@ class RjxzsBms : public CanBattery {
uint16_t low_voltage_power_outage_delayed = 0; uint16_t low_voltage_power_outage_delayed = 0;
uint16_t num_of_triggering_protection_cells = 0; uint16_t num_of_triggering_protection_cells = 0;
uint16_t balanced_reference_voltage = 0; uint16_t balanced_reference_voltage = 0;
uint16_t minimum_cell_voltage = 0; uint16_t minimum_cell_voltage = 3300;
uint16_t maximum_cell_voltage = 0; uint16_t maximum_cell_voltage = 3300;
uint16_t cellvoltages[MAX_AMOUNT_CELLS]; uint16_t cellvoltages[MAX_AMOUNT_CELLS] = {0};
uint8_t populated_cellvoltages = 0; uint8_t populated_cellvoltages = 0;
uint16_t accumulated_total_capacity_high = 0; uint16_t accumulated_total_capacity_high = 0;
uint16_t accumulated_total_capacity_low = 0; uint16_t accumulated_total_capacity_low = 0;

View file

@ -4,10 +4,6 @@
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef SAMSUNG_SDI_LV_BATTERY
#define SELECTED_BATTERY_CLASS SamsungSdiLVBattery
#endif
class SamsungSdiLVBattery : public CanBattery { class SamsungSdiLVBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);

View file

@ -1,4 +1,5 @@
#include "SANTA-FE-PHEV-BATTERY.h" #include "SANTA-FE-PHEV-BATTERY.h"
#include <cstring> //For unit test
#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"

View file

@ -3,10 +3,6 @@
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "CanBattery.h" #include "CanBattery.h"
#ifdef SANTA_FE_PHEV_BATTERY
#define SELECTED_BATTERY_CLASS SantaFePhevBattery
#endif
class SantaFePhevBattery : public CanBattery { class SantaFePhevBattery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.

View file

@ -1,7 +1,8 @@
#include "SIMPBMS-BATTERY.h" #include "SIMPBMS-BATTERY.h"
#include <cstring> //For unit test
#include "../battery/BATTERIES.h"
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
void SimpBmsBattery::update_values() { void SimpBmsBattery::update_values() {
datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00 datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00
@ -39,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];
@ -93,10 +95,9 @@ void SimpBmsBattery::transmit_can(unsigned long currentMillis) {
void SimpBmsBattery::setup(void) { // Performs one time setup at startup void SimpBmsBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = CELL_COUNT; datalayer.battery.info.max_design_voltage_dV = user_selected_max_pack_voltage_dV;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = user_selected_min_pack_voltage_dV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.max_cell_voltage_mV = user_selected_max_cell_voltage_mV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.min_cell_voltage_mV = user_selected_min_cell_voltage_mV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
} }

View file

@ -3,10 +3,6 @@
#include "CanBattery.h" #include "CanBattery.h"
#ifdef SIMPBMS_BATTERY
#define SELECTED_BATTERY_CLASS SimpBmsBattery
#endif
class SimpBmsBattery : public CanBattery { class SimpBmsBattery : public CanBattery {
public: public:
virtual void setup(void); virtual void setup(void);
@ -16,14 +12,6 @@ class SimpBmsBattery : public CanBattery {
static constexpr const char* Name = "SIMPBMS battery"; static constexpr const char* Name = "SIMPBMS battery";
private: private:
/* DEFAULT VALUES BMS will send configured */
static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V
static const int MIN_PACK_VOLTAGE_DV = 1500;
static const int MAX_CELL_VOLTAGE_MV = 4250; //Battery is put into emergency stop if one cell goes over this value
static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value
static const int MAX_CELL_DEVIATION_MV = 500;
static const int CELL_COUNT = 96;
static const int SIMPBMS_MAX_CELLS = 128; static const int SIMPBMS_MAX_CELLS = 128;
unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent

View file

@ -1,4 +1,5 @@
#include "SONO-BATTERY.h" #include "SONO-BATTERY.h"
#include <cstring> //For unit test
#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"

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