mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 02:39:57 +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
|
@ -44,6 +44,10 @@
|
||||||
#include "src/devboard/mqtt/mqtt.h"
|
#include "src/devboard/mqtt/mqtt.h"
|
||||||
#endif // MQTT
|
#endif // MQTT
|
||||||
#endif // WIFI
|
#endif // WIFI
|
||||||
|
#ifdef PERIODIC_BMS_RESET_AT
|
||||||
|
#include "src/devboard/utils/ntp_time.h"
|
||||||
|
#endif
|
||||||
|
volatile unsigned long long bmsResetTimeOffset = 0;
|
||||||
|
|
||||||
// The current software version, shown on webserver
|
// The current software version, shown on webserver
|
||||||
const char* version_number = "8.7.dev";
|
const char* version_number = "8.7.dev";
|
||||||
|
@ -52,7 +56,6 @@ const char* version_number = "8.7.dev";
|
||||||
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
||||||
unsigned long previousMillis10ms = 0;
|
unsigned long previousMillis10ms = 0;
|
||||||
unsigned long previousMillisUpdateVal = 0;
|
unsigned long previousMillisUpdateVal = 0;
|
||||||
|
|
||||||
// Task time measurement for debugging and for setting CPU load events
|
// Task time measurement for debugging and for setting CPU load events
|
||||||
int64_t core_task_time_us;
|
int64_t core_task_time_us;
|
||||||
MyTimer core_task_timer_10s(INTERVAL_10_S);
|
MyTimer core_task_timer_10s(INTERVAL_10_S);
|
||||||
|
@ -135,6 +138,14 @@ void setup() {
|
||||||
// Start tasks
|
// Start tasks
|
||||||
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, &core_task_time_us, TASK_CORE_PRIO,
|
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, &core_task_time_us, TASK_CORE_PRIO,
|
||||||
&main_loop_task, CORE_FUNCTION_CORE);
|
&main_loop_task, CORE_FUNCTION_CORE);
|
||||||
|
#ifdef PERIODIC_BMS_RESET_AT
|
||||||
|
bmsResetTimeOffset = getTimeOffsetfromNowUntil(PERIODIC_BMS_RESET_AT);
|
||||||
|
if (bmsResetTimeOffset == 0) {
|
||||||
|
set_event(EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED, 0);
|
||||||
|
} else {
|
||||||
|
set_event(EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform main program functions
|
// Perform main program functions
|
||||||
|
|
|
@ -66,3 +66,11 @@ volatile float CHARGER_MIN_HV = 200; // Min permissible output (VDC) of cha
|
||||||
volatile float CHARGER_MAX_POWER = 3300; // Max power capable of charger, as a ceiling for validating config
|
volatile float CHARGER_MAX_POWER = 3300; // Max power capable of charger, as a ceiling for validating config
|
||||||
volatile float CHARGER_MAX_A = 11.5; // Max current output (amps) of charger
|
volatile float CHARGER_MAX_A = 11.5; // Max current output (amps) of charger
|
||||||
volatile float CHARGER_END_A = 1.0; // Current at which charging is considered complete
|
volatile float CHARGER_END_A = 1.0; // Current at which charging is considered complete
|
||||||
|
|
||||||
|
#ifdef PERIODIC_BMS_RESET_AT
|
||||||
|
// A list of rules for your zone can be obtained from https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h
|
||||||
|
const char* time_zone =
|
||||||
|
"GMT0BST,M3.5.0/1,M10.5.0"; // TimeZone rule for Europe/London including daylight adjustment rules (optional)
|
||||||
|
const char* ntpServer1 = "pool.ntp.org";
|
||||||
|
const char* ntpServer2 = "time.nist.gov";
|
||||||
|
#endif
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
//#define RENAULT_ZOE_GEN2_BATTERY
|
//#define RENAULT_ZOE_GEN2_BATTERY
|
||||||
//#define SONO_BATTERY
|
//#define SONO_BATTERY
|
||||||
//#define SANTA_FE_PHEV_BATTERY
|
//#define SANTA_FE_PHEV_BATTERY
|
||||||
|
//#define SIMPBMS_BATTERY
|
||||||
//#define STELLANTIS_ECMP_BATTERY
|
//#define STELLANTIS_ECMP_BATTERY
|
||||||
//#define TESLA_MODEL_3Y_BATTERY
|
//#define TESLA_MODEL_3Y_BATTERY
|
||||||
//#define TESLA_MODEL_SX_BATTERY
|
//#define TESLA_MODEL_SX_BATTERY
|
||||||
|
@ -80,6 +81,8 @@
|
||||||
//#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting!
|
//#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting!
|
||||||
//#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF
|
//#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF
|
||||||
//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF
|
//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF
|
||||||
|
// PERIODIC_BMS_RESET_AT Uses NTP server, internet required. In 24 Hour format WITHOUT leading 0. e.g 0230 should be 230. Time Zone is set in USER_SETTINGS.cpp
|
||||||
|
#define PERIODIC_BMS_RESET_AT 525
|
||||||
|
|
||||||
/* Shunt/Contactor settings (Optional) */
|
/* Shunt/Contactor settings (Optional) */
|
||||||
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement
|
||||||
|
@ -156,6 +159,14 @@
|
||||||
// 3000 = 300.0V, Target discharge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true
|
// 3000 = 300.0V, Target discharge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true
|
||||||
#define BATTERY_MAX_DISCHARGE_VOLTAGE 3000
|
#define BATTERY_MAX_DISCHARGE_VOLTAGE 3000
|
||||||
|
|
||||||
|
/* LED settings. Optional customization for how the blinking pattern on the LED should behave.
|
||||||
|
* CLASSIC - Slow up/down ramp. If CLASSIC, then a ramp up and ramp down will finish in LED_PERIOD_MS milliseconds
|
||||||
|
* FLOW - Ramp up/down depending on flow of energy
|
||||||
|
* HEARTBEAT - Heartbeat-like LED pattern that reacts to the system state with color and BPM
|
||||||
|
*/
|
||||||
|
#define LED_MODE CLASSIC
|
||||||
|
#define LED_PERIOD_MS 3000
|
||||||
|
|
||||||
/* Do not change any code below this line */
|
/* Do not change any code below this line */
|
||||||
/* Only change battery specific settings above and in "USER_SETTINGS.cpp" */
|
/* Only change battery specific settings above and in "USER_SETTINGS.cpp" */
|
||||||
typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CANFD_ADDON_MCP2518 = 3 } CAN_Interface;
|
typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CANFD_ADDON_MCP2518 = 3 } CAN_Interface;
|
||||||
|
@ -176,6 +187,8 @@ extern volatile float CHARGER_MAX_POWER;
|
||||||
extern volatile float CHARGER_MAX_A;
|
extern volatile float CHARGER_MAX_A;
|
||||||
extern volatile float CHARGER_END_A;
|
extern volatile float CHARGER_END_A;
|
||||||
|
|
||||||
|
extern volatile unsigned long long bmsResetTimeOffset;
|
||||||
|
|
||||||
#ifdef EQUIPMENT_STOP_BUTTON
|
#ifdef EQUIPMENT_STOP_BUTTON
|
||||||
typedef enum { LATCHING_SWITCH = 0, MOMENTARY_SWITCH = 1 } STOP_BUTTON_BEHAVIOR;
|
typedef enum { LATCHING_SWITCH = 0, MOMENTARY_SWITCH = 1 } STOP_BUTTON_BEHAVIOR;
|
||||||
extern volatile STOP_BUTTON_BEHAVIOR equipment_stop_behavior;
|
extern volatile STOP_BUTTON_BEHAVIOR equipment_stop_behavior;
|
||||||
|
|
|
@ -122,6 +122,10 @@ void setup_can_shunt();
|
||||||
#include "SANTA-FE-PHEV-BATTERY.h"
|
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SIMPBMS_BATTERY
|
||||||
|
#include "SIMPBMS-BATTERY.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(TESLA_MODEL_SX_BATTERY) || defined(TESLA_MODEL_3Y_BATTERY)
|
#if defined(TESLA_MODEL_SX_BATTERY) || defined(TESLA_MODEL_3Y_BATTERY)
|
||||||
#define TESLA_BATTERY
|
#define TESLA_BATTERY
|
||||||
#include "TESLA-BATTERY.h"
|
#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_shortterm_discharge = 0;
|
||||||
static uint32_t battery_BEV_available_power_longterm_charge = 0;
|
static uint32_t battery_BEV_available_power_longterm_charge = 0;
|
||||||
static uint32_t battery_BEV_available_power_longterm_discharge = 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_display_SOC = 0;
|
||||||
static uint16_t battery_volts = 0;
|
static uint16_t battery_volts = 0;
|
||||||
static uint16_t battery_HVBatt_SOC = 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_shortterm_discharge = 0;
|
||||||
static uint32_t battery2_BEV_available_power_longterm_charge = 0;
|
static uint32_t battery2_BEV_available_power_longterm_charge = 0;
|
||||||
static uint32_t battery2_BEV_available_power_longterm_discharge = 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_display_SOC = 0;
|
||||||
static uint16_t battery2_volts = 0;
|
static uint16_t battery2_volts = 0;
|
||||||
static uint16_t battery2_HVBatt_SOC = 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.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;
|
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.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;
|
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_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_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_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;
|
battery_energy_content_maximum_Wh = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]) * 20;
|
||||||
if (battery_energy_content_maximum_kWh > 33) {
|
if (battery_energy_content_maximum_Wh > 33000) {
|
||||||
detectedBattery = BATTERY_120AH;
|
detectedBattery = BATTERY_120AH;
|
||||||
} else if (battery_energy_content_maximum_kWh > 20) {
|
} else if (battery_energy_content_maximum_Wh > 20000) {
|
||||||
detectedBattery = BATTERY_94AH;
|
detectedBattery = BATTERY_94AH;
|
||||||
} else {
|
} else {
|
||||||
detectedBattery = BATTERY_60AH;
|
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_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_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_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;
|
battery2_energy_content_maximum_Wh = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]) * 20;
|
||||||
if (battery2_energy_content_maximum_kWh > 33) {
|
if (battery2_energy_content_maximum_Wh > 33000) {
|
||||||
detectedBattery2 = BATTERY_120AH;
|
detectedBattery2 = BATTERY_120AH;
|
||||||
} else if (battery2_energy_content_maximum_kWh > 20) {
|
} else if (battery2_energy_content_maximum_Wh > 20000) {
|
||||||
detectedBattery2 = BATTERY_94AH;
|
detectedBattery2 = BATTERY_94AH;
|
||||||
} else {
|
} else {
|
||||||
detectedBattery2 = BATTERY_60AH;
|
detectedBattery2 = BATTERY_60AH;
|
||||||
|
|
|
@ -36,6 +36,9 @@ static int16_t BMS_highest_cell_temperature = 0;
|
||||||
static int16_t BMS_average_cell_temperature = 0;
|
static int16_t BMS_average_cell_temperature = 0;
|
||||||
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
|
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
|
||||||
static uint16_t BMS_highest_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
|
#ifdef DOUBLE_BATTERY
|
||||||
static int16_t battery2_temperature_ambient = 0;
|
static int16_t battery2_temperature_ambient = 0;
|
||||||
static int16_t battery2_daughterboard_temperatures[10];
|
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 int16_t BMS2_average_cell_temperature = 0;
|
||||||
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
|
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
|
||||||
static uint16_t BMS2_highest_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
|
#endif //DOUBLE_BATTERY
|
||||||
#define POLL_FOR_BATTERY_SOC 0x05
|
#define POLL_FOR_BATTERY_SOC 0x05
|
||||||
#define POLL_FOR_BATTERY_VOLTAGE 0x08
|
#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;
|
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
|
#ifdef SKIP_TEMPERATURE_SENSOR_NUMBER
|
||||||
// Initialize min and max variables for temperature calculation
|
// Initialize min and max variables for temperature calculation
|
||||||
battery_calc_min_temperature = battery_daughterboard_temperatures[0];
|
battery_calc_min_temperature = battery_daughterboard_temperatures[0];
|
||||||
|
@ -253,6 +261,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
|
||||||
break;
|
break;
|
||||||
case 0x43D:
|
case 0x43D:
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
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;
|
break;
|
||||||
case 0x444:
|
case 0x444:
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
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_min_dC = BMS2_lowest_cell_temperature * 10; // Add decimals
|
||||||
|
|
||||||
datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10;
|
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) {
|
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;
|
break;
|
||||||
case 0x43D:
|
case 0x43D:
|
||||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
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;
|
break;
|
||||||
case 0x444:
|
case 0x444:
|
||||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
|
@ -1075,7 +1075,7 @@ void transmit_can_battery() {
|
||||||
|
|
||||||
unsigned long currentMillis = millis();
|
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
|
// Transmitting towards battery is halted while BMS is being reset
|
||||||
// Reset sending counters to avoid overrun messages when reset is over
|
// Reset sending counters to avoid overrun messages when reset is over
|
||||||
previousMillis10 = currentMillis;
|
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
|
#endif
|
||||||
unsigned long currentTime = 0;
|
unsigned long currentTime = 0;
|
||||||
unsigned long lastPowerRemovalTime = 0;
|
unsigned long lastPowerRemovalTime = 0;
|
||||||
|
unsigned long bmsPowerOnTime = 0;
|
||||||
const unsigned long powerRemovalInterval = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
const unsigned long powerRemovalInterval = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||||
const unsigned long powerRemovalDuration = 30000; // 30 seconds 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) {
|
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
|
||||||
#ifdef PWM_CONTACTOR_CONTROL
|
#ifdef PWM_CONTACTOR_CONTROL
|
||||||
|
@ -249,23 +251,29 @@ void handle_BMSpower() {
|
||||||
|
|
||||||
#ifdef PERIODIC_BMS_RESET
|
#ifdef PERIODIC_BMS_RESET
|
||||||
// Check if 24 hours have passed since the last power removal
|
// Check if 24 hours have passed since the last power removal
|
||||||
if (currentTime - lastPowerRemovalTime >= powerRemovalInterval) {
|
if ((currentTime + bmsResetTimeOffset) - lastPowerRemovalTime >= powerRemovalInterval) {
|
||||||
start_bms_reset();
|
start_bms_reset();
|
||||||
}
|
}
|
||||||
#endif //PERIODIC_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) {
|
if (datalayer.system.status.BMS_reset_in_progress && currentTime - lastPowerRemovalTime >= powerRemovalDuration) {
|
||||||
// Reapply power to the BMS
|
// Reapply power to the BMS
|
||||||
digitalWrite(BMS_POWER, HIGH);
|
digitalWrite(BMS_POWER, HIGH);
|
||||||
#ifdef BMS_2_POWER
|
#ifdef BMS_2_POWER
|
||||||
digitalWrite(BMS_2_POWER, HIGH); // Same for battery 2
|
digitalWrite(BMS_2_POWER, HIGH); // Same for battery 2
|
||||||
#endif
|
#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);
|
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)
|
#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 defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET)
|
||||||
if (!datalayer.system.status.BMS_reset_in_progress) {
|
if (!datalayer.system.status.BMS_reset_in_progress) {
|
||||||
lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started
|
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.
|
// 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;
|
datalayer.system.status.BMS_reset_in_progress = true;
|
||||||
|
|
||||||
// Set emulator state to paused (Max Charge/Discharge = 0 & CAN = stop)
|
// 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 */
|
/** The current battery status, which for now has the name real_bms_status */
|
||||||
real_bms_status_enum real_bms_status = BMS_DISCONNECTED;
|
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;
|
} DATALAYER_BATTERY_STATUS_TYPE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -288,6 +292,8 @@ typedef struct {
|
||||||
#endif
|
#endif
|
||||||
/** True if the BMS is being reset, by cutting power towards it */
|
/** True if the BMS is being reset, by cutting power towards it */
|
||||||
bool BMS_reset_in_progress = false;
|
bool BMS_reset_in_progress = false;
|
||||||
|
/** True if the BMS is starting up */
|
||||||
|
bool BMS_startup_in_progress = false;
|
||||||
#ifdef PRECHARGE_CONTROL
|
#ifdef PRECHARGE_CONTROL
|
||||||
/** State of automatic precharge sequence */
|
/** State of automatic precharge sequence */
|
||||||
PrechargeState precharge_status = AUTO_PRECHARGE_IDLE;
|
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_MQTT_DISCONNECT].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
|
||||||
|
events.entries[EVENT_PERIODIC_BMS_RESET].level = EVENT_LEVEL_INFO;
|
||||||
|
events.entries[EVENT_PERIODIC_BMS_RESET_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_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
|
||||||
|
|
||||||
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
|
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!!!";
|
return "EQUIPMENT STOP ACTIVATED!!!";
|
||||||
case EVENT_SD_INIT_FAILED:
|
case EVENT_SD_INIT_FAILED:
|
||||||
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
|
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
|
||||||
|
case EVENT_PERIODIC_BMS_RESET:
|
||||||
|
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:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,99 +25,102 @@
|
||||||
* - Increment EE_MAGIC_HEADER_VALUE in case you've changed the order
|
* - Increment EE_MAGIC_HEADER_VALUE in case you've changed the order
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define EVENTS_ENUM_TYPE(XX) \
|
#define EVENTS_ENUM_TYPE(XX) \
|
||||||
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
|
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
|
||||||
XX(EVENT_CANMCP2515_INIT_FAILURE) \
|
XX(EVENT_CANMCP2515_INIT_FAILURE) \
|
||||||
XX(EVENT_CANFD_BUFFER_FULL) \
|
XX(EVENT_CANFD_BUFFER_FULL) \
|
||||||
XX(EVENT_CAN_BUFFER_FULL) \
|
XX(EVENT_CAN_BUFFER_FULL) \
|
||||||
XX(EVENT_CAN_OVERRUN) \
|
XX(EVENT_CAN_OVERRUN) \
|
||||||
XX(EVENT_CAN_CORRUPTED_WARNING) \
|
XX(EVENT_CAN_CORRUPTED_WARNING) \
|
||||||
XX(EVENT_CAN_BATTERY_MISSING) \
|
XX(EVENT_CAN_BATTERY_MISSING) \
|
||||||
XX(EVENT_CAN_BATTERY2_MISSING) \
|
XX(EVENT_CAN_BATTERY2_MISSING) \
|
||||||
XX(EVENT_CAN_CHARGER_MISSING) \
|
XX(EVENT_CAN_CHARGER_MISSING) \
|
||||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||||
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
|
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
|
||||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||||
XX(EVENT_CONTACTOR_WELDED) \
|
XX(EVENT_CONTACTOR_WELDED) \
|
||||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||||
XX(EVENT_WATER_INGRESS) \
|
XX(EVENT_WATER_INGRESS) \
|
||||||
XX(EVENT_12V_LOW) \
|
XX(EVENT_12V_LOW) \
|
||||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||||
XX(EVENT_SOC_UNAVAILABLE) \
|
XX(EVENT_SOC_UNAVAILABLE) \
|
||||||
XX(EVENT_STALE_VALUE) \
|
XX(EVENT_STALE_VALUE) \
|
||||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||||
XX(EVENT_BALANCING_START) \
|
XX(EVENT_BALANCING_START) \
|
||||||
XX(EVENT_BALANCING_END) \
|
XX(EVENT_BALANCING_END) \
|
||||||
XX(EVENT_BATTERY_EMPTY) \
|
XX(EVENT_BATTERY_EMPTY) \
|
||||||
XX(EVENT_BATTERY_FULL) \
|
XX(EVENT_BATTERY_FULL) \
|
||||||
XX(EVENT_BATTERY_FUSE) \
|
XX(EVENT_BATTERY_FUSE) \
|
||||||
XX(EVENT_BATTERY_FROZEN) \
|
XX(EVENT_BATTERY_FROZEN) \
|
||||||
XX(EVENT_BATTERY_CAUTION) \
|
XX(EVENT_BATTERY_CAUTION) \
|
||||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||||
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
||||||
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
||||||
XX(EVENT_BATTERY_OVERHEAT) \
|
XX(EVENT_BATTERY_OVERHEAT) \
|
||||||
XX(EVENT_BATTERY_OVERVOLTAGE) \
|
XX(EVENT_BATTERY_OVERVOLTAGE) \
|
||||||
XX(EVENT_BATTERY_UNDERVOLTAGE) \
|
XX(EVENT_BATTERY_UNDERVOLTAGE) \
|
||||||
XX(EVENT_BATTERY_VALUE_UNAVAILABLE) \
|
XX(EVENT_BATTERY_VALUE_UNAVAILABLE) \
|
||||||
XX(EVENT_BATTERY_ISOLATION) \
|
XX(EVENT_BATTERY_ISOLATION) \
|
||||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||||
XX(EVENT_BATTERY_WARMED_UP) \
|
XX(EVENT_BATTERY_WARMED_UP) \
|
||||||
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
||||||
XX(EVENT_SOH_DIFFERENCE) \
|
XX(EVENT_SOH_DIFFERENCE) \
|
||||||
XX(EVENT_SOH_LOW) \
|
XX(EVENT_SOH_LOW) \
|
||||||
XX(EVENT_HVIL_FAILURE) \
|
XX(EVENT_HVIL_FAILURE) \
|
||||||
XX(EVENT_PRECHARGE_FAILURE) \
|
XX(EVENT_PRECHARGE_FAILURE) \
|
||||||
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
||||||
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
|
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
|
||||||
XX(EVENT_INTERFACE_MISSING) \
|
XX(EVENT_INTERFACE_MISSING) \
|
||||||
XX(EVENT_MODBUS_INVERTER_MISSING) \
|
XX(EVENT_MODBUS_INVERTER_MISSING) \
|
||||||
XX(EVENT_ERROR_OPEN_CONTACTOR) \
|
XX(EVENT_ERROR_OPEN_CONTACTOR) \
|
||||||
XX(EVENT_CELL_CRITICAL_UNDER_VOLTAGE) \
|
XX(EVENT_CELL_CRITICAL_UNDER_VOLTAGE) \
|
||||||
XX(EVENT_CELL_CRITICAL_OVER_VOLTAGE) \
|
XX(EVENT_CELL_CRITICAL_OVER_VOLTAGE) \
|
||||||
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
||||||
XX(EVENT_CELL_OVER_VOLTAGE) \
|
XX(EVENT_CELL_OVER_VOLTAGE) \
|
||||||
XX(EVENT_CELL_DEVIATION_HIGH) \
|
XX(EVENT_CELL_DEVIATION_HIGH) \
|
||||||
XX(EVENT_UNKNOWN_EVENT_SET) \
|
XX(EVENT_UNKNOWN_EVENT_SET) \
|
||||||
XX(EVENT_OTA_UPDATE) \
|
XX(EVENT_OTA_UPDATE) \
|
||||||
XX(EVENT_OTA_UPDATE_TIMEOUT) \
|
XX(EVENT_OTA_UPDATE_TIMEOUT) \
|
||||||
XX(EVENT_DUMMY_INFO) \
|
XX(EVENT_DUMMY_INFO) \
|
||||||
XX(EVENT_DUMMY_DEBUG) \
|
XX(EVENT_DUMMY_DEBUG) \
|
||||||
XX(EVENT_DUMMY_WARNING) \
|
XX(EVENT_DUMMY_WARNING) \
|
||||||
XX(EVENT_DUMMY_ERROR) \
|
XX(EVENT_DUMMY_ERROR) \
|
||||||
XX(EVENT_PERSISTENT_SAVE_INFO) \
|
XX(EVENT_PERSISTENT_SAVE_INFO) \
|
||||||
XX(EVENT_SERIAL_RX_WARNING) \
|
XX(EVENT_SERIAL_RX_WARNING) \
|
||||||
XX(EVENT_SERIAL_RX_FAILURE) \
|
XX(EVENT_SERIAL_RX_FAILURE) \
|
||||||
XX(EVENT_SERIAL_TX_FAILURE) \
|
XX(EVENT_SERIAL_TX_FAILURE) \
|
||||||
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
|
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
|
||||||
XX(EVENT_EEPROM_WRITE) \
|
XX(EVENT_EEPROM_WRITE) \
|
||||||
XX(EVENT_RESET_UNKNOWN) \
|
XX(EVENT_RESET_UNKNOWN) \
|
||||||
XX(EVENT_RESET_POWERON) \
|
XX(EVENT_RESET_POWERON) \
|
||||||
XX(EVENT_RESET_EXT) \
|
XX(EVENT_RESET_EXT) \
|
||||||
XX(EVENT_RESET_SW) \
|
XX(EVENT_RESET_SW) \
|
||||||
XX(EVENT_RESET_PANIC) \
|
XX(EVENT_RESET_PANIC) \
|
||||||
XX(EVENT_RESET_INT_WDT) \
|
XX(EVENT_RESET_INT_WDT) \
|
||||||
XX(EVENT_RESET_TASK_WDT) \
|
XX(EVENT_RESET_TASK_WDT) \
|
||||||
XX(EVENT_RESET_WDT) \
|
XX(EVENT_RESET_WDT) \
|
||||||
XX(EVENT_RESET_DEEPSLEEP) \
|
XX(EVENT_RESET_DEEPSLEEP) \
|
||||||
XX(EVENT_RESET_BROWNOUT) \
|
XX(EVENT_RESET_BROWNOUT) \
|
||||||
XX(EVENT_RESET_SDIO) \
|
XX(EVENT_RESET_SDIO) \
|
||||||
XX(EVENT_RESET_USB) \
|
XX(EVENT_RESET_USB) \
|
||||||
XX(EVENT_RESET_JTAG) \
|
XX(EVENT_RESET_JTAG) \
|
||||||
XX(EVENT_RESET_EFUSE) \
|
XX(EVENT_RESET_EFUSE) \
|
||||||
XX(EVENT_RESET_PWR_GLITCH) \
|
XX(EVENT_RESET_PWR_GLITCH) \
|
||||||
XX(EVENT_RESET_CPU_LOCKUP) \
|
XX(EVENT_RESET_CPU_LOCKUP) \
|
||||||
XX(EVENT_RJXZS_LOG) \
|
XX(EVENT_RJXZS_LOG) \
|
||||||
XX(EVENT_PAUSE_BEGIN) \
|
XX(EVENT_PAUSE_BEGIN) \
|
||||||
XX(EVENT_PAUSE_END) \
|
XX(EVENT_PAUSE_END) \
|
||||||
XX(EVENT_WIFI_CONNECT) \
|
XX(EVENT_WIFI_CONNECT) \
|
||||||
XX(EVENT_WIFI_DISCONNECT) \
|
XX(EVENT_WIFI_DISCONNECT) \
|
||||||
XX(EVENT_MQTT_CONNECT) \
|
XX(EVENT_MQTT_CONNECT) \
|
||||||
XX(EVENT_MQTT_DISCONNECT) \
|
XX(EVENT_MQTT_DISCONNECT) \
|
||||||
XX(EVENT_EQUIPMENT_STOP) \
|
XX(EVENT_EQUIPMENT_STOP) \
|
||||||
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
|
||||||
XX(EVENT_SD_INIT_FAILED) \
|
XX(EVENT_SD_INIT_FAILED) \
|
||||||
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
|
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)
|
XX(EVENT_NOF_EVENTS)
|
||||||
|
|
||||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
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_peak2 = 0.55;
|
||||||
static const float heartbeat_deviation = 0.05;
|
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) {
|
void led_init(void) {
|
||||||
led.init();
|
led.init();
|
||||||
|
@ -31,14 +31,14 @@ led_color led_get_color() {
|
||||||
void LED::exe(void) {
|
void LED::exe(void) {
|
||||||
|
|
||||||
// Update brightness
|
// Update brightness
|
||||||
switch (mode) {
|
switch (datalayer.battery.status.led_mode) {
|
||||||
case led_mode::FLOW:
|
case led_mode_enum::FLOW:
|
||||||
flow_run();
|
flow_run();
|
||||||
break;
|
break;
|
||||||
case led_mode::HEARTBEAT:
|
case led_mode_enum::HEARTBEAT:
|
||||||
heartbeat_run();
|
heartbeat_run();
|
||||||
break;
|
break;
|
||||||
case led_mode::CLASSIC:
|
case led_mode_enum::CLASSIC:
|
||||||
default:
|
default:
|
||||||
classic_run();
|
classic_run();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
#include "../../include.h"
|
#include "../../include.h"
|
||||||
#include "../../lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h"
|
#include "../../lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h"
|
||||||
|
|
||||||
enum led_mode { CLASSIC, FLOW, HEARTBEAT };
|
|
||||||
|
|
||||||
class LED {
|
class LED {
|
||||||
public:
|
public:
|
||||||
led_color color = led_color::GREEN;
|
led_color color = led_color::GREEN;
|
||||||
|
@ -14,9 +12,9 @@ class LED {
|
||||||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||||
max_brightness(LED_MAX_BRIGHTNESS),
|
max_brightness(LED_MAX_BRIGHTNESS),
|
||||||
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),
|
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||||
max_brightness(LED_MAX_BRIGHTNESS),
|
max_brightness(LED_MAX_BRIGHTNESS),
|
||||||
brightness(LED_MAX_BRIGHTNESS),
|
brightness(LED_MAX_BRIGHTNESS),
|
||||||
|
@ -29,7 +27,7 @@ class LED {
|
||||||
Adafruit_NeoPixel pixels;
|
Adafruit_NeoPixel pixels;
|
||||||
uint8_t max_brightness;
|
uint8_t max_brightness;
|
||||||
uint8_t brightness;
|
uint8_t brightness;
|
||||||
led_mode mode;
|
led_mode_enum mode;
|
||||||
|
|
||||||
void classic_run(void);
|
void classic_run(void);
|
||||||
void flow_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 real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 };
|
||||||
enum battery_chemistry_enum { NCA, NMC, LFP };
|
enum battery_chemistry_enum { NCA, NMC, LFP };
|
||||||
enum led_color { GREEN, YELLOW, RED, BLUE };
|
enum led_color { GREEN, YELLOW, RED, BLUE };
|
||||||
|
enum led_mode_enum { CLASSIC, FLOW, HEARTBEAT };
|
||||||
enum PrechargeState {
|
enum PrechargeState {
|
||||||
AUTO_PRECHARGE_IDLE,
|
AUTO_PRECHARGE_IDLE,
|
||||||
AUTO_PRECHARGE_START,
|
AUTO_PRECHARGE_START,
|
||||||
|
|
|
@ -20,6 +20,7 @@ static unsigned long currentMillis;
|
||||||
static unsigned long startupMillis = 0;
|
static unsigned long startupMillis = 0;
|
||||||
static unsigned long contactorMillis = 0;
|
static unsigned long contactorMillis = 0;
|
||||||
|
|
||||||
|
static uint16_t rx_index = 0;
|
||||||
static boolean RX_allow = false;
|
static boolean RX_allow = false;
|
||||||
|
|
||||||
union f32b {
|
union f32b {
|
||||||
|
@ -27,66 +28,75 @@ union f32b {
|
||||||
byte b[4];
|
byte b[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
uint8_t BATTERY_INFO[40] = {
|
uint8_t BATTERY_INFO[40] = {
|
||||||
0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
0x00, // First zero byte pointer
|
||||||
0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
|
0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
|
||||||
0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16
|
0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256
|
||||||
0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527
|
0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac])
|
||||||
0x01, 0x05, 0xC8, 0x41, // 25.0024 ?
|
0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0
|
||||||
0xC2, 0x18, // Battery Firmware, modbus register 586
|
0x00, 0x00, 0xC8, 0x41, // Nominal Capacity (0x10b4)
|
||||||
0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ??
|
0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8)
|
||||||
0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02,
|
0x00, // Static (BYD: GetBatteryInfo this[0x10ba])
|
||||||
0x4D, // CRC
|
0x00, // ?
|
||||||
0x00}; //
|
0x59, 0x42, // Vendor identifier
|
||||||
|
// 0x59 0x42 -> 'YB' -> BYD
|
||||||
// values in CyclicData will be overwritten at update_modbus_registers_inverter()
|
// 0x59 0x44 -> 'YD' -> Dyness
|
||||||
|
0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be])
|
||||||
uint8_t CyclicData[64] = {
|
0x00, 0x00,
|
||||||
0x00, // First zero byte pointer
|
0x05, 0x00, // Number of blocks in series (uint16)
|
||||||
0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header
|
0xA0, 0x00, 0x00, 0x00,
|
||||||
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9
|
0x4D, // CRC
|
||||||
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};
|
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 04 01 40 xx 01 01 02 yy (fully charged)
|
||||||
// FE 02 01 02 xx 01 01 02 yy (charging or discharging)
|
// FE 02 01 02 xx 01 01 02 yy (charging or discharging)
|
||||||
|
|
||||||
uint8_t frame3[9] = {
|
uint8_t STATUS_FRAME[9] = {
|
||||||
0x08, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header
|
0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, //header
|
||||||
0x06, //Unknown
|
0x06, //Unknown (battery status/error?)
|
||||||
0xEF, //CRC
|
0xEF, //CRC
|
||||||
0x00 //endbyte
|
0x00 //endbyte
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
|
uint8_t ACK_FRAME[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 RS485_RXFRAME[300];
|
uint8_t RS485_RXFRAME[300];
|
||||||
|
|
||||||
bool register_content_ok = false;
|
bool register_content_ok = false;
|
||||||
|
|
||||||
void float2frame(byte* arr, float value, byte framepointer) {
|
static void float2frame(byte* arr, float value, byte framepointer) {
|
||||||
f32b g;
|
f32b g;
|
||||||
g.f = value;
|
g.f = value;
|
||||||
arr[framepointer] = g.b[0];
|
arr[framepointer] = g.b[0];
|
||||||
|
@ -117,6 +127,18 @@ static void dbg_frame(byte* frame, int len, const char* prefix) {
|
||||||
}
|
}
|
||||||
logging.println("");
|
logging.println("");
|
||||||
#endif
|
#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) {
|
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 */
|
/* 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;
|
int last_null_byte = 0;
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
if (lfc[i] == '\0') {
|
if (lfc[i] == '\0') {
|
||||||
|
@ -143,7 +165,7 @@ static void send_kostal(byte* frame, int len) {
|
||||||
Serial2.write(frame, 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;
|
unsigned int sum = 0;
|
||||||
if (lfc[0] != 0) {
|
if (lfc[0] != 0) {
|
||||||
logging.printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[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);
|
return (byte)(-sum & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte calculate_frame1_crc(byte* lfc, int lastbyte) {
|
static bool check_kostal_frame_crc(int len) {
|
||||||
unsigned int sum = 0;
|
unsigned int sum = 0;
|
||||||
for (int i = 0; i < lastbyte; ++i) {
|
int zeropointer = RS485_RXFRAME[0];
|
||||||
sum += lfc[i];
|
int last_zero = 0;
|
||||||
}
|
for (int i = 1; i < len - 2; ++i) {
|
||||||
return ((byte) ~(sum - 0x28) & 0xff);
|
if (i == zeropointer + last_zero) {
|
||||||
}
|
zeropointer = RS485_RXFRAME[i];
|
||||||
|
last_zero = i;
|
||||||
bool check_kostal_frame_crc() {
|
RS485_RXFRAME[i] = 0x00;
|
||||||
unsigned int sum = 0;
|
}
|
||||||
for (int i = 1; i < 8; ++i) {
|
|
||||||
sum += RS485_RXFRAME[i];
|
sum += RS485_RXFRAME[i];
|
||||||
}
|
}
|
||||||
if (((~sum + 1) & 0xff) == (RS485_RXFRAME[8] & 0xff)) {
|
|
||||||
|
if ((-sum & 0xff) == (RS485_RXFRAME[len - 2] & 0xff)) {
|
||||||
return (true);
|
return (true);
|
||||||
} else {
|
} else {
|
||||||
return (false);
|
return (false);
|
||||||
|
@ -184,11 +206,9 @@ void update_RS485_registers_inverter() {
|
||||||
|
|
||||||
if (datalayer.system.status.battery_allows_contactor_closing &
|
if (datalayer.system.status.battery_allows_contactor_closing &
|
||||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||||
float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
|
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
|
||||||
CyclicData[0] = 0x0A;
|
|
||||||
} else {
|
} else {
|
||||||
CyclicData[0] = 0x06;
|
float2frame(CYCLIC_DATA, 0.0, 6);
|
||||||
float2frame(CyclicData, 0.0, 6);
|
|
||||||
}
|
}
|
||||||
// Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V)
|
// Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V)
|
||||||
nominal_voltage_dV =
|
nominal_voltage_dV =
|
||||||
|
@ -196,54 +216,75 @@ void update_RS485_registers_inverter() {
|
||||||
datalayer.battery.info.min_design_voltage_dV);
|
datalayer.battery.info.min_design_voltage_dV);
|
||||||
float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 6);
|
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.
|
#ifdef BMW_SBOX
|
||||||
// float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float)
|
float2frame(CYCLIC_DATA, (float)(datalayer.shunt.measured_amperage_mA / 100) / 10, 18);
|
||||||
// float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22);
|
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);
|
if (datalayer.shunt.contactors_engaged) {
|
||||||
|
CYCLIC_DATA[59] = 0;
|
||||||
// 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);
|
|
||||||
} else {
|
} 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,.
|
// On startup, byte 56 seems to be always 0x00 couple of frames,.
|
||||||
if (f2_startup_count < 9) {
|
if (f2_startup_count < 9) {
|
||||||
CyclicData[56] = 0x00;
|
CYCLIC_DATA[56] = 0x00;
|
||||||
} else {
|
} else {
|
||||||
CyclicData[56] = 0x01;
|
CYCLIC_DATA[56] = 0x01;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On startup, byte 59 seems to be always 0x02 couple of frames,.
|
// On startup, byte 59 seems to be always 0x02 couple of frames,.
|
||||||
if (f2_startup_count < 14) {
|
if (f2_startup_count < 14) {
|
||||||
CyclicData[59] = 0x02;
|
CYCLIC_DATA[59] = 0x02;
|
||||||
} else {
|
} 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) {
|
if (nominal_voltage_dV > 0) {
|
||||||
float2frame(CyclicData, (float)(datalayer.battery.info.total_capacity_Wh / nominal_voltage_dV * 10),
|
float2frame(CYCLIC_DATA, (float)(datalayer.battery.info.total_capacity_Wh / nominal_voltage_dV * 10),
|
||||||
30); // BAttery capacity Ah
|
30); // Battery capacity Ah
|
||||||
}
|
}
|
||||||
float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38);
|
float2frame(CYCLIC_DATA, (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_min_dC / 10, 42);
|
||||||
|
|
||||||
float2frame(CyclicData, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46);
|
float2frame(CYCLIC_DATA, (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_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;
|
register_content_ok = true;
|
||||||
|
|
||||||
BATTERY_INFO[38] = calculate_frame1_crc(BATTERY_INFO, 38);
|
|
||||||
|
|
||||||
if (incoming_message_counter > 0) {
|
if (incoming_message_counter > 0) {
|
||||||
incoming_message_counter--;
|
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
|
void receive_RS485() // Runs as fast as possible to handle the serial stream
|
||||||
{
|
{
|
||||||
currentMillis = millis();
|
currentMillis = millis();
|
||||||
|
@ -265,105 +304,95 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
|
||||||
contactorMillis = currentMillis;
|
contactorMillis = currentMillis;
|
||||||
}
|
}
|
||||||
if (currentMillis - contactorMillis >= INTERVAL_2_S & !RX_allow) {
|
if (currentMillis - contactorMillis >= INTERVAL_2_S & !RX_allow) {
|
||||||
RX_allow = true;
|
|
||||||
dbg_message("RX_allow -> true");
|
dbg_message("RX_allow -> true");
|
||||||
|
RX_allow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startupMillis) {
|
if (Serial2.available()) {
|
||||||
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()) {
|
|
||||||
RS485_RXFRAME[rx_index] = Serial2.read();
|
RS485_RXFRAME[rx_index] = Serial2.read();
|
||||||
if (RX_allow) {
|
if (RX_allow) {
|
||||||
rx_index++;
|
rx_index++;
|
||||||
if (RS485_RXFRAME[rx_index - 1] == 0x00) {
|
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");
|
dbg_frame(RS485_RXFRAME, 10, "RX");
|
||||||
rx_index = 0;
|
if (check_kostal_frame_crc(rx_index)) {
|
||||||
if (check_kostal_frame_crc()) {
|
|
||||||
incoming_message_counter = RS485_HEALTHY;
|
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 (RS485_RXFRAME[1] == 'c') {
|
||||||
if (headerB && (RS485_RXFRAME[6] == 0x5E) && (RS485_RXFRAME[7] == 0xFF)) {
|
if (RS485_RXFRAME[6] == 0x47) {
|
||||||
send_kostal(frameB1, 10);
|
// Set time function - Do nothing.
|
||||||
B1_delay = true;
|
send_kostal(ACK_FRAME, 8); // ACK
|
||||||
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[6] == 0x5E) {
|
||||||
if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x04)) { // "frame 2"
|
// Set State function
|
||||||
update_values_battery();
|
if (RS485_RXFRAME[7] == 0x00) {
|
||||||
update_RS485_registers_inverter();
|
// Allow contactor closing
|
||||||
if (f2_startup_count < 15) {
|
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||||
f2_startup_count++;
|
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 {
|
rx_index = 0;
|
||||||
dbg_frame(RS485_RXFRAME, 10, "RX (dropped)");
|
|
||||||
}
|
}
|
||||||
rx_index = 0;
|
rx_index = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rx_index >= 10) {
|
if (rx_index >= 299) {
|
||||||
dbg_frame(RS485_RXFRAME, 10, "RX (!RX_allow)");
|
|
||||||
rx_index = 0;
|
rx_index = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_inverter(void) { // Performs one time setup at startup
|
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);
|
strncpy(datalayer.system.info.inverter_protocol, "BYD battery via Kostal RS485", 63);
|
||||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#define RS485_INVERTER_SELECTED
|
#define RS485_INVERTER_SELECTED
|
||||||
#define RS485_BAUDRATE 57600
|
#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 // 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)
|
#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"
|
#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
|
#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
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue