mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge branch 'main' into improvement/compiler-warnings
This commit is contained in:
commit
6d5eeaf964
6 changed files with 313 additions and 131 deletions
|
@ -417,10 +417,10 @@ void init_contactors() {
|
||||||
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
|
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
|
||||||
digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW);
|
digitalWrite(NEGATIVE_CONTACTOR_PIN, LOW);
|
||||||
#ifdef PWM_CONTACTOR_CONTROL
|
#ifdef PWM_CONTACTOR_CONTROL
|
||||||
ledcSetup(POSITIVE_PWM_Ch, PWM_Freq, PWM_Res); // Setup PWM Channel Frequency and Resolution
|
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res,
|
||||||
ledcSetup(NEGATIVE_PWM_Ch, PWM_Freq, PWM_Res); // Setup PWM Channel Frequency and Resolution
|
POSITIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution
|
||||||
ledcAttachPin(POSITIVE_CONTACTOR_PIN, POSITIVE_PWM_Ch); // Attach Positive Contactor Pin to Hardware PWM Channel
|
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res,
|
||||||
ledcAttachPin(NEGATIVE_CONTACTOR_PIN, NEGATIVE_PWM_Ch); // Attach Positive Contactor Pin to Hardware PWM Channel
|
NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution
|
||||||
ledcWrite(POSITIVE_PWM_Ch, 0); // Set Positive PWM to 0%
|
ledcWrite(POSITIVE_PWM_Ch, 0); // Set Positive PWM to 0%
|
||||||
ledcWrite(NEGATIVE_PWM_Ch, 0); // Set Negative PWM to 0%
|
ledcWrite(NEGATIVE_PWM_Ch, 0); // Set Negative PWM to 0%
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -92,7 +92,6 @@ struct x102_Vehicle_Charging_Session { //Frame byte
|
||||||
} status;
|
} status;
|
||||||
} s;
|
} s;
|
||||||
|
|
||||||
//TODO discharge compatible is a status set here in bit 7, see beaglebone
|
|
||||||
uint8_t StateOfCharge = 0; //6 state of charge?
|
uint8_t StateOfCharge = 0; //6 state of charge?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ struct x108_EVSE_Capabilities { // Frame byte
|
||||||
struct x109_EVSE_Status { // Frame byte
|
struct x109_EVSE_Status { // Frame byte
|
||||||
uint8_t CHADEMO_protocol_number = 0x02; // 0
|
uint8_t CHADEMO_protocol_number = 0x02; // 0
|
||||||
uint16_t setpoint_HV_VDC =
|
uint16_t setpoint_HV_VDC =
|
||||||
0; // 1,2 NOTE: charger_stepoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
|
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
|
uint8_t setpoint_HV_IDC = 0; // 3
|
||||||
//
|
//
|
||||||
bool discharge_compatible = true; // 4, bit 0. bits
|
bool discharge_compatible = true; // 4, bit 0. bits
|
|
@ -4,15 +4,32 @@
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.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"
|
||||||
#include "CHADEMO-BATTERY-TYPES.h"
|
#ifdef ISA_SHUNT
|
||||||
|
#include "../lib/smaresca-SimpleISA/SimpleISA.h"
|
||||||
|
#endif
|
||||||
|
#include "CHADEMO-BATTERY-INTERNAL.h"
|
||||||
#include "CHADEMO-BATTERY.h"
|
#include "CHADEMO-BATTERY.h"
|
||||||
|
|
||||||
|
/* CHADEMO handling runs at 6.25 times the rate of most other code, so, rather than the
|
||||||
|
* default value of 12 (for 12 iterations of the 5s value update loop) * 5 for a 60s timeout,
|
||||||
|
* instead use 75 for 75*0.8s = 60s
|
||||||
|
*/
|
||||||
|
#undef CAN_STILL_ALIVE
|
||||||
|
#define CAN_STILL_ALIVE 75
|
||||||
|
//#define CH_CAN_DEBUG
|
||||||
|
|
||||||
|
static unsigned long setupMillis = 0;
|
||||||
|
static unsigned long handlerBeforeMillis = 0;
|
||||||
|
static unsigned long handlerAfterMillis = 0;
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* 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 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 unsigned long previousMillis5000 =
|
||||||
|
0; // will store last time a 5s threshold was reached for display during debug
|
||||||
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
|
||||||
|
|
||||||
bool plug_inserted = false;
|
bool plug_inserted = false;
|
||||||
|
bool vehicle_can_initialized = false;
|
||||||
bool vehicle_can_received = false;
|
bool vehicle_can_received = false;
|
||||||
bool vehicle_permission = false;
|
bool vehicle_permission = false;
|
||||||
bool evse_permission = false;
|
bool evse_permission = false;
|
||||||
|
@ -20,8 +37,14 @@ bool evse_permission = false;
|
||||||
bool precharge_low = false;
|
bool precharge_low = false;
|
||||||
bool positive_high = false;
|
bool positive_high = false;
|
||||||
bool contactors_ready = false;
|
bool contactors_ready = false;
|
||||||
uint8_t maximum_soc = 90;
|
|
||||||
uint8_t minimum_soc = 10;
|
uint8_t framecount = 0;
|
||||||
|
|
||||||
|
uint8_t max_discharge_current = 0; //TODO not sure on this one, but really influenced by inverter capability
|
||||||
|
|
||||||
|
#ifdef ISA_SHUNT
|
||||||
|
extern ISA sensor;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool high_current_control_enabled = false; // set to true when high current control is operating
|
bool high_current_control_enabled = false; // set to true when high current control is operating
|
||||||
// if true, values from 110.1 and 110.2 should be used instead of 102.3
|
// if true, values from 110.1 and 110.2 should be used instead of 102.3
|
||||||
|
@ -111,7 +134,7 @@ void update_values_battery() {
|
||||||
datalayer.battery.status.max_discharge_power_W =
|
datalayer.battery.status.max_discharge_power_W =
|
||||||
(x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P
|
(x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P
|
||||||
|
|
||||||
datalayer.battery.status.voltage_dV = x102_chg_session.TargetBatteryVoltage; //TODO: scaling?
|
datalayer.battery.status.voltage_dV = sensor.Voltage * 10;
|
||||||
|
|
||||||
datalayer.battery.info.total_capacity_Wh =
|
datalayer.battery.info.total_capacity_Wh =
|
||||||
((x101_chg_est.RatedBatteryCapacity / 0.11) *
|
((x101_chg_est.RatedBatteryCapacity / 0.11) *
|
||||||
|
@ -125,50 +148,23 @@ void update_values_battery() {
|
||||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
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);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
/* Check if the Vehicle 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);
|
|
||||||
CHADEMO_Status = CHADEMO_STOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To simulate or NOT to simulate battery cell voltages, that is .. A question.
|
/* To simulate or NOT to simulate battery cell voltages, that is .. A question.
|
||||||
* Answer for now: Not, because they are not available in any direct manner.
|
* Answer for now: Not, because they are not available in any direct manner.
|
||||||
* This will impact Solax inverter support, which uses cell min/max mV to populate
|
* This will impact Solax inverter support, which uses cell min/max mV to populate
|
||||||
* CAN frames.
|
* CAN frames.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef DEBUG_VIA_USB
|
if (vehicle_can_received) {
|
||||||
Serial.print("SOC 0x100: ");
|
uint8_t chargingrate = 0;
|
||||||
Serial.println(x100_chg_lim.ConstantOfChargingRateIndication);
|
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
|
||||||
#endif
|
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO see Table A.26—Charge control termination command pattern on pg58
|
//TODO simplified start/stop helper functions
|
||||||
|
//see IEEE Table A.26—Charge control termination command pattern on pg58
|
||||||
//for stop conditions
|
//for stop conditions
|
||||||
void evse_stop() {
|
|
||||||
/*
|
|
||||||
/// Sets 109.5.5 high
|
|
||||||
x109_evse_state.status_charger_stop_control = true;
|
|
||||||
x109_evse_state.output_voltage = 0.0;
|
|
||||||
x109_evse_state.output_current = 0;
|
|
||||||
x109_evse_state.remaining_charging_time_10s_bit = 0;
|
|
||||||
x109_evse_state.remaining_charging_time_1min_bit = 0;
|
|
||||||
x109_evse_state.status.fault_battery_incompatibility = false;
|
|
||||||
x109_evse_state.status.fault_charging_system_malfunction = false;
|
|
||||||
x109_evse_state.status.fault_station_malfunction = false;
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
void evse_charge_start() {
|
|
||||||
/*
|
|
||||||
x109_evse_state.status_charger_stop_control = false;
|
|
||||||
x109_evse_state.status_station = true;
|
|
||||||
x109_evse_state.remaining_charging_time_10s_bit = 255;
|
|
||||||
x109_evse_state.remaining_charging_time_1min_bit = 60;
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void process_vehicle_charging_minimums(CAN_frame_t rx_frame) {
|
inline void process_vehicle_charging_minimums(CAN_frame_t rx_frame) {
|
||||||
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
|
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
|
||||||
|
@ -185,11 +181,14 @@ inline void process_vehicle_charging_maximums(CAN_frame_t rx_frame) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
|
inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
|
||||||
|
|
||||||
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||||
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
|
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
|
||||||
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
|
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
|
||||||
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
|
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
|
||||||
|
|
||||||
|
vehicle_can_initialized = true;
|
||||||
|
|
||||||
vehicle_permission = digitalRead(CHADEMO_PIN_4);
|
vehicle_permission = digitalRead(CHADEMO_PIN_4);
|
||||||
|
|
||||||
x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0];
|
x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0];
|
||||||
|
@ -208,9 +207,23 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
|
||||||
|
|
||||||
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
|
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
|
||||||
|
|
||||||
|
//NOTE: behavior differs in the case of high current control (x110 support TBD)
|
||||||
|
// In that mode, ChargingCurrentRequest is set to 0xFF when >= 1 A is specified in
|
||||||
|
// in “request charging current (for extended) (x110.1, x110.2),” and then afterward in x102,
|
||||||
|
// it will not be updated
|
||||||
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
|
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
|
||||||
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
|
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
//Note on p131
|
||||||
|
uint8_t chargingrate = 0;
|
||||||
|
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
|
||||||
|
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
|
||||||
|
Serial.print("Charge Rate (kW):");
|
||||||
|
Serial.println(chargingrate);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//Table A.26—Charge control termination command patterns -- should echo x108 handling
|
//Table A.26—Charge control termination command patterns -- should echo x108 handling
|
||||||
|
|
||||||
/* charge/discharge permission signal from vehicle on pin 4 should NOT be sensed before first CAN received from vehicle.
|
/* charge/discharge permission signal from vehicle on pin 4 should NOT be sensed before first CAN received from vehicle.
|
||||||
|
@ -228,14 +241,6 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vehicle_permission || !x102_chg_session.s.status.StatusVehicleChargingEnabled) {
|
|
||||||
#ifdef DEBUG_VIA_USB
|
|
||||||
Serial.println("Vehicle indicates dis/charging should cease");
|
|
||||||
#endif
|
|
||||||
CHADEMO_Status = CHADEMO_STOP;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x102_chg_session.f.fault.FaultBatteryOverVoltage) {
|
if (x102_chg_session.f.fault.FaultBatteryOverVoltage) {
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
Serial.println("Vehicle indicates fault, battery over voltage.");
|
Serial.println("Vehicle indicates fault, battery over voltage.");
|
||||||
|
@ -274,16 +279,10 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x102_chg_session.StateOfCharge < minimum_soc) {
|
|
||||||
CHADEMO_Status = CHADEMO_STOP;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//FIXME condition nesting or more stanzas needed here for clear determination of cessation reason
|
//FIXME condition nesting or more stanzas needed here for clear determination of cessation reason
|
||||||
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE &&
|
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) {
|
||||||
(x102_chg_session.StateOfCharge >= maximum_soc || !vehicle_permission)) {
|
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
Serial.println("State of charge ceiling reached, stop charging");
|
Serial.println("State of charge ceiling reached or charging interrupted, stop charging");
|
||||||
#endif
|
#endif
|
||||||
CHADEMO_Status = CHADEMO_STOP;
|
CHADEMO_Status = CHADEMO_STOP;
|
||||||
return;
|
return;
|
||||||
|
@ -324,19 +323,52 @@ inline void process_vehicle_charging_session(CAN_frame_t rx_frame) {
|
||||||
|
|
||||||
/* x200 Vehicle, peer to x208 EVSE */
|
/* x200 Vehicle, peer to x208 EVSE */
|
||||||
inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
|
inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
|
||||||
|
|
||||||
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
|
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
|
||||||
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||||
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
|
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
|
||||||
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
|
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
/* unsigned long currentMillis = millis();
|
||||||
|
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||||
|
previousMillis5000 = currentMillis;
|
||||||
|
Serial.println("x200 Max remaining capacity for charging/discharging:");
|
||||||
|
// initially this is set to 0, which is represented as 0xFF
|
||||||
|
Serial.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ISA_SHUNT
|
||||||
|
if (sensor.Voltage <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
|
||||||
|
#endif
|
||||||
|
CHADEMO_Status = CHADEMO_STOP;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Vehicle 0x201, peer to EVSE 0x209
|
/* Vehicle 0x201, peer to EVSE 0x209
|
||||||
* HOWEVER, 201 isn't even emitted in any of the v2x canlogs available
|
* HOWEVER, 201 isn't even emitted in any of the v2x canlogs available
|
||||||
*/
|
*/
|
||||||
inline void process_vehicle_discharge_estimate(CAN_frame_t rx_frame) {
|
inline void process_vehicle_discharge_estimate(CAN_frame_t rx_frame) {
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
|
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
|
||||||
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||||
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||||
|
previousMillis5000 = currentMillis;
|
||||||
|
Serial.println("x201 availabile vehicle energy, completion time");
|
||||||
|
Serial.println(x201_discharge_estimate.AvailableVehicleEnergy);
|
||||||
|
Serial.println("x201 approx vehicle completion time");
|
||||||
|
Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void process_vehicle_dynamic_control(CAN_frame_t rx_frame) {
|
inline void process_vehicle_dynamic_control(CAN_frame_t rx_frame) {
|
||||||
|
@ -354,12 +386,7 @@ inline void process_vehicle_vendor_ID(CAN_frame_t rx_frame) {
|
||||||
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
|
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
|
||||||
}
|
}
|
||||||
|
|
||||||
//#define CH_CAN_DEBUG
|
|
||||||
void receive_can_battery(CAN_frame_t rx_frame) {
|
void receive_can_battery(CAN_frame_t rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive =
|
|
||||||
CAN_STILL_ALIVE; //We are getting CAN messages from the vehicle, inform the watchdog
|
|
||||||
CANstillAlive = 12; //We are getting CAN messages from the vehicle, inform the watchdog
|
|
||||||
|
|
||||||
#ifdef CH_CAN_DEBUG
|
#ifdef CH_CAN_DEBUG
|
||||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||||
Serial.print(" ");
|
Serial.print(" ");
|
||||||
|
@ -374,17 +401,20 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
||||||
Serial.println("");
|
Serial.println("");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* e.g., CHADEMO_INIT state is a transient, used to indicate when CAN
|
// CHADEMO coexists with a CAN-based shunt. Only process CHADEMO-specific IDs
|
||||||
* has not yet been receied from a vehicle
|
// 202 is unknown
|
||||||
*/
|
if (!((rx_frame.MsgID >= 0x100 && rx_frame.MsgID <= 0x202) || rx_frame.MsgID == 0x700)) {
|
||||||
if (CHADEMO_Status == CHADEMO_INIT) {
|
return;
|
||||||
// First CAN messages received, entering into negotiation
|
|
||||||
CHADEMO_Status = CHADEMO_NEGOTIATE;
|
|
||||||
// TODO consider tracking delta since transition time for expiry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for testing vehicle sanity
|
// used for testing vehicle sanity
|
||||||
vehicle_can_received = true;
|
vehicle_can_received = true;
|
||||||
|
/* CHADEMO_INIT state is a transient, used to indicate when CAN
|
||||||
|
* has not yet been receied from a vehicle
|
||||||
|
*/
|
||||||
|
|
||||||
|
datalayer.battery.status.CAN_battery_still_alive =
|
||||||
|
CAN_STILL_ALIVE; //We are getting CAN messages from the vehicle, inform the watchdog
|
||||||
|
|
||||||
switch (rx_frame.MsgID) {
|
switch (rx_frame.MsgID) {
|
||||||
case 0x100:
|
case 0x100:
|
||||||
|
@ -394,7 +424,13 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
||||||
process_vehicle_charging_maximums(rx_frame);
|
process_vehicle_charging_maximums(rx_frame);
|
||||||
break;
|
break;
|
||||||
case 0x102:
|
case 0x102:
|
||||||
|
framecount++;
|
||||||
|
//the first few frames start as 0x03, then like 20 of 0x01
|
||||||
|
if (vehicle_can_initialized && framecount < 20) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
process_vehicle_charging_session(rx_frame);
|
process_vehicle_charging_session(rx_frame);
|
||||||
|
/* counter to help discard inital frames with bad SOC data */
|
||||||
break;
|
break;
|
||||||
case 0x200: //For V2X
|
case 0x200: //For V2X
|
||||||
process_vehicle_charging_limits(rx_frame);
|
process_vehicle_charging_limits(rx_frame);
|
||||||
|
@ -408,10 +444,17 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
||||||
case 0x700:
|
case 0x700:
|
||||||
process_vehicle_vendor_ID(rx_frame);
|
process_vehicle_vendor_ID(rx_frame);
|
||||||
break;
|
break;
|
||||||
|
case 0x202: // unknown. LEAF specific?
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CHADEMO_Status == CHADEMO_INIT) {
|
||||||
|
// First CAN messages received, entering into negotiation
|
||||||
|
// TODO consider tracking delta since transition time for expiry
|
||||||
|
CHADEMO_Status = CHADEMO_NEGOTIATE;
|
||||||
|
}
|
||||||
|
|
||||||
handle_chademo_sequence();
|
handle_chademo_sequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,7 +493,8 @@ void update_evse_capabilities(CAN_frame_t& f) {
|
||||||
*/
|
*/
|
||||||
x108_evse_cap.contactor_weld_detection = 0x1;
|
x108_evse_cap.contactor_weld_detection = 0x1;
|
||||||
|
|
||||||
x108_evse_cap.available_output_voltage = x102_chg_session.TargetBatteryVoltage;
|
/* should this be set to MAX_EVSE_OUTPUT_VOLTAGE or x102_chg_session.TargetBatteryVoltage ? */
|
||||||
|
x108_evse_cap.available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE;
|
||||||
|
|
||||||
/* calculate max threshold to protect battery - using vehicle-provided max minus 2% */
|
/* calculate max threshold to protect battery - using vehicle-provided max minus 2% */
|
||||||
x108_evse_cap.threshold_voltage =
|
x108_evse_cap.threshold_voltage =
|
||||||
|
@ -500,19 +544,26 @@ void update_evse_status(CAN_frame_t& f) {
|
||||||
x109_evse_state.remaining_time_1m = 60;
|
x109_evse_state.remaining_time_1m = 60;
|
||||||
|
|
||||||
} else if (EVSE_mode == CHADEMO_CHARGE) {
|
} else if (EVSE_mode == CHADEMO_CHARGE) {
|
||||||
//FIXME these are supposed to be measured values, e.g., from a shunt
|
#ifdef ISA_SENSOR
|
||||||
//for now we are literally saying they're equivalent to the request or max charger capability
|
x109_evse_state.setpoint_HV_VDC = sensor.Voltage;
|
||||||
//this is wrong
|
x109_evse_state.setpoint_HV_IDC = sensor.Amperes;
|
||||||
|
#else
|
||||||
|
//NOTE: these are supposed to be measured values, e.g., from a shunt
|
||||||
|
//If a sensor is not used, we are literally asserting that the measured value is exactly equivalent to the request or max charger capability
|
||||||
|
//this is pretty likely to fail on most vehicles
|
||||||
x109_evse_state.setpoint_HV_VDC =
|
x109_evse_state.setpoint_HV_VDC =
|
||||||
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
|
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
|
||||||
x109_evse_state.setpoint_HV_IDC =
|
x109_evse_state.setpoint_HV_IDC =
|
||||||
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
|
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* The spec suggests throwing a 109.5.4 = 1 if vehicle curr request 102.3 > evse curr available 108.3,
|
/* The spec suggests throwing a 109.5.4 = 1 if vehicle curr request 102.3 > evse curr available 108.3,
|
||||||
* but realistically many chargers seem to act tolerant here and stay under limits and supply whatever they are able
|
* but realistically many chargers seem to act tolerant here and stay under limits and supply whatever they are able
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* if power overcommitted, back down to just below while maintaining voltage target */
|
/* if power overcommitted, back down to just below while maintaining voltage target */
|
||||||
if (x109_evse_state.setpoint_HV_IDC * x109_evse_state.setpoint_HV_VDC > MAX_EVSE_POWER_CHARGING) {
|
if (x109_evse_state.setpoint_HV_VDC > 0 &&
|
||||||
|
x109_evse_state.setpoint_HV_IDC * x109_evse_state.setpoint_HV_VDC > MAX_EVSE_POWER_CHARGING) {
|
||||||
x109_evse_state.setpoint_HV_IDC = floor(MAX_EVSE_POWER_CHARGING / x109_evse_state.setpoint_HV_VDC);
|
x109_evse_state.setpoint_HV_IDC = floor(MAX_EVSE_POWER_CHARGING / x109_evse_state.setpoint_HV_VDC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,8 +577,7 @@ void update_evse_status(CAN_frame_t& f) {
|
||||||
*/
|
*/
|
||||||
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
|
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
|
||||||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
|
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
|
||||||
|
//Toggl battery incompatibility flag 109.5.3
|
||||||
///Battery incompatibility” flag #109.5.3 to 1
|
|
||||||
x109_evse_state.s.status.EVSE_error = 1;
|
x109_evse_state.s.status.EVSE_error = 1;
|
||||||
x109_evse_state.s.status.battery_incompatible = 1;
|
x109_evse_state.s.status.battery_incompatible = 1;
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||||
|
@ -576,15 +626,35 @@ void update_evse_discharge_estimate(CAN_frame_t& f) {
|
||||||
|
|
||||||
/* x208 EVSE, peer to 0x200 Vehicle */
|
/* x208 EVSE, peer to 0x200 Vehicle */
|
||||||
void update_evse_discharge_capabilities(CAN_frame_t& f) {
|
void update_evse_discharge_capabilities(CAN_frame_t& f) {
|
||||||
//FIXME these are supposed to be measured values, e.g., from a shunt
|
#ifdef ISA_SHUNT
|
||||||
//we are literally saying theyre arbitrary for now
|
//present discharge current is a measured value
|
||||||
//this is wrong
|
x208_evse_dischg_cap.present_discharge_current = 0xFF - sensor.Amperes;
|
||||||
|
#else
|
||||||
|
//Present discharge current is a measured value. In the absence of
|
||||||
|
// a shunt, the evse here is quite literally lying to the vehicle. The spec
|
||||||
|
// seems to suggest this is tolerated unless the current measured on the EV
|
||||||
|
// side continualy exceeds the maximum discharge current by 10amps
|
||||||
x208_evse_dischg_cap.present_discharge_current = 0xFF - 6;
|
x208_evse_dischg_cap.present_discharge_current = 0xFF - 6;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//EVSE maximum current input is partly an inverter-influenced value i.e., min(inverter, vehicle_max_discharge)
|
||||||
|
//use max_discharge_current variable if nonzero, otherwise tell the vehicle the EVSE will take everything it can give
|
||||||
|
if (max_discharge_current) {
|
||||||
|
x208_evse_dischg_cap.available_input_current = 0xFF - max_discharge_current;
|
||||||
|
} else {
|
||||||
x208_evse_dischg_cap.available_input_current = 0xFF - x200_discharge_limits.MaximumDischargeCurrent;
|
x208_evse_dischg_cap.available_input_current = 0xFF - x200_discharge_limits.MaximumDischargeCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
x208_evse_dischg_cap.available_input_voltage = x200_discharge_limits.MinimumDischargeVoltage;
|
x208_evse_dischg_cap.available_input_voltage = x200_discharge_limits.MinimumDischargeVoltage;
|
||||||
|
|
||||||
/* calculate min threshold to protect battery - using vehicle-provided minimum plus 2% */
|
/* calculate min threshold to protect battery - using vehicle-provided minimum plus 2%
|
||||||
|
*
|
||||||
|
*As this is partly an inverter-influenced value, should this be a configurable variable backed by a defined default?
|
||||||
|
* It seems sensible to be MAX(lowest usable voltage of the inverter input, lowest tolerable voltage of the vehicle battery)
|
||||||
|
* NOT the vehicle minimumDischargeVoltage.
|
||||||
|
* Thus, why here we are adding a few percent of cushion atop the minimum
|
||||||
|
* This is the reverse treatment of the lower_threshold_voltage of charging mode
|
||||||
|
*/
|
||||||
x208_evse_dischg_cap.lower_threshold_voltage =
|
x208_evse_dischg_cap.lower_threshold_voltage =
|
||||||
x200_discharge_limits.MinimumDischargeVoltage + (int)(x200_discharge_limits.MinimumDischargeVoltage / 100 * 2);
|
x200_discharge_limits.MinimumDischargeVoltage + (int)(x200_discharge_limits.MinimumDischargeVoltage / 100 * 2);
|
||||||
|
|
||||||
|
@ -610,14 +680,9 @@ void send_can_battery() {
|
||||||
|
|
||||||
unsigned long currentMillis = millis();
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
|
handlerBeforeMillis = currentMillis;
|
||||||
handle_chademo_sequence();
|
handle_chademo_sequence();
|
||||||
|
handlerAfterMillis = millis();
|
||||||
/* no EVSE messages should be sent until the vehicle has
|
|
||||||
* initiated
|
|
||||||
*/
|
|
||||||
if (CHADEMO_Status <= CHADEMO_INIT || !vehicle_can_received) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 100ms CAN Message
|
// Send 100ms CAN Message
|
||||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||||
|
@ -627,6 +692,14 @@ void send_can_battery() {
|
||||||
}
|
}
|
||||||
previousMillis100 = currentMillis;
|
previousMillis100 = currentMillis;
|
||||||
|
|
||||||
|
/* no EVSE messages should be sent until the vehicle has
|
||||||
|
* initiated
|
||||||
|
*/
|
||||||
|
// if (CHADEMO_Status <= CHADEMO_INIT || !vehicle_can_received) {
|
||||||
|
if (CHADEMO_Status <= CHADEMO_INIT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
update_evse_capabilities(CHADEMO_108);
|
update_evse_capabilities(CHADEMO_108);
|
||||||
update_evse_status(CHADEMO_109);
|
update_evse_status(CHADEMO_109);
|
||||||
update_evse_discharge_capabilities(CHADEMO_208);
|
update_evse_discharge_capabilities(CHADEMO_208);
|
||||||
|
@ -689,6 +762,7 @@ void send_can_battery() {
|
||||||
*/
|
*/
|
||||||
void handle_chademo_sequence() {
|
void handle_chademo_sequence() {
|
||||||
|
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
|
precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
|
||||||
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
|
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
|
||||||
contactors_ready = precharge_low && positive_high;
|
contactors_ready = precharge_low && positive_high;
|
||||||
|
@ -714,7 +788,7 @@ void handle_chademo_sequence() {
|
||||||
/* ------------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------------ */
|
||||||
switch (CHADEMO_Status) {
|
switch (CHADEMO_Status) {
|
||||||
case CHADEMO_IDLE:
|
case CHADEMO_IDLE:
|
||||||
/* this is where we can unlock connector? */
|
/* this is where we can unlock connector */
|
||||||
digitalWrite(CHADEMO_LOCK, LOW);
|
digitalWrite(CHADEMO_LOCK, LOW);
|
||||||
plug_inserted = digitalRead(CHADEMO_PIN_7);
|
plug_inserted = digitalRead(CHADEMO_PIN_7);
|
||||||
|
|
||||||
|
@ -735,6 +809,10 @@ void handle_chademo_sequence() {
|
||||||
break;
|
break;
|
||||||
case CHADEMO_CONNECTED:
|
case CHADEMO_CONNECTED:
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
//Serial.println("CHADEMO_CONNECTED State");
|
||||||
|
#endif
|
||||||
/* plug_inserted is .. essentially a volatile of sorts, so verify */
|
/* plug_inserted is .. essentially a volatile of sorts, so verify */
|
||||||
if (plug_inserted) {
|
if (plug_inserted) {
|
||||||
/* If connection is detectable, jumpstart handshake by
|
/* If connection is detectable, jumpstart handshake by
|
||||||
|
@ -763,7 +841,7 @@ void handle_chademo_sequence() {
|
||||||
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
|
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
|
||||||
*/
|
*/
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
|
// Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
|
||||||
#endif
|
#endif
|
||||||
evse_init();
|
evse_init();
|
||||||
break;
|
break;
|
||||||
|
@ -771,17 +849,19 @@ void handle_chademo_sequence() {
|
||||||
/* Vehicle and EVSE dance */
|
/* Vehicle and EVSE dance */
|
||||||
//TODO if pin 4 / j goes high,
|
//TODO if pin 4 / j goes high,
|
||||||
|
|
||||||
Serial.print("State of charge: ");
|
#ifdef DEBUG_VIA_USB
|
||||||
Serial.println(x102_chg_session.StateOfCharge);
|
// Commented unless needed for debug
|
||||||
Serial.print("Parked?: ");
|
// Serial.println("CHADEMO_NEGOTIATE State");
|
||||||
Serial.println(x102_chg_session.s.status.StatusVehicleShifterPosition);
|
#endif
|
||||||
Serial.print("Target Battery Voltage: ");
|
|
||||||
Serial.println(x102_chg_session.TargetBatteryVoltage);
|
|
||||||
|
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||||
break;
|
break;
|
||||||
case CHADEMO_EV_ALLOWED:
|
case CHADEMO_EV_ALLOWED:
|
||||||
// pin 4 (j) reads high
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_EV_ALLOWED State");
|
||||||
|
#endif
|
||||||
|
// If we are in this state, vehicle_permission was already set to true...but re-verify
|
||||||
|
// that pin 4 (j) reads high
|
||||||
if (vehicle_permission) {
|
if (vehicle_permission) {
|
||||||
//lock connector here
|
//lock connector here
|
||||||
digitalWrite(CHADEMO_LOCK, HIGH);
|
digitalWrite(CHADEMO_LOCK, HIGH);
|
||||||
|
@ -789,38 +869,79 @@ void handle_chademo_sequence() {
|
||||||
//TODO spec requires test to validate solenoid has indeed engaged.
|
//TODO spec requires test to validate solenoid has indeed engaged.
|
||||||
// example uses a comparator/current consumption check around solenoid
|
// example uses a comparator/current consumption check around solenoid
|
||||||
x109_evse_state.s.status.connector_locked = true;
|
x109_evse_state.s.status.connector_locked = true;
|
||||||
}
|
|
||||||
CHADEMO_Status = CHADEMO_EVSE_PREPARE;
|
|
||||||
|
|
||||||
|
CHADEMO_Status = CHADEMO_EVSE_PREPARE;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CHADEMO_EVSE_PREPARE:
|
case CHADEMO_EVSE_PREPARE:
|
||||||
/* TODO voltage check of output < 10v */
|
#ifdef DEBUG_VIA_USB
|
||||||
/* insulation test hypothetically happens here before triggering PIN 10 high */
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_EVSE_PREPARE State");
|
||||||
|
#endif
|
||||||
|
/* TODO voltage check of output < 20v
|
||||||
|
* insulation test hypothetically happens here before triggering PIN 10 high
|
||||||
|
* see Table A.28—Requirements for the insulation test for output DC circuit
|
||||||
|
Note: required that if 102.5.0 == 0, do not perform evse insulation test
|
||||||
|
we should not be here in this state unless 102.5.0 was == 1 previously, but check again in case it has changed
|
||||||
|
|
||||||
|
simulate via?
|
||||||
|
if evse_present _voltage + 10 <= vehicle voltage_target {
|
||||||
|
evse_present_voltage += 10;
|
||||||
|
} else {
|
||||||
|
evse_present_voltage = vehicle voltage_target;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (x102_chg_session.s.status.StatusVehicleChargingEnabled) {
|
||||||
|
if (sensor.Voltage < 20) {
|
||||||
|
|
||||||
digitalWrite(CHADEMO_PIN_10, HIGH);
|
digitalWrite(CHADEMO_PIN_10, HIGH);
|
||||||
evse_permission = true;
|
evse_permission = true;
|
||||||
|
} else {
|
||||||
|
Serial.println("Insulation check measures > 20v ");
|
||||||
|
}
|
||||||
|
|
||||||
// likely unnecessary but just to be sure. consider removal
|
// likely unnecessary but just to be sure. consider removal
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||||
x109_evse_state.s.status.EVSE_status = 0;
|
x109_evse_state.s.status.EVSE_status = 0;
|
||||||
//state changes only upon receipt of charging session request
|
} else {
|
||||||
|
CHADEMO_Status = CHADEMO_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
//state changes to CHADEMO_EVSE_START only upon receipt of charging session request
|
||||||
break;
|
break;
|
||||||
case CHADEMO_EVSE_START:
|
case CHADEMO_EVSE_START:
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_EVSE_START State");
|
||||||
|
#endif
|
||||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||||
x109_evse_state.s.status.EVSE_status = 0;
|
x109_evse_state.s.status.EVSE_status = 0;
|
||||||
|
|
||||||
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
|
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("Initiating contactors");
|
||||||
|
#endif
|
||||||
|
|
||||||
/* break rather than fall through because contactors are not instantaneous;
|
/* break rather than fall through because contactors are not instantaneous;
|
||||||
* worth giving it a cycle to finish
|
* worth giving it a cycle to finish
|
||||||
*/
|
*/
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CHADEMO_EVSE_CONTACTORS_ENABLED:
|
case CHADEMO_EVSE_CONTACTORS_ENABLED:
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_EVSE_CONTACTORS State");
|
||||||
|
#endif
|
||||||
|
|
||||||
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
|
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
|
||||||
if (contactors_ready) {
|
if (contactors_ready) {
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("Contactors ready");
|
||||||
|
Serial.print("Voltage: ");
|
||||||
|
Serial.println(sensor.Voltage);
|
||||||
|
#endif
|
||||||
/* transition to POWERFLOW state if discharge compatible on both sides */
|
/* transition to POWERFLOW state if discharge compatible on both sides */
|
||||||
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
|
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
|
||||||
(EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL)) {
|
(EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL)) {
|
||||||
|
@ -839,6 +960,10 @@ void handle_chademo_sequence() {
|
||||||
/* break or fall through ? TODO */
|
/* break or fall through ? TODO */
|
||||||
break;
|
break;
|
||||||
case CHADEMO_POWERFLOW:
|
case CHADEMO_POWERFLOW:
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_POWERFLOW State");
|
||||||
|
#endif
|
||||||
/* POWERFLOW for charging, discharging, and bidirectional */
|
/* POWERFLOW for charging, discharging, and bidirectional */
|
||||||
/* Interpretation */
|
/* Interpretation */
|
||||||
if (x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
if (x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||||
|
@ -853,26 +978,51 @@ void handle_chademo_sequence() {
|
||||||
//TODO flag error and do not calculate power in EVSE response?
|
//TODO flag error and do not calculate power in EVSE response?
|
||||||
// probably unnecessary as other flags will be set causing this to be caught
|
// probably unnecessary as other flags will be set causing this to be caught
|
||||||
}
|
}
|
||||||
|
#ifdef ISA_SHUNT
|
||||||
|
if (sensor.Voltage <= x200_discharge_limits.MinimumDischargeVoltage) {
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
|
||||||
|
#endif
|
||||||
|
CHADEMO_Status = CHADEMO_STOP;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// Potentially unnecessary (set in CHADEMO_EVSE_CONTACTORS_ENABLED stanza), but just in case
|
// Potentially unnecessary (set in CHADEMO_EVSE_CONTACTORS_ENABLED stanza), but just in case
|
||||||
x109_evse_state.s.status.EVSE_status = 1;
|
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 0;
|
x109_evse_state.s.status.ChgDischStopControl = 0;
|
||||||
vehicle_permission = digitalRead(CHADEMO_PIN_4);
|
x109_evse_state.s.status.EVSE_status = 1;
|
||||||
break;
|
break;
|
||||||
case CHADEMO_STOP:
|
case CHADEMO_STOP:
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_STOP State");
|
||||||
|
#endif
|
||||||
/* back to CHADEMO_IDLE after teardown */
|
/* back to CHADEMO_IDLE after teardown */
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||||
x109_evse_state.s.status.EVSE_status = 0;
|
x109_evse_state.s.status.EVSE_status = 0;
|
||||||
x109_evse_state.s.status.battery_incompatible = 0;
|
x109_evse_state.s.status.battery_incompatible = 0;
|
||||||
digitalWrite(CHADEMO_PIN_10, LOW);
|
|
||||||
digitalWrite(CHADEMO_PIN_2, LOW);
|
|
||||||
evse_permission = false;
|
evse_permission = false;
|
||||||
vehicle_permission = false;
|
vehicle_permission = false;
|
||||||
x209_sent = false;
|
x209_sent = false;
|
||||||
x201_received = false;
|
x201_received = false;
|
||||||
|
|
||||||
|
/* protection of EV contactors - IEEE A.7.2.9 Protection of EV contactor
|
||||||
|
* see also Table A.29—Charging stage and check item
|
||||||
|
*
|
||||||
|
* We will re-enter the handler until the amperage drops sufficiently
|
||||||
|
* and then transition to CHADEMO_IDLE
|
||||||
|
*/
|
||||||
|
if (sensor.Amperes <= 5 && sensor.Voltage <= 10) {
|
||||||
|
/* welding detection ideally here */
|
||||||
|
digitalWrite(CHADEMO_PIN_10, LOW);
|
||||||
|
digitalWrite(CHADEMO_PIN_2, LOW);
|
||||||
CHADEMO_Status = CHADEMO_IDLE;
|
CHADEMO_Status = CHADEMO_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CHADEMO_FAULT:
|
case CHADEMO_FAULT:
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
// Commented unless needed for debug
|
||||||
|
Serial.println("CHADEMO_FAULT State");
|
||||||
|
#endif
|
||||||
/* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */
|
/* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */
|
||||||
x109_evse_state.s.status.EVSE_error = 1;
|
x109_evse_state.s.status.EVSE_error = 1;
|
||||||
x109_evse_state.s.status.ChgDischError = 1;
|
x109_evse_state.s.status.ChgDischError = 1;
|
||||||
|
@ -909,6 +1059,19 @@ void setup_battery(void) { // Performs one time setup at startup
|
||||||
/* disallow contactors until permissions is granted by vehicle */
|
/* disallow contactors until permissions is granted by vehicle */
|
||||||
datalayer.system.status.battery_allows_contactor_closing = false;
|
datalayer.system.status.battery_allows_contactor_closing = false;
|
||||||
|
|
||||||
|
/* Pretend that we know the SOH, assert that it is 99% */
|
||||||
|
datalayer.battery.status.soh_pptt = 9900;
|
||||||
|
|
||||||
|
/* Briefly assert that we're starting at a modest SOC of 30% */
|
||||||
|
datalayer.battery.status.real_soc = 300;
|
||||||
|
|
||||||
|
//TODO Must be user configured, most likely. Artificially capped for the time being
|
||||||
|
datalayer.battery.status.max_discharge_power_W = 1000;
|
||||||
|
datalayer.battery.status.max_charge_power_W = 1000;
|
||||||
|
|
||||||
|
datalayer.battery.status.current_dA = 0;
|
||||||
|
datalayer.battery.status.remaining_capacity_Wh = 12000;
|
||||||
|
|
||||||
//TODO this is probably fine for a baseline, though CHADEMO can go as low as 150v and as high as 1500v in the latest revision
|
//TODO this is probably fine for a baseline, though CHADEMO can go as low as 150v and as high as 1500v in the latest revision
|
||||||
//the below is relative to a 96 cell NMC. lower end is possibly too low
|
//the below is relative to a 96 cell NMC. lower end is possibly too low
|
||||||
datalayer.battery.info.max_design_voltage_dV =
|
datalayer.battery.info.max_design_voltage_dV =
|
||||||
|
@ -931,5 +1094,7 @@ void setup_battery(void) { // Performs one time setup at startup
|
||||||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||||
|
|
||||||
handle_chademo_sequence();
|
handle_chademo_sequence();
|
||||||
|
|
||||||
|
setupMillis = millis();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
//Contactor control is required for CHADEMO support
|
//Contactor control is required for CHADEMO support
|
||||||
#define CONTACTOR_CONTROL
|
#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);
|
void setup_battery(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
#include "../utils/events.h"
|
#include "../utils/events.h"
|
||||||
|
|
||||||
static uint16_t cell_deviation_mV = 0;
|
static uint16_t cell_deviation_mV = 0;
|
||||||
static uint8_t charge_discharge_limit_failures = 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() {
|
void update_machineryprotection() {
|
||||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||||
|
@ -39,19 +42,27 @@ void update_machineryprotection() {
|
||||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
// 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 (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||||
{
|
{
|
||||||
|
if (!battery_full_event_fired) {
|
||||||
set_event(EVENT_BATTERY_FULL, 0);
|
set_event(EVENT_BATTERY_FULL, 0);
|
||||||
|
battery_full_event_fired = true;
|
||||||
|
}
|
||||||
datalayer.battery.status.max_charge_power_W = 0;
|
datalayer.battery.status.max_charge_power_W = 0;
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_BATTERY_FULL);
|
clear_event(EVENT_BATTERY_FULL);
|
||||||
|
battery_full_event_fired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery is empty. Do not allow further discharge.
|
// Battery is empty. Do not allow further discharge.
|
||||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
// 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 (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||||
|
if (!battery_empty_event_fired) {
|
||||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||||
|
battery_empty_event_fired = true;
|
||||||
|
}
|
||||||
datalayer.battery.status.max_discharge_power_W = 0;
|
datalayer.battery.status.max_discharge_power_W = 0;
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_BATTERY_EMPTY);
|
clear_event(EVENT_BATTERY_EMPTY);
|
||||||
|
battery_empty_event_fired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery is extremely degraded, not fit for secondlifestorage!
|
// Battery is extremely degraded, not fit for secondlifestorage!
|
||||||
|
@ -83,28 +94,28 @@ void update_machineryprotection() {
|
||||||
// Inverter is charging with more power than battery wants!
|
// 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 > 0) { // Charging
|
||||||
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
|
||||||
if (charge_discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
if (charge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||||
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||||
} else {
|
} else {
|
||||||
charge_discharge_limit_failures++;
|
charge_limit_failures++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
|
||||||
charge_discharge_limit_failures = 0;
|
charge_limit_failures = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverter is pulling too much power from battery!
|
// Inverter is pulling too much power from battery!
|
||||||
if (datalayer.battery.status.active_power_W < 0) { // Discharging
|
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 (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
|
||||||
if (charge_discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
if (discharge_limit_failures > MAX_CHARGE_DISCHARGE_LIMIT_FAILURES) {
|
||||||
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
|
||||||
} else {
|
} else {
|
||||||
charge_discharge_limit_failures++;
|
discharge_limit_failures++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
|
||||||
charge_discharge_limit_failures = 0;
|
discharge_limit_failures = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
You are licensed to use this library for any purpose, commercial or private,
|
You are licensed to use this library for any purpose, commercial or private,
|
||||||
without restriction.
|
without restriction.
|
||||||
|
|
||||||
|
Note for posterity: IVT-MOD has X1 pinout: vcc gnd CAN-L CAN-H
|
||||||
|
IVT-S has X1 pinout: vcc CAN-L CAN-H GND
|
||||||
|
|
||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "../miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
#include "../miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue