Merge branch 'main' into feature/mqtt-on-main

This commit is contained in:
Cabooman 2024-02-03 19:20:42 +01:00
commit 7900aedd83
17 changed files with 698 additions and 138 deletions

View file

@ -70,6 +70,7 @@ uint8_t bms_status = ACTIVE; // ACTIVE - [0..5]<>[STANDBY,INACTIVE,DA
uint16_t stat_batt_power = 0; // Power going in/out of battery uint16_t stat_batt_power = 0; // Power going in/out of battery
uint16_t cell_max_voltage = 3700; // Stores the highest cell voltage value in the system uint16_t cell_max_voltage = 3700; // Stores the highest cell voltage value in the system
uint16_t cell_min_voltage = 3700; // Stores the minimum cell voltage value in the system uint16_t cell_min_voltage = 3700; // Stores the minimum cell voltage value in the system
uint16_t cellvoltages[120]; // Stores all cell voltages
bool LFP_Chemistry = false; bool LFP_Chemistry = false;
// Common charger parameters // Common charger parameters
@ -148,6 +149,7 @@ void loop() {
#ifdef WEBSERVER #ifdef WEBSERVER
// Over-the-air updates by ElegantOTA // Over-the-air updates by ElegantOTA
ElegantOTA.loop(); ElegantOTA.loop();
WiFi_monitor_loop();
#ifdef MQTT #ifdef MQTT
mqtt_loop(); mqtt_loop();
#endif #endif
@ -416,8 +418,12 @@ void receive_can() { // This section checks if we have a complete CAN message i
#ifdef SMA_CAN #ifdef SMA_CAN
receive_can_sma(rx_frame); receive_can_sma(rx_frame);
#endif #endif
// Charger
#ifdef CHEVYVOLT_CHARGER #ifdef CHEVYVOLT_CHARGER
receive_can_chevyvolt_charger(rx_frame); receive_can_chevyvolt_charger(rx_frame);
#endif
#ifdef NISSANLEAF_CHARGER
receive_can_nissanleaf_charger(rx_frame);
#endif #endif
} else { } else {
//printf("New extended frame"); //printf("New extended frame");
@ -480,6 +486,9 @@ void send_can() {
#ifdef CHEVYVOLT_CHARGER #ifdef CHEVYVOLT_CHARGER
send_can_chevyvolt_charger(); send_can_chevyvolt_charger();
#endif #endif
#ifdef NISSANLEAF_CHARGER
send_can_nissanleaf_charger();
#endif
} }
#ifdef DUAL_CAN #ifdef DUAL_CAN

View file

@ -15,6 +15,7 @@ volatile uint16_t MAXCHARGEAMP =
volatile uint16_t MAXDISCHARGEAMP = volatile uint16_t MAXDISCHARGEAMP =
300; //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited) 300; //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited)
/* Charger settings (Optional, when generator charging) */
// MQTT // MQTT
#ifdef MQTT #ifdef MQTT
const char* mqtt_user = "REDACTED"; const char* mqtt_user = "REDACTED";

View file

@ -48,6 +48,7 @@
/* Select charger used (Optional) */ /* Select charger used (Optional) */
//#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function. //#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function.
//#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging
/* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */ /* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */
extern volatile uint16_t BATTERY_WH_MAX; extern volatile uint16_t BATTERY_WH_MAX;
@ -57,7 +58,7 @@ extern volatile uint16_t MAXCHARGEAMP;
extern volatile uint16_t MAXDISCHARGEAMP; extern volatile uint16_t MAXDISCHARGEAMP;
extern volatile uint8_t AccessPointEnabled; extern volatile uint8_t AccessPointEnabled;
/* Charger limits: Set in the USER_SETTINGS.cpp or later in the webserver */ /* Charger limits (Optional): Set in the USER_SETTINGS.cpp or later in the webserver */
extern volatile float charger_setpoint_HV_VDC; extern volatile float charger_setpoint_HV_VDC;
extern volatile float charger_setpoint_HV_IDC; extern volatile float charger_setpoint_HV_IDC;
extern volatile float charger_setpoint_HV_IDC_END; extern volatile float charger_setpoint_HV_IDC_END;

View file

@ -173,8 +173,6 @@ void print_with_units(char* header, int value, char* units) {
} }
void update_values_leaf_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */ void update_values_leaf_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
bms_status = ACTIVE; //Startout in active mode
/* Start with mapping all values */ /* Start with mapping all values */
StateOfHealth = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00% StateOfHealth = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00%
@ -250,6 +248,13 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
max_target_charge_power = 0; //No need to charge further, set max power to 0 max_target_charge_power = 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) {
cellvoltages[i] = cell_voltages[i];
}
bms_status = ACTIVE; //Startout in active mode
/*Extra safety functions below*/ /*Extra safety functions below*/
if (LB_GIDS < 10) //800Wh left in battery if (LB_GIDS < 10) //800Wh left in battery
{ //Battery is running abnormally low, some discharge logic might have failed. Zero it all out. { //Battery is running abnormally low, some discharge logic might have failed. Zero it all out.
@ -262,7 +267,9 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
(ABSOLUTE_MAX_VOLTAGE - 100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT (ABSOLUTE_MAX_VOLTAGE - 100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
if (LB_SOC < 650) { if (LB_SOC < 650) {
bms_status = FAULT; bms_status = FAULT;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!");
#endif
} }
} }
@ -270,8 +277,14 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
max_target_charge_power = 0; max_target_charge_power = 0;
} }
if (LB_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already
max_target_discharge_power = 0;
}
if (LB_Relay_Cut_Request) { //LB_FAIL, BMS requesting shutdown and contactors to be opened if (LB_Relay_Cut_Request) { //LB_FAIL, BMS requesting shutdown and contactors to be opened
#ifdef DEBUG_VIA_USB
Serial.println("Battery requesting immediate shutdown and contactors to be opened!"); Serial.println("Battery requesting immediate shutdown and contactors to be opened!");
#endif
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario //Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
errorCode = 1; errorCode = 1;
max_target_discharge_power = 0; max_target_discharge_power = 0;
@ -294,27 +307,35 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
//Normal stop request. For stationary storage we don't disconnect contactors, so we ignore this. //Normal stop request. For stationary storage we don't disconnect contactors, so we ignore this.
break; break;
case (4): case (4):
//Caution Lamp Request //Caution Lamp Request
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: Battery raised caution indicator. Inspect battery status!"); Serial.println("ERROR: Battery raised caution indicator. Inspect battery status!");
#endif
break; break;
case (5): case (5):
//Caution Lamp Request & Normal Stop Request //Caution Lamp Request & Normal Stop Request
bms_status = FAULT; bms_status = FAULT;
errorCode = 2; errorCode = 2;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!");
#endif
break; break;
case (6): case (6):
//Caution Lamp Request & Charging Mode Stop Request //Caution Lamp Request & Charging Mode Stop Request
bms_status = FAULT; bms_status = FAULT;
errorCode = 3; errorCode = 3;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!");
#endif
break; break;
case (7): case (7):
//Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request //Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request
bms_status = FAULT; bms_status = FAULT;
errorCode = 4; errorCode = 4;
#ifdef DEBUG_VIA_USB
Serial.println( Serial.println(
"ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!");
#endif
break; break;
default: default:
break; break;
@ -323,8 +344,10 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
if (LB_StateOfHealth < 25) { //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out. 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 if (LB_StateOfHealth != 0) { //Extra check to see that we actually have a SOH Value available
#ifdef DEBUG_VIA_USB
Serial.println( Serial.println(
"ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."); "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery.");
#endif
bms_status = FAULT; bms_status = FAULT;
errorCode = 5; errorCode = 5;
max_target_discharge_power = 0; max_target_discharge_power = 0;
@ -334,9 +357,11 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
#ifdef INTERLOCK_REQUIRED #ifdef INTERLOCK_REQUIRED
if (!LB_Interlock) { if (!LB_Interlock) {
#ifdef DEBUG_VIA_USB
Serial.println( Serial.println(
"ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be "
"disabled!"); "disabled!");
#endif
bms_status = FAULT; bms_status = FAULT;
errorCode = 6; errorCode = 6;
SOC = 0; SOC = 0;
@ -349,7 +374,9 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
if (!CANstillAlive) { if (!CANstillAlive) {
bms_status = FAULT; bms_status = FAULT;
errorCode = 7; errorCode = 7;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
#endif
} else { } else {
CANstillAlive--; CANstillAlive--;
} }
@ -358,7 +385,9 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
{ {
errorCode = 10; errorCode = 10;
LEDcolor = YELLOW; LEDcolor = YELLOW;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!");
#endif
} }
/*Finally print out values to serial if configured to do so*/ /*Finally print out values to serial if configured to do so*/
@ -463,6 +492,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) {
if (LB_TEMP != 0x3ff) { //3FF is unavailable value if (LB_TEMP != 0x3ff) { //3FF is unavailable value
LB_SOC = LB_TEMP; LB_SOC = LB_TEMP;
} }
LB_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7);
break; break;
case 0x5BC: case 0x5BC:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
@ -593,18 +623,24 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) {
if (cell_deviation_mV > MAX_CELL_DEVIATION) { if (cell_deviation_mV > MAX_CELL_DEVIATION) {
LEDcolor = YELLOW; LEDcolor = YELLOW;
#ifdef DEBUG_VIA_USB
Serial.println("HIGH CELL DEVIATION!!! Inspect battery!"); Serial.println("HIGH CELL DEVIATION!!! Inspect battery!");
#endif
} }
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) { if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
bms_status = FAULT; bms_status = FAULT;
errorCode = 8; errorCode = 8;
#ifdef DEBUG_VIA_USB
Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
#endif
} }
if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) { if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) {
bms_status = FAULT; bms_status = FAULT;
errorCode = 9; errorCode = 9;
#ifdef DEBUG_VIA_USB
Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
#endif
} }
break; break;
} }
@ -700,54 +736,56 @@ void send_can_leaf_battery() {
// VCM message, containing info if battery should sleep or stay awake // VCM message, containing info if battery should sleep or stay awake
ESP32Can.CANWriteFrame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1 ESP32Can.CANWriteFrame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1
mprun100++; switch (mprun100) {
if (mprun100 > 3) { case 0:
mprun100 = 0; LEAF_50C.data.u8[3] = 0x00;
} LEAF_50C.data.u8[4] = 0x5D;
LEAF_50C.data.u8[5] = 0xC8;
if (mprun100 == 0) { break;
LEAF_50C.data.u8[3] = 0x00; case 1:
LEAF_50C.data.u8[4] = 0x5D; LEAF_50C.data.u8[3] = 0x01;
LEAF_50C.data.u8[5] = 0xC8; LEAF_50C.data.u8[4] = 0xB2;
} else if (mprun100 == 1) { LEAF_50C.data.u8[5] = 0x31;
LEAF_50C.data.u8[3] = 0x01; break;
LEAF_50C.data.u8[4] = 0xB2; case 2:
LEAF_50C.data.u8[5] = 0x31; LEAF_50C.data.u8[3] = 0x02;
} else if (mprun100 == 2) { LEAF_50C.data.u8[4] = 0x5D;
LEAF_50C.data.u8[3] = 0x02; LEAF_50C.data.u8[5] = 0x63;
LEAF_50C.data.u8[4] = 0x5D; break;
LEAF_50C.data.u8[5] = 0x63; case 3:
} else if (mprun100 == 3) { LEAF_50C.data.u8[3] = 0x03;
LEAF_50C.data.u8[3] = 0x03; LEAF_50C.data.u8[4] = 0xB2;
LEAF_50C.data.u8[4] = 0xB2; LEAF_50C.data.u8[5] = 0x9A;
LEAF_50C.data.u8[5] = 0x9A; break;
} }
ESP32Can.CANWriteFrame(&LEAF_50C); ESP32Can.CANWriteFrame(&LEAF_50C);
mprun100 = (mprun100 + 1) % 4; // mprun100 cycles between 0-1-2-3-0-1...
} }
//Send 10ms message //Send 10ms message
if (currentMillis - previousMillis10 >= interval10) { if (currentMillis - previousMillis10 >= interval10) {
previousMillis10 = currentMillis; previousMillis10 = currentMillis;
if (mprun10 == 0) { switch (mprun10) {
LEAF_1D4.data.u8[4] = 0x07; case 0:
LEAF_1D4.data.u8[7] = 0x12; LEAF_1D4.data.u8[4] = 0x07;
} else if (mprun10 == 1) { LEAF_1D4.data.u8[7] = 0x12;
LEAF_1D4.data.u8[4] = 0x47; break;
LEAF_1D4.data.u8[7] = 0xD5; case 1:
} else if (mprun10 == 2) { LEAF_1D4.data.u8[4] = 0x47;
LEAF_1D4.data.u8[4] = 0x87; LEAF_1D4.data.u8[7] = 0xD5;
LEAF_1D4.data.u8[7] = 0x19; break;
} else if (mprun10 == 3) { case 2:
LEAF_1D4.data.u8[4] = 0xC7; LEAF_1D4.data.u8[4] = 0x87;
LEAF_1D4.data.u8[7] = 0xDE; 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); ESP32Can.CANWriteFrame(&LEAF_1D4);
mprun10++;
if (mprun10 > 3) {
mprun10 = 0;
}
switch (mprun10r) { switch (mprun10r) {
case (0): case (0):
LEAF_1F2.data.u8[3] = 0xB0; LEAF_1F2.data.u8[3] = 0xB0;
@ -755,22 +793,18 @@ void send_can_leaf_battery() {
LEAF_1F2.data.u8[7] = 0x8F; LEAF_1F2.data.u8[7] = 0x8F;
break; break;
case (1): case (1):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x01; LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x80; LEAF_1F2.data.u8[7] = 0x80;
break; break;
case (2): case (2):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x02; LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x81; LEAF_1F2.data.u8[7] = 0x81;
break; break;
case (3): case (3):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x03; LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x82; LEAF_1F2.data.u8[7] = 0x82;
break; break;
case (4): case (4):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x00; LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x8F; LEAF_1F2.data.u8[7] = 0x8F;
break; break;
@ -780,22 +814,18 @@ void send_can_leaf_battery() {
LEAF_1F2.data.u8[7] = 0x84; LEAF_1F2.data.u8[7] = 0x84;
break; break;
case (6): case (6):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x02; LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x85; LEAF_1F2.data.u8[7] = 0x85;
break; break;
case (7): case (7):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x03; LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x86; LEAF_1F2.data.u8[7] = 0x86;
break; break;
case (8): case (8):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x00; LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x83; LEAF_1F2.data.u8[7] = 0x83;
break; break;
case (9): case (9):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x01; LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x84; LEAF_1F2.data.u8[7] = 0x84;
break; break;
@ -805,22 +835,18 @@ void send_can_leaf_battery() {
LEAF_1F2.data.u8[7] = 0x81; LEAF_1F2.data.u8[7] = 0x81;
break; break;
case (11): case (11):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x03; LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x82; LEAF_1F2.data.u8[7] = 0x82;
break; break;
case (12): case (12):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x00; LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x8F; LEAF_1F2.data.u8[7] = 0x8F;
break; break;
case (13): case (13):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x01; LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x80; LEAF_1F2.data.u8[7] = 0x80;
break; break;
case (14): case (14):
LEAF_1F2.data.u8[3] = 0xB0;
LEAF_1F2.data.u8[6] = 0x02; LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x81; LEAF_1F2.data.u8[7] = 0x81;
break; break;
@ -830,22 +856,18 @@ void send_can_leaf_battery() {
LEAF_1F2.data.u8[7] = 0x86; LEAF_1F2.data.u8[7] = 0x86;
break; break;
case (16): case (16):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x00; LEAF_1F2.data.u8[6] = 0x00;
LEAF_1F2.data.u8[7] = 0x83; LEAF_1F2.data.u8[7] = 0x83;
break; break;
case (17): case (17):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x01; LEAF_1F2.data.u8[6] = 0x01;
LEAF_1F2.data.u8[7] = 0x84; LEAF_1F2.data.u8[7] = 0x84;
break; break;
case (18): case (18):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x02; LEAF_1F2.data.u8[6] = 0x02;
LEAF_1F2.data.u8[7] = 0x85; LEAF_1F2.data.u8[7] = 0x85;
break; break;
case (19): case (19):
LEAF_1F2.data.u8[3] = 0xB4;
LEAF_1F2.data.u8[6] = 0x03; LEAF_1F2.data.u8[6] = 0x03;
LEAF_1F2.data.u8[7] = 0x86; LEAF_1F2.data.u8[7] = 0x86;
break; break;
@ -853,12 +875,14 @@ void send_can_leaf_battery() {
break; 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 = (mprun10r + 1) % 20; // 0x1F2 patter repeats after 20 messages. 0-1..19-0
if (mprun10r > 19) { // 0x1F2 patter repeats after 20 messages,
mprun10r = 0; mprun10 = (mprun10 + 1) % 4; // mprun10 cycles between 0-1-2-3-0-1...
}
} }
//Send 10s CAN messages //Send 10s CAN messages
if (currentMillis - previousMillis10s >= interval10s) { if (currentMillis - previousMillis10s >= interval10s) {
@ -866,13 +890,8 @@ void send_can_leaf_battery() {
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?) //Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
if (!stop_battery_query) { if (!stop_battery_query) {
if (group == 1) { // Cycle between group 1, 2, and 4 using bit manipulation group = (group == 1) ? 2 : (group == 2) ? 4 : 1;
group = 2; // Cycle between group 1, 2, and 4 using ternary operation
} else if (group == 2) {
group = 4;
} else if (group == 4) {
group = 1;
}
LEAF_GROUP_REQUEST.data.u8[2] = group; LEAF_GROUP_REQUEST.data.u8[2] = group;
ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST); ESP32Can.CANWriteFrame(&LEAF_GROUP_REQUEST);
} }

View file

@ -21,11 +21,12 @@ extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5 extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2 extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10 extern uint8_t LEDcolor; //Enum, 0-10
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
void update_values_leaf_battery(); void update_values_leaf_battery();

View file

@ -160,6 +160,8 @@ static const char* hvilStatusState[] = {"NOT OK",
#define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value #define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_LFP 150 //LED turns yellow on the board if mv delta exceeds this value #define MAX_CELL_DEVIATION_LFP 150 //LED turns yellow on the board if mv delta exceeds this value
#define REASONABLE_ENERGYAMOUNT 20 //When the BMS stops making sense on some values, they are always <20
void update_values_tesla_model_3_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus void update_values_tesla_model_3_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 //After values are mapped, we perform some safety checks, and do some serial printouts
//Calculate the SOH% to send to inverter //Calculate the SOH% to send to inverter
@ -167,6 +169,10 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
StateOfHealth = StateOfHealth =
static_cast<uint16_t>((static_cast<double>(nominal_full_pack_energy) / bat_beginning_of_life) * 10000.0); static_cast<uint16_t>((static_cast<double>(nominal_full_pack_energy) / bat_beginning_of_life) * 10000.0);
} }
//If the value is unavailable, set SOH to 99%
if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
StateOfHealth = 9900;
}
//Calculate the SOC% value to send to inverter //Calculate the SOC% value to send to inverter
soc_calculated = soc_vi; soc_calculated = soc_vi;
@ -256,8 +262,8 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
} }
//Check if BMS is in need of recalibration //Check if BMS is in need of recalibration
if (nominal_full_pack_energy < 20) { if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
Serial.println("ERROR: kWh remaining reported by battery not plausible. Battery needs cycling or is broken!"); Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling.");
LEDcolor = YELLOW; LEDcolor = YELLOW;
} }
@ -358,9 +364,9 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
Serial.print(", "); Serial.print(", ");
print_int_with_units(" Max charge power: ", max_target_charge_power, "W"); print_int_with_units(" Max charge power: ", max_target_charge_power, "W");
Serial.println(""); Serial.println("");
print_int_with_units(" Max temperature: ", temperature_max, "C"); print_int_with_units(" Max temperature: ", (temperature_max * 0.1), "°C");
Serial.print(", "); Serial.print(", ");
print_int_with_units(" Min temperature: ", temperature_min, "C"); print_int_with_units(" Min temperature: ", (temperature_min * 0.1), "°C");
Serial.println(""); Serial.println("");
#endif #endif
} }
@ -445,6 +451,22 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C) min_temp = (rx_frame.data.u8[3] * 5) - 400; //Multiply by 5 and remove offset to get C+1 (0x61*5=485-400=8.5*C)
} }
break; break;
case 0x401: // Cell stats
mux = (rx_frame.data.u8[0]);
static uint16_t volts;
if (rx_frame.data.u8[1] == 0x2A) // status byte must be 0x2A to read cellvoltages
{
// Example, frame3=0x89,frame2=0x1D = 35101 / 10 = 3510mV
volts = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) / 10;
cellvoltages[mux * 3] = volts;
volts = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) / 10;
cellvoltages[1 + mux * 3] = volts;
volts = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) / 10;
cellvoltages[2 + mux * 3] = volts;
}
break;
case 0x2d2: case 0x2d2:
//Min / max limits //Min / max limits
min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V
@ -456,7 +478,7 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
break; break;
case 0x2b4: case 0x2b4:
low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625; low_voltage = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]) * 0.0390625;
high_voltage = (((rx_frame.data.u8[2] << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484; high_voltage = ((((rx_frame.data.u8[2] & 0x3F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2))) * 0.146484;
output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100; output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100;
break; break;
case 0x292: case 0x292:

View file

@ -24,11 +24,12 @@ extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5 extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2 extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10 extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool LFP_Chemistry; extern bool LFP_Chemistry;

View file

@ -25,7 +25,7 @@ void update_values_test_battery() { /* This function puts fake values onto the p
StateOfHealth = 9900; // 99.00% StateOfHealth = 9900; // 99.00%
battery_voltage = 3700; // 370.0V //battery_voltage = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
battery_current = 0; // 0 A battery_current = 0; // 0 A
@ -33,9 +33,9 @@ void update_values_test_battery() { /* This function puts fake values onto the p
remaining_capacity_Wh = 15000; // 15kWh remaining_capacity_Wh = 15000; // 15kWh
cell_max_voltage = 3750; cell_max_voltage = 3596;
cell_min_voltage = 3730; cell_min_voltage = 3500;
stat_batt_power = 0; // 0W stat_batt_power = 0; // 0W
@ -47,6 +47,10 @@ void update_values_test_battery() { /* This function puts fake values onto the p
max_target_charge_power = 5000; // 5kW max_target_charge_power = 5000; // 5kW
for (int i = 0; i < 97; ++i) {
cellvoltages[i] = 3500 + i;
}
/*Finally print out values to serial if configured to do so*/ /*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
Serial.println("FAKE Values going to inverter"); Serial.println("FAKE Values going to inverter");

View file

@ -21,10 +21,11 @@ extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5 extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2 extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-5000 per cell
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint8_t LEDcolor; //Enum, 0-10 extern uint8_t LEDcolor; //Enum, 0-10

View file

@ -2,7 +2,11 @@
#define CHARGERS_H #define CHARGERS_H
#ifdef CHEVYVOLT_CHARGER #ifdef CHEVYVOLT_CHARGER
#include "chevyvolt.h" #include "CHEVY-VOLT-CHARGER.h"
#endif
#ifdef NISSANLEAF_CHARGER
#include "NISSAN-LEAF-CHARGER.h"
#endif #endif
#endif #endif

View file

@ -1,4 +1,4 @@
#include "chevyvolt.h" #include "CHEVY-VOLT-CHARGER.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"

View file

@ -1,5 +1,5 @@
#ifndef CHEVYVOLT_H #ifndef CHEVYVOLT_CHARGER_H
#define CHEVYVOLT_H #define CHEVYVOLT_CHARGER_H
#include <Arduino.h> #include <Arduino.h>
#include "../../USER_SETTINGS.h" #include "../../USER_SETTINGS.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"

View file

@ -0,0 +1,275 @@
#include "NISSAN-LEAF-CHARGER.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
/* This implements Nissan LEAF PDM charger support. 2013-2024 Gen2/3 PDMs are supported
*
* This code is intended to facilitate standalone battery charging,
* for instance for troubleshooting purposes or emergency generator usage
*
* Credits go to:
* Damien Maguire (evmbw.org, https://github.com/damienmaguire/Stm32-vcu)
*
* The code functions a bit differently incase a Nissan LEAF battery is used. Almost
* all CAN messages are already sent from the battery in that case, so the charger
* implementation can focus on only controlling the charger, spoofing battery messages
* is not needed.
*
* Incase another battery type is used, the code ofcourse emulates a complete Nissan LEAF
* battery onto the CAN bus.
*/
/* CAN cycles and timers */
static const uint8_t interval10ms = 10;
static const uint8_t interval100ms = 100;
static unsigned long previousMillis10ms = 0;
static unsigned long previousMillis100ms = 0;
/* LEAF charger/battery parameters */
enum OBC_MODES : uint8_t {
IDLE_OR_QC = 1,
FINISHED = 2,
CHARGING_OR_INTERRUPTED = 4,
IDLE1 = 8,
IDLE2 = 9,
PLUGGED_IN_WAITING_ON_TIMER
};
enum OBC_VOLTAGES : uint8_t { NO_SIGNAL = 0, AC110 = 1, AC230 = 2, ABNORMAL_WAVE = 3 };
static uint16_t OBC_Charge_Power = 0; // Actual charger output
static uint8_t mprun100 = 0; // Counter 0-3
static uint8_t mprun10 = 0; // Counter 0-3
static uint8_t OBC_Charge_Status = IDLE_OR_QC;
static uint8_t OBC_Status_AC_Voltage = 0; //1=110V, 2=230V
static uint8_t OBCpowerSetpoint = 0;
static uint8_t OBCpower = 0;
static bool PPStatus = false;
static bool OBCwakeup = false;
/* Voltage and current settings. Validation performed to set ceiling of 3300w vol*cur */
extern volatile float charger_setpoint_HV_VDC;
extern volatile float charger_setpoint_HV_IDC;
extern volatile float charger_setpoint_HV_IDC_END;
extern bool charger_HV_enabled;
extern bool charger_aux12V_enabled;
extern float charger_stat_HVcur;
extern float charger_stat_HVvol;
extern float charger_stat_ACcur;
extern float charger_stat_ACvol;
extern float charger_stat_LVcur;
extern float charger_stat_LVvol;
//Actual content messages
static CAN_frame_t LEAF_1DB = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x1DB,
.data = {0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00}};
static CAN_frame_t LEAF_1DC = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x1DC,
.data = {0x6E, 0x0A, 0x05, 0xD5, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t LEAF_1F2 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x1F2,
.data = {0x30, 0x00, 0x20, 0xAC, 0x00, 0x3C, 0x00, 0x8F}};
static CAN_frame_t LEAF_50B = {.FIR = {.B =
{
.DLC = 7,
.FF = CAN_frame_std,
}},
.MsgID = 0x50B,
.data = {0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00}};
static CAN_frame_t LEAF_55B = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x55B,
.data = {0xA4, 0x40, 0xAA, 0x00, 0xDF, 0xC0, 0x10, 0x00}};
static CAN_frame_t LEAF_5BC = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x5BC,
.data = {0x3D, 0x80, 0xF0, 0x64, 0xB0, 0x01, 0x00, 0x32}};
static CAN_frame_t LEAF_59E = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x59E,
.data = {0x00, 0x00, 0x0C, 0x76, 0x18, 0x00, 0x00, 0x00}};
static uint8_t crctable[256] = {
0, 133, 143, 10, 155, 30, 20, 145, 179, 54, 60, 185, 40, 173, 167, 34, 227, 102, 108, 233, 120, 253,
247, 114, 80, 213, 223, 90, 203, 78, 68, 193, 67, 198, 204, 73, 216, 93, 87, 210, 240, 117, 127, 250,
107, 238, 228, 97, 160, 37, 47, 170, 59, 190, 180, 49, 19, 150, 156, 25, 136, 13, 7, 130, 134, 3,
9, 140, 29, 152, 146, 23, 53, 176, 186, 63, 174, 43, 33, 164, 101, 224, 234, 111, 254, 123, 113, 244,
214, 83, 89, 220, 77, 200, 194, 71, 197, 64, 74, 207, 94, 219, 209, 84, 118, 243, 249, 124, 237, 104,
98, 231, 38, 163, 169, 44, 189, 56, 50, 183, 149, 16, 26, 159, 14, 139, 129, 4, 137, 12, 6, 131,
18, 151, 157, 24, 58, 191, 181, 48, 161, 36, 46, 171, 106, 239, 229, 96, 241, 116, 126, 251, 217, 92,
86, 211, 66, 199, 205, 72, 202, 79, 69, 192, 81, 212, 222, 91, 121, 252, 246, 115, 226, 103, 109, 232,
41, 172, 166, 35, 178, 55, 61, 184, 154, 31, 21, 144, 1, 132, 142, 11, 15, 138, 128, 5, 148, 17,
27, 158, 188, 57, 51, 182, 39, 162, 168, 45, 236, 105, 99, 230, 119, 242, 248, 125, 95, 218, 208, 85,
196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42,
32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141};
static uint8_t calculate_CRC_Nissan(CAN_frame_t* frame) {
uint8_t crc = 0;
for (uint8_t j = 0; j < 7; j++) {
crc = crctable[(crc ^ static_cast<uint8_t>(frame->data.u8[j])) % 256];
}
return crc;
}
static uint8_t calculate_checksum_nibble(CAN_frame_t* frame) {
uint8_t sum = 0;
for (uint8_t i = 0; i < 7; i++) {
sum += frame->data.u8[i] >> 4;
sum += frame->data.u8[i] & 0xF;
}
sum = (sum + 2) & 0xF;
return sum;
}
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x679: // This message fires once when charging cable is plugged in
OBCwakeup = true;
charger_aux12V_enabled = true; //Not possible to turn off 12V charging
// Startout with default values, so that charging can begin right when user plugs in cable
charger_HV_enabled = true;
charger_setpoint_HV_IDC = 16; // Ampere
charger_setpoint_HV_VDC = 400; // Target voltage
break;
case 0x390:
OBC_Charge_Status = ((rx_frame.data.u8[5] & 0x7E) >> 1);
if (OBC_Charge_Status == PLUGGED_IN_WAITING_ON_TIMER || CHARGING_OR_INTERRUPTED) {
PPStatus = true; //plug inserted
} else {
PPStatus = false; //plug not inserted
}
OBC_Status_AC_Voltage = ((rx_frame.data.u8[3] & 0x18) >> 3);
if (OBC_Status_AC_Voltage == AC110) {
charger_stat_ACvol = 110;
}
if (OBC_Status_AC_Voltage == AC230) {
charger_stat_ACvol = 230;
}
if (OBC_Status_AC_Voltage == ABNORMAL_WAVE) {
charger_stat_ACvol = 1;
}
OBC_Charge_Power = ((rx_frame.data.u8[0] & 0x01) << 8) | (rx_frame.data.u8[1]);
charger_stat_HVcur = OBC_Charge_Power;
break;
default:
break;
}
}
void send_can_nissanleaf_charger() {
unsigned long currentMillis = millis();
/* Send keepalive with mode every 10ms */
if (currentMillis - previousMillis10ms >= interval10ms) {
previousMillis10ms = currentMillis;
mprun10++;
if (mprun10 >= 4)
mprun10 = 0;
/* 1DB is the main control message. If LEAF battery is used, the battery controls almost everything */
// Only send these messages if Nissan LEAF battery is not used
#ifndef NISSAN_LEAF_BATTERY
// VCM message, containing info if battery should sleep or stay awake
ESP32Can.CANWriteFrame(&LEAF_50B); // HCM_WakeUpSleepCommand == 11b == WakeUp, and CANMASK = 1
LEAF_1DB.data.u8[7] = calculate_CRC_Nissan(&LEAF_1DB);
ESP32Can.CANWriteFrame(&LEAF_1DB);
LEAF_1DC.data.u8[7] = calculate_CRC_Nissan(&LEAF_1DC);
ESP32Can.CANWriteFrame(&LEAF_1DC);
#endif
OBCpowerSetpoint = ((charger_setpoint_HV_IDC * 4) + 0x64);
// convert power setpoint to PDM format:
// 0xA0 = 15A (60x)
// 0x70 = 3 amps ish (12x)
// 0x6a = 1.4A (6x)
// 0x66 = 0.5A (2x)
// 0x65 = 0.3A (1x)
// 0x64 = no chg
// so 0x64=100. 0xA0=160. so 60 decimal steps. 1 step=100W???
// This line controls if power should flow or not
if (PPStatus &&
charger_HV_enabled) { //Charging starts when cable plugged in and User has requested charging to start via WebUI
// clamp min and max values
if (OBCpowerSetpoint > 0xA0) { //15A TODO, raise once cofirmed how to map bits into frame0 and frame1
OBCpowerSetpoint = 0xA0;
} else if (OBCpowerSetpoint <= 0x64) {
OBCpowerSetpoint = 0x64; // 100W? stuck at 100 in drive mode (no charging)
}
// if actual battery_voltage is less than setpoint got to max power set from web ui
if (battery_voltage < (CHARGER_SET_HV * 10)) { //battery_voltage = V+1, 0-500.0 (0-5000)
OBCpower = OBCpowerSetpoint;
}
// decrement charger power if volt setpoint is reached
if (battery_voltage >= (CHARGER_SET_HV * 10)) {
if (OBCpower > 0x64) {
OBCpower--;
}
}
} else {
// set power to 0 if charge control is set to off or not in charge mode
OBCpower = 0x64;
}
LEAF_1F2.data.u8[1] = OBCpower;
LEAF_1F2.data.u8[6] = mprun10;
LEAF_1F2.data.u8[7] = calculate_checksum_nibble(&LEAF_1F2);
ESP32Can.CANWriteFrame(
&LEAF_1F2); // Sending of 1F2 message is halted in LEAF-BATTERY function incase charger is used!
}
/* Send messages every 100ms here */
if (currentMillis - previousMillis100ms >= interval100ms) {
previousMillis100ms = currentMillis;
mprun100++;
if (mprun100 > 3) {
mprun100 = 0;
}
// Only send these messages if Nissan LEAF battery is not used
#ifndef NISSAN_LEAF_BATTERY
LEAF_55B.data.u8[6] = ((0x1 << 4) | (mprun100));
LEAF_55B.data.u8[7] = calculate_CRC_Nissan(&LEAF_55B);
ESP32Can.CANWriteFrame(&LEAF_55B);
ESP32Can.CANWriteFrame(&LEAF_59E);
ESP32Can.CANWriteFrame(&LEAF_5BC);
#endif
}
}

View file

@ -0,0 +1,12 @@
#ifndef NISSANLEAF_CHARGER_H
#define NISSANLEAF_CHARGER_H
#include <Arduino.h>
#include "../../USER_SETTINGS.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
void send_can_nissanleaf_charger();
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame);
#endif

View file

@ -42,7 +42,9 @@ bool wifi_connected;
// Wifi connect time declarations and definition // Wifi connect time declarations and definition
unsigned long wifi_connect_start_time; unsigned long wifi_connect_start_time;
unsigned long wifi_connect_current_time; unsigned long wifi_connect_current_time;
const long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds unsigned long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds
unsigned long wifi_monitor_loop_time = 30000; // Will check if WiFi is connected and try reconnect every x milliseconds
unsigned long last_wifi_monitor_run = 0;
void init_webserver() { void init_webserver() {
// Configure WiFi // Configure WiFi
@ -63,6 +65,11 @@ void init_webserver() {
server.on("/settings", HTTP_GET, server.on("/settings", HTTP_GET,
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, settings_processor); }); [](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, settings_processor); });
// Route for going to cellmonitor web page
server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, cellmonitor_processor);
});
// Route for editing Wh // Route for editing Wh
server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) { server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("value")) { if (request->hasParam("value")) {
@ -123,7 +130,23 @@ void init_webserver() {
} }
}); });
#ifdef CHEVYVOLT_CHARGER #ifdef TEST_FAKE_BATTERY
// Route for editing FakeBatteryVoltage
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
float val = value.toFloat();
battery_voltage = val * 10;
request->send(200, "text/plain", "Updated successfully");
});
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Route for editing ChargerTargetV // Route for editing ChargerTargetV
server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) { server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->hasParam("value")) { if (!request->hasParam("value")) {
@ -226,6 +249,23 @@ void init_webserver() {
#endif #endif
} }
void WiFi_monitor_loop() {
unsigned long currentMillis = millis();
if (currentMillis - last_wifi_monitor_run > wifi_monitor_loop_time) {
last_wifi_monitor_run = currentMillis;
if (WiFi.status() != WL_CONNECTED && wifi_state != "Connecting") {
wifi_connected = false;
wifi_state = "Not connected";
Serial.print("Wifi disconnected. Attempting reconnection");
init_WiFi_STA(ssid, password);
} else if (WiFi.status() == WL_CONNECTED && wifi_state != "Connected") {
wifi_connected = true;
wifi_state = "Connected";
Serial.println("Wifi reconnected");
}
}
}
void init_WiFi_AP() { void init_WiFi_AP() {
Serial.print("Creating Access Point: "); Serial.print("Creating Access Point: ");
Serial.println(ssidAP); Serial.println(ssidAP);
@ -241,36 +281,45 @@ void init_WiFi_AP() {
} }
void init_WiFi_STA(const char* ssid, const char* password) { void init_WiFi_STA(const char* ssid, const char* password) {
// Connect to Wi-Fi network with SSID and password // If we're already connected, there's nothing to do
Serial.print("Connecting to "); if (WiFi.status() == WL_CONNECTED) {
Serial.println(ssid); if (wifi_state != "Connected") {
WiFi.begin(ssid, password); wifi_connected = true;
wifi_state = "Connected";
wifi_connect_start_time = millis(); // Print local IP address and start web server
wifi_connect_current_time = wifi_connect_start_time; Serial.println("");
while ((wifi_connect_current_time - wifi_connect_start_time) <= wifi_connect_timeout && Serial.print("Connected to WiFi network: ");
WiFi.status() != WL_CONNECTED) { // do this loop for up to 5000ms Serial.println(ssid);
// to break the loop when the connection is not established (wrong ssid or password). Serial.print("IP address: ");
delay(500); Serial.println(WiFi.localIP());
Serial.print("."); }
wifi_connect_current_time = millis(); return;
} }
if (WiFi.status() == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network
wifi_connected = true; // If we're not currently trying to connect, start the connection process
wifi_state = "Connected"; if (wifi_state != "Connecting") {
// Print local IP address and start web server Serial.print("Connecting to: ");
Serial.println("");
Serial.print("Connected to WiFi network: ");
Serial.println(ssid); Serial.println(ssid);
Serial.print("IP address: "); WiFi.begin(ssid, password);
Serial.println(WiFi.localIP()); wifi_state = "Connecting";
} else { wifi_connect_start_time = millis();
wifi_connected = false; return;
}
// If we've been trying to connect for more than 5000ms, give up
if (millis() - wifi_connect_start_time > wifi_connect_timeout) {
wifi_state = "Not connected"; wifi_state = "Not connected";
Serial.print("Not connected to WiFi network: "); Serial.print("Failed to connect to WiFi network: ");
Serial.println(ssid); Serial.println(ssid);
Serial.println("Please check WiFi network name and password, and if WiFi network is available."); Serial.println("Please check WiFi network name and password, and if WiFi network is available.");
Serial.print("Will try again in ");
Serial.print((wifi_monitor_loop_time - (millis() - last_wifi_monitor_run)) / 1000);
Serial.println(" seconds.");
return;
} }
// Otherwise, just print a dot to indicate that we're still trying to connect
Serial.print(".");
} }
void init_ElegantOTA() { void init_ElegantOTA() {
@ -391,10 +440,13 @@ String processor(const String& var) {
#endif #endif
content += "</h4>"; content += "</h4>";
#ifdef CHEVYVOLT_CHARGER #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
content += "<h4 style='color: white;'>Charger protocol: "; content += "<h4 style='color: white;'>Charger protocol: ";
#ifdef CHEVYVOLT_CHARGER #ifdef CHEVYVOLT_CHARGER
content += "Chevy Volt Gen1 Charger"; content += "Chevy Volt Gen1 Charger";
#endif
#ifdef NISSANLEAF_CHARGER
content += "Nissan LEAF 2013-2024 PDM charger";
#endif #endif
content += "</h4>"; content += "</h4>";
#endif #endif
@ -492,7 +544,13 @@ String processor(const String& var) {
content += "<span style='color: red;'>&#10005;</span></h4>"; content += "<span style='color: red;'>&#10005;</span></h4>";
} }
#ifdef CHEVYVOLT_CHARGER // Close the block
content += "</div>";
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
content += "<h4>Charger HV Enabled: "; content += "<h4>Charger HV Enabled: ";
if (charger_HV_enabled) { if (charger_HV_enabled) {
content += "<span>&#10003;</span>"; content += "<span>&#10003;</span>";
@ -508,6 +566,7 @@ String processor(const String& var) {
content += "<span style='color: red;'>&#10005;</span>"; content += "<span style='color: red;'>&#10005;</span>";
} }
content += "</h4>"; content += "</h4>";
#ifdef CHEVYVOLT_CHARGER
float chgPwrDC = static_cast<float>(charger_stat_HVcur * charger_stat_HVvol); float chgPwrDC = static_cast<float>(charger_stat_HVcur * charger_stat_HVvol);
float chgPwrAC = static_cast<float>(charger_stat_ACcur * charger_stat_ACvol); float chgPwrAC = static_cast<float>(charger_stat_ACcur * charger_stat_ACvol);
float chgEff = chgPwrDC / chgPwrAC * 100; float chgEff = chgPwrDC / chgPwrAC * 100;
@ -520,24 +579,40 @@ String processor(const String& var) {
content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1); content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1);
content += "<h4 style='color: white;'>Charger Efficiency: " + String(chgEff) + "%</h4>"; content += "<h4 style='color: white;'>Charger Efficiency: " + String(chgEff) + "%</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output V: " + String(HVvol, 2) + "</h4>"; content += "<h4 style='color: white;'>Charger HVDC Output V: " + String(HVvol, 2) + " V</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output I: " + String(HVcur, 2) + "</h4>"; content += "<h4 style='color: white;'>Charger HVDC Output I: " + String(HVcur, 2) + " A</h4>";
content += "<h4 style='color: white;'>Charger LVDC Output I: " + String(LVcur, 2) + "</h4>"; content += "<h4 style='color: white;'>Charger LVDC Output I: " + String(LVcur, 2) + "</h4>";
content += "<h4 style='color: white;'>Charger LVDC Output V: " + String(LVvol, 2) + "</h4>"; content += "<h4 style='color: white;'>Charger LVDC Output V: " + String(LVvol, 2) + "</h4>";
content += "<h4 style='color: white;'>Charger AC Input V: " + String(ACvol, 2) + "VAC</h4>"; content += "<h4 style='color: white;'>Charger AC Input V: " + String(ACvol, 2) + " VAC</h4>";
content += "<h4 style='color: white;'>Charger AC Input I: " + String(ACvol, 2) + "VAC</h4>"; content += "<h4 style='color: white;'>Charger AC Input I: " + String(ACcur, 2) + " A</h4>";
#endif #endif
#ifdef NISSANLEAF_CHARGER
float chgPwrDC = static_cast<float>(charger_stat_HVcur * 100);
charger_stat_HVcur = chgPwrDC / (battery_voltage / 10); // P/U=I
charger_stat_HVvol = static_cast<float>(battery_voltage / 10);
float ACvol = charger_stat_ACvol;
float HVvol = charger_stat_HVvol;
float HVcur = charger_stat_HVcur;
content += formatPowerValue("Charger Output Power", chgPwrDC, "", 1);
content += "<h4 style='color: white;'>Charger HVDC Output V: " + String(HVvol, 2) + " V</h4>";
content += "<h4 style='color: white;'>Charger HVDC Output I: " + String(HVcur, 2) + " A</h4>";
content += "<h4 style='color: white;'>Charger AC Input V: " + String(ACvol, 2) + " VAC</h4>";
#endif
// Close the block // Close the block
content += "</div>"; content += "</div>";
#endif
content += "<button onclick='goToUpdatePage()'>Perform OTA update</button>"; content += "<button onclick='goToUpdatePage()'>Perform OTA update</button>";
content += " "; content += " ";
content += "<button onclick='goToSettingsPage()'>Change Battery Settings</button>"; content += "<button onclick='goToSettingsPage()'>Change Settings</button>";
content += " ";
content += "<button onclick='goToCellmonitorPage()'>Cellmonitor</button>";
content += " "; content += " ";
content += "<button onclick='promptToReboot()'>Reboot Emulator</button>"; content += "<button onclick='promptToReboot()'>Reboot Emulator</button>";
content += "<script>"; content += "<script>";
content += "function goToUpdatePage() { window.location.href = '/update'; }"; content += "function goToUpdatePage() { window.location.href = '/update'; }";
content += "function goToCellmonitorPage() { window.location.href = '/cellmonitor'; }";
content += "function goToSettingsPage() { window.location.href = '/settings'; }"; content += "function goToSettingsPage() { window.location.href = '/settings'; }";
content += content +=
"function promptToReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If " "function promptToReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If "
@ -582,7 +657,25 @@ String settings_processor(const String& var) {
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>"; " A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max discharge speed: " + String(MAXDISCHARGEAMP / 10.0, 1) + content += "<h4 style='color: white;'>Max discharge speed: " + String(MAXDISCHARGEAMP / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>"; " A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
#ifdef CHEVYVOLT_CHARGER // Close the block
content += "</div>";
#ifdef TEST_FAKE_BATTERY
// Start a new block with blue background color
content += "<div style='background-color: #2E37AD; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
float voltageFloat = static_cast<float>(battery_voltage) / 10.0; // Convert to float and divide by 10
content += "<h4 style='color: white;'>Fake battery voltage: " + String(voltageFloat, 1) +
" V </span> <button onclick='editFakeBatteryVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
content += "<h4 style='color: white;'>Charger HVDC Enabled: "; content += "<h4 style='color: white;'>Charger HVDC Enabled: ";
if (charger_HV_enabled) { if (charger_HV_enabled) {
content += "<span>&#10003;</span>"; content += "<span>&#10003;</span>";
@ -603,6 +696,9 @@ String settings_processor(const String& var) {
" V </span> <button onclick='editChargerSetpointVDC()'>Edit</button></h4>"; " V </span> <button onclick='editChargerSetpointVDC()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Current Setpoint: " + String(charger_setpoint_HV_IDC, 1) + content += "<h4 style='color: white;'>Charger Current Setpoint: " + String(charger_setpoint_HV_IDC, 1) +
" A </span> <button onclick='editChargerSetpointIDC()'>Edit</button></h4>"; " A </span> <button onclick='editChargerSetpointIDC()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif #endif
content += "<script>"; content += "<script>";
@ -675,7 +771,22 @@ String settings_processor(const String& var) {
content += "}"; content += "}";
content += "}"; content += "}";
#ifdef CHEVYVOLT_CHARGER #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.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 += "}";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
content += "function editChargerHVDCEnabled() {"; content += "function editChargerHVDCEnabled() {";
content += " var value = prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 for disabled');"; content += " var value = prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 for disabled');";
content += " if (value !== null) {"; content += " if (value !== null) {";
@ -751,6 +862,57 @@ String settings_processor(const String& var) {
#endif #endif
content += "</script>"; content += "</script>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
content += "<script>";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
return String();
}
String cellmonitor_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content += ".container { display: flex; flex-wrap: wrap; justify-content: space-around; }";
content += ".cell { width: 48%; margin: 1%; padding: 10px; border: 1px solid white; text-align: center; }";
content += ".low-voltage { color: red; }"; // Style for low voltage text
content += ".voltage-values { margin-bottom: 10px; }"; // Style for voltage values section
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
// Display max, min, and deviation voltage values
content += "<div class='voltage-values'>";
content += "Max Voltage: " + String(cell_max_voltage) + " mV<br>";
content += "Min Voltage: " + String(cell_min_voltage) + " mV<br>";
int deviation = cell_max_voltage - cell_min_voltage;
content += "Voltage Deviation: " + String(deviation) + " mV";
content += "</div>";
// Visualize the populated cells in forward order using flexbox with conditional text color
content += "<div class='container'>";
for (int i = 0; i < 120; ++i) {
// Skip empty values
if (cellvoltages[i] == 0) {
continue;
}
String cellContent = "Cell " + String(i + 1) + "<br>" + String(cellvoltages[i]) + " mV";
// Check if the cell voltage is below 3000, apply red color
if (cellvoltages[i] < 3000) {
cellContent = "<span class='low-voltage'>" + cellContent + "</span>";
}
content += "<div class='cell'>" + cellContent + "</div>";
}
content += "</div>";
// Close the block // Close the block
content += "</div>"; content += "</div>";

View file

@ -27,11 +27,12 @@ extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5 extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2 extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10 extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
@ -49,6 +50,9 @@ extern float charger_stat_ACvol;
extern float charger_stat_LVcur; extern float charger_stat_LVcur;
extern float charger_stat_LVvol; extern float charger_stat_LVvol;
//LEAF charger
extern uint16_t OBC_Charge_Power;
/** /**
* @brief Initialization function for the webserver. * @brief Initialization function for the webserver.
* *
@ -77,6 +81,15 @@ void init_WiFi_AP();
*/ */
void init_WiFi_STA(const char* ssid, const char* password); void init_WiFi_STA(const char* ssid, const char* password);
/**
* @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down.
*
* @param[in] void
*
* @return void
*/
void WiFi_monitor_loop();
/** /**
* @brief Initialization function for ElegantOTA. * @brief Initialization function for ElegantOTA.
* *
@ -104,6 +117,15 @@ String processor(const String& var);
*/ */
String settings_processor(const String& var); String settings_processor(const String& var);
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String cellmonitor_processor(const String& var);
/** /**
* @brief Executes on OTA start * @brief Executes on OTA start
* *

View file

@ -9,6 +9,13 @@ static unsigned long previousMillis60s = 0; // will store last time a 60s CAN M
static const int interval2s = 2000; // interval (ms) at which send CAN Messages static const int interval2s = 2000; // interval (ms) at which send CAN Messages
static const int interval10s = 10000; // interval (ms) at which send CAN Messages static const int interval10s = 10000; // interval (ms) at which send CAN Messages
static const int interval60s = 60000; // interval (ms) at which send CAN Messages static const int interval60s = 60000; // interval (ms) at which send CAN Messages
static uint8_t char1_151 = 0;
static uint8_t char2_151 = 0;
static uint8_t char3_151 = 0;
static uint8_t char4_151 = 0;
static uint8_t char5_151 = 0;
static uint8_t char6_151 = 0;
static uint8_t char7_151 = 0;
//Startup messages //Startup messages
const CAN_frame_t BYD_250 = { const CAN_frame_t BYD_250 = {
@ -172,13 +179,34 @@ void update_values_can_byd() { //This function maps all the values fetched from
//Temperature min //Temperature min
BYD_210.data.u8[2] = (temperature_min >> 8); BYD_210.data.u8[2] = (temperature_min >> 8);
BYD_210.data.u8[3] = (temperature_min & 0x00FF); BYD_210.data.u8[3] = (temperature_min & 0x00FF);
#ifdef DEBUG_VIA_USB
if (char1_151 != 0) {
Serial.print("Detected inverter: ");
Serial.print((char)char1_151);
Serial.print((char)char2_151);
Serial.print((char)char3_151);
Serial.print((char)char4_151);
Serial.print((char)char5_151);
Serial.print((char)char6_151);
Serial.println((char)char7_151);
}
#endif
} }
void receive_can_byd(CAN_frame_t rx_frame) { void receive_can_byd(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) { switch (rx_frame.MsgID) {
case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier! case 0x151: //Message originating from BYD HVS compatible inverter. Reply with CAN identifier!
if (rx_frame.data.u8[0] & 0x01) { if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification
send_intial_data(); send_intial_data();
} else { // We can identify what inverter type we are connected to
char1_151 = rx_frame.data.u8[1];
char2_151 = rx_frame.data.u8[2];
char3_151 = rx_frame.data.u8[3];
char4_151 = rx_frame.data.u8[4];
char5_151 = rx_frame.data.u8[5];
char6_151 = rx_frame.data.u8[6];
char7_151 = rx_frame.data.u8[7];
} }
break; break;
case 0x091: case 0x091:
@ -217,14 +245,12 @@ void send_can_byd() {
ESP32Can.CANWriteFrame(&BYD_150); ESP32Can.CANWriteFrame(&BYD_150);
ESP32Can.CANWriteFrame(&BYD_1D0); ESP32Can.CANWriteFrame(&BYD_1D0);
ESP32Can.CANWriteFrame(&BYD_210); ESP32Can.CANWriteFrame(&BYD_210);
//Serial.println("CAN 10s done");
} }
//Send 60s message //Send 60s message
if (currentMillis - previousMillis60s >= interval60s) { if (currentMillis - previousMillis60s >= interval60s) {
previousMillis60s = currentMillis; previousMillis60s = currentMillis;
ESP32Can.CANWriteFrame(&BYD_190); ESP32Can.CANWriteFrame(&BYD_190);
//Serial.println("CAN 60s done");
} }
} }