mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
Merge branch 'main' into feature/double-battery
This commit is contained in:
commit
1f295871b9
111 changed files with 6451 additions and 1698 deletions
|
@ -4,55 +4,79 @@
|
|||
#include "../../USER_SETTINGS.h"
|
||||
|
||||
#ifdef BMW_I3_BATTERY
|
||||
#include "BMW-I3-BATTERY.h" //See this file for more i3 battery settings
|
||||
#include "BMW-I3-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
#include "BYD-ATTO-3-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#include "CHADEMO-BATTERY.h" //See this file for more Chademo settings
|
||||
#include "CHADEMO-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
#include "IMIEV-CZERO-ION-BATTERY.h" //See this file for more triplet battery settings
|
||||
#include "IMIEV-CZERO-ION-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
#include "JAGUAR-IPACE-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
#include "KIA-E-GMP-BATTERY.h" //See this file for more GMP battery settings
|
||||
#include "KIA-E-GMP-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
#include "KIA-HYUNDAI-64-BATTERY.h" //See this file for more 64kWh battery settings
|
||||
#include "KIA-HYUNDAI-64-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef MG_5_BATTERY
|
||||
#include "MG-5-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef NISSAN_LEAF_BATTERY
|
||||
#include "NISSAN-LEAF-BATTERY.h" //See this file for more LEAF battery settings
|
||||
#include "NISSAN-LEAF-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef PYLON_BATTERY
|
||||
#include "PYLON-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
#include "RENAULT-KANGOO-BATTERY.h" //See this file for more Kangoo battery settings
|
||||
#include "RENAULT-KANGOO-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
#include "RENAULT-ZOE-BATTERY.h" //See this file for more Zoe battery settings
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SANTA_FE_PHEV_BATTERY
|
||||
#include "SANTA-FE-PHEV-BATTERY.h" //See this file for more Santa Fe PHEV battery settings
|
||||
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef TESLA_MODEL_3_BATTERY
|
||||
#include "TESLA-MODEL-3-BATTERY.h" //See this file for more Tesla battery settings
|
||||
#include "TESLA-MODEL-3-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
#include "TEST-FAKE-BATTERY.h" //See this file for more Fake battery settings
|
||||
#include "TEST-FAKE-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef VOLVO_SPA_BATTERY
|
||||
#include "VOLVO-SPA-BATTERY.h" //See this file for more XC40 Recharge/Polestar2 settings
|
||||
#include "VOLVO-SPA-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_LINK_RECEIVER
|
||||
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" //See this file for more Serial-battery settings
|
||||
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_LINK_RECEIVER // The serial thing does its thing
|
||||
|
|
|
@ -15,11 +15,12 @@ static unsigned long previousMillis640 = 0; // will store last time a 600ms C
|
|||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
|
||||
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
|
||||
static uint8_t CAN2stillAlive = 12; // counter for checking if CAN2 is still alive
|
||||
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
|
||||
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
||||
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
|
||||
static BatterySize detectedBattery = BATTERY_60AH;
|
||||
|
||||
enum CmdState { SOH, CELL_VOLTAGE, SOC, CELL_VOLTAGE_AVG };
|
||||
static CmdState cmdState = SOH;
|
||||
|
||||
|
@ -318,6 +319,7 @@ static uint8_t BMW_380_counter = 0;
|
|||
static uint32_t BMW_328_counter = 0;
|
||||
static bool battery_awake = false;
|
||||
static bool battery2_awake = false;
|
||||
static bool battery_info_available = false;
|
||||
|
||||
static uint32_t battery_serial_number = 0;
|
||||
static uint32_t battery_available_power_shortterm_charge = 0;
|
||||
|
@ -386,7 +388,7 @@ static uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
|
|||
static uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
|
||||
static uint8_t battery_ID2 = 0;
|
||||
static uint8_t battery_cellvoltage_mux = 0;
|
||||
static uint8_t battery_soh = 0;
|
||||
static uint8_t battery_soh = 99;
|
||||
|
||||
static uint32_t battery2_serial_number = 0;
|
||||
static uint32_t battery2_available_power_shortterm_charge = 0;
|
||||
|
@ -488,6 +490,9 @@ void CAN_WriteFrame(CAN_frame_t* tx_frame) {
|
|||
}
|
||||
|
||||
void update_values_battery2() { //This function maps all the values fetched via CAN2 to the battery2 datalayer
|
||||
if (!battery2_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
datalayer.battery2.status.real_soc = (battery2_HVBatt_SOC * 10);
|
||||
|
||||
|
@ -520,23 +525,12 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery2.status.cell_min_voltage_mV = datalayer.battery2.status.cell_voltages_mV[0];
|
||||
datalayer.battery2.status.cell_max_voltage_mV = datalayer.battery2.status.cell_voltages_mV[1];
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CAN2stillAlive) {
|
||||
set_event(EVENT_CAN2_RX_FAILURE, 2);
|
||||
datalayer.battery2.status.bms_status = FAULT; //TODO: Refactor handling of event for battery2
|
||||
datalayer.system.status.battery2_allows_contactor_closing = false;
|
||||
} else {
|
||||
CAN2stillAlive--;
|
||||
clear_event(EVENT_CAN2_RX_FAILURE);
|
||||
}
|
||||
// Check if we have encountered any malformed CAN messages
|
||||
if (CANerror > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||
if (!battery_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
datalayer.battery.status.real_soc = (battery_HVBatt_SOC * 10);
|
||||
|
||||
|
@ -548,16 +542,9 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.soh_pptt = battery_soh * 100;
|
||||
|
||||
if (battery_BEV_available_power_longterm_discharge > 65000) {
|
||||
datalayer.battery.status.max_discharge_power_W = 65000;
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
|
||||
}
|
||||
if (battery_BEV_available_power_longterm_charge > 65000) {
|
||||
datalayer.battery.status.max_charge_power_W = 65000;
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
|
||||
|
||||
battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
|
||||
|
@ -567,19 +554,53 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = datalayer.battery.status.cell_voltages_mV[0];
|
||||
datalayer.battery.status.cell_max_voltage_mV = datalayer.battery.status.cell_voltages_mV[1];
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
if (datalayer.battery.status.cell_voltages_mV[0] > 0 && datalayer.battery.status.cell_voltages_mV[2] > 0) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = datalayer.battery.status.cell_voltages_mV[0];
|
||||
datalayer.battery.status.cell_max_voltage_mV = datalayer.battery.status.cell_voltages_mV[2];
|
||||
}
|
||||
// Check if we have encountered any malformed CAN messages
|
||||
if (CANerror > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
|
||||
if (battery_info_available) {
|
||||
// Start checking safeties. First up, cellvoltages!
|
||||
if (detectedBattery == BATTERY_60AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_60AH) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_60AH) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
} else if (detectedBattery == BATTERY_94AH) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_94AH) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_94AH) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
} else { // BATTERY_120AH
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
|
||||
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_120AH) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_120AH) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform other safety checks
|
||||
if (battery_status_error_locking == 2) { // HVIL seated?
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
if (battery_status_precharge_locked == 2) { // Capacitor seated?
|
||||
set_event(EVENT_PRECHARGE_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_PRECHARGE_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -612,7 +633,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery_awake = true;
|
||||
CANstillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
datalayer.battery.status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info
|
||||
|
@ -650,7 +672,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
battery_awake = true;
|
||||
if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
//If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break;
|
||||
}
|
||||
battery_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
|
||||
|
@ -689,18 +711,6 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2
|
||||
battery_status_cooling_HV = (rx_frame.data.u8[1] & 0x03);
|
||||
break;
|
||||
case 0x426: // TODO: Figure out how to trigger sending of this. Does the SME require some CAN command?
|
||||
battery_cellvoltage_mux = rx_frame.data.u8[0];
|
||||
if (battery_cellvoltage_mux == 0) {
|
||||
datalayer.battery.status.cell_voltages_mV[0] = ((rx_frame.data.u8[1] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[2] = ((rx_frame.data.u8[3] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800);
|
||||
datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800);
|
||||
}
|
||||
break;
|
||||
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2
|
||||
battery_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
|
||||
battery_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
|
||||
|
@ -714,6 +724,13 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
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 > 37) {
|
||||
detectedBattery = BATTERY_120AH;
|
||||
} else if (battery_energy_content_maximum_kWh > 25) {
|
||||
detectedBattery = BATTERY_94AH;
|
||||
} else {
|
||||
detectedBattery = BATTERY_60AH;
|
||||
}
|
||||
break;
|
||||
case 0x432: //BMS [200ms] SOC% info
|
||||
battery_request_operating_mode = (rx_frame.data.u8[0] & 0x03);
|
||||
|
@ -758,6 +775,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
case SOH:
|
||||
if (next_data >= 4) {
|
||||
battery_soh = message_data[3];
|
||||
battery_info_available = true;
|
||||
}
|
||||
break;
|
||||
case SOC:
|
||||
|
@ -778,7 +796,8 @@ void receive_can_battery2(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||
battery2_awake = true;
|
||||
CAN2stillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
datalayer.battery2.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
||||
battery2_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
|
||||
battery2_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
|
||||
datalayer.battery2.status.voltage_dV =
|
||||
|
@ -817,7 +836,7 @@ void receive_can_battery2(CAN_frame_t rx_frame) {
|
|||
battery2_awake = true;
|
||||
if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||
//If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||
CANerror++;
|
||||
datalayer.battery2.status.CAN_error_counter++;
|
||||
break;
|
||||
}
|
||||
battery2_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
|
||||
|
@ -950,6 +969,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis20 >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis20 = currentMillis;
|
||||
|
||||
|
@ -1093,6 +1114,38 @@ void send_can_battery() {
|
|||
|
||||
BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this
|
||||
BMW_3E8.data.u8[0] = 0xF1; // First 3E8 message byte0 we send is unique, once we sent initial value send this
|
||||
|
||||
next_data = 0;
|
||||
switch (cmdState) {
|
||||
case SOC:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_CELL);
|
||||
#endif
|
||||
cmdState = CELL_VOLTAGE;
|
||||
break;
|
||||
case CELL_VOLTAGE:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_SOH);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_SOH);
|
||||
#endif
|
||||
cmdState = SOH;
|
||||
break;
|
||||
case SOH:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL_VOLTAGE_AVG);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_CELL_VOLTAGE_AVG);
|
||||
#endif
|
||||
cmdState = CELL_VOLTAGE_AVG;
|
||||
break;
|
||||
case CELL_VOLTAGE_AVG:
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_SOC);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_SOC);
|
||||
#endif
|
||||
cmdState = SOC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Send 5000ms CAN Message
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
|
@ -1137,14 +1190,17 @@ void send_can_battery() {
|
|||
CAN_WriteFrame(&BMW_37B);
|
||||
#endif
|
||||
|
||||
next_data = 0;
|
||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL);
|
||||
#ifdef DOUBLE_BATTERY
|
||||
CAN_WriteFrame(&BMW_6F1_CELL);
|
||||
#endif
|
||||
|
||||
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
|
||||
}
|
||||
} else {
|
||||
previousMillis20 = currentMillis;
|
||||
previousMillis100 = currentMillis;
|
||||
previousMillis200 = currentMillis;
|
||||
previousMillis500 = currentMillis;
|
||||
previousMillis640 = currentMillis;
|
||||
previousMillis1000 = currentMillis;
|
||||
previousMillis5000 = currentMillis;
|
||||
previousMillis10000 = currentMillis;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1153,9 +1209,9 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
Serial.println("BMW i3 battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||
datalayer.battery.info.min_design_voltage_dV = 2800; // 280.0V under this, discharging further is disabled
|
||||
//Before we have started up and detected which battery is in use, use 60AH values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
|
|
|
@ -10,6 +10,19 @@ extern ACAN2515 can;
|
|||
#define BATTERY_SELECTED
|
||||
|
||||
#define WUP_PIN 25
|
||||
#define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_94AH 2700 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_VOLTAGE_120AH 4190 // Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_120AH 2790 // Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION_MV 250 // LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_60AH 3950 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_60AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_94AH 3980 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
||||
|
|
367
Software/src/battery/BYD-ATTO-3-BATTERY.cpp
Normal file
367
Software/src/battery/BYD-ATTO-3-BATTERY.cpp
Normal file
|
@ -0,0 +1,367 @@
|
|||
#include "../include.h"
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "BYD-ATTO-3-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
- Get contactor closing working
|
||||
- NOTE: Some packs can be locked hard? after a crash has occured. Bypassing contactors manually might be required?
|
||||
- Figure out which CAN messages need to be sent towards the battery to keep it alive
|
||||
-Maybe already enough with 0x12D and 0x411? Plus the PID polls might keep it alive.
|
||||
- Map all values from battery CAN messages
|
||||
-SOC% still not found (Lets take it from PID poll, not working right yet)
|
||||
-SOC% is now ESTIMATED. This is bad, and should be fixed as soon as possible with the real value from CAN
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static uint8_t counter_50ms = 0;
|
||||
static uint8_t counter_100ms = 0;
|
||||
static uint8_t frame6_counter = 0xB;
|
||||
static uint8_t frame7_counter = 0x5;
|
||||
|
||||
static int16_t temperature_ambient = 0;
|
||||
static int16_t daughterboard_temperatures[10];
|
||||
static int16_t lowest_temperature = 0;
|
||||
static int16_t highest_temperature = 0;
|
||||
static int16_t calc_min_temperature = 0;
|
||||
static int16_t calc_max_temperature = 0;
|
||||
|
||||
static uint16_t BMS_SOC = 0;
|
||||
static uint16_t BMS_voltage = 0;
|
||||
static int16_t BMS_current = 0;
|
||||
static int16_t BMS_lowest_cell_temperature = 0;
|
||||
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;
|
||||
|
||||
#define POLL_FOR_BATTERY_SOC 0x05
|
||||
#define POLL_FOR_BATTERY_VOLTAGE 0x08
|
||||
#define POLL_FOR_BATTERY_CURRENT 0x09
|
||||
#define POLL_FOR_LOWEST_TEMP_CELL 0x2f
|
||||
#define POLL_FOR_HIGHEST_TEMP_CELL 0x31
|
||||
#define POLL_FOR_BATTERY_PACK_AVG_TEMP 0x32
|
||||
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
|
||||
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
|
||||
#define UNKNOWN_POLL_1 0xFC
|
||||
|
||||
CAN_frame_t ATTO_3_12D = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x12D,
|
||||
.data = {0xA0, 0x28, 0x02, 0xA0, 0x0C, 0x71, 0xCF, 0x49}};
|
||||
CAN_frame_t ATTO_3_411 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x411,
|
||||
.data = {0x98, 0x3A, 0x88, 0x13, 0x9D, 0x00, 0xFF, 0x8C}};
|
||||
|
||||
CAN_frame_t ATTO_3_7E7_POLL = {
|
||||
.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E7,
|
||||
.data = {0x03, 0x22, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 00 05 (POLL_FOR_BATTERY_SOC)
|
||||
|
||||
// Define the data points for %SOC depending on pack voltage
|
||||
const uint8_t numPoints = 14;
|
||||
const uint16_t SOC[numPoints] = {10000, 9970, 9490, 8470, 7750, 6790, 5500, 4900, 3910, 3000, 2280, 1600, 480, 0};
|
||||
const uint16_t voltage[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130,
|
||||
4121, 4119, 4100, 4070, 4030, 3950, 3800};
|
||||
|
||||
uint16_t estimateSOC(uint16_t packVoltage) { // Linear interpolation function
|
||||
if (packVoltage >= voltage[0]) {
|
||||
return SOC[0];
|
||||
}
|
||||
if (packVoltage <= voltage[numPoints - 1]) {
|
||||
return SOC[numPoints - 1];
|
||||
}
|
||||
|
||||
for (int i = 1; i < numPoints; ++i) {
|
||||
if (packVoltage >= voltage[i]) {
|
||||
double t = (packVoltage - voltage[i]) / (voltage[i - 1] - voltage[i]);
|
||||
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
|
||||
}
|
||||
}
|
||||
return 0; // Default return for safety, should never reach here
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
||||
datalayer.battery.status.voltage_dV = BMS_voltage * 10;
|
||||
|
||||
//datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found!
|
||||
// We instead estimate the SOC% based on the battery voltage
|
||||
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
|
||||
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
|
||||
|
||||
datalayer.battery.status.current_dA = -BMS_current;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.active_power_W =
|
||||
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
|
||||
|
||||
// Initialize min and max variables
|
||||
calc_min_temperature = daughterboard_temperatures[0];
|
||||
calc_max_temperature = daughterboard_temperatures[0];
|
||||
|
||||
// Loop through the array of daughterboard temps to find the smallest and largest values
|
||||
for (int i = 1; i < 10; i++) {
|
||||
if (daughterboard_temperatures[i] < calc_min_temperature) {
|
||||
calc_min_temperature = daughterboard_temperatures[i];
|
||||
}
|
||||
if (daughterboard_temperatures[i] > calc_max_temperature) {
|
||||
calc_max_temperature = daughterboard_temperatures[i];
|
||||
}
|
||||
}
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = calc_min_temperature * 10; // Add decimals
|
||||
datalayer.battery.status.temperature_max_dC = calc_max_temperature * 10;
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) { //Log values taken with 422V from battery
|
||||
case 0x244: //00,00,00,04,41,0F,20,8B - Static, values never changes between logs
|
||||
break;
|
||||
case 0x245: //01,00,02,19,3A,25,90,F4 Seems to have a mux in frame0
|
||||
//02,00,90,01,79,79,90,EA // Point of interest, went from 7E,75 to 7B,7C when discharging
|
||||
//03,C6,88,12,FD,48,90,5C
|
||||
//04,00,FF,FF,00,00,90,6D
|
||||
if (rx_frame.data.u8[0] == 0x01) {
|
||||
temperature_ambient = (rx_frame.data.u8[4] - 40); // TODO, check if this is actually temperature_ambient
|
||||
}
|
||||
break;
|
||||
case 0x286: //01,FF,FF,FF,FF,FF,FF,04 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x334: //FF,FF,FF,FC,3F,00,F0,D7 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x338: //01,52,02,00,88,13,00,0F
|
||||
//01,51,02,00,88,13,00,10 407.5V 18deg
|
||||
//01,4F,02,00,88,13,00,12 408.5V 14deg
|
||||
break;
|
||||
case 0x344: //00,52,02,CC,1F,FF,04,BD
|
||||
break;
|
||||
case 0x345: //27,0B,00,00,00,E0,01,EC - Static, values never changes between logs
|
||||
break;
|
||||
case 0x347: //FF,00,00,F9,FF,FF,FF,0A - Static, values never changes between logs
|
||||
break;
|
||||
case 0x34A: //00,52,02,CC,1F,FF,04,BD
|
||||
//00,51,02,CC,1F,FF,04,BE //407.5V 18deg
|
||||
//00,4F,02,CC,1F,FF,04,C0 //408.5V 14deg
|
||||
break;
|
||||
case 0x35E: //01,00,C8,32,00,63,00,A1 - Flickering between A0 and A1, Could be temperature?
|
||||
//01,00,64,01,10,63,00,26 //407.5V 18deg
|
||||
//01,00,64,1C,10,63,00,0B //408.5V 14deg
|
||||
break;
|
||||
case 0x360: //30,19,DE,D1,0B,C3,4B,EE - Static, values never changes between logs, Last and first byte has F-0 counters
|
||||
break;
|
||||
case 0x36C: //01,57,13,DC,08,70,17,29 Seems to have a mux in frame0 , first message is static, never changes between logs
|
||||
//02,03,DC,05,C0,0F,0F,3B - Static, values never changes between logs
|
||||
//03,86,01,40,06,5C,02,D1 - Static, values never changes between logs
|
||||
//04,57,13,73,04,01,FF,1A - Static, values never changes between logs
|
||||
//05,FF,FF,FF,FF,FF,FF,00 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x438: //55,55,01,F6,47,2E,10,D9 - 0x10D9 = 4313
|
||||
//55,55,01,F6,47,FD,0F,0B //407.5V 18deg
|
||||
//55,55,01,F6,47,15,10,F2 //408.5V 14deg
|
||||
break;
|
||||
case 0x43A: //7E,0A,B0,1C,63,E1,03,64
|
||||
//7E,0A,E0,1E,63,E1,03,32 //407.5V 18deg
|
||||
//7E,0A,66,1C,63,E1,03,AE //408.5V 14deg
|
||||
break;
|
||||
case 0x43B: //01,3B,06,39,FF,64,64,BD
|
||||
//01,3B,06,38,FF,64,64,BE
|
||||
break;
|
||||
case 0x43C: // Daughterboard temperatures reside in this CAN message
|
||||
if (rx_frame.data.u8[0] == 0x00) {
|
||||
daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
|
||||
daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
|
||||
daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
|
||||
daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
|
||||
daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
|
||||
daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x01) {
|
||||
daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
|
||||
daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
|
||||
daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
|
||||
daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
|
||||
}
|
||||
break;
|
||||
case 0x43D: //Varies a lot
|
||||
break;
|
||||
case 0x444: //9E,01,88,13,64,64,98,65
|
||||
//9A,01,B6,13,64,64,98,3B //407.5V 18deg
|
||||
//9B,01,B8,13,64,64,98,38 //408.5V 14deg
|
||||
break;
|
||||
case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs
|
||||
break;
|
||||
case 0x446: //2C,D4,0C,4D,21,DC,0C,9D - 0,1,7th frame varies a lot
|
||||
break;
|
||||
case 0x447: // Seems to contain more temperatures, highest and lowest?
|
||||
//06,38,01,3B,E0,03,39,69
|
||||
//06,36,02,36,E0,03,36,72,
|
||||
lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
|
||||
highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
|
||||
break;
|
||||
case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs
|
||||
break;
|
||||
case 0x524: //24,40,00,00,00,00,00,9B - Static, values never changes between logs
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
|
||||
//This message transmits every 5?seconds. Seems like suitable place to poll for a PID
|
||||
ESP32Can.CANWriteFrame(&ATTO_3_7E7_POLL);
|
||||
|
||||
switch (ATTO_3_7E7_POLL.data.u8[3]) {
|
||||
case POLL_FOR_BATTERY_SOC:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_VOLTAGE:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CURRENT;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CURRENT:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_LOWEST_TEMP_CELL;
|
||||
break;
|
||||
case POLL_FOR_LOWEST_TEMP_CELL:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_HIGHEST_TEMP_CELL;
|
||||
break;
|
||||
case POLL_FOR_HIGHEST_TEMP_CELL:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_PACK_AVG_TEMP;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CELL_MV_MAX;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MAX:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_CELL_MV_MIN;
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MIN:
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
|
||||
break;
|
||||
default: //Something went wrong with logic, request voltage
|
||||
ATTO_3_7E7_POLL.data.u8[3] = POLL_FOR_BATTERY_VOLTAGE;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x7EF: //OBD2 PID reply from battery
|
||||
switch (rx_frame.data.u8[3]) {
|
||||
case POLL_FOR_BATTERY_SOC:
|
||||
BMS_SOC = rx_frame.data.u8[4];
|
||||
break;
|
||||
case POLL_FOR_BATTERY_VOLTAGE:
|
||||
BMS_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CURRENT:
|
||||
BMS_current = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) - 5000;
|
||||
break;
|
||||
case POLL_FOR_LOWEST_TEMP_CELL:
|
||||
BMS_lowest_cell_temperature = (rx_frame.data.u8[4] - 40);
|
||||
break;
|
||||
case POLL_FOR_HIGHEST_TEMP_CELL:
|
||||
BMS_highest_cell_temperature = (rx_frame.data.u8[4] - 40);
|
||||
break;
|
||||
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
|
||||
BMS_average_cell_temperature = (rx_frame.data.u8[4] - 40);
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MAX:
|
||||
BMS_highest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
|
||||
break;
|
||||
case POLL_FOR_BATTERY_CELL_MV_MIN:
|
||||
BMS_lowest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
|
||||
break;
|
||||
default: //Unrecognized reply
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
//Send 50ms message
|
||||
if (currentMillis - previousMillis50 >= INTERVAL_50_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis50 >= INTERVAL_50_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis50));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis50 = currentMillis;
|
||||
|
||||
counter_50ms++;
|
||||
if (counter_50ms > 23) {
|
||||
ATTO_3_12D.data.u8[2] = 0x00; // Goes from 02->00
|
||||
ATTO_3_12D.data.u8[3] = 0x22; // Goes from A0->22
|
||||
ATTO_3_12D.data.u8[5] = 0x31; // Goes from 71->31
|
||||
// TODO: handle more variations after more seconds have passed if needed
|
||||
}
|
||||
|
||||
// Update the counters in frame 6 & 7 (they are not in sync)
|
||||
if (frame6_counter == 0x0) {
|
||||
frame6_counter = 0xF; // Reset to 0xF after reaching 0x0
|
||||
} else {
|
||||
frame6_counter--; // Decrement the counter
|
||||
}
|
||||
if (frame7_counter == 0x0) {
|
||||
frame7_counter = 0xF; // Reset to 0xF after reaching 0x0
|
||||
} else {
|
||||
frame7_counter--; // Decrement the counter
|
||||
}
|
||||
|
||||
ATTO_3_12D.data.u8[6] = (0x0F | (frame6_counter << 4));
|
||||
ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4));
|
||||
|
||||
ESP32Can.CANWriteFrame(&ATTO_3_12D);
|
||||
}
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
counter_100ms++;
|
||||
|
||||
if (counter_100ms > 3) {
|
||||
ATTO_3_411.data.u8[5] = 0x01;
|
||||
ATTO_3_411.data.u8[7] = 0xF5;
|
||||
}
|
||||
|
||||
ESP32Can.CANWriteFrame(&ATTO_3_411);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BYD Atto 3 battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4410; // Over this charging is not possible
|
||||
datalayer.battery.info.min_design_voltage_dV = 3800; // Under this discharging is disabled
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/BYD-ATTO-3-BATTERY.h
Normal file
12
Software/src/battery/BYD-ATTO-3-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef ATTO_3_BATTERY_H
|
||||
#define ATTO_3_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
233
Software/src/battery/CHADEMO-BATTERY-INTERNAL.h
Normal file
233
Software/src/battery/CHADEMO-BATTERY-INTERNAL.h
Normal file
|
@ -0,0 +1,233 @@
|
|||
#ifndef CHADEMO_BATTERY_TYPES_H
|
||||
#define CHADEMO_BATTERY_TYPES_H
|
||||
|
||||
#define MAX_EVSE_POWER_CHARGING 3300
|
||||
#define MAX_EVSE_OUTPUT_VOLTAGE 410
|
||||
#define MAX_EVSE_OUTPUT_CURRENT 11
|
||||
|
||||
enum CHADEMO_STATE {
|
||||
CHADEMO_FAULT,
|
||||
CHADEMO_STOP,
|
||||
CHADEMO_IDLE,
|
||||
CHADEMO_CONNECTED,
|
||||
CHADEMO_INIT, // intermediate state indicating CAN from Vehicle not yet received after connection
|
||||
CHADEMO_NEGOTIATE,
|
||||
CHADEMO_EV_ALLOWED,
|
||||
CHADEMO_EVSE_PREPARE,
|
||||
CHADEMO_EVSE_START,
|
||||
CHADEMO_EVSE_CONTACTORS_ENABLED,
|
||||
CHADEMO_POWERFLOW,
|
||||
};
|
||||
|
||||
enum Mode { CHADEMO_CHARGE, CHADEMO_DISCHARGE, CHADEMO_BIDIRECTIONAL };
|
||||
|
||||
/* Charge/discharge sequence, indicating applicable V2H guideline
|
||||
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
|
||||
* V2H 1.1 is assumed, which..is somehow between 0x0 and 0x1 ? TODO: better understanding here
|
||||
* Use CHADEMO_seq to decide whether emitting 209 is necessary
|
||||
* 0x0 1.0 and earlier
|
||||
* 0x1 2.0 appendix A
|
||||
* 0x2 2.0 appendix B
|
||||
* TODO: is this influenced by x109->CHADEMO_protocol_number, or x102->ControlProtocolNumberEV ??
|
||||
* Unused for now.
|
||||
uint8_t CHADEMO_seq = 0x0;
|
||||
*/
|
||||
|
||||
/*----------- CHARGING SUPPORT V2X --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
|
||||
//H100 - Vehicle - Minimum charging expectations
|
||||
//TODO decide whether default values for vehicle-origin frames is even appropriate
|
||||
struct x100_Vehicle_Charging_Limits {
|
||||
uint8_t MinimumChargeCurrent = 0;
|
||||
uint16_t MinimumBatteryVoltage = 300;
|
||||
uint16_t MaximumBatteryVoltage = 402;
|
||||
uint8_t ConstantOfChargingRateIndication = 0;
|
||||
};
|
||||
//H101 - Vehicle - Maximum charging expectations
|
||||
struct x101_Vehicle_Charging_Estimate {
|
||||
uint8_t MaxChargingTime10sBit = 0;
|
||||
uint8_t MaxChargingTime1minBit = 0;
|
||||
uint8_t EstimatedChargingTime = 0;
|
||||
uint16_t RatedBatteryCapacity = 0;
|
||||
};
|
||||
|
||||
//H102 - Vehicle - Charging targets and Status
|
||||
// peer to x109 from EVSE
|
||||
// termination triggers in both
|
||||
// TODO see also Table A.26—Charge control termination command patterns
|
||||
struct x102_Vehicle_Charging_Session { //Frame byte
|
||||
uint8_t ControlProtocolNumberEV = 0; // 0
|
||||
uint16_t TargetBatteryVoltage = 0; // 1-2
|
||||
uint8_t ChargingCurrentRequest = 0; // 3 Note: per spec, units for this changed from kWh --> %
|
||||
|
||||
union {
|
||||
uint8_t faults;
|
||||
struct {
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool FaultBatteryVoltageDeviation : 1; // 4
|
||||
bool FaultHighBatteryTemperature : 1; // 3
|
||||
bool FaultBatteryCurrentDeviation : 1; // 2
|
||||
bool FaultBatteryUnderVoltage : 1; // 1
|
||||
bool FaultBatteryOverVoltage : 1; // 0
|
||||
} fault;
|
||||
} f;
|
||||
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool StatusVehicleDischargeCompatible : 1; //5.7
|
||||
bool unused_2 : 1; //5.6
|
||||
bool unused_1 : 1; //5.5
|
||||
bool StatusNormalStopRequest : 1; //5.4
|
||||
bool StatusVehicle : 1; //5.3
|
||||
bool StatusChargingError : 1; //5.2
|
||||
bool StatusVehicleShifterPosition : 1; //5.1
|
||||
bool StatusVehicleChargingEnabled : 1; //5.0 - bit zero is TODO. Vehicle charging enabled ==1 *AND* charge
|
||||
// permission signal k needs to be active for charging to be
|
||||
// permitted -- TODO document bits per byte for these flags
|
||||
// and update variables to be more appropriate
|
||||
} status;
|
||||
} s;
|
||||
|
||||
uint8_t StateOfCharge = 0; //6 state of charge?
|
||||
};
|
||||
|
||||
/* ---------- CHARGING: EVSE Data structures */
|
||||
struct x108_EVSE_Capabilities { // Frame byte
|
||||
bool contactor_weld_detection = 1; // 0
|
||||
uint16_t available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE; // 1,2
|
||||
uint8_t available_output_current = MAX_EVSE_OUTPUT_CURRENT; // 3
|
||||
uint16_t threshold_voltage = 297; // 4,5 voltage that EVSE will stop if car fails to
|
||||
// perhaps vehicle minus 3%, hardcoded initially to 96*2.95
|
||||
// 6,7 = unused
|
||||
};
|
||||
|
||||
/* Does double duty for charging and discharging */
|
||||
struct x109_EVSE_Status { // Frame byte
|
||||
uint8_t CHADEMO_protocol_number = 0x02; // 0
|
||||
uint16_t setpoint_HV_VDC =
|
||||
0; // 1,2 NOTE: charger_setpoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
|
||||
uint8_t setpoint_HV_IDC = 0; // 3
|
||||
//
|
||||
bool discharge_compatible = true; // 4, bit 0. bits
|
||||
// 4, bit 7-6 (?) unused. spec typo? maybe 1-7 unused
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool EVSE_status : 1; // 5, bit 0
|
||||
bool EVSE_error : 1; // 5, bit 1
|
||||
bool connector_locked : 1; // 5, bit 2 //NOTE: treated as connector_lock during discharge, but
|
||||
// seen as 'energizing' during charging mode
|
||||
|
||||
bool battery_incompatible : 1; // 5, bit 3
|
||||
bool ChgDischError : 1; // 5, bit 4
|
||||
|
||||
bool ChgDischStopControl : 1; // 5, bit 5 - set to false for initialization to indicate 'preparing to charge'
|
||||
// set to false when ready to charge/discharge
|
||||
|
||||
} status;
|
||||
} s;
|
||||
|
||||
// Either, or; not both.
|
||||
// seconds field set to 0xFF by default
|
||||
// indicating only the minutes field is used instead
|
||||
// BOTH observed initially set to 0xFF in logs, so use
|
||||
// that as the initialzed value
|
||||
uint8_t remaining_time_10s = 0xFF; // 6
|
||||
uint8_t remaining_time_1m = 0xFF; // 7
|
||||
};
|
||||
|
||||
/*----------- DISCHARGING SUPPORT V2X --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
//H200 - Vehicle - Discharge limits
|
||||
struct x200_Vehicle_Discharge_Limits {
|
||||
uint8_t MaximumDischargeCurrent = 0xFF;
|
||||
uint16_t MinimumDischargeVoltage = 0;
|
||||
uint16_t MinimumBatteryDischargeLevel = 0;
|
||||
uint16_t MaxRemainingCapacityForCharging = 0;
|
||||
};
|
||||
|
||||
/* TODO When charge/discharge sequence control number (ID201/209) is not received, the vehicle or the EVSE
|
||||
should determine that the other is the EVSE or the vehicle of the model before the V2H guideline 1.1. */
|
||||
//H201 - Vehicle - Estimated capacity available
|
||||
// Intended primarily for display purposes.
|
||||
// Peer to H209
|
||||
// NOTE: in available CAN logs from a Leaf, 209 is sent with no 201 reply, so < 1.1 must be the inferred version
|
||||
struct x201_Vehicle_Discharge_Estimate {
|
||||
uint8_t V2HchargeDischargeSequenceNum = 0;
|
||||
uint16_t ApproxDischargeCompletionTime = 0;
|
||||
uint16_t AvailableVehicleEnergy = 0;
|
||||
};
|
||||
|
||||
/* ---------- EVSE Data structures */
|
||||
struct x208_EVSE_Discharge_Capability { // Frame byte
|
||||
uint8_t present_discharge_current = 0xFF; // 0
|
||||
uint16_t available_input_voltage = 500; // 1,2 -- poorly named as both 'available' and minimum input voltage
|
||||
uint16_t available_input_current = 250; // 3 -- poorly named as both 'available' and maximum input current
|
||||
// spec idiosyncracy in naming/description
|
||||
// 4,5 = unused
|
||||
uint16_t lower_threshold_voltage = 0; // 6,7
|
||||
};
|
||||
|
||||
// H209 - EVSE - Estimated Discharge Duration
|
||||
// peer to Vehicle's 201 event (Note: 209 seen
|
||||
// in CAN logs even when 201 is not)
|
||||
struct x209_EVSE_Discharge_Estimate { // Frame byte
|
||||
uint8_t sequence_control_number = 0x2; // 0
|
||||
uint16_t remaining_discharge_time = 0x0000; // 0x0000 == unused
|
||||
};
|
||||
|
||||
/*----------- DYNAMIC CONTROL SUPPORT --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
struct x110_Vehicle_Dynamic_Control { //Frame byte
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool PermissionResetMaxChgTime : 1; // bit 5 or 6? is this only x118 not x110?
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
|
||||
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
|
||||
// rate of change is -20A/s to 20A/s relative to 102.3
|
||||
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
|
||||
} status;
|
||||
} u;
|
||||
};
|
||||
|
||||
/* ---------- EVSE Data structures */
|
||||
// TODO 118
|
||||
//H118
|
||||
//see also table a.59 page 104 IEEE
|
||||
struct x118_EVSE_Dynamic_Control { // Frame byte
|
||||
union {
|
||||
uint8_t packed;
|
||||
struct {
|
||||
bool PermissionResetMaxChgTime : 1; // bit 5 or 6?
|
||||
bool unused_3 : 1;
|
||||
bool unused_2 : 1;
|
||||
bool unused_1 : 1;
|
||||
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
|
||||
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
|
||||
// rate of change is -20A/s to 20A/s relative to 102.3
|
||||
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
|
||||
} status;
|
||||
} u;
|
||||
};
|
||||
|
||||
/*----------- MANUFACTURER ID SUPPORT --------------------------------------------------------------*/
|
||||
/* ---------- VEHICLE Data structures */
|
||||
//H700 - Vehicle - Manufacturer identification
|
||||
//Peer to H708
|
||||
//Used to adapt to manufacturer-prescribed optional specification
|
||||
struct x700_Vehicle_Vendor_ID {
|
||||
uint8_t AutomakerCode = 0; // 0 = set to 0x0 to indicate incompatibility. Best as a starting place
|
||||
uint8_t OptionalContent = 0; // 1-7, variable per vendor spec
|
||||
};
|
||||
|
||||
void handle_chademo_sequence();
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,14 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
//Contactor control is required for CHADEMO support
|
||||
#define CONTACTOR_CONTROL
|
||||
|
||||
//ISA shunt is currently required for CHADEMO support
|
||||
// other measurement sources may be added in the future
|
||||
#define ISA_SHUNT
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
354
Software/src/battery/CHADEMO-SHUNTS.cpp
Normal file
354
Software/src/battery/CHADEMO-SHUNTS.cpp
Normal file
|
@ -0,0 +1,354 @@
|
|||
/* Portions of this file are an adaptation of the SimpleISA library, originally authored by Jack Rickard.
|
||||
*
|
||||
* At present, this code supports the Scale IVT Modular current/voltage sensor device.
|
||||
* These devices measure current, up to three voltages, and provide temperature compensation.
|
||||
* Additional sensors are planned to provide flexibility/lower BOM costs.
|
||||
*
|
||||
* Original license/copyright header of SimpleISA is shown below:
|
||||
* This library was written by Jack Rickard of EVtv - http://www.evtv.me
|
||||
* copyright 2014
|
||||
* You are licensed to use this library for any purpose, commercial or private,
|
||||
* without restriction.
|
||||
*
|
||||
* 2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
|
||||
*
|
||||
*/
|
||||
#include "../include.h"
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "CHADEMO-BATTERY-INTERNAL.h"
|
||||
#include "CHADEMO-BATTERY.h"
|
||||
#include "CHADEMO-SHUNTS.h"
|
||||
|
||||
/* Initial frames received from ISA shunts provide invalid during initialization */
|
||||
static int framecount = 0;
|
||||
|
||||
/* original variables/names/types from SimpleISA. These warrant refinement */
|
||||
float Amperes; // Floating point with current in Amperes
|
||||
double AH; //Floating point with accumulated ampere-hours
|
||||
double KW;
|
||||
double KWH;
|
||||
|
||||
double Voltage;
|
||||
double Voltage1;
|
||||
double Voltage2;
|
||||
double Voltage3;
|
||||
double VoltageHI;
|
||||
double Voltage1HI;
|
||||
double Voltage2HI;
|
||||
double Voltage3HI;
|
||||
double VoltageLO;
|
||||
double Voltage1LO;
|
||||
double Voltage2LO;
|
||||
double Voltage3LO;
|
||||
|
||||
double Temperature;
|
||||
|
||||
bool firstframe;
|
||||
double milliamps;
|
||||
long watt;
|
||||
long As;
|
||||
long lastAs;
|
||||
long wh;
|
||||
long lastWh;
|
||||
|
||||
/* Output command frame used to alter or initialize ISA shunt behavior
|
||||
* Please note that all delay/sleep operations are solely in this section of code,
|
||||
* not used during normal operation. Such delays are currently commented out.
|
||||
*/
|
||||
CAN_frame_t outframe = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.unknown_2 = 0,
|
||||
.RTR = CAN_no_RTR,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
|
||||
.MsgID = 0x411,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
uint16_t get_measured_voltage() {
|
||||
return (uint16_t)Voltage;
|
||||
}
|
||||
|
||||
uint16_t get_measured_current() {
|
||||
return (uint16_t)Amperes;
|
||||
}
|
||||
|
||||
//This is our CAN interrupt service routine to catch inbound frames
|
||||
inline void ISA_handleFrame(CAN_frame_t* frame) {
|
||||
|
||||
if (frame->MsgID < 0x521 || frame->MsgID > 0x528) {
|
||||
return;
|
||||
}
|
||||
|
||||
framecount++;
|
||||
|
||||
switch (frame->MsgID) {
|
||||
case 0x511:
|
||||
break;
|
||||
|
||||
case 0x521:
|
||||
ISA_handle521(frame);
|
||||
break;
|
||||
|
||||
case 0x522:
|
||||
ISA_handle522(frame);
|
||||
break;
|
||||
|
||||
case 0x523:
|
||||
ISA_handle523(frame);
|
||||
break;
|
||||
|
||||
case 0x524:
|
||||
ISA_handle524(frame);
|
||||
break;
|
||||
|
||||
case 0x525:
|
||||
ISA_handle525(frame);
|
||||
break;
|
||||
|
||||
case 0x526:
|
||||
ISA_handle526(frame);
|
||||
break;
|
||||
|
||||
case 0x527:
|
||||
ISA_handle527(frame);
|
||||
break;
|
||||
|
||||
case 0x528:
|
||||
ISA_handle528(frame);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//handle frame for Amperes
|
||||
inline void ISA_handle521(CAN_frame_t* frame) {
|
||||
long current = 0;
|
||||
current =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
milliamps = current;
|
||||
Amperes = current / 1000.0f;
|
||||
}
|
||||
|
||||
//handle frame for Voltage
|
||||
inline void ISA_handle522(CAN_frame_t* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage = volt / 1000.0f;
|
||||
Voltage1 = Voltage - (Voltage2 + Voltage3);
|
||||
|
||||
if (framecount < 150) {
|
||||
VoltageLO = Voltage;
|
||||
Voltage1LO = Voltage1;
|
||||
} else {
|
||||
if (Voltage < VoltageLO)
|
||||
VoltageLO = Voltage;
|
||||
if (Voltage > VoltageHI)
|
||||
VoltageHI = Voltage;
|
||||
if (Voltage1 < Voltage1LO)
|
||||
Voltage1LO = Voltage1;
|
||||
if (Voltage1 > Voltage1HI)
|
||||
Voltage1HI = Voltage1;
|
||||
}
|
||||
}
|
||||
|
||||
//handle frame for Voltage 2
|
||||
inline void ISA_handle523(CAN_frame_t* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage2 = volt / 1000.0f;
|
||||
if (Voltage2 > 3)
|
||||
Voltage2 -= Voltage3;
|
||||
|
||||
if (framecount < 150) {
|
||||
Voltage2LO = Voltage2;
|
||||
} else {
|
||||
if (Voltage2 < Voltage2LO)
|
||||
Voltage2LO = Voltage2;
|
||||
if (Voltage2 > Voltage2HI)
|
||||
Voltage2HI = Voltage2;
|
||||
}
|
||||
}
|
||||
|
||||
//handle frame for Voltage3
|
||||
inline void ISA_handle524(CAN_frame_t* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Voltage3 = volt / 1000.0f;
|
||||
|
||||
if (framecount < 150) {
|
||||
Voltage3LO = Voltage3;
|
||||
} else {
|
||||
if (Voltage3 < Voltage3LO && Voltage3 > 10)
|
||||
Voltage3LO = Voltage3;
|
||||
if (Voltage3 > Voltage3HI)
|
||||
Voltage3HI = Voltage3;
|
||||
}
|
||||
}
|
||||
|
||||
//handle frame for Temperature
|
||||
inline void ISA_handle525(CAN_frame_t* frame) {
|
||||
long temp = 0;
|
||||
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
|
||||
Temperature = temp / 10;
|
||||
}
|
||||
|
||||
//handle frame for Kilowatts
|
||||
inline void ISA_handle526(CAN_frame_t* frame) {
|
||||
watt = 0;
|
||||
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
KW = watt / 1000.0f;
|
||||
}
|
||||
|
||||
//handle frame for Ampere-Hours
|
||||
inline void ISA_handle527(CAN_frame_t* frame) {
|
||||
As = 0;
|
||||
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
|
||||
|
||||
AH += (As - lastAs) / 3600.0f;
|
||||
lastAs = As;
|
||||
}
|
||||
|
||||
//handle frame for kiloWatt-hours
|
||||
inline void ISA_handle528(CAN_frame_t* frame) {
|
||||
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
KWH += (wh - lastWh) / 1000.0f;
|
||||
lastWh = wh;
|
||||
}
|
||||
|
||||
/*
|
||||
void ISA_initialize() {
|
||||
firstframe=false;
|
||||
STOP();
|
||||
delay(700);
|
||||
for(int i=0;i<9;i++) {
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=(0x20+i);
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x02;
|
||||
outframe.data.u8[3]=(0x60+(i*18));
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
}
|
||||
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
|
||||
}
|
||||
|
||||
void ISA_STOP() {
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
}
|
||||
|
||||
void ISA_sendSTORE() {
|
||||
outframe.data.u8[0]=0x32;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_START() {
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x01;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_RESTART() {
|
||||
//Has the effect of zeroing AH and KWH
|
||||
outframe.data.u8[0]=0x3F;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_deFAULT() {
|
||||
//Returns module to original defaults
|
||||
outframe.data.u8[0]=0x3D;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
}
|
||||
|
||||
void ISA_initCurrent() {
|
||||
STOP();
|
||||
delay(500);
|
||||
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=0x21;
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x61;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
|
||||
ESP32Can.CANWriteFrame(&outframe);
|
||||
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
16
Software/src/battery/CHADEMO-SHUNTS.h
Normal file
16
Software/src/battery/CHADEMO-SHUNTS.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef CHADEMO_SHUNTS_H
|
||||
#define CHADEMO_SHUNTS_H
|
||||
|
||||
uint16_t get_measured_voltage();
|
||||
uint16_t get_measured_current();
|
||||
inline void ISA_handler(CAN_frame_t* frame);
|
||||
inline void ISA_handle521(CAN_frame_t* frame);
|
||||
inline void ISA_handle522(CAN_frame_t* frame);
|
||||
inline void ISA_handle523(CAN_frame_t* frame);
|
||||
inline void ISA_handle524(CAN_frame_t* frame);
|
||||
inline void ISA_handle525(CAN_frame_t* frame);
|
||||
inline void ISA_handle526(CAN_frame_t* frame);
|
||||
inline void ISA_handle527(CAN_frame_t* frame);
|
||||
inline void ISA_handle528(CAN_frame_t* frame);
|
||||
|
||||
#endif
|
|
@ -10,8 +10,7 @@
|
|||
//Figure out if CAN messages need to be sent to keep the system happy?
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint8_t BMU_Detected = 0;
|
||||
static uint8_t CMU_Detected = 0;
|
||||
|
||||
|
@ -49,19 +48,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
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);
|
||||
|
||||
//We do not know if the max charge power is sent by the battery. So we estimate the value based on SOC%
|
||||
if (datalayer.battery.status.reported_soc == 10000) { //100.00%
|
||||
datalayer.battery.status.max_charge_power_W = 0; //When battery is 100% full, set allowed charge W to 0
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = 10000; //Otherwise we can push 10kW into the pack!
|
||||
}
|
||||
//We do not know the max charge/discharge power is sent by the battery. We hardcode value for now.
|
||||
datalayer.battery.status.max_charge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
||||
|
||||
if (datalayer.battery.status.reported_soc < 200) { //2.00%
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
0; //When battery is empty (below 2%), set allowed discharge W to 0
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; //Otherwise we can discharge 10kW from the pack!
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
||||
|
||||
datalayer.battery.status.active_power_W = BMU_Power; //TODO: Scaling?
|
||||
|
||||
|
@ -108,14 +98,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)(max_temp_cel * 10);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (!BMU_Detected) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMU not detected, check wiring!");
|
||||
|
@ -144,8 +126,8 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive =
|
||||
12; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x374: //BMU message, 10ms - SOC
|
||||
temp_value = ((rx_frame.data.u8[1] - 10) / 2);
|
||||
|
@ -206,6 +188,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
324
Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
Normal file
324
Software/src/battery/JAGUAR-IPACE-BATTERY.cpp
Normal file
|
@ -0,0 +1,324 @@
|
|||
#include "../include.h"
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "JAGUAR-IPACE-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
|
||||
static uint8_t HVBattAvgSOC = 0;
|
||||
static uint8_t HVBattFastChgCounter = 0;
|
||||
static uint8_t HVBattTempColdCellID = 0;
|
||||
static uint8_t HVBatTempHotCellID = 0;
|
||||
static uint8_t HVBattVoltMaxCellID = 0;
|
||||
static uint8_t HVBattVoltMinCellID = 0;
|
||||
static uint8_t HVBattPwerGPCS = 0;
|
||||
static uint8_t HVBattPwrGpCounter = 0;
|
||||
static int8_t HVBattCurrentTR = 0;
|
||||
static uint16_t HVBattCellVoltageMaxMv = 3700;
|
||||
static uint16_t HVBattCellVoltageMinMv = 3700;
|
||||
static uint16_t HVBattEnergyAvailable = 0;
|
||||
static uint16_t HVBattEnergyUsableMax = 0;
|
||||
static uint16_t HVBattTotalCapacityWhenNew = 0;
|
||||
static uint16_t HVBattDischargeContiniousPowerLimit = 0;
|
||||
static uint16_t HVBattDischargePowerLimitExt = 0;
|
||||
static uint16_t HVBattDischargeVoltageLimit = 0;
|
||||
static uint16_t HVBattVoltageExt = 0;
|
||||
static uint16_t HVBatteryVoltageOC = 0;
|
||||
static uint16_t HVBatteryChgCurrentLimit = 0;
|
||||
static uint16_t HVBattChargeContiniousPowerLimit = 0;
|
||||
static int16_t HVBattAverageTemperature = 0;
|
||||
static int16_t HVBattCellTempAverage = 0;
|
||||
static int16_t HVBattCellTempColdest = 0;
|
||||
static int16_t HVBattCellTempHottest = 0;
|
||||
static int16_t HVBattInletCoolantTemp = 0;
|
||||
static bool HVBatteryContactorStatus = false;
|
||||
static bool HVBatteryContactorStatusT = false;
|
||||
static bool HVBattHVILError = false;
|
||||
static bool HVILBattIsolationError = false;
|
||||
static bool HVIsolationTestStatus = false;
|
||||
|
||||
/* TODO: Actually use a proper keepalive message */
|
||||
CAN_frame_t ipace_keep_alive = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x063,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
CAN_frame_t ipace_7e4 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7e4,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
void print_units(char* header, int value, char* units) {
|
||||
Serial.print(header);
|
||||
Serial.print(value);
|
||||
Serial.print(units);
|
||||
}
|
||||
|
||||
void update_values_battery() {
|
||||
|
||||
datalayer.battery.status.real_soc = HVBattAvgSOC * 100; //Add two decimals
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; //TODO: Map
|
||||
|
||||
datalayer.battery.status.voltage_dV = HVBattVoltageExt * 10; //TODO: This value OK?
|
||||
|
||||
datalayer.battery.status.current_dA = HVBattCurrentTR * 10; //TODO: This value OK?
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh = HVBattEnergyUsableMax * 100; // kWh+1 to Wh
|
||||
|
||||
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 = HVBattCellVoltageMaxMv;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = HVBattCellVoltageMinMv;
|
||||
|
||||
//Power in watts, Negative = charging batt
|
||||
datalayer.battery.status.active_power_W =
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = HVBattCellTempColdest * 10; // C to dC
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = HVBattCellTempHottest * 10; // C to dC
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
HVBattDischargeContiniousPowerLimit * 10; // kWh+2 to W (TODO: Check that scaling is right way)
|
||||
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
HVBattChargeContiniousPowerLimit * 10; // kWh+2 to W (TODO: Check that scaling is right way)
|
||||
|
||||
if (HVBattHVILError) { // Alert user incase the high voltage interlock is not OK
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
|
||||
if (HVILBattIsolationError) { // Alert user incase battery reports isolation error
|
||||
set_event(EVENT_BATTERY_ISOLATION, 0);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_ISOLATION);
|
||||
}
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values going to inverter");
|
||||
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
|
||||
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
|
||||
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
|
||||
print_units(", Max discharge power: ", datalayer.battery.status.max_discharge_power_W, "W ");
|
||||
print_units(", Max charge power: ", datalayer.battery.status.max_charge_power_W, "W ");
|
||||
print_units(", Max temp: ", (datalayer.battery.status.temperature_max_dC * 0.1), "°C ");
|
||||
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
|
||||
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
|
||||
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
// Do not log noisy startup messages - there are many !
|
||||
if (rx_frame.MsgID == 0 && rx_frame.FIR.B.DLC == 8 && rx_frame.data.u8[0] == 0 && rx_frame.data.u8[1] == 0 &&
|
||||
rx_frame.data.u8[2] == 0 && rx_frame.data.u8[3] == 0 && rx_frame.data.u8[4] == 0 && rx_frame.data.u8[5] == 0 &&
|
||||
rx_frame.data.u8[6] == 0x80 && rx_frame.data.u8[7] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (rx_frame.MsgID) { // These messages are periodically transmitted by the battery
|
||||
case 0x080:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBatteryContactorStatus = ((rx_frame.data.u8[0] & 0x80) >> 7);
|
||||
HVBattHVILError = ((rx_frame.data.u8[0] & 0x40) >> 6);
|
||||
HVILBattIsolationError = ((rx_frame.data.u8[0] & 0x20) >> 5);
|
||||
break;
|
||||
case 0x100:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBattDischargeContiniousPowerLimit =
|
||||
((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); // 0x3269 = 12905 = 129.05kW
|
||||
HVBattDischargePowerLimitExt = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); // 0x7BD5 = 31701 = 317.01kW
|
||||
HVBattDischargeVoltageLimit =
|
||||
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); // Lowest voltage the pack can go to
|
||||
break;
|
||||
case 0x102:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBattPwrGpCounter = ((rx_frame.data.u8[1] & 0x3C) >> 2); // Loops 0-F-0
|
||||
HVBattPwerGPCS = rx_frame.data.u8[0]; // SAE J1850 CRC8 Checksum.
|
||||
//TODO: Add function that checks if CRC is correct. We can use this to detect corrupted CAN messages
|
||||
//HVBattCurrentExt = //Used only on 2018+
|
||||
HVBattVoltageExt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]);
|
||||
break;
|
||||
case 0x104:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBatteryContactorStatusT = ((rx_frame.data.u8[2] & 0x80) >> 7);
|
||||
HVIsolationTestStatus = ((rx_frame.data.u8[2] & 0x10) >> 4);
|
||||
HVBatteryVoltageOC = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]);
|
||||
HVBatteryChgCurrentLimit = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]);
|
||||
break;
|
||||
case 0x10A:
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
HVBattChargeContiniousPowerLimit = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
|
||||
break;
|
||||
case 0x198:
|
||||
break;
|
||||
case 0x1C4:
|
||||
HVBattCurrentTR = rx_frame.data.u8[0]; //TODO: scaling?
|
||||
//HVBattPwrExtGPCounter = (rx_frame.data.u8[2] & 0xF0) >> 4; // not needed
|
||||
//HVBattPwrExtGPCS = rx_frame.data.u8[1]; // Checksum, not needed
|
||||
//HVBattVoltageBusTF Frame 3/4/5?
|
||||
//HVBattVoltageBusTR Frame 3/4/5?
|
||||
break;
|
||||
case 0x220:
|
||||
break;
|
||||
case 0x222:
|
||||
HVBattAvgSOC = rx_frame.data.u8[4];
|
||||
HVBattAverageTemperature = (rx_frame.data.u8[5] / 2) - 40;
|
||||
HVBattFastChgCounter = rx_frame.data.u8[7];
|
||||
//HVBattLogEvent
|
||||
HVBattTempColdCellID = rx_frame.data.u8[0];
|
||||
HVBatTempHotCellID = rx_frame.data.u8[1];
|
||||
HVBattVoltMaxCellID = rx_frame.data.u8[2];
|
||||
HVBattVoltMinCellID = rx_frame.data.u8[3];
|
||||
break;
|
||||
case 0x248:
|
||||
break;
|
||||
case 0x308:
|
||||
break;
|
||||
case 0x424:
|
||||
HVBattCellVoltageMaxMv = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
HVBattCellVoltageMinMv = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
//HVBattHeatPowerGenChg // kW
|
||||
//HVBattHeatPowerGenDcg // kW
|
||||
//HVBattWarmupRateChg // degC/minute
|
||||
//HVBattWarmupRateDcg // degC/minute
|
||||
break;
|
||||
case 0x448:
|
||||
HVBattCellTempAverage = (rx_frame.data.u8[0] / 2) - 40;
|
||||
HVBattCellTempColdest = (rx_frame.data.u8[1] / 2) - 40;
|
||||
HVBattCellTempHottest = (rx_frame.data.u8[2] / 2) - 40;
|
||||
//HVBattCIntPumpDiagStat // Pump OK / NOK
|
||||
//HVBattCIntPumpDiagStat_UB // True/False
|
||||
//HVBattCoolantLevel // Coolant level OK / NOK
|
||||
//HVBattHeaterCtrlStat // Off / On
|
||||
HVBattInletCoolantTemp = (rx_frame.data.u8[5] / 2) - 40;
|
||||
//HVBattInlentCoolantTemp_UB // True/False
|
||||
//HVBattMILRequested // True/False
|
||||
//HVBattThermalOvercheck // OK / NOK
|
||||
break;
|
||||
case 0x449:
|
||||
break;
|
||||
case 0x464:
|
||||
HVBattEnergyAvailable =
|
||||
((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) / 2; // 0x0198 = 408 / 2 = 204 = 20.4kWh
|
||||
HVBattEnergyUsableMax =
|
||||
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) / 2; // 0x06EA = 1770 / 2 = 885 = 88.5kWh
|
||||
HVBattTotalCapacityWhenNew = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); //0x0395 = 917 = 91.7kWh
|
||||
break;
|
||||
case 0x522:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Discard non-interesting can messages so they do not get logged via serial
|
||||
if (rx_frame.MsgID < 0x500) {
|
||||
return;
|
||||
}
|
||||
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.MsgID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.FIR.B.DLC);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < rx_frame.FIR.B.DLC; ++i) {
|
||||
Serial.print(rx_frame.data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
|
||||
int state = 0;
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
//ESP32Can.CANWriteFrame(&ipace_keep_alive);
|
||||
}
|
||||
|
||||
// Send 500ms CAN Message
|
||||
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
|
||||
previousMillis500 = currentMillis;
|
||||
|
||||
CAN_frame_t msg;
|
||||
int err;
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
|
||||
// car response: 7ec 07 59 02 8f c0 64 88 28
|
||||
// response: 7EC 07 59 02 8F F0 01 00 28
|
||||
// response: 7EC 03 59 02 8F 00 00 00 00
|
||||
// 7EC 8 3 7F 19 11 0 0 0 0
|
||||
msg = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7e4,
|
||||
.data = {0x03, 0x19, 0x02, 0x8f, 0x00, 0x00, 0x00, 0x00}};
|
||||
err = ESP32Can.CANWriteFrame(&msg);
|
||||
if (err == 0)
|
||||
state++;
|
||||
|
||||
break;
|
||||
case 1:
|
||||
// car response: 7EC 11 fa 59 04 c0 64 88 28
|
||||
// response:
|
||||
|
||||
msg = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7e4,
|
||||
.data = {0x06, 0x19, 0x04, 0xc0, 0x64, 0x88, 0xff, 0x00}};
|
||||
err = ESP32Can.CANWriteFrame(&msg);
|
||||
if (err == 0)
|
||||
state++;
|
||||
break;
|
||||
case 2:
|
||||
/* reset */
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Jaguar iPace 90kWh battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_design_voltage_dV = 4546;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3370;
|
||||
}
|
||||
|
||||
#endif
|
10
Software/src/battery/JAGUAR-IPACE-BATTERY.h
Normal file
10
Software/src/battery/JAGUAR-IPACE-BATTERY.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef JAGUAR_IPACE_BATTERY_H
|
||||
#define JAGUAR_IPACE_BATTERY_H
|
||||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999 // TODO is this ok ?
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -9,11 +9,9 @@
|
|||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
|
@ -23,7 +21,6 @@ static uint16_t SOC_Display = 0;
|
|||
static uint16_t batterySOH = 1000;
|
||||
static uint16_t CellVoltMax_mV = 3700;
|
||||
static uint16_t CellVoltMin_mV = 3700;
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint16_t batteryVoltage = 0;
|
||||
static int16_t leadAcidBatteryVoltage = 120;
|
||||
static int16_t batteryAmps = 0;
|
||||
|
@ -51,7 +48,9 @@ CANFDMessage EGMP_7E4_ack;
|
|||
|
||||
void set_cell_voltages(CANFDMessage frame, int start, int length, int startCell) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
datalayer.battery.status.cell_voltages_mV[startCell + i] = (frame.data[start + i] * 20);
|
||||
if ((frame.data[start + i] * 20) > 1000) {
|
||||
datalayer.battery.status.cell_voltages_mV[startCell + i] = (frame.data[start + i] * 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,24 +62,18 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.voltage_dV = batteryVoltage; //value is *10 (3700 = 370.0)
|
||||
|
||||
datalayer.battery.status.current_dA = batteryAmps; //value is *10 (150 = 15.0)
|
||||
datalayer.battery.status.current_dA = -batteryAmps; //value is *10 (150 = 15.0)
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
//datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
|
||||
//The allowed charge power is not available. We estimate this value
|
||||
if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else { // No limits, max charging power allowed
|
||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||
}
|
||||
//The allowed charge power is not available. We hardcode this value for now
|
||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||
|
||||
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
||||
if (datalayer.battery.status.reported_soc < 100) { // When scaled SOC is <1%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else { // No limits, max charging power allowed
|
||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
||||
}
|
||||
//The allowed discharge power is not available. We hardcode this value for now
|
||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
||||
|
||||
powerWatt = ((batteryVoltage * batteryAmps) / 100);
|
||||
|
||||
|
@ -95,10 +88,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CANFD_RX_FAILURE);
|
||||
}
|
||||
|
||||
|
@ -111,25 +104,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
// Check if cell voltages are within allowed range
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
|
@ -193,17 +173,29 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
#endif
|
||||
}
|
||||
|
||||
void send_canfd_frame(CANFDMessage frame) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
const bool ok = canfd.tryToSend(frame);
|
||||
if (ok) {
|
||||
} else {
|
||||
Serial.println("Send canfd failure.");
|
||||
}
|
||||
#else
|
||||
canfd.tryToSend(frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_canfd_battery(CANFDMessage frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (frame.id) {
|
||||
case 0x7EC:
|
||||
// printFrame(frame);
|
||||
// print_canfd_frame(frame);
|
||||
switch (frame.data[0]) {
|
||||
case 0x10: //"PID Header"
|
||||
// Serial.println ("Send ack");
|
||||
poll_data_pid = frame.data[4];
|
||||
// if (frame.data[4] == poll_data_pid) {
|
||||
canfd.tryToSend(EGMP_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
send_canfd_frame(EGMP_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
// }
|
||||
break;
|
||||
case 0x21: //First frame in PID group
|
||||
|
@ -385,10 +377,19 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis500ms));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis500ms = currentMillis;
|
||||
// Section added to close contractor
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
} else { //datalayer.battery.status.bms_status == FAULT or inverter requested opening contactors
|
||||
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||
}
|
||||
// Section end
|
||||
EGMP_7E4.data[3] = KIA_7E4_COUNTER;
|
||||
canfd.tryToSend(EGMP_7E4);
|
||||
send_canfd_frame(EGMP_7E4);
|
||||
|
||||
KIA_7E4_COUNTER++;
|
||||
if (KIA_7E4_COUNTER > 0x0D) { // gets up to 0x010C before repeating
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
extern ACAN2517FD canfd;
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
#define MAXCHARGEPOWERALLOWED 10000
|
||||
#define MAXDISCHARGEPOWERALLOWED 10000
|
||||
|
||||
|
|
|
@ -9,41 +9,38 @@
|
|||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10s CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||
|
||||
static uint16_t soc_calculated = 0;
|
||||
static uint16_t SOC_BMS = 0;
|
||||
static uint16_t SOC_Display = 0;
|
||||
static uint16_t batterySOH = 1000;
|
||||
static uint8_t waterleakageSensor = 164;
|
||||
static int16_t leadAcidBatteryVoltage = 0;
|
||||
static uint16_t CellVoltMax_mV = 3700;
|
||||
static uint8_t CellVmaxNo = 0;
|
||||
static uint16_t CellVoltMin_mV = 3700;
|
||||
static uint8_t CellVminNo = 0;
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint16_t allowedDischargePower = 0;
|
||||
static uint16_t allowedChargePower = 0;
|
||||
static uint16_t batteryVoltage = 0;
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
static uint16_t cellvoltages_mv[98];
|
||||
static int16_t leadAcidBatteryVoltage = 120;
|
||||
static int16_t batteryAmps = 0;
|
||||
static int16_t temperatureMax = 0;
|
||||
static int16_t temperatureMin = 0;
|
||||
static int8_t temperature_water_inlet = 0;
|
||||
static int16_t poll_data_pid = 0;
|
||||
static uint8_t CellVmaxNo = 0;
|
||||
static uint8_t CellVminNo = 0;
|
||||
static uint8_t batteryManagementMode = 0;
|
||||
static uint8_t BMS_ign = 0;
|
||||
static int16_t poll_data_pid = 0;
|
||||
static int8_t heatertemp = 0;
|
||||
static uint8_t batteryRelay = 0;
|
||||
static int8_t powerRelayTemperature = 0;
|
||||
static uint16_t inverterVoltageFrameHigh = 0;
|
||||
static uint16_t inverterVoltage = 0;
|
||||
static uint8_t startedUp = false;
|
||||
static uint8_t waterleakageSensor = 164;
|
||||
static uint8_t counter_200 = 0;
|
||||
static uint16_t cellvoltages_mv[98];
|
||||
static int8_t temperature_water_inlet = 0;
|
||||
static int8_t heatertemp = 0;
|
||||
static int8_t powerRelayTemperature = 0;
|
||||
static bool startedUp = false;
|
||||
|
||||
CAN_frame_t KIA_HYUNDAI_200 = {.FIR = {.B =
|
||||
{
|
||||
|
@ -158,17 +155,9 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
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);
|
||||
|
||||
if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else { // Limit according to CAN value
|
||||
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10;
|
||||
}
|
||||
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10;
|
||||
|
||||
if (datalayer.battery.status.reported_soc < 100) { // When scaled SOC is <1%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else { // Limit according to CAN value
|
||||
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10;
|
||||
|
||||
//Power in watts, Negative = charging batt
|
||||
datalayer.battery.status.active_power_W =
|
||||
|
@ -182,14 +171,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (waterleakageSensor == 0) {
|
||||
set_event(EVENT_WATER_INGRESS, 0);
|
||||
}
|
||||
|
@ -198,43 +179,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
|
||||
}
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
for (int i = 0; i < 98; ++i) {
|
||||
if (cellvoltages_mv[i] > 1000) {
|
||||
datalayer.battery.status.cell_voltages_mV[i] = cellvoltages_mv[i];
|
||||
}
|
||||
}
|
||||
// Check if we have 98S or 90S battery
|
||||
if (datalayer.battery.status.cell_voltages_mV[97] > 0) {
|
||||
datalayer.battery.info.number_of_cells = 98;
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100;
|
||||
} else {
|
||||
datalayer.battery.info.number_of_cells = 90;
|
||||
datalayer.battery.info.max_design_voltage_dV = 3870;
|
||||
datalayer.battery.info.min_design_voltage_dV = 2250;
|
||||
}
|
||||
|
||||
// Check if cell voltages are within allowed range
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
|
@ -298,20 +249,40 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
#endif
|
||||
}
|
||||
|
||||
void update_number_of_cells() {
|
||||
//If we have cell values and number_of_cells not initialized yet
|
||||
if (cellvoltages_mv[0] > 0 && datalayer.battery.info.number_of_cells == 0) {
|
||||
// Check if we have 98S or 90S battery
|
||||
if (datalayer.battery.status.cell_voltages_mV[97] > 0) {
|
||||
datalayer.battery.info.number_of_cells = 98;
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100;
|
||||
} else {
|
||||
datalayer.battery.info.number_of_cells = 90;
|
||||
datalayer.battery.info.max_design_voltage_dV = 3870;
|
||||
datalayer.battery.info.min_design_voltage_dV = 2250;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x4DE:
|
||||
startedUp = true;
|
||||
break;
|
||||
case 0x542: //BMS SOC
|
||||
CANstillAlive = 12; //We use this message to verify that BMS is still alive
|
||||
case 0x542: //BMS SOC
|
||||
startedUp = true;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 )
|
||||
break;
|
||||
case 0x594:
|
||||
startedUp = true;
|
||||
allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
SOC_BMS = rx_frame.data.u8[5] * 5; //100% = 200 ( 200 * 5 = 1000 )
|
||||
break;
|
||||
case 0x595:
|
||||
startedUp = true;
|
||||
batteryVoltage = (rx_frame.data.u8[7] << 8) + rx_frame.data.u8[6];
|
||||
batteryAmps = (rx_frame.data.u8[5] << 8) + rx_frame.data.u8[4];
|
||||
if (counter_200 > 3) {
|
||||
|
@ -320,18 +291,21 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
} //VCU measured voltage sent back to bms
|
||||
break;
|
||||
case 0x596:
|
||||
startedUp = true;
|
||||
leadAcidBatteryVoltage = rx_frame.data.u8[1]; //12v Battery Volts
|
||||
temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
|
||||
temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
|
||||
break;
|
||||
case 0x598:
|
||||
startedUp = true;
|
||||
break;
|
||||
case 0x5D5:
|
||||
startedUp = true;
|
||||
waterleakageSensor = rx_frame.data.u8[3]; //Water sensor inside pack, value 164 is no water --> 0 is short
|
||||
powerRelayTemperature = rx_frame.data.u8[7];
|
||||
break;
|
||||
case 0x5D8:
|
||||
startedUp = 1;
|
||||
startedUp = true;
|
||||
|
||||
//PID data is polled after last message sent from battery:
|
||||
if (poll_data_pid >= 10) { //polling one of ten PIDs at 100ms, resolution = 1s
|
||||
|
@ -508,6 +482,10 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
break;
|
||||
case 0x26: //Sixth datarow in PID group
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
|
||||
//Update number of cells
|
||||
update_number_of_cells();
|
||||
break;
|
||||
case 0x27: //Seventh datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
|
@ -529,6 +507,11 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (!startedUp) {
|
||||
return; // Don't send any CAN messages towards battery until it has started up
|
||||
}
|
||||
|
||||
//Send 100ms message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
@ -542,6 +525,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
|
@ -560,11 +545,7 @@ void send_can_battery() {
|
|||
break;
|
||||
case 3:
|
||||
KIA_HYUNDAI_200.data.u8[5] = 0xD7;
|
||||
if (startedUp) {
|
||||
++counter_200;
|
||||
} else {
|
||||
counter_200 = 0;
|
||||
}
|
||||
++counter_200;
|
||||
break;
|
||||
case 4:
|
||||
KIA_HYUNDAI_200.data.u8[3] = 0x10;
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
void update_number_of_cells();
|
||||
|
||||
#endif
|
||||
|
|
284
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
Normal file
284
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
#include "../include.h"
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
- The HEV battery seems to turn off after 1 minute of use. When this happens SOC% stops updating.
|
||||
- We need to figure out how to keep the BMS alive. Most likely we need to send a specific CAN message
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 100ms CAN Message was send
|
||||
|
||||
static uint16_t SOC = 0;
|
||||
static uint16_t SOC_display = 0;
|
||||
static bool interlock_missing = false;
|
||||
static int16_t battery_current = 0;
|
||||
static uint8_t battery_current_high_byte = 0;
|
||||
static uint16_t battery_voltage = 0;
|
||||
static uint32_t available_charge_power = 0;
|
||||
static uint32_t available_discharge_power = 0;
|
||||
static int8_t battery_module_max_temperature = 0;
|
||||
static int8_t battery_module_min_temperature = 0;
|
||||
static uint8_t poll_data_pid = 0;
|
||||
static uint16_t cellvoltages_mv[98];
|
||||
static uint16_t min_cell_voltage_mv = 3700;
|
||||
static uint16_t max_cell_voltage_mv = 3700;
|
||||
|
||||
CAN_frame_t KIA_7E4_id1 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_id2 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_id3 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_id5 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4,
|
||||
.data = {0x02, 0x21, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t KIA_7E4_ack = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x7E4, //Ack frame, correct PID is returned. Flow control message
|
||||
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
||||
datalayer.battery.status.real_soc = SOC * 50;
|
||||
|
||||
datalayer.battery.status.voltage_dV = battery_voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = battery_current;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = available_discharge_power * 10;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = available_charge_power * 10;
|
||||
|
||||
//Power in watts, Negative = charging batt
|
||||
datalayer.battery.status.active_power_W =
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)(battery_module_min_temperature * 10);
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = (int16_t)(battery_module_max_temperature * 10);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage_mv;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage_mv;
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
|
||||
|
||||
if (interlock_missing) {
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x5F1:
|
||||
break;
|
||||
case 0x51E:
|
||||
break;
|
||||
case 0x588:
|
||||
break;
|
||||
case 0x5AE:
|
||||
interlock_missing = (bool)(rx_frame.data.u8[1] & 0x02) >> 1;
|
||||
break;
|
||||
case 0x5AF:
|
||||
break;
|
||||
case 0x5AD:
|
||||
break;
|
||||
case 0x670:
|
||||
break;
|
||||
case 0x7EC: //Data From polled PID group, BigEndian
|
||||
switch (rx_frame.data.u8[0]) {
|
||||
case 0x10: //"PID Header"
|
||||
if (rx_frame.data.u8[3] == poll_data_pid) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
}
|
||||
break;
|
||||
case 0x21: //First frame in PID group
|
||||
if (poll_data_pid == 1) { // 21 01
|
||||
SOC = rx_frame.data.u8[1]; // 0xBC = 188 /2 = 94%
|
||||
available_charge_power = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
available_discharge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
battery_current_high_byte = rx_frame.data.u8[7];
|
||||
} else if (poll_data_pid == 2) { //21 02
|
||||
cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) { //21 03
|
||||
cellvoltages_mv[31] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[32] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[33] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[34] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[35] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[36] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) { //21 05
|
||||
}
|
||||
break;
|
||||
case 0x22: //Second datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
battery_current = ((battery_current_high_byte << 8) | rx_frame.data.u8[1]);
|
||||
battery_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
battery_module_max_temperature = rx_frame.data.u8[4];
|
||||
battery_module_min_temperature = rx_frame.data.u8[5];
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
|
||||
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[37] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[38] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[39] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[40] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[41] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[42] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
}
|
||||
break;
|
||||
case 0x23: //Third datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
max_cell_voltage_mv = rx_frame.data.u8[6] * 20;
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[43] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[44] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[45] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[46] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[47] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[48] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
}
|
||||
break;
|
||||
case 0x24: //Fourth datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
min_cell_voltage_mv = rx_frame.data.u8[1] * 20;
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[49] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[50] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[51] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[52] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[53] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[54] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
SOC_display = rx_frame.data.u8[7]; //0x26 = 38%
|
||||
}
|
||||
break;
|
||||
case 0x25: //Fifth datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
|
||||
} else if (poll_data_pid == 2) {
|
||||
cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
}
|
||||
break;
|
||||
case 0x26: //Sixth datarow in PID group
|
||||
break;
|
||||
case 0x27: //Seventh datarow in PID group
|
||||
break;
|
||||
case 0x28: //Eighth datarow in PID group
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send 1000ms CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
previousMillis1000 = currentMillis;
|
||||
|
||||
//PID data is polled after last message sent from battery:
|
||||
if (poll_data_pid >= 5) { //polling one of 5 PIDs at 100ms, resolution = 500ms
|
||||
poll_data_pid = 0;
|
||||
}
|
||||
poll_data_pid++;
|
||||
if (poll_data_pid == 1) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id1);
|
||||
} else if (poll_data_pid == 2) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id2);
|
||||
} else if (poll_data_pid == 3) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id3);
|
||||
} else if (poll_data_pid == 4) {
|
||||
|
||||
} else if (poll_data_pid == 5) {
|
||||
ESP32Can.CANWriteFrame(&KIA_7E4_id5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Kia/Hyundai Hybrid battery selected");
|
||||
#endif
|
||||
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
|
||||
datalayer.battery.info.max_design_voltage_dV = 2550; //TODO: Values OK?
|
||||
datalayer.battery.info.min_design_voltage_dV = 1700;
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
Normal file
12
Software/src/battery/KIA-HYUNDAI-HYBRID-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef KIA_HYUNDAI_HYBRID_BATTERY_H
|
||||
#define KIA_HYUNDAI_HYBRID_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 100
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
152
Software/src/battery/MG-5-BATTERY.cpp
Normal file
152
Software/src/battery/MG-5-BATTERY.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include "../include.h"
|
||||
#ifdef MG_5_BATTERY_H
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "MG-5-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
- Get contactor closing working
|
||||
- Figure out which CAN messages need to be sent towards the battery to keep it alive
|
||||
- Map all values from battery CAN messages
|
||||
- Most important ones
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
|
||||
static int BMS_SOC = 0;
|
||||
|
||||
CAN_frame_t MG_5_100 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x100,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00}};
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
|
||||
datalayer.battery.status.real_soc;
|
||||
|
||||
datalayer.battery.status.voltage_dV;
|
||||
|
||||
datalayer.battery.status.current_dA;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x171: //Following messages were detected on a MG5 battery BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
break;
|
||||
case 0x172:
|
||||
break;
|
||||
case 0x173:
|
||||
break;
|
||||
case 0x293:
|
||||
break;
|
||||
case 0x295:
|
||||
break;
|
||||
case 0x297:
|
||||
break;
|
||||
case 0x29B:
|
||||
break;
|
||||
case 0x29C:
|
||||
break;
|
||||
case 0x2A0:
|
||||
break;
|
||||
case 0x2A2:
|
||||
break;
|
||||
case 0x322:
|
||||
break;
|
||||
case 0x334:
|
||||
break;
|
||||
case 0x33F:
|
||||
break;
|
||||
case 0x391:
|
||||
break;
|
||||
case 0x393:
|
||||
break;
|
||||
case 0x3AB:
|
||||
break;
|
||||
case 0x3AC:
|
||||
break;
|
||||
case 0x3B8:
|
||||
break;
|
||||
case 0x3BA:
|
||||
break;
|
||||
case 0x3BC:
|
||||
break;
|
||||
case 0x3BE:
|
||||
break;
|
||||
case 0x3C0:
|
||||
break;
|
||||
case 0x3C2:
|
||||
break;
|
||||
case 0x400:
|
||||
break;
|
||||
case 0x402:
|
||||
break;
|
||||
case 0x418:
|
||||
break;
|
||||
case 0x44C:
|
||||
break;
|
||||
case 0x620:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
//Send 10ms message
|
||||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&MG_5_100);
|
||||
}
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
//ESP32Can.CANWriteFrame(&MG_5_100);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("MG 5 battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040; // Over this charging is not possible
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100; // Under this discharging is disabled
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/MG-5-BATTERY.h
Normal file
12
Software/src/battery/MG-5-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef MG_5_BATTERY_H
|
||||
#define MG_5_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 150
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -13,11 +13,10 @@
|
|||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
|
||||
static uint16_t CANerror = 0; //counter on how many CAN errors encountered
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
|
||||
static uint8_t mprun10 = 0; //counter 0-3
|
||||
static uint8_t mprun100 = 0; //counter 0-3
|
||||
static bool can_bus_alive = false;
|
||||
|
||||
CAN_frame_t LEAF_1F2 = {.FIR = {.B =
|
||||
{
|
||||
|
@ -91,7 +90,6 @@ static uint8_t crctable[256] = {
|
|||
static uint8_t LEAF_Battery_Type = ZE0_BATTERY;
|
||||
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define WH_PER_GID 77 //One GID is this amount of Watt hours
|
||||
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
|
||||
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
|
||||
|
@ -132,14 +130,13 @@ static bool Batt_Heater_Mail_Send_Request = false; //Stores info when a heat re
|
|||
static uint8_t battery_request_idx = 0;
|
||||
static uint8_t group_7bb = 0;
|
||||
static uint8_t group = 1;
|
||||
static uint8_t stop_battery_query = 1;
|
||||
static bool stop_battery_query = true;
|
||||
static uint8_t hold_off_with_polling_10seconds = 10;
|
||||
static uint16_t cell_voltages[97]; //array with all the cellvoltages
|
||||
static uint8_t cellcounter = 0;
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint16_t HX = 0; //Internal resistance
|
||||
static uint16_t insulation = 0; //Insulation resistance
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t HX = 0; //Internal resistance
|
||||
static uint16_t insulation = 0; //Insulation resistance
|
||||
static uint16_t temp_raw_1 = 0;
|
||||
static uint8_t temp_raw_2_highnibble = 0;
|
||||
static uint16_t temp_raw_2 = 0;
|
||||
|
@ -202,32 +199,9 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
// Define power able to be discharged from battery
|
||||
if (LB_Discharge_Power_Limit > 60) { //if >60kW can be pulled from battery
|
||||
datalayer.battery.status.max_discharge_power_W = 60000; //cap value so we don't go over uint16 value
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc ==
|
||||
0) { //Scaled SOC% value is 0.00%, we should not discharge battery further
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W
|
||||
|
||||
// Define power able to be put into the battery
|
||||
if (LB_Charge_Power_Limit > 60) { //if >60kW can be put into the battery
|
||||
datalayer.battery.status.max_charge_power_W = 60000; //cap value so we don't go over uint16 value
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
datalayer.battery.status.max_charge_power_W = 0; //No need to charge further, set max power to 0
|
||||
}
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
for (int i = 0; i < 96; ++i) {
|
||||
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
|
||||
}
|
||||
datalayer.battery.status.max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W
|
||||
|
||||
/*Extra safety functions below*/
|
||||
if (LB_GIDS < 10) //700Wh left in battery!
|
||||
|
@ -237,17 +211,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
//Check if SOC% is plausible
|
||||
if (datalayer.battery.status.voltage_dV >
|
||||
(datalayer.battery.info.max_design_voltage_dV -
|
||||
100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
|
||||
if (LB_SOC < 650) {
|
||||
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); // Set event with the SOC as data
|
||||
} else {
|
||||
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (LB_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
|
@ -308,14 +271,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
|
||||
}
|
||||
|
||||
if (LB_StateOfHealth < 25) { //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out.
|
||||
if (LB_StateOfHealth != 0) { //Extra check to see that we actually have a SOH Value available
|
||||
set_event(EVENT_LOW_SOH, LB_StateOfHealth);
|
||||
} else {
|
||||
clear_event(EVENT_LOW_SOH);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef INTERLOCK_REQUIRED
|
||||
if (!LB_Interlock) {
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
|
@ -324,19 +279,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
if (CANerror >
|
||||
MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
|
||||
{
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
}
|
||||
|
||||
if (LB_HeatExist) {
|
||||
if (LB_Heating_Stop) {
|
||||
set_event(EVENT_BATTERY_WARMED_UP, 0);
|
||||
|
@ -346,12 +288,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values from battery");
|
||||
|
@ -361,7 +297,6 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
print_with_units(", Has heater: ", LB_HeatExist, " ");
|
||||
print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV ");
|
||||
print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV ");
|
||||
print_with_units(", Cell deviation: ", cell_deviation_mV, "mV ");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -369,7 +304,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
switch (rx_frame.MsgID) {
|
||||
case 0x1DB:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
|
||||
|
@ -394,7 +329,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x1DC:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
|
||||
|
@ -403,7 +338,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
break;
|
||||
case 0x55B:
|
||||
if (is_message_corrupt(rx_frame)) {
|
||||
CANerror++;
|
||||
datalayer.battery.status.CAN_error_counter++;
|
||||
break; //Message content malformed, abort reading data from it
|
||||
}
|
||||
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
|
||||
|
@ -413,7 +348,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LB_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7);
|
||||
break;
|
||||
case 0x5BC:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
can_bus_alive = true;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
|
||||
|
||||
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
|
||||
if (LB_MAX) {
|
||||
|
@ -466,7 +402,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
LEAF_Battery_Type = ZE1_BATTERY;
|
||||
break;
|
||||
case 0x79B:
|
||||
stop_battery_query = 1; //Someone is trying to read data with Leafspy, stop our own polling!
|
||||
stop_battery_query = true; //Someone is trying to read data with Leafspy, stop our own polling!
|
||||
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
|
||||
break;
|
||||
case 0x7BB:
|
||||
|
@ -512,6 +448,11 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
if (rx_frame.data.u8[6] == 0xFF && rx_frame.data.u8[0] == 0x2C) { //Last frame
|
||||
//Last frame does not contain any cell data, calculate the result
|
||||
|
||||
//Map all cell voltages to the global array
|
||||
memcpy(datalayer.battery.status.cell_voltages_mV, cell_voltages, 96 * sizeof(uint16_t));
|
||||
|
||||
//calculate min/max voltages
|
||||
min_max_voltage[0] = 9999;
|
||||
min_max_voltage[1] = 0;
|
||||
for (cellcounter = 0; cellcounter < 96; cellcounter++) {
|
||||
|
@ -521,15 +462,9 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = min_max_voltage[1];
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_max_voltage[0];
|
||||
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
}
|
||||
|
||||
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
@ -615,189 +550,194 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (can_bus_alive) {
|
||||
|
||||
//Send 10ms message
|
||||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
switch (mprun10) {
|
||||
case 0:
|
||||
LEAF_1D4.data.u8[4] = 0x07;
|
||||
LEAF_1D4.data.u8[7] = 0x12;
|
||||
break;
|
||||
case 1:
|
||||
LEAF_1D4.data.u8[4] = 0x47;
|
||||
LEAF_1D4.data.u8[7] = 0xD5;
|
||||
break;
|
||||
case 2:
|
||||
LEAF_1D4.data.u8[4] = 0x87;
|
||||
LEAF_1D4.data.u8[7] = 0x19;
|
||||
break;
|
||||
case 3:
|
||||
LEAF_1D4.data.u8[4] = 0xC7;
|
||||
LEAF_1D4.data.u8[7] = 0xDE;
|
||||
break;
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&LEAF_1D4);
|
||||
//Send 10ms message
|
||||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
switch (mprun10r) {
|
||||
case (0):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case (1):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x80;
|
||||
break;
|
||||
case (2):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case (3):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x82;
|
||||
break;
|
||||
case (4):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case (5): // Set 2
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case (6):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x85;
|
||||
break;
|
||||
case (7):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
case (8):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x83;
|
||||
break;
|
||||
case (9):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case (10): // Set 3
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case (11):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x82;
|
||||
break;
|
||||
case (12):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case (13):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x80;
|
||||
break;
|
||||
case (14):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case (15): // Set 4
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
case (16):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x83;
|
||||
break;
|
||||
case (17):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case (18):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x85;
|
||||
break;
|
||||
case (19):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (mprun10) {
|
||||
case 0:
|
||||
LEAF_1D4.data.u8[4] = 0x07;
|
||||
LEAF_1D4.data.u8[7] = 0x12;
|
||||
break;
|
||||
case 1:
|
||||
LEAF_1D4.data.u8[4] = 0x47;
|
||||
LEAF_1D4.data.u8[7] = 0xD5;
|
||||
break;
|
||||
case 2:
|
||||
LEAF_1D4.data.u8[4] = 0x87;
|
||||
LEAF_1D4.data.u8[7] = 0x19;
|
||||
break;
|
||||
case 3:
|
||||
LEAF_1D4.data.u8[4] = 0xC7;
|
||||
LEAF_1D4.data.u8[7] = 0xDE;
|
||||
break;
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&LEAF_1D4);
|
||||
|
||||
switch (mprun10r) {
|
||||
case (0):
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case (1):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x80;
|
||||
break;
|
||||
case (2):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case (3):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x82;
|
||||
break;
|
||||
case (4):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case (5): // Set 2
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case (6):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x85;
|
||||
break;
|
||||
case (7):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
case (8):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x83;
|
||||
break;
|
||||
case (9):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case (10): // Set 3
|
||||
LEAF_1F2.data.u8[3] = 0xB0;
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case (11):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x82;
|
||||
break;
|
||||
case (12):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x8F;
|
||||
break;
|
||||
case (13):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x80;
|
||||
break;
|
||||
case (14):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x81;
|
||||
break;
|
||||
case (15): // Set 4
|
||||
LEAF_1F2.data.u8[3] = 0xB4;
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
case (16):
|
||||
LEAF_1F2.data.u8[6] = 0x00;
|
||||
LEAF_1F2.data.u8[7] = 0x83;
|
||||
break;
|
||||
case (17):
|
||||
LEAF_1F2.data.u8[6] = 0x01;
|
||||
LEAF_1F2.data.u8[7] = 0x84;
|
||||
break;
|
||||
case (18):
|
||||
LEAF_1F2.data.u8[6] = 0x02;
|
||||
LEAF_1F2.data.u8[7] = 0x85;
|
||||
break;
|
||||
case (19):
|
||||
LEAF_1F2.data.u8[6] = 0x03;
|
||||
LEAF_1F2.data.u8[7] = 0x86;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//Only send this message when NISSANLEAF_CHARGER is not defined (otherwise it will collide!)
|
||||
#ifndef NISSANLEAF_CHARGER
|
||||
ESP32Can.CANWriteFrame(&LEAF_1F2); //Contains (CHG_STA_RQ == 1 == Normal Charge)
|
||||
ESP32Can.CANWriteFrame(&LEAF_1F2); //Contains (CHG_STA_RQ == 1 == Normal Charge)
|
||||
#endif
|
||||
|
||||
mprun10r = (mprun10r + 1) % 20; // 0x1F2 patter repeats after 20 messages. 0-1..19-0
|
||||
mprun10r = (mprun10r + 1) % 20; // 0x1F2 patter repeats after 20 messages. 0-1..19-0
|
||||
|
||||
mprun10 = (mprun10 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1...
|
||||
}
|
||||
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
//When battery requests heating pack status change, ack this
|
||||
if (Batt_Heater_Mail_Send_Request) {
|
||||
LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK
|
||||
} else {
|
||||
LEAF_50B.data.u8[6] = 0x00; //Batt_Heater_Mail_Send_NG
|
||||
mprun10 = (mprun10 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1...
|
||||
}
|
||||
|
||||
// VCM message, containing info if battery should sleep or stay awake
|
||||
ESP32Can.CANWriteFrame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
LEAF_50C.data.u8[3] = mprun100;
|
||||
switch (mprun100) {
|
||||
case 0:
|
||||
LEAF_50C.data.u8[4] = 0x5D;
|
||||
LEAF_50C.data.u8[5] = 0xC8;
|
||||
break;
|
||||
case 1:
|
||||
LEAF_50C.data.u8[4] = 0xB2;
|
||||
LEAF_50C.data.u8[5] = 0x31;
|
||||
break;
|
||||
case 2:
|
||||
LEAF_50C.data.u8[4] = 0x5D;
|
||||
LEAF_50C.data.u8[5] = 0x63;
|
||||
break;
|
||||
case 3:
|
||||
LEAF_50C.data.u8[4] = 0xB2;
|
||||
LEAF_50C.data.u8[5] = 0x9A;
|
||||
break;
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&LEAF_50C);
|
||||
//When battery requests heating pack status change, ack this
|
||||
if (Batt_Heater_Mail_Send_Request) {
|
||||
LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK
|
||||
} else {
|
||||
LEAF_50B.data.u8[6] = 0x00; //Batt_Heater_Mail_Send_NG
|
||||
}
|
||||
|
||||
mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1...
|
||||
}
|
||||
// VCM message, containing info if battery should sleep or stay awake
|
||||
ESP32Can.CANWriteFrame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1
|
||||
|
||||
//Send 10s CAN messages
|
||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||
previousMillis10s = currentMillis;
|
||||
LEAF_50C.data.u8[3] = mprun100;
|
||||
switch (mprun100) {
|
||||
case 0:
|
||||
LEAF_50C.data.u8[4] = 0x5D;
|
||||
LEAF_50C.data.u8[5] = 0xC8;
|
||||
break;
|
||||
case 1:
|
||||
LEAF_50C.data.u8[4] = 0xB2;
|
||||
LEAF_50C.data.u8[5] = 0x31;
|
||||
break;
|
||||
case 2:
|
||||
LEAF_50C.data.u8[4] = 0x5D;
|
||||
LEAF_50C.data.u8[5] = 0x63;
|
||||
break;
|
||||
case 3:
|
||||
LEAF_50C.data.u8[4] = 0xB2;
|
||||
LEAF_50C.data.u8[5] = 0x9A;
|
||||
break;
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&LEAF_50C);
|
||||
|
||||
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
|
||||
if (!stop_battery_query) {
|
||||
group = (group == 1) ? 2 : (group == 2) ? 4 : 1;
|
||||
// Cycle between group 1, 2, and 4 using ternary operation
|
||||
LEAF_GROUP_REQUEST.data.u8[2] = group;
|
||||
ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST);
|
||||
mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1...
|
||||
}
|
||||
|
||||
if (hold_off_with_polling_10seconds > 0) {
|
||||
hold_off_with_polling_10seconds--;
|
||||
} else {
|
||||
stop_battery_query = 0;
|
||||
//Send 10s CAN messages
|
||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||
previousMillis10s = currentMillis;
|
||||
|
||||
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
|
||||
if (!stop_battery_query) {
|
||||
group = (group == 1) ? 2 : (group == 2) ? 4 : 1;
|
||||
// Cycle between group 1, 2, and 4 using ternary operation
|
||||
LEAF_GROUP_REQUEST.data.u8[2] = group;
|
||||
ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST);
|
||||
}
|
||||
|
||||
if (hold_off_with_polling_10seconds > 0) {
|
||||
hold_off_with_polling_10seconds--;
|
||||
} else {
|
||||
stop_battery_query = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
|
||||
bool is_message_corrupt(CAN_frame_t rx_frame);
|
||||
|
|
196
Software/src/battery/PYLON-BATTERY.cpp
Normal file
196
Software/src/battery/PYLON-BATTERY.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include "../include.h"
|
||||
#ifdef PYLON_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "PYLON-BATTERY.h"
|
||||
|
||||
/* 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
|
||||
CAN_frame_t PYLON_3010 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x3010,
|
||||
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_8200 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x8200,
|
||||
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_8210 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x8210,
|
||||
.data = {0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame_t PYLON_4200 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x4200,
|
||||
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
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 = 0;
|
||||
static uint16_t cellvoltage_min_mV = 0;
|
||||
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 battery_module_quantity = 0;
|
||||
static uint8_t battery_modules_in_series = 0;
|
||||
static uint8_t cell_quantity_in_module = 0;
|
||||
static uint8_t voltage_level = 0;
|
||||
static uint8_t ah_number = 0;
|
||||
static uint8_t SOC = 0;
|
||||
static uint8_t SOH = 0;
|
||||
static uint8_t charge_forbidden = 0;
|
||||
static uint8_t discharge_forbidden = 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.active_power_W = //Power in watts, Negative = charging batt
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x7310:
|
||||
case 0x7311:
|
||||
ensemble_info_ack = true;
|
||||
// This message contains software/hardware version info. No interest to us
|
||||
break;
|
||||
case 0x7320:
|
||||
case 0x7321:
|
||||
ensemble_info_ack = true;
|
||||
battery_module_quantity = rx_frame.data.u8[0];
|
||||
battery_modules_in_series = rx_frame.data.u8[2];
|
||||
cell_quantity_in_module = rx_frame.data.u8[3];
|
||||
voltage_level = rx_frame.data.u8[4];
|
||||
ah_number = rx_frame.data.u8[6];
|
||||
break;
|
||||
case 0x4210:
|
||||
case 0x4211:
|
||||
voltage_dV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
current_dA = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 30000;
|
||||
SOC = rx_frame.data.u8[6];
|
||||
SOH = rx_frame.data.u8[7];
|
||||
break;
|
||||
case 0x4220:
|
||||
case 0x4221:
|
||||
charge_cutoff_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
discharge_cutoff_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
max_charge_current = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * 0.1) - 3000;
|
||||
max_discharge_current = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) * 0.1) - 3000;
|
||||
break;
|
||||
case 0x4230:
|
||||
case 0x4231:
|
||||
cellvoltage_max_mV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
|
||||
cellvoltage_min_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
break;
|
||||
case 0x4240:
|
||||
case 0x4241:
|
||||
celltemperature_max_dC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) - 1000;
|
||||
celltemperature_min_dC = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 1000;
|
||||
break;
|
||||
case 0x4250:
|
||||
case 0x4251:
|
||||
//Byte0 Basic Status
|
||||
//Byte1-2 Cycle Period
|
||||
//Byte3 Error
|
||||
//Byte4-5 Alarm
|
||||
//Byte6-7 Protection
|
||||
break;
|
||||
case 0x4260:
|
||||
case 0x4261:
|
||||
//Byte0-1 Module Max Voltage
|
||||
//Byte2-3 Module Min Voltage
|
||||
//Byte4-5 Module Max. Voltage Number
|
||||
//Byte6-7 Module Min. Voltage Number
|
||||
break;
|
||||
case 0x4270:
|
||||
case 0x4271:
|
||||
//Byte0-1 Module Max. Temperature
|
||||
//Byte2-3 Module Min. Temperature
|
||||
//Byte4-5 Module Max. Temperature Number
|
||||
//Byte6-7 Module Min. Temperature Number
|
||||
break;
|
||||
case 0x4280:
|
||||
case 0x4281:
|
||||
charge_forbidden = rx_frame.data.u8[0];
|
||||
discharge_forbidden = rx_frame.data.u8[1];
|
||||
break;
|
||||
case 0x4290:
|
||||
case 0x4291:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 1s CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
|
||||
previousMillis1000 = currentMillis;
|
||||
|
||||
ESP32Can.CANWriteFrame(&PYLON_3010); // Heartbeat
|
||||
ESP32Can.CANWriteFrame(&PYLON_4200); // Ensemble OR System equipment info, depends on frame0
|
||||
ESP32Can.CANWriteFrame(&PYLON_8200); // Control device quit sleep status
|
||||
ESP32Can.CANWriteFrame(&PYLON_8210); // Charge command
|
||||
|
||||
if (ensemble_info_ack) {
|
||||
PYLON_4200.data.u8[0] = 0x00; //Request system equipment info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Pylon battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040; // 404.0V, charging over this is not possible
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V, under this, discharging further is disabled
|
||||
}
|
||||
|
||||
#endif
|
12
Software/src/battery/PYLON-BATTERY.h
Normal file
12
Software/src/battery/PYLON-BATTERY.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef PYLON_BATTERY_H
|
||||
#define PYLON_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -6,10 +6,23 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-KANGOO-BATTERY.h"
|
||||
|
||||
/* TODO:
|
||||
There seems to be some values on the Kangoo that differ between the 22/33 kWh version
|
||||
- Find some way to autodetect which Kangoo size we are working with
|
||||
- Fix the mappings of values accordingly
|
||||
- Values still need fixing
|
||||
- SOC% is not valid on all packs
|
||||
-Try to use the 7BB value?
|
||||
- Max charge power is 0W on some packs
|
||||
- SOH% is too high on some packs
|
||||
- Add all cellvoltages from https://github.com/jamiejones85/Kangoo36_canDecode/tree/main
|
||||
|
||||
This page has info on the larger 33kWh pack: https://openinverter.org/wiki/Renault_Kangoo_36
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint32_t LB_Battery_Voltage = 3700;
|
||||
static uint32_t LB_Charge_Power_Limit_Watts = 0;
|
||||
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static int16_t LB_MAX_TEMPERATURE = 0;
|
||||
static int16_t LB_MIN_TEMPERATURE = 0;
|
||||
|
@ -20,10 +33,19 @@ static uint16_t LB_Charge_Power_Limit = 0;
|
|||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint16_t LB_MaxChargeAllowed_W = 0;
|
||||
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
|
||||
static uint8_t GVI_Pollcounter = 0;
|
||||
static uint8_t LB_EOCR = 0;
|
||||
static uint8_t LB_HVBUV = 0;
|
||||
static uint8_t LB_HVBIR = 0;
|
||||
static uint8_t LB_CUV = 0;
|
||||
static uint8_t LB_COV = 0;
|
||||
static uint8_t LB_HVBOV = 0;
|
||||
static uint8_t LB_HVBOT = 0;
|
||||
static uint8_t LB_HVBOC = 0;
|
||||
static uint8_t LB_MaxInput_kW = 0;
|
||||
static uint8_t LB_MaxOutput_kW = 0;
|
||||
static bool GVB_79B_Continue = false;
|
||||
|
||||
CAN_frame_t KANGOO_423 = {.FIR = {.B =
|
||||
|
@ -32,7 +54,11 @@ CAN_frame_t KANGOO_423 = {.FIR = {.B =
|
|||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x423,
|
||||
.data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
.data = {0x0B, 0x1D, 0x00, 0x02, 0xB2, 0x20, 0xB2, 0xD9}}; // Charging
|
||||
// Driving: 0x07 0x1D 0x00 0x02 0x5D 0x80 0x5D 0xD8
|
||||
// Charging: 0x0B 0x1D 0x00 0x02 0xB2 0x20 0xB2 0xD9
|
||||
// Fastcharging: 0x07 0x1E 0x00 0x01 0x5D 0x20 0xB2 0xC7
|
||||
// Old hardcoded message: .data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
CAN_frame_t KANGOO_79B = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
|
@ -53,47 +79,32 @@ static unsigned long previousMillis100 = 0; // will store last time a 100ms CA
|
|||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
static unsigned long GVL_pause = 0;
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 100); //increase LB_SOC range from 0-100 -> 100.00
|
||||
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
if (datalayer.battery.status.soh_pptt > 10000) { // Cap value if glitched out
|
||||
datalayer.battery.status.soh_pptt = 10000;
|
||||
}
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current;
|
||||
datalayer.battery.status.current_dA = LB_Current * 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);
|
||||
|
||||
LB_Discharge_Power_Limit_Watts = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
/* Define power able to be discharged from battery */
|
||||
if (LB_Discharge_Power_Limit_Watts > 60000) //if >60kW can be pulled from battery
|
||||
{
|
||||
datalayer.battery.status.max_discharge_power_W = 60000; //cap value so we don't go over the uint16 limit
|
||||
} else {
|
||||
datalayer.battery.status.max_discharge_power_W = LB_Discharge_Power_Limit_Watts;
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further
|
||||
{
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W =
|
||||
(LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
|
||||
LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts
|
||||
/* Define power able to be put into the battery */
|
||||
if (LB_Charge_Power_Limit_Watts > 60000) //if >60kW can be put into the battery
|
||||
{
|
||||
datalayer.battery.status.max_charge_power_W = 60000; //cap value so we don't go over the uint16 limit
|
||||
} else {
|
||||
datalayer.battery.status.max_charge_power_W = LB_Charge_Power_Limit_Watts;
|
||||
}
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
datalayer.battery.status.max_charge_power_W = 0; //No need to charge further, set max power to 0
|
||||
}
|
||||
//The above value is 0 on some packs. We instead hardcode this now.
|
||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W;
|
||||
|
||||
datalayer.battery.status.active_power_W =
|
||||
(datalayer.battery.status.voltage_dV * LB_Current); //TODO: check if scaling is OK
|
||||
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = (LB_MIN_TEMPERATURE * 10);
|
||||
|
||||
|
@ -103,26 +114,11 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
|
||||
|
||||
cell_deviation_mV = (datalayer.battery.status.temperature_max_dC - datalayer.battery.status.temperature_min_dC);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, (LB_Cell_Max_Voltage / 20));
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, (LB_Cell_Min_Voltage / 20));
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -159,31 +155,41 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) //GKOE reworked
|
||||
{
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x155: //BMS1
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
case 0x155: //BMS1
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_MaxChargeAllowed_W = (rx_frame.data.u8[0] * 300);
|
||||
LB_Current = word((rx_frame.data.u8[1] & 0xF), rx_frame.data.u8[2]) * 0.25 - 500; //OK!
|
||||
|
||||
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5])) * 0.0025; //OK!
|
||||
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5])) * 0.0025; //OK!
|
||||
break;
|
||||
|
||||
case 0x424: //BMS2
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_SOH = (rx_frame.data.u8[5]);
|
||||
case 0x424: //BMS2
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_EOCR = (rx_frame.data.u8[0] & 0x03);
|
||||
LB_HVBUV = (rx_frame.data.u8[0] & 0x0C) >> 2;
|
||||
LB_HVBIR = (rx_frame.data.u8[0] & 0x30) >> 4;
|
||||
LB_CUV = (rx_frame.data.u8[0] & 0xC0) >> 6;
|
||||
LB_COV = (rx_frame.data.u8[1] & 0x03);
|
||||
LB_HVBOV = (rx_frame.data.u8[1] & 0x0C) >> 2;
|
||||
LB_HVBOT = (rx_frame.data.u8[1] & 0x30) >> 4;
|
||||
LB_HVBOC = (rx_frame.data.u8[1] & 0xC0) >> 6;
|
||||
LB_MaxInput_kW = rx_frame.data.u8[2] / 2;
|
||||
LB_MaxOutput_kW = rx_frame.data.u8[3] / 2;
|
||||
LB_SOH = (rx_frame.data.u8[5]); // Only seems valid on Kangoo33?
|
||||
LB_MIN_TEMPERATURE = ((rx_frame.data.u8[4]) - 40); //OK!
|
||||
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7]) - 40); //OK!
|
||||
break;
|
||||
|
||||
case 0x425:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_kWh_Remaining = word((rx_frame.data.u8[0] & 0x1), rx_frame.data.u8[1]) / 10; //OK!
|
||||
break;
|
||||
|
||||
case 0x445:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
LB_Cell_Max_Voltage = 1000 + word((rx_frame.data.u8[3] & 0x1), rx_frame.data.u8[4]) * 10; //OK!
|
||||
LB_Cell_Min_Voltage = 1000 + (word(rx_frame.data.u8[5], rx_frame.data.u8[6]) >> 7) * 10; //OK!
|
||||
|
||||
|
@ -194,7 +200,8 @@ void receive_can_battery(CAN_frame_t rx_frame) //GKOE reworked
|
|||
}
|
||||
break;
|
||||
case 0x7BB:
|
||||
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
|
||||
|
||||
if (rx_frame.data.u8[0] == 0x10) { //1st response Bytes 0-7
|
||||
GVB_79B_Continue = true;
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE \
|
||||
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE \
|
||||
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
|
||||
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4150 // If cellvoltage goes over this mV, we go into FAULT mode
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 2500 // If cellvoltage goes under this mV, we go into FAULT mode
|
||||
#define MAX_CELL_DEVIATION_MV 500 // If cell mV delta exceeds this, we go into WARNING mode
|
||||
#define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
#include "../include.h"
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-ZOE-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_MIN_TEMPERATURE = 0;
|
||||
static int16_t LB_MAX_TEMPERATURE = 0;
|
||||
static uint16_t LB_Discharge_Power_Limit = 0;
|
||||
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
|
||||
static uint16_t LB_Charge_Power_Limit = 0;
|
||||
static uint32_t LB_Charge_Power_Limit_Watts = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint32_t LB_Battery_Voltage = 3700;
|
||||
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
|
||||
|
||||
CAN_frame_t ZOE_423 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x423,
|
||||
.data = {0x33, 0x00, 0xFF, 0xFF, 0x00, 0xE0, 0x00, 0x00}};
|
||||
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
static unsigned long GVL_pause = 0;
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current;
|
||||
|
||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values going to inverter:");
|
||||
Serial.print("SOH%: ");
|
||||
Serial.print(datalayer.battery.status.soh_pptt);
|
||||
Serial.print(", SOC% scaled: ");
|
||||
Serial.print(datalayer.battery.status.reported_soc);
|
||||
Serial.print(", Voltage: ");
|
||||
Serial.print(datalayer.battery.status.voltage_dV);
|
||||
Serial.print(", Max discharge power: ");
|
||||
Serial.print(datalayer.battery.status.max_discharge_power_W);
|
||||
Serial.print(", Max charge power: ");
|
||||
Serial.print(datalayer.battery.status.max_charge_power_W);
|
||||
Serial.print(", Max temp: ");
|
||||
Serial.print(datalayer.battery.status.temperature_max_dC);
|
||||
Serial.print(", Min temp: ");
|
||||
Serial.print(datalayer.battery.status.temperature_min_dC);
|
||||
Serial.print(", BMS Status (3=OK): ");
|
||||
Serial.print(datalayer.battery.status.bms_status);
|
||||
|
||||
Serial.println("Battery values: ");
|
||||
Serial.print("Real SOC: ");
|
||||
Serial.print(LB_SOC);
|
||||
Serial.print(", Current: ");
|
||||
Serial.print(LB_Current);
|
||||
Serial.print(", kWh remain: ");
|
||||
Serial.print(LB_kWh_Remaining);
|
||||
Serial.print(", max mV: ");
|
||||
Serial.print(LB_Cell_Max_Voltage);
|
||||
Serial.print(", min mV: ");
|
||||
Serial.print(LB_Cell_Min_Voltage);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x42E: //HV SOC & Battery Temp & Charging Power
|
||||
break;
|
||||
case 0x430: //HVBatteryCoolingState & HVBatteryEvapTemp & HVBatteryEvapSetpoint
|
||||
break;
|
||||
case 0x432: //BatVEShutDownAlert & HVBatCondPriorityLevel & HVBatteryLevelAlert & HVBatCondPriorityLevel & HVBatteryConditioningMode
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
//ESP32Can.CANWriteFrame(&ZOE_423);
|
||||
}
|
||||
// 1000ms CAN handling
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
previousMillis1000 = currentMillis;
|
||||
//ESP32Can.CANWriteFrame(&ZOE_423);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef RENAULT_ZOE_BATTERY_H
|
||||
#define RENAULT_ZOE_BATTERY_H
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE \
|
||||
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE \
|
||||
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
|
||||
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
144
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
Normal file
144
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
#include "../include.h"
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||
|
||||
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
|
||||
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp
|
||||
/*
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_Average_Temperature = 0;
|
||||
static uint32_t LB_Charge_Power_W = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t LB_Battery_Voltage = 3700;
|
||||
|
||||
CAN_frame_t ZOE_423 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x423,
|
||||
.data = {0x07, 0x1d, 0x00, 0x02, 0x5d, 0x80, 0x5d, 0xc8}};
|
||||
CAN_frame_t ZOE_POLL_79B = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x79B, //0x41 = cell bat module 1-62 , 0x42 = cell bat module 63-96
|
||||
.data = {0x02, 0x21, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 1000ms CAN Message was sent
|
||||
static uint8_t counter_423 = 0;
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00%
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); // Increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current; //TODO: Take from CAN
|
||||
|
||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = 5000; //TODO: Take from CAN
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = LB_Charge_Power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = LB_Average_Temperature;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = LB_Average_Temperature;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x427:
|
||||
LB_Charge_Power_W = rx_frame.data.u8[5] * 300;
|
||||
LB_kWh_Remaining = (((((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])) >> 6) & 0x3ff) * 0.1);
|
||||
break;
|
||||
case 0x42E: //HV SOC & Battery Temp & Charging Power
|
||||
LB_Battery_Voltage = (((((rx_frame.data.u8[3] << 8) | (rx_frame.data.u8[4])) >> 5) & 0x3ff) * 0.5); //0.5V/bit
|
||||
LB_Average_Temperature = (((((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6])) >> 5) & 0x7F) - 40);
|
||||
break;
|
||||
case 0x654: //SOC
|
||||
LB_SOC = rx_frame.data.u8[3];
|
||||
break;
|
||||
case 0x658: //SOH
|
||||
LB_SOH = (rx_frame.data.u8[4] & 0x7F);
|
||||
break;
|
||||
case 0x7BB: //Reply from active polling
|
||||
// TODO: Handle the cellvoltages
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
ESP32Can.CANWriteFrame(&ZOE_423);
|
||||
|
||||
if ((counter_423 / 5) % 2 == 0) { // Alternate every 5 messages between these two
|
||||
ZOE_423.data.u8[4] = 0xB2;
|
||||
ZOE_423.data.u8[6] = 0xB2;
|
||||
} else {
|
||||
ZOE_423.data.u8[4] = 0x5D;
|
||||
ZOE_423.data.u8[6] = 0x5D;
|
||||
}
|
||||
counter_423 = (counter_423 + 1) % 10;
|
||||
}
|
||||
// 5000ms CAN handling
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
previousMillis5000 = currentMillis;
|
||||
ESP32Can.CANWriteFrame(&ZOE_POLL_79B);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe 22/40kWh battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 2700;
|
||||
}
|
||||
|
||||
#endif
|
14
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
Normal file
14
Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#ifndef RENAULT_ZOE_GEN1_BATTERY_H
|
||||
#define RENAULT_ZOE_GEN1_BATTERY_H
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
113
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
Normal file
113
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "../include.h"
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||
|
||||
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
|
||||
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
|
||||
/*
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static uint16_t LB_SOC = 50;
|
||||
static uint16_t LB_SOH = 99;
|
||||
static int16_t LB_Average_Temperature = 0;
|
||||
static uint32_t LB_Charge_Power_W = 0;
|
||||
static int32_t LB_Current = 0;
|
||||
static uint16_t LB_kWh_Remaining = 0;
|
||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
||||
static uint16_t LB_Battery_Voltage = 3700;
|
||||
|
||||
CAN_frame_t ZOE_373 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_std,
|
||||
}},
|
||||
.MsgID = 0x373,
|
||||
.data = {0xC1, 0x80, 0x5D, 0x5D, 0x00, 0x00, 0xff, 0xcb}};
|
||||
CAN_frame_t ZOE_POLL_18DADBF1 = {.FIR = {.B =
|
||||
{
|
||||
.DLC = 8,
|
||||
.FF = CAN_frame_ext,
|
||||
}},
|
||||
.MsgID = 0x18DADBF1,
|
||||
.data = {0x03, 0x22, 0x90, 0x00, 0xff, 0xff, 0xff, 0xff}};
|
||||
|
||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
||||
|
||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
||||
|
||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = LB_Current;
|
||||
|
||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W;
|
||||
|
||||
datalayer.battery.status.active_power_W;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV;
|
||||
|
||||
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
|
||||
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x18daf1db: // LBC Reply from active polling
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 200ms CAN Message
|
||||
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
||||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis200 >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
|
||||
}
|
||||
previousMillis200 = currentMillis;
|
||||
ESP32Can.CANWriteFrame(&ZOE_373);
|
||||
ESP32Can.CANWriteFrame(&ZOE_POLL_18DADBF1);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe 50kWh battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = 4040;
|
||||
datalayer.battery.info.min_design_voltage_dV = 3100;
|
||||
}
|
||||
|
||||
#endif
|
14
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
Normal file
14
Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#ifndef RENAULT_ZOE_GEN2_BATTERY_H
|
||||
#define RENAULT_ZOE_GEN2_BATTERY_H
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
||||
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
|
||||
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
|
||||
#define MAX_CELL_DEVIATION_MV 500
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
#endif
|
|
@ -18,7 +18,6 @@ TODO: Map all values from battery CAN messages
|
|||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
static int SOC_1 = 0;
|
||||
static int SOC_2 = 0;
|
||||
|
@ -77,21 +76,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_max_dC;
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x200:
|
||||
break;
|
||||
|
@ -132,6 +123,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis10 >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis10));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
|
||||
uint8_t CalculateCRC8(CAN_frame_t rx_frame);
|
||||
void setup_battery(void);
|
||||
|
|
|
@ -40,7 +40,7 @@ void __getData() {
|
|||
(uint32_t)(dataLinkReceive.getReceivedData(6) * 10); //add back missing decimal
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
(uint32_t)(dataLinkReceive.getReceivedData(7) * 10); //add back missing decimal
|
||||
uint16_t _datalayer.battery.status.bms_status = (uint16_t)dataLinkReceive.getReceivedData(8);
|
||||
uint16_t _system_bms_status = (uint16_t)dataLinkReceive.getReceivedData(8);
|
||||
datalayer.battery.status.active_power_W =
|
||||
(uint32_t)(dataLinkReceive.getReceivedData(9) * 10); //add back missing decimal
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)dataLinkReceive.getReceivedData(10);
|
||||
|
@ -51,7 +51,7 @@ void __getData() {
|
|||
datalayer.system.status.battery_allows_contactor_closing = (bool)dataLinkReceive.getReceivedData(15);
|
||||
|
||||
batteryFault = false;
|
||||
if (_datalayer.battery.status.bms_status == FAULT) {
|
||||
if (_system_bms_status == FAULT) {
|
||||
batteryFault = true;
|
||||
set_event(EVENT_SERIAL_TRANSMITTER_FAILURE, 0);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#ifndef MAX_CELL_DEVIATION_MV
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
#endif
|
||||
|
||||
#include "../include.h"
|
||||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
|
||||
|
||||
static unsigned long previousMillis30 = 0; // will store last time a 30ms CAN Message was send
|
||||
static uint8_t stillAliveCAN = 6; //counter for checking if CAN is still alive
|
||||
|
||||
CAN_frame_t TESLA_221_1 = {
|
||||
.FIR = {.B =
|
||||
|
@ -33,7 +32,6 @@ static uint32_t total_discharge = 0;
|
|||
static uint32_t total_charge = 0;
|
||||
static uint16_t volts = 0; // V
|
||||
static int16_t amps = 0; // A
|
||||
static int16_t power = 0; // W
|
||||
static uint16_t raw_amps = 0; // A
|
||||
static int16_t max_temp = 0; // C*
|
||||
static int16_t min_temp = 0; // C*
|
||||
|
@ -164,19 +162,8 @@ static const char* hvilStatusState[] = {"NOT OK",
|
|||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||
//After values are mapped, we perform some safety checks, and do some serial printouts
|
||||
//Calculate the SOH% to send to inverter
|
||||
if (bat_beginning_of_life != 0) { //div/0 safeguard
|
||||
datalayer.battery.status.soh_pptt =
|
||||
static_cast<uint16_t>((static_cast<double>(nominal_full_pack_energy) / bat_beginning_of_life) * 10000.0);
|
||||
}
|
||||
//If the calculation went wrong, set SOH to 100%
|
||||
if (datalayer.battery.status.soh_pptt > 10000) {
|
||||
datalayer.battery.status.soh_pptt = 10000;
|
||||
}
|
||||
//If the value is unavailable, set SOH to 99%
|
||||
if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
|
||||
datalayer.battery.status.soh_pptt = 9900;
|
||||
}
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; //Tesla batteries do not send a SOH% value on bus. Hardcode to 99%
|
||||
|
||||
datalayer.battery.status.real_soc = (soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00
|
||||
|
||||
|
@ -190,17 +177,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
// Define the allowed discharge power
|
||||
datalayer.battery.status.max_discharge_power_W = (max_discharge_current * volts);
|
||||
// Cap the allowed discharge power if battery is empty, or discharge power is higher than the maximum discharge power allowed
|
||||
if (datalayer.battery.status.reported_soc == 0) {
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
|
||||
// Cap the allowed discharge power if higher than the maximum discharge power allowed
|
||||
if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
|
||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
||||
}
|
||||
|
||||
//The allowed charge power behaves strangely. We instead estimate this value
|
||||
if (datalayer.battery.status.reported_soc == 10000) { // When scaled SOC is 100.00%, set allowed charge power to 0
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else if (soc_vi > 990) {
|
||||
if (soc_vi > 990) {
|
||||
datalayer.battery.status.max_charge_power_W = FLOAT_MAX_POWER_W;
|
||||
} else if (soc_vi > RAMPDOWN_SOC) { // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
|
@ -219,8 +202,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||
}
|
||||
|
||||
power = ((volts / 10) * amps);
|
||||
datalayer.battery.status.active_power_W = power;
|
||||
datalayer.battery.status.active_power_W = ((volts / 10) * amps);
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = min_temp;
|
||||
|
||||
|
@ -232,14 +214,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
/* Value mapping is completed. Start to check all safeties */
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!stillAliveCAN) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
stillAliveCAN--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
|
||||
set_event(EVENT_INTERNAL_OPEN_FAULT, 0);
|
||||
} else {
|
||||
|
@ -312,12 +286,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.bms_status ==
|
||||
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
}
|
||||
|
||||
/* Safeties verified. Perform USB serial printout if configured to do so */
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -513,7 +481,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100;
|
||||
break;
|
||||
case 0x292:
|
||||
stillAliveCAN = 12; //We are getting CAN messages from the BMS, set the CAN detect counter
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS
|
||||
bat_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]);
|
||||
soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]);
|
||||
soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2));
|
||||
|
@ -589,6 +557,8 @@ the first, for a few cycles, then stop all messages which causes the contactor
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis30 >= INTERVAL_30_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis30));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis30 = currentMillis;
|
||||
|
||||
|
@ -636,8 +606,8 @@ void printFaultCodesIfActive() {
|
|||
}
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing == false) {
|
||||
Serial.println(
|
||||
"ERROR: Solar inverter does not allow for contactor closing. Check "
|
||||
"datalayer.system.status.inverter_allows_contactor_closing parameter");
|
||||
"ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR "
|
||||
"disable the inverter protocol to proceed with contactor closing");
|
||||
}
|
||||
// Check each symbol and print debug information if its value is 1
|
||||
printDebugIfActive(WatchdogReset, "ERROR: The processor has experienced a reset due to watchdog reset");
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
//#define LFP_CHEMISTRY // Enable this line to startup in LFP mode
|
||||
|
||||
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||
#define MAX_CELL_DEVIATION_MV 9999 // Handled inside the Tesla.cpp file, just for compilation
|
||||
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
|
||||
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
|
||||
#define MAXCHARGEPOWERALLOWED 15000 // 15000W we use a define since the value supplied by Tesla is always 0
|
||||
|
|
|
@ -16,6 +16,7 @@ void print_units(char* header, int value, char* units) {
|
|||
}
|
||||
|
||||
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
||||
|
||||
datalayer.battery.status.real_soc = 5000; // 50.00%
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
||||
|
@ -46,6 +47,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
|
||||
}
|
||||
|
||||
//Fake that we get CAN messages
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("FAKE Values going to inverter");
|
||||
|
@ -62,7 +66,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) { // All CAN messages recieved will be logged via serial
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.MsgID, HEX);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 9999
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
|
||||
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
|
||||
|
||||
#define MAX_CELL_VOLTAGE 4210 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
|
||||
#define MAX_CELL_VOLTAGE 4210 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
|
||||
static float BATT_U = 0; //0x3A
|
||||
static float MAX_U = 0; //0x3A
|
||||
|
@ -26,16 +24,14 @@ static float BATT_T_MIN = 0; //0x413
|
|||
static float BATT_T_AVG = 0; //0x413
|
||||
static uint16_t SOC_BMS = 0; //0X37D
|
||||
static uint16_t SOC_CALC = 0;
|
||||
static uint16_t CELL_U_MAX = 0; //0x37D
|
||||
static uint16_t CELL_U_MIN = 0; //0x37D
|
||||
static uint8_t CELL_ID_U_MAX = 0; //0x37D
|
||||
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
|
||||
|
||||
static uint16_t CELL_U_MAX = 0; //0x37D
|
||||
static uint16_t CELL_U_MIN = 0; //0x37D
|
||||
static uint8_t CELL_ID_U_MAX = 0; //0x37D
|
||||
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
|
||||
static uint8_t batteryModuleNumber = 0x10; // First battery module
|
||||
static uint8_t battery_request_idx = 0;
|
||||
static uint8_t rxConsecutiveFrames = 0;
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint8_t cellcounter = 0;
|
||||
static uint32_t remaining_capacity = 0;
|
||||
static uint16_t cell_voltages[108]; //array with all the cellvoltages
|
||||
|
@ -114,14 +110,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
|
||||
}
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!CANstillAlive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
CANstillAlive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("BMS reported SOC%: ");
|
||||
Serial.println(SOC_BMS);
|
||||
|
@ -159,8 +147,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
Serial.println(min_max_voltage[1]);
|
||||
Serial.print("Lowest cell voltage: ");
|
||||
Serial.println(min_max_voltage[0]);
|
||||
Serial.print("Cell deviation voltage: ");
|
||||
Serial.println(cell_deviation_mV);
|
||||
Serial.print("Cell voltage,");
|
||||
while (cnt < 108) {
|
||||
Serial.print(cell_voltages[cnt++]);
|
||||
|
@ -171,7 +157,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
}
|
||||
|
||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||
CANstillAlive = 12;
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x3A:
|
||||
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
|
||||
|
@ -314,12 +300,6 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
|||
min_max_voltage[1] = cell_voltages[cellcounter];
|
||||
}
|
||||
|
||||
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
|
||||
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
|
||||
}
|
||||
|
||||
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
|
||||
}
|
||||
|
@ -351,6 +331,8 @@ void send_can_battery() {
|
|||
// Check if sending of CAN messages has been delayed too much.
|
||||
if ((currentMillis - previousMillis100 >= INTERVAL_100_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis100));
|
||||
} else {
|
||||
clear_event(EVENT_CAN_OVERRUN);
|
||||
}
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
|
||||
void setup_battery(void);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef CHARGERS_H
|
||||
#define CHARGERS_H
|
||||
|
||||
#include "../../USER_SETTINGS.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" // This include is annoying, consider defining a frame type in types.h
|
||||
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
#include "CHEVY-VOLT-CHARGER.h"
|
||||
|
@ -11,4 +11,7 @@
|
|||
#include "NISSAN-LEAF-CHARGER.h"
|
||||
#endif
|
||||
|
||||
void receive_can_charger(CAN_frame_t rx_frame);
|
||||
void send_can_charger();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "../include.h"
|
||||
#ifdef CHEVYVOLT_CHARGER
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "CHEVY-VOLT-CHARGER.h"
|
||||
|
@ -63,7 +64,7 @@ static CAN_frame_t charger_set_targets = {.FIR = {.B =
|
|||
.data = {0x40, 0x00, 0x00, 0x00}};
|
||||
|
||||
/* We are mostly sending out not receiving */
|
||||
void receive_can_chevyvolt_charger(CAN_frame_t rx_frame) {
|
||||
void receive_can_charger(CAN_frame_t rx_frame) {
|
||||
uint16_t charger_stat_HVcur_temp = 0;
|
||||
uint16_t charger_stat_HVvol_temp = 0;
|
||||
uint16_t charger_stat_LVcur_temp = 0;
|
||||
|
@ -115,7 +116,7 @@ void receive_can_chevyvolt_charger(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_chevyvolt_charger() {
|
||||
void send_can_charger() {
|
||||
unsigned long currentMillis = millis();
|
||||
uint16_t Vol_temp = 0;
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define CHARGER_SELECTED
|
||||
|
||||
/* Charger hardware limits
|
||||
*
|
||||
* Relative to runtime settings, expectations are:
|
||||
|
@ -14,8 +16,4 @@
|
|||
#define CHEVYVOLT_MAX_AMP 11.5
|
||||
#define CHEVYVOLT_MAX_POWER 3300
|
||||
|
||||
void update_values_can_chevyvolt_charger();
|
||||
void send_can_chevyvolt_charger();
|
||||
void receive_can_chevyvolt_charger(CAN_frame_t rx_frame);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "../include.h"
|
||||
#ifdef NISSAN_LEAF_CHARGER
|
||||
#ifdef NISSANLEAF_CHARGER
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "NISSAN-LEAF-CHARGER.h"
|
||||
|
@ -144,7 +145,7 @@ static uint8_t calculate_checksum_nibble(CAN_frame_t* frame) {
|
|||
return sum;
|
||||
}
|
||||
|
||||
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame) {
|
||||
void receive_can_charger(CAN_frame_t rx_frame) {
|
||||
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x679: // This message fires once when charging cable is plugged in
|
||||
|
@ -181,7 +182,7 @@ void receive_can_nissanleaf_charger(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_nissanleaf_charger() {
|
||||
void send_can_charger() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
/* Send keepalive with mode every 10ms */
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
void send_can_nissanleaf_charger();
|
||||
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame);
|
||||
#define CHARGER_SELECTED
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,6 +30,8 @@ typedef struct {
|
|||
typedef struct {
|
||||
/** int32_t */
|
||||
/** Instantaneous battery power in Watts */
|
||||
/* Positive value = Battery Charging */
|
||||
/* Negative value = Battery Discharging */
|
||||
int32_t active_power_W;
|
||||
|
||||
/** uint32_t */
|
||||
|
@ -68,6 +70,14 @@ typedef struct {
|
|||
* battery.settings.soc_scaling_active
|
||||
*/
|
||||
uint16_t reported_soc;
|
||||
/** A counter that increases incase a CAN CRC read error occurs */
|
||||
uint16_t CAN_error_counter;
|
||||
/** uint8_t */
|
||||
/** A counter set each time a new message comes from battery.
|
||||
* This value then gets decremented each 5 seconds. Incase we reach 0
|
||||
* we report the battery as missing entirely on the CAN bus.
|
||||
*/
|
||||
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
||||
/** Other */
|
||||
/** The current BMS status */
|
||||
|
@ -93,6 +103,13 @@ typedef struct {
|
|||
DATALAYER_BATTERY_SETTINGS_TYPE settings;
|
||||
} DATALAYER_BATTERY_TYPE;
|
||||
|
||||
typedef struct {
|
||||
/** measured voltage in deciVolts. 4200 = 420.0 V */
|
||||
uint16_t measured_voltage_dV = 0;
|
||||
/** measured amperage in deciAmperes. 300 = 30.0 A */
|
||||
uint16_t measured_amperage_dA = 0;
|
||||
} DATALAYER_SHUNT_TYPE;
|
||||
|
||||
typedef struct {
|
||||
// TODO
|
||||
} DATALAYER_SYSTEM_INFO_TYPE;
|
||||
|
@ -103,13 +120,15 @@ typedef struct {
|
|||
int64_t core_task_max_us = 0;
|
||||
/** Core task measurement variable, reset each 10 seconds */
|
||||
int64_t core_task_10s_max_us = 0;
|
||||
/** MQTT task measurement variable, reset each 10 seconds */
|
||||
/** MQTT sub-task measurement variable, reset each 10 seconds */
|
||||
int64_t mqtt_task_10s_max_us = 0;
|
||||
/** Wifi sub-task measurement variable, reset each 10 seconds */
|
||||
int64_t wifi_task_10s_max_us = 0;
|
||||
/** loop() task measurement variable, reset each 10 seconds */
|
||||
int64_t loop_task_10s_max_us = 0;
|
||||
|
||||
/** OTA/Wifi handling function measurement variable */
|
||||
int64_t time_wifi_us = 0;
|
||||
/** OTA handling function measurement variable */
|
||||
int64_t time_ota_us = 0;
|
||||
/** CAN RX or serial link function measurement variable */
|
||||
int64_t time_comm_us = 0;
|
||||
/** 10 ms function measurement variable */
|
||||
|
@ -120,9 +139,9 @@ typedef struct {
|
|||
int64_t time_cantx_us = 0;
|
||||
|
||||
/** Function measurement snapshot variable.
|
||||
* This will show the performance of OTA/Wifi handling when the total time reached a new worst case
|
||||
* This will show the performance of OTA handling when the total time reached a new worst case
|
||||
*/
|
||||
int64_t time_snap_wifi_us = 0;
|
||||
int64_t time_snap_ota_us = 0;
|
||||
/** Function measurement snapshot variable.
|
||||
* This will show the performance of CAN RX or serial link when the total time reached a new worst case
|
||||
*/
|
||||
|
@ -161,6 +180,7 @@ class DataLayer {
|
|||
public:
|
||||
DATALAYER_BATTERY_TYPE battery;
|
||||
DATALAYER_BATTERY_TYPE battery2;
|
||||
DATALAYER_SHUNT_TYPE shunt;
|
||||
DATALAYER_SYSTEM_TYPE system;
|
||||
};
|
||||
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
|
||||
/* Select HW - DONT TOUCH */
|
||||
#define HW_LILYGO
|
||||
|
||||
#if defined(HW_LILYGO)
|
||||
#include "hw_lilygo.h"
|
||||
#elif defined(HW_STARK)
|
||||
#include "hw_stark.h"
|
||||
#elif defined(HW_SJB_V1)
|
||||
#include "hw_sjb_v1.h"
|
||||
#endif
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
#define MCP2517_CS 18 // CS input of MCP2517
|
||||
#define MCP2517_INT 35 // INT output of MCP2517
|
||||
|
||||
// CHAdeMO support pin dependencies
|
||||
#define CHADEMO_PIN_2 12
|
||||
#define CHADEMO_PIN_10 5
|
||||
#define CHADEMO_PIN_7 34
|
||||
#define CHADEMO_PIN_4 35
|
||||
#define CHADEMO_LOCK 18
|
||||
|
||||
// Contactor handling
|
||||
#define POSITIVE_CONTACTOR_PIN 32
|
||||
#define NEGATIVE_CONTACTOR_PIN 33
|
||||
|
@ -62,4 +69,10 @@
|
|||
#error Multiple HW defined! Please select a single HW
|
||||
#endif
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#ifdef DUAL_CAN
|
||||
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
66
Software/src/devboard/hal/hw_stark.h
Normal file
66
Software/src/devboard/hal/hw_stark.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef __HW_STARK06_H__
|
||||
#define __HW_STARK06_H__
|
||||
|
||||
// Board boot-up time
|
||||
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
|
||||
|
||||
// Core assignment
|
||||
#define CORE_FUNCTION_CORE 1
|
||||
#define MODBUS_CORE 0
|
||||
#define WIFI_CORE 0
|
||||
|
||||
// RS485
|
||||
// #define PIN_5V_EN 16 // No function, GPIO 16 used instead as MCP_SCK
|
||||
// #define RS485_EN_PIN 17 // RE, No function, GPIO 17 is instead available as extra GPIO via pin header
|
||||
#define RS485_TX_PIN 22
|
||||
#define RS485_RX_PIN 21
|
||||
// #define RS485_SE_PIN 19 // No function, GPIO 19 is instead available as extra GPIO via pin header
|
||||
|
||||
// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings
|
||||
#define CAN_1_TYPE ESP32CAN
|
||||
|
||||
// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB
|
||||
#define CAN_TX_PIN GPIO_NUM_27
|
||||
#define CAN_RX_PIN GPIO_NUM_26
|
||||
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
|
||||
|
||||
// CAN2 defines below
|
||||
|
||||
// DUAL_CAN defines
|
||||
//#define MCP2515_SCK 12 // SCK input of MCP2515
|
||||
//#define MCP2515_MOSI 5 // SDI input of MCP2515
|
||||
//#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
|
||||
//#define MCP2515_CS 18 // CS input of MCP2515
|
||||
//#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
|
||||
|
||||
// CAN_FD defines
|
||||
#define MCP2517_SCK 16 // SCK input of MCP2517 (Changed from 12 to 16 since 12 can be used for JTAG TDI)
|
||||
#define MCP2517_SDI 5 // SDI input of MCP2517
|
||||
#define MCP2517_SDO 34 // SDO output of MCP2517
|
||||
#define MCP2517_CS 18 // CS input of MCP2517
|
||||
#define MCP2517_INT 35 // INT output of MCP2517
|
||||
|
||||
// Contactor handling
|
||||
#define POSITIVE_CONTACTOR_PIN 32
|
||||
#define NEGATIVE_CONTACTOR_PIN 33
|
||||
#define PRECHARGE_PIN 25
|
||||
#define BMS_POWER 23 // Also connected to MCP_SCK
|
||||
|
||||
// SD card
|
||||
//#define SD_MISO_PIN 2
|
||||
//#define SD_MOSI_PIN 15
|
||||
//#define SD_SCLK_PIN 14
|
||||
//#define SD_CS_PIN 13
|
||||
|
||||
// LED
|
||||
#define LED_PIN 4
|
||||
#define LED_MAX_BRIGHTNESS 40
|
||||
|
||||
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
|
||||
#ifndef HW_CONFIGURED
|
||||
#define HW_CONFIGURED
|
||||
#else
|
||||
#error Multiple HW defined! Please select a single HW
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -9,9 +9,6 @@
|
|||
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
|
||||
#include "../utils/timer.h"
|
||||
|
||||
const char* mqtt_subscriptions[] = MQTT_SUBSCRIPTIONS;
|
||||
const size_t mqtt_nof_subscriptions = sizeof(mqtt_subscriptions) / sizeof(mqtt_subscriptions[0]);
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
|
||||
|
@ -75,7 +72,8 @@ static void publish_cell_voltages(void) {
|
|||
doc.clear(); // clear after sending autoconfig
|
||||
} else {
|
||||
// If cell voltages haven't been populated...
|
||||
if (datalayer.battery.info.number_of_cells == 0u) {
|
||||
if (datalayer.battery.info.number_of_cells == 0u ||
|
||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -158,10 +156,13 @@ static void publish_common_info(void) {
|
|||
doc["temperature_max"] = ((float)((int16_t)datalayer.battery.status.temperature_max_dC)) / 10.0;
|
||||
doc["stat_batt_power"] = ((float)((int32_t)datalayer.battery.status.active_power_W));
|
||||
doc["battery_current"] = ((float)((int16_t)datalayer.battery.status.current_dA)) / 10.0;
|
||||
doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0;
|
||||
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
|
||||
doc["battery_voltage"] = ((float)datalayer.battery.status.voltage_dV) / 10.0;
|
||||
|
||||
// publish only if cell voltages have been populated...
|
||||
if (datalayer.battery.info.number_of_cells != 0u &&
|
||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
|
||||
doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0;
|
||||
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
|
||||
}
|
||||
serializeJson(doc, mqtt_msg);
|
||||
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -172,20 +173,7 @@ static void publish_common_info(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* This is called whenever a subscribed topic changes (hopefully) */
|
||||
static void callback(char* topic, byte* payload, unsigned int length) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If we lose the connection, get it back and re-sub */
|
||||
/* If we lose the connection, get it back */
|
||||
static void reconnect() {
|
||||
// attempt one reconnection
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -199,14 +187,6 @@ static void reconnect() {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("connected");
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < mqtt_nof_subscriptions; i++) {
|
||||
client.subscribe(mqtt_subscriptions[i]);
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Subscribed to: ");
|
||||
Serial.println(mqtt_subscriptions[i]);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("failed, rc=");
|
||||
|
@ -219,7 +199,6 @@ static void reconnect() {
|
|||
|
||||
void init_mqtt(void) {
|
||||
client.setServer(MQTT_SERVER, MQTT_PORT);
|
||||
client.setCallback(callback);
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("MQTT initialized");
|
||||
#endif
|
||||
|
|
136
Software/src/devboard/safety/safety.cpp
Normal file
136
Software/src/devboard/safety/safety.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
#include "../utils/events.h"
|
||||
|
||||
static uint16_t cell_deviation_mV = 0;
|
||||
static uint8_t charge_limit_failures = 0;
|
||||
static uint8_t discharge_limit_failures = 0;
|
||||
static bool battery_full_event_fired = false;
|
||||
static bool battery_empty_event_fired = false;
|
||||
|
||||
void update_machineryprotection() {
|
||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||
|
||||
// Battery is overheated!
|
||||
if (datalayer.battery.status.temperature_max_dC > 500) {
|
||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERHEAT);
|
||||
}
|
||||
|
||||
// Battery is frozen!
|
||||
if (datalayer.battery.status.temperature_min_dC < -250) {
|
||||
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FROZEN);
|
||||
}
|
||||
|
||||
// Battery voltage is over designed max voltage!
|
||||
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_OVERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery voltage is under designed min voltage!
|
||||
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
|
||||
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
|
||||
}
|
||||
|
||||
// Battery is fully charged. Dont allow any more power into it
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
{
|
||||
if (!battery_full_event_fired) {
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
battery_full_event_fired = true;
|
||||
}
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_FULL);
|
||||
battery_full_event_fired = false;
|
||||
}
|
||||
|
||||
// Battery is empty. Do not allow further discharge.
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||
if (!battery_empty_event_fired) {
|
||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||
battery_empty_event_fired = true;
|
||||
}
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
} else {
|
||||
clear_event(EVENT_BATTERY_EMPTY);
|
||||
battery_empty_event_fired = false;
|
||||
}
|
||||
|
||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||
if (datalayer.battery.status.soh_pptt < 2500) {
|
||||
set_event(EVENT_LOW_SOH, datalayer.battery.status.soh_pptt);
|
||||
} else {
|
||||
clear_event(EVENT_LOW_SOH);
|
||||
}
|
||||
|
||||
// Check if SOC% is plausible
|
||||
if (datalayer.battery.status.voltage_dV >
|
||||
(datalayer.battery.info.max_design_voltage_dV -
|
||||
100)) { // When pack voltage is close to max, and SOC% is still low, raise event
|
||||
if (datalayer.battery.status.real_soc < 6500) { // 65.00%
|
||||
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, datalayer.battery.status.real_soc);
|
||||
} else {
|
||||
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// Check diff between highest and lowest cell
|
||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
|
||||
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
|
||||
} else {
|
||||
clear_event(EVENT_CELL_DEVIATION_HIGH);
|
||||
}
|
||||
|
||||
// Inverter is charging with more power than battery wants!
|
||||
if (datalayer.battery.status.active_power_W > 0) { // Charging
|
||||
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
||||
if (charge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
charge_limit_failures++;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
||||
charge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverter is pulling too much power from battery!
|
||||
if (datalayer.battery.status.active_power_W < 0) { // Discharging
|
||||
if (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
|
||||
if (discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||
} else {
|
||||
discharge_limit_failures++;
|
||||
}
|
||||
} else {
|
||||
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
||||
discharge_limit_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_RX_WARNING);
|
||||
}
|
||||
}
|
11
Software/src/devboard/safety/safety.h
Normal file
11
Software/src/devboard/safety/safety.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#ifndef SAFETY_H
|
||||
#define SAFETY_H
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MAX_CAN_FAILURES 50
|
||||
|
||||
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1
|
||||
|
||||
void update_machineryprotection();
|
||||
|
||||
#endif
|
|
@ -5,8 +5,14 @@
|
|||
#endif
|
||||
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime.h"
|
||||
#include "timer.h"
|
||||
|
||||
// Time conversion macros
|
||||
#define DAYS_TO_SECS 86400 // 24 * 60 * 60
|
||||
#define HOURS_TO_SECS 3600 // 60 * 60
|
||||
#define MINUTES_TO_SECS 60
|
||||
|
||||
#define EE_NOF_EVENT_ENTRIES 30
|
||||
#define EE_EVENT_ENTRY_SIZE sizeof(EVENT_LOG_ENTRY_TYPE)
|
||||
#define EE_WRITE_PERIOD_MINUTES 10
|
||||
|
@ -44,7 +50,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
|
||||
uint32_t time_seconds;
|
||||
unsigned long time_seconds;
|
||||
MyTimer second_timer;
|
||||
MyTimer ee_timer;
|
||||
MyTimer update_timer;
|
||||
|
@ -134,20 +140,30 @@ void init_events(void) {
|
|||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_CAUTION].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_CHG_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_OVERHEAT].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_BATTERY_OVERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_MODBUS_INVERTER_MISSING].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_ERROR_OPEN_CONTACTOR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_CELL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CELL_OVER_VOLTAGE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CELL_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
|
||||
|
@ -163,10 +179,26 @@ void init_events(void) {
|
|||
events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_SERIAL_TRANSMITTER_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_EEPROM_WRITE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_UNKNOWN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_POWERON].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_EXT].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_SW].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_PANIC].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_INT_WDT].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_TASK_WDT].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_WDT].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_RESET_DEEPSLEEP].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_BROWNOUT].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_SDIO].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_USB].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_JTAG].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_EFUSE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_PWR_GLITCH].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_CPU_LOCKUP].level = EVENT_LEVEL_WARNING;
|
||||
|
||||
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
|
||||
|
||||
events.second_timer.set_interval(1000);
|
||||
events.second_timer.set_interval(600);
|
||||
// Write to EEPROM every X minutes (if an event has been set)
|
||||
events.ee_timer.set_interval(EE_WRITE_PERIOD_MINUTES * 60 * 1000);
|
||||
events.update_timer.set_interval(2000);
|
||||
|
@ -204,6 +236,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
|
||||
case EVENT_CAN_TX_FAILURE:
|
||||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is charging faster than battery is allowing.";
|
||||
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is discharging faster than battery is allowing.";
|
||||
case EVENT_WATER_INGRESS:
|
||||
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
|
||||
case EVENT_12V_LOW:
|
||||
|
@ -216,6 +252,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: Battery is completely discharged";
|
||||
case EVENT_BATTERY_FULL:
|
||||
return "Info: Battery is fully charged";
|
||||
case EVENT_BATTERY_FROZEN:
|
||||
return "Info: Battery is too cold to operate optimally. Consider warming it up!";
|
||||
case EVENT_BATTERY_CAUTION:
|
||||
return "Info: Battery has raised a general caution flag. Might want to inspect it closely.";
|
||||
case EVENT_BATTERY_CHG_STOP_REQ:
|
||||
|
@ -228,18 +266,33 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: COLD BATTERY! Battery requesting heating pads to activate!";
|
||||
case EVENT_BATTERY_WARMED_UP:
|
||||
return "Info: Battery requesting heating pads to stop. The battery is now warm enough.";
|
||||
case EVENT_BATTERY_OVERHEAT:
|
||||
return "ERROR: Battery overheated. Shutting down to prevent thermal runaway!";
|
||||
case EVENT_BATTERY_OVERVOLTAGE:
|
||||
return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
|
||||
case EVENT_BATTERY_UNDERVOLTAGE:
|
||||
return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!";
|
||||
case EVENT_BATTERY_ISOLATION:
|
||||
return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!";
|
||||
case EVENT_VOLTAGE_DIFFERENCE:
|
||||
return "Info: Too large voltage diff between the batteries. Second battery cannot join the DC-link";
|
||||
case EVENT_LOW_SOH:
|
||||
return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle "
|
||||
"battery.";
|
||||
case EVENT_HVIL_FAILURE:
|
||||
return "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be "
|
||||
"disabled!";
|
||||
return "ERROR: Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. "
|
||||
"Battery will be disabled!";
|
||||
case EVENT_PRECHARGE_FAILURE:
|
||||
return "Info: Battery failed to precharge. Check that capacitor is seated on high voltage output.";
|
||||
case EVENT_INTERNAL_OPEN_FAULT:
|
||||
return "ERROR: High voltage cable removed while battery running. Opening contactors!";
|
||||
case EVENT_INVERTER_OPEN_CONTACTOR:
|
||||
return "Info: Inverter side opened contactors. Normal operation.";
|
||||
case EVENT_ERROR_OPEN_CONTACTOR:
|
||||
return "Info: Too much time spent in error state. Opening contactors, not safe to continue charging. "
|
||||
"Check other error code for reason!";
|
||||
case EVENT_MODBUS_INVERTER_MISSING:
|
||||
return "Info: Modbus inverter has not sent any data. Inspect communication wiring!";
|
||||
case EVENT_CELL_UNDER_VOLTAGE:
|
||||
return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
|
||||
case EVENT_CELL_OVER_VOLTAGE:
|
||||
|
@ -270,6 +323,39 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "OTA update timed out!";
|
||||
case EVENT_EEPROM_WRITE:
|
||||
return "Info: The EEPROM was written";
|
||||
case EVENT_RESET_UNKNOWN:
|
||||
return "Info: The board was reset unexpectedly, and reason can't be determined";
|
||||
case EVENT_RESET_POWERON:
|
||||
return "Info: The board was reset from a power-on event. Normal operation";
|
||||
case EVENT_RESET_EXT:
|
||||
return "Info: The board was reset from an external pin";
|
||||
case EVENT_RESET_SW:
|
||||
return "Info: The board was reset via software, webserver or OTA. Normal operation";
|
||||
case EVENT_RESET_PANIC:
|
||||
return "Warning: The board was reset due to an exception or panic. Inform developers!";
|
||||
case EVENT_RESET_INT_WDT:
|
||||
return "Warning: The board was reset due to an interrupt watchdog timeout. Inform developers!";
|
||||
case EVENT_RESET_TASK_WDT:
|
||||
return "Warning: The board was reset due to a task watchdog timeout. Inform developers!";
|
||||
case EVENT_RESET_WDT:
|
||||
return "Warning: The board was reset due to other watchdog timeout. Inform developers!";
|
||||
case EVENT_RESET_DEEPSLEEP:
|
||||
return "Info: The board was reset after exiting deep sleep mode";
|
||||
case EVENT_RESET_BROWNOUT:
|
||||
return "Info: The board was reset due to a momentary low voltage condition. This is expected during certain "
|
||||
"operations like flashing via USB";
|
||||
case EVENT_RESET_SDIO:
|
||||
return "Info: The board was reset over SDIO";
|
||||
case EVENT_RESET_USB:
|
||||
return "Info: The board was reset by the USB peripheral";
|
||||
case EVENT_RESET_JTAG:
|
||||
return "Info: The board was reset by JTAG";
|
||||
case EVENT_RESET_EFUSE:
|
||||
return "Info: The board was reset due to an efuse error";
|
||||
case EVENT_RESET_PWR_GLITCH:
|
||||
return "Info: The board was reset due to a detected power glitch";
|
||||
case EVENT_RESET_CPU_LOCKUP:
|
||||
return "Warning: The board was reset due to CPU lockup. Inform developers!";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
@ -355,11 +441,20 @@ static void update_event_level(void) {
|
|||
}
|
||||
|
||||
static void update_event_time(void) {
|
||||
// This should run roughly 2 times per second
|
||||
if (events.second_timer.elapsed() == true) {
|
||||
events.time_seconds++;
|
||||
uptime::calculateUptime(); // millis() overflows every 50 days, so update occasionally to adjust
|
||||
events.time_seconds = uptime::getDays() * DAYS_TO_SECS;
|
||||
events.time_seconds += uptime::getHours() * HOURS_TO_SECS;
|
||||
events.time_seconds += uptime::getMinutes() * MINUTES_TO_SECS;
|
||||
events.time_seconds += uptime::getSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long get_current_event_time_secs(void) {
|
||||
return events.time_seconds;
|
||||
}
|
||||
|
||||
static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
|
||||
// Update head with wrap to 0
|
||||
if (++events.event_log_head_index == EE_NOF_EVENT_ENTRIES) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0003 // 0x0000 to 0xFFFF
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0009 // 0x0000 to 0xFFFF
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
@ -33,23 +33,33 @@
|
|||
XX(EVENT_CANFD_RX_FAILURE) \
|
||||
XX(EVENT_CAN_RX_WARNING) \
|
||||
XX(EVENT_CAN_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BATTERY_EMPTY) \
|
||||
XX(EVENT_BATTERY_FULL) \
|
||||
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_ISOLATION) \
|
||||
XX(EVENT_BATTERY_REQUESTS_HEAT) \
|
||||
XX(EVENT_BATTERY_WARMED_UP) \
|
||||
XX(EVENT_VOLTAGE_DIFFERENCE) \
|
||||
XX(EVENT_LOW_SOH) \
|
||||
XX(EVENT_HVIL_FAILURE) \
|
||||
XX(EVENT_PRECHARGE_FAILURE) \
|
||||
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
||||
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
|
||||
XX(EVENT_MODBUS_INVERTER_MISSING) \
|
||||
XX(EVENT_ERROR_OPEN_CONTACTOR) \
|
||||
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_DEVIATION_HIGH) \
|
||||
|
@ -65,6 +75,22 @@
|
|||
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_NOF_EVENTS)
|
||||
|
||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
||||
|
@ -99,6 +125,7 @@ const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
|
|||
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_type(EVENTS_ENUM_TYPE event);
|
||||
unsigned long get_current_event_time_secs(void);
|
||||
|
||||
EVENTS_LEVEL_TYPE get_event_level(void);
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ led_color led_get_color() {
|
|||
}
|
||||
|
||||
void LED::exe(void) {
|
||||
static bool test_all_colors = true;
|
||||
// Don't run too often
|
||||
if (!timer.elapsed()) {
|
||||
return;
|
||||
|
@ -70,7 +69,7 @@ void LED::exe(void) {
|
|||
break;
|
||||
case EVENT_LEVEL_ERROR:
|
||||
color = led_color::RED;
|
||||
pixels.setPixelColor(0, COLOR_RED(brightness)); // Red LED full brightness
|
||||
pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -93,7 +92,6 @@ void LED::classic_run(void) {
|
|||
|
||||
void LED::flow_run(void) {
|
||||
// Determine how bright the LED should be
|
||||
bool power_positive;
|
||||
int16_t power_W = datalayer.battery.status.active_power_W;
|
||||
if (power_W < -50) {
|
||||
// Discharging
|
||||
|
@ -178,7 +176,6 @@ void LED::rainbow_run(void) {
|
|||
}
|
||||
|
||||
// Assemble the color
|
||||
uint32_t color = (static_cast<uint32_t>(r) << 16) | (static_cast<uint32_t>(g) << 8) | b;
|
||||
pixels.setPixelColor(0, pixels.Color(r, g, b)); // RGB
|
||||
}
|
||||
|
||||
|
|
|
@ -13,14 +13,19 @@ class LED {
|
|||
led_color color = led_color::GREEN;
|
||||
|
||||
LED()
|
||||
: mode(led_mode::CLASSIC),
|
||||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
max_brightness(LED_MAX_BRIGHTNESS),
|
||||
pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
brightness(LED_MAX_BRIGHTNESS),
|
||||
mode(led_mode::CLASSIC),
|
||||
state(LED_NORMAL),
|
||||
timer(LED_EXECUTION_FREQUENCY) {}
|
||||
|
||||
LED(led_mode mode)
|
||||
: mode(mode),
|
||||
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
max_brightness(LED_MAX_BRIGHTNESS),
|
||||
pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
|
||||
brightness(LED_MAX_BRIGHTNESS),
|
||||
mode(mode),
|
||||
state(LED_NORMAL),
|
||||
timer(LED_EXECUTION_FREQUENCY) {}
|
||||
|
||||
void exe(void);
|
||||
|
@ -30,7 +35,7 @@ class LED {
|
|||
Adafruit_NeoPixel pixels;
|
||||
uint8_t max_brightness;
|
||||
uint8_t brightness;
|
||||
led_mode mode = led_mode::CLASSIC;
|
||||
led_mode mode;
|
||||
led_state state = LED_NORMAL;
|
||||
MyTimer timer;
|
||||
|
||||
|
@ -46,4 +51,4 @@ void led_init(void);
|
|||
void led_exe(void);
|
||||
led_color led_get_color(void);
|
||||
|
||||
#endif
|
||||
#endif // LED_H_
|
||||
|
|
|
@ -25,9 +25,12 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
|
|||
#define INTERVAL_10_MS_DELAYED 15
|
||||
#define INTERVAL_20_MS_DELAYED 30
|
||||
#define INTERVAL_30_MS_DELAYED 40
|
||||
#define INTERVAL_50_MS_DELAYED 65
|
||||
#define INTERVAL_100_MS_DELAYED 120
|
||||
#define INTERVAL_200_MS_DELAYED 240
|
||||
#define INTERVAL_500_MS_DELAYED 550
|
||||
|
||||
#define MAX_CAN_FAILURES 500 // Amount of malformed CAN messages to allow before raising a warning
|
||||
#define CAN_STILL_ALIVE \
|
||||
12 // Set by battery each time we get a CAN message. Decrements every 5seconds. Incase we reach 0 (after 60 seconds of inactivity)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
String cellmonitor_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
// Page format
|
||||
content += "<style>";
|
||||
|
@ -37,7 +37,7 @@ String cellmonitor_processor(const String& var) {
|
|||
content += "<script>";
|
||||
// Populate cell data
|
||||
content += "const data = [";
|
||||
for (uint8_t i = 0u; i < MAX_AMOUNT_CELLS; i++) {
|
||||
for (uint8_t i = 0u; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
if (datalayer.battery.status.cell_voltages_mV[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -9,16 +9,19 @@ const char EVENTS_HTML_END[] = R"=====(
|
|||
</div></div>
|
||||
<button onclick='home()'>Back to main page</button>
|
||||
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style>
|
||||
<script>function showEvent(){document.querySelector(".event-log");var i=(new Date).getTime()/1e3;document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago"),t=e.querySelector(".timestamp");if(n&&t){var o=parseInt(n.innerText,10),a=parseFloat(t.innerText),r=new Date(1e3*(i-a+o)).toLocaleString();n.innerText=r}})}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
||||
<script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(new Date().getTime()-1e3*parseInt(n.innerText,10)).toLocaleString())})}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
||||
)=====";
|
||||
|
||||
String events_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
content.reserve(5000);
|
||||
// Page format
|
||||
content.concat(FPSTR(EVENTS_HTML_START));
|
||||
const EVENTS_STRUCT_TYPE* event_pointer;
|
||||
|
||||
unsigned long timestamp_now = get_current_event_time_secs();
|
||||
|
||||
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
|
||||
EVENTS_ENUM_TYPE event_handle = static_cast<EVENTS_ENUM_TYPE>(i);
|
||||
|
@ -32,11 +35,10 @@ String events_processor(const String& var) {
|
|||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
|
||||
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(timestamp_now - event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='timestamp' style='display:none;'>" + String(millis() / 1000) + "</div>");
|
||||
content.concat("</div>"); // End of event row
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const char index_html[] = R"rawliteral(
|
||||
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style><h2>Battery Emulator</h2>%ABC%
|
||||
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style>%X%
|
||||
)rawliteral";
|
||||
|
||||
/* The above code is minified (https://kangax.github.io/html-minifier/) to increase performance. Here is the full HTML function:
|
||||
|
@ -14,8 +14,7 @@ const char index_html[] = R"rawliteral(
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Battery Emulator</h2>
|
||||
%ABC%
|
||||
%X%
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
String settings_processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
//Page format
|
||||
content += "<style>";
|
||||
|
@ -13,6 +13,18 @@ String settings_processor(const String& var) {
|
|||
// Start a new block with a specific background color
|
||||
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||
|
||||
content += "<h4 style='color: white;'>SSID: <span id='SSID'>" + String(ssid.c_str()) +
|
||||
" </span> <button onclick='editSSID()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: white;'>Password: ######## <span id='Password'></span> <button "
|
||||
"onclick='editPassword()'>Edit</button></h4>";
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
||||
// Start a new block with a specific background color
|
||||
content += "<div style='background-color: #2D3F2F; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||
|
||||
// Show current settings with edit buttons and input fields
|
||||
content += "<h4 style='color: white;'>Battery capacity: <span id='BATTERY_WH_MAX'>" +
|
||||
String(datalayer.battery.info.total_capacity_Wh) +
|
||||
|
@ -78,208 +90,99 @@ String settings_processor(const String& var) {
|
|||
content += "</div>";
|
||||
#endif
|
||||
|
||||
content += "<script>";
|
||||
content += "function editComplete() {";
|
||||
content += " if (this.status == 200) {";
|
||||
content += " window.location.reload();";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "function editError() {";
|
||||
content += " alert('Invalid input');";
|
||||
content += "}";
|
||||
content += "function editWh() {";
|
||||
content += "var value = prompt('How much energy the battery can store. Enter new Wh value (1-120000):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 1 && value <= 120000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateBatterySize?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 1 and 120000.');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editUseScaledSOC() {";
|
||||
content += "var value = prompt('Should SOC% be scaled? (0 = No, 1 = Yes):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value == 0 || value == 1) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateUseScaledSOC?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1.');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editSocMax() {";
|
||||
content += "<script>"; // Note, this section is minified to improve performance
|
||||
content += "function editComplete(){if(this.status==200){window.location.reload();}}";
|
||||
content += "function editError(){alert('Invalid input');}";
|
||||
content +=
|
||||
"var value = prompt('Inverter will see fully charged (100pct)SOC when this value is reached. Enter new maximum "
|
||||
"SOC value that battery will charge to (50.0-100.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 50 && value <= 100) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateSocMax?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 50.0 and 100.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editSocMin() {";
|
||||
"function editSSID(){var value=prompt('Enter new SSID:');if(value!==null){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateSSID?value='+encodeURIComponent(value),true);xhr.send();}}";
|
||||
content +=
|
||||
"var value = prompt('Inverter will see completely discharged (0pct)SOC when this value is reached. Enter new "
|
||||
"minimum SOC value that battery will discharge to (0-50.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 50) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateSocMin?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 50.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editMaxChargeA() {";
|
||||
"function editPassword(){var value=prompt('Enter new password:');if(value!==null){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updatePassword?value='+encodeURIComponent(value),true);xhr.send();}}";
|
||||
content +=
|
||||
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
|
||||
"maximum charge current in A (0-1000.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateMaxChargeA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content += "function editMaxDischargeA() {";
|
||||
"function editWh(){var value=prompt('How much energy the battery can store. Enter new Wh value "
|
||||
"(1-120000):');if(value!==null){if(value>=1&&value<=120000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 "
|
||||
"and 120000.');}}}";
|
||||
content +=
|
||||
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
|
||||
"maximum discharge current in A (0-1000.0):');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateMaxDischargeA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
"function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (0 = No, 1 = "
|
||||
"Yes):');if(value!==null){if(value==0||value==1){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateUseScaledSOC?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
|
||||
"and 1.');}}}";
|
||||
content +=
|
||||
"function editSocMax(){var value=prompt('Inverter will see fully charged (100pct)SOC when this value is "
|
||||
"reached. Enter new maximum SOC value that battery will charge to "
|
||||
"(50.0-100.0):');if(value!==null){if(value>=50&&value<=100){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateSocMax?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 50.0 and "
|
||||
"100.0');}}}";
|
||||
content +=
|
||||
"function editSocMin(){var value=prompt('Inverter will see completely discharged (0pct)SOC when this value is "
|
||||
"reached. Enter new minimum SOC value that battery will discharge to "
|
||||
"(0-50.0):');if(value!==null){if(value>=0&&value<=50){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateSocMin?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 and "
|
||||
"50.0');}}}";
|
||||
content +=
|
||||
"function editMaxChargeA(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
|
||||
"maximum charge current in A (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateMaxChargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
|
||||
"and 1000.0');}}}";
|
||||
content +=
|
||||
"function editMaxDischargeA(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
|
||||
"maximum discharge current in A (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateMaxDischargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
|
||||
"and 1000.0');}}}";
|
||||
content += "</script>";
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
content += "function editFakeBatteryVoltage() {";
|
||||
content += " var value = prompt('Enter new fake battery voltage');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 5000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateFakeBatteryVoltage?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
content +=
|
||||
"function editFakeBatteryVoltage(){var value=prompt('Enter new fake battery "
|
||||
"voltage');if(value!==null){if(value>=0&&value<=5000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateFakeBatteryVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
|
||||
"between 0 and 1000');}}}";
|
||||
#endif
|
||||
|
||||
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||
content += "function editChargerHVDCEnabled() {";
|
||||
content += " var value = prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 for disabled');";
|
||||
content += " if (value !== null) {";
|
||||
content += " if (value == 0 || value == 1) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargerHvEnabled?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " }";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter 1 or 0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerAux12vEnabled() {";
|
||||
content +=
|
||||
"var value = prompt('Enable or disable low voltage 12v auxiliary DC output. Enter 1 for enabled, 0 for "
|
||||
"disabled');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value == 0 || value == 1) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargerAux12vEnabled?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter 1 or 0');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerSetpointVDC() {";
|
||||
"function editChargerHVDCEnabled(){var value=prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 "
|
||||
"for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargerHvEnabled?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}";
|
||||
content +=
|
||||
"var value = prompt('Set charging voltage. Input will be validated against inverter and/or charger "
|
||||
"configuration parameters, but use sensible values like 200 to 420.');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargeSetpointV?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerSetpointIDC() {";
|
||||
"function editChargerAux12vEnabled(){var value=prompt('Enable or disable low voltage 12v auxiliary DC output. "
|
||||
"Enter 1 for enabled, 0 for disabled');if(value!==null){if(value==0||value==1){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargerAux12vEnabled?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter 1 or "
|
||||
"0');}}}";
|
||||
content +=
|
||||
"var value = prompt('Set charging amperage. Input will be validated against inverter and/or charger "
|
||||
"configuration parameters, but use sensible values like 6 to 48.');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargeSetpointA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 100');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
|
||||
content += "function editChargerSetpointEndI() {";
|
||||
"function editChargerSetpointVDC(){var value=prompt('Set charging voltage. Input will be validated against "
|
||||
"inverter and/or charger configuration parameters, but use sensible values like 200 to "
|
||||
"420.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargeSetpointV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
|
||||
"0 and 1000');}}}";
|
||||
content +=
|
||||
"var value = prompt('Set amperage that terminates charge as being sufficiently complete. Input will be "
|
||||
"validated against inverter and/or charger configuration parameters, but use sensible values like 1-5.');";
|
||||
content += "if (value !== null) {";
|
||||
content += " if (value >= 0 && value <= 1000) {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.onload = editComplete;";
|
||||
content += " xhr.onerror = editError;";
|
||||
content += " xhr.open('GET', '/updateChargeEndA?value=' + value, true);";
|
||||
content += " xhr.send();";
|
||||
content += " } else {";
|
||||
content += " alert('Invalid value. Please enter a value between 0 and 100');";
|
||||
content += " }";
|
||||
content += "}";
|
||||
content += "}";
|
||||
"function editChargerSetpointIDC(){var value=prompt('Set charging amperage. Input will be validated against "
|
||||
"inverter and/or charger configuration parameters, but use sensible values like 6 to "
|
||||
"48.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargeSetpointA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
|
||||
"0 and 100');}}}";
|
||||
content +=
|
||||
"function editChargerSetpointEndI(){var value=prompt('Set amperage that terminates charge as being "
|
||||
"sufficiently complete. Input will be validated against inverter and/or charger configuration parameters, but "
|
||||
"use sensible values like 1-5.');if(value!==null){if(value>=0&&value<=1000){var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
|
||||
"updateChargeEndA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
|
||||
"and 100');}}}";
|
||||
#endif
|
||||
content += "</script>";
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
#define SETTINGS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
|
||||
extern std::string ssid;
|
||||
extern std::string password;
|
||||
|
||||
#include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ bool ota_active = false;
|
|||
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
|
||||
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
|
||||
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 30000; // Maximum wifi retry interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi retry interval in ms
|
||||
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
|
@ -43,7 +43,7 @@ void init_webserver() {
|
|||
} else {
|
||||
WiFi.mode(WIFI_STA); // Only Router connection
|
||||
}
|
||||
init_WiFi_STA(ssid, password, wifi_channel);
|
||||
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
|
||||
|
||||
String content = index_html;
|
||||
|
||||
|
@ -64,6 +64,37 @@ void init_webserver() {
|
|||
server.on("/events", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); });
|
||||
|
||||
// Route for editing SSID
|
||||
server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
if (value.length() <= 63) { // Check if SSID is within the allowable length
|
||||
ssid = value.c_str();
|
||||
storeSettings();
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
} else {
|
||||
request->send(400, "text/plain", "SSID must be 63 characters or less");
|
||||
}
|
||||
} else {
|
||||
request->send(400, "text/plain", "Bad Request");
|
||||
}
|
||||
});
|
||||
// Route for editing Password
|
||||
server.on("/updatePassword", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
if (value.length() > 8) { // Check if password is within the allowable length
|
||||
password = value.c_str();
|
||||
storeSettings();
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
} else {
|
||||
request->send(400, "text/plain", "Password must be atleast 8 characters");
|
||||
}
|
||||
} else {
|
||||
request->send(400, "text/plain", "Bad Request");
|
||||
}
|
||||
});
|
||||
|
||||
// Route for editing Wh
|
||||
server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
|
@ -302,7 +333,7 @@ void wifi_monitor() {
|
|||
Serial.println(getConnectResultString(status));
|
||||
#endif
|
||||
if (wifi_state == INIT) { //we haven't been connected yet, try the init logic
|
||||
init_WiFi_STA(ssid, password, wifi_channel);
|
||||
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
|
||||
} else { //we were connected before, try the reconnect logic
|
||||
if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) {
|
||||
last_wifi_attempt_time = currentMillis;
|
||||
|
@ -319,7 +350,7 @@ void wifi_monitor() {
|
|||
wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
// Print local IP address and start web server
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Connected to WiFi network: " + String(ssid));
|
||||
Serial.print("Connected to WiFi network: " + String(ssid.c_str()));
|
||||
Serial.print(" IP address: " + WiFi.localIP().toString());
|
||||
Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm");
|
||||
Serial.println(" Channel: " + String(WiFi.channel()));
|
||||
|
@ -346,6 +377,9 @@ void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_ch
|
|||
WiFi.begin(ssid, password, wifi_channel);
|
||||
WiFi.setAutoReconnect(true); // Enable auto reconnect
|
||||
wl_status_t result = static_cast<wl_status_t>(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT));
|
||||
if (result) {
|
||||
//TODO: Add event or serial print?
|
||||
}
|
||||
}
|
||||
|
||||
// Function to initialize ElegantOTA
|
||||
|
@ -358,8 +392,9 @@ void init_ElegantOTA() {
|
|||
}
|
||||
|
||||
String processor(const String& var) {
|
||||
if (var == "ABC") {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
content += "<h2>" + String(ssidAP) + "</h2>"; // ssidAP name is used as header name
|
||||
//Page format
|
||||
content += "<style>";
|
||||
content += "body { background-color: black; color: white; }";
|
||||
|
@ -370,11 +405,24 @@ String processor(const String& var) {
|
|||
|
||||
// Show version number
|
||||
content += "<h4>Software: " + String(version_number) + "</h4>";
|
||||
// Show hardware used:
|
||||
#ifdef HW_LILYGO
|
||||
content += "<h4>Hardware: LilyGo T-CAN485</h4>";
|
||||
#endif
|
||||
#ifdef HW_STARK
|
||||
content += "<h4>Hardware: Stark CMR Module</h4>";
|
||||
#endif
|
||||
content += "<h4>Uptime: " + uptime_formatter::getUptime() + "</h4>";
|
||||
#ifdef FUNCTION_TIME_MEASUREMENT
|
||||
// Load information
|
||||
content += "<h4>Core task max load: " + String(datalayer.system.status.core_task_max_us) + " us</h4>";
|
||||
content += "<h4>Core task max load last 10 s: " + String(datalayer.system.status.core_task_10s_max_us) + " us</h4>";
|
||||
content += "<h4>MQTT task max load last 10 s: " + String(datalayer.system.status.mqtt_task_10s_max_us) + " us</h4>";
|
||||
content +=
|
||||
"<h4>MQTT function (MQTT task) max load last 10 s: " + String(datalayer.system.status.mqtt_task_10s_max_us) +
|
||||
" us</h4>";
|
||||
content +=
|
||||
"<h4>WIFI function (MQTT task) max load last 10 s: " + String(datalayer.system.status.wifi_task_10s_max_us) +
|
||||
" us</h4>";
|
||||
content +=
|
||||
"<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>";
|
||||
content += "<h4>Max load @ worst case execution of core task:</h4>";
|
||||
|
@ -382,17 +430,16 @@ String processor(const String& var) {
|
|||
content += "<h4>5s function timing: " + String(datalayer.system.status.time_snap_5s_us) + " us</h4>";
|
||||
content += "<h4>CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us</h4>";
|
||||
content += "<h4>CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us</h4>";
|
||||
content += "<h4>Wifi and OTA function timing: " + String(datalayer.system.status.time_snap_wifi_us) + " us</h4>";
|
||||
content += "<h4>OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us</h4>";
|
||||
#endif
|
||||
|
||||
wl_status_t status = WiFi.status();
|
||||
// Display ssid of network connected to and, if connected to the WiFi, its own IP
|
||||
content += "<h4>SSID: " + String(ssid) + "</h4>";
|
||||
content += "<h4>SSID: " + String(ssid.c_str()) + "</h4>";
|
||||
if (status == WL_CONNECTED) {
|
||||
content += "<h4>IP: " + WiFi.localIP().toString() + "</h4>";
|
||||
// Get and display the signal strength (RSSI)
|
||||
content += "<h4>Signal Strength: " + String(WiFi.RSSI()) + " dBm</h4>";
|
||||
content += "<h4>Channel: " + String(WiFi.channel()) + "</h4>";
|
||||
// Get and display the signal strength (RSSI) and channel
|
||||
content += "<h4>Signal strength: " + String(WiFi.RSSI()) + " dBm, at channel " + String(WiFi.channel()) + "</h4>";
|
||||
} else {
|
||||
content += "<h4>Wifi state: " + getConnectResultString(status) + "</h4>";
|
||||
}
|
||||
|
@ -434,26 +481,41 @@ String processor(const String& var) {
|
|||
#ifdef BMW_I3_BATTERY
|
||||
content += "BMW i3";
|
||||
#endif
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
content += "BYD Atto 3";
|
||||
#endif
|
||||
#ifdef CHADEMO_BATTERY
|
||||
content += "Chademo V2X mode";
|
||||
#endif
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
content += "I-Miev / C-Zero / Ion Triplet";
|
||||
#endif
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
content += "Jaguar I-PACE";
|
||||
#endif
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
content += "Kia/Hyundai 64kWh";
|
||||
#endif
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
content += "Kia/Hyundai EGMP platform";
|
||||
#endif
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
content += "Kia/Hyundai Hybrid";
|
||||
#endif
|
||||
#ifdef MG_5_BATTERY
|
||||
content += "MG 5";
|
||||
#endif
|
||||
#ifdef NISSAN_LEAF_BATTERY
|
||||
content += "Nissan LEAF";
|
||||
#endif
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
content += "Renault Kangoo";
|
||||
#endif
|
||||
#ifdef RENAULT_ZOE_BATTERY
|
||||
content += "Renault Zoe";
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
content += "Renault Zoe Gen1 22/40";
|
||||
#endif
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
content += "Renault Zoe Gen2 50";
|
||||
#endif
|
||||
#ifdef SERIAL_LINK_RECEIVER
|
||||
content += "Serial link to another LilyGo board";
|
||||
|
@ -491,7 +553,7 @@ String processor(const String& var) {
|
|||
content += "<div style='display: flex; width: 100%;'>";
|
||||
content += "<div style='flex: 1; background-color: ";
|
||||
#else
|
||||
// Start a new block with a specific background color. Color changes depending on BMS status
|
||||
// Start a new block with a specific background color. Color changes depending on system status
|
||||
content += "<div style='background-color: ";
|
||||
#endif
|
||||
|
||||
|
@ -547,11 +609,11 @@ String processor(const String& var) {
|
|||
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
content += "<h4>BMS Status: OK </h4>";
|
||||
content += "<h4>System status: OK </h4>";
|
||||
} else if (datalayer.battery.status.bms_status == UPDATING) {
|
||||
content += "<h4>BMS Status: UPDATING </h4>";
|
||||
content += "<h4>System status: UPDATING </h4>";
|
||||
} else {
|
||||
content += "<h4>BMS Status: FAULT </h4>";
|
||||
content += "<h4>System status: FAULT </h4>";
|
||||
}
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
content += "<h4>Battery idle</h4>";
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <Preferences.h>
|
||||
#include <WiFi.h>
|
||||
#include "../../include.h"
|
||||
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
|
||||
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
|
||||
#ifdef MQTT
|
||||
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
|
||||
|
@ -17,8 +18,9 @@
|
|||
|
||||
extern const char* version_number; // The current software version, shown on webserver
|
||||
|
||||
extern const char* ssid;
|
||||
extern const char* password;
|
||||
#include <string>
|
||||
extern std::string ssid;
|
||||
extern std::string password;
|
||||
extern const uint8_t wifi_channel;
|
||||
extern const char* ssidAP;
|
||||
extern const char* passwordAP;
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
#include "system_settings.h"
|
||||
|
||||
#include "devboard/hal/hal.h"
|
||||
#include "devboard/safety/safety.h"
|
||||
#include "devboard/utils/time_meas.h"
|
||||
#include "devboard/utils/types.h"
|
||||
|
||||
#include "battery/BATTERIES.h"
|
||||
#include "charger/CHARGERS.h"
|
||||
#include "inverter/INVERTERS.h"
|
||||
|
||||
/* - ERROR CHECKS BELOW, DON'T TOUCH - */
|
||||
|
@ -25,7 +27,7 @@
|
|||
#error CAN-FD AND DUAL-CAN CANNOT BE USED SIMULTANEOUSLY
|
||||
#endif
|
||||
|
||||
#if defined(BYD_MODBUS) || defined(LUNA2000_MODBUS)
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
|
||||
// Check that Dual LilyGo via RS485 option isn't enabled, this collides with Modbus!
|
||||
#error MODBUS CANNOT BE USED IN DOUBLE LILYGO SETUPS! CHECK USER SETTINGS!
|
||||
|
|
|
@ -114,24 +114,29 @@ static uint16_t inverter_SOC = 0;
|
|||
static long inverter_timestamp = 0;
|
||||
static bool initialDataSent = 0;
|
||||
|
||||
void update_values_can_byd() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//Calculate values
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current =
|
||||
datalayer.battery.info
|
||||
|
@ -199,7 +204,7 @@ void update_values_can_byd() { //This function maps all the values fetched from
|
|||
#endif
|
||||
}
|
||||
|
||||
void receive_can_byd(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier!
|
||||
if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification
|
||||
|
@ -229,7 +234,7 @@ void receive_can_byd(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_byd() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send initial CAN data once on bootup
|
||||
if (!initialDataSent) {
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
#ifndef BYD_CAN_H
|
||||
#define BYD_CAN_H
|
||||
#include <Arduino.h>
|
||||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void update_values_can_byd();
|
||||
void send_can_byd();
|
||||
void receive_can_byd(CAN_frame_t rx_frame);
|
||||
void send_intial_data();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
#include "../include.h"
|
||||
#ifdef BYD_MODBUS
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "BYD-MODBUS.h"
|
||||
|
||||
void update_modbus_registers_byd() {
|
||||
//Updata for ModbusRTU Server for BYD
|
||||
// For modbus register definitions, see https://gitlab.com/pelle8/inverter_resources/-/blob/main/byd_registers_modbus_rtu.md
|
||||
|
||||
#define HISTORY_LENGTH 3 // Amount of samples(minutes) that needs to match for register to be considered stale
|
||||
static unsigned long previousMillis60s = 0; // will store last time a 60s event occured
|
||||
static uint32_t user_configured_max_discharge_W = 0;
|
||||
static uint32_t user_configured_max_charge_W = 0;
|
||||
static uint32_t max_discharge_W = 0;
|
||||
static uint32_t max_charge_W = 0;
|
||||
static uint16_t register_401_history[HISTORY_LENGTH] = {0};
|
||||
static uint8_t history_index = 0;
|
||||
static uint8_t bms_char_dis_status = STANDBY;
|
||||
static bool all_401_values_equal = false;
|
||||
|
||||
void update_modbus_registers_inverter() {
|
||||
verify_temperature_modbus();
|
||||
verify_inverter_modbus();
|
||||
handle_update_data_modbusp201_byd();
|
||||
handle_update_data_modbusp301_byd();
|
||||
}
|
||||
|
@ -28,46 +42,20 @@ void handle_static_data_modbus_byd() {
|
|||
memcpy(&mbPV[i], data_array_pointers[arr_idx], data_size);
|
||||
i += data_size / sizeof(uint16_t);
|
||||
}
|
||||
static uint16_t init_p201[13] = {0, 0, 0, MAX_POWER, MAX_POWER, 0, 0, 53248, 10, 53248, 10, 0, 0};
|
||||
memcpy(&mbPV[200], init_p201, sizeof(init_p201));
|
||||
static uint16_t init_p301[24] = {0, 0, 128, 0, 0, 0, 0, 0, 0, 2000, 0, 2000,
|
||||
75, 95, 0, 0, 16, 22741, 0, 0, 13, 52064, 230, 9900};
|
||||
memcpy(&mbPV[300], init_p301, sizeof(init_p301));
|
||||
}
|
||||
|
||||
void handle_update_data_modbusp201_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t system_data[13];
|
||||
system_data[0] = 0; // Id.: p201 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[1] = 0; // Id.: p202 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
if (datalayer.battery.info.total_capacity_Wh > 60000) {
|
||||
system_data[2] = 60000;
|
||||
} else {
|
||||
system_data[2] =
|
||||
(datalayer.battery.info
|
||||
.total_capacity_Wh); // Id.: p203 Value.: 32000 Scaled value.: 32kWh Comment.: Capacity rated, maximum value is 60000 (60kWh)
|
||||
}
|
||||
system_data[3] = MAX_POWER; // Id.: p204 Value.: 32000 Scaled value.: 32kWh Comment.: Nominal capacity
|
||||
system_data[4] =
|
||||
MAX_POWER; // Id.: p205 Value.: 14500 Scaled value.: 30,42kW Comment.: Max Charge/Discharge Power=10.24kW (lowest value of 204 and 205 will be enforced by Gen24)
|
||||
system_data[5] =
|
||||
(datalayer.battery.info
|
||||
.max_design_voltage_dV); // Id.: p206 Value.: 3667 Scaled value.: 362,7VDC Comment.: Max Voltage, if higher charging is not possible (goes into forced discharge)
|
||||
system_data[6] =
|
||||
(datalayer.battery.info
|
||||
.min_design_voltage_dV); // Id.: p207 Value.: 2776 Scaled value.: 277,6VDC Comment.: Min Voltage, if lower Gen24 disables battery
|
||||
system_data[7] =
|
||||
53248; // Id.: p208 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Charge power?
|
||||
system_data[8] = 10; // Id.: p209 Value.: 10 Scaled value.: 10 Comment.: Always 10
|
||||
system_data[9] =
|
||||
53248; // Id.: p210 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Discharge power?
|
||||
system_data[10] = 10; // Id.: p211 Value.: 10 Scaled value.: 10 Comment.: Always 10
|
||||
system_data[11] = 0; // Id.: p212 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
system_data[12] = 0; // Id.: p213 Value.: 0 Scaled value.: 0 Comment.: Always 0
|
||||
static uint16_t i = 200;
|
||||
memcpy(&mbPV[i], system_data, sizeof(system_data));
|
||||
mbPV[202] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast<uint32_t>(60000u)); //Cap to 60kWh
|
||||
mbPV[205] = (datalayer.battery.info.max_design_voltage_dV); // Max Voltage, if higher Gen24 forces discharge
|
||||
mbPV[206] = (datalayer.battery.info.min_design_voltage_dV); // Min Voltage, if lower Gen24 disables battery
|
||||
}
|
||||
|
||||
void handle_update_data_modbusp301_byd() {
|
||||
// Store the data into the array
|
||||
static uint16_t battery_data[24];
|
||||
|
||||
static uint8_t bms_char_dis_status = STANDBY;
|
||||
if (datalayer.battery.status.current_dA == 0) {
|
||||
bms_char_dis_status = STANDBY;
|
||||
} else if (datalayer.battery.status.current_dA < 0) { //Negative value = Discharging
|
||||
|
@ -75,76 +63,34 @@ void handle_update_data_modbusp301_byd() {
|
|||
} else { //Positive value = Charging
|
||||
bms_char_dis_status = CHARGING;
|
||||
}
|
||||
// Convert max discharge Amp value to max Watt
|
||||
user_configured_max_discharge_W =
|
||||
((datalayer.battery.info.max_discharge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
|
||||
// Use the smaller value, battery reported value OR user configured value
|
||||
max_discharge_W = std::min(datalayer.battery.status.max_discharge_power_W, user_configured_max_discharge_W);
|
||||
|
||||
// Convert max charge Amp value to max Watt
|
||||
user_configured_max_charge_W =
|
||||
((datalayer.battery.info.max_charge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
|
||||
// Use the smaller value, battery reported value OR user configured value
|
||||
max_charge_W = std::min(datalayer.battery.status.max_charge_power_W, user_configured_max_charge_W);
|
||||
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
battery_data[8] =
|
||||
datalayer.battery.status
|
||||
.voltage_dV; // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
|
||||
mbPV[308] = datalayer.battery.status.voltage_dV;
|
||||
} else {
|
||||
battery_data[8] = 0;
|
||||
mbPV[308] = 0;
|
||||
}
|
||||
battery_data[0] =
|
||||
datalayer.battery.status
|
||||
.bms_status; // Id.: p301 Value.: 3 Scaled value.: 3 Comment.: status(*): ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
|
||||
battery_data[1] = 0; // Id.: p302 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[2] = 128 + bms_char_dis_status; // Id.: p303 Value.: 130 Scaled value.: 130 Comment.: mode(*): normal
|
||||
battery_data[3] =
|
||||
datalayer.battery.status
|
||||
.reported_soc; // Id.: p304 Value.: 1700 Scaled value.: 50% Comment.: SOC: (50% would equal 5000)
|
||||
if (datalayer.battery.info.total_capacity_Wh > 60000) {
|
||||
battery_data[4] = 60000;
|
||||
} else {
|
||||
battery_data[4] =
|
||||
datalayer.battery.info.total_capacity_Wh; // Id.: p305 Value.: 32000 Scaled value.: 32kWh Comment.: tot cap:
|
||||
}
|
||||
if (datalayer.battery.status.remaining_capacity_Wh > 60000) {
|
||||
battery_data[5] = 60000;
|
||||
} else {
|
||||
// Id.: p306 Value.: 13260 Scaled value.: 13,26kWh Comment.: remaining cap: 7.68kWh
|
||||
battery_data[5] = datalayer.battery.status.remaining_capacity_Wh;
|
||||
}
|
||||
if (datalayer.battery.status.max_discharge_power_W > 30000) {
|
||||
battery_data[6] = 30000;
|
||||
} else {
|
||||
battery_data[6] =
|
||||
datalayer.battery.status
|
||||
.max_discharge_power_W; // Id.: p307 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target discharge power: 0W (0W > restricts to no discharge)
|
||||
}
|
||||
|
||||
if (datalayer.battery.status.max_charge_power_W > 30000) {
|
||||
battery_data[7] = 30000;
|
||||
} else {
|
||||
battery_data[7] =
|
||||
datalayer.battery.status
|
||||
.max_charge_power_W; // Id.: p308 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target charge power: 4.3kW (during charge), both 307&308 can be set (>0) at the same time
|
||||
}
|
||||
//Battery_data[8] set previously in function // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
|
||||
battery_data[9] =
|
||||
2000; // Id.: p310 Value.: 64121 Scaled value.: 6412,1W Comment.: Current Power to API: if>32768... -(65535-61760)=3775W
|
||||
battery_data[10] =
|
||||
datalayer.battery.status
|
||||
.voltage_dV; // Id.: p311 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage inner: 173.2V
|
||||
battery_data[11] = 2000; // Id.: p312 Value.: 64121 Scaled value.: 6412,1W Comment.: p310
|
||||
battery_data[12] =
|
||||
datalayer.battery.status
|
||||
.temperature_min_dC; // Id.: p313 Value.: 75 Scaled value.: 7,5 Comment.: temp min: 7 degrees (if below 0....65535-t)
|
||||
battery_data[13] =
|
||||
datalayer.battery.status
|
||||
.temperature_max_dC; // Id.: p314 Value.: 95 Scaled value.: 9,5 Comment.: temp max: 9 degrees (if below 0....65535-t)
|
||||
battery_data[14] = 0; // Id.: p315 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[15] = 0; // Id.: p316 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[16] = 16; // Id.: p317 Value.: 0 Scaled value.: 0 Comment.: counter charge hi
|
||||
battery_data[17] =
|
||||
22741; // Id.: p318 Value.: 0 Scaled value.: 0 Comment.: counter charge lo....65536*101+9912 = 6629048 Wh?
|
||||
battery_data[18] = 0; // Id.: p319 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[19] = 0; // Id.: p320 Value.: 0 Scaled value.: 0 Comment.: always 0
|
||||
battery_data[20] = 13; // Id.: p321 Value.: 0 Scaled value.: 0 Comment.: counter discharge hi
|
||||
battery_data[21] =
|
||||
52064; // Id.: p322 Value.: 0 Scaled value.: 0 Comment.: counter discharge lo....65536*92+7448 = 6036760 Wh?
|
||||
battery_data[22] = 230; // Id.: p323 Value.: 0 Scaled value.: 0 Comment.: device temperature (23 degrees)
|
||||
battery_data[23] = datalayer.battery.status.soh_pptt; // Id.: p324 Value.: 9900 Scaled value.: 99% Comment.: SOH
|
||||
static uint16_t i = 300;
|
||||
memcpy(&mbPV[i], battery_data, sizeof(battery_data));
|
||||
mbPV[300] = datalayer.battery.status.bms_status;
|
||||
mbPV[302] = 128 + bms_char_dis_status;
|
||||
mbPV[303] = datalayer.battery.status.reported_soc;
|
||||
mbPV[304] = std::min(datalayer.battery.info.total_capacity_Wh, static_cast<uint32_t>(60000u)); //Cap to 60kWh
|
||||
mbPV[305] = std::min(datalayer.battery.status.remaining_capacity_Wh, static_cast<uint32_t>(60000u)); //Cap to 60kWh
|
||||
mbPV[306] = std::min(max_discharge_W, static_cast<uint32_t>(30000u)); //Cap to 30000 if exceeding
|
||||
mbPV[307] = std::min(max_charge_W, static_cast<uint32_t>(30000u)); //Cap to 30000 if exceeding
|
||||
mbPV[310] = datalayer.battery.status.voltage_dV;
|
||||
mbPV[312] = datalayer.battery.status.temperature_min_dC;
|
||||
mbPV[313] = datalayer.battery.status.temperature_max_dC;
|
||||
mbPV[323] = datalayer.battery.status.soh_pptt;
|
||||
}
|
||||
|
||||
void verify_temperature_modbus() {
|
||||
|
@ -168,4 +114,32 @@ void verify_temperature_modbus() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void verify_inverter_modbus() {
|
||||
// Every 60 seconds, the Gen24 writes to this 401 register, alternating between 00FF and FF00.
|
||||
// We sample the register every 60 seconds. Incase the value has not changed for 3 minutes, we raise an event
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
||||
previousMillis60s = currentMillis;
|
||||
|
||||
all_401_values_equal = true;
|
||||
for (int i = 0; i < HISTORY_LENGTH; ++i) {
|
||||
if (register_401_history[i] != mbPV[401]) {
|
||||
all_401_values_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_401_values_equal) {
|
||||
set_event(EVENT_MODBUS_INVERTER_MISSING, 0);
|
||||
} else {
|
||||
clear_event(EVENT_MODBUS_INVERTER_MISSING);
|
||||
}
|
||||
|
||||
// Update history
|
||||
register_401_history[history_index] = mbPV[401];
|
||||
history_index = (history_index + 1) % HISTORY_LENGTH;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#define BYD_MODBUS_H
|
||||
#include "../include.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define MODBUS_INVERTER_SELECTED
|
||||
|
||||
#define MB_RTU_NUM_VALUES 30000
|
||||
#define MAX_POWER 40960 //BYD Modbus specific value
|
||||
|
@ -11,7 +11,7 @@ extern uint16_t mbPV[MB_RTU_NUM_VALUES];
|
|||
|
||||
void handle_static_data_modbus_byd();
|
||||
void verify_temperature_modbus();
|
||||
void verify_inverter_modbus();
|
||||
void handle_update_data_modbusp201_byd();
|
||||
void handle_update_data_modbusp301_byd();
|
||||
void update_modbus_registers_byd();
|
||||
#endif
|
||||
|
|
|
@ -39,4 +39,15 @@
|
|||
#include "SERIAL-LINK-TRANSMITTER-INVERTER.h"
|
||||
#endif
|
||||
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" // This include is annoying, consider defining a frame type in types.h
|
||||
void update_values_can_inverter();
|
||||
void receive_can_inverter(CAN_frame_t rx_frame);
|
||||
void send_can_inverter();
|
||||
#endif
|
||||
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
void update_modbus_registers_inverter();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
#include "../datalayer/datalayer.h"
|
||||
#include "LUNA2000-MODBUS.h"
|
||||
|
||||
void update_modbus_registers_luna2000() {
|
||||
//Updata for ModbusRTU Server for Luna2000
|
||||
void update_modbus_registers_inverter() {
|
||||
handle_update_data_modbus32051();
|
||||
handle_update_data_modbus39500();
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
#define LUNA2000_MODBUS_H
|
||||
#include "../include.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define MODBUS_INVERTER_SELECTED
|
||||
|
||||
#define MB_RTU_NUM_VALUES 30000
|
||||
|
||||
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
|
||||
|
||||
void update_modbus_registers_luna2000();
|
||||
void handle_update_data_modbus32051();
|
||||
void handle_update_data_modbus39500();
|
||||
#endif
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
|
||||
//#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
|
||||
#define INVERT_VOLTAGE //If defined, the min/max voltage frames will be inverted, \
|
||||
//useful for some inverters like Sofar that report the voltages incorrect otherwise
|
||||
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
|
||||
//useful for some inverters like Sofar that report the voltages incorrect otherwise
|
||||
//#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
//Actual content messages
|
||||
|
@ -170,10 +171,32 @@ CAN_frame_t PYLON_4291 = {.FIR = {.B =
|
|||
.MsgID = 0x4291,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
void update_values_can_pylon() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
static int16_t max_charge_current = 0;
|
||||
static int16_t max_discharge_current = 0;
|
||||
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//There are more mappings that could be added, but this should be enough to use as a starting point
|
||||
// Note we map both 0 and 1 messages
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
|
||||
max_charge_current = (datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV;
|
||||
if (max_charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
max_charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
max_discharge_current =
|
||||
(datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV;
|
||||
if (max_discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
max_discharge_current =
|
||||
datalayer.battery.info
|
||||
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
} else {
|
||||
max_charge_current = 0;
|
||||
max_discharge_current = 0;
|
||||
}
|
||||
|
||||
//Charge / Discharge allowed
|
||||
PYLON_4280.data.u8[0] = 0;
|
||||
PYLON_4280.data.u8[1] = 0;
|
||||
|
@ -196,6 +219,18 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
|
||||
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
|
||||
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
#else
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
#endif
|
||||
//SOC (100.00%)
|
||||
PYLON_4210.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
|
||||
PYLON_4211.data.u8[6] = (datalayer.battery.status.reported_soc / 100); //Remove decimals
|
||||
|
@ -204,8 +239,42 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
PYLON_4210.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
|
||||
PYLON_4211.data.u8[7] = (datalayer.battery.status.soh_pptt / 100);
|
||||
|
||||
#ifdef INVERT_VOLTAGE //Useful for Sofar inverters \
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
|
||||
// Status=Bit 0,1,2= 0:Sleep, 1:Charge, 2:Discharge 3:Idle. Bit3 ForceChargeReq. Bit4 Balance charge Request
|
||||
if (datalayer.battery.status.current_dA < 0) {
|
||||
PYLON_4251.data.u8[0] = (0x11); // Charge
|
||||
} else if (datalayer.battery.status.current_dA > 0) {
|
||||
PYLON_4251.data.u8[0] = (0x12); // Discharge
|
||||
} else if (datalayer.battery.status.current_dA == 0) {
|
||||
PYLON_4251.data.u8[0] = (0x13); // Idle
|
||||
}
|
||||
|
||||
#ifdef INVERT_LOW_HIGH_BYTES //Useful for Sofar inverters
|
||||
//Voltage (370.0)
|
||||
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Current (15.0)
|
||||
PYLON_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
#else
|
||||
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||
#endif
|
||||
|
||||
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
|
||||
PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
|
@ -216,20 +285,187 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) & 0x00FF);
|
||||
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) >> 8);
|
||||
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) & 0x00FF);
|
||||
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) >> 8);
|
||||
|
||||
//Max DischargeCurrent
|
||||
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) >> 8);
|
||||
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) >> 8);
|
||||
#else
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
|
||||
PYLON_4220.data.u8[0] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[1] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[0] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[1] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = (max_charge_current & 0x00FF);
|
||||
PYLON_4220.data.u8[5] = (max_charge_current >> 8);
|
||||
PYLON_4221.data.u8[4] = (max_charge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[5] = (max_charge_current >> 8);
|
||||
|
||||
//Max DishargeCurrent
|
||||
PYLON_4220.data.u8[6] = (max_discharge_current & 0x00FF);
|
||||
PYLON_4220.data.u8[7] = (max_discharge_current >> 8);
|
||||
PYLON_4221.data.u8[6] = (max_discharge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[7] = (max_discharge_current >> 8);
|
||||
#endif
|
||||
|
||||
//Max cell voltage
|
||||
PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
|
||||
//Min cell voltage
|
||||
PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
|
||||
//Max temperature per cell
|
||||
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
|
||||
//Max/Min temperature per cell
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
|
||||
//Max temperature per module
|
||||
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
|
||||
//Min temperature per module
|
||||
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
#else
|
||||
//Voltage (370.0)
|
||||
PYLON_4210.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8;
|
||||
PYLON_4210.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
PYLON_4211.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
PYLON_4211.data.u8[1] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Current (15.0)
|
||||
PYLON_4210.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
PYLON_4210.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
PYLON_4211.data.u8[2] = ((datalayer.battery.status.current_dA + 30000) >> 8);
|
||||
PYLON_4211.data.u8[3] = ((datalayer.battery.status.current_dA + 30000) & 0x00FF);
|
||||
#else
|
||||
PYLON_4210.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4210.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
PYLON_4211.data.u8[2] = (datalayer.battery.status.current_dA >> 8);
|
||||
PYLON_4211.data.u8[3] = (datalayer.battery.status.current_dA & 0x00FF);
|
||||
#endif
|
||||
|
||||
// BMS Temperature (We dont have BMS temp, send max cell voltage instead)
|
||||
PYLON_4210.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4210.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
PYLON_4211.data.u8[4] = ((datalayer.battery.status.temperature_max_dC + 1000) >> 8);
|
||||
PYLON_4211.data.u8[5] = ((datalayer.battery.status.temperature_max_dC + 1000) & 0x00FF);
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
|
||||
PYLON_4220.data.u8[2] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[3] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[2] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[3] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4220.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
|
||||
|
||||
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
|
||||
PYLON_4220.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4220.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
PYLON_4221.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
|
||||
PYLON_4221.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
|
||||
|
||||
#ifdef SET_30K_OFFSET
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) >> 8);
|
||||
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
|
||||
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) >> 8);
|
||||
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) & 0x00FF);
|
||||
|
||||
//Max DischargeCurrent
|
||||
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) >> 8);
|
||||
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) >> 8);
|
||||
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) & 0x00FF);
|
||||
#else
|
||||
//Max ChargeCurrent
|
||||
PYLON_4220.data.u8[4] = (max_charge_current >> 8);
|
||||
PYLON_4220.data.u8[5] = (max_charge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[4] = (max_charge_current >> 8);
|
||||
PYLON_4221.data.u8[5] = (max_charge_current & 0x00FF);
|
||||
|
||||
//Max DishargeCurrent
|
||||
PYLON_4220.data.u8[6] = (max_discharge_current >> 8);
|
||||
PYLON_4220.data.u8[7] = (max_discharge_current & 0x00FF);
|
||||
PYLON_4221.data.u8[6] = (max_discharge_current >> 8);
|
||||
PYLON_4221.data.u8[7] = (max_discharge_current & 0x00FF);
|
||||
#endif
|
||||
|
||||
//Max cell voltage
|
||||
PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
|
||||
//Min cell voltage
|
||||
PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
PYLON_4231.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4231.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
|
||||
//Max temperature per cell
|
||||
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4241.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4241.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
|
||||
//Max/Min temperature per cell
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
|
||||
//Max temperature per module
|
||||
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4271.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
|
||||
//Min temperature per module
|
||||
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
PYLON_4271.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4271.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
#endif
|
||||
|
||||
//Max/Min cell voltage
|
||||
PYLON_4230.data.u8[0] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[1] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
PYLON_4230.data.u8[2] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
PYLON_4230.data.u8[3] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
|
||||
//Max/Min temperature per cell
|
||||
PYLON_4240.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4240.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4240.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4240.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
|
||||
//Max/Min temperature per module
|
||||
PYLON_4270.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
PYLON_4270.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
PYLON_4270.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
PYLON_4270.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
|
||||
//In case we run into any errors/faults, we can set charge / discharge forbidden
|
||||
if (datalayer.battery.status.bms_status == FAULT) {
|
||||
PYLON_4280.data.u8[0] = 0xAA;
|
||||
|
@ -243,7 +479,7 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
|
|||
}
|
||||
}
|
||||
|
||||
void receive_can_pylon(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
|
||||
if (rx_frame.data.u8[0] == 0x02) {
|
||||
|
@ -258,7 +494,20 @@ void receive_can_pylon(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_inverter() {
|
||||
// No periodic sending, we only react on received can messages
|
||||
}
|
||||
|
||||
void send_setup_info() { //Ensemble information
|
||||
//Amount of cells
|
||||
PYLON_7320.data.u8[0] = datalayer.battery.info.number_of_cells;
|
||||
//Modules in series (not really how EV packs work, but let's map it to a reasonable Pylon value)
|
||||
PYLON_7320.data.u8[2] = (datalayer.battery.info.number_of_cells / 15);
|
||||
//Capacity in AH
|
||||
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
|
||||
PYLON_7320.data.u8[6] = (datalayer.battery.info.total_capacity_Wh / (datalayer.battery.status.voltage_dV / 10));
|
||||
}
|
||||
|
||||
#ifdef SEND_0
|
||||
ESP32Can.CANWriteFrame(&PYLON_7310);
|
||||
ESP32Can.CANWriteFrame(&PYLON_7320);
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void update_values_can_pylon();
|
||||
void receive_can_pylon(CAN_frame_t rx_frame);
|
||||
void send_system_data();
|
||||
void send_setup_info();
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
|
||||
void manageSerialLinkTransmitter();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -103,24 +103,26 @@ static int16_t charge_current = 0;
|
|||
static int16_t temperature_average = 0;
|
||||
static uint16_t ampere_hours_remaining = 0;
|
||||
|
||||
void update_values_can_sma() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
//Calculate values
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current =
|
||||
datalayer.battery.info
|
||||
|
@ -130,8 +132,10 @@ void update_values_can_sma() { //This function maps all the values fetched from
|
|||
temperature_average =
|
||||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
|
||||
ampere_hours_remaining = ((datalayer.battery.status.remaining_capacity_Wh / datalayer.battery.status.voltage_dV) *
|
||||
100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
ampere_hours_remaining = ((datalayer.battery.status.remaining_capacity_Wh / datalayer.battery.status.voltage_dV) *
|
||||
100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
|
||||
}
|
||||
|
||||
//Map values to CAN messages
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
|
||||
|
@ -226,7 +230,7 @@ void update_values_can_sma() { //This function maps all the values fetched from
|
|||
*/
|
||||
}
|
||||
|
||||
void receive_can_sma(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x360: //Message originating from SMA inverter - Voltage and current
|
||||
//Frame0-1 Voltage
|
||||
|
@ -246,7 +250,7 @@ void receive_can_sma(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_sma() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every 100ms
|
||||
|
|
|
@ -3,13 +3,9 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
#define READY_STATE 0x03
|
||||
#define STOP_STATE 0x02
|
||||
|
||||
void update_values_can_sma();
|
||||
void send_can_sma();
|
||||
void receive_can_sma(CAN_frame_t rx_frame);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -161,24 +161,26 @@ Command2Battery command2Battery = RUN;
|
|||
enum InvInitState { SYSTEM_FREQUENCY, XPHASE_SYSTEM, BLACKSTART_OPERATION };
|
||||
InvInitState invInitState = SYSTEM_FREQUENCY;
|
||||
|
||||
void update_values_can_sma_tripower() { //This function maps all the values fetched from battery CAN to the inverter CAN
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
|
||||
//Calculate values
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
charge_current =
|
||||
((datalayer.battery.status.max_charge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
}
|
||||
|
||||
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
|
||||
charge_current =
|
||||
datalayer.battery.info
|
||||
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
|
||||
}
|
||||
|
||||
discharge_current =
|
||||
((datalayer.battery.status.max_discharge_power_W * 10) /
|
||||
datalayer.battery.info.max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
|
||||
//The above calculation results in (30 000*10)/3700=81A
|
||||
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
|
||||
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
|
||||
discharge_current =
|
||||
datalayer.battery.info
|
||||
|
@ -328,7 +330,7 @@ void update_values_can_sma_tripower() { //This function maps all the values fet
|
|||
//SMA_018.data.u8[7] = BatteryName;
|
||||
}
|
||||
|
||||
void receive_can_sma_tripower(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) {
|
||||
case 0x00D: //Inverter Measurements
|
||||
break;
|
||||
|
@ -347,7 +349,7 @@ void receive_can_sma_tripower(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_sma_tripower() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every 500ms
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void update_values_can_sma_tripower();
|
||||
void send_can_sma_tripower();
|
||||
void receive_can_sma_tripower(CAN_frame_t rx_frame);
|
||||
void send_tripower_init();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -280,7 +280,7 @@ CAN_frame_t SOFAR_7C0 = {.FIR = {.B =
|
|||
.MsgID = 0x7C0,
|
||||
.data = {0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x80, 0x00}};
|
||||
|
||||
void update_values_can_sofar() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
|
||||
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
|
||||
SOFAR_351.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
|
||||
|
@ -308,7 +308,7 @@ void update_values_can_sofar() { //This function maps all the values fetched fr
|
|||
SOFAR_356.data.u8[3] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
}
|
||||
|
||||
void receive_can_sofar(CAN_frame_t rx_frame) {
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
switch (rx_frame.MsgID) { //In here we need to respond to the inverter. TODO: make logic
|
||||
case 0x605:
|
||||
//frame1_605 = rx_frame.data.u8[1];
|
||||
|
@ -323,7 +323,7 @@ void receive_can_sofar(CAN_frame_t rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void send_can_sofar() {
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
#include "../include.h"
|
||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
|
||||
void update_values_can_sofar();
|
||||
void send_can_sofar();
|
||||
void receive_can_sofar(CAN_frame_t rx_frame);
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
#endif
|
||||
|
|
|
@ -121,7 +121,7 @@ void CAN_WriteFrame(CAN_frame_t* tx_frame) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void update_values_can_solax() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
// If not receiveing any communication from the inverter, open contactors and return to battery announce state
|
||||
if (millis() - LastFrameTime >= SolaxTimeout) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false;
|
||||
|
@ -136,28 +136,38 @@ void update_values_can_solax() { //This function maps all the values fetched fr
|
|||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
|
||||
//datalayer.battery.status.max_charge_power_W (30000W max)
|
||||
if (datalayer.battery.status.reported_soc > 9999) //99.99%
|
||||
{ //Additional safety incase SOC% is 100, then do not charge battery further
|
||||
if (datalayer.battery.status.reported_soc > 9999) { // 99.99%
|
||||
// Additional safety incase SOC% is 100, then do not charge battery further
|
||||
max_charge_rate_amp = 0;
|
||||
} else { //We can pass on the battery charge rate (in W) to the inverter (that takes A)
|
||||
} else { // We can pass on the battery charge rate (in W) to the inverter (that takes A)
|
||||
if (datalayer.battery.status.max_charge_power_W >= 30000) {
|
||||
max_charge_rate_amp = 75; //Incase battery can take over 30kW, cap value to 75A
|
||||
} else { //Calculate the W value into A
|
||||
max_charge_rate_amp =
|
||||
(datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1)); // P/U = I
|
||||
max_charge_rate_amp = 75; // Incase battery can take over 30kW, cap value to 75A
|
||||
} else { // Calculate the W value into A
|
||||
if (datalayer.battery.status.voltage_dV > 10) {
|
||||
max_charge_rate_amp =
|
||||
datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
|
||||
} else { // We avoid dividing by 0 and crashing the board
|
||||
// If we have no voltage, something has gone wrong, do not allow charging
|
||||
max_charge_rate_amp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//datalayer.battery.status.max_discharge_power_W (30000W max)
|
||||
if (datalayer.battery.status.reported_soc < 100) //1.00%
|
||||
{ //Additional safety incase SOC% is below 1, then do not charge battery further
|
||||
if (datalayer.battery.status.reported_soc < 100) { // 1.00%
|
||||
// Additional safety in case SOC% is below 1, then do not discharge battery further
|
||||
max_discharge_rate_amp = 0;
|
||||
} else { //We can pass on the battery discharge rate to the inverter
|
||||
} else { // We can pass on the battery discharge rate to the inverter
|
||||
if (datalayer.battery.status.max_discharge_power_W >= 30000) {
|
||||
max_discharge_rate_amp = 75; //Incase battery can be charged with over 30kW, cap value to 75A
|
||||
} else { //Calculate the W value into A
|
||||
max_discharge_rate_amp =
|
||||
(datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1)); // P/U = I
|
||||
max_discharge_rate_amp = 75; // Incase battery can be charged with over 30kW, cap value to 75A
|
||||
} else { // Calculate the W value into A
|
||||
if (datalayer.battery.status.voltage_dV > 10) {
|
||||
max_discharge_rate_amp =
|
||||
datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
|
||||
} else { // We avoid dividing by 0 and crashing the board
|
||||
// If we have no voltage, something has gone wrong, do not allow discharging
|
||||
max_discharge_rate_amp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,8 +202,8 @@ void update_values_can_solax() { //This function maps all the values fetched fr
|
|||
SOLAX_1873.data.u8[3] = (datalayer.battery.status.current_dA >> 8);
|
||||
SOLAX_1873.data.u8[4] = (uint8_t)(datalayer.battery.status.reported_soc / 100); //SOC (100.00%)
|
||||
//SOLAX_1873.data.u8[5] = //Seems like this is not required? Or shall we put SOC decimals here?
|
||||
SOLAX_1873.data.u8[6] = (uint8_t)(capped_remaining_capacity_Wh / 100);
|
||||
SOLAX_1873.data.u8[7] = ((capped_remaining_capacity_Wh / 100) >> 8);
|
||||
SOLAX_1873.data.u8[6] = (uint8_t)(capped_remaining_capacity_Wh / 10);
|
||||
SOLAX_1873.data.u8[7] = ((capped_remaining_capacity_Wh / 10) >> 8);
|
||||
|
||||
//BMS_CellData
|
||||
SOLAX_1874.data.u8[0] = (int8_t)datalayer.battery.status.temperature_max_dC;
|
||||
|
@ -237,7 +247,11 @@ void update_values_can_solax() { //This function maps all the values fetched fr
|
|||
SOLAX_1801.data.u8[4] = 1;
|
||||
}
|
||||
|
||||
void receive_can_solax(CAN_frame_t rx_frame) {
|
||||
void send_can_inverter() {
|
||||
// No periodic sending used on this protocol, we react only on incoming CAN messages!
|
||||
}
|
||||
|
||||
void receive_can_inverter(CAN_frame_t rx_frame) {
|
||||
if (rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x01) ||
|
||||
rx_frame.MsgID == 0x1871 && rx_frame.data.u8[0] == (0x02)) {
|
||||
LastFrameTime = millis();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "../lib/pierremolinaro-acan2515/ACAN2515.h"
|
||||
|
||||
#define INVERTER_SELECTED
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
extern ACAN2515 can;
|
||||
|
||||
|
@ -18,6 +18,5 @@ extern ACAN2515 can;
|
|||
#define FAULT_SOLAX 3
|
||||
#define UPDATING_FW 4
|
||||
|
||||
void update_values_can_solax();
|
||||
void receive_can_solax(CAN_frame_t rx_frame);
|
||||
#endif
|
||||
|
|
674
Software/src/lib/YiannisBourkelis-Uptime-Library/LICENSE
Normal file
674
Software/src/lib/YiannisBourkelis-Uptime-Library/LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
103
Software/src/lib/YiannisBourkelis-Uptime-Library/README.md
Normal file
103
Software/src/lib/YiannisBourkelis-Uptime-Library/README.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Uptime Library
|
||||
|
||||
With the uptime library for Arduino boards and compatible systems you can read the time passed since device startup, without the 49 days overflow limitation of the millis() function.
|
||||
|
||||
# Usage
|
||||
|
||||
#### Example 1: [Device Uptime](https://github.com/YiannisBourkelis/Uptime-Library/tree/master/examples/DeviceUptime "Device Uptime")
|
||||
```cpp
|
||||
#include "uptime_formatter.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//uptime_formatter::get_uptime() returns a string
|
||||
//containing the total device uptime since startup in days, hours, minutes and seconds
|
||||
Serial.println("up " + uptime_formatter::getUptime());
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
||||
```
|
||||
|
||||
#### Output:
|
||||
```
|
||||
up 0 days, 0 hours, 0 minutes, 56 seconds
|
||||
up 0 days, 0 hours, 0 minutes, 57 seconds
|
||||
up 0 days, 0 hours, 0 minutes, 58 seconds
|
||||
up 0 days, 0 hours, 0 minutes, 59 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 0 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 1 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 2 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 3 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 4 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 5 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 6 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 7 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 8 seconds
|
||||
up 0 days, 0 hours, 1 minutes, 9 seconds
|
||||
```
|
||||
|
||||
#### Example 2: [Device Uptime Custom Formatting](https://github.com/YiannisBourkelis/Uptime-Library/tree/master/examples/DeviceUptimeCustomFormatting "Device Uptime Custom Formatting")
|
||||
```cpp
|
||||
#include "uptime.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//If you do not want to use the string library
|
||||
//you can get the total uptime variables
|
||||
//and format the output the way you want.
|
||||
|
||||
//First call calculate_uptime() to calculate the uptime
|
||||
//and then read the uptime variables.
|
||||
uptime::calculateUptime();
|
||||
|
||||
Serial.print("days: ");
|
||||
Serial.println(uptime::getDays());
|
||||
|
||||
Serial.print("hours: ");
|
||||
Serial.println(uptime::getHours());
|
||||
|
||||
Serial.print("minutes: ");
|
||||
Serial.println(uptime::getMinutes());
|
||||
|
||||
Serial.print("seconds: ");
|
||||
Serial.println(uptime::getSeconds());
|
||||
|
||||
Serial.print("milliseconds: ");
|
||||
Serial.println(uptime::getMilliseconds());
|
||||
|
||||
Serial.print("\n");
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
||||
```
|
||||
|
||||
#### Output:
|
||||
```
|
||||
days: 0
|
||||
hours: 0
|
||||
minutes: 23
|
||||
seconds: 16
|
||||
milliseconds: 23
|
||||
|
||||
days: 0
|
||||
hours: 0
|
||||
minutes: 23
|
||||
seconds: 17
|
||||
milliseconds: 23
|
||||
|
||||
days: 0
|
||||
hours: 0
|
||||
minutes: 23
|
||||
seconds: 18
|
||||
milliseconds: 23
|
||||
```
|
|
@ -0,0 +1,35 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
|
||||
#include "uptime_formatter.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//uptime_formatter::get_uptime() returns a string
|
||||
//containing the total device uptime since startup in days, hours, minutes and seconds
|
||||
Serial.println("up " + uptime_formatter::getUptime());
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
|
||||
#include "uptime.h"
|
||||
|
||||
void setup() {
|
||||
// connect at 115200 so we can read the uptime fast enough
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
//If you do not want to use the string library
|
||||
//you can get the total uptime variables
|
||||
//and format the output the way you want.
|
||||
|
||||
//First call calculate_uptime() to calculate the uptime
|
||||
//and then read the uptime variables.
|
||||
uptime::calculateUptime();
|
||||
|
||||
Serial.print("days: ");
|
||||
Serial.println(uptime::getDays());
|
||||
|
||||
Serial.print("hours: ");
|
||||
Serial.println(uptime::getHours());
|
||||
|
||||
Serial.print("minutes: ");
|
||||
Serial.println(uptime::getMinutes());
|
||||
|
||||
Serial.print("seconds: ");
|
||||
Serial.println(uptime::getSeconds());
|
||||
|
||||
Serial.print("milliseconds: ");
|
||||
Serial.println(uptime::getMilliseconds());
|
||||
|
||||
Serial.print("\n");
|
||||
|
||||
//wait 1 second
|
||||
delay(1000);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
name=Uptime Library
|
||||
version=1.0.0
|
||||
author=Yiannis Bourkelis
|
||||
maintainer=Yiannis Bourkelis
|
||||
sentence=Uptime library for Arduino boards and compatible systems
|
||||
paragraph=Easily read the uptime since device startup, in days, hours, minutes and milliseconds, without the 49 days overflow limitation of the millis() function.
|
||||
category=Timing
|
||||
url=https://github.com/YiannisBourkelis/Uptime-Library
|
||||
architectures=*
|
124
Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.cpp
Normal file
124
Software/src/lib/YiannisBourkelis-Uptime-Library/src/uptime.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
|
||||
/*
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Caclulates the time passed since the device boot time, even after the millis() overflow, after 49 days
|
||||
*
|
||||
* Usage:
|
||||
* include "uptime_formatter.h"
|
||||
* Inside your loop() function:
|
||||
* Serial.println("Uptime: " + uptime_formatter::get_uptime());
|
||||
*
|
||||
* Examples here:
|
||||
*
|
||||
* Created 08 May 2019
|
||||
* By Yiannis Bourkelis
|
||||
*
|
||||
* https://github.com/YiannisBourkelis/
|
||||
*/
|
||||
|
||||
#include <Arduino.h> //for millis()
|
||||
#include "uptime.h"
|
||||
|
||||
//private variabes for converting milliseconds to total seconds,minutes,hours and days
|
||||
//after each call to millis()
|
||||
unsigned long uptime::m_milliseconds;
|
||||
unsigned long uptime::m_seconds;
|
||||
unsigned long uptime::m_minutes;
|
||||
unsigned long uptime::m_hours;
|
||||
unsigned long uptime::m_days;
|
||||
|
||||
//in case of millis() overflow, we store in these private variables
|
||||
//the existing time passed until the moment of the overflow
|
||||
//so that we can add them on the next call to compute the time passed
|
||||
unsigned long uptime::m_last_milliseconds = 0;
|
||||
unsigned long uptime::m_remaining_seconds = 0;
|
||||
unsigned long uptime::m_remaining_minutes = 0;
|
||||
unsigned long uptime::m_remaining_hours = 0;
|
||||
unsigned long uptime::m_remaining_days = 0;
|
||||
|
||||
//private variables that in combination hold the actual time passed
|
||||
//Use the coresponding uptime::get_.... to read these private variables
|
||||
unsigned long uptime::m_mod_milliseconds;
|
||||
unsigned long uptime::m_mod_seconds;
|
||||
unsigned long uptime::m_mod_minutes;
|
||||
unsigned long uptime::m_mod_hours;
|
||||
|
||||
uptime::uptime()
|
||||
{
|
||||
}
|
||||
|
||||
/**** get the actual time passed from device boot time ****/
|
||||
unsigned long uptime::getMilliseconds()
|
||||
{
|
||||
return uptime::m_mod_milliseconds;
|
||||
}
|
||||
unsigned long uptime::getSeconds()
|
||||
{
|
||||
return uptime::m_mod_seconds;
|
||||
}
|
||||
unsigned long uptime::getMinutes()
|
||||
{
|
||||
return uptime::m_mod_minutes;
|
||||
}
|
||||
unsigned long uptime::getHours()
|
||||
{
|
||||
return uptime::m_mod_hours;
|
||||
}
|
||||
unsigned long uptime::getDays()
|
||||
{
|
||||
return uptime::m_days;
|
||||
}
|
||||
/***********************************************************/
|
||||
|
||||
//calculate milliseconds, seconds, hours and days
|
||||
//and store them in their static variables
|
||||
void uptime::calculateUptime()
|
||||
{
|
||||
uptime::m_milliseconds = millis();
|
||||
|
||||
if (uptime::m_last_milliseconds > uptime::m_milliseconds){
|
||||
//in case of millis() overflow, store existing passed seconds,minutes,hours and days
|
||||
uptime::m_remaining_seconds = uptime::m_mod_seconds;
|
||||
uptime::m_remaining_minutes = uptime::m_mod_minutes;
|
||||
uptime::m_remaining_hours = uptime::m_mod_hours;
|
||||
uptime::m_remaining_days = uptime::m_days;
|
||||
}
|
||||
//store last millis(), so that we can detect on the next call
|
||||
//if there is a millis() overflow ( millis() returns 0 )
|
||||
uptime::m_last_milliseconds = uptime::m_milliseconds;
|
||||
|
||||
//convert passed millis to total seconds, minutes, hours and days.
|
||||
//In case of overflow, the uptime::m_remaining_... variables contain the remaining time before the overflow.
|
||||
//We add the remaining time, so that we can continue measuring the time passed from the last boot of the device.
|
||||
uptime::m_seconds = (uptime::m_milliseconds / 1000) + uptime::m_remaining_seconds;
|
||||
uptime::m_minutes = (uptime::m_seconds / 60) + uptime::m_remaining_minutes;
|
||||
uptime::m_hours = (uptime::m_minutes / 60) + uptime::m_remaining_hours;
|
||||
uptime::m_days = (uptime::m_hours / 24) + uptime::m_remaining_days;
|
||||
|
||||
//calculate the actual time passed, using modulus, in milliseconds, seconds and hours.
|
||||
//The days are calculated allready in the previous step.
|
||||
uptime::m_mod_milliseconds = uptime::m_milliseconds % 1000;
|
||||
uptime::m_mod_seconds = uptime::m_seconds % 60;
|
||||
uptime::m_mod_minutes = uptime::m_minutes % 60;
|
||||
uptime::m_mod_hours = uptime::m_hours % 24;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
#ifndef UPTIMELIB_H
|
||||
#define UPTIMELIB_H
|
||||
/*
|
||||
* Uptime library for Arduino devices
|
||||
*
|
||||
* Caclulates the time passed since the device boot time, even after the millis() overflow, after 49 days
|
||||
*
|
||||
* Usage:
|
||||
* include "uptime_formatter.h"
|
||||
* Inside your loop() function:
|
||||
* Serial.println("Uptime: " + uptime_formatter::get_uptime());
|
||||
*
|
||||
* Examples here:
|
||||
*
|
||||
* Created 08 May 2019
|
||||
* By Yiannis Bourkelis
|
||||
*
|
||||
* https://github.com/YiannisBourkelis/
|
||||
*
|
||||
*Complete documentation for each function and variable name exist
|
||||
*inside the implementation uptime.cpp file
|
||||
*/
|
||||
|
||||
class uptime
|
||||
{
|
||||
public:
|
||||
uptime();
|
||||
|
||||
static void calculateUptime();
|
||||
|
||||
static unsigned long getMilliseconds();
|
||||
static unsigned long getSeconds();
|
||||
static unsigned long getMinutes();
|
||||
static unsigned long getHours();
|
||||
static unsigned long getDays();
|
||||
|
||||
private:
|
||||
static unsigned long m_milliseconds;
|
||||
static unsigned long m_seconds;
|
||||
static unsigned long m_minutes;
|
||||
static unsigned long m_hours;
|
||||
static unsigned long m_days;
|
||||
|
||||
static unsigned long m_mod_milliseconds;
|
||||
static unsigned long m_mod_seconds;
|
||||
static unsigned long m_mod_minutes;
|
||||
static unsigned long m_mod_hours;
|
||||
|
||||
static unsigned long m_last_milliseconds;
|
||||
static unsigned long m_remaining_seconds;
|
||||
static unsigned long m_remaining_minutes;
|
||||
static unsigned long m_remaining_hours;
|
||||
static unsigned long m_remaining_days;
|
||||
};
|
||||
#endif
|
|
@ -0,0 +1,46 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************/
|
||||
|
||||
#include "uptime_formatter.h"
|
||||
#include "uptime.h"
|
||||
|
||||
uptime_formatter::uptime_formatter()
|
||||
{
|
||||
}
|
||||
|
||||
//returns the actual time passed since device boot
|
||||
//in the format: x days, y hours, z minutes, s seconds
|
||||
String uptime_formatter::getUptime()
|
||||
{
|
||||
uptime::calculateUptime();
|
||||
|
||||
return (String)(uptime::getDays() ) + " days, " +
|
||||
(String)(uptime::getHours() ) + " hours, " +
|
||||
(String)(uptime::getMinutes()) + " minutes, " +
|
||||
(String)(uptime::getSeconds()) + " seconds";
|
||||
}
|
||||
|
||||
//returns the actual time passed since device boot
|
||||
//in the format: x days, y hours, z minutes, s seconds, n milliseconds
|
||||
String uptime_formatter::getUptimeWithMillis()
|
||||
{
|
||||
return uptime_formatter::getUptime() + ", " +
|
||||
(String)(uptime::getMilliseconds()) + " milliseconds";
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ***********************************************************************
|
||||
* Uptime library for Arduino boards and compatible systems
|
||||
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
|
||||
*
|
||||
* This file is part of Uptime library for Arduino boards and compatible systems
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ***********************************************************************
|
||||
* Complete documentation for each function name exist
|
||||
* inside the implementation uptime_formatter.cpp file
|
||||
*/
|
||||
#ifndef UPTIMELIBF_H
|
||||
#define UPTIMELIBF_H
|
||||
|
||||
#include "WString.h"
|
||||
|
||||
class uptime_formatter
|
||||
{
|
||||
public:
|
||||
uptime_formatter();
|
||||
|
||||
static String getUptime();
|
||||
static String getUptimeWithMillis();
|
||||
};
|
||||
#endif
|
|
@ -87,89 +87,77 @@ static void IRAM_ATTR ws2812_rmt_adapter(const void* src, rmt_item32_t* dest, si
|
|||
*item_num = num;
|
||||
}
|
||||
|
||||
static bool rmt_initialized = false;
|
||||
static bool rmt_adapter_initialized = false;
|
||||
|
||||
void espShow(uint8_t pin, uint8_t* pixels, uint32_t numBytes, boolean is800KHz) {
|
||||
// Reserve channel
|
||||
rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX;
|
||||
for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) {
|
||||
if (!rmt_reserved_channels[i]) {
|
||||
rmt_reserved_channels[i] = true;
|
||||
channel = i;
|
||||
break;
|
||||
if (rmt_initialized == false) {
|
||||
// Reserve channel
|
||||
rmt_channel_t channel = 0;
|
||||
|
||||
#if defined(HAS_ESP_IDF_4)
|
||||
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
|
||||
config.clk_div = 2;
|
||||
#else
|
||||
// Match default TX config from ESP-IDF version 3.4
|
||||
rmt_config_t config = {.rmt_mode = RMT_MODE_TX,
|
||||
.channel = channel,
|
||||
.gpio_num = pin,
|
||||
.clk_div = 2,
|
||||
.mem_block_num = 1,
|
||||
.tx_config = {
|
||||
.carrier_freq_hz = 38000,
|
||||
.carrier_level = RMT_CARRIER_LEVEL_HIGH,
|
||||
.idle_level = RMT_IDLE_LEVEL_LOW,
|
||||
.carrier_duty_percent = 33,
|
||||
.carrier_en = false,
|
||||
.loop_en = false,
|
||||
.idle_output_en = true,
|
||||
}};
|
||||
#endif
|
||||
rmt_config(&config);
|
||||
rmt_driver_install(config.channel, 0, 0);
|
||||
|
||||
// Convert NS timings to ticks
|
||||
uint32_t counter_clk_hz = 0;
|
||||
|
||||
#if defined(HAS_ESP_IDF_4)
|
||||
rmt_get_counter_clock(channel, &counter_clk_hz);
|
||||
#else
|
||||
// this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
|
||||
if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
|
||||
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
|
||||
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
|
||||
counter_clk_hz = REF_CLK_FREQ / (div);
|
||||
} else {
|
||||
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
|
||||
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
|
||||
counter_clk_hz = APB_CLK_FREQ / (div);
|
||||
}
|
||||
#endif
|
||||
|
||||
// NS to tick converter
|
||||
float ratio = (float)counter_clk_hz / 1e9;
|
||||
|
||||
if (is800KHz) {
|
||||
t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
|
||||
t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
|
||||
t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
|
||||
t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
|
||||
} else {
|
||||
t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS);
|
||||
t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS);
|
||||
t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS);
|
||||
t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS);
|
||||
}
|
||||
|
||||
// Initialize automatic timing translator
|
||||
rmt_translator_init(0, ws2812_rmt_adapter);
|
||||
rmt_initialized = true;
|
||||
}
|
||||
if (channel == ADAFRUIT_RMT_CHANNEL_MAX) {
|
||||
// Ran out of channels!
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(HAS_ESP_IDF_4)
|
||||
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
|
||||
config.clk_div = 2;
|
||||
#else
|
||||
// Match default TX config from ESP-IDF version 3.4
|
||||
rmt_config_t config = {.rmt_mode = RMT_MODE_TX,
|
||||
.channel = channel,
|
||||
.gpio_num = pin,
|
||||
.clk_div = 2,
|
||||
.mem_block_num = 1,
|
||||
.tx_config = {
|
||||
.carrier_freq_hz = 38000,
|
||||
.carrier_level = RMT_CARRIER_LEVEL_HIGH,
|
||||
.idle_level = RMT_IDLE_LEVEL_LOW,
|
||||
.carrier_duty_percent = 33,
|
||||
.carrier_en = false,
|
||||
.loop_en = false,
|
||||
.idle_output_en = true,
|
||||
}};
|
||||
#endif
|
||||
rmt_config(&config);
|
||||
rmt_driver_install(config.channel, 0, 0);
|
||||
|
||||
// Convert NS timings to ticks
|
||||
uint32_t counter_clk_hz = 0;
|
||||
|
||||
#if defined(HAS_ESP_IDF_4)
|
||||
rmt_get_counter_clock(channel, &counter_clk_hz);
|
||||
#else
|
||||
// this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
|
||||
if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
|
||||
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
|
||||
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
|
||||
counter_clk_hz = REF_CLK_FREQ / (div);
|
||||
} else {
|
||||
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
|
||||
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
|
||||
counter_clk_hz = APB_CLK_FREQ / (div);
|
||||
}
|
||||
#endif
|
||||
|
||||
// NS to tick converter
|
||||
float ratio = (float)counter_clk_hz / 1e9;
|
||||
|
||||
if (is800KHz) {
|
||||
t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
|
||||
t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
|
||||
t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
|
||||
t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
|
||||
} else {
|
||||
t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS);
|
||||
t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS);
|
||||
t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS);
|
||||
t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS);
|
||||
}
|
||||
|
||||
// Initialize automatic timing translator
|
||||
rmt_translator_init(config.channel, ws2812_rmt_adapter);
|
||||
|
||||
// Write and wait to finish
|
||||
rmt_write_sample(config.channel, pixels, (size_t)numBytes, true);
|
||||
rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
|
||||
|
||||
// Free channel again
|
||||
rmt_driver_uninstall(config.channel);
|
||||
rmt_reserved_channels[channel] = false;
|
||||
|
||||
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
|
||||
rmt_write_sample(0, pixels, (size_t)numBytes, false);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -185,7 +185,7 @@ void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage)
|
|||
return;
|
||||
}
|
||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
||||
ets_printf("ERROR: Too many messages queued\n");
|
||||
//ets_printf("ERROR: Too many messages queued\n");
|
||||
delete dataMessage;
|
||||
} else {
|
||||
_messageQueue.add(dataMessage);
|
||||
|
|
|
@ -548,7 +548,7 @@ void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){
|
|||
return;
|
||||
}
|
||||
if(_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES){
|
||||
ets_printf("ERROR: Too many messages queued\n");
|
||||
//ets_printf("ERROR: Too many messages queued\n");
|
||||
delete dataMessage;
|
||||
} else {
|
||||
_messageQueue.add(dataMessage);
|
||||
|
@ -829,7 +829,7 @@ void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer)
|
|||
|
||||
IPAddress AsyncWebSocketClient::remoteIP() {
|
||||
if(!_client) {
|
||||
return IPAddress(0U);
|
||||
return IPAddress(static_cast<uint32_t>(0U));
|
||||
}
|
||||
return _client->remoteIP();
|
||||
}
|
||||
|
@ -1259,9 +1259,9 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket
|
|||
(String&)key += WS_STR_UUID;
|
||||
mbedtls_sha1_context ctx;
|
||||
mbedtls_sha1_init(&ctx);
|
||||
mbedtls_sha1_starts_ret(&ctx);
|
||||
mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length());
|
||||
mbedtls_sha1_finish_ret(&ctx, hash);
|
||||
mbedtls_sha1_starts(&ctx);
|
||||
mbedtls_sha1_update(&ctx, (const unsigned char*)key.c_str(), key.length());
|
||||
mbedtls_sha1_finish(&ctx, hash);
|
||||
mbedtls_sha1_free(&ctx);
|
||||
#endif
|
||||
base64_encodestate _state;
|
||||
|
|
|
@ -71,9 +71,9 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
|
|||
memset(_buf, 0x00, 16);
|
||||
#ifdef ESP32
|
||||
mbedtls_md5_init(&_ctx);
|
||||
mbedtls_md5_starts_ret(&_ctx);
|
||||
mbedtls_md5_update_ret(&_ctx, data, len);
|
||||
mbedtls_md5_finish_ret(&_ctx, _buf);
|
||||
mbedtls_md5_starts(&_ctx);
|
||||
mbedtls_md5_update(&_ctx, data, len);
|
||||
mbedtls_md5_finish(&_ctx, _buf);
|
||||
#else
|
||||
MD5Init(&_ctx);
|
||||
MD5Update(&_ctx, data, len);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue