Merge branch 'main' into feature/modbus-write-event

This commit is contained in:
Daniel Öster 2024-05-26 22:00:14 +03:00 committed by GitHub
commit 46267445c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 2088 additions and 370 deletions

View file

@ -15,8 +15,6 @@ static unsigned long previousMillis640 = 0; // will store last time a 600ms C
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
@ -357,7 +355,6 @@ static uint16_t battery_soc = 0;
static uint16_t battery_soc_hvmax = 0;
static uint16_t battery_soc_hvmin = 0;
static uint16_t battery_capacity_cah = 0;
static uint16_t battery_cell_deviation_mV = 0;
static int16_t battery_temperature_HV = 0;
static int16_t battery_temperature_heat_exchanger = 0;
static int16_t battery_temperature_max = 0;
@ -454,27 +451,10 @@ void update_values_battery() { //This function maps all the values fetched via
if (datalayer.battery.status.cell_voltages_mV[0] > 0 && datalayer.battery.status.cell_voltages_mV[2] > 0) {
datalayer.battery.status.cell_min_voltage_mV = datalayer.battery.status.cell_voltages_mV[0];
datalayer.battery.status.cell_max_voltage_mV = datalayer.battery.status.cell_voltages_mV[2];
battery_cell_deviation_mV =
(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
} else {
battery_cell_deviation_mV = 0;
}
if (battery_info_available) {
// Start checking safeties. First up, cellvoltages!
if (battery_cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
uint8_t report_value = 0;
if (battery_cell_deviation_mV <= 20 * 254) {
report_value = battery_cell_deviation_mV / 20;
} else {
report_value = 255;
}
set_event(EVENT_CELL_DEVIATION_HIGH, report_value);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
if (detectedBattery == BATTERY_60AH) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
@ -505,18 +485,6 @@ void update_values_battery() { //This function maps all the values fetched via
}
}
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
// Check if we have encountered any malformed CAN messages
if (CANerror > MAX_CAN_FAILURES) {
set_event(EVENT_CAN_RX_WARNING, 0);
}
// Perform other safety checks
if (battery_status_error_locking == 2) { // HVIL seated?
set_event(EVENT_HVIL_FAILURE, 0);
@ -559,7 +527,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
battery_awake = true;
CANstillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
battery_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
@ -596,7 +565,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
battery_awake = true;
if (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
//If calculated CRC does not match transmitted CRC, increase CANerror counter
CANerror++;
datalayer.battery.status.CAN_error_counter++;
break;
}
battery_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);

View file

@ -0,0 +1,234 @@
#ifndef CHADEMO_BATTERY_TYPES_H
#define CHADEMO_BATTERY_TYPES_H
#define MAX_EVSE_POWER_CHARGING 3300
#define MAX_EVSE_OUTPUT_VOLTAGE 410
#define MAX_EVSE_OUTPUT_CURRENT 11
enum CHADEMO_STATE {
CHADEMO_FAULT,
CHADEMO_STOP,
CHADEMO_IDLE,
CHADEMO_CONNECTED,
CHADEMO_INIT, // intermediate state indicating CAN from Vehicle not yet received after connection
CHADEMO_NEGOTIATE,
CHADEMO_EV_ALLOWED,
CHADEMO_EVSE_PREPARE,
CHADEMO_EVSE_START,
CHADEMO_EVSE_CONTACTORS_ENABLED,
CHADEMO_POWERFLOW,
};
enum Mode { CHADEMO_CHARGE, CHADEMO_DISCHARGE, CHADEMO_BIDIRECTIONAL };
/* Charge/discharge sequence, indicating applicable V2H guideline
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
* V2H 1.1 is assumed, which..is somehow between 0x0 and 0x1 ? TODO: better understanding here
* Use CHADEMO_seq to decide whether emitting 209 is necessary
* 0x0 1.0 and earlier
* 0x1 2.0 appendix A
* 0x2 2.0 appendix B
* TODO: is this influenced by x109->CHADEMO_protocol_number, or x102->ControlProtocolNumberEV ??
* Unused for now.
uint8_t CHADEMO_seq = 0x0;
*/
/*----------- CHARGING SUPPORT V2X --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H100 - Vehicle - Minimum charging expectations
//TODO decide whether default values for vehicle-origin frames is even appropriate
struct x100_Vehicle_Charging_Limits {
uint8_t MinimumChargeCurrent = 0;
uint16_t MinimumBatteryVoltage = 300;
uint16_t MaximumBatteryVoltage = 402;
uint8_t ConstantOfChargingRateIndication = 0;
};
//H101 - Vehicle - Maximum charging expectations
struct x101_Vehicle_Charging_Estimate {
uint8_t MaxChargingTime10sBit = 0;
uint8_t MaxChargingTime1minBit = 0;
uint8_t EstimatedChargingTime = 0;
uint16_t RatedBatteryCapacity = 0;
};
//H102 - Vehicle - Charging targets and Status
// peer to x109 from EVSE
// termination triggers in both
// TODO see also Table A.26—Charge control termination command patterns
struct x102_Vehicle_Charging_Session { //Frame byte
uint8_t ControlProtocolNumberEV = 0; // 0
uint16_t TargetBatteryVoltage = 0; // 1-2
uint8_t ChargingCurrentRequest = 0; // 3 Note: per spec, units for this changed from kWh --> %
union {
uint8_t faults;
struct {
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool FaultBatteryVoltageDeviation : 1; // 4
bool FaultHighBatteryTemperature : 1; // 3
bool FaultBatteryCurrentDeviation : 1; // 2
bool FaultBatteryUnderVoltage : 1; // 1
bool FaultBatteryOverVoltage : 1; // 0
} fault;
} f;
union {
uint8_t packed;
struct {
bool StatusVehicleDischargeCompatible : 1; //5.7
bool unused_2 : 1; //5.6
bool unused_1 : 1; //5.5
bool StatusNormalStopRequest : 1; //5.4
bool StatusVehicle : 1; //5.3
bool StatusChargingError : 1; //5.2
bool StatusVehicleShifterPosition : 1; //5.1
bool StatusVehicleChargingEnabled : 1; //5.0 - bit zero is TODO. Vehicle charging enabled ==1 *AND* charge
// permission signal k needs to be active for charging to be
// permitted -- TODO document bits per byte for these flags
// and update variables to be more appropriate
} status;
} s;
//TODO discharge compatible is a status set here in bit 7, see beaglebone
uint8_t StateOfCharge = 0; //6 state of charge?
};
/* ---------- CHARGING: EVSE Data structures */
struct x108_EVSE_Capabilities { // Frame byte
bool contactor_weld_detection = 1; // 0
uint16_t available_output_voltage = MAX_EVSE_OUTPUT_VOLTAGE; // 1,2
uint8_t available_output_current = MAX_EVSE_OUTPUT_CURRENT; // 3
uint16_t threshold_voltage = 297; // 4,5 voltage that EVSE will stop if car fails to
// perhaps vehicle minus 3%, hardcoded initially to 96*2.95
// 6,7 = unused
};
/* Does double duty for charging and discharging */
struct x109_EVSE_Status { // Frame byte
uint8_t CHADEMO_protocol_number = 0x02; // 0
uint16_t setpoint_HV_VDC =
0; // 1,2 NOTE: charger_stepoint_HV_VDC elsewhere is a float. THIS is protocol-defined as an int. cast float->int and lose some precision for protocol adherence
uint8_t setpoint_HV_IDC = 0; // 3
//
bool discharge_compatible = true; // 4, bit 0. bits
// 4, bit 7-6 (?) unused. spec typo? maybe 1-7 unused
union {
uint8_t packed;
struct {
bool EVSE_status : 1; // 5, bit 0
bool EVSE_error : 1; // 5, bit 1
bool connector_locked : 1; // 5, bit 2 //NOTE: treated as connector_lock during discharge, but
// seen as 'energizing' during charging mode
bool battery_incompatible : 1; // 5, bit 3
bool ChgDischError : 1; // 5, bit 4
bool ChgDischStopControl : 1; // 5, bit 5 - set to false for initialization to indicate 'preparing to charge'
// set to false when ready to charge/discharge
} status;
} s;
// Either, or; not both.
// seconds field set to 0xFF by default
// indicating only the minutes field is used instead
// BOTH observed initially set to 0xFF in logs, so use
// that as the initialzed value
uint8_t remaining_time_10s = 0xFF; // 6
uint8_t remaining_time_1m = 0xFF; // 7
};
/*----------- DISCHARGING SUPPORT V2X --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H200 - Vehicle - Discharge limits
struct x200_Vehicle_Discharge_Limits {
uint8_t MaximumDischargeCurrent = 0xFF;
uint16_t MinimumDischargeVoltage = 0;
uint16_t MinimumBatteryDischargeLevel = 0;
uint16_t MaxRemainingCapacityForCharging = 0;
};
/* TODO When charge/discharge sequence control number (ID201/209) is not received, the vehicle or the EVSE
should determine that the other is the EVSE or the vehicle of the model before the V2H guideline 1.1. */
//H201 - Vehicle - Estimated capacity available
// Intended primarily for display purposes.
// Peer to H209
// NOTE: in available CAN logs from a Leaf, 209 is sent with no 201 reply, so < 1.1 must be the inferred version
struct x201_Vehicle_Discharge_Estimate {
uint8_t V2HchargeDischargeSequenceNum = 0;
uint16_t ApproxDischargeCompletionTime = 0;
uint16_t AvailableVehicleEnergy = 0;
};
/* ---------- EVSE Data structures */
struct x208_EVSE_Discharge_Capability { // Frame byte
uint8_t present_discharge_current = 0xFF; // 0
uint16_t available_input_voltage = 500; // 1,2 -- poorly named as both 'available' and minimum input voltage
uint16_t available_input_current = 250; // 3 -- poorly named as both 'available' and maximum input current
// spec idiosyncracy in naming/description
// 4,5 = unused
uint16_t lower_threshold_voltage = 0; // 6,7
};
// H209 - EVSE - Estimated Discharge Duration
// peer to Vehicle's 201 event (Note: 209 seen
// in CAN logs even when 201 is not)
struct x209_EVSE_Discharge_Estimate { // Frame byte
uint8_t sequence_control_number = 0x2; // 0
uint16_t remaining_discharge_time = 0x0000; // 0x0000 == unused
};
/*----------- DYNAMIC CONTROL SUPPORT --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
struct x110_Vehicle_Dynamic_Control { //Frame byte
union {
uint8_t packed;
struct {
bool PermissionResetMaxChgTime : 1; // bit 5 or 6? is this only x118 not x110?
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
// rate of change is -20A/s to 20A/s relative to 102.3
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
} status;
} u;
};
/* ---------- EVSE Data structures */
// TODO 118
//H118
//see also table a.59 page 104 IEEE
struct x118_EVSE_Dynamic_Control { // Frame byte
union {
uint8_t packed;
struct {
bool PermissionResetMaxChgTime : 1; // bit 5 or 6?
bool unused_3 : 1;
bool unused_2 : 1;
bool unused_1 : 1;
bool HighVoltageControlStatus : 1; // bit 2 = High voltage control support
bool HighCurrentControlStatus : 1; // bit 1 = High current control support
// rate of change is -20A/s to 20A/s relative to 102.3
bool DynamicControlStatus : 1; // bit 0 = Dynamic Control support
} status;
} u;
};
/*----------- MANUFACTURER ID SUPPORT --------------------------------------------------------------*/
/* ---------- VEHICLE Data structures */
//H700 - Vehicle - Manufacturer identification
//Peer to H708
//Used to adapt to manufacturer-prescribed optional specification
struct x700_Vehicle_Vendor_ID {
uint8_t AutomakerCode = 0; // 0 = set to 0x0 to indicate incompatibility. Best as a starting place
uint8_t OptionalContent = 0; // 1-7, variable per vendor spec
};
void handle_chademo_sequence();
#endif

View file

@ -4,11 +4,60 @@
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "CHADEMO-BATTERY-TYPES.h"
#include "CHADEMO-BATTERY.h"
/* 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 uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
bool plug_inserted = false;
bool vehicle_can_received = false;
bool vehicle_permission = false;
bool evse_permission = false;
bool precharge_low = false;
bool positive_high = false;
bool contactors_ready = false;
uint8_t maximum_soc = 90;
uint8_t minimum_soc = 10;
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
// and 118 should be used for evse responses
// permissible rate of change is -20A/s to 20A/s relative to 102.3
Mode EVSE_mode = CHADEMO_DISCHARGE;
CHADEMO_STATE CHADEMO_Status = CHADEMO_IDLE;
/* Charge/discharge sequence, indicating applicable V2H guideline
* If sequence number is not agreed upon via H201/H209 between EVSE and Vehicle,
* V2H 1.1 is assumed
* Use CHADEMO_seq to decide whether emitting 209 is necessary
* 0x0 1.0 and earlier
* 0x1 2.0 appendix A
* 0x2 2.0 appendix B
* Unused for now.
uint8_t CHADEMO_seq = 0x0;
*/
bool x201_received = false;
bool x209_sent = false;
struct x100_Vehicle_Charging_Limits x100_chg_lim = {};
struct x101_Vehicle_Charging_Estimate x101_chg_est = {};
struct x102_Vehicle_Charging_Session x102_chg_session = {};
struct x110_Vehicle_Dynamic_Control x110_vehicle_dyn = {};
struct x200_Vehicle_Discharge_Limits x200_discharge_limits = {};
struct x201_Vehicle_Discharge_Estimate x201_discharge_estimate = {};
struct x700_Vehicle_Vendor_ID x700_vendor_id = {};
struct x209_EVSE_Discharge_Estimate x209_evse_dischg_est;
struct x108_EVSE_Capabilities x108_evse_cap;
struct x109_EVSE_Status x109_evse_state;
struct x118_EVSE_Dynamic_Control x118_evse_dyn;
struct x208_EVSE_Discharge_Capability x208_evse_dischg_cap;
CAN_frame_t CHADEMO_108 = {.FIR = {.B =
{
@ -31,7 +80,13 @@ CAN_frame_t CHADEMO_118 = {.FIR = {.B =
.FF = CAN_frame_std,
}},
.MsgID = 0x118,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// OLD value from skeleton implementation, indicates dynamic control is possible.
// Hardcode above as being incompatible for simplicity in current incarnation.
// .data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
// 0x200 : From vehicle-side. A V2X-ready vehicle will send this message to broadcast its “Maximum discharger current”. (It is a similar logic to the limits set in 0x100 or 0x102 during a DC charging session)
// 0x208 : From EVSE-side. A V2X EVSE will use this to send the “present discharger current” during the session, and the “available input current”. (uses similar logic to 0x108 and 0x109 during a DC charging session)
CAN_frame_t CHADEMO_208 = {.FIR = {.B =
{
.DLC = 8,
@ -39,6 +94,7 @@ CAN_frame_t CHADEMO_208 = {.FIR = {.B =
}},
.MsgID = 0x208,
.data = {0xFF, 0xF4, 0x01, 0xF0, 0x00, 0x00, 0xFA, 0x00}};
CAN_frame_t CHADEMO_209 = {.FIR = {.B =
{
.DLC = 8,
@ -47,61 +103,25 @@ CAN_frame_t CHADEMO_209 = {.FIR = {.B =
.MsgID = 0x209,
.data = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
//H100
uint8_t MinimumChargeCurrent = 0;
uint16_t MinumumBatteryVoltage = 0;
uint16_t MaximumBatteryVoltage = 0;
uint8_t ConstantOfChargingRateIndication = 0;
//H101
uint8_t MaxChargingTime10sBit = 0;
uint8_t MaxChargingTime1minBit = 0;
uint8_t EstimatedChargingTime = 0;
uint16_t RatedBatteryCapacity = 0;
//H102
uint8_t ControlProtocolNumberEV = 0;
uint16_t TargetBatteryVoltage = 0;
uint8_t ChargingCurrentRequest = 0;
uint8_t FaultBatteryVoltageDeviation = 0;
uint8_t FaultHighBatteryTemperature = 0;
uint8_t FaultBatteryCurrentDeviation = 0;
uint8_t FaultBatteryUndervoltage = 0;
uint8_t FaultBatteryOvervoltage = 0;
uint8_t StatusNormalStopRequest = 0;
uint8_t StatusVehicle = 0;
uint8_t StatusChargingSystem = 0;
uint8_t StatusVehicleShifterPosition = 0;
uint8_t StatusVehicleCharging = 0;
uint8_t ChargingRate = 0;
//H200
uint8_t MaximumDischargeCurrent = 0;
uint16_t MinimumDischargeVoltage = 0;
uint8_t MinimumBatteryDischargeLevel = 0;
uint8_t MaxRemainingCapacityForCharging = 0;
//H201
uint8_t V2HchargeDischargeSequenceNum = 0;
uint16_t ApproxDischargeCompletionTime = 0;
uint16_t AvailableVehicleEnergy = 0;
//H700
uint8_t AutomakerCode = 0;
uint32_t OptionalContent = 0;
//H118
uint8_t DynamicControlStatus = 0;
uint8_t HighCurrentControlStatus = 0;
uint8_t HighVoltageControlStatus = 0;
//This function maps all the values fetched via CAN to the correct parameters used for the inverter
void update_values_battery() {
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
datalayer.battery.status.real_soc = ChargingRate;
datalayer.battery.status.real_soc = x102_chg_session.StateOfCharge;
datalayer.battery.status.max_discharge_power_W =
(MaximumDischargeCurrent * 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 = TargetBatteryVoltage; //TODO: scaling?
datalayer.battery.status.voltage_dV = x102_chg_session.TargetBatteryVoltage; //TODO: scaling?
datalayer.battery.info.total_capacity_Wh =
((RatedBatteryCapacity / 0.11) *
((x101_chg_est.RatedBatteryCapacity / 0.11) *
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
/* TODO max charging rate =
* x200_discharge_limits.MaxRemainingCapacityForCharging /
* x101_chg_est.RatedBatteryCapacity * 100;
*/
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);
@ -111,72 +131,494 @@ void update_values_battery() { //This function maps all the values fetched via
} 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.
* 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
* CAN frames.
*/
#ifdef DEBUG_VIA_USB
Serial.print("SOC 0x100: ");
Serial.println(ConstantOfChargingRateIndication);
Serial.println(x100_chg_lim.ConstantOfChargingRateIndication);
#endif
}
//TODO see Table A.26—Charge control termination command pattern on pg58
//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) {
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6];
}
inline void process_vehicle_charging_maximums(CAN_frame_t rx_frame) {
x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1];
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3];
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
}
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 priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
vehicle_permission = digitalRead(CHADEMO_PIN_4);
x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0];
x102_chg_session.f.fault.FaultBatteryOverVoltage = bitRead(rx_frame.data.u8[4], 0);
x102_chg_session.f.fault.FaultBatteryUnderVoltage = bitRead(rx_frame.data.u8[4], 1);
x102_chg_session.f.fault.FaultBatteryCurrentDeviation = bitRead(rx_frame.data.u8[4], 2);
x102_chg_session.f.fault.FaultHighBatteryTemperature = bitRead(rx_frame.data.u8[4], 3);
x102_chg_session.f.fault.FaultBatteryVoltageDeviation = bitRead(rx_frame.data.u8[4], 4);
x102_chg_session.s.status.StatusVehicleChargingEnabled = bitRead(rx_frame.data.u8[5], 0);
x102_chg_session.s.status.StatusVehicleShifterPosition = bitRead(rx_frame.data.u8[5], 1);
x102_chg_session.s.status.StatusChargingError = bitRead(rx_frame.data.u8[5], 2);
x102_chg_session.s.status.StatusVehicle = bitRead(rx_frame.data.u8[5], 3);
x102_chg_session.s.status.StatusNormalStopRequest = bitRead(rx_frame.data.u8[5], 4);
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
//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.
* Also, vehicle CAN should not simultaneously indicate enabled while permissions signal is absent
*
* Either is a logical inconsistency per spec (vehicle has lost state, some wire/pin is broken, etc)
* this should trigger stop and teardown
*/
if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) ||
(x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) {
#ifdef DEBUG_VIA_USB
Serial.println("Inconsistent charge/discharge state.");
#endif
CHADEMO_Status = CHADEMO_FAULT;
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) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery over voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery under voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
// end
if (priorTargetBatteryVoltage > 0 && newTargetBatteryVoltage == 0) {
CHADEMO_Status = CHADEMO_STOP;
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
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE &&
(x102_chg_session.StateOfCharge >= maximum_soc || !vehicle_permission)) {
#ifdef DEBUG_VIA_USB
Serial.println("State of charge ceiling reached, stop charging");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) {
CHADEMO_Status = CHADEMO_EV_ALLOWED;
#ifdef DEBUG_VIA_USB
Serial.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
#endif
return;
}
// TODO this and the next stanza influence state/control
// and probably don't belong in this function
// consider relocating
if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 &&
newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) {
#ifdef DEBUG_VIA_USB
Serial.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
#endif
CHADEMO_Status = CHADEMO_EVSE_START;
return;
}
if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) {
#ifdef DEBUG_VIA_USB
Serial.println("updating vehicle request in process_vehicle_charging_session()");
#endif
return;
}
#ifdef DEBUG_VIA_USB
Serial.println("UNHANDLED STATE IN process_vehicle_charging_session()");
#endif
return;
}
/* x200 Vehicle, peer to x208 EVSE */
inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
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.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
}
/* Vehicle 0x201, peer to EVSE 0x209
* HOWEVER, 201 isn't even emitted in any of the v2x canlogs available
*/
inline void process_vehicle_discharge_estimate(CAN_frame_t rx_frame) {
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.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
}
inline void process_vehicle_dynamic_control(CAN_frame_t rx_frame) {
//SM Dynamic Control = Charging station can increase of decrease "available output current" during charging.
//If you set 0x110 byte 0, bit 0 to 1 you say you can do dynamic control.
//Charging station communicates this in 0x118 byte 0, bit 0
x110_vehicle_dyn.u.status.HighVoltageControlStatus = bitRead(rx_frame.data.u8[0], 2);
x110_vehicle_dyn.u.status.HighCurrentControlStatus = bitRead(rx_frame.data.u8[0], 1);
x110_vehicle_dyn.u.status.DynamicControlStatus = bitRead(rx_frame.data.u8[0], 0);
}
inline void process_vehicle_vendor_ID(CAN_frame_t rx_frame) {
x700_vendor_id.AutomakerCode = rx_frame.data.u8[0];
x700_vendor_id.OptionalContent =
((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) {
CANstillAlive == 12; //We are getting CAN messages from the vehicle, inform the watchdog
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
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.MsgID, HEX);
Serial.print(" ");
Serial.print(rx_frame.FIR.B.DLC);
Serial.print(" ");
for (int i = 0; i < rx_frame.FIR.B.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println("");
#endif
/* e.g., CHADEMO_INIT state is a transient, used to indicate when CAN
* has not yet been receied from a vehicle
*/
if (CHADEMO_Status == CHADEMO_INIT) {
// 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
vehicle_can_received = true;
switch (rx_frame.MsgID) {
case 0x100:
MinimumChargeCurrent = rx_frame.data.u8[0];
MinumumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
ConstantOfChargingRateIndication = rx_frame.data.u8[6];
process_vehicle_charging_minimums(rx_frame);
break;
case 0x101:
MaxChargingTime10sBit = rx_frame.data.u8[1];
MaxChargingTime1minBit = rx_frame.data.u8[2];
EstimatedChargingTime = rx_frame.data.u8[3];
RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
process_vehicle_charging_maximums(rx_frame);
break;
case 0x102:
ControlProtocolNumberEV = rx_frame.data.u8[0];
TargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
ChargingCurrentRequest = rx_frame.data.u8[3];
FaultBatteryOvervoltage = (rx_frame.data.u8[4] & 0x01);
FaultBatteryUndervoltage = (rx_frame.data.u8[4] & 0x02) >> 1;
FaultBatteryCurrentDeviation = (rx_frame.data.u8[4] & 0x04) >> 2;
FaultHighBatteryTemperature = (rx_frame.data.u8[4] & 0x08) >> 3;
FaultBatteryVoltageDeviation = (rx_frame.data.u8[4] & 0x10) >> 4;
StatusVehicleCharging = (rx_frame.data.u8[5] & 0x01);
StatusVehicleShifterPosition = (rx_frame.data.u8[5] & 0x02) >> 1;
StatusChargingSystem = (rx_frame.data.u8[5] & 0x04) >> 2;
StatusVehicle = (rx_frame.data.u8[5] & 0x08) >> 3;
StatusNormalStopRequest = (rx_frame.data.u8[5] & 0x10) >> 4;
ChargingRate = rx_frame.data.u8[6];
process_vehicle_charging_session(rx_frame);
break;
case 0x200: //For V2X
MaximumDischargeCurrent = rx_frame.data.u8[0];
MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
process_vehicle_charging_limits(rx_frame);
break;
case 0x201: //For V2X
V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
break;
case 0x700:
AutomakerCode = rx_frame.data.u8[0];
OptionalContent =
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
x201_received = true;
process_vehicle_discharge_estimate(rx_frame);
break;
case 0x110: //Only present on Chademo v2.0
DynamicControlStatus = (rx_frame.data.u8[0] & 0x01);
HighCurrentControlStatus = (rx_frame.data.u8[0] & 0x02) >> 1;
HighVoltageControlStatus = (rx_frame.data.u8[0] & 0x04) >> 2;
process_vehicle_dynamic_control(rx_frame);
case 0x700:
process_vehicle_vendor_ID(rx_frame);
break;
default:
break;
}
handle_chademo_sequence();
}
/* (re)initialize evse structures to pre-charge/discharge states */
void evse_init() {
// Held at 1 until start of charge when set to 0
// returns to 1 when ceasing power flow
// mutually exclusive values
x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0;
x109_evse_state.s.status.connector_locked = 0;
x109_evse_state.s.status.battery_incompatible = 0;
x109_evse_state.s.status.ChgDischError = 0;
/* values set during object initialization
x109_evse_state.CHADEMO_protocol_number
x109_evse_state.remaining_time_10s
x109_evse_state.remaining_time_1m
*/
}
/* updates for x108 */
void update_evse_capabilities(CAN_frame_t& f) {
/* TODO use charger defines/runtime config?
* for now..leave as a future tweak.
* use vehicle requests as a ceiling
*/
/* interpret this as mostly a timing concern, so indicate yes we support EV contactor weld detection
* expectations: <1s after vehicle contactors open, charger output will drop to <=25% of prior voltage
* <2s after vehicle contactors open, charter output voltage will drop to <=10V
*
* see A.10.2.1 Example of welding detection logic on the vehicle
*/
x108_evse_cap.contactor_weld_detection = 0x1;
x108_evse_cap.available_output_voltage = x102_chg_session.TargetBatteryVoltage;
/* calculate max threshold to protect battery - using vehicle-provided max minus 2% */
x108_evse_cap.threshold_voltage =
x100_chg_lim.MaximumBatteryVoltage - (int)(x100_chg_lim.MaximumBatteryVoltage / 100 * 2);
// Power and voltage may be best derived from config/defines not from the x108 settings elsewhere, ideally
// only set current when voltage > 0, as it is set by x102 TargetBatteryVoltage
if (x108_evse_cap.available_output_voltage) {
x108_evse_cap.available_output_current = MAX_EVSE_POWER_CHARGING / x108_evse_cap.available_output_voltage;
}
/* update Frame - byte 6 and 7 are unused */
CHADEMO_108.data.u8[0] = x108_evse_cap.contactor_weld_detection;
CHADEMO_108.data.u8[1] = lowByte(x108_evse_cap.available_output_voltage);
CHADEMO_108.data.u8[2] = highByte(x108_evse_cap.available_output_voltage);
CHADEMO_108.data.u8[3] = x108_evse_cap.available_output_current;
CHADEMO_108.data.u8[4] = lowByte(x108_evse_cap.threshold_voltage);
CHADEMO_108.data.u8[5] = highByte(x108_evse_cap.threshold_voltage);
}
/* updates for x109 */
void update_evse_status(CAN_frame_t& f) {
x109_evse_state.s.status.EVSE_status = 1;
x109_evse_state.s.status.EVSE_error = 0;
x109_evse_state.s.status.ChgDischError = 0;
x109_evse_state.s.status.ChgDischStopControl = 0;
/* updated only in state handler
* TODO..why?
x109_evse_state.s.connector_locked = 0;
*/
if (EVSE_mode == CHADEMO_DISCHARGE) {
/* Occasionally oberved as set to 0 when discharging
* this may be true for all V2H versions
* unless it was a logging discrepancy
x109_evse_state.setpoint_HV_VDC = 0;
x109_evse_state.setpoint_HV_IDC = 0;
*/
x109_evse_state.setpoint_HV_VDC =
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
x109_evse_state.setpoint_HV_IDC =
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
/* TODO calculate remaining discharge time : for now == 60m */
x109_evse_state.remaining_time_1m = 60;
} else if (EVSE_mode == CHADEMO_CHARGE) {
//FIXME these are supposed to be measured values, e.g., from a shunt
//for now we are literally saying they're equivalent to the request or max charger capability
//this is wrong
x109_evse_state.setpoint_HV_VDC =
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
x109_evse_state.setpoint_HV_IDC =
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
/* 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
*/
/* 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) {
x109_evse_state.setpoint_HV_IDC = floor(MAX_EVSE_POWER_CHARGING / x109_evse_state.setpoint_HV_VDC);
}
/* TODO calculate remaining charge time : for now == 60m */
x109_evse_state.remaining_time_1m = 60;
}
/* Table A.26 - See also Charge control termination command patterns
* This handling must be mirrored in handling for vehicle x102
*
*/
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
///Battery incompatibility” flag #109.5.3 to 1
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.battery_incompatible = 1;
x109_evse_state.s.status.ChgDischStopControl = 1;
CHADEMO_Status = CHADEMO_FAULT;
}
//CHADEMO_109.data.u8[0] hardcoded to 0x2 for CHAdeMO v1, 1.0.1, 1.1, 1.2
// in initialization
CHADEMO_109.data.u8[0] = x109_evse_state.CHADEMO_protocol_number;
CHADEMO_109.data.u8[1] = lowByte(x109_evse_state.setpoint_HV_VDC);
CHADEMO_109.data.u8[2] = highByte(x109_evse_state.setpoint_HV_VDC);
CHADEMO_109.data.u8[3] = x109_evse_state.setpoint_HV_IDC;
CHADEMO_109.data.u8[4] = x109_evse_state.discharge_compatible;
/* clear statuses/faults, then set explicitly */
CHADEMO_109.data.u8[5] = 0;
CHADEMO_109.data.u8[5] =
x109_evse_state.s.status.EVSE_status | x109_evse_state.s.status.EVSE_error << 1 |
x109_evse_state.s.status.connector_locked << 2 | x109_evse_state.s.status.battery_incompatible << 3 |
x109_evse_state.s.status.ChgDischError << 4 | x109_evse_state.s.status.ChgDischStopControl << 5;
CHADEMO_109.data.u8[6] = x109_evse_state.remaining_time_10s;
CHADEMO_109.data.u8[7] = x109_evse_state.remaining_time_1m;
}
/* Discharge estimates: x209 EVSE = peer to x201 Vehicle
* NOTE: x209 is emitted in CAN logs when x201 isn't even present
* it may not be understood by leaf (or ignored unless >= a certain protocol version or v2h sequence number
*/
void update_evse_discharge_estimate(CAN_frame_t& f) {
//x209_evse_dischg_est.remaining_discharge_time_1m = x201_discharge_estimate.ApproxDischargeCompletionTime;
//x201_discharge_estimate.AvailableVehicleEnergy = 0;
//do nothing to alter the default initialized values..this may be unneeded
/* TODO
if (x200_discharge_limits.MaxRemainingCapacityForCharging = max charge voltage){
if (x200_discharge_limits.MinimumBatteryDischargeLevel = kwH for v2h<1.0){
if (x200_discharge_limits.MaxRemainingCapacityForCharging = kwH for v2h<1.0){
*/
CHADEMO_209.data.u8[0] = x209_evse_dischg_est.sequence_control_number;
CHADEMO_209.data.u8[1] = x209_evse_dischg_est.remaining_discharge_time;
}
/* x208 EVSE, peer to 0x200 Vehicle */
void update_evse_discharge_capabilities(CAN_frame_t& f) {
//FIXME these are supposed to be measured values, e.g., from a shunt
//we are literally saying theyre arbitrary for now
//this is wrong
x208_evse_dischg_cap.present_discharge_current = 0xFF - 6;
x208_evse_dischg_cap.available_input_current = 0xFF - x200_discharge_limits.MaximumDischargeCurrent;
x208_evse_dischg_cap.available_input_voltage = x200_discharge_limits.MinimumDischargeVoltage;
/* calculate min threshold to protect battery - using vehicle-provided minimum plus 2% */
x208_evse_dischg_cap.lower_threshold_voltage =
x200_discharge_limits.MinimumDischargeVoltage + (int)(x200_discharge_limits.MinimumDischargeVoltage / 100 * 2);
/* 0x00 == unused
if (x200_discharge_limits.MinimumBatteryDischargeLevel > 0 &&
x208_evse_dischg_cap.minimum_input_voltage < x200_discharge_limits.MinimumBatteryDischargeLevel){
// stop discharging, but permit charging if mode = bidirectional
}
*/
//TODO might be ideal to do the 0xFF subtraction HERE during serialization, rather than above?
CHADEMO_208.data.u8[0] = x208_evse_dischg_cap.present_discharge_current;
CHADEMO_208.data.u8[1] = lowByte(x208_evse_dischg_cap.available_input_voltage);
CHADEMO_208.data.u8[2] = highByte(x208_evse_dischg_cap.available_input_voltage);
CHADEMO_208.data.u8[3] = x208_evse_dischg_cap.available_input_current;
CHADEMO_208.data.u8[6] = lowByte(x208_evse_dischg_cap.lower_threshold_voltage);
CHADEMO_208.data.u8[7] = highByte(x208_evse_dischg_cap.lower_threshold_voltage);
}
void send_can_battery() {
unsigned long currentMillis = millis();
handle_chademo_sequence();
/* no EVSE messages should be sent until the vehicle has
* initiated
*/
if (CHADEMO_Status <= CHADEMO_INIT || !vehicle_can_received) {
return;
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
// Check if sending of CAN messages has been delayed too much.
@ -185,24 +627,309 @@ void send_can_battery() {
}
previousMillis100 = currentMillis;
update_evse_capabilities(CHADEMO_108);
update_evse_status(CHADEMO_109);
update_evse_discharge_capabilities(CHADEMO_208);
update_evse_discharge_estimate(CHADEMO_209);
/* most updates to these EVSE frames are made
* upon receipt of a Vehicle message, as
* that is the limiting factor. Therefore, we
* can generally send as is without tweaks here.
*/
ESP32Can.CANWriteFrame(&CHADEMO_108);
ESP32Can.CANWriteFrame(&CHADEMO_109);
ESP32Can.CANWriteFrame(&CHADEMO_208);
ESP32Can.CANWriteFrame(&CHADEMO_209);
if (ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles?
/* TODO for dynamic control: can send x118 with byte 6 bit 0 set to 0 for 1s (before flipping back to 1) as a way of giving vehicle a chance to update 101.1 and 101.2
* within 6 seconds of x118 toggle.
* Then 109.6 and 109.7 reset remaining charging time
* see A.11.5.3.1.3 Remaining charging time (H109.6, H109.7) for a better description
*/
if (EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL) {
ESP32Can.CANWriteFrame(&CHADEMO_208);
if (x201_received) {
ESP32Can.CANWriteFrame(&CHADEMO_209);
x209_sent = true;
}
}
// TODO need an update_evse_dynamic_control(..) function above before we send 118
// 110.0.0
if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles?
#ifdef DEBUG_VIA_USB
//FIXME REMOVE
Serial.println("REMOVE: proto 2.0");
#endif
ESP32Can.CANWriteFrame(&CHADEMO_118);
}
}
}
/* A lot of the heavy lifting happens here. This is essentially the state hander. SOME
* state transitions happen in functions before/after this is called.
*
* stages according to IEEE SPEC, with our states mapped into each.
* 1) Standby stage
* CHADEMO_IDLE
* 2) Preparation stage
* CHADEMO_CONNECTED
* CHADEMO_INIT
* CHADEMO_NEGOTIATE
* CHADEMO_EV_ALLOWED
* CHADEMO_EVSE_PREPARE
* CHADEMO_EVSE_START
* CHADEMO_EVSE_CONTACTORS_ENABLED
* 3) Charging/Discharging stage
* CHADEMO_POWERFLOW
* 4) Termination stage
* CHADEMO_STOP
* 5) Emergency stop stage
* CHADEMO_FAULT
*/
void handle_chademo_sequence() {
precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
contactors_ready = precharge_low && positive_high;
vehicle_permission = digitalRead(CHADEMO_PIN_4);
/* ------------------- State override conditions checks ------------------- */
/* ------------------------------------------------------------------------------ */
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !x102_chg_session.s.status.StatusVehicleShifterPosition) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle is not parked, abort.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle charge/discharge permission ended, stop.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
/* ------------------- STATE HANDLER ------------------- */
/* ------------------------------------------------------------------------------ */
switch (CHADEMO_Status) {
case CHADEMO_IDLE:
/* this is where we can unlock connector? */
digitalWrite(CHADEMO_LOCK, LOW);
plug_inserted = digitalRead(CHADEMO_PIN_7);
if (!plug_inserted) {
#ifdef DEBUG_VIA_USB
// Commented unless needed for debug
// Serial.println("CHADEMO plug is not inserted.");
//
#endif
return;
}
CHADEMO_Status = CHADEMO_CONNECTED;
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
#endif
break;
case CHADEMO_CONNECTED:
/* plug_inserted is .. essentially a volatile of sorts, so verify */
if (plug_inserted) {
/* If connection is detectable, jumpstart handshake by
* indicate that the EVSE is ready to begin
*/
digitalWrite(CHADEMO_PIN_2, HIGH);
/* State change to initializing. We will re-enter the handler upon receipt of CAN */
CHADEMO_Status = CHADEMO_INIT;
} else {
/* this potentially-viewed-as-redundant condition checking is candidly
* an expression racy-relaties of the real world. Depending upon
* testing/performance, it may be better to pepper this state handler
* with timers to have higher confidence of certain conditions hitting
* a steady state
*/
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization.");
#endif
CHADEMO_Status = CHADEMO_IDLE;
}
break;
case CHADEMO_INIT:
/* Transient state while awaiting CAN from Vehicle.
* Used for triggers/error handling elsewhere;
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
*/
#ifdef DEBUG_VIA_USB
Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
#endif
evse_init();
break;
case CHADEMO_NEGOTIATE:
/* Vehicle and EVSE dance */
//TODO if pin 4 / j goes high,
Serial.print("State of charge: ");
Serial.println(x102_chg_session.StateOfCharge);
Serial.print("Parked?: ");
Serial.println(x102_chg_session.s.status.StatusVehicleShifterPosition);
Serial.print("Target Battery Voltage: ");
Serial.println(x102_chg_session.TargetBatteryVoltage);
x109_evse_state.s.status.ChgDischStopControl = 1;
break;
case CHADEMO_EV_ALLOWED:
// pin 4 (j) reads high
if (vehicle_permission) {
//lock connector here
digitalWrite(CHADEMO_LOCK, HIGH);
//TODO spec requires test to validate solenoid has indeed engaged.
// example uses a comparator/current consumption check around solenoid
x109_evse_state.s.status.connector_locked = true;
}
CHADEMO_Status = CHADEMO_EVSE_PREPARE;
break;
case CHADEMO_EVSE_PREPARE:
/* TODO voltage check of output < 10v */
/* insulation test hypothetically happens here before triggering PIN 10 high */
digitalWrite(CHADEMO_PIN_10, HIGH);
evse_permission = true;
// likely unnecessary but just to be sure. consider removal
x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0;
//state changes only upon receipt of charging session request
break;
case CHADEMO_EVSE_START:
datalayer.system.status.battery_allows_contactor_closing = true;
x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0;
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
/* break rather than fall through because contactors are not instantaneous;
* worth giving it a cycle to finish
*/
break;
case CHADEMO_EVSE_CONTACTORS_ENABLED:
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
if (contactors_ready) {
/* transition to POWERFLOW state if discharge compatible on both sides */
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
(EVSE_mode == CHADEMO_DISCHARGE || EVSE_mode == CHADEMO_BIDIRECTIONAL)) {
CHADEMO_Status = CHADEMO_POWERFLOW;
x109_evse_state.s.status.ChgDischStopControl = 0;
x109_evse_state.s.status.EVSE_status = 1;
}
if (EVSE_mode == CHADEMO_CHARGE) {
//TODO not supported currently
//CHADEMO_Status = CHADEMO_POWERFLOW;
//x109_evse_state.s.status.ChgDischStopControl = 0;
}
}
/* break or fall through ? TODO */
break;
case CHADEMO_POWERFLOW:
/* POWERFLOW for charging, discharging, and bidirectional */
/* Interpretation */
if (x102_chg_session.s.status.StatusVehicleShifterPosition) {
/* Vehicle is no longer in park */
// vehicle will switch k off, EVSE MAY see j (pin 4) go low
// EVEN IF evse reads read pin 4 to see high, if this condition is true then trigger EVSE charge/discharge stop
// per spec
// SEPARATE check for pin 4/j condition does not depend on this flag. it's an OR condition
}
if (x102_chg_session.TargetBatteryVoltage == 0x00) {
//TODO flag error and do not calculate power in EVSE response?
// probably unnecessary as other flags will be set causing this to be caught
}
// 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;
vehicle_permission = digitalRead(CHADEMO_PIN_4);
break;
case CHADEMO_STOP:
/* back to CHADEMO_IDLE after teardown */
x109_evse_state.s.status.ChgDischStopControl = 1;
x109_evse_state.s.status.EVSE_status = 0;
x109_evse_state.s.status.battery_incompatible = 0;
digitalWrite(CHADEMO_PIN_10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW);
evse_permission = false;
vehicle_permission = false;
x209_sent = false;
x201_received = false;
CHADEMO_Status = CHADEMO_IDLE;
break;
case CHADEMO_FAULT:
/* 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.ChgDischError = 1;
x109_evse_state.s.status.ChgDischStopControl = 1;
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO fault encountered, tearing down to make safe");
#endif
digitalWrite(CHADEMO_PIN_10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW);
evse_permission = false;
vehicle_permission = false;
x209_sent = false;
x201_received = false;
break;
default:
#ifdef DEBUG_VIA_USB
Serial.println("UNHANDLED CHADEMO_STATE, setting FAULT");
#endif
CHADEMO_Status = CHADEMO_FAULT;
break;
}
return;
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Chademo battery selected");
#endif
CHADEMO_Status = CHADEMO_IDLE;
/* disallow contactors until permissions is granted by vehicle */
datalayer.system.status.battery_allows_contactor_closing = false;
//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
datalayer.battery.info.max_design_voltage_dV =
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
datalayer.battery.info.min_design_voltage_dV = 2000; // 200.0V under this, discharging further is disabled
datalayer.battery.info.min_design_voltage_dV = 2600; // 260.0V under this, discharging further is disabled
/* initialize EVSE data, state, and CAN frame representations */
switch (EVSE_mode) {
case CHADEMO_DISCHARGE:
case CHADEMO_BIDIRECTIONAL:
x109_evse_state.discharge_compatible = true;
break;
case CHADEMO_CHARGE:
x109_evse_state.discharge_compatible = false;
break;
default:
break;
}
x109_evse_state.s.status.ChgDischStopControl = 1;
handle_chademo_sequence();
}
#endif

View file

@ -5,6 +5,10 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999
//Contactor control is required for CHADEMO support
#define CONTACTOR_CONTROL
void setup_battery(void);

View file

@ -10,8 +10,7 @@
//Figure out if CAN messages need to be sent to keep the system happy?
/* Do not change code below unless you are sure what you are doing */
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t BMU_Detected = 0;
static uint8_t CMU_Detected = 0;
@ -108,14 +107,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_min_dC = (int16_t)(max_temp_cel * 10);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (!BMU_Detected) {
#ifdef DEBUG_VIA_USB
Serial.println("BMU not detected, check wiring!");
@ -144,8 +135,8 @@ void update_values_battery() { //This function maps all the values fetched via
}
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive =
12; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
switch (rx_frame.MsgID) {
case 0x374: //BMU message, 10ms - SOC
temp_value = ((rx_frame.data.u8[1] - 10) / 2);

View file

@ -5,6 +5,7 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 500
void setup_battery(void);

View file

@ -9,11 +9,9 @@
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
static uint16_t inverterVoltageFrameHigh = 0;
static uint16_t inverterVoltage = 0;
@ -23,7 +21,6 @@ static uint16_t SOC_Display = 0;
static uint16_t batterySOH = 1000;
static uint16_t CellVoltMax_mV = 3700;
static uint16_t CellVoltMin_mV = 3700;
static uint16_t cell_deviation_mV = 0;
static uint16_t batteryVoltage = 0;
static int16_t leadAcidBatteryVoltage = 120;
static int16_t batteryAmps = 0;
@ -97,10 +94,10 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
if (!datalayer.battery.status.CAN_battery_still_alive) {
set_event(EVENT_CANFD_RX_FAILURE, 0);
} else {
CANstillAlive--;
datalayer.battery.status.CAN_battery_still_alive--;
clear_event(EVENT_CANFD_RX_FAILURE);
}
@ -113,19 +110,12 @@ void update_values_battery() { //This function maps all the values fetched via
}
// Check if cell voltages are within allowed range
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
if (datalayer.battery.status.bms_status ==
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
@ -208,7 +198,7 @@ void send_canfd_frame(CANFDMessage frame) {
}
void receive_canfd_battery(CANFDMessage frame) {
CANstillAlive = 12;
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (frame.id) {
case 0x7EC:
// print_canfd_frame(frame);

View file

@ -8,7 +8,7 @@
extern ACAN2517FD canfd;
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 150
#define MAXCHARGEPOWERALLOWED 10000
#define MAXDISCHARGEPOWERALLOWED 10000

View file

@ -9,11 +9,9 @@
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10 = 0; // will store last time a 10s CAN Message was send
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
static uint16_t soc_calculated = 0;
static uint16_t SOC_BMS = 0;
@ -21,7 +19,6 @@ static uint16_t SOC_Display = 0;
static uint16_t batterySOH = 1000;
static uint16_t CellVoltMax_mV = 3700;
static uint16_t CellVoltMin_mV = 3700;
static uint16_t cell_deviation_mV = 0;
static uint16_t allowedDischargePower = 0;
static uint16_t allowedChargePower = 0;
static uint16_t batteryVoltage = 0;
@ -182,14 +179,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (waterleakageSensor == 0) {
set_event(EVENT_WATER_INGRESS, 0);
}
@ -216,19 +205,12 @@ void update_values_battery() { //This function maps all the values fetched via
}
// Check if cell voltages are within allowed range
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
if (datalayer.battery.status.bms_status ==
FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
@ -305,7 +287,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
break;
case 0x542: //BMS SOC
startedUp = true;
CANstillAlive = 12; //We use this message to verify that BMS is still alive
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
SOC_Display = rx_frame.data.u8[0] * 5; //100% = 200 ( 200 * 5 = 1000 )
break;
case 0x594:

View file

@ -5,6 +5,7 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 150
void setup_battery(void);

View file

@ -13,8 +13,6 @@
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send
static uint16_t CANerror = 0; //counter on how many CAN errors encountered
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t mprun10r = 0; //counter 0-20 for 0x1F2 message
static uint8_t mprun10 = 0; //counter 0-3
static uint8_t mprun100 = 0; //counter 0-3
@ -91,7 +89,6 @@ static uint8_t crctable[256] = {
static uint8_t LEAF_Battery_Type = ZE0_BATTERY;
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
#define WH_PER_GID 77 //One GID is this amount of Watt hours
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
@ -132,14 +129,13 @@ static bool Batt_Heater_Mail_Send_Request = false; //Stores info when a heat re
static uint8_t battery_request_idx = 0;
static uint8_t group_7bb = 0;
static uint8_t group = 1;
static uint8_t stop_battery_query = 1;
static bool stop_battery_query = true;
static uint8_t hold_off_with_polling_10seconds = 10;
static uint16_t cell_voltages[97]; //array with all the cellvoltages
static uint8_t cellcounter = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static uint16_t HX = 0; //Internal resistance
static uint16_t insulation = 0; //Insulation resistance
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint16_t HX = 0; //Internal resistance
static uint16_t insulation = 0; //Insulation resistance
static uint16_t temp_raw_1 = 0;
static uint8_t temp_raw_2_highnibble = 0;
static uint16_t temp_raw_2 = 0;
@ -308,14 +304,6 @@ void update_values_battery() { /* This function maps all the values fetched via
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
}
if (LB_StateOfHealth < 25) { //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out.
if (LB_StateOfHealth != 0) { //Extra check to see that we actually have a SOH Value available
set_event(EVENT_LOW_SOH, LB_StateOfHealth);
} else {
clear_event(EVENT_LOW_SOH);
}
}
#ifdef INTERLOCK_REQUIRED
if (!LB_Interlock) {
set_event(EVENT_HVIL_FAILURE, 0);
@ -324,19 +312,6 @@ void update_values_battery() { /* This function maps all the values fetched via
}
#endif
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (CANerror >
MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
{
set_event(EVENT_CAN_RX_WARNING, 0);
}
if (LB_HeatExist) {
if (LB_Heating_Stop) {
set_event(EVENT_BATTERY_WARMED_UP, 0);
@ -361,7 +336,6 @@ void update_values_battery() { /* This function maps all the values fetched via
print_with_units(", Has heater: ", LB_HeatExist, " ");
print_with_units(", Max cell voltage: ", min_max_voltage[1], "mV ");
print_with_units(", Min cell voltage: ", min_max_voltage[0], "mV ");
print_with_units(", Cell deviation: ", cell_deviation_mV, "mV ");
#endif
}
@ -369,7 +343,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x1DB:
if (is_message_corrupt(rx_frame)) {
CANerror++;
datalayer.battery.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
LB_Current2 = (rx_frame.data.u8[0] << 3) | (rx_frame.data.u8[1] & 0xe0) >> 5;
@ -394,7 +368,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
break;
case 0x1DC:
if (is_message_corrupt(rx_frame)) {
CANerror++;
datalayer.battery.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
LB_Discharge_Power_Limit = ((rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6) / 4.0);
@ -403,7 +377,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
break;
case 0x55B:
if (is_message_corrupt(rx_frame)) {
CANerror++;
datalayer.battery.status.CAN_error_counter++;
break; //Message content malformed, abort reading data from it
}
LB_TEMP = (rx_frame.data.u8[0] << 2 | rx_frame.data.u8[1] >> 6);
@ -413,7 +387,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
LB_Capacity_Empty = (bool)((rx_frame.data.u8[6] & 0x80) >> 7);
break;
case 0x5BC:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery is sending CAN
LB_MAX = ((rx_frame.data.u8[5] & 0x10) >> 4);
if (LB_MAX) {
@ -466,7 +440,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
LEAF_Battery_Type = ZE1_BATTERY;
break;
case 0x79B:
stop_battery_query = 1; //Someone is trying to read data with Leafspy, stop our own polling!
stop_battery_query = true; //Someone is trying to read data with Leafspy, stop our own polling!
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
@ -521,15 +495,9 @@ void receive_can_battery(CAN_frame_t rx_frame) {
min_max_voltage[1] = cell_voltages[cellcounter];
}
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
datalayer.battery.status.cell_max_voltage_mV = min_max_voltage[1];
datalayer.battery.status.cell_min_voltage_mV = min_max_voltage[0];
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
}
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
@ -797,7 +765,7 @@ void send_can_battery() {
if (hold_off_with_polling_10seconds > 0) {
hold_off_with_polling_10seconds--;
} else {
stop_battery_query = 0;
stop_battery_query = false;
}
}
}

View file

@ -6,6 +6,7 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 500
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
bool is_message_corrupt(CAN_frame_t rx_frame);

View file

@ -15,7 +15,6 @@
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
//Actual content messages
CAN_frame_t PYLON_3010 = {.FIR = {.B =
@ -96,18 +95,10 @@ void update_values_battery() {
datalayer.battery.info.max_design_voltage_dV = charge_cutoff_voltage;
datalayer.battery.info.min_design_voltage_dV = discharge_cutoff_voltage;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
}
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive = 12;
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.MsgID) {
case 0x7310:
case 0x7311:

View file

@ -5,6 +5,7 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999
void setup_battery(void);

View file

@ -34,9 +34,7 @@ static uint16_t LB_Charge_Power_Limit = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Cell_Max_Voltage = 3700;
static uint16_t LB_Cell_Min_Voltage = 3700;
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static uint16_t LB_MaxChargeAllowed_W = 0;
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
static uint8_t GVI_Pollcounter = 0;
static uint8_t LB_EOCR = 0;
@ -130,27 +128,12 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, (LB_Cell_Max_Voltage / 20));
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, (LB_Cell_Min_Voltage / 20));
}
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter:");
@ -189,14 +172,16 @@ void update_values_battery() { //This function maps all the values fetched via
void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x155: //BMS1
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
case 0x155: //BMS1
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
LB_MaxChargeAllowed_W = (rx_frame.data.u8[0] * 300);
LB_Current = word((rx_frame.data.u8[1] & 0xF), rx_frame.data.u8[2]) * 0.25 - 500; //OK!
LB_SOC = ((rx_frame.data.u8[4] << 8) | (rx_frame.data.u8[5])) * 0.0025; //OK!
break;
case 0x424: //BMS2
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
case 0x424: //BMS2
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
LB_EOCR = (rx_frame.data.u8[0] & 0x03);
LB_HVBUV = (rx_frame.data.u8[0] & 0x0C) >> 2;
LB_HVBIR = (rx_frame.data.u8[0] & 0x30) >> 4;
@ -212,11 +197,13 @@ void receive_can_battery(CAN_frame_t rx_frame) {
LB_MAX_TEMPERATURE = ((rx_frame.data.u8[7]) - 40); //OK!
break;
case 0x425:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
LB_kWh_Remaining = word((rx_frame.data.u8[0] & 0x1), rx_frame.data.u8[1]) / 10; //OK!
break;
case 0x445:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
LB_Cell_Max_Voltage = 1000 + word((rx_frame.data.u8[3] & 0x1), rx_frame.data.u8[4]) * 10; //OK!
LB_Cell_Min_Voltage = 1000 + (word(rx_frame.data.u8[5], rx_frame.data.u8[6]) >> 7) * 10; //OK!
@ -227,7 +214,8 @@ void receive_can_battery(CAN_frame_t rx_frame) {
}
break;
case 0x7BB:
CANstillAlive = 12; //Indicate that we are still getting CAN messages from the BMS
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //Indicate that we are still getting CAN messages from the BMS
if (rx_frame.data.u8[0] == 0x10) { //1st response Bytes 0-7
GVB_79B_Continue = true;

View file

@ -7,8 +7,7 @@
#include "RENAULT-ZOE-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint16_t LB_SOC = 50;
static uint16_t LB_SOH = 99;
static int16_t LB_MIN_TEMPERATURE = 0;
@ -21,7 +20,6 @@ static int32_t LB_Current = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Cell_Max_Voltage = 3700;
static uint16_t LB_Cell_Min_Voltage = 3700;
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static uint32_t LB_Battery_Voltage = 3700;
static uint8_t LB_Discharge_Power_Limit_Byte1 = 0;
@ -65,27 +63,12 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_max_voltage_mV;
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter:");
@ -122,7 +105,7 @@ void update_values_battery() { //This function maps all the values fetched via
}
void receive_can_battery(CAN_frame_t rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.MsgID) {
case 0x42E: //HV SOC & Battery Temp & Charging Power
break;

View file

@ -5,11 +5,9 @@
#define BATTERY_SELECTED
#define ABSOLUTE_CELL_MAX_VOLTAGE \
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_CELL_MIN_VOLTAGE \
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
#define MAX_CELL_DEVIATION_MV 500
void setup_battery(void);

View file

@ -18,7 +18,6 @@ TODO: Map all values from battery CAN messages
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static int SOC_1 = 0;
static int SOC_2 = 0;
@ -77,21 +76,13 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_max_dC;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive = 12;
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.MsgID) {
case 0x200:
break;

View file

@ -5,6 +5,7 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 250
uint8_t CalculateCRC8(CAN_frame_t rx_frame);
void setup_battery(void);

View file

@ -4,6 +4,9 @@
#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H
#define BATTERY_SELECTED
#ifndef MAX_CELL_DEVIATION_MV
#define MAX_CELL_DEVIATION_MV 9999
#endif
#include "../include.h"
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"

View file

@ -10,7 +10,6 @@
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
static unsigned long previousMillis30 = 0; // will store last time a 30ms CAN Message was send
static uint8_t stillAliveCAN = 6; //counter for checking if CAN is still alive
CAN_frame_t TESLA_221_1 = {
.FIR = {.B =
@ -232,14 +231,6 @@ void update_values_battery() { //This function maps all the values fetched via
/* Value mapping is completed. Start to check all safeties */
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!stillAliveCAN) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
stillAliveCAN--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
set_event(EVENT_INTERNAL_OPEN_FAULT, 0);
} else {
@ -513,7 +504,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
output_current = (((rx_frame.data.u8[4] & 0x0F) << 8) | rx_frame.data.u8[3]) / 100;
break;
case 0x292:
stillAliveCAN = 12; //We are getting CAN messages from the BMS, set the CAN detect counter
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; //We are getting CAN messages from the BMS
bat_beginning_of_life = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[5]);
soc_min = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[0]);
soc_vi = (((rx_frame.data.u8[2] & 0x0F) << 6) | ((rx_frame.data.u8[1] & 0xFC) >> 2));

View file

@ -8,6 +8,7 @@
//#define LFP_CHEMISTRY // Enable this line to startup in LFP mode
#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define MAX_CELL_DEVIATION_MV 9999 // Handled inside the Tesla.cpp file, just for compilation
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
#define MAXCHARGEPOWERALLOWED 15000 // 15000W we use a define since the value supplied by Tesla is always 0

View file

@ -16,6 +16,7 @@ void print_units(char* header, int value, char* units) {
}
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
datalayer.battery.status.real_soc = 5000; // 50.00%
datalayer.battery.status.soh_pptt = 9900; // 99.00%
@ -46,6 +47,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
}
//Fake that we get CAN messages
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("FAKE Values going to inverter");
@ -62,7 +66,9 @@ void update_values_battery() { /* This function puts fake values onto the parame
#endif
}
void receive_can_battery(CAN_frame_t rx_frame) { // All CAN messages recieved will be logged via serial
void receive_can_battery(CAN_frame_t rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.MsgID, HEX);

View file

@ -3,6 +3,7 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999
void setup_battery(void);

View file

@ -9,11 +9,9 @@
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define MAX_CELL_VOLTAGE 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
static float BATT_U = 0; //0x3A
static float MAX_U = 0; //0x3A
@ -26,16 +24,14 @@ static float BATT_T_MIN = 0; //0x413
static float BATT_T_AVG = 0; //0x413
static uint16_t SOC_BMS = 0; //0X37D
static uint16_t SOC_CALC = 0;
static uint16_t CELL_U_MAX = 0; //0x37D
static uint16_t CELL_U_MIN = 0; //0x37D
static uint8_t CELL_ID_U_MAX = 0; //0x37D
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
static uint16_t CELL_U_MAX = 0; //0x37D
static uint16_t CELL_U_MIN = 0; //0x37D
static uint8_t CELL_ID_U_MAX = 0; //0x37D
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
static uint8_t batteryModuleNumber = 0x10; // First battery module
static uint8_t battery_request_idx = 0;
static uint8_t rxConsecutiveFrames = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint16_t cell_deviation_mV = 0; //contains the deviation between highest and lowest cell in mV
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint8_t cellcounter = 0;
static uint32_t remaining_capacity = 0;
static uint16_t cell_voltages[108]; //array with all the cellvoltages
@ -114,14 +110,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
}
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
#ifdef DEBUG_VIA_USB
Serial.print("BMS reported SOC%: ");
Serial.println(SOC_BMS);
@ -159,8 +147,6 @@ void update_values_battery() { //This function maps all the values fetched via
Serial.println(min_max_voltage[1]);
Serial.print("Lowest cell voltage: ");
Serial.println(min_max_voltage[0]);
Serial.print("Cell deviation voltage: ");
Serial.println(cell_deviation_mV);
Serial.print("Cell voltage,");
while (cnt < 108) {
Serial.print(cell_voltages[cnt++]);
@ -171,7 +157,7 @@ void update_values_battery() { //This function maps all the values fetched via
}
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive = 12;
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.MsgID) {
case 0x3A:
if ((rx_frame.data.u8[6] & 0x80) == 0x80)
@ -314,12 +300,6 @@ void receive_can_battery(CAN_frame_t rx_frame) {
min_max_voltage[1] = cell_voltages[cellcounter];
}
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
}
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}

View file

@ -5,6 +5,7 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 250
void setup_battery(void);

View file

@ -30,6 +30,8 @@ typedef struct {
typedef struct {
/** int32_t */
/** Instantaneous battery power in Watts */
/* Positive value = Battery Charging */
/* Negative value = Battery Discharging */
int32_t active_power_W;
/** uint32_t */
@ -68,6 +70,14 @@ typedef struct {
* battery.settings.soc_scaling_active
*/
uint16_t reported_soc;
/** A counter that increases incase a CAN CRC read error occurs */
uint16_t CAN_error_counter;
/** uint8_t */
/** A counter set each time a new message comes from battery.
* This value then gets decremented each 5 seconds. Incase we reach 0
* we report the battery as missing entirely on the CAN bus.
*/
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
/** Other */
/** The current BMS status */

View file

@ -3,11 +3,10 @@
#include "../../../USER_SETTINGS.h"
/* Select HW - DONT TOUCH */
#define HW_LILYGO
#if defined(HW_LILYGO)
#include "hw_lilygo.h"
#elif defined(HW_STARK)
#include "hw_stark.h"
#elif defined(HW_SJB_V1)
#include "hw_sjb_v1.h"
#endif

View file

@ -40,6 +40,13 @@
#define MCP2517_CS 18 // CS input of MCP2517
#define MCP2517_INT 35 // INT output of MCP2517
// CHAdeMO support pin dependencies
#define CHADEMO_PIN_2 12
#define CHADEMO_PIN_10 5
#define CHADEMO_PIN_7 34
#define CHADEMO_PIN_4 35
#define CHADEMO_LOCK 18
// Contactor handling
#define POSITIVE_CONTACTOR_PIN 32
#define NEGATIVE_CONTACTOR_PIN 33
@ -62,4 +69,10 @@
#error Multiple HW defined! Please select a single HW
#endif
#ifdef CHADEMO_BATTERY
#ifdef DUAL_CAN
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#endif

View file

@ -0,0 +1,66 @@
#ifndef __HW_STARK06_H__
#define __HW_STARK06_H__
// Board boot-up time
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
// Core assignment
#define CORE_FUNCTION_CORE 1
#define MODBUS_CORE 0
#define WIFI_CORE 0
// RS485
// #define PIN_5V_EN 16 // No function, GPIO 16 used instead as MCP_SCK
// #define RS485_EN_PIN 17 // RE, No function, GPIO 17 is instead available as extra GPIO via pin header
#define RS485_TX_PIN 22
#define RS485_RX_PIN 21
// #define RS485_SE_PIN 19 // No function, GPIO 19 is instead available as extra GPIO via pin header
// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings
#define CAN_1_TYPE ESP32CAN
// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB
#define CAN_TX_PIN GPIO_NUM_27
#define CAN_RX_PIN GPIO_NUM_26
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
// CAN2 defines below
// DUAL_CAN defines
//#define MCP2515_SCK 12 // SCK input of MCP2515
//#define MCP2515_MOSI 5 // SDI input of MCP2515
//#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
//#define MCP2515_CS 18 // CS input of MCP2515
//#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CAN_FD defines
#define MCP2517_SCK 16 // SCK input of MCP2517 (Changed from 12 to 16 since 12 can be used for JTAG TDI)
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517
#define MCP2517_CS 18 // CS input of MCP2517
#define MCP2517_INT 35 // INT output of MCP2517
// Contactor handling
#define POSITIVE_CONTACTOR_PIN 32
#define NEGATIVE_CONTACTOR_PIN 33
#define PRECHARGE_PIN 25
#define BMS_POWER 23 // Also connected to MCP_SCK
// SD card
//#define SD_MISO_PIN 2
//#define SD_MOSI_PIN 15
//#define SD_SCLK_PIN 14
//#define SD_CS_PIN 13
// LED
#define LED_PIN 4
#define LED_MAX_BRIGHTNESS 40
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED
#define HW_CONFIGURED
#else
#error Multiple HW defined! Please select a single HW
#endif
#endif

View file

@ -0,0 +1,84 @@
#include "../../datalayer/datalayer.h"
#include "../utils/events.h"
static uint16_t cell_deviation_mV = 0;
void update_machineryprotection() {
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
// Battery is overheated!
if (datalayer.battery.status.temperature_max_dC > 500) {
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
} else {
clear_event(EVENT_BATTERY_OVERHEAT);
}
// Battery is frozen!
if (datalayer.battery.status.temperature_min_dC < -250) {
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
} else {
clear_event(EVENT_BATTERY_FROZEN);
}
// Battery voltage is over designed max voltage!
if (datalayer.battery.status.voltage_dV > datalayer.battery.info.max_design_voltage_dV) {
set_event(EVENT_BATTERY_OVERVOLTAGE, datalayer.battery.status.voltage_dV);
} else {
clear_event(EVENT_BATTERY_OVERVOLTAGE);
}
// Battery voltage is under designed min voltage!
if (datalayer.battery.status.voltage_dV < datalayer.battery.info.min_design_voltage_dV) {
set_event(EVENT_BATTERY_UNDERVOLTAGE, datalayer.battery.status.voltage_dV);
} else {
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
}
// Battery is extremely degraded, not fit for secondlifestorage!
if (datalayer.battery.status.soh_pptt < 2500) {
set_event(EVENT_LOW_SOH, datalayer.battery.status.soh_pptt);
} else {
clear_event(EVENT_LOW_SOH);
}
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
// Inverter is charging with more power than battery wants!
if (datalayer.battery.status.active_power_W > 0) { // Charging
if (datalayer.battery.status.active_power_W > (datalayer.battery.status.max_charge_power_W + 2000)) {
set_event(EVENT_CHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
} else {
clear_event(EVENT_CHARGE_LIMIT_EXCEEDED);
}
}
// Inverter is pulling too much power from battery!
if (datalayer.battery.status.active_power_W < 0) { // Discharging
if (-datalayer.battery.status.active_power_W > (datalayer.battery.status.max_discharge_power_W + 2000)) {
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // Alert when 2kW over requested max
} else {
clear_event(EVENT_DISCHARGE_LIMIT_EXCEEDED);
}
}
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
if (!datalayer.battery.status.CAN_battery_still_alive) {
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
datalayer.battery.status.CAN_battery_still_alive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
// Too many malformed CAN messages recieved!
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
set_event(EVENT_CAN_RX_WARNING, 0);
} else {
clear_event(EVENT_CAN_RX_WARNING);
}
}

View file

@ -0,0 +1,9 @@
#ifndef SAFETY_H
#define SAFETY_H
#include <Arduino.h>
#define MAX_CAN_FAILURES 50
void update_machineryprotection();
#endif

View file

@ -139,15 +139,21 @@ void init_events(void) {
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_CAUTION].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_CHG_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_OVERHEAT].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_OVERVOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_PRECHARGE_FAILURE].level = EVENT_LEVEL_INFO;
@ -209,6 +215,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
case EVENT_CAN_TX_FAILURE:
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
case EVENT_CHARGE_LIMIT_EXCEEDED:
return "Info: Inverter is charging faster than battery is allowing.";
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
return "Info: Inverter is discharging faster than battery is allowing.";
case EVENT_WATER_INGRESS:
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
case EVENT_12V_LOW:
@ -221,6 +231,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Info: Battery is completely discharged";
case EVENT_BATTERY_FULL:
return "Info: Battery is fully charged";
case EVENT_BATTERY_FROZEN:
return "Info: Battery is too cold to operate optimally. Consider warming it up!";
case EVENT_BATTERY_CAUTION:
return "Info: Battery has raised a general caution flag. Might want to inspect it closely.";
case EVENT_BATTERY_CHG_STOP_REQ:
@ -233,6 +245,12 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Info: COLD BATTERY! Battery requesting heating pads to activate!";
case EVENT_BATTERY_WARMED_UP:
return "Info: Battery requesting heating pads to stop. The battery is now warm enough.";
case EVENT_BATTERY_OVERHEAT:
return "ERROR: Battery overheated. Shutting down to prevent thermal runaway!";
case EVENT_BATTERY_OVERVOLTAGE:
return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
case EVENT_BATTERY_UNDERVOLTAGE:
return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!";
case EVENT_LOW_SOH:
return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle "
"battery.";

View file

@ -6,7 +6,7 @@
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
#define EE_MAGIC_HEADER_VALUE 0x0005 // 0x0000 to 0xFFFF
#define EE_MAGIC_HEADER_VALUE 0x0006 // 0x0000 to 0xFFFF
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
@ -32,16 +32,22 @@
XX(EVENT_CANFD_RX_FAILURE) \
XX(EVENT_CAN_RX_WARNING) \
XX(EVENT_CAN_TX_FAILURE) \
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
XX(EVENT_WATER_INGRESS) \
XX(EVENT_12V_LOW) \
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
XX(EVENT_BATTERY_EMPTY) \
XX(EVENT_BATTERY_FULL) \
XX(EVENT_BATTERY_FROZEN) \
XX(EVENT_BATTERY_CAUTION) \
XX(EVENT_BATTERY_CHG_STOP_REQ) \
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
XX(EVENT_BATTERY_OVERHEAT) \
XX(EVENT_BATTERY_OVERVOLTAGE) \
XX(EVENT_BATTERY_UNDERVOLTAGE) \
XX(EVENT_BATTERY_REQUESTS_HEAT) \
XX(EVENT_BATTERY_WARMED_UP) \
XX(EVENT_LOW_SOH) \

View file

@ -28,6 +28,7 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
#define INTERVAL_100_MS_DELAYED 120
#define INTERVAL_500_MS_DELAYED 550
#define MAX_CAN_FAILURES 500 // Amount of malformed CAN messages to allow before raising a warning
#define CAN_STILL_ALIVE \
12 // Set by battery each time we get a CAN message. Decrements every 5seconds. Incase we reach 0 (after 60 seconds of inactivity)
#endif

View file

@ -3,7 +3,7 @@
#include "../../datalayer/datalayer.h"
String cellmonitor_processor(const String& var) {
if (var == "ABC") {
if (var == "X") {
String content = "";
// Page format
content += "<style>";

View file

@ -13,7 +13,7 @@ const char EVENTS_HTML_END[] = R"=====(
)=====";
String events_processor(const String& var) {
if (var == "ABC") {
if (var == "X") {
String content = "";
content.reserve(5000);
// Page format

View file

@ -1,5 +1,5 @@
const char index_html[] = R"rawliteral(
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style><h2>Battery Emulator</h2>%ABC%
<!doctypehtml><title>Battery Emulator</title><meta content="width=device-width"name=viewport><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}body{max-width:800px;margin:0 auto}</style>%X%
)rawliteral";
/* The above code is minified (https://kangax.github.io/html-minifier/) to increase performance. Here is the full HTML function:
@ -14,8 +14,7 @@ const char index_html[] = R"rawliteral(
</style>
</head>
<body>
<h2>Battery Emulator</h2>
%ABC%
%X%
</body>
</html>
*/

View file

@ -3,7 +3,7 @@
#include "../../datalayer/datalayer.h"
String settings_processor(const String& var) {
if (var == "ABC") {
if (var == "X") {
String content = "";
//Page format
content += "<style>";

View file

@ -30,7 +30,7 @@ bool ota_active = false;
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
unsigned const long MAX_WIFI_RETRY_INTERVAL = 30000; // Maximum wifi retry interval in ms
unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi retry interval in ms
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
@ -358,8 +358,9 @@ void init_ElegantOTA() {
}
String processor(const String& var) {
if (var == "ABC") {
if (var == "X") {
String content = "";
content += "<h2>" + String(ssidAP) + "</h2>"; // ssidAP name is used as header name
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";

View file

@ -8,6 +8,7 @@
#include "system_settings.h"
#include "devboard/hal/hal.h"
#include "devboard/safety/safety.h"
#include "devboard/utils/time_meas.h"
#include "devboard/utils/types.h"

View file

@ -0,0 +1,4 @@
http://www.digikey.com/short/3c2wwr
This digikey shopping cart contains all the connectors and pins
for the ISA IVT-1K-U3-TOI-CAN2-12 Current Sensor

View file

@ -0,0 +1,9 @@
# SimpleISA
Simple library for IVT shunts.
Based on the EVTV library of 2016, revised for use with CHAdeMO.
Originally intended to integrate with Arduino Due.
Adapted for ESP32 and ESP32-Arduino-CAN for use in the Battery-Emulator project https://github.com/dalathegreat/Battery-Emulator
hosted at https://github.com/smaresca/SimpleISA-ESP32-Arduino-CAN
Derived from https://github.com/isaac96/simpleISA/ and https://github.com/damienmaguire/SimpleISA/

View file

@ -0,0 +1,396 @@
/* This library supports ISA Scale IVT Modular current/voltage sensor device. These devices measure current, up to three voltages, and provide temperature compensation.
This library was written by Jack Rickard of EVtv - http://www.evtv.me
copyright 2014
You are licensed to use this library for any purpose, commercial or private,
without restriction.
2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
*/
#include "SimpleISA.h"
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; }
ISA::ISA() // Define the constructor.
{
timestamp = millis();
debug=false;
debug2=false;
framecount=0;
firstframe=true;
}
ISA::~ISA() //Define destructor
{
}
void ISA::begin(int Port, int speed)
{
}
void ISA::handleFrame(CAN_frame_t *frame)
//This is our CAN interrupt service routine to catch inbound frames
{
switch (frame->MsgID)
{
case 0x511:
break;
case 0x521:
handle521(frame);
break;
case 0x522:
handle522(frame);
break;
case 0x523:
handle523(frame);
break;
case 0x524:
handle524(frame);
break;
case 0x525:
handle525(frame);
break;
case 0x526:
handle526(frame);
break;
case 0x527:
handle527(frame);
break;
case 0x528:
handle528(frame);
break;
}
if(debug)printCAN(frame);
}
void ISA::handle521(CAN_frame_t *frame) //AMperes
{
framecount++;
long current=0;
current = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
milliamps=current;
Amperes=current/1000.0f;
if(debug2)Serial<<"Current: "<<Amperes<<" amperes "<<milliamps<<" ma frames:"<<framecount<<"\n";
}
void ISA::handle522(CAN_frame_t *frame) //Voltage
{
framecount++;
long volt=0;
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage=volt/1000.0f;
Voltage1=Voltage-(Voltage2+Voltage3);
if(framecount<150)
{
VoltageLO=Voltage;
Voltage1LO=Voltage1;
}
if(Voltage<VoltageLO && framecount>150)VoltageLO=Voltage;
if(Voltage>VoltageHI && framecount>150)VoltageHI=Voltage;
if(Voltage1<Voltage1LO && framecount>150)Voltage1LO=Voltage1;
if(Voltage1>Voltage1HI && framecount>150)Voltage1HI=Voltage1;
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 1: "<<Voltage1<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
}
void ISA::handle523(CAN_frame_t *frame) //Voltage2
{
framecount++;
long volt=0;
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage2=volt/1000.0f;
if(Voltage2>3)Voltage2-=Voltage3;
if(framecount<150)Voltage2LO=Voltage2;
if(Voltage2<Voltage2LO && framecount>150)Voltage2LO=Voltage2;
if(Voltage2>Voltage2HI&& framecount>150)Voltage2HI=Voltage2;
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 2: "<<Voltage2<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
}
void ISA::handle524(CAN_frame_t *frame) //Voltage3
{
framecount++;
long volt=0;
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage3=volt/1000.0f;
if(framecount<150)Voltage3LO=Voltage3;
if(Voltage3<Voltage3LO && framecount>150 && Voltage3>10)Voltage3LO=Voltage3;
if(Voltage3>Voltage3HI && framecount>150)Voltage3HI=Voltage3;
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 3: "<<Voltage3<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
}
void ISA::handle525(CAN_frame_t *frame) //Temperature
{
framecount++;
long temp=0;
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Temperature=temp/10;
if(debug2)Serial<<"Temperature: "<<Temperature<<" C frames:"<<framecount<<"\n";
}
void ISA::handle526(CAN_frame_t *frame) //Kilowatts
{
framecount++;
watt=0;
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
KW=watt/1000.0f;
if(debug2)Serial<<"Power: "<<watt<<" Watts "<<KW<<" kW frames:"<<framecount<<"\n";
}
void ISA::handle527(CAN_frame_t *frame) //Ampere-Hours
{
framecount++;
As=0;
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
AH+=(As-lastAs)/3600.0f;
lastAs=As;
if(debug2)Serial<<"Amphours: "<<AH<<" Ampseconds: "<<As<<" frames:"<<framecount<<"\n";
}
void ISA::handle528(CAN_frame_t *frame) //kiloWatt-hours
{
framecount++;
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
KWH+=(wh-lastWh)/1000.0f;
lastWh=wh;
if(debug2)Serial<<"KiloWattHours: "<<KWH<<" Watt Hours: "<<wh<<" frames:"<<framecount<<"\n";
}
void ISA::printCAN(CAN_frame_t *frame)
{
//This routine simply prints a timestamp and the contents of the
//incoming CAN message
milliseconds = (int) (millis()/1) %1000 ;
seconds = (int) (millis() / 1000) % 60 ;
minutes = (int) ((millis() / (1000*60)) % 60);
hours = (int) ((millis() / (1000*60*60)) % 24);
sprintf(buffer,"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
Serial<<buffer<<" ";
sprintf(bigbuffer,"%02X %02X %02X %02X %02X %02X %02X %02X %02X",
frame->MsgID, frame->data.u8[0],frame->data.u8[1],frame->data.u8[2],
frame->data.u8[3],frame->data.u8[4],frame->data.u8[5],frame->data.u8[6],frame->data.u8[7],0);
Serial<<"Rcvd ISA frame: 0x"<<bigbuffer<<"\n";
}
void ISA::initialize()
{
firstframe=false;
STOP();
delay(700);
for(int i=0;i<9;i++)
{
Serial.println("initialization \n");
outframe.data.u8[0]=(0x20+i);
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x02;
outframe.data.u8[3]=(0x60+(i*18));
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe);
delay(500);
sendSTORE();
delay(500);
}
// delay(500);
START();
delay(500);
lastAs=As;
lastWh=wh;
}
void ISA::STOP()
{
//SEND STOP///////
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug) {printCAN(&outframe);} //If the debug variable is set, show our transmitted frame
}
void ISA::sendSTORE()
{
//SEND STORE///////
outframe.data.u8[0]=0x32;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::START()
{
//SEND START///////
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x01;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::RESTART()
{
//Has the effect of zeroing AH and KWH
outframe.data.u8[0]=0x3F;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::deFAULT()
{
//Returns module to original defaults
outframe.data.u8[0]=0x3D;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::initCurrent()
{
STOP();
delay(500);
Serial.println("initialization \n");
outframe.data.u8[0]=(0x21);
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=(0x61);
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe);
delay(500);
sendSTORE();
delay(500);
// delay(500);
START();
delay(500);
lastAs=As;
lastWh=wh;
}

View file

@ -0,0 +1,104 @@
#ifndef SimpleISA_h
#define SimpleISA_h
/* This library supports the ISA Scale IVT Modular current/voltage sensor device. These devices measure current, up to three voltages, and provide temperature compensation.
This library was written by Jack Rickard of EVtv - http://www.evtv.me copyright 2016
You are licensed to use this library for any purpose, commercial or private,
without restriction.
*/
#include <Arduino.h>
#include "../miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
class ISA
{
public:
ISA();
~ISA();
void initialize();
void begin(int Port, int speed);
void initCurrent();
void sendSTORE();
void STOP();
void START();
void RESTART();
void deFAULT();
float Amperes; // Floating point with current in Amperes
double AH; //Floating point with accumulated ampere-hours
double KW;
double KWH;
double Voltage;
double Voltage1;
double Voltage2;
double Voltage3;
double VoltageHI;
double Voltage1HI;
double Voltage2HI;
double Voltage3HI;
double VoltageLO;
double Voltage1LO;
double Voltage2LO;
double Voltage3LO;
double Temperature;
bool debug;
bool debug2;
bool firstframe;
int framecount;
unsigned long timestamp;
double milliamps;
long watt;
long As;
long lastAs;
long wh;
long lastWh;
void handleFrame(CAN_frame_t *frame); // CAN handler
uint8_t page;
private:
CAN_frame_t frame;
unsigned long elapsedtime;
double ampseconds;
int milliseconds ;
int seconds;
int minutes;
int hours;
char buffer[9];
char bigbuffer[90];
uint32_t inbox;
CAN_frame_t outframe = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x411,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void printCAN(CAN_frame_t *frame);
void handle521(CAN_frame_t *frame);
void handle522(CAN_frame_t *frame);
void handle523(CAN_frame_t *frame);
void handle524(CAN_frame_t *frame);
void handle525(CAN_frame_t *frame);
void handle526(CAN_frame_t *frame);
void handle527(CAN_frame_t *frame);
void handle528(CAN_frame_t *frame);
};
#endif /* SimpleISA_h */

View file

@ -0,0 +1,188 @@
#include <due_can.h>
#include "variant.h"
#include <SimpleISA.h>
#define Serial SerialUSB //Use native port
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; } //Allow streaming
float Version=2.00;
uint16_t loopcount=0;
unsigned long startime=0;
unsigned long elapsedtime=0;
uint port=0;
uint16_t datarate=500;
ISA Sensor; //Instantiate ISA Module Sensor object to measure current and voltage
void setup()
{
Serial.begin(115200);
Sensor.begin(port,datarate); //Start ISA object on CAN 0 at 500 kbps
Serial<<"\nISA Scale Startup Successful \n";
printMenu();
}
void loop()
{
if(loopcount++==40000)
{
printStatus();
loopcount-0;
}
checkforinput(); //Check keyboard for user input
}
void printStatus()
{
char buffer[40];
//printimestamp();
sprintf(buffer,"%4.2f",Sensor.Voltage);
Serial<<"Volt:"<<buffer<<"v ";
sprintf(buffer,"%4.2f",Sensor.Voltage1);
Serial<<"V1:"<<buffer<<"v ";
sprintf(buffer,"%4.2f",Sensor.Voltage2);
Serial<<"V2:"<<buffer<<"v ";
sprintf(buffer,"%4.2f",Sensor.Voltage3);
Serial<<"V3:"<<buffer<<"v ";
sprintf(buffer,"%4.3f",Sensor.Amperes);
Serial<<"Amps:"<<buffer<<"A ";
sprintf(buffer,"%4.3f",Sensor.KW);
Serial<<buffer<<"kW ";
sprintf(buffer,"%4.3f",Sensor.AH);
Serial<<buffer<<"Ah ";
sprintf(buffer,"%4.3f",Sensor.KWH);
Serial<<buffer<<"kWh";
sprintf(buffer,"%4.0f",Sensor.Temperature);
Serial<<buffer<<"C ";
Serial<<"Frame:"<<Sensor.framecount<<" \n";
}
void printimestamp()
{
//Prints a timestamp to the serial port
elapsedtime=millis() - startime;
int milliseconds = (elapsedtime/1) %1000 ;
int seconds = (elapsedtime / 1000) % 60 ;
int minutes = ((elapsedtime / (1000*60)) % 60);
int hours = ((elapsedtime / (1000*60*60)) % 24);
char buffer[19];
sprintf(buffer,"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
Serial<<buffer<<" ";
}
void printMenu()
{
Serial<<"\f\n=========== ISA Scale Sample Program Version "<<Version<<" ==============\n************ List of Available Commands ************\n\n";
Serial<<" ? - Print this menu\n ";
Serial<<" d - toggles Debug off and on to print recieved CAN data traffic\n";
Serial<<" D - toggles Debug2 off and on to print derived values\n";
Serial<<" f - zero frame count\n ";
Serial<<" i - initialize new sensor\n ";
Serial<<" p - Select new CAN port\n ";
Serial<<" r - Set new datarate\n ";
Serial<<" z - zero ampere-hours\n ";
Serial<<"**************************************************************\n==============================================================\n\n";
}
void checkforinput()
{
//Checks for keyboard input from Native port
if (Serial.available())
{
int inByte = Serial.read();
switch (inByte)
{
case 'z': //Zeroes ampere-hours
Sensor.KWH=0;
Sensor.AH=0;
Sensor.RESTART();
break;
case 'p':
getPort();
break;
case 'r':
getRate();
break;
case 'f':
Sensor.framecount=0;
break;
case 'd': //Causes ISA object to print incoming CAN messages for debugging
Sensor.debug=!Sensor.debug;
break;
case 'D': //Causes ISA object to print derived values for debugging
Sensor.debug2=!Sensor.debug2;
break;
case 'i':
Sensor.initialize();
break;
case '?': //Print a menu describing these functions
printMenu();
break;
case '1':
Sensor.STOP();
break;
case '3':
Sensor.START();
break;
}
}
}
void getRate()
{
Serial<<"\n Enter the Data Rate in Kbps you want for CAN : ";
while(Serial.available() == 0){}
float V = Serial.parseFloat();
if(V>0)
{
Serial<<"Datarate:"<<V<<"\n\n";
uint8_t rate=V;
datarate=V*1000;
Sensor.begin(port,datarate);
}
}
void getPort()
{
Serial<<"\n Enter port selection: c0=CAN0 c1=CAN1 ";
while(Serial.available() == 0){}
int P = Serial.parseInt();
if(P>1) Serial<<"Entry out of range, enter 0 or 1 \n";
else
{
port=P;
Sensor.begin(port,datarate);
}
}