mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Merge branch 'main' into feature/geely-geometry-battery
This commit is contained in:
commit
11ca72b731
95 changed files with 3271 additions and 2644 deletions
93
.github/workflows/compile-all-double-batteries.yml
vendored
Normal file
93
.github/workflows/compile-all-double-batteries.yml
vendored
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
69
Software/src/battery/BATTERIES.cpp
Normal file
69
Software/src/battery/BATTERIES.cpp
Normal 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
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
17
Software/src/battery/CanBattery.h
Normal file
17
Software/src/battery/CanBattery.h
Normal 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
|
|
@ -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]) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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 battery’s 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
9
Software/src/charger/CHARGERS.cpp
Normal file
9
Software/src/charger/CHARGERS.cpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#include "../include.h"
|
||||
|
||||
CanCharger* charger = nullptr;
|
||||
|
||||
void setup_charger() {
|
||||
#ifdef SELECTED_CHARGER_CLASS
|
||||
charger = new SELECTED_CHARGER_CLASS();
|
||||
#endif
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
48
Software/src/charger/CanCharger.h
Normal file
48
Software/src/charger/CanCharger.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'; }";
|
||||
|
|
|
@ -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>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
content += " <button onclick='editChargerHVDCEnabled()'>Edit</button></h4>";
|
||||
|
||||
content += "<h4 style='color: white;'>Charger HVDC Enabled: ";
|
||||
if (datalayer.charger.charger_HV_enabled) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
content += "<h4 style='color: white;'>Charger Aux12VDC Enabled: ";
|
||||
if (datalayer.charger.charger_aux12V_enabled) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</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>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</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>";
|
||||
|
|
|
@ -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>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Inverter: ";
|
||||
content += " Inverter allows contactor closing: ";
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
|
||||
content += "<span>✓</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>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
content += "<h4>Charger HV Enabled: ";
|
||||
if (datalayer.charger.charger_HV_enabled) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
|
||||
content += "<h4>Charger Aux12v Enabled: ";
|
||||
if (datalayer.charger.charger_aux12V_enabled) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</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>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</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> ";
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
56
Software/src/inverter/INVERTERS.cpp
Normal file
56
Software/src/inverter/INVERTERS.cpp
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue