mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Merge branch 'main' of https://github.com/nmainil/Battery-Emulator
Improvement support # Please enter a commit message to explain why this merge is necessary,
This commit is contained in:
commit
f315321c24
21 changed files with 630 additions and 301 deletions
|
@ -122,6 +122,10 @@ void setup_can_shunt();
|
|||
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SIMPBMS_BATTERY
|
||||
#include "SIMPBMS-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#if defined(TESLA_MODEL_SX_BATTERY) || defined(TESLA_MODEL_3Y_BATTERY)
|
||||
#define TESLA_BATTERY
|
||||
#include "TESLA-BATTERY.h"
|
||||
|
|
|
@ -216,7 +216,7 @@ static uint32_t battery_BEV_available_power_shortterm_charge = 0;
|
|||
static uint32_t battery_BEV_available_power_shortterm_discharge = 0;
|
||||
static uint32_t battery_BEV_available_power_longterm_charge = 0;
|
||||
static uint32_t battery_BEV_available_power_longterm_discharge = 0;
|
||||
static uint16_t battery_energy_content_maximum_kWh = 0;
|
||||
static uint16_t battery_energy_content_maximum_Wh = 0;
|
||||
static uint16_t battery_display_SOC = 0;
|
||||
static uint16_t battery_volts = 0;
|
||||
static uint16_t battery_HVBatt_SOC = 0;
|
||||
|
@ -284,7 +284,7 @@ static uint32_t battery2_BEV_available_power_shortterm_charge = 0;
|
|||
static uint32_t battery2_BEV_available_power_shortterm_discharge = 0;
|
||||
static uint32_t battery2_BEV_available_power_longterm_charge = 0;
|
||||
static uint32_t battery2_BEV_available_power_longterm_discharge = 0;
|
||||
static uint16_t battery2_energy_content_maximum_kWh = 0;
|
||||
static uint16_t battery2_energy_content_maximum_Wh = 0;
|
||||
static uint16_t battery2_display_SOC = 0;
|
||||
static uint16_t battery2_volts = 0;
|
||||
static uint16_t battery2_HVBatt_SOC = 0;
|
||||
|
@ -373,7 +373,7 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery2.status.current_dA = battery2_current;
|
||||
|
||||
datalayer.battery2.info.total_capacity_Wh = (battery2_energy_content_maximum_kWh * 1000); // Convert kWh to Wh
|
||||
datalayer.battery2.info.total_capacity_Wh = battery2_energy_content_maximum_Wh;
|
||||
|
||||
datalayer.battery2.status.remaining_capacity_Wh = battery2_predicted_energy_charge_condition;
|
||||
|
||||
|
@ -443,7 +443,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.current_dA = battery_current;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh = (battery_energy_content_maximum_kWh * 1000); // Convert kWh to Wh
|
||||
datalayer.battery.info.total_capacity_Wh = battery_energy_content_maximum_Wh;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = battery_predicted_energy_charge_condition;
|
||||
|
||||
|
@ -611,10 +611,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
battery_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
battery_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
battery_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4];
|
||||
battery_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
|
||||
if (battery_energy_content_maximum_kWh > 33) {
|
||||
battery_energy_content_maximum_Wh = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]) * 20;
|
||||
if (battery_energy_content_maximum_Wh > 33000) {
|
||||
detectedBattery = BATTERY_120AH;
|
||||
} else if (battery_energy_content_maximum_kWh > 20) {
|
||||
} else if (battery_energy_content_maximum_Wh > 20000) {
|
||||
detectedBattery = BATTERY_94AH;
|
||||
} else {
|
||||
detectedBattery = BATTERY_60AH;
|
||||
|
@ -797,10 +797,10 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
|
|||
battery2_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
battery2_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
battery2_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4];
|
||||
battery2_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
|
||||
if (battery2_energy_content_maximum_kWh > 33) {
|
||||
battery2_energy_content_maximum_Wh = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]) * 20;
|
||||
if (battery2_energy_content_maximum_Wh > 33000) {
|
||||
detectedBattery2 = BATTERY_120AH;
|
||||
} else if (battery2_energy_content_maximum_kWh > 20) {
|
||||
} else if (battery2_energy_content_maximum_Wh > 20000) {
|
||||
detectedBattery2 = BATTERY_94AH;
|
||||
} else {
|
||||
detectedBattery2 = BATTERY_60AH;
|
||||
|
|
|
@ -36,6 +36,9 @@ 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 uint8_t battery_frame_index = 0;
|
||||
#define NOF_CELLS 126
|
||||
static uint16_t battery_cellvoltages[NOF_CELLS] = {0};
|
||||
#ifdef DOUBLE_BATTERY
|
||||
static int16_t battery2_temperature_ambient = 0;
|
||||
static int16_t battery2_daughterboard_temperatures[10];
|
||||
|
@ -52,6 +55,8 @@ static int16_t BMS2_highest_cell_temperature = 0;
|
|||
static int16_t BMS2_average_cell_temperature = 0;
|
||||
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
|
||||
static uint16_t BMS2_highest_cell_voltage_mV = 3300;
|
||||
static uint8_t battery2_frame_index = 0;
|
||||
static uint16_t battery2_cellvoltages[NOF_CELLS] = {0};
|
||||
#endif //DOUBLE_BATTERY
|
||||
#define POLL_FOR_BATTERY_SOC 0x05
|
||||
#define POLL_FOR_BATTERY_VOLTAGE 0x08
|
||||
|
@ -135,6 +140,9 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, NOF_CELLS * sizeof(uint16_t));
|
||||
|
||||
#ifdef SKIP_TEMPERATURE_SENSOR_NUMBER
|
||||
// Initialize min and max variables for temperature calculation
|
||||
battery_calc_min_temperature = battery_daughterboard_temperatures[0];
|
||||
|
@ -253,6 +261,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
|||
break;
|
||||
case 0x43D:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
battery_frame_index = rx_frame.data.u8[0];
|
||||
|
||||
if (battery_frame_index < (NOF_CELLS / 3)) {
|
||||
uint8_t base_index = battery_frame_index * 3;
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
battery_cellvoltages[base_index + i] =
|
||||
(((rx_frame.data.u8[2 * (i + 1)] & 0x0F) << 8) | rx_frame.data.u8[2 * i + 1]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x444:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
@ -479,6 +496,9 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
datalayer.battery2.status.temperature_min_dC = BMS2_lowest_cell_temperature * 10; // Add decimals
|
||||
|
||||
datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10;
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, NOF_CELLS * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
|
||||
|
@ -547,6 +567,14 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
|
|||
break;
|
||||
case 0x43D:
|
||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
battery2_frame_index = rx_frame.data.u8[0];
|
||||
if (battery2_frame_index < (NOF_CELLS / 3)) {
|
||||
uint8_t base2_index = battery2_frame_index * 3;
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
battery2_cellvoltages[base2_index + i] =
|
||||
(((rx_frame.data.u8[2 * (i + 1)] & 0x0F) << 8) | rx_frame.data.u8[2 * i + 1]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x444:
|
||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
|
|
@ -1075,7 +1075,7 @@ void transmit_can_battery() {
|
|||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (datalayer.system.status.BMS_reset_in_progress) {
|
||||
if (datalayer.system.status.BMS_reset_in_progress || datalayer.system.status.BMS_startup_in_progress) {
|
||||
// Transmitting towards battery is halted while BMS is being reset
|
||||
// Reset sending counters to avoid overrun messages when reset is over
|
||||
previousMillis10 = currentMillis;
|
||||
|
|
131
Software/src/battery/SIMPBMS-BATTERY.cpp
Normal file
131
Software/src/battery/SIMPBMS-BATTERY.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#include "../include.h"
|
||||
#ifdef SIMPBMS_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "SIMPBMS-BATTERY.h"
|
||||
|
||||
#define SIMPBMS_MAX_CELLS 128
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
|
||||
|
||||
//Actual content messages
|
||||
|
||||
static int16_t celltemperature_max_dC = 0;
|
||||
static int16_t celltemperature_min_dC = 0;
|
||||
static int16_t current_dA = 0;
|
||||
static uint16_t voltage_dV = 0;
|
||||
static uint16_t cellvoltage_max_mV = 3700;
|
||||
static uint16_t cellvoltage_min_mV = 3700;
|
||||
static uint16_t charge_cutoff_voltage = 0;
|
||||
static uint16_t discharge_cutoff_voltage = 0;
|
||||
static int16_t max_charge_current = 0;
|
||||
static int16_t max_discharge_current = 0;
|
||||
static uint8_t ensemble_info_ack = 0;
|
||||
static uint8_t cells_in_series = 0;
|
||||
static uint8_t voltage_level = 0;
|
||||
static uint8_t ah_total = 0;
|
||||
static uint8_t SOC = 0;
|
||||
static uint8_t SOH = 99;
|
||||
static uint8_t charge_forbidden = 0;
|
||||
static uint8_t discharge_forbidden = 0;
|
||||
static uint16_t cellvoltages_mV[SIMPBMS_MAX_CELLS] = {0};
|
||||
|
||||
void update_values_battery() {
|
||||
|
||||
datalayer.battery.status.real_soc = (SOC * 100); //increase SOC range from 0-100 -> 100.00
|
||||
|
||||
datalayer.battery.status.soh_pptt = (SOH * 100); //Increase decimals from 100% -> 100.00%
|
||||
|
||||
datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0)
|
||||
|
||||
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = (max_charge_current * (voltage_dV / 10));
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = (max_discharge_current * (voltage_dV / 10));
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh = ah_total * (voltage_dV / 10);
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = celltemperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = celltemperature_max_dC;
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = charge_cutoff_voltage;
|
||||
|
||||
datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage;
|
||||
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, SIMPBMS_MAX_CELLS * sizeof(uint16_t));
|
||||
|
||||
datalayer.battery.info.number_of_cells = cells_in_series;
|
||||
}
|
||||
|
||||
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.ID) {
|
||||
case 0x355:
|
||||
SOC = (rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0];
|
||||
SOH = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
|
||||
|
||||
break;
|
||||
case 0x351:
|
||||
//discharge and charge limits
|
||||
charge_cutoff_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
discharge_cutoff_voltage = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
|
||||
max_charge_current = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2])) / 10;
|
||||
max_discharge_current = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4])) / 10;
|
||||
break;
|
||||
case 0x356:
|
||||
//current status
|
||||
current_dA = ((rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2]) / 1000;
|
||||
voltage_dV = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 10;
|
||||
break;
|
||||
case 0x373:
|
||||
//min/max values
|
||||
cellvoltage_max_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
cellvoltage_min_mV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
celltemperature_max_dC = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 273.15) * 10;
|
||||
celltemperature_min_dC = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) - 273.15) * 10;
|
||||
break;
|
||||
case 0x372:
|
||||
//cells_in_series = rx_frame.data.u8[0];
|
||||
if (rx_frame.data.u8[3] > 0 && rx_frame.data.u8[3] <= SIMPBMS_MAX_CELLS) {
|
||||
uint8_t cellnumber = rx_frame.data.u8[3];
|
||||
if (cellnumber > cells_in_series) {
|
||||
cells_in_series = cellnumber;
|
||||
}
|
||||
cellvoltages_mV[cellnumber - 1] = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x379:
|
||||
ah_total = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void transmit_can_battery() {}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "SIMPBMS battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = CELL_COUNT;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
}
|
||||
|
||||
#endif
|
19
Software/src/battery/SIMPBMS-BATTERY.h
Normal file
19
Software/src/battery/SIMPBMS-BATTERY.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef SIMPBMS_BATTERY_H
|
||||
#define SIMPBMS_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
/* DEFAULT VALUES BMS will send configured */
|
||||
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
|
||||
#define MIN_PACK_VOLTAGE_DV 1500
|
||||
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
#define CELL_COUNT 96
|
||||
|
||||
void setup_battery(void);
|
||||
void transmit_can_frame(CAN_frame* tx_frame, int interface);
|
||||
|
||||
#endif
|
|
@ -41,8 +41,10 @@ unsigned long timeSpentInFaultedMode = 0;
|
|||
#endif
|
||||
unsigned long currentTime = 0;
|
||||
unsigned long lastPowerRemovalTime = 0;
|
||||
unsigned long bmsPowerOnTime = 0;
|
||||
const unsigned long powerRemovalInterval = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
const unsigned long powerRemovalDuration = 30000; // 30 seconds in milliseconds
|
||||
const unsigned long bmsWarmupDuration = 3000;
|
||||
|
||||
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
|
||||
#ifdef PWM_CONTACTOR_CONTROL
|
||||
|
@ -249,23 +251,29 @@ void handle_BMSpower() {
|
|||
|
||||
#ifdef PERIODIC_BMS_RESET
|
||||
// Check if 24 hours have passed since the last power removal
|
||||
if (currentTime - lastPowerRemovalTime >= powerRemovalInterval) {
|
||||
if ((currentTime + bmsResetTimeOffset) - lastPowerRemovalTime >= powerRemovalInterval) {
|
||||
start_bms_reset();
|
||||
}
|
||||
#endif //PERIODIC_BMS_RESET
|
||||
|
||||
// If power has been removed for 30 seconds, restore the power and resume the emulator
|
||||
// If power has been removed for 30 seconds, restore the power
|
||||
if (datalayer.system.status.BMS_reset_in_progress && currentTime - lastPowerRemovalTime >= powerRemovalDuration) {
|
||||
// Reapply power to the BMS
|
||||
digitalWrite(BMS_POWER, HIGH);
|
||||
#ifdef BMS_2_POWER
|
||||
digitalWrite(BMS_2_POWER, HIGH); // Same for battery 2
|
||||
#endif
|
||||
bmsPowerOnTime = currentTime;
|
||||
datalayer.system.status.BMS_reset_in_progress = false; // Reset the power removal flag
|
||||
datalayer.system.status.BMS_startup_in_progress = true; // Set the BMS warmup flag
|
||||
}
|
||||
//if power has been restored we need to wait a couple of seconds to unpause the battery
|
||||
if (datalayer.system.status.BMS_startup_in_progress && currentTime - bmsPowerOnTime >= bmsWarmupDuration) {
|
||||
|
||||
//Resume from the power pause
|
||||
setBatteryPause(false, false, false, false);
|
||||
|
||||
datalayer.system.status.BMS_reset_in_progress = false; // Reset the power removal flag
|
||||
datalayer.system.status.BMS_startup_in_progress = false; // Reset the BMS warmup removal flag
|
||||
set_event(EVENT_PERIODIC_BMS_RESET, 0);
|
||||
}
|
||||
#endif //defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET)
|
||||
}
|
||||
|
@ -274,9 +282,10 @@ void start_bms_reset() {
|
|||
#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET)
|
||||
if (!datalayer.system.status.BMS_reset_in_progress) {
|
||||
lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started
|
||||
|
||||
// we are now resetting at the correct time. We don't need to offset anymore
|
||||
bmsResetTimeOffset = 0;
|
||||
// Set a flag to let the rest of the system know we are cutting power to the BMS.
|
||||
// The battery CAN sending routine will then know not to try to send anything towards battery while active
|
||||
// The battery CAN sending routine will then know not to try guto send anything towards battery while active
|
||||
datalayer.system.status.BMS_reset_in_progress = true;
|
||||
|
||||
// Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop)
|
||||
|
|
|
@ -97,6 +97,10 @@ typedef struct {
|
|||
|
||||
/** The current battery status, which for now has the name real_bms_status */
|
||||
real_bms_status_enum real_bms_status = BMS_DISCONNECTED;
|
||||
|
||||
/** LED mode, customizable by user */
|
||||
led_mode_enum led_mode = LED_MODE;
|
||||
|
||||
} DATALAYER_BATTERY_STATUS_TYPE;
|
||||
|
||||
typedef struct {
|
||||
|
@ -288,6 +292,8 @@ typedef struct {
|
|||
#endif
|
||||
/** True if the BMS is being reset, by cutting power towards it */
|
||||
bool BMS_reset_in_progress = false;
|
||||
/** True if the BMS is starting up */
|
||||
bool BMS_startup_in_progress = false;
|
||||
#ifdef PRECHARGE_CONTROL
|
||||
/** State of automatic precharge sequence */
|
||||
PrechargeState precharge_status = AUTO_PRECHARGE_IDLE;
|
||||
|
|
|
@ -219,6 +219,9 @@ void init_events(void) {
|
|||
events.entries[EVENT_MQTT_DISCONNECT].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_PERIODIC_BMS_RESET].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
|
||||
|
||||
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
|
||||
|
@ -453,6 +456,13 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "EQUIPMENT STOP ACTIVATED!!!";
|
||||
case EVENT_SD_INIT_FAILED:
|
||||
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
|
||||
case EVENT_PERIODIC_BMS_RESET:
|
||||
return "BMS Reset Event Completed.";
|
||||
case EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS:
|
||||
return "Successfully syncronised with the NTP Server. BMS will reset every 24 hours at defined time";
|
||||
case EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED:
|
||||
return "Failed to syncronise with the NTP Server. BMS will reset every 24 hours from when the emulator was "
|
||||
"powered on";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -25,99 +25,102 @@
|
|||
* - Increment EE_MAGIC_HEADER_VALUE in case you've changed the order
|
||||
*/
|
||||
|
||||
#define EVENTS_ENUM_TYPE(XX) \
|
||||
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
|
||||
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) \
|
||||
XX(EVENT_CAN_CHARGER_MISSING) \
|
||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_CONTACTOR_WELDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_SOC_UNAVAILABLE) \
|
||||
XX(EVENT_STALE_VALUE) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BALANCING_START) \
|
||||
XX(EVENT_BALANCING_END) \
|
||||
XX(EVENT_BATTERY_EMPTY) \
|
||||
XX(EVENT_BATTERY_FULL) \
|
||||
XX(EVENT_BATTERY_FUSE) \
|
||||
XX(EVENT_BATTERY_FROZEN) \
|
||||
XX(EVENT_BATTERY_CAUTION) \
|
||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_OVERHEAT) \
|
||||
XX(EVENT_BATTERY_OVERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_UNDERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_VALUE_UNAVAILABLE) \
|
||||
XX(EVENT_BATTERY_ISOLATION) \
|
||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||
XX(EVENT_BATTERY_WARMED_UP) \
|
||||
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
||||
XX(EVENT_SOH_DIFFERENCE) \
|
||||
XX(EVENT_SOH_LOW) \
|
||||
XX(EVENT_HVIL_FAILURE) \
|
||||
XX(EVENT_PRECHARGE_FAILURE) \
|
||||
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
||||
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
|
||||
XX(EVENT_INTERFACE_MISSING) \
|
||||
XX(EVENT_MODBUS_INVERTER_MISSING) \
|
||||
XX(EVENT_ERROR_OPEN_CONTACTOR) \
|
||||
XX(EVENT_CELL_CRITICAL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_CRITICAL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_DEVIATION_HIGH) \
|
||||
XX(EVENT_UNKNOWN_EVENT_SET) \
|
||||
XX(EVENT_OTA_UPDATE) \
|
||||
XX(EVENT_OTA_UPDATE_TIMEOUT) \
|
||||
XX(EVENT_DUMMY_INFO) \
|
||||
XX(EVENT_DUMMY_DEBUG) \
|
||||
XX(EVENT_DUMMY_WARNING) \
|
||||
XX(EVENT_DUMMY_ERROR) \
|
||||
XX(EVENT_PERSISTENT_SAVE_INFO) \
|
||||
XX(EVENT_SERIAL_RX_WARNING) \
|
||||
XX(EVENT_SERIAL_RX_FAILURE) \
|
||||
XX(EVENT_SERIAL_TX_FAILURE) \
|
||||
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
|
||||
XX(EVENT_EEPROM_WRITE) \
|
||||
XX(EVENT_RESET_UNKNOWN) \
|
||||
XX(EVENT_RESET_POWERON) \
|
||||
XX(EVENT_RESET_EXT) \
|
||||
XX(EVENT_RESET_SW) \
|
||||
XX(EVENT_RESET_PANIC) \
|
||||
XX(EVENT_RESET_INT_WDT) \
|
||||
XX(EVENT_RESET_TASK_WDT) \
|
||||
XX(EVENT_RESET_WDT) \
|
||||
XX(EVENT_RESET_DEEPSLEEP) \
|
||||
XX(EVENT_RESET_BROWNOUT) \
|
||||
XX(EVENT_RESET_SDIO) \
|
||||
XX(EVENT_RESET_USB) \
|
||||
XX(EVENT_RESET_JTAG) \
|
||||
XX(EVENT_RESET_EFUSE) \
|
||||
XX(EVENT_RESET_PWR_GLITCH) \
|
||||
XX(EVENT_RESET_CPU_LOCKUP) \
|
||||
XX(EVENT_RJXZS_LOG) \
|
||||
XX(EVENT_PAUSE_BEGIN) \
|
||||
XX(EVENT_PAUSE_END) \
|
||||
XX(EVENT_WIFI_CONNECT) \
|
||||
XX(EVENT_WIFI_DISCONNECT) \
|
||||
XX(EVENT_MQTT_CONNECT) \
|
||||
XX(EVENT_MQTT_DISCONNECT) \
|
||||
XX(EVENT_EQUIPMENT_STOP) \
|
||||
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
||||
XX(EVENT_SD_INIT_FAILED) \
|
||||
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
|
||||
#define EVENTS_ENUM_TYPE(XX) \
|
||||
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
|
||||
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) \
|
||||
XX(EVENT_CAN_CHARGER_MISSING) \
|
||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_CONTACTOR_WELDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_SOC_UNAVAILABLE) \
|
||||
XX(EVENT_STALE_VALUE) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BALANCING_START) \
|
||||
XX(EVENT_BALANCING_END) \
|
||||
XX(EVENT_BATTERY_EMPTY) \
|
||||
XX(EVENT_BATTERY_FULL) \
|
||||
XX(EVENT_BATTERY_FUSE) \
|
||||
XX(EVENT_BATTERY_FROZEN) \
|
||||
XX(EVENT_BATTERY_CAUTION) \
|
||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_OVERHEAT) \
|
||||
XX(EVENT_BATTERY_OVERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_UNDERVOLTAGE) \
|
||||
XX(EVENT_BATTERY_VALUE_UNAVAILABLE) \
|
||||
XX(EVENT_BATTERY_ISOLATION) \
|
||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||
XX(EVENT_BATTERY_WARMED_UP) \
|
||||
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
||||
XX(EVENT_SOH_DIFFERENCE) \
|
||||
XX(EVENT_SOH_LOW) \
|
||||
XX(EVENT_HVIL_FAILURE) \
|
||||
XX(EVENT_PRECHARGE_FAILURE) \
|
||||
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
||||
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
|
||||
XX(EVENT_INTERFACE_MISSING) \
|
||||
XX(EVENT_MODBUS_INVERTER_MISSING) \
|
||||
XX(EVENT_ERROR_OPEN_CONTACTOR) \
|
||||
XX(EVENT_CELL_CRITICAL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_CRITICAL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_DEVIATION_HIGH) \
|
||||
XX(EVENT_UNKNOWN_EVENT_SET) \
|
||||
XX(EVENT_OTA_UPDATE) \
|
||||
XX(EVENT_OTA_UPDATE_TIMEOUT) \
|
||||
XX(EVENT_DUMMY_INFO) \
|
||||
XX(EVENT_DUMMY_DEBUG) \
|
||||
XX(EVENT_DUMMY_WARNING) \
|
||||
XX(EVENT_DUMMY_ERROR) \
|
||||
XX(EVENT_PERSISTENT_SAVE_INFO) \
|
||||
XX(EVENT_SERIAL_RX_WARNING) \
|
||||
XX(EVENT_SERIAL_RX_FAILURE) \
|
||||
XX(EVENT_SERIAL_TX_FAILURE) \
|
||||
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
|
||||
XX(EVENT_EEPROM_WRITE) \
|
||||
XX(EVENT_RESET_UNKNOWN) \
|
||||
XX(EVENT_RESET_POWERON) \
|
||||
XX(EVENT_RESET_EXT) \
|
||||
XX(EVENT_RESET_SW) \
|
||||
XX(EVENT_RESET_PANIC) \
|
||||
XX(EVENT_RESET_INT_WDT) \
|
||||
XX(EVENT_RESET_TASK_WDT) \
|
||||
XX(EVENT_RESET_WDT) \
|
||||
XX(EVENT_RESET_DEEPSLEEP) \
|
||||
XX(EVENT_RESET_BROWNOUT) \
|
||||
XX(EVENT_RESET_SDIO) \
|
||||
XX(EVENT_RESET_USB) \
|
||||
XX(EVENT_RESET_JTAG) \
|
||||
XX(EVENT_RESET_EFUSE) \
|
||||
XX(EVENT_RESET_PWR_GLITCH) \
|
||||
XX(EVENT_RESET_CPU_LOCKUP) \
|
||||
XX(EVENT_RJXZS_LOG) \
|
||||
XX(EVENT_PAUSE_BEGIN) \
|
||||
XX(EVENT_PAUSE_END) \
|
||||
XX(EVENT_WIFI_CONNECT) \
|
||||
XX(EVENT_WIFI_DISCONNECT) \
|
||||
XX(EVENT_MQTT_CONNECT) \
|
||||
XX(EVENT_MQTT_DISCONNECT) \
|
||||
XX(EVENT_EQUIPMENT_STOP) \
|
||||
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
||||
XX(EVENT_SD_INIT_FAILED) \
|
||||
XX(EVENT_PERIODIC_BMS_RESET) \
|
||||
XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS) \
|
||||
XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED) \
|
||||
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
|
||||
XX(EVENT_NOF_EVENTS)
|
||||
|
||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
||||
|
|
|
@ -16,7 +16,7 @@ static const float heartbeat_peak1 = 0.80;
|
|||
static const float heartbeat_peak2 = 0.55;
|
||||
static const float heartbeat_deviation = 0.05;
|
||||
|
||||
static LED led(LED_MODE_DEFAULT);
|
||||
static LED led(datalayer.battery.status.led_mode);
|
||||
|
||||
void led_init(void) {
|
||||
led.init();
|
||||
|
@ -31,14 +31,14 @@ led_color led_get_color() {
|
|||
void LED::exe(void) {
|
||||
|
||||
// Update brightness
|
||||
switch (mode) {
|
||||
case led_mode::FLOW:
|
||||
switch (datalayer.battery.status.led_mode) {
|
||||
case led_mode_enum::FLOW:
|
||||
flow_run();
|
||||
break;
|
||||
case led_mode::HEARTBEAT:
|
||||
case led_mode_enum::HEARTBEAT:
|
||||
heartbeat_run();
|
||||
break;
|
||||
case led_mode::CLASSIC:
|
||||
case led_mode_enum::CLASSIC:
|
||||
default:
|
||||
classic_run();
|
||||
break;
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include "../../include.h"
|
||||
#include "../../lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h"
|
||||
|
||||
enum led_mode { CLASSIC, FLOW, HEARTBEAT };
|
||||
|
||||
class LED {
|
||||
public:
|
||||
led_color color = led_color::GREEN;
|
||||
|
@ -14,9 +12,9 @@ class LED {
|
|||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
max_brightness(LED_MAX_BRIGHTNESS),
|
||||
brightness(LED_MAX_BRIGHTNESS),
|
||||
mode(led_mode::CLASSIC) {}
|
||||
mode(led_mode_enum::CLASSIC) {}
|
||||
|
||||
LED(led_mode mode)
|
||||
LED(led_mode_enum mode)
|
||||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
max_brightness(LED_MAX_BRIGHTNESS),
|
||||
brightness(LED_MAX_BRIGHTNESS),
|
||||
|
@ -29,7 +27,7 @@ class LED {
|
|||
Adafruit_NeoPixel pixels;
|
||||
uint8_t max_brightness;
|
||||
uint8_t brightness;
|
||||
led_mode mode;
|
||||
led_mode_enum mode;
|
||||
|
||||
void classic_run(void);
|
||||
void flow_run(void);
|
||||
|
|
63
Software/src/devboard/utils/ntp_time.cpp
Normal file
63
Software/src/devboard/utils/ntp_time.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "ntp_time.h"
|
||||
#include "../../include.h"
|
||||
#include "time.h"
|
||||
|
||||
const unsigned long millisInADay = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
|
||||
unsigned long long getNtpTimeInMillis() {
|
||||
configTzTime(time_zone, ntpServer1, ntpServer2);
|
||||
struct tm timeinfo;
|
||||
|
||||
// Wait for time to be set
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (getLocalTime(&timeinfo)) {
|
||||
logging.println("Time retrieved from NTP Server");
|
||||
break;
|
||||
}
|
||||
logging.println("Waiting for NTP time...");
|
||||
}
|
||||
|
||||
if (!getLocalTime(&timeinfo)) {
|
||||
logging.println("Failed to obtain time");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert to milliseconds
|
||||
time_t epochTime = mktime(&timeinfo);
|
||||
return static_cast<unsigned long long>(epochTime) * 1000;
|
||||
}
|
||||
|
||||
// Function to calculate the difference in milliseconds to the next target time
|
||||
unsigned long long millisToNextTargetTime(unsigned long long currentMillis, int targetTime) {
|
||||
int hour = targetTime / 100;
|
||||
int minute = targetTime % 100;
|
||||
|
||||
time_t currentTime = currentMillis / 1000; // Convert milliseconds to seconds
|
||||
struct tm* timeinfo = localtime(¤tTime);
|
||||
|
||||
// Set timeinfo to the target time on the next day
|
||||
timeinfo->tm_hour = hour;
|
||||
timeinfo->tm_min = minute;
|
||||
timeinfo->tm_sec = 0;
|
||||
|
||||
// Increment day if the current time is past the target time
|
||||
if (mktime(timeinfo) <= currentTime) {
|
||||
timeinfo->tm_mday += 1;
|
||||
}
|
||||
time_t nextTargetTime = mktime(timeinfo);
|
||||
unsigned long long nextTargetMillis = static_cast<unsigned long long>(nextTargetTime) * 1000;
|
||||
return nextTargetMillis - currentMillis;
|
||||
}
|
||||
|
||||
unsigned long long getTimeOffsetfromNowUntil(int targetTime) {
|
||||
logging.println("Getting time offset from now until " + String(targetTime));
|
||||
unsigned long long timeinMillis = getNtpTimeInMillis();
|
||||
if (timeinMillis != 0) {
|
||||
logging.println("Time in millis: " + String(timeinMillis));
|
||||
unsigned long long timeOffsetUntilTargetTime = millisInADay - (millisToNextTargetTime(timeinMillis, targetTime));
|
||||
logging.println("Time offset until target time: " + String(timeOffsetUntilTargetTime));
|
||||
return timeOffsetUntilTargetTime;
|
||||
} else
|
||||
|
||||
return 0;
|
||||
}
|
12
Software/src/devboard/utils/ntp_time.h
Normal file
12
Software/src/devboard/utils/ntp_time.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef __NTPTIME_H__
|
||||
#define __NTPTIME_H__
|
||||
|
||||
extern const char* ntpServer1;
|
||||
extern const char* ntpServer2;
|
||||
extern const char* time_zone;
|
||||
|
||||
unsigned long long getNtpTimeInMillis();
|
||||
unsigned long long millisToNextTargetTime(unsigned long long currentMillis, int targetTime);
|
||||
unsigned long long getTimeOffsetfromNowUntil(int targetTime);
|
||||
|
||||
#endif
|
|
@ -7,6 +7,7 @@ enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAU
|
|||
enum real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 };
|
||||
enum battery_chemistry_enum { NCA, NMC, LFP };
|
||||
enum led_color { GREEN, YELLOW, RED, BLUE };
|
||||
enum led_mode_enum { CLASSIC, FLOW, HEARTBEAT };
|
||||
enum PrechargeState {
|
||||
AUTO_PRECHARGE_IDLE,
|
||||
AUTO_PRECHARGE_START,
|
||||
|
|
|
@ -20,6 +20,7 @@ static unsigned long currentMillis;
|
|||
static unsigned long startupMillis = 0;
|
||||
static unsigned long contactorMillis = 0;
|
||||
|
||||
static uint16_t rx_index = 0;
|
||||
static boolean RX_allow = false;
|
||||
|
||||
union f32b {
|
||||
|
@ -27,66 +28,75 @@ union f32b {
|
|||
byte b[4];
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
uint8_t BATTERY_INFO[40] = {
|
||||
0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
||||
0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
|
||||
0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16
|
||||
0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527
|
||||
0x01, 0x05, 0xC8, 0x41, // 25.0024 ?
|
||||
0xC2, 0x18, // Battery Firmware, modbus register 586
|
||||
0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ??
|
||||
0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02,
|
||||
0x4D, // CRC
|
||||
0x00}; //
|
||||
|
||||
// values in CyclicData will be overwritten at update_modbus_registers_inverter()
|
||||
|
||||
uint8_t CyclicData[64] = {
|
||||
0x00, // First zero byte pointer
|
||||
0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header
|
||||
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9
|
||||
0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13
|
||||
0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17
|
||||
0x00, 0x00, 0x00, 0x00, // Peak Current (1s period?), Bytes 18-21
|
||||
0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25
|
||||
0x00, 0x00, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax
|
||||
0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512
|
||||
0x00, 0x00, 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 // Sunspec: AChaMax
|
||||
0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41
|
||||
0x00, 0x00, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45
|
||||
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49
|
||||
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53
|
||||
|
||||
0xFE, 0x04, // Cycle count,
|
||||
0x00, // Byte 56
|
||||
0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
|
||||
0x64, // SOC , Bit 58
|
||||
0x00, // Unknown,
|
||||
0x00, // Unknown,
|
||||
0x02, // Unknown, Mostly 0x02. seen also 0x01
|
||||
0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
|
||||
0x00, // First zero byte pointer
|
||||
0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
||||
0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256
|
||||
0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac])
|
||||
0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0
|
||||
0x00, 0x00, 0xC8, 0x41, // Nominal Capacity (0x10b4)
|
||||
0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8)
|
||||
0x00, // Static (BYD: GetBatteryInfo this[0x10ba])
|
||||
0x00, // ?
|
||||
0x59, 0x42, // Vendor identifier
|
||||
// 0x59 0x42 -> 'YB' -> BYD
|
||||
// 0x59 0x44 -> 'YD' -> Dyness
|
||||
0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be])
|
||||
0x00, 0x00,
|
||||
0x05, 0x00, // Number of blocks in series (uint16)
|
||||
0xA0, 0x00, 0x00, 0x00,
|
||||
0x4D, // CRC
|
||||
0x00};
|
||||
// clang-format on
|
||||
|
||||
// values in CYCLIC_DATA will be overwritten at update_modbus_registers_inverter()
|
||||
|
||||
// clang-format off
|
||||
uint8_t CYCLIC_DATA[64] = {
|
||||
0x00, // First zero byte pointer
|
||||
0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
||||
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float32) Bytes 6- 9 Modbus register 216
|
||||
0x00, 0x00, 0x8D, 0x43, // Max Voltage (float32) Bytes 10-13
|
||||
0x00, 0x00, 0xAC, 0x41, // Battery Temperature (float32) Bytes 14-17 Modbus register 214
|
||||
0x00, 0x00, 0x00, 0x00, // Peak Current (1s period?) (float32) Bytes 18-21
|
||||
0x00, 0x00, 0x00, 0x00, // Avg current (1s period?) (float32) Bytes 22-25
|
||||
0x00, 0x00, 0x48, 0x42, // Max discharge current (float32) Bytes 26-29 Sunspec: ADisChaMax
|
||||
0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (float32) Bytes 30-33 Modbus register 512
|
||||
0x00, 0x00, 0xA0, 0x41, // Max charge current (float32) Bytes 34-37 0.0f when SoC is 100%, Sunspec: AChaMax
|
||||
0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (float32) Bytes 38-41
|
||||
0x00, 0x00, 0xA4, 0x41, // MinCellTemp (float32) Bytes 42-45
|
||||
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float32) Bytes 46-49
|
||||
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float32) Bytes 50-53
|
||||
|
||||
0xFE, 0x04, // Bytes 54-55, Cycle count (uint16)
|
||||
0x00, // Byte 56, charge/discharge control, 0=disable, 1=enable
|
||||
0x00, // Byte 57, When SoC is 100%, seen as 0x40
|
||||
0x64, // Byte 58, SoC (uint8)
|
||||
0x00, // Byte 59, Unknown
|
||||
0x00, // Byte 60, Unknown
|
||||
0x01, // Byte 61, Unknown, 1 only at first frame, 0 otherwise
|
||||
0x00, // Byte 62, CRC
|
||||
0x00};
|
||||
// clang-format on
|
||||
|
||||
// FE 04 01 40 xx 01 01 02 yy (fully charged)
|
||||
// FE 02 01 02 xx 01 01 02 yy (charging or discharging)
|
||||
|
||||
uint8_t frame3[9] = {
|
||||
0x08, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header
|
||||
0x06, //Unknown
|
||||
uint8_t STATUS_FRAME[9] = {
|
||||
0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header
|
||||
0x06, //Unknown (battery status/error?)
|
||||
0xEF, //CRC
|
||||
0x00 //endbyte
|
||||
};
|
||||
|
||||
uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
|
||||
|
||||
uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00};
|
||||
uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
|
||||
uint8_t ACK_FRAME[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
|
||||
|
||||
uint8_t RS485_RXFRAME[300];
|
||||
|
||||
bool register_content_ok = false;
|
||||
|
||||
void float2frame(byte* arr, float value, byte framepointer) {
|
||||
static void float2frame(byte* arr, float value, byte framepointer) {
|
||||
f32b g;
|
||||
g.f = value;
|
||||
arr[framepointer] = g.b[0];
|
||||
|
@ -117,6 +127,18 @@ static void dbg_frame(byte* frame, int len, const char* prefix) {
|
|||
}
|
||||
logging.println("");
|
||||
#endif
|
||||
#ifdef DEBUG_KOSTAL_RS485_DATA_USB
|
||||
Serial.print(prefix);
|
||||
Serial.print(": ");
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
if (frame[i] < 0x10) {
|
||||
Serial.print("0");
|
||||
}
|
||||
Serial.print(frame[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void dbg_message(const char* msg) {
|
||||
|
@ -128,7 +150,7 @@ static void dbg_message(const char* msg) {
|
|||
|
||||
/* https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing#Encoding_examples */
|
||||
|
||||
void null_stuffer(byte* lfc, int len) {
|
||||
static void null_stuffer(byte* lfc, int len) {
|
||||
int last_null_byte = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (lfc[i] == '\0') {
|
||||
|
@ -143,7 +165,7 @@ static void send_kostal(byte* frame, int len) {
|
|||
Serial2.write(frame, len);
|
||||
}
|
||||
|
||||
byte calculate_kostal_crc(byte* lfc, int len) {
|
||||
static byte calculate_kostal_crc(byte* lfc, int len) {
|
||||
unsigned int sum = 0;
|
||||
if (lfc[0] != 0) {
|
||||
logging.printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]);
|
||||
|
@ -154,20 +176,20 @@ byte calculate_kostal_crc(byte* lfc, int len) {
|
|||
return (byte)(-sum & 0xff);
|
||||
}
|
||||
|
||||
byte calculate_frame1_crc(byte* lfc, int lastbyte) {
|
||||
static bool check_kostal_frame_crc(int len) {
|
||||
unsigned int sum = 0;
|
||||
for (int i = 0; i < lastbyte; ++i) {
|
||||
sum += lfc[i];
|
||||
}
|
||||
return ((byte) ~(sum - 0x28) & 0xff);
|
||||
}
|
||||
|
||||
bool check_kostal_frame_crc() {
|
||||
unsigned int sum = 0;
|
||||
for (int i = 1; i < 8; ++i) {
|
||||
int zeropointer = RS485_RXFRAME[0];
|
||||
int last_zero = 0;
|
||||
for (int i = 1; i < len - 2; ++i) {
|
||||
if (i == zeropointer + last_zero) {
|
||||
zeropointer = RS485_RXFRAME[i];
|
||||
last_zero = i;
|
||||
RS485_RXFRAME[i] = 0x00;
|
||||
}
|
||||
sum += RS485_RXFRAME[i];
|
||||
}
|
||||
if (((~sum + 1) & 0xff) == (RS485_RXFRAME[8] & 0xff)) {
|
||||
|
||||
if ((-sum & 0xff) == (RS485_RXFRAME[len - 2] & 0xff)) {
|
||||
return (true);
|
||||
} else {
|
||||
return (false);
|
||||
|
@ -184,11 +206,9 @@ void update_RS485_registers_inverter() {
|
|||
|
||||
if (datalayer.system.status.battery_allows_contactor_closing &
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
|
||||
CyclicData[0] = 0x0A;
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
|
||||
} else {
|
||||
CyclicData[0] = 0x06;
|
||||
float2frame(CyclicData, 0.0, 6);
|
||||
float2frame(CYCLIC_DATA, 0.0, 6);
|
||||
}
|
||||
// Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V)
|
||||
nominal_voltage_dV =
|
||||
|
@ -196,54 +216,75 @@ void update_RS485_registers_inverter() {
|
|||
datalayer.battery.info.min_design_voltage_dV);
|
||||
float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 6);
|
||||
|
||||
float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 10);
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.info.max_design_voltage_dV / 10, 10);
|
||||
|
||||
float2frame(CyclicData, (float)average_temperature_dC / 10, 14);
|
||||
float2frame(CYCLIC_DATA, (float)average_temperature_dC / 10, 14);
|
||||
|
||||
// Some current values causes communication error, must be resolved, why.
|
||||
// float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float)
|
||||
// float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22);
|
||||
#ifdef BMW_SBOX
|
||||
float2frame(CYCLIC_DATA, (float)(datalayer.shunt.measured_amperage_mA / 100) / 10, 18);
|
||||
float2frame(CYCLIC_DATA, (float)(datalayer.shunt.measured_avg1S_amperage_mA / 100) / 10, 22);
|
||||
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26);
|
||||
|
||||
// When SOC = 100%, drop down allowed charge current down.
|
||||
|
||||
if ((datalayer.battery.status.reported_soc / 100) < 100) {
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34);
|
||||
if (datalayer.shunt.contactors_engaged) {
|
||||
CYCLIC_DATA[59] = 0;
|
||||
} else {
|
||||
float2frame(CyclicData, 0.0, 34);
|
||||
CYCLIC_DATA[59] = 2;
|
||||
}
|
||||
|
||||
if (datalayer.shunt.precharging || datalayer.shunt.contactors_engaged) {
|
||||
CYCLIC_DATA[56] = 1;
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.max_discharge_current_dA / 10,
|
||||
26); // Maximum discharge current
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.max_charge_current_dA / 10, 34); // Maximum charge current
|
||||
} else {
|
||||
CYCLIC_DATA[56] = 0;
|
||||
float2frame(CYCLIC_DATA, 0.0, 26);
|
||||
float2frame(CYCLIC_DATA, 0.0, 34);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 18); // Last current
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s)
|
||||
|
||||
// On startup, byte 56 seems to be always 0x00 couple of frames,.
|
||||
if (f2_startup_count < 9) {
|
||||
CyclicData[56] = 0x00;
|
||||
CYCLIC_DATA[56] = 0x00;
|
||||
} else {
|
||||
CyclicData[56] = 0x01;
|
||||
CYCLIC_DATA[56] = 0x01;
|
||||
}
|
||||
|
||||
// On startup, byte 59 seems to be always 0x02 couple of frames,.
|
||||
if (f2_startup_count < 14) {
|
||||
CyclicData[59] = 0x02;
|
||||
CYCLIC_DATA[59] = 0x02;
|
||||
} else {
|
||||
CyclicData[59] = 0x00;
|
||||
CYCLIC_DATA[59] = 0x00;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26);
|
||||
|
||||
// When SoC is 100%, drop down allowed charge current.
|
||||
if ((datalayer.battery.status.reported_soc / 100) < 100) {
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.max_charge_current_dA / 10, 34);
|
||||
} else {
|
||||
float2frame(CYCLIC_DATA, 0.0, 34);
|
||||
}
|
||||
|
||||
if (nominal_voltage_dV > 0) {
|
||||
float2frame(CyclicData, (float)(datalayer.battery.info.total_capacity_Wh / nominal_voltage_dV * 10),
|
||||
30); // BAttery capacity Ah
|
||||
float2frame(CYCLIC_DATA, (float)(datalayer.battery.info.total_capacity_Wh / nominal_voltage_dV * 10),
|
||||
30); // Battery capacity Ah
|
||||
}
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38);
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.temperature_min_dC / 10, 42);
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.temperature_max_dC / 10, 38);
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.temperature_min_dC / 10, 42);
|
||||
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46);
|
||||
float2frame(CyclicData, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50);
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46);
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50);
|
||||
|
||||
CyclicData[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping
|
||||
CYCLIC_DATA[58] = (byte)(datalayer.battery.status.reported_soc / 100);
|
||||
|
||||
register_content_ok = true;
|
||||
|
||||
BATTERY_INFO[38] = calculate_frame1_crc(BATTERY_INFO, 38);
|
||||
|
||||
if (incoming_message_counter > 0) {
|
||||
incoming_message_counter--;
|
||||
}
|
||||
|
@ -255,8 +296,6 @@ void update_RS485_registers_inverter() {
|
|||
}
|
||||
}
|
||||
|
||||
static uint8_t rx_index = 0;
|
||||
|
||||
void receive_RS485() // Runs as fast as possible to handle the serial stream
|
||||
{
|
||||
currentMillis = millis();
|
||||
|
@ -265,105 +304,95 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
|
|||
contactorMillis = currentMillis;
|
||||
}
|
||||
if (currentMillis - contactorMillis >= INTERVAL_2_S & !RX_allow) {
|
||||
RX_allow = true;
|
||||
dbg_message("RX_allow -> true");
|
||||
RX_allow = true;
|
||||
}
|
||||
|
||||
if (startupMillis) {
|
||||
if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) &
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
// Disconnect allowed only, when curren zero
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||
dbg_message("inverter_allows_contactor_closing -> false");
|
||||
}
|
||||
} else if (((currentMillis - startupMillis) >= 7000) &
|
||||
datalayer.system.status.inverter_allows_contactor_closing == false) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||
dbg_message("inverter_allows_contactor_closing -> true");
|
||||
}
|
||||
}
|
||||
|
||||
if (B1_delay) {
|
||||
if ((currentMillis - B1_last_millis) > INTERVAL_1_S) {
|
||||
send_kostal(frameB1b, 8);
|
||||
B1_delay = false;
|
||||
dbg_message("B1_delay -> false");
|
||||
}
|
||||
} else if (Serial2.available()) {
|
||||
if (Serial2.available()) {
|
||||
RS485_RXFRAME[rx_index] = Serial2.read();
|
||||
if (RX_allow) {
|
||||
rx_index++;
|
||||
if (RS485_RXFRAME[rx_index - 1] == 0x00) {
|
||||
if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) {
|
||||
if ((rx_index > 9) && register_content_ok) {
|
||||
dbg_frame(RS485_RXFRAME, 10, "RX");
|
||||
rx_index = 0;
|
||||
if (check_kostal_frame_crc()) {
|
||||
if (check_kostal_frame_crc(rx_index)) {
|
||||
incoming_message_counter = RS485_HEALTHY;
|
||||
bool headerA = true;
|
||||
bool headerB = true;
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER[i]) {
|
||||
headerA = false;
|
||||
}
|
||||
if (RS485_RXFRAME[i + 1] != KOSTAL_FRAMEHEADER2[i]) {
|
||||
headerB = false;
|
||||
}
|
||||
}
|
||||
|
||||
// "frame B1", maybe reset request, seen after battery power on/partial data
|
||||
if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) {
|
||||
send_kostal(frameB1, 10);
|
||||
B1_delay = true;
|
||||
dbg_message("B1_delay -> true");
|
||||
B1_last_millis = currentMillis;
|
||||
}
|
||||
|
||||
// "frame B1", maybe reset request, seen after battery power on/partial data
|
||||
if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0x04)) {
|
||||
send_kostal(frame4, 8);
|
||||
// This needs more reverse engineering, disabled...
|
||||
}
|
||||
|
||||
if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1"
|
||||
send_kostal(BATTERY_INFO, 40);
|
||||
if (!startupMillis) {
|
||||
startupMillis = currentMillis;
|
||||
if (RS485_RXFRAME[1] == 'c') {
|
||||
if (RS485_RXFRAME[6] == 0x47) {
|
||||
// Set time function - Do nothing.
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
}
|
||||
}
|
||||
if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2"
|
||||
update_values_battery();
|
||||
update_RS485_registers_inverter();
|
||||
if (f2_startup_count < 15) {
|
||||
f2_startup_count++;
|
||||
if (RS485_RXFRAME[6] == 0x5E) {
|
||||
// Set State function
|
||||
if (RS485_RXFRAME[7] == 0x00) {
|
||||
// Allow contactor closing
|
||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||
dbg_message("inverter_allows_contactor_closing -> true");
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
} else if (RS485_RXFRAME[7] == 0x04) {
|
||||
// INVALID STATE, no ACK sent
|
||||
} else {
|
||||
// Battery deep sleep?
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
}
|
||||
}
|
||||
} else if (RS485_RXFRAME[1] == 'b') {
|
||||
if (RS485_RXFRAME[6] == 0x50) {
|
||||
//Reverse polarity, do nothing
|
||||
} else {
|
||||
int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100;
|
||||
if (code == 0x44a) {
|
||||
//Send cyclic data
|
||||
update_values_battery();
|
||||
update_RS485_registers_inverter();
|
||||
if (f2_startup_count < 15) {
|
||||
f2_startup_count++;
|
||||
}
|
||||
byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation
|
||||
memcpy(tmpframe, CYCLIC_DATA, 64);
|
||||
tmpframe[62] = calculate_kostal_crc(tmpframe, 62);
|
||||
null_stuffer(tmpframe, 64);
|
||||
send_kostal(tmpframe, 64);
|
||||
CYCLIC_DATA[61] = 0x00;
|
||||
}
|
||||
if (code == 0x84a) {
|
||||
//Send battery info
|
||||
byte tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation
|
||||
memcpy(tmpframe, BATTERY_INFO, 40);
|
||||
tmpframe[38] = calculate_kostal_crc(tmpframe, 38);
|
||||
null_stuffer(tmpframe, 40);
|
||||
send_kostal(tmpframe, 40);
|
||||
if (!startupMillis) {
|
||||
startupMillis = currentMillis;
|
||||
}
|
||||
}
|
||||
if (code == 0x353) {
|
||||
//Send battery error/status
|
||||
byte tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation
|
||||
memcpy(tmpframe, STATUS_FRAME, 9);
|
||||
tmpframe[7] = calculate_kostal_crc(tmpframe, 7);
|
||||
null_stuffer(tmpframe, 9);
|
||||
send_kostal(tmpframe, 9);
|
||||
}
|
||||
}
|
||||
byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation
|
||||
memcpy(tmpframe, CyclicData, 64);
|
||||
|
||||
tmpframe[62] = calculate_kostal_crc(tmpframe, 62);
|
||||
null_stuffer(tmpframe, 64);
|
||||
send_kostal(tmpframe, 64);
|
||||
}
|
||||
if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3"
|
||||
send_kostal(frame3, 9);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dbg_frame(RS485_RXFRAME, 10, "RX (dropped)");
|
||||
rx_index = 0;
|
||||
}
|
||||
rx_index = 0;
|
||||
}
|
||||
}
|
||||
if (rx_index >= 10) {
|
||||
dbg_frame(RS485_RXFRAME, 10, "RX (!RX_allow)");
|
||||
if (rx_index >= 299) {
|
||||
rx_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_inverter(void) { // Performs one time setup at startup
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||
dbg_message("inverter_allows_contactor_closing -> false");
|
||||
strncpy(datalayer.system.info.inverter_protocol, "BYD battery via Kostal RS485", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#define RS485_INVERTER_SELECTED
|
||||
#define RS485_BAUDRATE 57600
|
||||
//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via logging
|
||||
//#define DEBUG_KOSTAL_RS485_DATA_USB // Enable this line to get TX / RX printed out via USB
|
||||
|
||||
#if defined(DEBUG_KOSTAL_RS485_DATA) && !defined(DEBUG_LOG)
|
||||
#error "enable LOG_TO_SD, DEBUG_VIA_USB or DEBUG_VIA_WEB in order to use DEBUG_KOSTAL_RS485_DATA"
|
||||
|
|
|
@ -37,21 +37,4 @@
|
|||
*/
|
||||
#define MAX_AMOUNT_CELLS 192
|
||||
|
||||
/** LED
|
||||
*
|
||||
* Parameter: LED_MODE_DEFAULT
|
||||
* Description:
|
||||
* The default LED mode. Available modes:
|
||||
* CLASSIC - slow up/down ramp
|
||||
* FLOW - slow ramp up or down depending on flow of energy
|
||||
* HEARTBEAT - Heartbeat-like LED pattern that reacts to the system state with color and BPM
|
||||
*
|
||||
* Parameter: LED_PERIOD_MS
|
||||
* Description:
|
||||
* The period of whatever LED mode is active. If CLASSIC, then a ramp up and ramp down will finish in
|
||||
* LED_PERIOD_MS milliseconds
|
||||
*/
|
||||
#define LED_MODE_DEFAULT FLOW
|
||||
#define LED_PERIOD_MS 3000
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue