Merge branch 'main' into feature/geely-geometry-battery

This commit is contained in:
Daniel Öster 2025-05-13 22:50:41 +03:00
commit 11ca72b731
95 changed files with 3271 additions and 2644 deletions

View file

@ -0,0 +1,93 @@
# This is the name of the workflow, visible on GitHub UI.
name: Compile All Double Batteries
# 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.
build-batteries:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
strategy:
# The matrix will produce one job for each combination of parameters.
matrix:
# This is the development board hardware for which the code will be compiled.
# FBQN stands for "fully qualified board name", and is used by Arduino to define the hardware to compile for.
fqbn:
- esp32:esp32:esp32
# further ESP32 chips
#- esp32:esp32:esp32c3
#- esp32:esp32:esp32c2
#- esp32:esp32:esp32c6
#- esp32:esp32:esp32h2
#- esp32:esp32:esp32s3
# These are the batteries for which the code will be compiled.
battery:
- BMW_I3_BATTERY
- BYD_ATTO_3_BATTERY
- KIA_HYUNDAI_64_BATTERY
- PYLON_BATTERY
- SANTA_FE_PHEV_BATTERY
- TEST_FAKE_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
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest
# This is the list of steps this job will run.
steps:
# First we clone the repo using the `checkout` action.
- name: Checkout
uses: actions/checkout@v4
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# We use the `arduino/setup-arduino-cli` action to install and
# configure the Arduino CLI on the system.
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
# We then install the platform.
- name: Install platform
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32
# Finally, we compile the sketch, using the FQBN that was set
# in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix.
- name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DDOUBLE_BATTERY -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

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

View file

@ -49,9 +49,10 @@
volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver
const char* version_number = "8.11.dev";
const char* version_number = "8.13.dev";
// Interval timers
volatile unsigned long currentMillis = 0;
unsigned long previousMillis10ms = 0;
unsigned long previousMillisUpdateVal = 0;
unsigned long lastMillisOverflowCheck = 0;
@ -100,12 +101,15 @@ void setup() {
init_precharge_control();
#endif // PRECHARGE_CONTROL
init_rs485();
setup_charger();
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) || defined(RS485_INVERTER_SELECTED)
setup_inverter();
#endif
setup_battery();
init_rs485();
#ifdef EQUIPMENT_STOP_BUTTON
init_equipment_stop_button();
#endif
@ -208,6 +212,7 @@ void core_loop(void*) {
led_init();
while (true) {
START_TIME_MEASUREMENT(all);
START_TIME_MEASUREMENT(comm);
#ifdef EQUIPMENT_STOP_BUTTON
@ -227,8 +232,12 @@ void core_loop(void*) {
#endif // WEBSERVER
// Process
if (millis() - previousMillis10ms >= INTERVAL_10_MS) {
previousMillis10ms = millis();
currentMillis = millis();
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_TASK_OVERRUN, (currentMillis - previousMillis10ms));
}
previousMillis10ms = currentMillis;
#ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(time_10ms);
#endif
@ -242,8 +251,8 @@ void core_loop(void*) {
#endif
}
if (millis() - previousMillisUpdateVal >= INTERVAL_1_S) {
previousMillisUpdateVal = millis(); // Order matters on the update_loop!
if (currentMillis - previousMillisUpdateVal >= INTERVAL_1_S) {
previousMillisUpdateVal = currentMillis; // Order matters on the update_loop!
#ifdef FUNCTION_TIME_MEASUREMENT
START_TIME_MEASUREMENT(time_values);
#endif
@ -264,7 +273,7 @@ void core_loop(void*) {
START_TIME_MEASUREMENT(cantx);
#endif
// Output
transmit_can(); // Send CAN messages to all components
transmit_can(currentMillis); // Send CAN messages to all components
#ifdef RS485_BATTERY_SELECTED
transmit_rs485();
@ -401,86 +410,78 @@ void update_calculated_values() {
if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
*
* This is essentially a more static version of a stochastic oscillator (https://en.wikipedia.org/wiki/Stochastic_oscillator)
*
* The idea is this:
*
* real_soc - min_percent 3000 - 1000
* ------------------------- = scaled_soc, or ----------- = 0.25
* max_percent - min-percent 8000 - 1000
*
* Because we use integers, we want to account for the scaling:
*
* 10000 * (real_soc - min_percent) 10000 * (3000 - 1000)
* -------------------------------- = scaled_soc, or --------------------- = 2500
* max_percent - min_percent 8000 - 1000
*
* Or as a one-liner: (10000 * (real_soc - min_percentage)) / (max_percentage - min_percentage)
*
* Before we use real_soc, we must make sure that it's within the range of min_percentage and max_percentage.
*/
uint32_t calc_soc;
uint32_t calc_max_capacity;
uint32_t calc_reserved_capacity;
// Make sure that the SOC starts out between min and max percentages
calc_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage,
datalayer.battery.settings.max_percentage);
// Perform scaling
calc_soc = 10000 * (calc_soc - datalayer.battery.settings.min_percentage);
calc_soc = calc_soc / (datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage);
datalayer.battery.status.reported_soc = calc_soc;
//Extra safety since we allow scaling negatively, if real% is < 1.00%, zero it out
if (datalayer.battery.status.real_soc < 100) {
datalayer.battery.status.reported_soc = 0;
} else {
datalayer.battery.status.reported_soc = calc_soc;
* A static version of a stochastic oscillator. The scaled SoC is calculated as:
*
* 10000 * (real_soc - min_percentage)
* ---------------------------------------
* (max_percentage - min_percentage)
*
* And scaled capacity is:
*
* reported_total_capacity_Wh = total_capacity_Wh * (max - min) / 10000
* reported_remaining_capacity_Wh = reported_total_capacity_Wh * scaled_soc / 10000
*/
// Compute delta_pct and clamped_soc
int32_t delta_pct = datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage;
int32_t clamped_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage,
datalayer.battery.settings.max_percentage);
int32_t scaled_soc = 0;
int32_t scaled_total_capacity = 0;
if (delta_pct != 0) { //Safeguard against division by 0
scaled_soc = 10000 * (clamped_soc - datalayer.battery.settings.min_percentage) / delta_pct;
}
// Calculate the scaled remaining capacity in Wh
// Clamp low SOCs to zero for extra safety
if (datalayer.battery.status.real_soc < 100) {
scaled_soc = 0;
}
datalayer.battery.status.reported_soc = scaled_soc;
// If battery info is valid
if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc);
calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000;
// remove % capacity reserved in min_percentage to total_capacity_Wh
if (datalayer.battery.status.remaining_capacity_Wh > calc_reserved_capacity) {
datalayer.battery.status.reported_remaining_capacity_Wh =
datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity;
} else {
datalayer.battery.status.reported_remaining_capacity_Wh = 0;
}
datalayer.battery.info.reported_total_capacity_Wh =
(datalayer.battery.info.total_capacity_Wh *
(datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage)) /
10000;
// Scale total usable capacity
scaled_total_capacity = (datalayer.battery.info.total_capacity_Wh * delta_pct) / 10000;
datalayer.battery.info.reported_total_capacity_Wh = scaled_total_capacity;
// Scale remaining capacity based on scaled SOC
datalayer.battery.status.reported_remaining_capacity_Wh = (scaled_total_capacity * scaled_soc) / 10000;
} else {
// Fallback if scaling cannot be performed
datalayer.battery.info.reported_total_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
#ifdef DOUBLE_BATTERY
// Calculate the scaled remaining capacity in Wh
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) {
calc_max_capacity =
(datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc);
calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000;
// remove % capacity reserved in min_percentage to total_capacity_Wh
if (datalayer.battery2.status.remaining_capacity_Wh > calc_reserved_capacity) {
datalayer.battery2.status.reported_remaining_capacity_Wh =
datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity;
} else {
datalayer.battery2.status.reported_remaining_capacity_Wh = 0;
}
// If battery info is valid
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
datalayer.battery2.info.reported_total_capacity_Wh = scaled_total_capacity;
// Scale remaining capacity based on scaled SOC
datalayer.battery2.status.reported_remaining_capacity_Wh = (scaled_total_capacity * scaled_soc) / 10000;
} else {
// Fallback if scaling cannot be performed
datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
//Since we are running double battery, the scaled value of battery1 becomes the sum of battery1+battery2
//This way the inverter connected to the system sees both batteries as one large battery
datalayer.battery.info.reported_total_capacity_Wh += datalayer.battery2.info.reported_total_capacity_Wh;
datalayer.battery.status.reported_remaining_capacity_Wh += datalayer.battery2.status.reported_remaining_capacity_Wh;
#endif // DOUBLE_BATTERY
} else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
datalayer.battery.info.reported_total_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
#ifdef DOUBLE_BATTERY
datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh;
#endif
}
#ifdef DOUBLE_BATTERY
@ -503,11 +504,11 @@ void update_calculated_values() {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
#endif // DOUBLE_BATTERY
// Check if millis() has overflowed. Used in events to keep better track of time
if (millis() < lastMillisOverflowCheck) { // Overflow detected
// Check if millis has overflowed. Used in events to keep better track of time
if (currentMillis < lastMillisOverflowCheck) { // Overflow detected
datalayer.system.status.millisrolloverCount++;
}
lastMillisOverflowCheck = millis();
lastMillisOverflowCheck = currentMillis;
}
void update_values_inverter() {

View file

@ -0,0 +1,69 @@
#include "../include.h"
// These functions adapt the old C-style global functions battery-API to the
// object-oriented battery API.
// The instantiated class is defined by the pre-compiler define
// to support battery class selection at compile-time
#ifdef SELECTED_BATTERY_CLASS
static CanBattery* battery = nullptr;
#ifdef DOUBLE_BATTERY
static CanBattery* battery2 = nullptr;
#endif
void setup_battery() {
// Instantiate the battery only once just in case this function gets called multiple times.
if (battery == nullptr) {
battery = new SELECTED_BATTERY_CLASS();
}
battery->setup();
#ifdef DOUBLE_BATTERY
if (battery2 == nullptr) {
#ifdef BMW_I3_BATTERY
battery2 =
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allows_contactor_closing,
can_config.battery_double, WUP_PIN2);
#else
battery2 =
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allows_contactor_closing,
nullptr, can_config.battery_double);
#endif
}
battery2->setup();
#endif
}
void update_values_battery() {
battery->update_values();
}
// transmit_can_battery is called once and we need to
// call both batteries.
void transmit_can_battery(unsigned long currentMillis) {
battery->transmit_can(currentMillis);
#ifdef DOUBLE_BATTERY
if (battery2) {
battery2->transmit_can(currentMillis);
}
#endif
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery->handle_incoming_can_frame(rx_frame);
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() {
battery2->update_values();
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
battery2->handle_incoming_can_frame(rx_frame);
}
#endif
#endif

View file

@ -5,7 +5,7 @@
#ifdef BMW_SBOX
#include "BMW-SBOX.h"
void handle_incoming_can_frame_shunt(CAN_frame rx_frame);
void transmit_can_shunt();
void transmit_can_shunt(unsigned long currentMillis);
void setup_can_shunt();
#endif
@ -159,7 +159,7 @@ void transmit_rs485();
void receive_RS485();
#else
void handle_incoming_can_frame_battery(CAN_frame rx_frame);
void transmit_can_battery();
void transmit_can_battery(unsigned long currentMillis);
#endif
#ifdef DOUBLE_BATTERY

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,334 @@
#ifndef BMW_I3_BATTERY_H
#define BMW_I3_BATTERY_H
#include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BmwI3Battery
#define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_94AH 2700 // Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_120AH 4190 // Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_120AH 2790 // Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 250 // LED turns yellow on the board if mv delta exceeds this value
#define MAX_PACK_VOLTAGE_60AH 3950 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_60AH 2590 // Discharge stops if pack voltage exceeds this value
#define MAX_PACK_VOLTAGE_94AH 3980 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
#define NUMBER_OF_CELLS 96
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
class BmwI3Battery : public CanBattery {
public:
// Use this constructor for the second battery.
BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* allows_contactor_closing_ptr, int targetCan, int wakeup) {
datalayer_battery = datalayer_ptr;
allows_contactor_closing = allows_contactor_closing_ptr;
can_interface = targetCan;
wakeup_pin = wakeup;
*allows_contactor_closing = true;
//Init voltage to 0 to allow contactor check to operate without fear of default values colliding
battery_volts = 0;
}
// Use the default constructor to create the first or single battery.
BmwI3Battery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
can_interface = can_config.battery;
wakeup_pin = WUP_PIN1;
}
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);
private:
const int MAX_CELL_VOLTAGE_60AH = 4110; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_60AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_94AH = 4140; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_94AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_120AH = 4190; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_120AH = 2790; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_DEVIATION_MV = 250; // LED turns yellow on the board if mv delta exceeds this value
const int MAX_PACK_VOLTAGE_60AH = 3950; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_60AH = 2590; // Discharge stops if pack voltage exceeds this value
const int MAX_PACK_VOLTAGE_94AH = 3980; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_94AH = 2590; // Discharge stops if pack voltage exceeds this value
const int MAX_PACK_VOLTAGE_120AH = 4030; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_120AH = 2680; // Discharge stops if pack voltage exceeds this value
const int NUMBER_OF_CELLS = 96;
DATALAYER_BATTERY_TYPE* datalayer_battery;
bool* allows_contactor_closing;
int wakeup_pin;
int can_interface;
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
BatterySize detectedBattery = BATTERY_60AH;
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
CmdState cmdState = SOC;
/* CAN messages from PT-CAN2 not needed to operate the battery
0AA 105 13D 0BB 0AD 0A5 150 100 1A1 10E 153 197 429 1AA 12F 59A 2E3 2BE 211 2b3 3FD 2E8 2B7 108 29D 29C 29B 2C0 330
3E9 32F 19E 326 55E 515 509 50A 51A 2F5 3A4 432 3C9
*/
CAN_frame BMW_10B = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
CAN_frame BMW_12F = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12F,
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
CAN_frame BMW_13E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
CAN_frame BMW_192 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x192,
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_19B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x19B,
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_1D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D0,
.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}};
CAN_frame BMW_2E2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2E2,
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
CAN_frame BMW_30B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x30B,
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
CAN_frame BMW_328 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x328,
.data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
CAN_frame BMW_37B = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x37B,
.data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
CAN_frame BMW_380 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
CAN_frame BMW_3A0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3A0,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
CAN_frame BMW_3A7 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x3A7,
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
CAN_frame BMW_3C5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3C5,
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
CAN_frame BMW_3CA = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3CA,
.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}};
CAN_frame BMW_3E4 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x3E4,
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
CAN_frame BMW_3EC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3EC,
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
CAN_frame BMW_3F9 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3F9,
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
CAN_frame BMW_3FB = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x3FB,
.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_418 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x418,
.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}};
CAN_frame BMW_433 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x433,
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
CAN_frame BMW_512 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
CAN_frame BMW_592_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
CAN_frame BMW_592_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BMW_5F8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5F8,
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
CAN_frame BMW_6F1_CELL = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.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}};
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.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}};
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x6F4,
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}};
//The above CAN messages need to be sent towards the battery to keep it alive
uint8_t startup_counter_contactor = 0;
uint8_t alive_counter_20ms = 0;
uint8_t alive_counter_100ms = 0;
uint8_t alive_counter_200ms = 0;
uint8_t alive_counter_500ms = 0;
uint8_t alive_counter_1000ms = 0;
uint8_t alive_counter_5000ms = 0;
uint8_t BMW_1D0_counter = 0;
uint8_t BMW_13E_counter = 0;
uint8_t BMW_380_counter = 0;
uint32_t BMW_328_seconds = 243785948; // Initialized to make the battery think vehicle was made 7.7years ago
uint16_t BMW_328_days =
9244; //Time since 1.1.2000. Hacky implementation to make it think current date is 23rd April 2025
uint32_t BMS_328_seconds_to_day = 0; //Counter to keep track of days uptime
bool battery_awake = false;
bool battery_info_available = false;
bool skipCRCCheck = false;
bool CRCCheckPassedPreviously = false;
uint16_t cellvoltage_temp_mV = 0;
uint32_t battery_serial_number = 0;
uint32_t battery_available_power_shortterm_charge = 0;
uint32_t battery_available_power_shortterm_discharge = 0;
uint32_t battery_available_power_longterm_charge = 0;
uint32_t battery_available_power_longterm_discharge = 0;
uint32_t battery_BEV_available_power_shortterm_charge = 0;
uint32_t battery_BEV_available_power_shortterm_discharge = 0;
uint32_t battery_BEV_available_power_longterm_charge = 0;
uint32_t battery_BEV_available_power_longterm_discharge = 0;
uint16_t battery_energy_content_maximum_Wh = 0;
uint16_t battery_display_SOC = 0;
uint16_t battery_volts = 0;
uint16_t battery_HVBatt_SOC = 0;
uint16_t battery_DC_link_voltage = 0;
uint16_t battery_max_charge_voltage = 0;
uint16_t battery_min_discharge_voltage = 0;
uint16_t battery_predicted_energy_charge_condition = 0;
uint16_t battery_predicted_energy_charging_target = 0;
uint16_t battery_actual_value_power_heating = 0; //0 - 4094 W
uint16_t battery_prediction_voltage_shortterm_charge = 0;
uint16_t battery_prediction_voltage_shortterm_discharge = 0;
uint16_t battery_prediction_voltage_longterm_charge = 0;
uint16_t battery_prediction_voltage_longterm_discharge = 0;
uint16_t battery_prediction_duration_charging_minutes = 0;
uint16_t battery_target_voltage_in_CV_mode = 0;
uint16_t battery_soc = 0;
uint16_t battery_soc_hvmax = 0;
uint16_t battery_soc_hvmin = 0;
uint16_t battery_capacity_cah = 0;
int16_t battery_temperature_HV = 0;
int16_t battery_temperature_heat_exchanger = 0;
int16_t battery_temperature_max = 0;
int16_t battery_temperature_min = 0;
int16_t battery_max_charge_amperage = 0;
int16_t battery_max_discharge_amperage = 0;
int16_t battery_current = 0;
uint8_t battery_status_error_isolation_external_Bordnetz = 0;
uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
uint8_t battery_request_cooling = 0;
uint8_t battery_status_valve_cooling = 0;
uint8_t battery_status_error_locking = 0;
uint8_t battery_status_precharge_locked = 0;
uint8_t battery_status_disconnecting_switch = 0;
uint8_t battery_status_emergency_mode = 0;
uint8_t battery_request_service = 0;
uint8_t battery_error_emergency_mode = 0;
uint8_t battery_status_error_disconnecting_switch = 0;
uint8_t battery_status_warning_isolation = 0;
uint8_t battery_status_cold_shutoff_valve = 0;
uint8_t battery_request_open_contactors = 0;
uint8_t battery_request_open_contactors_instantly = 0;
uint8_t battery_request_open_contactors_fast = 0;
uint8_t battery_charging_condition_delta = 0;
uint8_t battery_status_service_disconnection_plug = 0;
uint8_t battery_status_measurement_isolation = 0;
uint8_t battery_request_abort_charging = 0;
uint8_t battery_prediction_time_end_of_charging_minutes = 0;
uint8_t battery_request_operating_mode = 0;
uint8_t battery_request_charging_condition_minimum = 0;
uint8_t battery_request_charging_condition_maximum = 0;
uint8_t battery_status_cooling_HV = 0; //1 works, 2 does not start
uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
uint8_t battery_ID2 = 0;
uint8_t battery_soh = 99;
uint8_t message_data[50];
uint8_t next_data = 0;
uint8_t current_cell_polled = 0;
};
#endif

View file

@ -6,13 +6,13 @@
#include "BMW-IX-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
static unsigned long previousMillis1000 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
@ -21,85 +21,195 @@ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE
static CmdState cmdState = SOC;
static bool battery_awake = false;
bool contactorCloseReq = false;
struct ContactorCloseRequestStruct {
bool previous;
bool present;
} ContactorCloseRequest = {false, false};
struct ContactorStateStruct {
bool closed;
bool open;
};
ContactorStateStruct ContactorState = {false, true};
struct InverterContactorCloseRequestStruct {
bool previous;
bool present;
};
InverterContactorCloseRequestStruct InverterContactorCloseRequest = {false, false};
/*
Suspected Vehicle comms required:
0x06D DLC? 1000ms - counters?
0x2F1 DLC? 1000ms during run : 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter
0x439 DLC4 1000ms STATIC
0x0C0 DLC2 200ms needs counter
0x587 DLC8 appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF
SME output:
0x12B8D087 5000ms - Extended ID
0x1D2 DLC8 1000ms
0x20B DLC8 1000ms
0x2E2 DLC16 1000ms
0x31F DLC16 100ms - 2 downward counters?
0x3EA DLC8
0x453 DLC20 200ms
0x486 DLC48 1000ms
0x49C DLC8 1000ms
0x4A1 DLC8 1000ms
0x4BB DLC64 200ms - seems multplexed on [0]
0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault
0x507 DLC8
0x587 DLC8 - appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF
0x607 - UDS response
0x7AB DLC64 - seen at startup
0x8F DLC48 10ms - appears to have analog readings like volt/temp/current
0xD0D087 DLC4
SME Output:
0x08F DLC48 10ms - Appears to have analog readings like volt/temp/current
0x12B8D087 5000ms - Extended ID
0x1D2 DLC8 1000ms
0x20B DLC8 1000ms
0x2E2 DLC16 1000ms
0x2F1 DLC8 1000ms
0x31F DLC16 100ms - 2 downward counters?
0x453 DLC20 200ms
0x486 DLC48 1000ms
0x49C DLC8 1000ms
0x4A1 DLC8 1000ms
0x4BB DLC64 200ms - seems multplexed on [0]
0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault
0x510 DLC8 100ms STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00
0x607 UDS Response
BDC output:
0x276 DLC8 - vehicle condition
0x2F1 DLC8 1000ms - during run: 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter
0x439 DLC4 1000ms - STATIC: byte0, byte1. byte3 changes, byte4 = 0xF2 OR 0xF3
0x4EB DLC8 - RSU condition
0x510 DLC8 100ms - FIXME:(update as this content is not seen in car logs:) STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00
0x6D DLC8 1000ms - counters? counter on byte3
0xC0 DLC2 200ms - needs counter
No vehicle log available, SME asks for:
SME asks for:
0x125 (CCU)
0x16E (CCU)
0x340 (CCU)
0x4F8 (CCU)
0x188 (CCU)
0x1A1 (DSC) - vehicle speed (not seen in car logs)
0x1EA (KOMBI)
0x1FC (FIXME:(add transmitter node.))
0x21D (FIXME:(add transmitter node.))
0x276 (BDC)
0x2ED (FIXME:(add transmitter node.))
0x340 (CCU)
0x380 (FIXME:(add transmitter node.))
0x442 (FIXME:(add transmitter node.))
0x4EB (BDC)
0x4F8 (CCU)
0x91 (EME1)
0xAA (EME2)
0xAA (EME2) - all wheel drive only
0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes
TODO
TODO:
- Request batt serial number on F1 8C (already parsing RX)
*/
//Vehicle CAN START
CAN_frame BMWiX_06D = {
CAN_frame BMWiX_125 = {
.FD = true,
.ext_ID = false,
.DLC = 20,
.ID = 0x125,
//.data = {TODO:, TODO:, TODO:, TODO:, 0xFE, 0x7F, 0xFE, 0x7F, TODO:, TODO:, TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF}
}; // CCU output
/* SME output
CAN_frame BMWiX_12B8D087 = {.FD = true,
.ext_ID = true,
.DLC = 2,
.ID = 0x12B8D087,
.data = {0xFC, 0xFF}}; // 5000ms SME output - Static values
*/
CAN_frame BMWiX_16E = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x16E,
.data = {0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF,
0x60, // FIXME: find out what this value represents
0xC9,
0x3A, // 0x3A to close contactors, 0x33 to open contactors
0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output.
CAN_frame BMWiX_188 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x188,
.data = {0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF}}; // CCU output - values while driving
CAN_frame BMWiX_1EA = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x06D,
.data = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC Output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
.ID = 0x1EA,
//.data = {TODO:km_least_significant, TODO:, TODO:, TODO:, TODO:km_most_significant, 0xFF, TODO:, TODO:}
}; // KOMBI output - kilometerstand
CAN_frame BMWiX_0C0 = {
CAN_frame BMWiX_1FC = {
.FD = true,
.ext_ID = false,
.DLC = 2,
.ID = 0x0C0,
.data = {
0xF0,
0x00}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 00 static - MINIMUM ID TO KEEP SME AWAKE
.DLC = 8,
.ID = 0x1FC,
.data = {0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xC0,
0x00}}; // FIXME:(add transmitter node) output - heat management engine control - static values
CAN_frame BMWiX_21D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x21D,
// .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:}
}; // FIXME:(add transmitter node) output - request heating and air conditioning system 1
CAN_frame BMWiX_276 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x476,
.ID = 0x276,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFC}}; // 5000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
0xFD}}; // BDC output - vehicle condition. Used for contactor closing
CAN_frame BMWiX_2ED = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2ED,
.data = {
0x75,
0x75}}; // FIXME:(add transmitter node) output - ambient temperature (values seen in logs vary between 0x72 and 0x79)
CAN_frame BMWiX_2F1 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2F1,
.data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup
.data = {0xFF, 0xFF, 0xD0, 0x39, 0x94, 0x00, 0xF3, 0xFF}}; // 1000ms BDC output - Static values - varies at startup
CAN_frame BMWiX_340 = {
.FD = true,
.ext_ID = false,
.DLC = 12,
.ID = 0x340,
// .data = {TODO:, TODO:, TODO:, 0xFF, TODO:, TODO:, 0x00, 0x00, TODO:, TODO:, TODO:, 0xFF, 0xFF, }
}; // CCU output
CAN_frame BMWiX_380 = {
.FD = true,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
// .data = {FIXME:(VIN_char1), FIXME:(VIN_char2), FIXME:(VIN_char3), FIXME:(VIN_char4), FIXME:(VIN_char5), FIXME:(VIN_char6), FIXME:(VIN_char7)}
}; // FIXME:(add transmitter node) output - VIN: ASCII2HEX
/* Not requested by SME
CAN_frame BMWiX_439 = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x439,
.data = {0xFF, 0x3F, 0xFF, 0xF3}}; // 1000ms BDC Output
.data = {0xFF, 0x3F, 0xFF, 0xF3}}; // 1000ms BDC output
*/
CAN_frame BMWiX_442 = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x442,
// .data = {TODO: relative time byte 0, TODO: relative time byte1, 0xA9, 0x00, 0xE0, 0x23}
}; // FIXME:(add transmitter node) output - relative time BN2020
/* SME output
CAN_frame
BMWiX_486 =
{
@ -112,28 +222,64 @@ CAN_frame
0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF,
0xFE, 0xFF, 0xFF, 0x7F, 0x33, 0xFD, 0xFD, 0xFD, 0xFD, 0xC0, 0x41, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms SME output - Suspected keep alive Static CONFIRM NEEDED
*/
/* SME output
CAN_frame BMWiX_49C = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x49C,
.data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
0xFF}}; // 1000ms SME output - Suspected keep alive Static CONFIRM NEEDED
*/
CAN_frame BMWiX_4EB = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x4EB,
// .data = {TODO:, TODO:, TODO: 0xE0 or 0xE5 (while driving), 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // BDC output - RSU condition
CAN_frame BMWiX_4F8 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x4F8,
// .data = {0xFF, 0xFD, 0xFF, 0xFF, 0xFF, TODO:, TODO:, 0xC8, 0x00, 0x00, 0xF0, 0x40, 0xFE, 0xFF, 0xFD, 0xFF, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}; // CCU output
CAN_frame BMWiX_510 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x510,
.data = {0x40, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00}}; // 100ms BDC Output - Values change in car logs, these bytes are the most common
.data = {
0x40, 0x10,
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01,
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
CAN_frame BMWiX_12B8D087 = {.FD = true,
.ext_ID = true,
.DLC = 2,
.ID = 0x12B8D087,
.data = {0xFC, 0xFF}}; // 5000ms SME Output - Static values
CAN_frame BMWiX_6D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x6D,
.data = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
CAN_frame BMWiX_C0 = {
.FD = true,
.ext_ID = false,
.DLC = 2,
.ID = 0xC0,
.data = {
0xF0,
0x00}}; // BDC output - Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 00 static - MINIMUM ID TO KEEP SME AWAKE
//Vehicle CAN END
//Request Data CAN START
@ -294,12 +440,6 @@ CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true,
.data = {0x07, 0x30, 0x00, 0x02}};
//Action Requests:
CAN_frame BMW_10B = {.FD = true,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command?
CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
@ -312,8 +452,6 @@ CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
//Request Data CAN End
static bool battery_awake = false;
//Setup UDS values to poll for
CAN_frame* UDS_REQUESTS100MS[] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
&BMWiX_6F4_REQUEST_SOC,
@ -383,13 +521,13 @@ static uint8_t uds_req_id_counter = 0;
static uint8_t detected_number_of_cells = 108;
const unsigned long STALE_PERIOD =
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0
//End iX Intermediate vars
static uint8_t current_cell_polled = 0;
static uint16_t counter_10ms = 0; // max 65535 --> 655.35 seconds
static uint8_t counter_100ms = 0; // max 255 --> 25.5 seconds
// Function to check if a value has gone stale over a specified time period
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) {
unsigned long currentTime = millis();
@ -422,7 +560,7 @@ static uint8_t increment_alive_counter(uint8_t counter) {
return counter;
}
static byte increment_0C0_counter(byte counter) {
static byte increment_C0_counter(byte counter) {
counter++;
// Reset to 0xF0 if it exceeds 0xFE
if (counter > 0xFE) {
@ -524,13 +662,53 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_awake = true;
switch (rx_frame.ID) {
case 0x112:
case 0x12B8D087:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x1D2:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x20B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x2E2:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x31F:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x3EA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x453:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x486:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x49C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4A1:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4BB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4D0:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x507:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x587:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x607: //SME responds to UDS requests on 0x607
if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 &&
rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) {
//First of multi frame data - Parse the first frame
@ -680,7 +858,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
(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");
#endif
#endif // DEBUG_LOG
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
} else { //Only ingest values if they are not the 10V Error state
@ -727,73 +905,103 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_serial_number = strtoul(numberString, NULL, 10);
}
break;
case 0x7AB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x8F:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0xD0D087:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
default:
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// We can always send CAN as the iX BMS will wake up on vehicle comms
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
ContactorCloseRequest.present = contactorCloseReq;
// Detect edge
if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) {
// Rising edge detected
#ifdef DEBUG_LOG
logging.println("Rising edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter
} else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) {
// Dropping edge detected
#ifdef DEBUG_LOG
logging.println("Dropping edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter
}
ContactorCloseRequest.previous = ContactorCloseRequest.present;
HandleBmwIxCloseContactorsRequest(counter_10ms);
HandleBmwIxOpenContactorsRequest(counter_10ms);
counter_10ms++;
//if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms
// prevent counter overflow: 2^16-1 = 65535
if (counter_10ms == 65535) {
counter_10ms = 1; // set to 1, to differentiate the counter being set to 0 by the functions above
}
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
HandleIncomingInverterRequest();
//Loop through and send a different UDS request each cycle
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery);
//Loop through and send a different UDS request once the contactors are closed
if (contactorCloseReq == true &&
ContactorState.closed ==
true) { // Do not send unless the contactors are requested to be closed and are closed, as sending these does not allow the contactors to close
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter],
can_config.battery); // FIXME: sending these does not allow the contactors to close
} else { // FIXME: hotfix: If contactors are not requested to be closed, ensure the battery is reported as alive, even if no CAN messages are received
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
// Keep contactors closed if needed
BmwIxKeepContactorsClosed(counter_100ms);
counter_100ms++;
if (counter_100ms == 140) {
counter_100ms = 0; // reset counter every 14 seconds
}
//Send SME Keep alive values 100ms
transmit_can_frame(&BMWiX_510, can_config.battery);
//transmit_can_frame(&BMWiX_510, can_config.battery);
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
//Send SME Keep alive values 200ms
BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1
transmit_can_frame(&BMWiX_0C0, can_config.battery);
//BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
//transmit_can_frame(&BMWiX_C0, can_config.battery);
}
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
//Send SME Keep alive values 1000ms
//Don't believe this is needed: transmit_can_frame(&BMWiX_06D, can_config.battery);
//Don't believe this is needed: transmit_can_frame(&BMWiX_2F1, can_config.battery);
//Don't believe this is needed: transmit_can_frame(&BMWiX_439, can_config.battery);
}
// Send 5000ms CAN Message
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
HandleIncomingUserRequest();
}
// Send 10000ms CAN Message
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
previousMillis10000 = currentMillis;
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
}
}
//We can always send CAN as the iX BMS will wake up on vehicle comms
// else {
// previousMillis20 = currentMillis;
// previousMillis100 = currentMillis;
// previousMillis200 = currentMillis;
// previousMillis500 = currentMillis;
// previousMillis640 = currentMillis;
// previousMillis1000 = currentMillis;
// previousMillis5000 = currentMillis;
// previousMillis10000 = currentMillis;
// }
//} //We can always send CAN as the iX BMS will wake up on vehicle comms
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
datalayer.system.info.battery_protocol[63] = '\0';
//Reset Battery at bootup
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
//transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
//Before we have started up and detected which battery is in use, use 108S values
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
@ -804,4 +1012,211 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif
void HandleIncomingUserRequest(void) {
// Debug user request to open or close the contactors
#ifdef DEBUG_LOG
logging.print("User request: contactor close: ");
logging.print(datalayer_extended.bmwix.UserRequestContactorClose);
logging.print(" User request: contactor open: ");
logging.println(datalayer_extended.bmwix.UserRequestContactorOpen);
#endif // DEBUG_LOG
if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
// do nothing
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
BmwIxCloseContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorClose = false;
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
BmwIxOpenContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorOpen = false;
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
// these flasgs should not be true at the same time, therefore open contactors, as that is the safest state
BmwIxOpenContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorClose = false;
datalayer_extended.bmwix.UserRequestContactorOpen = false;
// print error, as both these flags shall not be true at the same time
#ifdef DEBUG_LOG
logging.println(
"Error: user requested contactors to close and open at the same time. Contactors have been opened.");
#endif // DEBUG_LOG
}
}
void HandleIncomingInverterRequest(void) {
InverterContactorCloseRequest.present = datalayer.system.status.inverter_allows_contactor_closing;
// Detect edge
if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) {
// Rising edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to close contactors");
#endif // DEBUG_LOG
BmwIxCloseContactors();
} else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) {
// Falling edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to open contactors");
#endif // DEBUG_LOG
BmwIxOpenContactors();
} // else: do nothing
// Update state
InverterContactorCloseRequest.previous = InverterContactorCloseRequest.present;
}
void BmwIxCloseContactors(void) {
#ifdef DEBUG_LOG
logging.println("Closing contactors");
#endif // DEBUG_LOG
contactorCloseReq = true;
}
void BmwIxOpenContactors(void) {
#ifdef DEBUG_LOG
logging.println("Opening contactors");
#endif // DEBUG_LOG
contactorCloseReq = false;
counter_100ms = 0; // reset counter, such that keep contactors closed message sequence starts from the beginning
}
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
if (contactorCloseReq == true) { // Only when contactor close request is set to true
if (ContactorState.closed == false &&
ContactorState.open ==
true) { // Only when the following commands have not been completed yet, because it shall not be run when commands have already been run, AND only when contactor open commands have finished
// Initially 0x510[2] needs to be 0x02, and 0x510[5] needs to be 0x00
BMWiX_510.data = {0x40, 0x10,
0x02, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x00, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01, // 0x01 at contactor closing
0x00}; // Explicit declaration, to prevent modification by other functions
BMWiX_16E.data = {
0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF, 0x60,
0xC9, 0x3A, 0xF7}; // Explicit declaration of default values, to prevent modification by other functions
if (counter_10ms == 0) {
// @0 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 1/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 5) {
// @50 ms
transmit_can_frame(&BMWiX_276, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x276 - 2/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 10) {
// @100 ms
BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 3/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 20) {
// @200 ms
BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed
BMWiX_510.data.u8[5] = 0x80; // needed to close contactors
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 4/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 30) {
// @300 ms
BMWiX_16E.data.u8[0] = 0x6A;
BMWiX_16E.data.u8[1] = 0xAD;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 5/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 50) {
// @500 ms
BMWiX_16E.data.u8[0] = 0x03;
BMWiX_16E.data.u8[1] = 0xA9;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 6/6");
#endif // DEBUG_LOG
ContactorState.closed = true;
ContactorState.open = false;
}
}
}
}
void BmwIxKeepContactorsClosed(uint8_t counter_100ms) {
if ((ContactorState.closed == true) && (ContactorState.open == false)) {
BMWiX_510.data = {0x40, 0x10,
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01, // 0x01 at contactor closing
0x00}; // Explicit declaration, to prevent modification by other functions
BMWiX_16E.data = {0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF, 0x60,
0xC9, 0x3A, 0xF7}; // Explicit declaration, to prevent modification by other functions
if (counter_100ms == 0) {
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages started");
#endif // DEBUG_LOG
// @0 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
} else if (counter_100ms == 7) {
// @ 730 ms
BMWiX_16E.data.u8[0] = 0x8C;
BMWiX_16E.data.u8[1] = 0xA0;
transmit_can_frame(&BMWiX_16E, can_config.battery);
} else if (counter_100ms == 24) {
// @2380 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
} else if (counter_100ms == 29) {
// @ 2900 ms
BMWiX_16E.data.u8[0] = 0x02;
BMWiX_16E.data.u8[1] = 0xA7;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages finished");
#endif // DEBUG_LOG
} else if (counter_100ms == 140) {
// @14000 ms
// reset counter (outside of this function)
}
}
}
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms) {
if (contactorCloseReq == false) { // if contactors are not requested to be closed, they are requested to be opened
if (ContactorState.open == false) { // only if contactors are not open yet
// message content to quickly open contactors
if (counter_10ms == 0) {
// @0 ms (0.00) RX0 510 [8] 40 10 00 00 00 80 00 00
BMWiX_510.data = {0x40, 0x10, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00}; // Explicit declaration, to prevent modification by other functions
transmit_can_frame(&BMWiX_510, can_config.battery);
// set back to default values
BMWiX_510.data = {0x40, 0x10, 0x04, 0x00, 0x00, 0x80, 0x01, 0x00}; // default values
} else if (counter_10ms == 6) {
// @60 ms (0.06) RX0 16E [8] E6 A4 C8 FF 60 C9 33 F0
BMWiX_16E.data = {0xE6, 0xA4, 0xC8, 0xFF,
0x60, 0xC9, 0x33, 0xF0}; // Explicit declaration, to prevent modification by other functions
transmit_can_frame(&BMWiX_16E, can_config.battery);
// set back to default values
BMWiX_16E.data = {0x00, 0xA0, 0xC9, 0xFF, 0x60, 0xC9, 0x3A, 0xF7}; // default values
ContactorState.closed = false;
ContactorState.open = true;
}
}
}
}
#endif // BMW_IX_BATTERY

View file

@ -19,4 +19,69 @@
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
/**
* @brief Handle incoming user request to close or open contactors
*
* @param[in] void
*
* @return void
*/
void HandleIncomingUserRequest(void);
/**
* @brief Handle incoming inverter request to close or open contactors.alignas
*
* This function uses the "inverter_allows_contactor_closing" flag from the datalayer, to determine if CAN messages shall be sent to the battery to close or open the contactors.
*
* @param[in] void
*
* @return void
*/
void HandleIncomingInverterRequest(void);
/**
* @brief Close contactors of the BMW iX battery
*
* @param[in] void
*
* @return void
*/
void BmwIxCloseContactors(void);
/**
* @brief Handle close contactors requests for the BMW iX battery
*
* @param[in] counter_10ms Counter that increments by 1, every 10ms
*
* @return void
*/
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms);
/**
* @brief Keep contactors of the BMW iX battery closed
*
* @param[in] counter_100ms Counter that increments by 1, every 100 ms
*
* @return void
*/
void BmwIxKeepContactorsClosed(uint8_t counter_100ms);
/**
* @brief Open contactors of the BMW iX battery
*
* @param[in] void
*
* @return void
*/
void BmwIxOpenContactors(void);
/**
* @brief Handle open contactors requests for the BMW iX battery
*
* @param[in] counter_10ms Counter that increments by 1, every 10ms
*
* @return void
*/
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms);
#endif

View file

@ -1005,8 +1005,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
//if (battery_awake) { //We can always send CAN as the PHEV BMS will wake up on vehicle comms

View file

@ -100,18 +100,17 @@ void handle_incoming_can_frame_shunt(CAN_frame rx_frame) {
}
}
void transmit_can_shunt() {
unsigned long currentTime = millis();
void transmit_can_shunt(unsigned long currentMillis) {
/** Shunt can frames seen? **/
if (ShuntLastSeen + 1000 < currentTime) {
if (ShuntLastSeen + 1000 < currentMillis) {
datalayer.shunt.available = false;
} else {
datalayer.shunt.available = true;
}
// Send 20ms CAN Message
if (currentTime - LastMsgTime >= INTERVAL_20_MS) {
LastMsgTime = currentTime;
if (currentMillis - LastMsgTime >= INTERVAL_20_MS) {
LastMsgTime = currentMillis;
// First check if we have any active errors, incase we do, turn off the battery
if (datalayer.battery.status.bms_status == FAULT) {
timeSpentInFaultedMode++;
@ -154,16 +153,16 @@ void transmit_can_shunt() {
switch (contactorStatus) {
case PRECHARGE:
SBOX_100.data.u8[0] = 0x86; // Precharge relay only
prechargeStartTime = currentTime;
prechargeStartTime = currentMillis;
contactorStatus = NEGATIVE;
#ifdef DEBUG_VIA_USB
Serial.println("S-BOX Precharge relay engaged");
#endif
break;
case NEGATIVE:
if (currentTime - prechargeStartTime >= CONTACTOR_CONTROL_T1) {
if (currentMillis - prechargeStartTime >= CONTACTOR_CONTROL_T1) {
SBOX_100.data.u8[0] = 0xA6; // Precharge + Negative
negativeStartTime = currentTime;
negativeStartTime = currentMillis;
contactorStatus = POSITIVE;
datalayer.shunt.precharging = true;
#ifdef DEBUG_VIA_USB
@ -172,11 +171,11 @@ void transmit_can_shunt() {
}
break;
case POSITIVE:
if (currentTime - negativeStartTime >= CONTACTOR_CONTROL_T2 &&
if (currentMillis - negativeStartTime >= CONTACTOR_CONTROL_T2 &&
(datalayer.shunt.measured_voltage_mV * MAX_PRECHARGE_RESISTOR_VOLTAGE_PERCENT <
datalayer.shunt.measured_outvoltage_mV)) {
SBOX_100.data.u8[0] = 0xAA; // Precharge + Negative + Positive
positiveStartTime = currentTime;
positiveStartTime = currentMillis;
contactorStatus = PRECHARGE_OFF;
datalayer.shunt.precharging = false;
#ifdef DEBUG_VIA_USB
@ -185,7 +184,7 @@ void transmit_can_shunt() {
}
break;
case PRECHARGE_OFF:
if (currentTime - positiveStartTime >= CONTACTOR_CONTROL_T3) {
if (currentMillis - positiveStartTime >= CONTACTOR_CONTROL_T3) {
SBOX_100.data.u8[0] = 0x6A; // Negative + Positive
contactorStatus = COMPLETED;
#ifdef DEBUG_VIA_USB

View file

@ -769,17 +769,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
//Send 20ms message
if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis20ms >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20ms));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis20ms = currentMillis;
transmit_can_frame(&BOLT_778, can_config.battery);
}

View file

@ -6,19 +6,24 @@
#include "BYD-ATTO-3-BATTERY.h"
/* Notes
- SOC% by default is now ESTIMATED.
- If you have a non-crashed pack, enable using real SOC. See Wiki for info.
- TODO: In the future, we might be able to unlock crashed batteries and get SOC going always
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
*/
/* Do not change code below unless you are sure what you are doing */
#define NOT_DETERMINED_YET 0
#define STANDARD_RANGE 1
#define EXTENDED_RANGE 2
#define NOT_RUNNING 0xFF
#define STARTED 0
#define RUNNING_STEP_1 1
#define RUNNING_STEP_2 2
static uint8_t battery_type = NOT_DETERMINED_YET;
static uint8_t stateMachineClearCrash = NOT_RUNNING;
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static bool SOC_method = false;
static uint8_t counter_50ms = 0;
static uint8_t counter_100ms = 0;
@ -40,6 +45,20 @@ static int16_t BMS_highest_cell_temperature = 0;
static int16_t BMS_average_cell_temperature = 0;
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
static uint16_t BMS_highest_cell_voltage_mV = 3300;
static uint32_t BMS_unknown0 = 0;
static uint32_t BMS_unknown1 = 0;
static uint16_t BMS_allowed_charge_power = 0;
static uint16_t BMS_unknown3 = 0;
static uint16_t BMS_unknown4 = 0;
static uint16_t BMS_unknown5 = 0;
static uint16_t BMS_unknown6 = 0;
static uint16_t BMS_unknown7 = 0;
static uint16_t BMS_unknown8 = 0;
static uint16_t BMS_unknown9 = 0;
static uint8_t BMS_unknown10 = 0;
static uint8_t BMS_unknown11 = 0;
static uint8_t BMS_unknown12 = 0;
static uint8_t BMS_unknown13 = 0;
static uint8_t battery_frame_index = 0;
static uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#ifdef DOUBLE_BATTERY
@ -61,18 +80,93 @@ static uint16_t BMS2_highest_cell_voltage_mV = 3300;
static uint8_t battery2_frame_index = 0;
static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#endif //DOUBLE_BATTERY
#define POLL_FOR_BATTERY_SOC 0x05
#define POLL_FOR_BATTERY_VOLTAGE 0x08
#define POLL_FOR_BATTERY_CURRENT 0x09
#define POLL_FOR_LOWEST_TEMP_CELL 0x2f
#define POLL_FOR_HIGHEST_TEMP_CELL 0x31
#define POLL_FOR_BATTERY_PACK_AVG_TEMP 0x32
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
#define UNKNOWN_POLL_1 0xFC
#define POLL_FOR_BATTERY_SOC 0x0005
#define POLL_FOR_BATTERY_VOLTAGE 0x0008
#define POLL_FOR_BATTERY_CURRENT 0x0009
#define POLL_FOR_LOWEST_TEMP_CELL 0x002f
#define POLL_FOR_HIGHEST_TEMP_CELL 0x0031
#define POLL_FOR_BATTERY_PACK_AVG_TEMP 0x0032
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x002D
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x002B
#define UNKNOWN_POLL_0 0x1FFE //0x64 19 C4 3B
#define UNKNOWN_POLL_1 0x1FFC //0x72 1F C4 3B
#define POLL_MAX_CHARGE_POWER 0x000A
#define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!)
#define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!)
#define UNKNOWN_POLL_5 0x000F //0x00237B (9083 interesting!)
#define UNKNOWN_POLL_6 0x0010 //0x00231B (8987 interesting!)
#define UNKNOWN_POLL_7 0x0011 //0x0E4E (3662 interesting!)
#define UNKNOWN_POLL_8 0x0012 //0x0E27 (3623 interesting)
#define UNKNOWN_POLL_9 0x0004 //0x0034 (52 interesting!)
#define UNKNOWN_POLL_10 0x002A //0x5B
#define UNKNOWN_POLL_11 0x002E //0x08 (probably module number, or cell number?)
#define UNKNOWN_POLL_12 0x002C //0x43
#define UNKNOWN_POLL_13 0x0030 //0x01 (probably module number, or cell number?)
#define POLL_MODULE_1_LOWEST_MV_NUMBER 0x016C
#define POLL_MODULE_1_LOWEST_CELL_MV 0x016D
#define POLL_MODULE_1_HIGHEST_MV_NUMBER 0x016E
#define POLL_MODULE_1_HIGH_CELL_MV 0x016F
#define POLL_MODULE_1_HIGH_TEMP 0x0171
#define POLL_MODULE_1_LOW_TEMP 0x0173
#define POLL_MODULE_2_LOWEST_MV_NUMBER 0x0174
#define POLL_MODULE_2_LOWEST_CELL_MV 0x0175
#define POLL_MODULE_2_HIGHEST_MV_NUMBER 0x0176
#define POLL_MODULE_2_HIGH_CELL_MV 0x0177
#define POLL_MODULE_2_HIGH_TEMP 0x0179
#define POLL_MODULE_2_LOW_TEMP 0x017B
#define POLL_MODULE_3_LOWEST_MV_NUMBER 0x017C
#define POLL_MODULE_3_LOWEST_CELL_MV 0x017D
#define POLL_MODULE_3_HIGHEST_MV_NUMBER 0x017E
#define POLL_MODULE_3_HIGH_CELL_MV 0x017F
#define POLL_MODULE_3_HIGH_TEMP 0x0181
#define POLL_MODULE_3_LOW_TEMP 0x0183
#define POLL_MODULE_4_LOWEST_MV_NUMBER 0x0184
#define POLL_MODULE_4_LOWEST_CELL_MV 0x0185
#define POLL_MODULE_4_HIGHEST_MV_NUMBER 0x0186
#define POLL_MODULE_4_HIGH_CELL_MV 0x0187
#define POLL_MODULE_4_HIGH_TEMP 0x0189
#define POLL_MODULE_4_LOW_TEMP 0x018B
#define POLL_MODULE_5_LOWEST_MV_NUMBER 0x018C
#define POLL_MODULE_5_LOWEST_CELL_MV 0x018D
#define POLL_MODULE_5_HIGHEST_MV_NUMBER 0x018E
#define POLL_MODULE_5_HIGH_CELL_MV 0x018F
#define POLL_MODULE_5_HIGH_TEMP 0x0191
#define POLL_MODULE_5_LOW_TEMP 0x0193
#define POLL_MODULE_6_LOWEST_MV_NUMBER 0x0194
#define POLL_MODULE_6_LOWEST_CELL_MV 0x0195
#define POLL_MODULE_6_HIGHEST_MV_NUMBER 0x0196
#define POLL_MODULE_6_HIGH_CELL_MV 0x0197
#define POLL_MODULE_6_HIGH_TEMP 0x0199
#define POLL_MODULE_6_LOW_TEMP 0x019B
#define POLL_MODULE_7_LOWEST_MV_NUMBER 0x019C
#define POLL_MODULE_7_LOWEST_CELL_MV 0x019D
#define POLL_MODULE_7_HIGHEST_MV_NUMBER 0x019E
#define POLL_MODULE_7_HIGH_CELL_MV 0x019F
#define POLL_MODULE_7_HIGH_TEMP 0x01A1
#define POLL_MODULE_7_LOW_TEMP 0x01A3
#define POLL_MODULE_8_LOWEST_MV_NUMBER 0x01A4
#define POLL_MODULE_8_LOWEST_CELL_MV 0x01A5
#define POLL_MODULE_8_HIGHEST_MV_NUMBER 0x01A6
#define POLL_MODULE_8_HIGH_CELL_MV 0x01A7
#define POLL_MODULE_8_HIGH_TEMP 0x01A9
#define POLL_MODULE_8_LOW_TEMP 0x01AB
#define POLL_MODULE_9_LOWEST_MV_NUMBER 0x01AC
#define POLL_MODULE_9_LOWEST_CELL_MV 0x01AD
#define POLL_MODULE_9_HIGHEST_MV_NUMBER 0x01AE
#define POLL_MODULE_9_HIGH_CELL_MV 0x01AF
#define POLL_MODULE_9_HIGH_TEMP 0x01B1
#define POLL_MODULE_9_LOW_TEMP 0x01B3
#define POLL_MODULE_10_LOWEST_MV_NUMBER 0x01B4
#define POLL_MODULE_10_LOWEST_CELL_MV 0x01B5
#define POLL_MODULE_10_HIGHEST_MV_NUMBER 0x01B6
#define POLL_MODULE_10_HIGH_CELL_MV 0x01B7
#define POLL_MODULE_10_HIGH_TEMP 0x01B9
#define POLL_MODULE_10_LOW_TEMP 0x01BB
#define ESTIMATED 0
#define MEASURED 1
static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
static uint16_t pid_reply = 0;
CAN_frame ATTO_3_12D = {.FD = false,
.ext_ID = false,
@ -89,14 +183,29 @@ CAN_frame ATTO_3_7E7_POLL = {.FD = false,
.DLC = 8,
.ID = 0x7E7, //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_ACK = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7, //ACK frame for long PIDs
.data = {0x30, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ATTO_3_7E7_CLEAR_CRASH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
// Define the data points for %SOC depending on pack voltage
const uint8_t numPoints = 14;
const uint16_t SOC[numPoints] = {10000, 9970, 9490, 8470, 7750, 6790, 5500, 4900, 3910, 3000, 2280, 1600, 480, 0};
const uint16_t voltage_extended[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130,
4121, 4119, 4100, 4070, 4030, 3950, 3800};
const uint16_t voltage_standard[numPoints] = {3620, 3485, 3443, 3435, 3433, 3425, 3400,
3392, 3390, 3375, 3350, 3315, 3250, 3140};
const uint8_t numPoints = 28;
const uint16_t SOC[numPoints] = {10000, 9985, 9970, 9730, 9490, 8980, 8470, 8110, 7750, 7270, 6790, 6145, 5500, 5200,
4900, 4405, 3910, 3455, 3000, 2640, 2280, 1940, 1600, 1040, 480, 240, 120, 0};
const uint16_t voltage_extended[numPoints] = {4300, 4250, 4230, 4205, 4180, 4175, 4171, 4170, 4169, 4164,
4160, 4145, 4130, 4125, 4121, 4120, 4119, 4109, 4100, 4085,
4070, 4050, 4030, 3990, 3950, 3875, 3840, 3800};
const uint16_t voltage_standard[numPoints] = {3570, 3552, 3485, 3464, 3443, 3439, 3435, 3434, 3433, 3429,
3425, 3412, 3400, 3396, 3392, 3391, 3390, 3382, 3375, 3362,
3350, 3332, 3315, 3282, 3250, 3195, 3170, 3140};
uint16_t estimateSOCextended(uint16_t packVoltage) { // Linear interpolation function
if (packVoltage >= voltage_extended[0]) {
@ -161,7 +270,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer.battery.status.max_charge_power_W = MAXPOWER_CHARGE_W; //TODO: Map from CAN later on
datalayer.battery.status.max_charge_power_W = BMS_allowed_charge_power * 10; //TODO: Scaling unknown, *10 best guess
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
@ -227,11 +336,17 @@ void update_values_battery() { //This function maps all the values fetched via
}
}
//Write the result to datalayer
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10;
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
if ((battery_calc_min_temperature != 0) && (battery_calc_max_temperature != 0)) {
//Avoid triggering high delta if only one of the values is available
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10;
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
}
#else //User does not need filtering out a broken sensor, just use the min-max the BMS sends
datalayer.battery.status.temperature_min_dC = BMS_lowest_cell_temperature * 10;
datalayer.battery.status.temperature_max_dC = BMS_highest_cell_temperature * 10;
if ((BMS_lowest_cell_temperature != 0) && (BMS_highest_cell_temperature != 0)) {
//Avoid triggering high delta if only one of the values is available
datalayer.battery.status.temperature_min_dC = BMS_lowest_cell_temperature * 10;
datalayer.battery.status.temperature_max_dC = BMS_highest_cell_temperature * 10;
}
#endif //!SKIP_TEMPERATURE_SENSOR_NUMBER
// Update webserver datalayer
@ -252,6 +367,26 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.bydAtto3.battery_temperatures[7] = battery_daughterboard_temperatures[7];
datalayer_extended.bydAtto3.battery_temperatures[8] = battery_daughterboard_temperatures[8];
datalayer_extended.bydAtto3.battery_temperatures[9] = battery_daughterboard_temperatures[9];
datalayer_extended.bydAtto3.unknown0 = BMS_unknown0;
datalayer_extended.bydAtto3.unknown1 = BMS_unknown1;
datalayer_extended.bydAtto3.chargePower = BMS_allowed_charge_power;
datalayer_extended.bydAtto3.unknown3 = BMS_unknown3;
datalayer_extended.bydAtto3.unknown4 = BMS_unknown4;
datalayer_extended.bydAtto3.unknown5 = BMS_unknown5;
datalayer_extended.bydAtto3.unknown6 = BMS_unknown6;
datalayer_extended.bydAtto3.unknown7 = BMS_unknown7;
datalayer_extended.bydAtto3.unknown8 = BMS_unknown8;
datalayer_extended.bydAtto3.unknown9 = BMS_unknown9;
datalayer_extended.bydAtto3.unknown10 = BMS_unknown10;
datalayer_extended.bydAtto3.unknown11 = BMS_unknown11;
datalayer_extended.bydAtto3.unknown12 = BMS_unknown12;
datalayer_extended.bydAtto3.unknown13 = BMS_unknown13;
// Update requests from webserver datalayer
if (datalayer_extended.bydAtto3.UserRequestCrashReset && stateMachineClearCrash == NOT_RUNNING) {
stateMachineClearCrash = STARTED;
datalayer_extended.bydAtto3.UserRequestCrashReset = false;
}
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
@ -356,7 +491,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EF: //OBD2 PID reply from battery
switch (rx_frame.data.u8[3]) {
if (rx_frame.data.u8[0] == 0x10) {
transmit_can_frame(&ATTO_3_7E7_ACK, can_config.battery); //Send next line request
}
pid_reply = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
switch (pid_reply) {
case POLL_FOR_BATTERY_SOC:
BMS_SOC = rx_frame.data.u8[4];
break;
@ -381,6 +520,50 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case POLL_FOR_BATTERY_CELL_MV_MIN:
BMS_lowest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_0:
BMS_unknown0 = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) |
rx_frame.data.u8[4]);
break;
case UNKNOWN_POLL_1:
BMS_unknown1 = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) |
rx_frame.data.u8[4]);
break;
case POLL_MAX_CHARGE_POWER:
BMS_allowed_charge_power = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_3:
BMS_unknown3 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_4:
BMS_unknown4 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_5:
BMS_unknown5 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_6:
BMS_unknown6 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_7:
BMS_unknown7 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_8:
BMS_unknown8 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_9:
BMS_unknown9 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_10:
BMS_unknown10 = rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_11:
BMS_unknown11 = rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_12:
BMS_unknown12 = rx_frame.data.u8[4];
break;
case UNKNOWN_POLL_13:
BMS_unknown13 = rx_frame.data.u8[4];
break;
default: //Unrecognized reply
break;
}
@ -389,16 +572,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
//Send 50ms message
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis50 >= INTERVAL_50_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis50));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis50 = currentMillis;
// Set close contactors to allowed (Useful for crashed packs, started via contactor control thru GPIO)
@ -444,7 +620,6 @@ void transmit_can_battery() {
}
if (counter_100ms > 3) {
ATTO_3_441.data.u8[4] = 0x9D;
ATTO_3_441.data.u8[5] = 0x01;
ATTO_3_441.data.u8[6] = 0xFF;
@ -455,42 +630,141 @@ void transmit_can_battery() {
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_441, can_config.battery_double);
#endif //DOUBLE_BATTERY
switch (stateMachineClearCrash) {
case STARTED:
ATTO_3_7E7_CLEAR_CRASH.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery);
stateMachineClearCrash = RUNNING_STEP_1;
break;
case RUNNING_STEP_1:
ATTO_3_7E7_CLEAR_CRASH.data = {0x04, 0x14, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery);
stateMachineClearCrash = RUNNING_STEP_2;
break;
case RUNNING_STEP_2:
ATTO_3_7E7_CLEAR_CRASH.data = {0x03, 0x19, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&ATTO_3_7E7_CLEAR_CRASH, can_config.battery);
stateMachineClearCrash = NOT_RUNNING;
break;
case NOT_RUNNING:
break;
default:
break;
}
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
switch (poll_state) {
case POLL_FOR_BATTERY_SOC:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_SOC;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_BATTERY_SOC & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_BATTERY_SOC & 0x00FF);
poll_state = POLL_FOR_BATTERY_VOLTAGE;
break;
case POLL_FOR_BATTERY_VOLTAGE:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_BATTERY_VOLTAGE & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_BATTERY_VOLTAGE & 0x00FF);
poll_state = POLL_FOR_BATTERY_CURRENT;
break;
case POLL_FOR_BATTERY_CURRENT:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CURRENT;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_BATTERY_CURRENT & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_BATTERY_CURRENT & 0x00FF);
poll_state = POLL_FOR_LOWEST_TEMP_CELL;
break;
case POLL_FOR_LOWEST_TEMP_CELL:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_LOWEST_TEMP_CELL;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_LOWEST_TEMP_CELL & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_LOWEST_TEMP_CELL & 0x00FF);
poll_state = POLL_FOR_HIGHEST_TEMP_CELL;
break;
case POLL_FOR_HIGHEST_TEMP_CELL:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_HIGHEST_TEMP_CELL;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_HIGHEST_TEMP_CELL & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_HIGHEST_TEMP_CELL & 0x00FF);
poll_state = POLL_FOR_BATTERY_PACK_AVG_TEMP;
break;
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_PACK_AVG_TEMP;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_BATTERY_PACK_AVG_TEMP & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_BATTERY_PACK_AVG_TEMP & 0x00FF);
poll_state = POLL_FOR_BATTERY_CELL_MV_MAX;
break;
case POLL_FOR_BATTERY_CELL_MV_MAX:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CELL_MV_MAX;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_BATTERY_CELL_MV_MAX & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_BATTERY_CELL_MV_MAX & 0x00FF);
poll_state = POLL_FOR_BATTERY_CELL_MV_MIN;
break;
case POLL_FOR_BATTERY_CELL_MV_MIN:
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CELL_MV_MIN;
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_FOR_BATTERY_CELL_MV_MIN & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_FOR_BATTERY_CELL_MV_MIN & 0x00FF);
poll_state = UNKNOWN_POLL_0;
break;
case UNKNOWN_POLL_0:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_0 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_0 & 0x00FF);
poll_state = UNKNOWN_POLL_1;
break;
case UNKNOWN_POLL_1:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_1 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_1 & 0x00FF);
poll_state = POLL_MAX_CHARGE_POWER;
break;
case POLL_MAX_CHARGE_POWER:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_MAX_CHARGE_POWER & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_MAX_CHARGE_POWER & 0x00FF);
poll_state = UNKNOWN_POLL_3;
break;
case UNKNOWN_POLL_3:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_3 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_3 & 0x00FF);
poll_state = UNKNOWN_POLL_4;
break;
case UNKNOWN_POLL_4:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_4 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_4 & 0x00FF);
poll_state = UNKNOWN_POLL_5;
break;
case UNKNOWN_POLL_5:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_5 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_5 & 0x00FF);
poll_state = UNKNOWN_POLL_6;
break;
case UNKNOWN_POLL_6:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_6 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_6 & 0x00FF);
poll_state = UNKNOWN_POLL_7;
break;
case UNKNOWN_POLL_7:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_7 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_7 & 0x00FF);
poll_state = UNKNOWN_POLL_8;
break;
case UNKNOWN_POLL_8:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_8 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_8 & 0x00FF);
poll_state = UNKNOWN_POLL_9;
break;
case UNKNOWN_POLL_9:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_9 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_9 & 0x00FF);
poll_state = UNKNOWN_POLL_10;
break;
case UNKNOWN_POLL_10:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_10 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_10 & 0x00FF);
poll_state = UNKNOWN_POLL_11;
break;
case UNKNOWN_POLL_11:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_11 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_11 & 0x00FF);
poll_state = UNKNOWN_POLL_12;
break;
case UNKNOWN_POLL_12:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_12 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_12 & 0x00FF);
poll_state = UNKNOWN_POLL_13;
break;
case UNKNOWN_POLL_13:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_13 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_13 & 0x00FF);
poll_state = POLL_FOR_BATTERY_SOC;
break;
default:
@ -498,10 +772,12 @@ void transmit_can_battery() {
break;
}
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery);
if (stateMachineClearCrash == NOT_RUNNING) { //Don't poll battery for data if clear crash running
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery_double);
transmit_can_frame(&ATTO_3_7E7_POLL, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
}
}

View file

@ -20,9 +20,9 @@
#define MIN_PACK_VOLTAGE_EXTENDED_DV 3800 //Extended range
#define MAX_PACK_VOLTAGE_STANDARD_DV 3640 //Standard range
#define MIN_PACK_VOLTAGE_STANDARD_DV 3136 //Standard range
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 3800 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 230
#define MAX_CELL_VOLTAGE_MV 3650 //Charging stops if one cell exceeds this value
#define MIN_CELL_VOLTAGE_MV 2800 //Discharging stops if one cell goes below this value
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -316,11 +316,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 1s CAN Message
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;
/*

View file

@ -655,9 +655,7 @@ void update_evse_discharge_capabilities(CAN_frame& f) {
CHADEMO_208.data.u8[7] = highByte(x208_evse_dischg_cap.lower_threshold_voltage);
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
handlerBeforeMillis = currentMillis;
handle_chademo_sequence();
@ -665,12 +663,6 @@ void transmit_can_battery() {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis100 = currentMillis;
/* no EVSE messages should be sent until the vehicle has

View file

@ -513,14 +513,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 10ms CAN Message
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms));
}
previousMillis10ms = currentMillis;
transmit_can_frame(&CMFA_1EA, can_config.battery);
transmit_can_frame(&CMFA_135, can_config.battery);

View file

@ -0,0 +1,17 @@
#ifndef CAN_BATTERY_H
#define CAN_BATTERY_H
#include "src/devboard/utils/types.h"
// Abstract base class for next-generation battery implementations.
// Defines the interface to call battery specific functionality.
// No support for double battery yet.
class CanBattery {
public:
virtual void setup(void) = 0;
virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0;
virtual void update_values() = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
};
#endif

View file

@ -69,6 +69,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX;
datalayer.system.status.battery_allows_contactor_closing = true;
}
uint8_t calculate_checksum(uint8_t buff[12]) {

View file

@ -288,8 +288,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 1s CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
@ -303,6 +302,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = 4546; // 454.6V, charging over this is not possible
datalayer.battery.info.min_design_voltage_dV = 3210; // 321.0V, under this, discharging further is disabled
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -474,9 +474,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
@ -654,6 +652,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -207,16 +207,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis100 = currentMillis;
// Send CAN goes here...
@ -231,6 +225,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -1,5 +1,6 @@
#include "../include.h"
#ifdef JAGUAR_IPACE_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "JAGUAR-IPACE-BATTERY.h"
@ -62,7 +63,7 @@ void print_units(char* header, int value, char* units) {
logging.print(units);
}
void update_values_battery() {
void JaguarIpaceBattery::update_values() {
datalayer.battery.status.real_soc = HVBattAvgSOC * 100; //Add two decimals
@ -119,14 +120,7 @@ void update_values_battery() {
#endif
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
// Do not log noisy startup messages - there are many !
if (rx_frame.ID == 0 && rx_frame.DLC == 8 && rx_frame.data.u8[0] == 0 && rx_frame.data.u8[1] == 0 &&
rx_frame.data.u8[2] == 0 && rx_frame.data.u8[3] == 0 && rx_frame.data.u8[4] == 0 && rx_frame.data.u8[5] == 0 &&
rx_frame.data.u8[6] == 0x80 && rx_frame.data.u8[7] == 0) {
return;
}
void JaguarIpaceBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { // These messages are periodically transmitted by the battery
case 0x080:
@ -222,38 +216,17 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
default:
break;
}
// Discard non-interesting can messages so they do not get logged via serial
if (rx_frame.ID < 0x500) {
return;
}
// All CAN messages recieved will be logged via serial
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
logging.println("");
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void JaguarIpaceBattery::transmit_can(unsigned long currentMillis) {
/* Send keep-alive every 200ms */
if (currentMillis - previousMillisKeepAlive >= INTERVAL_200_MS) {
previousMillisKeepAlive = currentMillis;
transmit_can_frame(&ipace_keep_alive, can_config.battery);
return;
}
}
void setup_battery(void) { // Performs one time setup at startup
void JaguarIpaceBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108;
@ -262,7 +235,6 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}

View file

@ -1,15 +1,23 @@
#ifndef JAGUAR_IPACE_BATTERY_H
#define JAGUAR_IPACE_BATTERY_H
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS JaguarIpaceBattery
#define MAX_PACK_VOLTAGE_DV 4546 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3370
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
class JaguarIpaceBattery : 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);
};
#endif

View file

@ -1082,8 +1082,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
if (startedUp) {
//Send Contactor closing message loop
// Check if we still have messages to send
@ -1108,13 +1107,6 @@ void transmit_can_battery() {
//Send 200ms CANFD message
if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) {
previousMillis200ms = currentMillis;
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis200ms >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200ms));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis200ms = currentMillis;
EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER;

View file

@ -924,8 +924,7 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
}
#endif //DOUBLE_BATTERY
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
if (!startedUp) {
return; // Don't send any CAN messages towards battery until it has started up
@ -948,12 +947,6 @@ void transmit_can_battery() {
}
// Send 10ms CAN Message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis10 = currentMillis;
switch (counter_200) {
@ -1063,7 +1056,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;

View file

@ -230,8 +230,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
@ -259,7 +258,7 @@ void transmit_can_battery() {
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -1627,9 +1627,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
// Send 10ms CAN Message
void transmit_can_battery(unsigned long currentMillis) {
if (currentMillis > last_can_msg_timestamp + 500) {
#ifdef DEBUG_LOG
if (first_can_msg)
@ -1642,15 +1641,8 @@ void transmit_can_battery() {
datalayer.system.status.battery_allows_contactor_closing = false;
}
}
// Send 10ms CAN Message
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME) &&
previousMillis10ms > 0) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10ms));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis10ms = currentMillis;
MEB_0FC.data.u8[1] = ((MEB_0FC.data.u8[1] & 0xF0) | counter_10ms);

View file

@ -108,16 +108,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
//Send 10ms message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis10 = currentMillis;
transmit_can_frame(&MG_5_100, can_config.battery);
@ -133,7 +126,7 @@ void transmit_can_battery() {
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
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;

File diff suppressed because it is too large Load diff

View file

@ -1,26 +1,188 @@
#ifndef NISSAN_LEAF_BATTERY_H
#define NISSAN_LEAF_BATTERY_H
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS NissanLeafBattery
#define EXTENDED_DATA_PTR (&datalayer_extended.nissanleaf)
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2600
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
bool is_message_corrupt(CAN_frame rx_frame);
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void clearSOH(void);
//Cryptographic functions
void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer);
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2);
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3);
short ShortMaskedSumAndProduct(short param_1, short param_2);
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2);
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3);
class NissanLeafBattery : public CanBattery {
public:
// Use this constructor for the second battery.
NissanLeafBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* allows_contactor_closing_ptr,
DATALAYER_INFO_NISSAN_LEAF* extended, int targetCan) {
datalayer_battery = datalayer_ptr;
allows_contactor_closing = allows_contactor_closing_ptr;
datalayer_nissan = extended;
can_interface = targetCan;
battery_Total_Voltage2 = 0;
}
// Use the default constructor to create the first or single battery.
NissanLeafBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
datalayer_nissan = &datalayer_extended.nissanleaf;
can_interface = can_config.battery;
}
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);
private:
bool is_message_corrupt(CAN_frame rx_frame);
void clearSOH(void);
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_NISSAN_LEAF* datalayer_nissan;
bool* allows_contactor_closing;
int can_interface;
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
uint8_t mprun10 = 0; //counter 0-3
uint8_t mprun100 = 0; //counter 0-3
// These CAN messages need to be sent towards the battery to keep it alive
CAN_frame LEAF_1F2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
CAN_frame LEAF_50B = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
CAN_frame LEAF_50C = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x50C,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1D4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Active polling messages
uint8_t PIDgroups[6] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0;
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
CAN_frame LEAF_NEXT_LINE_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 1, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
// The Li-ion battery controller only accepts a multi-message query. In fact, the LBC transmits many
// groups: the first one contains lots of High Voltage battery data as SOC, currents, and voltage; the second
// replies with all the batterys cells voltages in millivolt, the third and the fifth one are still unknown, the
// fourth contains the four battery packs temperatures, and the last one tells which cell has the shunt active.
// There are also two more groups: group 61, which replies with lots of CAN messages (up to 48); here we
// found the SOH value, and group 84 that replies with the HV battery production serial.
uint8_t crctable[256] = {
0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
//Nissan LEAF battery parameters from constantly sent CAN
#define ZE0_BATTERY 0
#define AZE0_BATTERY 1
#define ZE1_BATTERY 2
uint8_t LEAF_battery_Type = ZE0_BATTERY;
bool battery_can_alive = false;
#define WH_PER_GID 77 //One GID is this amount of Watt hours
uint16_t battery_Discharge_Power_Limit = 0; //Limit in kW
uint16_t battery_Charge_Power_Limit = 0; //Limit in kW
int16_t battery_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
int16_t battery_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
uint16_t battery_TEMP = 0; //Temporary value used in status checks
uint16_t battery_Wh_Remaining = 0; //Amount of energy in battery, in Wh
uint16_t battery_GIDS = 273; //Startup in 24kWh mode
uint16_t battery_MAX = 0;
uint16_t battery_Max_GIDS = 273; //Startup in 24kWh mode
uint16_t battery_StateOfHealth = 99; //State of health %
uint16_t battery_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800]
int16_t battery_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400]
int16_t battery_HistData_Temperature_MAX = 6; //-40 to 86*C
int16_t battery_HistData_Temperature_MIN = 5; //-40 to 86*C
int16_t battery_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
uint8_t battery_Relay_Cut_Request = 0; //battery_FAIL
uint8_t battery_Failsafe_Status = 0; //battery_STATUS
bool battery_Interlock =
true; //Contains info on if HV leads are seated (Note, to use this both HV connectors need to be inserted)
bool battery_Full_CHARGE_flag = false; //battery_FCHGEND , Goes to 1 if battery is fully charged
bool battery_MainRelayOn_flag = false; //No-Permission=0, Main Relay On Permission=1
bool battery_Capacity_Empty = false; //battery_EMPTY, , Goes to 1 if battery is empty
bool battery_HeatExist = false; //battery_HEATEXIST, Specifies if battery pack is equipped with heating elements
bool battery_Heating_Stop = false; //When transitioning from 0->1, signals a STOP heat request
bool battery_Heating_Start = false; //When transitioning from 1->0, signals a START heat request
bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a heat request is happening
// Nissan LEAF battery data from polled CAN messages
uint8_t battery_request_idx = 0;
uint8_t group_7bb = 0;
bool stop_battery_query = true;
uint8_t hold_off_with_polling_10seconds = 2; //Paused for 20 seconds on startup
uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
uint8_t battery_cellcounter = 0;
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_insulation = 0; //Insulation resistance
uint16_t battery_temp_raw_1 = 0;
uint8_t battery_temp_raw_2_highnibble = 0;
uint16_t battery_temp_raw_2 = 0;
uint16_t battery_temp_raw_3 = 0;
uint16_t battery_temp_raw_4 = 0;
uint16_t battery_temp_raw_max = 0;
uint16_t battery_temp_raw_min = 0;
int16_t battery_temp_polled_max = 0;
int16_t battery_temp_polled_min = 0;
uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
uint8_t BMSIDcode[8] = {0};
// Clear SOH values
uint8_t stateMachineClearSOH = 0xFF;
uint32_t incomingChallenge = 0xFFFFFFFF;
uint8_t solvedChallenge[8];
bool challengeFailed = false;
CAN_frame LEAF_CLEAR_SOH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
};
#endif

View file

@ -132,8 +132,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// No transmission needed for this integration
}

View file

@ -158,11 +158,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 1s CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
transmit_can_frame(&PYLON_3010, can_config.battery); // Heartbeat
@ -325,7 +323,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;

View file

@ -301,11 +301,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 50ms CAN Message
if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) {
previousMillis50ms = currentMillis;
transmit_can_frame(&RANGE_ROVER_18B, can_config.battery);
@ -315,6 +313,7 @@ void transmit_can_battery() {
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
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;

View file

@ -210,8 +210,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message (for 2.4s, then pause 10s)
if ((currentMillis - previousMillis100) >= (INTERVAL_100_MS + GVL_pause)) {
previousMillis100 = currentMillis;
@ -237,7 +236,7 @@ void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
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;

View file

@ -127,7 +127,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
void transmit_can_battery(unsigned long currentMillis) {
// we do not need to send anything to the battery for now
}
@ -140,6 +140,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = 6600;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -4,6 +4,8 @@
#include "../devboard/utils/events.h"
#include "RENAULT-ZOE-GEN1-BATTERY.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
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
@ -75,7 +77,8 @@ static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN
static unsigned long previousMillis250 = 0; // will store last time a 250ms CAN Message was sent
static uint8_t counter_423 = 0;
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
void RenaultZoeGen1Battery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00%
datalayer.battery.status.real_soc = SOC_polled;
@ -134,7 +137,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.voltage_dV = static_cast<uint32_t>((calculated_total_pack_voltage_mV / 100)); // mV to dV
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
void RenaultZoeGen1Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x155: //10ms - Charging power, current and SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
@ -490,14 +493,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void RenaultZoeGen1Battery::transmit_can(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
}
previousMillis100 = currentMillis;
transmit_can_frame(&ZOE_423, can_config.battery);
@ -542,7 +541,7 @@ void transmit_can_battery() {
}
}
void setup_battery(void) { // Performs one time setup at startup
void RenaultZoeGen1Battery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;

View file

@ -1,15 +1,23 @@
#ifndef RENAULT_ZOE_GEN1_BATTERY_H
#define RENAULT_ZOE_GEN1_BATTERY_H
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS RenaultZoeGen1Battery
#define MAX_PACK_VOLTAGE_DV 4200 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
class RenaultZoeGen1Battery : 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);
};
#endif

View file

@ -64,12 +64,24 @@ static uint16_t battery_time = 0;
static uint16_t battery_pack_time = 0;
static uint16_t battery_soc_min = 0;
static uint16_t battery_soc_max = 0;
static uint32_t ZOE_376_time_now_s = 1745452800; // Initialized to make the battery think it is April 24, 2025
unsigned long kProductionTimestamp_s =
1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT
CAN_frame ZOE_373 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x373,
.data = {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}};
CAN_frame ZOE_373 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x373,
.data = {0xC1, 0x40, 0x5D, 0xB2, 0x00, 0x01, 0xff,
0xe3}}; // FIXME: remove if not needed: {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}};
CAN_frame ZOE_376 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x373,
.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.
CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
@ -151,8 +163,9 @@ static uint8_t poll_index = 0;
static uint16_t currentpoll = POLL_SOC;
static uint16_t reply_poll = 0;
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = battery_soh;
@ -371,40 +384,53 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
void transmit_can_battery(unsigned long currentMillis) {
if (datalayer_extended.zoePH2.UserRequestNVROLReset) {
// Send NVROL reset frames
transmit_reset_nvrol_frames();
// after transmitting the NVROL reset frames, set the nvrol reset flag to false, to continue normal operation
datalayer_extended.zoePH2.UserRequestNVROLReset = false;
} else {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
if ((counter_373 / 5) % 2 == 0) { // Alternate every 5 messages between these two
ZOE_373.data.u8[2] = 0xB2;
ZOE_373.data.u8[3] = 0xB2;
} else {
ZOE_373.data.u8[2] = 0x5D;
ZOE_373.data.u8[3] = 0x5D;
/* FIXME: remove if not needed
if ((counter_373 / 5) % 2 == 0) { // Alternate every 5 messages between these two
ZOE_373.data.u8[2] = 0xB2;
ZOE_373.data.u8[3] = 0xB2;
} else {
ZOE_373.data.u8[2] = 0x5D;
ZOE_373.data.u8[3] = 0x5D;
}
counter_373 = (counter_373 + 1) % 10;
*/
transmit_can_frame(&ZOE_373, can_config.battery);
transmit_can_frame_376();
}
counter_373 = (counter_373 + 1) % 10;
transmit_can_frame(&ZOE_373, can_config.battery);
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis200 >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
// Update current poll from the array
currentpoll = poll_commands[poll_index];
poll_index = (poll_index + 1) % 48;
ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
}
previousMillis200 = currentMillis;
// Update current poll from the array
currentpoll = poll_commands[poll_index];
poll_index = (poll_index + 1) % 48;
// 1000mss
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
// Time in seconds emulated
ZOE_376_time_now_s++; // Increment by 1 second
}
}
}
@ -420,4 +446,62 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
void transmit_can_frame_376(void) {
unsigned int secondsSinceProduction = ZOE_376_time_now_s - kProductionTimestamp_s;
float minutesSinceProduction = (float)secondsSinceProduction / 60.0;
float yearUnfloored = minutesSinceProduction / 255.0 / 255.0;
int yearSeg = floor(yearUnfloored);
float remainderYears = yearUnfloored - yearSeg;
float remainderHoursUnfloored = (remainderYears * 255.0);
int hourSeg = floor(remainderHoursUnfloored);
float remainderHours = remainderHoursUnfloored - hourSeg;
int minuteSeg = floor(remainderHours * 255.0);
ZOE_376.data.u8[0] = yearSeg;
ZOE_376.data.u8[1] = hourSeg;
ZOE_376.data.u8[2] = minuteSeg;
ZOE_376.data.u8[3] = yearSeg;
ZOE_376.data.u8[4] = hourSeg;
ZOE_376.data.u8[5] = minuteSeg;
transmit_can_frame(&ZOE_376, can_config.battery);
}
void transmit_reset_nvrol_frames(void) {
// NVROL reset, part 1: send 0x021003AAAAAAAAAA
ZOE_POLL_18DADBF1.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
// wait 100 ms
wait_ms(100);
// NVROL reset, part 2: send 0x043101B00900AAAA
ZOE_POLL_18DADBF1.data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA};
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
// wait 1 s
wait_ms(1000);
// Enable temporisation before sleep, part 1: send 0x021003AAAAAAAAAA
ZOE_POLL_18DADBF1.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
// wait 100 ms
wait_ms(100);
// Enable temporisation before sleep, part 2: send 0x042E928101AAAAAA
ZOE_POLL_18DADBF1.data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA};
transmit_can_frame(&ZOE_POLL_18DADBF1, can_config.battery);
// Set data back to init values
ZOE_POLL_18DADBF1.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00};
poll_index = 0;
// after transmitting these frames, wait 30 s
wait_ms(30000);
}
void wait_ms(int duration_ms) {
unsigned long freezeMillis = millis();
while (millis() - freezeMillis < duration_ms) {
// Do nothing - just wait
}
}
#endif

View file

@ -12,6 +12,34 @@
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
/**
* @brief Transmit CAN frame 0x376
*
* @param[in] void
*
* @return void
*
*/
void transmit_can_frame_376(void);
/**
* @brief Reset NVROL, by sending specific frames
*
* @param[in] void
*
* @return void
*/
void transmit_reset_nvrol_frames(void);
/**
* @brief Wait function
*
* @param[in] duration_ms wait duration in ms
*
* @return void
*/
void wait_ms(int duration_ms);
#define POLL_SOC 0x9001
#define POLL_USABLE_SOC 0x9002
#define POLL_SOH 0x9003

View file

@ -571,11 +571,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 10s CAN Message
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
previousMillis10s = currentMillis;
if (datalayer.battery.status.bms_status == FAULT) {
@ -597,6 +595,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif // RJXZS_BMS

View file

@ -333,17 +333,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
//Send 10ms message
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis10 = currentMillis;
SANTAFE_200.data.u8[6] = (counter_200 << 1);
@ -671,7 +663,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;

View file

@ -115,7 +115,9 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
}
}
void transmit_can_battery() {}
void transmit_can_battery(unsigned long currentMillis) {
// No periodic transmitting for this battery type
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "SIMPBMS battery", 63);

View file

@ -137,9 +137,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
@ -172,6 +170,7 @@ void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Sono Motors Sion 64kWh LFP ", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.system.status.battery_allows_contactor_closing = true;
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;

View file

@ -1864,14 +1864,12 @@ int index_1CF = 0;
int index_118 = 0;
#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL)
void transmit_can_battery() {
void transmit_can_battery(unsigned long currentMillis) {
/*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then,
to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so
two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send
the first, for a few cycles, then stop all messages which causes the contactor to open. */
unsigned long currentMillis = millis();
if (!cellvoltagesRead) {
return; //All cellvoltages not read yet, do not proceed with contactor closing
}
@ -1906,12 +1904,6 @@ the first, for a few cycles, then stop all messages which causes the contactor
//Send 50ms message
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis50 >= INTERVAL_50_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis50));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis50 = currentMillis;
if ((datalayer.system.status.inverter_allows_contactor_closing == true) &&

View file

@ -130,8 +130,7 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;

View file

@ -435,16 +435,9 @@ void readCellVoltages() {
transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis100 = currentMillis;
transmit_can_frame(&VOLVO_536, can_config.battery); //Send 0x536 Network managing frame to keep BMS alive

View file

@ -6,11 +6,11 @@
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_108S_DV 4540
#define MIN_PACK_VOLTAGE_108S_DV 2938
#define MAX_PACK_VOLTAGE_96S_DV 4030
#define MAX_PACK_VOLTAGE_96S_DV 4080
#define MIN_PACK_VOLTAGE_96S_DV 2620
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_MV 4260 // Charging is halted if one cell goes above this
#define MIN_CELL_VOLTAGE_MV 2700 // Charging is halted if one cell goes below this
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -610,16 +610,9 @@ void readCellVoltages() {
transmit_can_frame(&VOLVO_CELL_U_Req, can_config.battery); //Send cell voltage read request for first module
}
void transmit_can_battery() {
unsigned long currentMillis = millis();
void transmit_can_battery(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis100 = currentMillis;
transmit_can_frame(&VOLVO_536, can_config.battery); //Send 0x536 Network managing frame to keep BMS alive

View file

@ -0,0 +1,9 @@
#include "../include.h"
CanCharger* charger = nullptr;
void setup_charger() {
#ifdef SELECTED_CHARGER_CLASS
charger = new SELECTED_CHARGER_CLASS();
#endif
}

View file

@ -2,15 +2,14 @@
#define CHARGERS_H
#include "../../USER_SETTINGS.h"
#ifdef CHEVYVOLT_CHARGER
#include "CHEVY-VOLT-CHARGER.h"
#endif
#ifdef NISSANLEAF_CHARGER
#include "NISSAN-LEAF-CHARGER.h"
#endif
void map_can_frame_to_variable_charger(CAN_frame rx_frame);
void transmit_can_charger();
// Constructs the global charger object based on build-time selection of charger type.
// Safe to call even though no charger is selected.
void setup_charger();
// The selected charger or null if no charger in use.
extern CanCharger* charger;
#endif

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef CHEVYVOLT_CHARGER
#include "../datalayer/datalayer.h"
#include "CHEVY-VOLT-CHARGER.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* This implements Chevy Volt / Ampera charger support (2011-2015 model years).
*
@ -19,29 +19,8 @@
* 2024 smaresca
*/
/* CAN cycles and timers */
static unsigned long previousMillis30ms = 0; // 30ms cycle for keepalive frames
static unsigned long previousMillis200ms = 0; // 200ms cycle for commanding I/V targets
static unsigned long previousMillis5000ms = 0; // 5s status printout to serial
enum CHARGER_MODES : uint8_t { MODE_DISABLED = 0, MODE_LV, MODE_HV, MODE_HVLV };
//Actual content messages
static CAN_frame charger_keepalive_frame = {.FD = false,
.ext_ID = false,
.DLC = 1,
.ID = 0x30E, //one byte only, indicating enabled or disabled
.data = {MODE_DISABLED}};
static CAN_frame charger_set_targets = {
.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x304,
.data = {0x40, 0x00, 0x00, 0x00}}; // data[0] is a static value, meaning unknown
/* We are mostly sending out not receiving */
void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
void ChevyVoltCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
uint16_t charger_stat_HVcur_temp = 0;
uint16_t charger_stat_HVvol_temp = 0;
uint16_t charger_stat_LVcur_temp = 0;
@ -92,14 +71,13 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
break;
default:
#ifdef DEBUG_LOG
logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID);
logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.ID);
#endif
break;
}
}
void transmit_can_charger() {
unsigned long currentMillis = millis();
void ChevyVoltCharger::transmit_can(unsigned long currentMillis) {
uint16_t Vol_temp = 0;
uint16_t setpoint_HV_VDC = floor(datalayer.charger.charger_setpoint_HV_VDC);
@ -171,12 +149,11 @@ void transmit_can_charger() {
/* Serial echo every 5s of charger stats */
if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) {
previousMillis5000ms = currentMillis;
logging.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol);
logging.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol);
logging.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol);
logging.printf("Charger AC in IAC=%fA VAC=%fV\n", AC_input_current(), AC_input_voltage());
logging.printf("Charger HV out IDC=%fA VDC=%fV\n", HVDC_output_current(), HVDC_output_voltage());
logging.printf("Charger LV out IDC=%fA VDC=%fV\n", LVDC_output_current(), LVDC_output_voltage());
logging.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
logging.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
}
#endif
}
#endif

View file

@ -1,18 +1,62 @@
#ifndef CHEVYVOLT_CHARGER_H
#define CHEVYVOLT_CHARGER_H
#include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../include.h"
#include "CanCharger.h"
#define CHARGER_SELECTED
#ifdef CHEVYVOLT_CHARGER
#define SELECTED_CHARGER_CLASS ChevyVoltCharger
#endif
/* Charger hardware limits
class ChevyVoltCharger : public CanCharger {
public:
ChevyVoltCharger() : CanCharger(ChargerType::ChevyVolt) {}
const char* name() { return "Chevy Volt Gen1 Charger"; }
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can(unsigned long currentMillis);
float outputPowerDC() {
return static_cast<float>(datalayer.charger.charger_stat_HVcur * datalayer.charger.charger_stat_HVvol);
}
bool efficiencySupported() { return true; }
float efficiency() {
float chgPwrAC = static_cast<float>(datalayer.charger.charger_stat_ACcur * datalayer.charger.charger_stat_ACvol);
return outputPowerDC() / chgPwrAC * 100;
}
private:
/* Charger hardware limits
*
* Relative to runtime settings, expectations are:
* hw minimum <= setting minimum <= setting maximum <= hw max
*/
#define CHEVYVOLT_MAX_HVDC 420.0
#define CHEVYVOLT_MIN_HVDC 200.0
#define CHEVYVOLT_MAX_AMP 11.5
#define CHEVYVOLT_MAX_POWER 3300
const float CHEVYVOLT_MAX_HVDC = 420.0;
const float CHEVYVOLT_MIN_HVDC = 200.0;
const float CHEVYVOLT_MAX_AMP = 11.5;
const float CHEVYVOLT_MAX_POWER = 3300;
/* CAN cycles and timers */
unsigned long previousMillis30ms = 0; // 30ms cycle for keepalive frames
unsigned long previousMillis200ms = 0; // 200ms cycle for commanding I/V targets
unsigned long previousMillis5000ms = 0; // 5s status printout to serial
enum CHARGER_MODES : uint8_t { MODE_DISABLED = 0, MODE_LV, MODE_HV, MODE_HVLV };
//Actual content messages
CAN_frame charger_keepalive_frame = {.FD = false,
.ext_ID = false,
.DLC = 1,
.ID = 0x30E, //one byte only, indicating enabled or disabled
.data = {MODE_DISABLED}};
CAN_frame charger_set_targets = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x304,
.data = {0x40, 0x00, 0x00, 0x00}}; // data[0] is a value, meaning unknown
};
#endif

View file

@ -0,0 +1,48 @@
#ifndef CAN_CHARGER_H
#define CAN_CHARGER_H
#include "src/devboard/utils/types.h"
#include "../datalayer/datalayer.h"
enum class ChargerType { NissanLeaf, ChevyVolt };
// Generic base class for all chargers
class Charger {
public:
ChargerType type() { return m_type; }
virtual const char* name() = 0;
virtual float outputPowerDC() = 0;
virtual float HVDC_output_voltage() { return datalayer.charger.charger_stat_HVvol; }
virtual float HVDC_output_current() { return datalayer.charger.charger_stat_HVcur; }
virtual float LVDC_output_voltage() { return datalayer.charger.charger_stat_LVvol; }
virtual float LVDC_output_current() { return datalayer.charger.charger_stat_LVcur; }
virtual float AC_input_voltage() { return datalayer.charger.charger_stat_ACvol; }
virtual float AC_input_current() { return datalayer.charger.charger_stat_ACcur; }
virtual bool efficiencySupported() { return false; }
virtual float efficiency() { return 0; }
protected:
Charger(ChargerType type) : m_type(type) {}
private:
ChargerType m_type;
};
// Base class for chargers on a CAN bus
class CanCharger : public Charger {
public:
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
protected:
CanCharger(ChargerType type) : Charger(type) {}
};
#endif

View file

@ -1,7 +1,7 @@
#include "../include.h"
#ifdef NISSANLEAF_CHARGER
#include "../datalayer/datalayer.h"
#include "NISSAN-LEAF-CHARGER.h"
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../include.h"
/* This implements Nissan LEAF PDM charger support. 2013-2024 Gen2/3 PDMs are supported
*
@ -20,68 +20,6 @@
* battery onto the CAN bus.
*/
/* CAN cycles and timers */
static unsigned long previousMillis10ms = 0;
static unsigned long previousMillis100ms = 0;
/* LEAF charger/battery parameters */
enum OBC_MODES : uint8_t {
IDLE_OR_QC = 1,
FINISHED = 2,
CHARGING_OR_INTERRUPTED = 4,
IDLE1 = 8,
IDLE2 = 9,
PLUGGED_IN_WAITING_ON_TIMER
};
enum OBC_VOLTAGES : uint8_t { NO_SIGNAL = 0, AC110 = 1, AC230 = 2, ABNORMAL_WAVE = 3 };
static uint16_t OBC_Charge_Power = 0; // Actual charger output
static uint8_t mprun100 = 0; // Counter 0-3
static uint8_t mprun10 = 0; // Counter 0-3
static uint8_t OBC_Charge_Status = IDLE_OR_QC;
static uint8_t OBC_Status_AC_Voltage = 0; //1=110V, 2=230V
static uint8_t OBCpowerSetpoint = 0;
static uint8_t OBCpower = 0;
static bool PPStatus = false;
static bool OBCwakeup = false;
//Actual content messages
static CAN_frame LEAF_1DB = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DB,
.data = {0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00}};
static CAN_frame LEAF_1DC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DC,
.data = {0x6E, 0x0A, 0x05, 0xD5, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame LEAF_1F2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x30, 0x00, 0x20, 0xAC, 0x00, 0x3C, 0x00, 0x8F}};
static CAN_frame LEAF_50B = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
static CAN_frame LEAF_55B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x55B,
.data = {0xA4, 0x40, 0xAA, 0x00, 0xDF, 0xC0, 0x10, 0x00}};
static CAN_frame LEAF_5BC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5BC,
.data = {0x3D, 0x80, 0xF0, 0x64, 0xB0, 0x01, 0x00, 0x32}};
static CAN_frame LEAF_59E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x59E,
.data = {0x00, 0x00, 0x0C, 0x76, 0x18, 0x00, 0x00, 0x00}};
static uint8_t crctable[256] = {
0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
@ -114,7 +52,7 @@ static uint8_t calculate_checksum_nibble(CAN_frame* frame) {
return sum;
}
void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
void NissanLeafCharger::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x679: // This message fires once when charging cable is plugged in
@ -156,8 +94,7 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
}
}
void transmit_can_charger() {
unsigned long currentMillis = millis();
void NissanLeafCharger::transmit_can(unsigned long currentMillis) {
/* Send keepalive with mode every 10ms */
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
@ -247,4 +184,3 @@ void transmit_can_charger() {
#endif
}
}
#endif

View file

@ -3,6 +3,94 @@
#include <Arduino.h>
#include "../include.h"
#define CHARGER_SELECTED
#include "CanCharger.h"
#ifdef NISSANLEAF_CHARGER
#define SELECTED_CHARGER_CLASS NissanLeafCharger
#endif
class NissanLeafCharger : public CanCharger {
public:
NissanLeafCharger() : CanCharger(ChargerType::NissanLeaf) {}
const char* name() { return "Nissan LEAF 2013-2024 PDM charger"; }
void map_can_frame_to_variable(CAN_frame rx_frame);
void transmit_can(unsigned long currentMillis);
float outputPowerDC() { return static_cast<float>(datalayer.charger.charger_stat_HVcur * 100); }
float HVDC_output_current() {
// P/U=I
if (datalayer.battery.status.voltage_dV > 0) {
return outputPowerDC() / (datalayer.battery.status.voltage_dV / 10);
}
return 0;
}
float HVDC_output_voltage() { return static_cast<float>(datalayer.battery.status.voltage_dV / 10); }
private:
/* CAN cycles and timers */
unsigned long previousMillis10ms = 0;
unsigned long previousMillis100ms = 0;
/* LEAF charger/battery parameters */
enum OBC_MODES : uint8_t {
IDLE_OR_QC = 1,
FINISHED = 2,
CHARGING_OR_INTERRUPTED = 4,
IDLE1 = 8,
IDLE2 = 9,
PLUGGED_IN_WAITING_ON_TIMER
};
enum OBC_VOLTAGES : uint8_t { NO_SIGNAL = 0, AC110 = 1, AC230 = 2, ABNORMAL_WAVE = 3 };
uint16_t OBC_Charge_Power = 0; // Actual charger output
uint8_t mprun100 = 0; // Counter 0-3
uint8_t mprun10 = 0; // Counter 0-3
uint8_t OBC_Charge_Status = IDLE_OR_QC;
uint8_t OBC_Status_AC_Voltage = 0; //1=110V, 2=230V
uint8_t OBCpowerSetpoint = 0;
uint8_t OBCpower = 0;
bool PPStatus = false;
bool OBCwakeup = false;
//Actual content messages
CAN_frame LEAF_1DB = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DB,
.data = {0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1DC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1DC,
.data = {0x6E, 0x0A, 0x05, 0xD5, 0x00, 0x00, 0x00, 0x00}};
CAN_frame LEAF_1F2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1F2,
.data = {0x30, 0x00, 0x20, 0xAC, 0x00, 0x3C, 0x00, 0x8F}};
CAN_frame LEAF_50B = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
CAN_frame LEAF_55B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x55B,
.data = {0xA4, 0x40, 0xAA, 0x00, 0xDF, 0xC0, 0x10, 0x00}};
CAN_frame LEAF_5BC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5BC,
.data = {0x3D, 0x80, 0xF0, 0x64, 0xB0, 0x01, 0x00, 0x32}};
CAN_frame LEAF_59E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x59E,
.data = {0x00, 0x00, 0x0C, 0x76, 0x18, 0x00, 0x00, 0x00}};
};
#endif

View file

@ -3,12 +3,12 @@
#include "src/devboard/sdcard/sdcard.h"
// Parameters
CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size
CAN_device_t CAN_cfg; // CAN Config
const uint8_t rx_queue_size = 10; // Receive Queue size
volatile bool send_ok_native = 0;
volatile bool send_ok_2515 = 0;
volatile bool send_ok_2518 = 0;
static unsigned long previousMillis10 = 0;
#ifdef CAN_ADDON
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
@ -105,25 +105,26 @@ void init_CAN() {
}
// Transmit functions
void transmit_can() {
void transmit_can(unsigned long currentMillis) {
if (!allowed_to_send_CAN) {
return; //Global block of CAN messages
}
#ifndef RS485_BATTERY_SELECTED
transmit_can_battery();
transmit_can_battery(currentMillis);
#endif
#ifdef CAN_INVERTER_SELECTED
transmit_can_inverter();
transmit_can_inverter(currentMillis);
#endif // CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED
transmit_can_charger();
#endif // CHARGER_SELECTED
if (charger) {
charger->transmit_can(currentMillis);
}
#ifdef CAN_SHUNT_SELECTED
transmit_can_shunt();
transmit_can_shunt(currentMillis);
#endif // CAN_SHUNT_SELECTED
}
@ -329,10 +330,8 @@ void map_can_frame_to_variable(CAN_frame* rx_frame, int interface) {
handle_incoming_can_frame_battery2(*rx_frame);
#endif
}
if (interface == can_config.charger) {
#ifdef CHARGER_SELECTED
map_can_frame_to_variable_charger(*rx_frame);
#endif
if (interface == can_config.charger && charger) {
charger->map_can_frame_to_variable(*rx_frame);
}
if (interface == can_config.shunt) {
#ifdef CAN_SHUNT_SELECTED

View file

@ -16,6 +16,7 @@
#endif //CANFD_ADDON
void dump_can_frame(CAN_frame& frame, frameDirection msgDir);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
/**
* @brief Initialization function for CAN.
@ -40,10 +41,11 @@ void transmit_can_frame();
* @brief Send CAN messages to all components
*
* @param[in] void
* @param[in] unsigned long currentMillis
*
* @return void
*/
void transmit_can();
void transmit_can(unsigned long currentMillis);
/**
* @brief Receive CAN messages from all interfaces

View file

@ -1,6 +1,8 @@
#include "obd.h"
#include "comm_can.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void show_dtc(uint8_t byte0, uint8_t byte1);
void show_dtc(uint8_t byte0, uint8_t byte1) {

View file

@ -94,7 +94,7 @@ void init_contactors() {
#ifdef BMS_2_POWER //Hardware supports 2x BMS
pinMode(BMS_2_POWER, OUTPUT);
digitalWrite(BMS_2_POWER, HIGH);
#endif BMS_2_POWER
#endif //BMS_2_POWER
#endif // HW with dedicated BMS pins
#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) // User has enabled BMS reset, turn on output on start
pinMode(BMS_POWER, OUTPUT);

View file

@ -30,10 +30,8 @@ void init_rs485() {
Serial2.begin(RS485_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // RS485_INVERTER_SELECTED || RS485_BATTERY_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
#ifdef BYD_MODBUS
// Init Static data to the RTU Modbus
handle_static_data_modbus_byd();
#endif // BYD_MODBUS
handle_static_data_modbus();
// Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);

View file

@ -1,7 +1,9 @@
#ifndef _DATALAYER_H_
#define _DATALAYER_H_
#include "../include.h"
#include "../../USER_SETTINGS.h"
#include "../devboard/utils/types.h"
#include "../system_settings.h"
typedef struct {
/** uint32_t */

View file

@ -1,7 +1,8 @@
#ifndef _DATALAYER_EXTENDED_H_
#define _DATALAYER_EXTENDED_H_
#include "../include.h"
#include <stdint.h>
#include "../../USER_SETTINGS.h"
typedef struct {
/** uint16_t */
@ -40,6 +41,9 @@ typedef struct {
} DATALAYER_INFO_BOLTAMPERA;
typedef struct {
/** User requesting contactor open or close via WebUI*/
bool UserRequestContactorClose = false;
bool UserRequestContactorOpen = false;
/** uint16_t */
/** Terminal 30 - 12V SME Supply Voltage */
uint16_t T30_Voltage = 0;
@ -163,6 +167,9 @@ typedef struct {
} DATALAYER_INFO_BMWI3;
typedef struct {
/** bool */
/** User requesting crash reset via WebUI*/
bool UserRequestCrashReset = false;
/** bool */
/** Which SOC method currently used. 0 = Estimated, 1 = Measured */
bool SOC_method = 0;
@ -184,6 +191,21 @@ typedef struct {
/** int16_t */
/** All the temperature sensors inside the battery pack*/
int16_t battery_temperatures[10];
/** unknown values polled */
uint32_t unknown0 = 0;
uint32_t unknown1 = 0;
uint16_t chargePower = 0;
uint16_t unknown3 = 0;
uint16_t unknown4 = 0;
uint16_t unknown5 = 0;
uint16_t unknown6 = 0;
uint16_t unknown7 = 0;
uint16_t unknown8 = 0;
uint16_t unknown9 = 0;
uint8_t unknown10 = 0;
uint8_t unknown11 = 0;
uint8_t unknown12 = 0;
uint8_t unknown13 = 0;
} DATALAYER_INFO_BYDATTO3;
typedef struct {
@ -728,6 +750,8 @@ typedef struct {
} DATALAYER_INFO_VOLVO_HYBRID;
typedef struct {
/** User requesting NVROL reset via WebUI*/
bool UserRequestNVROLReset = false;
/** uint16_t */
/** Values WIP*/
uint16_t battery_soc = 0;

View file

@ -52,7 +52,7 @@ GPIOs on extra header
#define BMS_POWER 23
// SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 19
#define INVERTER_CONTACTOR_ENABLE_PIN 2
// LED
#define LED_PIN 4

View file

@ -484,6 +484,7 @@ void mqtt_message_received(char* topic_raw, int topic_len, char* data, int data_
if (strcmp(topic, generateButtonTopic("STOP").c_str()) == 0) {
setBatteryPause(true, false, true);
}
free(topic);
}
static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data) {

View file

@ -19,11 +19,19 @@ battery_pause_status emulator_pause_status = NORMAL;
//battery pause status end
void update_machineryprotection() {
// Check if the CPU is too hot
/* Check if the ESP32 CPU running the Battery-Emulator is too hot.
We start with a warning, you can start to see Wifi issues if it becomes too hot
If the chip starts to approach the design limit, we perform a graceful shutdown */
if (datalayer.system.info.CPU_temperature > 80.0f) {
set_event(EVENT_CPU_OVERHEAT, 0);
set_event(EVENT_CPU_OVERHEATING, 0);
} else {
clear_event(EVENT_CPU_OVERHEAT);
clear_event(EVENT_CPU_OVERHEATING);
}
if (datalayer.system.info.CPU_temperature > 110.0f) {
set_event(EVENT_CPU_OVERHEATED, 0);
}
if (datalayer.system.info.CPU_temperature < 105.0f) {
clear_event(EVENT_CPU_OVERHEATED); //Hysteresis on the clearing
}
// Check health status of CAN interfaces
@ -79,6 +87,7 @@ void update_machineryprotection() {
// Battery voltage is over designed max voltage!
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
datalayer.battery.status.max_charge_power_W = 0;
} else {
clear_event(EVENT_BATTERY_OVERVOLTAGE);
}
@ -86,6 +95,7 @@ void update_machineryprotection() {
// Battery voltage is under designed min voltage!
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
datalayer.battery.status.max_discharge_power_W = 0;
} else {
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
}
@ -221,15 +231,15 @@ void update_machineryprotection() {
}
#endif //CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED
// Check if the charger is still sending CAN messages. If we go 60s without messages we raise a warning
if (!datalayer.charger.CAN_charger_still_alive) {
set_event(EVENT_CAN_CHARGER_MISSING, can_config.charger);
} else {
datalayer.charger.CAN_charger_still_alive--;
clear_event(EVENT_CAN_CHARGER_MISSING);
if (charger) {
// Check if the charger is still sending CAN messages. If we go 60s without messages we raise a warning
if (!datalayer.charger.CAN_charger_still_alive) {
set_event(EVENT_CAN_CHARGER_MISSING, can_config.charger);
} else {
datalayer.charger.CAN_charger_still_alive--;
clear_event(EVENT_CAN_CHARGER_MISSING);
}
}
#endif //CHARGER_SELECTED
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning

View file

@ -39,7 +39,7 @@ void init_events(void) {
events.entries[EVENT_CANMCP2515_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANFD_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_TASK_OVERRUN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CAN_CORRUPTED_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_BATTERY_MISSING].level = EVENT_LEVEL_ERROR;
@ -47,7 +47,8 @@ void init_events(void) {
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CPU_OVERHEAT].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CPU_OVERHEATING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CPU_OVERHEATED].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
@ -81,6 +82,7 @@ void init_events(void) {
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_INTERFACE_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_MODBUS_INVERTER_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_NO_ENABLE_DETECTED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_ERROR_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CELL_CRITICAL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_CRITICAL_OVER_VOLTAGE].level = EVENT_LEVEL_ERROR;
@ -176,8 +178,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "MCP2518FD message failed to send. Buffer full or no one on the bus to ACK the message!";
case EVENT_CAN_BUFFER_FULL:
return "MCP2515 message failed to send. Buffer full or no one on the bus to ACK the message!";
case EVENT_CAN_OVERRUN:
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
case EVENT_TASK_OVERRUN:
return "Task took too long to complete. CPU load might be too high. Info message, no action required.";
case EVENT_CAN_CORRUPTED_WARNING:
return "High amount of corrupted CAN messages detected. Check CAN wire shielding!";
case EVENT_CAN_NATIVE_TX_FAILURE:
@ -192,8 +194,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Inverter not sending messages via CAN for the last 60 seconds. Check wiring!";
case EVENT_CONTACTOR_WELDED:
return "Contactors sticking/welded. Inspect battery with caution!";
case EVENT_CPU_OVERHEAT:
case EVENT_CPU_OVERHEATING:
return "Battery-Emulator CPU overheating! Increase airflow/cooling to increase hardware lifespan!";
case EVENT_CPU_OVERHEATED:
return "Battery-Emulator CPU melting! Performing controlled shutdown until temperature drops!";
case EVENT_CHARGE_LIMIT_EXCEEDED:
return "Inverter is charging faster than battery is allowing.";
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
@ -269,6 +273,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
"Check other error code for reason!";
case EVENT_MODBUS_INVERTER_MISSING:
return "Modbus inverter has not sent any data. Inspect communication wiring!";
case EVENT_NO_ENABLE_DETECTED:
return "Inverter Enable line has not been active for a long time. Check Wiring!";
case EVENT_CELL_CRITICAL_UNDER_VOLTAGE:
return "CELL VOLTAGE CRITICALLY LOW! Not possible to continue. Inspect battery!";
case EVENT_CELL_UNDER_VOLTAGE:

View file

@ -12,7 +12,6 @@
XX(EVENT_CANMCP2515_INIT_FAILURE) \
XX(EVENT_CANFD_BUFFER_FULL) \
XX(EVENT_CAN_BUFFER_FULL) \
XX(EVENT_CAN_OVERRUN) \
XX(EVENT_CAN_CORRUPTED_WARNING) \
XX(EVENT_CAN_BATTERY_MISSING) \
XX(EVENT_CAN_BATTERY2_MISSING) \
@ -21,7 +20,8 @@
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
XX(EVENT_CONTACTOR_WELDED) \
XX(EVENT_CPU_OVERHEAT) \
XX(EVENT_CPU_OVERHEATING) \
XX(EVENT_CPU_OVERHEATED) \
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
XX(EVENT_WATER_INGRESS) \
XX(EVENT_12V_LOW) \
@ -55,6 +55,7 @@
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
XX(EVENT_INTERFACE_MISSING) \
XX(EVENT_MODBUS_INVERTER_MISSING) \
XX(EVENT_NO_ENABLE_DETECTED) \
XX(EVENT_ERROR_OPEN_CONTACTOR) \
XX(EVENT_CELL_CRITICAL_UNDER_VOLTAGE) \
XX(EVENT_CELL_CRITICAL_OVER_VOLTAGE) \
@ -73,6 +74,7 @@
XX(EVENT_SERIAL_RX_FAILURE) \
XX(EVENT_SERIAL_TX_FAILURE) \
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
XX(EVENT_TASK_OVERRUN) \
XX(EVENT_RESET_UNKNOWN) \
XX(EVENT_RESET_POWERON) \
XX(EVENT_RESET_EXT) \

View file

@ -37,12 +37,6 @@ enum PrechargeState {
#define INTERVAL_60_S 60000
#define INTERVAL_10_MS_DELAYED 15
#define INTERVAL_20_MS_DELAYED 30
#define INTERVAL_30_MS_DELAYED 40
#define INTERVAL_50_MS_DELAYED 65
#define INTERVAL_100_MS_DELAYED 120
#define INTERVAL_200_MS_DELAYED 240
#define INTERVAL_500_MS_DELAYED 550
#define CAN_STILL_ALIVE 60
// Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event

View file

@ -58,6 +58,8 @@ String advanced_battery_processor(const String& var) {
#endif //BOLT_AMPERA_BATTERY
#ifdef BMW_IX_BATTERY
content += "<button onclick='askContactorClose()'>Close Contactors</button>";
content += "<button onclick='askContactorOpen()'>Open Contactors</button>";
content +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
" dV</h4>";
@ -531,6 +533,7 @@ String advanced_battery_processor(const String& var) {
#ifdef BYD_ATTO_3_BATTERY
static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"};
content += "<button onclick='askResetCrash()'>Unlock crashed BMS</button>";
content += "<h4>SOC method used: " + String(SOCmethod[datalayer_extended.bydAtto3.SOC_method]) + "</h4>";
content += "<h4>SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "</h4>";
content += "<h4>SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "</h4>";
@ -547,6 +550,20 @@ String advanced_battery_processor(const String& var) {
content += "<h4>Temperature sensor 8: " + String(datalayer_extended.bydAtto3.battery_temperatures[7]) + "</h4>";
content += "<h4>Temperature sensor 9: " + String(datalayer_extended.bydAtto3.battery_temperatures[8]) + "</h4>";
content += "<h4>Temperature sensor 10: " + String(datalayer_extended.bydAtto3.battery_temperatures[9]) + "</h4>";
content += "<h4>Unknown0: " + String(datalayer_extended.bydAtto3.unknown0) + "</h4>";
content += "<h4>Unknown1: " + String(datalayer_extended.bydAtto3.unknown1) + "</h4>";
content += "<h4>Charge power raw: " + String(datalayer_extended.bydAtto3.chargePower) + "</h4>";
content += "<h4>Unknown3: " + String(datalayer_extended.bydAtto3.unknown3) + "</h4>";
content += "<h4>Unknown4: " + String(datalayer_extended.bydAtto3.unknown4) + "</h4>";
content += "<h4>Unknown5: " + String(datalayer_extended.bydAtto3.unknown5) + "</h4>";
content += "<h4>Unknown6: " + String(datalayer_extended.bydAtto3.unknown6) + "</h4>";
content += "<h4>Unknown7: " + String(datalayer_extended.bydAtto3.unknown7) + "</h4>";
content += "<h4>Unknown8: " + String(datalayer_extended.bydAtto3.unknown8) + "</h4>";
content += "<h4>Unknown9: " + String(datalayer_extended.bydAtto3.unknown9) + "</h4>";
content += "<h4>Unknown10: " + String(datalayer_extended.bydAtto3.unknown10) + "</h4>";
content += "<h4>Unknown11: " + String(datalayer_extended.bydAtto3.unknown11) + "</h4>";
content += "<h4>Unknown12: " + String(datalayer_extended.bydAtto3.unknown12) + "</h4>";
content += "<h4>Unknown13: " + String(datalayer_extended.bydAtto3.unknown12) + "</h4>";
#endif //BYD_ATTO_3_BATTERY
#ifdef TESLA_BATTERY
@ -1278,6 +1295,7 @@ String advanced_battery_processor(const String& var) {
#endif //MEB_BATTERY
#ifdef RENAULT_ZOE_GEN2_BATTERY
content += "<button onclick='askTriggerNVROL()'>Perform NVROL reset</button>";
content += "<h4>soc: " + String(datalayer_extended.zoePH2.battery_soc) + "</h4>";
content += "<h4>usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "</h4>";
content += "<h4>soh: " + String(datalayer_extended.zoePH2.battery_soh) + "</h4>";
@ -1521,6 +1539,7 @@ String advanced_battery_processor(const String& var) {
#endif
content += "</div>";
content += "<script>";
content +=
"function askTeslaClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation "
@ -1533,6 +1552,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askTeslaResetBMS() { if (window.confirm('Are you sure you want to reset the "
@ -1545,6 +1565,58 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askResetCrash() { if (window.confirm('Are you sure you want to reset crash data? "
"Note this will unlock your BMS and enable contactor closing and SOC calculation.')) { "
"resetCrash(); } }";
content += "function resetCrash() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/resetCrash', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askTriggerNVROL() { if (window.confirm('Are you sure you want to trigger "
"an NVROL reset? Battery will be unavailable for 30 seconds while this is active!')) { "
"TriggerNVROL(); } }";
content += "function TriggerNVROL() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/triggerNVROL', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askContactorClose() { if (window.confirm('Are you sure you want to tirgger "
"a contactor close request?')) { "
"bmwIxCloseContactorRequest(); } }";
content += "function bmwIxCloseContactorRequest() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/bmwIxCloseContactorRequest', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askContactorOpen() { if (window.confirm('Are you sure you want to tirgger "
"a contactor open request?')) { "
"bmwIxOpenContactorRequest(); } }";
content += "function bmwIxOpenContactorRequest() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/bmwIxOpenContactorRequest', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
@ -1557,6 +1629,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function Volvo_askEraseDTC() { if (window.confirm('Are you sure you want to erase DTCs?')) { "
@ -1590,6 +1663,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
// Additial functions added
content += "<script>";
content += "function exportLog() { window.location.href = '/export_log'; }";

View file

@ -1,5 +1,6 @@
#include "settings_html.h"
#include <Arduino.h>
#include "../../charger/CHARGERS.h"
#include "../../datalayer/datalayer.h"
String settings_processor(const String& var) {
@ -143,37 +144,36 @@ String settings_processor(const String& var) {
content += "</div>";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
if (charger) {
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
content += "<h4 style='color: white;'>Charger HVDC Enabled: ";
if (datalayer.charger.charger_HV_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerHVDCEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger HVDC Enabled: ";
if (datalayer.charger.charger_HV_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += "<h4 style='color: white;'>Charger Aux12VDC Enabled: ";
if (datalayer.charger.charger_aux12V_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerAux12vEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Voltage Setpoint: " +
String(datalayer.charger.charger_setpoint_HV_VDC, 1) +
" V </span> <button onclick='editChargerSetpointVDC()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Current Setpoint: " +
String(datalayer.charger.charger_setpoint_HV_IDC, 1) +
" A </span> <button onclick='editChargerSetpointIDC()'>Edit</button></h4>";
// Close the block
content += "</div>";
}
content += " <button onclick='editChargerHVDCEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Aux12VDC Enabled: ";
if (datalayer.charger.charger_aux12V_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerAux12vEnabled()'>Edit</button></h4>";
content +=
"<h4 style='color: white;'>Charger Voltage Setpoint: " + String(datalayer.charger.charger_setpoint_HV_VDC, 1) +
" V </span> <button onclick='editChargerSetpointVDC()'>Edit</button></h4>";
content +=
"<h4 style='color: white;'>Charger Current Setpoint: " + String(datalayer.charger.charger_setpoint_HV_IDC, 1) +
" A </span> <button onclick='editChargerSetpointIDC()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
content += "<script>"; // Note, this section is minified to improve performance
content += "function editComplete(){if(this.status==200){window.location.reload();}}";
@ -305,40 +305,44 @@ String settings_processor(const String& var) {
"between 0 and 1000');}}}";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
content +=
"function editChargerHVDCEnabled(){var value=prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 "
"for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargerHvEnabled?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}";
content +=
"function editChargerAux12vEnabled(){var value=prompt('Enable or disable low voltage 12v auxiliary DC output. "
"Enter 1 for enabled, 0 for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargerAux12vEnabled?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter 1 or "
"0');}}}";
content +=
"function editChargerSetpointVDC(){var value=prompt('Set charging voltage. Input will be validated against "
"inverter and/or charger configuration parameters, but use sensible values like 200 to "
"420.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargeSetpointV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
"0 and 1000');}}}";
content +=
"function editChargerSetpointIDC(){var value=prompt('Set charging amperage. Input will be validated against "
"inverter and/or charger configuration parameters, but use sensible values like 6 to "
"48.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargeSetpointA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
"0 and 100');}}}";
content +=
"function editChargerSetpointEndI(){var value=prompt('Set amperage that terminates charge as being "
"sufficiently complete. Input will be validated against inverter and/or charger configuration parameters, but "
"use sensible values like 1-5.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargeEndA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
"and 100');}}}";
#endif
if (charger) {
content +=
"function editChargerHVDCEnabled(){var value=prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 "
"for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargerHvEnabled?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}";
content +=
"function editChargerAux12vEnabled(){var value=prompt('Enable or disable low voltage 12v auxiliary DC "
"output. "
"Enter 1 for enabled, 0 for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargerAux12vEnabled?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter 1 or "
"0');}}}";
content +=
"function editChargerSetpointVDC(){var value=prompt('Set charging voltage. Input will be validated against "
"inverter and/or charger configuration parameters, but use sensible values like 200 to "
"420.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargeSetpointV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between "
"0 and 1000');}}}";
content +=
"function editChargerSetpointIDC(){var value=prompt('Set charging amperage. Input will be validated against "
"inverter and/or charger configuration parameters, but use sensible values like 6 to "
"48.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargeSetpointA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between "
"0 and 100');}}}";
content +=
"function editChargerSetpointEndI(){var value=prompt('Set amperage that terminates charge as being "
"sufficiently complete. Input will be validated against inverter and/or charger configuration parameters, "
"but "
"use sensible values like 1-5.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateChargeEndA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
"and 100');}}}";
}
content += "</script>";
content += "<script>";

View file

@ -11,6 +11,8 @@
#include "../utils/timer.h"
#include "esp_task_wdt.h"
void transmit_can_frame(CAN_frame* tx_frame, int interface);
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
@ -588,6 +590,33 @@ void init_webserver() {
request->send(200, "text/plain", "Updated successfully");
});
// Route for triggering NVROL reset on Zoe Gen2 batteries
server.on("/triggerNVROL", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.zoePH2.UserRequestNVROLReset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for closing BMW iX Contactors
server.on("/bmwIxCloseContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bmwix.UserRequestContactorClose = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for opening BMW iX Contactors
server.on("/bmwIxOpenContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bmwix.UserRequestContactorOpen = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting SOH on Nissan LEAF batteries
server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
@ -597,6 +626,15 @@ void init_webserver() {
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting Crash data on BYD Atto3 batteries
server.on("/resetCrash", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bydAtto3.UserRequestCrashReset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for erasing DTC on Volvo/Polestar batteries
server.on("/volvoEraseDTC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
@ -732,94 +770,94 @@ void init_webserver() {
});
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Route for editing ChargerTargetV
server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
if (charger) {
// Route for editing ChargerTargetV
server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
float val = value.toFloat();
if (!(val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV)) {
request->send(400, "text/plain", "Bad Request");
}
if (!(val * datalayer.charger.charger_setpoint_HV_IDC <= CHARGER_MAX_POWER)) {
request->send(400, "text/plain", "Bad Request");
}
datalayer.charger.charger_setpoint_HV_VDC = val;
request->send(200, "text/plain", "Updated successfully");
});
// Route for editing ChargerTargetA
server.on("/updateChargeSetpointA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
float val = value.toFloat();
if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) {
request->send(400, "text/plain", "Bad Request");
}
if (!(val * datalayer.charger.charger_setpoint_HV_VDC <= CHARGER_MAX_POWER)) {
request->send(400, "text/plain", "Bad Request");
}
datalayer.charger.charger_setpoint_HV_IDC = value.toFloat();
request->send(200, "text/plain", "Updated successfully");
});
// Route for editing ChargerEndA
server.on("/updateChargeEndA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_setpoint_HV_IDC_END = value.toFloat();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
float val = value.toFloat();
// Route for enabling/disabling HV charger
server.on("/updateChargerHvEnabled", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_HV_enabled = (bool)value.toInt();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
if (!(val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV)) {
request->send(400, "text/plain", "Bad Request");
}
if (!(val * datalayer.charger.charger_setpoint_HV_IDC <= CHARGER_MAX_POWER)) {
request->send(400, "text/plain", "Bad Request");
}
datalayer.charger.charger_setpoint_HV_VDC = val;
// Route for enabling/disabling aux12v charger
server.on("/updateChargerAux12vEnabled", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_aux12V_enabled = (bool)value.toInt();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
});
// Route for editing ChargerTargetA
server.on("/updateChargeSetpointA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
float val = value.toFloat();
if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) {
request->send(400, "text/plain", "Bad Request");
}
if (!(val * datalayer.charger.charger_setpoint_HV_VDC <= CHARGER_MAX_POWER)) {
request->send(400, "text/plain", "Bad Request");
}
datalayer.charger.charger_setpoint_HV_IDC = value.toFloat();
request->send(200, "text/plain", "Updated successfully");
});
// Route for editing ChargerEndA
server.on("/updateChargeEndA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_setpoint_HV_IDC_END = value.toFloat();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for enabling/disabling HV charger
server.on("/updateChargerHvEnabled", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_HV_enabled = (bool)value.toInt();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for enabling/disabling aux12v charger
server.on("/updateChargerAux12vEnabled", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_aux12V_enabled = (bool)value.toInt();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
}
// Send a GET request to <ESP_IP>/update
server.on("/debug", HTTP_GET, [](AsyncWebServerRequest* request) {
@ -998,16 +1036,11 @@ String processor(const String& var) {
content += "</h4>";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
content += "<h4 style='color: white;'>Charger protocol: ";
#ifdef CHEVYVOLT_CHARGER
content += "Chevy Volt Gen1 Charger";
#endif // CHEVYVOLT_CHARGER
#ifdef NISSANLEAF_CHARGER
content += "Nissan LEAF 2013-2024 PDM charger";
#endif // NISSANLEAF_CHARGER
content += "</h4>";
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
if (charger) {
content += "<h4 style='color: white;'>Charger protocol: ";
content += charger->name();
content += "</h4>";
}
// Close the block
content += "</div>";
@ -1192,15 +1225,14 @@ String processor(const String& var) {
}
}
content += "<h4>Automatic contactor closing allowed:</h4>";
content += "<h4>Battery: ";
content += "<h4>Battery allows contactor closing: ";
if (datalayer.system.status.battery_allows_contactor_closing == true) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Inverter: ";
content += " Inverter allows contactor closing: ";
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
content += "<span>&#10003;</span></h4>";
} else {
@ -1408,61 +1440,52 @@ String processor(const String& var) {
content += "</div>";
#endif // DOUBLE_BATTERY
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
if (charger) {
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
content += "<h4>Charger HV Enabled: ";
if (datalayer.charger.charger_HV_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += "<h4>Charger HV Enabled: ";
if (datalayer.charger.charger_HV_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += "</h4>";
content += "<h4>Charger Aux12v Enabled: ";
if (datalayer.charger.charger_aux12V_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += "</h4>";
auto chgPwrDC = charger->outputPowerDC();
auto chgEff = charger->efficiency();
content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1);
if (charger->efficiencySupported()) {
content += "<h4 style='color: white;'>Charger Efficiency: " + String(chgEff) + "%</h4>";
}
float HVvol = charger->HVDC_output_voltage();
float HVcur = charger->HVDC_output_current();
float LVvol = charger->LVDC_output_voltage();
float LVcur = charger->LVDC_output_current();
content += "<h4 style='color: white;'>Charger HVDC Output V: " + String(HVvol, 2) + " V</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output I: " + String(HVcur, 2) + " A</h4>";
content += "<h4 style='color: white;'>Charger LVDC Output I: " + String(LVcur, 2) + "</h4>";
content += "<h4 style='color: white;'>Charger LVDC Output V: " + String(LVvol, 2) + "</h4>";
float ACcur = charger->AC_input_current();
float ACvol = charger->AC_input_voltage();
content += "<h4 style='color: white;'>Charger AC Input V: " + String(ACvol, 2) + " VAC</h4>";
content += "<h4 style='color: white;'>Charger AC Input I: " + String(ACcur, 2) + " A</h4>";
content += "</div>";
}
content += "</h4>";
content += "<h4>Charger Aux12v Enabled: ";
if (datalayer.charger.charger_aux12V_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += "</h4>";
#ifdef CHEVYVOLT_CHARGER
float chgPwrDC = static_cast<float>(datalayer.charger.charger_stat_HVcur * datalayer.charger.charger_stat_HVvol);
float chgPwrAC = static_cast<float>(datalayer.charger.charger_stat_ACcur * datalayer.charger.charger_stat_ACvol);
float chgEff = chgPwrDC / chgPwrAC * 100;
float ACcur = datalayer.charger.charger_stat_ACcur;
float ACvol = datalayer.charger.charger_stat_ACvol;
float HVvol = datalayer.charger.charger_stat_HVvol;
float HVcur = datalayer.charger.charger_stat_HVcur;
float LVvol = datalayer.charger.charger_stat_LVvol;
float LVcur = datalayer.charger.charger_stat_LVcur;
content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1);
content += "<h4 style='color: white;'>Charger Efficiency: " + String(chgEff) + "%</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output V: " + String(HVvol, 2) + " V</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output I: " + String(HVcur, 2) + " A</h4>";
content += "<h4 style='color: white;'>Charger LVDC Output I: " + String(LVcur, 2) + "</h4>";
content += "<h4 style='color: white;'>Charger LVDC Output V: " + String(LVvol, 2) + "</h4>";
content += "<h4 style='color: white;'>Charger AC Input V: " + String(ACvol, 2) + " VAC</h4>";
content += "<h4 style='color: white;'>Charger AC Input I: " + String(ACcur, 2) + " A</h4>";
#endif // CHEVYVOLT_CHARGER
#ifdef NISSANLEAF_CHARGER
float chgPwrDC = static_cast<float>(datalayer.charger.charger_stat_HVcur * 100);
datalayer.charger.charger_stat_HVcur = chgPwrDC / (datalayer.battery.status.voltage_dV / 10); // P/U=I
datalayer.charger.charger_stat_HVvol = static_cast<float>(datalayer.battery.status.voltage_dV / 10);
float ACvol = datalayer.charger.charger_stat_ACvol;
float HVvol = datalayer.charger.charger_stat_HVvol;
float HVcur = datalayer.charger.charger_stat_HVcur;
content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1);
content += "<h4 style='color: white;'>Charger HVDC Output V: " + String(HVvol, 2) + " V</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output I: " + String(HVcur, 2) + " A</h4>";
content += "<h4 style='color: white;'>Charger AC Input V: " + String(ACvol, 2) + " VAC</h4>";
#endif // NISSANLEAF_CHARGER
// Close the block
content += "</div>";
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
if (emulator_pause_request_ON)
content += "<button onclick='PauseBattery(false)'>Resume charge/discharge</button> ";

View file

@ -1,5 +1,6 @@
#include "../include.h"
#ifdef AFORE_CAN
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "AFORE-CAN.h"
@ -7,73 +8,9 @@
#define SOCMIN 1
/* Do not change code below unless you are sure what you are doing */
/* The code is following the Afore 2.3 CAN standard, little-endian, 500kbps, from 2023.08.07 */
static uint8_t inverter_status =
0; //0 = init, 1 = standby, 2 = starting, 3 = grid connected, 4 off-grid, 5 diesel generator, 6 grid connected, but disconnected, 7off grid and disconnected, 8 = power failure processing, 9 = power off, 10 = Failure
static bool time_to_send_info = false;
static uint8_t char0 = 0;
static uint8_t char1 = 0;
static uint8_t char2 = 0;
static uint8_t char3 = 0;
static uint8_t char4 = 0;
//Actual content messages
CAN_frame AFORE_350 = {.FD = false, // Operation information
.ext_ID = false,
.DLC = 8,
.ID = 0x350,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_351 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x351,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_352 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x352,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_353 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x353,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_354 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x354,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_355 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x355,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_356 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x356,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_357 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x357,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_359 = {.FD = false, // Serial number 0-7
.ext_ID = false,
.DLC = 8,
.ID = 0x359,
.data = {0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x2D}}; // Battery-
CAN_frame AFORE_35A = {.FD = false, // Serial number 8-15
.ext_ID = false,
.DLC = 8,
.ID = 0x35A,
.data = {0x65, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72}}; // Emulator
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void AforeCanInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point
/*0x350 Operation Information*/
@ -200,7 +137,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
*/
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void AforeCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x305: // Every 1s from inverter
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -217,7 +154,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
void AforeCanInverter::transmit_can(unsigned long currentMillis) {
if (time_to_send_info) { // Set every 1s if we get message from inverter
transmit_can_frame(&AFORE_350, can_config.inverter);
transmit_can_frame(&AFORE_351, can_config.inverter);
@ -233,7 +170,8 @@ void transmit_can_inverter() {
time_to_send_info = false;
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void AforeCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -3,8 +3,81 @@
#include "../include.h"
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS AforeCanInverter
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
class AforeCanInverter : public CanInverterProtocol {
public:
void setup();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void update_values();
private:
/* The code is following the Afore 2.3 CAN standard, little-endian, 500kbps, from 2023.08.07 */
uint8_t inverter_status =
0; //0 = init, 1 = standby, 2 = starting, 3 = grid connected, 4 off-grid, 5 diesel generator, 6 grid connected, but disconnected, 7off grid and disconnected, 8 = power failure processing, 9 = power off, 10 = Failure
bool time_to_send_info = false;
uint8_t char0 = 0;
uint8_t char1 = 0;
uint8_t char2 = 0;
uint8_t char3 = 0;
uint8_t char4 = 0;
//Actual content messages
CAN_frame AFORE_350 = {.FD = false, // Operation information
.ext_ID = false,
.DLC = 8,
.ID = 0x350,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_351 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x351,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_352 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x352,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_353 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x353,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_354 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x354,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_355 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x355,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_356 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x356,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_357 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x357,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_358 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x358,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame AFORE_359 = {.FD = false, // Serial number 0-7
.ext_ID = false,
.DLC = 8,
.ID = 0x359,
.data = {0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x2D}}; // Battery-
CAN_frame AFORE_35A = {.FD = false, // Serial number 8-15
.ext_ID = false,
.DLC = 8,
.ID = 0x35A,
.data = {0x65, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72}}; // Emulator
};
#endif

View file

@ -1,91 +1,13 @@
#include "../include.h"
#ifdef BYD_CAN
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "BYD-CAN.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
#define VOLTAGE_OFFSET_DV 20
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x250,
.data = {FW_MAJOR_VERSION, FW_MINOR_VERSION, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX / 100) >> 8),
(uint8_t)(BATTERY_WH_MAX / 100), 0x02,
0x09}}; //0-1 FW version , Capacity kWh byte4&5 (example 24kWh = 240)
CAN_frame BYD_290 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x290,
.data = {0x06, 0x37, 0x10, 0xD9, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BYD_2D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2D0,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //BYD
CAN_frame BYD_3D0_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //Battery
CAN_frame BYD_3D0_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
CAN_frame BYD_3D0_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
CAN_frame BYD_3D0_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
//Actual content messages
CAN_frame BYD_110 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x110,
.data = {0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BYD_150 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x150,
.data = {0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00}};
CAN_frame BYD_190 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x190,
.data = {0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BYD_1D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D0,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x08}};
CAN_frame BYD_210 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int16_t temperature_average = 0;
static uint16_t inverter_voltage = 0;
static uint16_t inverter_SOC = 0;
static int16_t inverter_current = 0;
static int16_t inverter_temperature = 0;
static uint16_t remaining_capacity_ah = 0;
static uint16_t fully_charged_capacity_ah = 0;
static long inverter_timestamp = 0;
static bool initialDataSent = false;
static bool inverterStartedUp = false;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void BydCanInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
/* Calculate temperature */
temperature_average =
@ -167,13 +89,13 @@ void update_values_can_inverter() { //This function maps all the values fetched
BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void BydCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier!
inverterStartedUp = true;
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification
send_intial_data();
send_initial_data();
} else { // We can identify what inverter type we are connected to
for (uint8_t i = 0; i < 7; i++) {
datalayer.system.info.inverter_brand[i] = rx_frame.data.u8[i + 1];
@ -204,8 +126,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void BydCanInverter::transmit_can(unsigned long currentMillis) {
if (!inverterStartedUp) {
//Avoid sending messages towards inverter, unless it has woken up and sent something to us first
@ -214,7 +135,7 @@ void transmit_can_inverter() {
// Send initial CAN data once on bootup
if (!initialDataSent) {
send_intial_data();
send_initial_data();
initialDataSent = true;
}
@ -240,7 +161,7 @@ void transmit_can_inverter() {
}
}
void send_intial_data() {
void BydCanInverter::send_initial_data() {
transmit_can_frame(&BYD_250, can_config.inverter);
transmit_can_frame(&BYD_290, can_config.inverter);
transmit_can_frame(&BYD_2D0, can_config.inverter);
@ -249,7 +170,8 @@ void send_intial_data() {
transmit_can_frame(&BYD_3D0_2, can_config.inverter);
transmit_can_frame(&BYD_3D0_3, can_config.inverter);
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void BydCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -3,11 +3,100 @@
#include "../include.h"
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS BydCanInverter
#define FW_MAJOR_VERSION 0x03
#define FW_MINOR_VERSION 0x29
void send_intial_data();
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
class BydCanInverter : public CanInverterProtocol {
public:
void setup();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
void update_values();
private:
void send_initial_data();
unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
#define VOLTAGE_OFFSET_DV 20
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x250,
.data = {FW_MAJOR_VERSION, FW_MINOR_VERSION, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX / 100) >> 8),
(uint8_t)(BATTERY_WH_MAX / 100), 0x02,
0x09}}; //0-1 FW version , Capacity kWh byte4&5 (example 24kWh = 240)
CAN_frame BYD_290 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x290,
.data = {0x06, 0x37, 0x10, 0xD9, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BYD_2D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2D0,
.data = {0x00, 0x42, 0x59, 0x44, 0x00, 0x00, 0x00, 0x00}}; //BYD
CAN_frame BYD_3D0_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79}}; //Battery
CAN_frame BYD_3D0_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x01, 0x2D, 0x42, 0x6F, 0x78, 0x20, 0x50, 0x72}}; //-Box Pr
CAN_frame BYD_3D0_2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x02, 0x65, 0x6D, 0x69, 0x75, 0x6D, 0x20, 0x48}}; //emium H
CAN_frame BYD_3D0_3 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3D0,
.data = {0x03, 0x56, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}}; //VS
//Actual content messages
CAN_frame BYD_110 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x110,
.data = {0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BYD_150 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x150,
.data = {0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00}};
CAN_frame BYD_190 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x190,
.data = {0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BYD_1D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D0,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x08}};
CAN_frame BYD_210 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
int16_t temperature_average = 0;
uint16_t inverter_voltage = 0;
uint16_t inverter_SOC = 0;
int16_t inverter_current = 0;
int16_t inverter_temperature = 0;
uint16_t remaining_capacity_ah = 0;
uint16_t fully_charged_capacity_ah = 0;
long inverter_timestamp = 0;
bool initialDataSent = false;
bool inverterStartedUp = false;
};
#endif

View file

@ -17,21 +17,28 @@ static uint8_t history_index = 0;
static uint8_t bms_char_dis_status = STANDBY;
static bool all_401_values_equal = false;
void update_modbus_registers_inverter() {
void verify_inverter_modbus();
void verify_temperature_modbus();
void handle_update_data_modbusp201_byd();
void handle_update_data_modbusp301_byd();
void handle_static_data_modbus_byd();
void BydModbusInverter::update_modbus_registers() {
verify_temperature_modbus();
verify_inverter_modbus();
handle_update_data_modbusp201_byd();
handle_update_data_modbusp301_byd();
}
void handle_static_data_modbus_byd() {
void BydModbusInverter::handle_static_data() {
// Store the data into the array
static uint16_t si_data[] = {21321, 1};
static uint16_t byd_data[] = {16985, 17408, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static uint16_t battery_data[] = {16985, 17440, 16993, 29812, 25970, 31021, 17007, 30752,
20594, 25965, 26997, 27936, 18518, 0, 0, 0};
static uint16_t volt_data[] = {13614, 12288, 0, 0, 0, 0, 0, 0, 13102, 12598, 0, 0, 0, 0, 0, 0};
static uint16_t serial_data[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static uint16_t volt_data[] = {13614, 12288, 0, 0, 0, 0, 0, 0, 13102, 12854, 0, 0, 0, 0, 0, 0};
static uint16_t serial_data[] = {20528, 13104, 21552, 12848, 23089, 14641, 12593, 14384,
12336, 12337, 0, 0, 0, 0, 0, 0};
static uint16_t static_data[] = {1, 0};
static uint16_t* data_array_pointers[] = {si_data, byd_data, battery_data, volt_data, serial_data, static_data};
static uint16_t data_sizes[] = {sizeof(si_data), sizeof(byd_data), sizeof(battery_data),
@ -143,8 +150,10 @@ void verify_inverter_modbus() {
history_index = (history_index + 1) % HISTORY_LENGTH;
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void BydModbusInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD 11kWh HVM battery over Modbus RTU", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -3,16 +3,18 @@
#include "../include.h"
#define MODBUS_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS BydModbusInverter
#define MB_RTU_NUM_VALUES 13100
#define MAX_POWER 40960 //BYD Modbus specific value
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
void handle_static_data_modbus_byd();
void verify_temperature_modbus();
void verify_inverter_modbus();
void handle_update_data_modbusp201_byd();
void handle_update_data_modbusp301_byd();
void setup_inverter(void);
class BydModbusInverter : public ModbusInverterProtocol {
public:
void setup();
void update_modbus_registers();
void handle_static_data();
};
#endif

View file

@ -1,143 +1,11 @@
#include "../include.h"
#ifdef FERROAMP_CAN
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "FERROAMP-CAN.h"
//#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
//useful for some inverters like Sofar that report the voltages incorrect otherwise
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
Change the following only if your inverter is generating fault codes about voltage range */
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
#define MODULES_IN_SERIES 4
#define CELLS_PER_MODULE 30
#define VOLTAGE_LEVEL 384
#define AH_CAPACITY 37
/* Do not change code below unless you are sure what you are doing */
//Actual content messages
CAN_frame PYLON_7310 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7310,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7311 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7311,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7320 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7320,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_7321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7321,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_4210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4210,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4220,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4230,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4240,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4250,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4260,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4270,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4211 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4211,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4221 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4221,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4231 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4231,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4241 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4241,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4251 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4251,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4261 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4261,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4271 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4271,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4281 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4281,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4291 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t cell_tweaked_max_voltage_mV = 3300;
static uint16_t cell_tweaked_min_voltage_mV = 3300;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void FerroampCanInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages
@ -437,7 +305,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void FerroampCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -453,11 +321,11 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
void FerroampCanInverter::transmit_can(unsigned long currentMillis) {
// No periodic sending, we only react on received can messages
}
void send_setup_info() { //Ensemble information
void FerroampCanInverter::send_setup_info() { //Ensemble information
#ifdef SEND_0
transmit_can_frame(&PYLON_7310, can_config.inverter);
transmit_can_frame(&PYLON_7320, can_config.inverter);
@ -468,7 +336,7 @@ void send_setup_info() { //Ensemble information
#endif
}
void send_system_data() { //System equipment information
void FerroampCanInverter::send_system_data() { //System equipment information
#ifdef SEND_0
transmit_can_frame(&PYLON_4210, can_config.inverter);
transmit_can_frame(&PYLON_4220, can_config.inverter);
@ -492,7 +360,8 @@ void send_system_data() { //System equipment information
transmit_can_frame(&PYLON_4291, can_config.inverter);
#endif
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void FerroampCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Ferroamp Pylon battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -3,10 +3,154 @@
#include "../include.h"
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS FerroampCanInverter
void send_system_data();
void send_setup_info();
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
class FerroampCanInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
void send_system_data();
void send_setup_info();
//#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
//useful for some inverters like Sofar that report the voltages incorrect otherwise
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
Change the following only if your inverter is generating fault codes about voltage range */
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
#define MODULES_IN_SERIES 4
#define CELLS_PER_MODULE 30
#define VOLTAGE_LEVEL 384
#define AH_CAPACITY 37
//Actual content messages
CAN_frame PYLON_7310 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7310,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7311 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7311,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7320 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7320,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_7321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7321,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_4210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4210,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4220,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4230,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4240,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4250,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4260,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4270,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4211 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4211,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4221 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4221,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4231 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4231,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4241 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4241,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4251 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4251,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4261 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4261,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4271 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4271,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4281 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4281,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4291 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint16_t cell_tweaked_max_voltage_mV = 3300;
uint16_t cell_tweaked_min_voltage_mV = 3300;
};
#endif

View file

@ -595,10 +595,9 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
// So do we really need to map the same two values over and over to 32 places?
}
void transmit_can_inverter() { // This function loops as fast as possible
void transmit_can_inverter(unsigned long currentMillis) {
if (send_bms_info) {
currentMillis = millis(); // Get the current time
// Check if enough time has passed since the last batch
if (currentMillis - previousMillisBMSinfo >= delay_between_batches_ms) {
@ -634,7 +633,6 @@ void transmit_can_inverter() { // This function loops as fast as possible
}
if (send_individual_pack_status) {
currentMillis = millis(); // Get the current time
// Check if enough time has passed since the last batch
if (currentMillis - previousMillisIndividualPacks >= delay_between_batches_ms) {
@ -670,7 +668,6 @@ void transmit_can_inverter() { // This function loops as fast as possible
}
if (send_serial_numbers) {
currentMillis = millis(); // Get the current time
// Check if enough time has passed since the last batch
if (currentMillis - previousMillisSerialNumber >= delay_between_batches_ms) {
@ -781,7 +778,6 @@ void transmit_can_inverter() { // This function loops as fast as possible
}
if (send_cellvoltages) {
currentMillis = millis(); // Get the current time
// Check if enough time has passed since the last batch
if (currentMillis - previousMillisCellvoltage >= delay_between_batches_ms) {

View file

@ -523,14 +523,12 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
void transmit_can_inverter(unsigned long currentMillis) {
if (!inverter_alive) {
return; //Dont send messages towards inverter until it has started
}
unsigned long currentMillis = millis();
//Check if 1 second has passed, then we start sending!
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;

View file

@ -267,7 +267,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
void transmit_can_inverter(unsigned long currentMillis) {
// No periodic sending for this battery type. Data is sent when inverter requests it
}

View file

@ -0,0 +1,56 @@
#include "../include.h"
// These functions adapt the old C-style global functions inverter-API to the
// object-oriented inverter protocol API.
#ifdef SELECTED_INVERTER_CLASS
InverterProtocol* inverter;
#ifdef CAN_INVERTER_SELECTED
CanInverterProtocol* can_inverter;
#endif
#ifdef MODBUS_INVERTER_SELECTED
ModbusInverterProtocol* modbus_inverter;
#endif
void setup_inverter() {
#ifdef MODBUS_INVERTER_SELECTED
modbus_inverter = new SELECTED_INVERTER_CLASS();
inverter = modbus_inverter;
#endif
#ifdef CAN_INVERTER_SELECTED
can_inverter = new SELECTED_INVERTER_CLASS();
inverter = can_inverter;
#endif
inverter->setup();
}
#ifdef MODBUS_INVERTER_SELECTED
void update_modbus_registers_inverter() {
modbus_inverter->update_modbus_registers();
}
void handle_static_data_modbus() {
modbus_inverter->handle_static_data();
}
#endif
#ifdef CAN_INVERTER_SELECTED
void update_values_can_inverter() {
can_inverter->update_values();
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
can_inverter->map_can_frame_to_variable(rx_frame);
}
void transmit_can_inverter(unsigned long currentMillis) {
can_inverter->transmit_can(currentMillis);
}
#endif
#endif

View file

@ -1,6 +1,29 @@
#ifndef INVERTERS_H
#define INVERTERS_H
// The abstract base class for all inverter protocols
class InverterProtocol {
public:
virtual void setup() = 0;
};
// The abstract base class for all Modbus inverter protocols
class ModbusInverterProtocol : public InverterProtocol {
public:
virtual void update_modbus_registers() = 0;
virtual void handle_static_data();
};
class CanInverterProtocol : public InverterProtocol {
public:
//This function maps all the values fetched from battery CAN to the correct CAN messages
virtual void update_values() = 0;
virtual void transmit_can(unsigned long currentMillis) = 0;
virtual void map_can_frame_to_variable(CAN_frame rx_frame) = 0;
};
#include "../../USER_SETTINGS.h"
#ifdef AFORE_CAN
@ -82,11 +105,14 @@
#ifdef CAN_INVERTER_SELECTED
void update_values_can_inverter();
void map_can_frame_to_variable_inverter(CAN_frame rx_frame);
void transmit_can_inverter();
void transmit_can_inverter(unsigned long currentMillis);
void setup_inverter();
#endif
#ifdef MODBUS_INVERTER_SELECTED
void update_modbus_registers_inverter();
void setup_inverter();
void handle_static_data_modbus();
#endif
#ifdef RS485_INVERTER_SELECTED

View file

@ -438,7 +438,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
void transmit_can_inverter(unsigned long currentMillis) {
// No periodic sending, we only react on received can messages
}

View file

@ -158,8 +158,7 @@ void dump_frame(CAN_frame* frame) {
}
#endif
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
if (currentMillis - previousMillis1000ms >= 1000) {
previousMillis1000ms = currentMillis;

View file

@ -265,8 +265,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
// Send 500ms CAN Message
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {

View file

@ -1,6 +1,7 @@
#include "../include.h"
#ifdef SMA_BYD_H_CAN
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SMA-BYD-H-CAN.h"
/* TODO: Map error bits in 0x158 */
@ -11,6 +12,8 @@ static unsigned long previousMillis100ms = 0;
static uint32_t inverter_time = 0;
static uint16_t inverter_voltage = 0;
static int16_t inverter_current = 0;
static uint16_t timeWithoutInverterAllowsContactorClosing = 0;
#define THIRTY_MINUTES 1200
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
@ -145,6 +148,17 @@ void update_values_can_inverter() { //This function maps all the values fetched
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
// Check if Enable line is working. If we go too long without any input, raise an event
if (!datalayer.system.status.inverter_allows_contactor_closing) {
timeWithoutInverterAllowsContactorClosing++;
if (timeWithoutInverterAllowsContactorClosing > THIRTY_MINUTES) {
set_event(EVENT_NO_ENABLE_DETECTED, 0);
}
} else {
timeWithoutInverterAllowsContactorClosing = 0;
}
/*
//SMA_158.data.u8[0] = //bit12 Fault high temperature, bit34Battery cellundervoltage, bit56 Battery cell overvoltage, bit78 batterysystemdefect
//TODO: add all error bits. Sending message with all 0xAA until that.
@ -236,8 +250,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
// Send CAN Message every 100ms if inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {

View file

@ -1,6 +1,7 @@
#include "../include.h"
#ifdef SMA_BYD_HVS_CAN
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SMA-BYD-HVS-CAN.h"
/* TODO: Map error bits in 0x158 */
@ -11,6 +12,8 @@ static unsigned long previousMillis100ms = 0;
static uint32_t inverter_time = 0;
static uint16_t inverter_voltage = 0;
static int16_t inverter_current = 0;
static uint16_t timeWithoutInverterAllowsContactorClosing = 0;
#define THIRTY_MINUTES 1200
//Actual content messages
CAN_frame SMA_158 = {.FD = false,
@ -135,18 +138,32 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
//Error bits
if (datalayer.system.status.inverter_allows_contactor_closing) {
if (datalayer.system.status.battery_allows_contactor_closing) {
SMA_158.data.u8[2] = 0xAA;
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
} else {
SMA_158.data.u8[2] = 0x6A;
}
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
// Inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
LOW); // Turn off LED to indicate that SMA inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
} else {
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
LOW); // Turn off LED to indicate that SMA inverter does not allow contactor closing
}
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
// Check if Enable line is working. If we go too long without any input, raise an event
if (!datalayer.system.status.inverter_allows_contactor_closing) {
timeWithoutInverterAllowsContactorClosing++;
if (timeWithoutInverterAllowsContactorClosing > THIRTY_MINUTES) {
set_event(EVENT_NO_ENABLE_DETECTED, 0);
}
} else {
timeWithoutInverterAllowsContactorClosing = 0;
}
/*
@ -227,22 +244,65 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
case 0x560: //Message originating from SMA inverter - Init
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x561:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x562:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x563:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x564:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x565:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x566:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x567:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E0: //Message originating from SMA inverter - String
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
//Inverter brand (frame1-3 = 0x53 0x4D 0x41) = SMA
break;
case 0x5E1:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E2:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E3:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E4:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E5:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E6:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
case 0x5E7: //Pairing request
#ifdef DEBUG_LOG
logging.println("Received 0x5E7: SMA pairing request");
#endif // DEBUG_LOG
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
transmit_can_init();
break;
case 0x62C:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
default:
break;
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
// Send CAN Message every 100ms if inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
if (currentMillis - previousMillis100ms >= 100) {

View file

@ -136,8 +136,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
previousMillis100ms = currentMillis;

View file

@ -1,6 +1,7 @@
#include "../include.h"
#ifdef SMA_TRIPOWER_CAN
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "SMA-TRIPOWER-CAN.h"
/* TODO:
@ -32,6 +33,8 @@ static int16_t inverter_current = 0;
static bool pairing_completed = false;
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
static uint16_t timeWithoutInverterAllowsContactorClosing = 0;
#define THIRTY_MINUTES 1200
//Actual content messages
CAN_frame SMA_358 = {.FD = false,
@ -146,6 +149,17 @@ void update_values_can_inverter() { //This function maps all the values fetched
} else {
SMA_4D8.data.u8[6] = READY_STATE;
}
// Check if Enable line is working. If we go too long without any input, raise an event
if (!datalayer.system.status.inverter_allows_contactor_closing) {
timeWithoutInverterAllowsContactorClosing++;
if (timeWithoutInverterAllowsContactorClosing > THIRTY_MINUTES) {
set_event(EVENT_NO_ENABLE_DETECTED, 0);
}
} else {
timeWithoutInverterAllowsContactorClosing = 0;
}
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
@ -190,8 +204,7 @@ void pushFrame(CAN_frame* frame, void (*callback)() = NULL) {
listLength++;
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
// Send CAN Message only if we're enabled by inverter
if (!datalayer.system.status.inverter_allows_contactor_closing) {

View file

@ -247,8 +247,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;

View file

@ -206,7 +206,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_187E.data.u8[5] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
}
void transmit_can_inverter() {
void transmit_can_inverter(unsigned long currentMillis) {
// No periodic sending used on this protocol, we react only on incoming CAN messages!
}

View file

@ -557,9 +557,7 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter() {
unsigned long currentMillis = millis();
void transmit_can_inverter(unsigned long currentMillis) {
// Send 1s CAN Message
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
previousMillis500ms = currentMillis;