Initial hal changes

This commit is contained in:
Jaakko Haakana 2025-06-22 23:30:11 +03:00
parent d6cbd1bcdd
commit 12b402f994
77 changed files with 924 additions and 672 deletions

View file

@ -70,6 +70,12 @@ Logging logging;
// Initialization // Initialization
void setup() { void setup() {
init_hal();
if (!led_init()) {
return;
}
init_serial(); init_serial();
// We print this after setting up serial, such that is also printed to serial with DEBUG_VIA_USB set. // We print this after setting up serial, such that is also printed to serial with DEBUG_VIA_USB set.
@ -89,23 +95,32 @@ void setup() {
&logging_loop_task, WIFI_CORE); &logging_loop_task, WIFI_CORE);
#endif #endif
init_CAN(); if (!init_CAN()) {
return;
}
init_contactors(); if (!init_contactors()) {
return;
}
#ifdef PRECHARGE_CONTROL if (!init_precharge_control()) {
init_precharge_control(); return;
#endif // PRECHARGE_CONTROL }
setup_charger(); setup_charger();
setup_inverter();
if (!setup_inverter()) {
return;
}
setup_battery(); setup_battery();
init_rs485(); if (!init_rs485()) {
return;
}
#ifdef EQUIPMENT_STOP_BUTTON if (!init_equipment_stop_button()) {
init_equipment_stop_button(); return;
#endif }
setup_can_shunt(); setup_can_shunt();
// BOOT button at runtime is used as an input for various things // BOOT button at runtime is used as an input for various things
@ -116,7 +131,7 @@ void setup() {
// Initialize Task Watchdog for subscribed tasks // Initialize Task Watchdog for subscribed tasks
esp_task_wdt_config_t wdt_config = { esp_task_wdt_config_t wdt_config = {
.timeout_ms = INTERVAL_5_S, // If task hangs for longer than this, reboot .timeout_ms = INTERVAL_5_S, // If task hangs for longer than this, reboot
.idle_core_mask = (1 << CORE_FUNCTION_CORE) | (1 << WIFI_CORE), // Watch both cores .idle_core_mask = (1 << esp32hal->CORE_FUNCTION_CORE()) | (1 << esp32hal->WIFI_CORE()), // Watch both cores
.trigger_panic = true // Enable panic reset on timeout .trigger_panic = true // Enable panic reset on timeout
}; };
@ -126,11 +141,11 @@ void setup() {
init_mqtt(); init_mqtt();
xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task, xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task,
WIFI_CORE); esp32hal->WIFI_CORE());
#endif #endif
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task, xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, NULL, TASK_CORE_PRIO, &main_loop_task,
CORE_FUNCTION_CORE); esp32hal->CORE_FUNCTION_CORE());
#ifdef PERIODIC_BMS_RESET_AT #ifdef PERIODIC_BMS_RESET_AT
bmsResetTimeOffset = getTimeOffsetfromNowUntil(PERIODIC_BMS_RESET_AT); bmsResetTimeOffset = getTimeOffsetfromNowUntil(PERIODIC_BMS_RESET_AT);
if (bmsResetTimeOffset == 0) { if (bmsResetTimeOffset == 0) {
@ -213,15 +228,13 @@ void core_loop(void*) {
esp_task_wdt_add(NULL); // Register this task with WDT esp_task_wdt_add(NULL); // Register this task with WDT
TickType_t xLastWakeTime = xTaskGetTickCount(); TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1); // Convert 1ms to ticks const TickType_t xFrequency = pdMS_TO_TICKS(1); // Convert 1ms to ticks
led_init();
while (true) { while (true) {
START_TIME_MEASUREMENT(all); START_TIME_MEASUREMENT(all);
START_TIME_MEASUREMENT(comm); START_TIME_MEASUREMENT(comm);
#ifdef EQUIPMENT_STOP_BUTTON
monitor_equipment_stop_button(); monitor_equipment_stop_button();
#endif
// Input, Runs as fast as possible // Input, Runs as fast as possible
receive_can(); // Receive CAN messages receive_can(); // Receive CAN messages
@ -237,7 +250,8 @@ void core_loop(void*) {
// Process // Process
currentMillis = millis(); currentMillis = millis();
if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) { if (currentMillis - previousMillis10ms >= INTERVAL_10_MS) {
if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) { if ((currentMillis - previousMillis10ms >= INTERVAL_10_MS_DELAYED) &&
(milliseconds(currentMillis) > esp32hal->BOOTUP_TIME())) {
set_event(EVENT_TASK_OVERRUN, (currentMillis - previousMillis10ms)); set_event(EVENT_TASK_OVERRUN, (currentMillis - previousMillis10ms));
} }
previousMillis10ms = currentMillis; previousMillis10ms = currentMillis;

View file

@ -52,13 +52,6 @@ const char* ha_device_id =
#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME #endif // MQTT_MANUAL_TOPIC_OBJECT_NAME
#endif // USE_MQTT #endif // USE_MQTT
#ifdef EQUIPMENT_STOP_BUTTON
// Equipment stop button behavior. Use NC button for safety reasons.
//LATCHING_SWITCH - Normally closed (NC), latching switch. When pressed it activates e-stop
//MOMENTARY_SWITCH - Short press to activate e-stop, long 15s press to deactivate. E-stop is persistent between reboots
volatile STOP_BUTTON_BEHAVIOR equipment_stop_behavior = LATCHING_SWITCH;
#endif
/* Charger settings (Optional, when using generator charging) */ /* Charger settings (Optional, when using generator charging) */
volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cell charging of a 96s pack volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cell charging of a 96s pack
volatile float CHARGER_MAX_HV = 420; // Max permissible output (VDC) of charger volatile float CHARGER_MAX_HV = 420; // Max permissible output (VDC) of charger

View file

@ -9,6 +9,8 @@
/* There are also some options for battery limits and extra functionality */ /* There are also some options for battery limits and extra functionality */
/* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/ /* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/
#define COMMON_IMAGE
/* Select battery used */ /* Select battery used */
//#define BMW_I3_BATTERY //#define BMW_I3_BATTERY
//#define BMW_IX_BATTERY //#define BMW_IX_BATTERY
@ -70,18 +72,18 @@
//#define SUNGROW_CAN //Enable this line to emulate a "Sungrow SBR064" over CAN bus //#define SUNGROW_CAN //Enable this line to emulate a "Sungrow SBR064" over CAN bus
/* Select hardware used for Battery-Emulator */ /* Select hardware used for Battery-Emulator */
//#define HW_LILYGO #define HW_LILYGO
//#define HW_STARK //#define HW_STARK
//#define HW_3LB //#define HW_3LB
//#define HW_DEVKIT //#define HW_DEVKIT
/* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */ /* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */
#define PRECHARGE_TIME_MS 500 //Precharge time in milliseconds. Modify to suit your inverter (See wiki for more info) #define PRECHARGE_TIME_MS 500 //Precharge time in milliseconds. Modify to suit your inverter (See wiki for more info)
//#define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins) #define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins)
//#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins) //#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins)
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled. //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled.
//#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting! //#define NC_CONTACTORS //Enable this line to control normally closed contactors. CONTACTOR_CONTROL must be enabled for this option. Extremely rare setting!
//#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF #define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF
//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF //#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF
// PERIODIC_BMS_RESET_AT Uses NTP server, internet required. In 24 Hour format WITHOUT leading 0. e.g 0230 should be 230. Time Zone is set in USER_SETTINGS.cpp // PERIODIC_BMS_RESET_AT Uses NTP server, internet required. In 24 Hour format WITHOUT leading 0. e.g 0230 should be 230. Time Zone is set in USER_SETTINGS.cpp
//#define PERIODIC_BMS_RESET_AT 525 //#define PERIODIC_BMS_RESET_AT 525
@ -101,6 +103,7 @@
//#define EQUIPMENT_STOP_BUTTON // Enable this to allow an equipment stop button connected to the Battery-Emulator to disengage the battery //#define EQUIPMENT_STOP_BUTTON // Enable this to allow an equipment stop button connected to the Battery-Emulator to disengage the battery
//#define LFP_CHEMISTRY //Tesla specific setting, enable this line to startup in LFP mode //#define LFP_CHEMISTRY //Tesla specific setting, enable this line to startup in LFP mode
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
//#define LOG_TO_SD //Enable this line to log diagnostic data to SD card (WARNING, raises CPU load, do not use for production) //#define LOG_TO_SD //Enable this line to log diagnostic data to SD card (WARNING, raises CPU load, do not use for production)
//#define LOG_CAN_TO_SD //Enable this line to log incoming/outgoing CAN & CAN-FD messages to SD card (WARNING, raises CPU load, do not use for production) //#define LOG_CAN_TO_SD //Enable this line to log incoming/outgoing CAN & CAN-FD messages to SD card (WARNING, raises CPU load, do not use for production)
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production) //#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
@ -193,9 +196,16 @@ extern volatile float CHARGER_END_A;
extern volatile unsigned long long bmsResetTimeOffset; extern volatile unsigned long long bmsResetTimeOffset;
#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h"
// Equipment stop button behavior. Use NC button for safety reasons.
//LATCHING_SWITCH - Normally closed (NC), latching switch. When pressed it activates e-stop
//MOMENTARY_SWITCH - Short press to activate e-stop, long 15s press to deactivate. E-stop is persistent between reboots
#ifdef EQUIPMENT_STOP_BUTTON #ifdef EQUIPMENT_STOP_BUTTON
typedef enum { LATCHING_SWITCH = 0, MOMENTARY_SWITCH = 1 } STOP_BUTTON_BEHAVIOR; const STOP_BUTTON_BEHAVIOR stop_button_default_behavior = STOP_BUTTON_BEHAVIOR::MOMENTARY_SWITCH;
extern volatile STOP_BUTTON_BEHAVIOR equipment_stop_behavior; #else
const STOP_BUTTON_BEHAVIOR stop_button_default_behavior = STOP_BUTTON_BEHAVIOR::NOT_CONNECTED;
#endif #endif
#ifdef WIFICONFIG #ifdef WIFICONFIG

View file

@ -35,10 +35,8 @@ extern const char* name_for_battery_type(BatteryType type) {
return BydAttoBattery::Name; return BydAttoBattery::Name;
case BatteryType::CellPowerBms: case BatteryType::CellPowerBms:
return CellPowerBms::Name; return CellPowerBms::Name;
#ifdef CHADEMO_PIN_2 // Only support chademo for certain platforms
case BatteryType::Chademo: case BatteryType::Chademo:
return ChademoBattery::Name; return ChademoBattery::Name;
#endif
case BatteryType::CmfaEv: case BatteryType::CmfaEv:
return CmfaEvBattery::Name; return CmfaEvBattery::Name;
case BatteryType::Foxess: case BatteryType::Foxess:
@ -124,10 +122,8 @@ Battery* create_battery(BatteryType type) {
return new BydAttoBattery(); return new BydAttoBattery();
case BatteryType::CellPowerBms: case BatteryType::CellPowerBms:
return new CellPowerBms(); return new CellPowerBms();
#ifdef CHADEMO_PIN_2 // Only support chademo for certain platforms
case BatteryType::Chademo: case BatteryType::Chademo:
return new ChademoBattery(); return new ChademoBattery();
#endif
case BatteryType::CmfaEv: case BatteryType::CmfaEv:
return new CmfaEvBattery(); return new CmfaEvBattery();
case BatteryType::Foxess: case BatteryType::Foxess:
@ -210,7 +206,7 @@ void setup_battery() {
break; break;
case BatteryType::BmwI3: case BatteryType::BmwI3:
battery2 = new BmwI3Battery(&datalayer.battery2, &datalayer.system.status.battery2_allowed_contactor_closing, battery2 = new BmwI3Battery(&datalayer.battery2, &datalayer.system.status.battery2_allowed_contactor_closing,
can_config.battery_double, WUP_PIN2); can_config.battery_double, esp32hal->WUP_PIN2());
break; break;
case BatteryType::KiaHyundai64: case BatteryType::KiaHyundai64:
battery2 = new KiaHyundai64Battery(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2, battery2 = new KiaHyundai64Battery(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2,

View file

@ -508,6 +508,10 @@ void BmwI3Battery::transmit_can(unsigned long currentMillis) {
} }
void BmwI3Battery::setup(void) { // Performs one time setup at startup void BmwI3Battery::setup(void) { // Performs one time setup at startup
if (!esp32hal->alloc_pins(Name, wakeup_pin)) {
return;
}
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';

View file

@ -15,7 +15,7 @@ class BmwI3Battery : public CanBattery {
public: public:
// Use this constructor for the second battery. // Use this constructor for the second battery.
BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, CAN_Interface targetCan, BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, CAN_Interface targetCan,
int wakeup) gpio_num_t wakeup)
: CanBattery(targetCan) { : CanBattery(targetCan) {
datalayer_battery = datalayer_ptr; datalayer_battery = datalayer_ptr;
contactor_closing_allowed = contactor_closing_allowed_ptr; contactor_closing_allowed = contactor_closing_allowed_ptr;
@ -32,7 +32,7 @@ class BmwI3Battery : public CanBattery {
datalayer_battery = &datalayer.battery; datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
contactor_closing_allowed = nullptr; contactor_closing_allowed = nullptr;
wakeup_pin = WUP_PIN1; wakeup_pin = esp32hal->WUP_PIN1();
} }
virtual void setup(void); virtual void setup(void);
@ -70,7 +70,7 @@ class BmwI3Battery : public CanBattery {
// If not null, this battery listens to this boolean to determine whether contactor closing is allowed // If not null, this battery listens to this boolean to determine whether contactor closing is allowed
bool* contactor_closing_allowed; bool* contactor_closing_allowed;
int wakeup_pin; gpio_num_t wakeup_pin;
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send

View file

@ -4,8 +4,6 @@
#include "../include.h" #include "../include.h"
#include "CHADEMO-SHUNTS.h" #include "CHADEMO-SHUNTS.h"
#ifdef CHADEMO_PIN_2 // Only support chademo for certain platforms
/* CHADEMO handling runs at 6.25 times the rate of most other code, so, rather than the /* CHADEMO handling runs at 6.25 times the rate of most other code, so, rather than the
* default value of 12 (for 12 iterations of the 5s value update loop) * 5 for a 60s timeout, * default value of 12 (for 12 iterations of the 5s value update loop) * 5 for a 60s timeout,
* instead use 75 for 75*0.8s = 60s * instead use 75 for 75*0.8s = 60s
@ -644,10 +642,11 @@ void ChademoBattery::transmit_can(unsigned long currentMillis) {
*/ */
void ChademoBattery::handle_chademo_sequence() { void ChademoBattery::handle_chademo_sequence() {
precharge_low = digitalRead(PRECHARGE_PIN) == LOW; precharge_low = digitalRead(precharge) == LOW;
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH; positive_high = digitalRead(positive_contactor) == HIGH;
contactors_ready = precharge_low && positive_high; contactors_ready = precharge_low && positive_high;
vehicle_permission = digitalRead(CHADEMO_PIN_4);
vehicle_permission = digitalRead(pin4);
/* ------------------- State override conditions checks ------------------- */ /* ------------------- State override conditions checks ------------------- */
/* ------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------ */
@ -670,8 +669,8 @@ void ChademoBattery::handle_chademo_sequence() {
switch (CHADEMO_Status) { switch (CHADEMO_Status) {
case CHADEMO_IDLE: case CHADEMO_IDLE:
/* this is where we can unlock connector */ /* this is where we can unlock connector */
digitalWrite(CHADEMO_LOCK, LOW); digitalWrite(pin_lock, LOW);
plug_inserted = digitalRead(CHADEMO_PIN_7); plug_inserted = digitalRead(pin7);
if (!plug_inserted) { if (!plug_inserted) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
@ -698,7 +697,7 @@ void ChademoBattery::handle_chademo_sequence() {
/* If connection is detectable, jumpstart handshake by /* If connection is detectable, jumpstart handshake by
* indicate that the EVSE is ready to begin * indicate that the EVSE is ready to begin
*/ */
digitalWrite(CHADEMO_PIN_2, HIGH); digitalWrite(pin2, HIGH);
/* State change to initializing. We will re-enter the handler upon receipt of CAN */ /* State change to initializing. We will re-enter the handler upon receipt of CAN */
CHADEMO_Status = CHADEMO_INIT; CHADEMO_Status = CHADEMO_INIT;
@ -744,7 +743,7 @@ void ChademoBattery::handle_chademo_sequence() {
// that pin 4 (j) reads high // that pin 4 (j) reads high
if (vehicle_permission) { if (vehicle_permission) {
//lock connector here //lock connector here
digitalWrite(CHADEMO_LOCK, HIGH); digitalWrite(pin_lock, HIGH);
//TODO spec requires test to validate solenoid has indeed engaged. //TODO spec requires test to validate solenoid has indeed engaged.
// example uses a comparator/current consumption check around solenoid // example uses a comparator/current consumption check around solenoid
@ -774,7 +773,7 @@ void ChademoBattery::handle_chademo_sequence() {
if (x102_chg_session.s.status.StatusVehicleChargingEnabled) { if (x102_chg_session.s.status.StatusVehicleChargingEnabled) {
if (get_measured_voltage() < 20) { if (get_measured_voltage() < 20) {
digitalWrite(CHADEMO_PIN_10, HIGH); digitalWrite(pin10, HIGH);
evse_permission = true; evse_permission = true;
} else { } else {
logging.println("Insulation check measures > 20v "); logging.println("Insulation check measures > 20v ");
@ -892,8 +891,8 @@ void ChademoBattery::handle_chademo_sequence() {
*/ */
if (get_measured_current() <= 5 && get_measured_voltage() <= 10) { if (get_measured_current() <= 5 && get_measured_voltage() <= 10) {
/* welding detection ideally here */ /* welding detection ideally here */
digitalWrite(CHADEMO_PIN_10, LOW); digitalWrite(pin10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW); digitalWrite(pin2, LOW);
CHADEMO_Status = CHADEMO_IDLE; CHADEMO_Status = CHADEMO_IDLE;
} }
@ -910,8 +909,8 @@ void ChademoBattery::handle_chademo_sequence() {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("CHADEMO fault encountered, tearing down to make safe"); logging.println("CHADEMO fault encountered, tearing down to make safe");
#endif #endif
digitalWrite(CHADEMO_PIN_10, LOW); digitalWrite(pin10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW); digitalWrite(pin2, LOW);
evse_permission = false; evse_permission = false;
vehicle_permission = false; vehicle_permission = false;
x209_sent = false; x209_sent = false;
@ -931,14 +930,18 @@ void ChademoBattery::handle_chademo_sequence() {
void ChademoBattery::setup(void) { // Performs one time setup at startup void ChademoBattery::setup(void) { // Performs one time setup at startup
pinMode(CHADEMO_PIN_2, OUTPUT); if (!esp32hal->alloc_pins("CHADEMO", pin2, pin10, pin4, pin7, pin_lock)) {
digitalWrite(CHADEMO_PIN_2, LOW); return;
pinMode(CHADEMO_PIN_10, OUTPUT); }
digitalWrite(CHADEMO_PIN_10, LOW);
pinMode(CHADEMO_LOCK, OUTPUT); pinMode(pin2, OUTPUT);
digitalWrite(CHADEMO_LOCK, LOW); digitalWrite(pin2, LOW);
pinMode(CHADEMO_PIN_4, INPUT); pinMode(pin10, OUTPUT);
pinMode(CHADEMO_PIN_7, INPUT); digitalWrite(pin10, LOW);
pinMode(pin_lock, OUTPUT);
digitalWrite(pin_lock, LOW);
pinMode(pin4, INPUT);
pinMode(pin7, INPUT);
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
@ -989,5 +992,3 @@ void ChademoBattery::setup(void) { // Performs one time setup at startup
setupMillis = millis(); setupMillis = millis();
} }
#endif

View file

@ -13,6 +13,18 @@
class ChademoBattery : public CanBattery { class ChademoBattery : public CanBattery {
public: public:
ChademoBattery() {
pin2 = esp32hal->CHADEMO_PIN_2();
pin10 = esp32hal->CHADEMO_PIN_10();
pin4 = esp32hal->CHADEMO_PIN_4();
pin7 = esp32hal->CHADEMO_PIN_7();
pin_lock = esp32hal->CHADEMO_LOCK();
// Assuming these are initialized by contactor control module.
precharge = esp32hal->PRECHARGE_PIN();
positive_contactor = esp32hal->POSITIVE_CONTACTOR_PIN();
}
virtual void setup(void); virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values(); virtual void update_values();
@ -21,6 +33,8 @@ class ChademoBattery : public CanBattery {
static constexpr char* Name = "Chademo V2X mode"; static constexpr char* Name = "Chademo V2X mode";
private: private:
gpio_num_t pin2, pin10, pin4, pin7, pin_lock, precharge, positive_contactor;
void process_vehicle_charging_minimums(CAN_frame rx_frame); void process_vehicle_charging_minimums(CAN_frame rx_frame);
void process_vehicle_charging_maximums(CAN_frame rx_frame); void process_vehicle_charging_maximums(CAN_frame rx_frame);
void process_vehicle_charging_session(CAN_frame rx_frame); void process_vehicle_charging_session(CAN_frame rx_frame);

View file

@ -70,7 +70,14 @@ void DalyBms::setup(void) { // Performs one time setup at startup
datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX; datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
Serial2.begin(baud_rate(), SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); auto rx_pin = esp32hal->RS485_RX_PIN();
auto tx_pin = esp32hal->RS485_TX_PIN();
if (!esp32hal->alloc_pins(Name, rx_pin, tx_pin)) {
return;
}
Serial2.begin(baud_rate(), SERIAL_8N1, rx_pin, tx_pin);
} }
uint8_t calculate_checksum(uint8_t buff[12]) { uint8_t calculate_checksum(uint8_t buff[12]) {

View file

@ -8,7 +8,4 @@ class CanReceiver {
virtual void receive_can_frame(CAN_frame* rx_frame) = 0; virtual void receive_can_frame(CAN_frame* rx_frame) = 0;
}; };
// Register a receiver object for a given CAN interface
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface);
#endif #endif

View file

@ -1,6 +1,8 @@
#include "comm_can.h" #include "comm_can.h"
#include <map> #include <map>
#include "../../include.h" #include "../../include.h"
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
#include "src/devboard/sdcard/sdcard.h" #include "src/devboard/sdcard/sdcard.h"
// Parameters // Parameters
@ -11,46 +13,88 @@ volatile bool send_ok_2515 = 0;
volatile bool send_ok_2518 = 0; volatile bool send_ok_2518 = 0;
static unsigned long previousMillis10 = 0; static unsigned long previousMillis10 = 0;
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
const bool use_canfd_as_can_default = true;
#else
const bool use_canfd_as_can_default = false;
#endif
bool use_canfd_as_can = use_canfd_as_can_default;
void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface); void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface);
#ifdef CAN_ADDON static std::multimap<CAN_Interface, CanReceiver*> can_receivers;
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface) {
can_receivers.insert({interface, receiver});
}
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
SPIClass SPI2515; SPIClass SPI2515;
ACAN2515 can(MCP2515_CS, SPI2515, MCP2515_INT);
ACAN2515* can2515;
//ACAN2515 can(MCP2515_CS, SPI2515, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer; static ACAN2515_Buffer16 gBuffer;
#endif //CAN_ADDON
#ifdef CANFD_ADDON
SPIClass SPI2517; SPIClass SPI2517;
ACAN2517FD canfd(MCP2517_CS, SPI2517, MCP2517_INT); //ACAN2517FD canfd(MCP2517_CS, SPI2517, MCP2517_INT);
#endif //CANFD_ADDON ACAN2517FD* canfd;
// Initialization functions // Initialization functions
void init_CAN() { bool init_CAN() {
// CAN pins
#ifdef CAN_SE_PIN if (can_receivers.find(CAN_NATIVE) != can_receivers.end()) {
pinMode(CAN_SE_PIN, OUTPUT); auto se_pin = esp32hal->CAN_SE_PIN();
digitalWrite(CAN_SE_PIN, LOW); auto tx_pin = esp32hal->CAN_TX_PIN();
#endif // CAN_SE_PIN auto rx_pin = esp32hal->CAN_RX_PIN();
if (se_pin != GPIO_NUM_NC) {
if (!esp32hal->alloc_pins("CAN", se_pin)) {
return false;
}
pinMode(se_pin, OUTPUT);
digitalWrite(se_pin, LOW);
}
CAN_cfg.speed = CAN_SPEED_500KBPS; CAN_cfg.speed = CAN_SPEED_500KBPS;
#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed #ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed
CAN_cfg.speed = CAN_SPEED_250KBPS; CAN_cfg.speed = CAN_SPEED_250KBPS;
#endif // NATIVECAN_250KBPS #endif // NATIVECAN_250KBPS
CAN_cfg.tx_pin_id = CAN_TX_PIN;
CAN_cfg.rx_pin_id = CAN_RX_PIN; if (!esp32hal->alloc_pins("CAN", tx_pin, rx_pin)) {
return false;
}
CAN_cfg.tx_pin_id = tx_pin;
CAN_cfg.rx_pin_id = rx_pin;
CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t)); CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t));
// Init CAN Module // Init CAN Module
ESP32Can.CANInit(); ESP32Can.CANInit();
}
if (can_receivers.find(CAN_ADDON_MCP2515) != can_receivers.end()) {
auto cs_pin = esp32hal->MCP2515_CS();
auto int_pin = esp32hal->MCP2515_INT();
auto sck_pin = esp32hal->MCP2515_SCK();
auto miso_pin = esp32hal->MCP2515_MISO();
auto mosi_pin = esp32hal->MCP2515_MOSI();
if (!esp32hal->alloc_pins("CAN", cs_pin, int_pin, sck_pin, miso_pin, mosi_pin)) {
return false;
}
#ifdef CAN_ADDON
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("Dual CAN Bus (ESP32+MCP2515) selected"); logging.println("Dual CAN Bus (ESP32+MCP2515) selected");
#endif // DEBUG_LOG #endif // DEBUG_LOG
gBuffer.initWithSize(25); gBuffer.initWithSize(25);
SPI2515.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI);
can2515 = new ACAN2515(cs_pin, SPI2515, int_pin);
SPI2515.begin(sck_pin, miso_pin, mosi_pin);
ACAN2515Settings settings2515(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s ACAN2515Settings settings2515(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s
settings2515.mRequestedMode = ACAN2515Settings::NormalMode; settings2515.mRequestedMode = ACAN2515Settings::NormalMode;
const uint16_t errorCode2515 = can.begin(settings2515, [] { can.isr(); }); const uint16_t errorCode2515 = can2515->begin(settings2515, [] { can2515->isr(); });
if (errorCode2515 == 0) { if (errorCode2515 == 0) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("Can ok"); logging.println("Can ok");
@ -61,23 +105,37 @@ void init_CAN() {
logging.println(errorCode2515, HEX); logging.println(errorCode2515, HEX);
#endif // DEBUG_LOG #endif // DEBUG_LOG
set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515); set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515);
return false;
}
} }
#endif // CAN_ADDON
#ifdef CANFD_ADDON if (can_receivers.find(CANFD_NATIVE) != can_receivers.end() ||
can_receivers.find(CANFD_ADDON_MCP2518) != can_receivers.end()) {
auto cs_pin = esp32hal->MCP2517_CS();
auto int_pin = esp32hal->MCP2517_INT();
auto sck_pin = esp32hal->MCP2517_SCK();
auto sdo_pin = esp32hal->MCP2517_SDO();
auto sdi_pin = esp32hal->MCP2517_SDI();
if (!esp32hal->alloc_pins("CAN", cs_pin, int_pin, sck_pin, sdo_pin, sdi_pin)) {
return false;
}
canfd = new ACAN2517FD(cs_pin, SPI2517, int_pin);
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("CAN FD add-on (ESP32+MCP2517) selected"); logging.println("CAN FD add-on (ESP32+MCP2517) selected");
#endif // DEBUG_LOG #endif // DEBUG_LOG
SPI2517.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI); SPI2517.begin(sck_pin, sdo_pin, sdi_pin);
ACAN2517FDSettings settings2517(CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000, ACAN2517FDSettings settings2517(
CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000,
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings2517.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD // ListenOnly / Normal20B / NormalFD
#else // not USE_CANFD_INTERFACE_AS_CLASSIC_CAN settings2517.mRequestedMode = use_canfd_as_can ? ACAN2517FDSettings::Normal20B : ACAN2517FDSettings::NormalFD;
settings2517.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
#endif // USE_CANFD_INTERFACE_AS_CLASSIC_CAN const uint32_t errorCode2517 = canfd->begin(settings2517, [] { canfd->isr(); });
const uint32_t errorCode2517 = canfd.begin(settings2517, [] { canfd.isr(); }); canfd->poll();
canfd.poll();
if (errorCode2517 == 0) { if (errorCode2517 == 0) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.print("Bit Rate prescaler: "); logging.print("Bit Rate prescaler: ");
@ -103,8 +161,11 @@ void init_CAN() {
logging.println(errorCode2517, HEX); logging.println(errorCode2517, HEX);
#endif // DEBUG_LOG #endif // DEBUG_LOG
set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517); set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517);
return false;
} }
#endif // CANFD_ADDON }
return true;
} }
void transmit_can_frame(CAN_frame* tx_frame, int interface) { void transmit_can_frame(CAN_frame* tx_frame, int interface) {
@ -133,7 +194,6 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
} }
break; break;
case CAN_ADDON_MCP2515: { case CAN_ADDON_MCP2515: {
#ifdef CAN_ADDON
//Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2
CANMessage MCP2515Frame; CANMessage MCP2515Frame;
MCP2515Frame.id = tx_frame->ID; MCP2515Frame.id = tx_frame->ID;
@ -144,17 +204,13 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
MCP2515Frame.data[i] = tx_frame->data.u8[i]; MCP2515Frame.data[i] = tx_frame->data.u8[i];
} }
send_ok_2515 = can.tryToSend(MCP2515Frame); send_ok_2515 = can2515->tryToSend(MCP2515Frame);
if (!send_ok_2515) { if (!send_ok_2515) {
datalayer.system.info.can_2515_send_fail = true; datalayer.system.info.can_2515_send_fail = true;
} }
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CAN_ADDON
} break; } break;
case CANFD_NATIVE: case CANFD_NATIVE:
case CANFD_ADDON_MCP2518: { case CANFD_ADDON_MCP2518: {
#ifdef CANFD_ADDON
CANFDMessage MCP2518Frame; CANFDMessage MCP2518Frame;
if (tx_frame->FD) { if (tx_frame->FD) {
MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH; MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH;
@ -167,13 +223,10 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
for (uint8_t i = 0; i < MCP2518Frame.len; i++) { for (uint8_t i = 0; i < MCP2518Frame.len; i++) {
MCP2518Frame.data[i] = tx_frame->data.u8[i]; MCP2518Frame.data[i] = tx_frame->data.u8[i];
} }
send_ok_2518 = canfd.tryToSend(MCP2518Frame); send_ok_2518 = canfd->tryToSend(MCP2518Frame);
if (!send_ok_2518) { if (!send_ok_2518) {
datalayer.system.info.can_2518_send_fail = true; datalayer.system.info.can_2518_send_fail = true;
} }
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CANFD_ADDON
} break; } break;
default: default:
// Invalid interface sent with function call. TODO: Raise event that coders messed up // Invalid interface sent with function call. TODO: Raise event that coders messed up
@ -184,12 +237,14 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
// Receive functions // Receive functions
void receive_can() { void receive_can() {
receive_frame_can_native(); // Receive CAN messages from native CAN port receive_frame_can_native(); // Receive CAN messages from native CAN port
#ifdef CAN_ADDON
if (can2515) {
receive_frame_can_addon(); // Receive CAN messages on add-on MCP2515 chip receive_frame_can_addon(); // Receive CAN messages on add-on MCP2515 chip
#endif // CAN_ADDON }
#ifdef CANFD_ADDON
if (canfd) {
receive_frame_canfd_addon(); // Receive CAN-FD messages. receive_frame_canfd_addon(); // Receive CAN-FD messages.
#endif // CANFD_ADDON }
} }
void receive_frame_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port void receive_frame_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port
@ -211,13 +266,12 @@ void receive_frame_can_native() { // This section checks if we have a complete
} }
} }
#ifdef CAN_ADDON
void receive_frame_can_addon() { // This section checks if we have a complete CAN message incoming on add-on CAN port void receive_frame_can_addon() { // This section checks if we have a complete CAN message incoming on add-on CAN port
CAN_frame rx_frame; // Struct with our CAN format CAN_frame rx_frame; // Struct with our CAN format
CANMessage MCP2515frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library CANMessage MCP2515frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library
if (can.available()) { if (can2515->available()) {
can.receive(MCP2515frame); can2515->receive(MCP2515frame);
rx_frame.ID = MCP2515frame.id; rx_frame.ID = MCP2515frame.id;
rx_frame.ext_ID = MCP2515frame.ext ? CAN_frame_ext : CAN_frame_std; rx_frame.ext_ID = MCP2515frame.ext ? CAN_frame_ext : CAN_frame_std;
@ -230,14 +284,12 @@ void receive_frame_can_addon() { // This section checks if we have a complete C
map_can_frame_to_variable(&rx_frame, CAN_ADDON_MCP2515); map_can_frame_to_variable(&rx_frame, CAN_ADDON_MCP2515);
} }
} }
#endif // CAN_ADDON
#ifdef CANFD_ADDON
void receive_frame_canfd_addon() { // This section checks if we have a complete CAN-FD message incoming void receive_frame_canfd_addon() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage MCP2518frame; CANFDMessage MCP2518frame;
int count = 0; int count = 0;
while (canfd.available() && count++ < 16) { while (canfd->available() && count++ < 16) {
canfd.receive(MCP2518frame); canfd->receive(MCP2518frame);
CAN_frame rx_frame; CAN_frame rx_frame;
rx_frame.ID = MCP2518frame.id; rx_frame.ID = MCP2518frame.id;
@ -249,7 +301,6 @@ void receive_frame_canfd_addon() { // This section checks if we have a complete
map_can_frame_to_variable(&rx_frame, CANFD_NATIVE); map_can_frame_to_variable(&rx_frame, CANFD_NATIVE);
} }
} }
#endif // CANFD_ADDON
// Support functions // Support functions
void print_can_frame(CAN_frame frame, frameDirection msgDir) { void print_can_frame(CAN_frame frame, frameDirection msgDir) {
@ -276,12 +327,6 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) {
} }
} }
static std::multimap<CAN_Interface, CanReceiver*> can_receivers;
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface) {
can_receivers.insert({interface, receiver});
}
void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface) { void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface) {
if (interface != if (interface !=
CANFD_NATIVE) { //Avoid printing twice due to receive_frame_canfd_addon sending to both FD interfaces CANFD_NATIVE) { //Avoid printing twice due to receive_frame_canfd_addon sending to both FD interfaces

View file

@ -8,24 +8,23 @@
#include "../../devboard/utils/value_mapping.h" #include "../../devboard/utils/value_mapping.h"
#include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" #include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#ifdef CAN_ADDON
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h" extern bool use_canfd_as_can;
#endif //CAN_ADDON
#ifdef CANFD_ADDON
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#endif //CANFD_ADDON
void dump_can_frame(CAN_frame& frame, frameDirection msgDir); void dump_can_frame(CAN_frame& frame, frameDirection msgDir);
void transmit_can_frame(CAN_frame* tx_frame, int interface); void transmit_can_frame(CAN_frame* tx_frame, int interface);
// Register a receiver object for a given CAN interface
void register_can_receiver(CanReceiver* receiver, CAN_Interface interface);
/** /**
* @brief Initialization function for CAN. * @brief Initializes all CAN interfaces requested earlier by other modules (see register_can_receiver)
* *
* @param[in] void * @param[in] void
* *
* @return void * @return true if CAN interfaces were initialized successfully, false otherwise.
*/ */
void init_CAN(); bool init_CAN();
/** /**
* @brief Receive CAN messages from all interfaces * @brief Receive CAN messages from all interfaces

View file

@ -92,41 +92,57 @@ void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
// Initialization functions // Initialization functions
void init_contactors() { const char* contactors = "Contactors";
bool init_contactors() {
// Init contactor pins // Init contactor pins
if (contactor_control_enabled) { if (contactor_control_enabled) {
auto posPin = esp32hal->POSITIVE_CONTACTOR_PIN();
auto negPin = esp32hal->NEGATIVE_CONTACTOR_PIN();
auto precPin = esp32hal->PRECHARGE_PIN();
if (!esp32hal->alloc_pins(contactors, posPin, negPin, precPin)) {
return false;
}
if (pwm_contactor_control) { if (pwm_contactor_control) {
// Setup PWM Channel Frequency and Resolution // Setup PWM Channel Frequency and Resolution
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Positive_Channel); ledcAttachChannel(posPin, PWM_Freq, PWM_Res, PWM_Positive_Channel);
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Negative_Channel); ledcAttachChannel(negPin, PWM_Freq, PWM_Res, PWM_Negative_Channel);
// Set all pins OFF (0% PWM) // Set all pins OFF (0% PWM)
ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); ledcWrite(posPin, PWM_OFF_DUTY);
ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); ledcWrite(negPin, PWM_OFF_DUTY);
} else { //Normal CONTACTOR_CONTROL } else { //Normal CONTACTOR_CONTROL
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); pinMode(posPin, OUTPUT);
set(POSITIVE_CONTACTOR_PIN, OFF); set(posPin, OFF);
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); pinMode(negPin, OUTPUT);
set(NEGATIVE_CONTACTOR_PIN, OFF); set(negPin, OFF);
} // Precharge never has PWM regardless of setting } // Precharge never has PWM regardless of setting
pinMode(PRECHARGE_PIN, OUTPUT); pinMode(precPin, OUTPUT);
set(PRECHARGE_PIN, OFF); set(precPin, OFF);
} }
if (contactor_control_enabled_double_battery) {
pinMode(SECOND_BATTERY_CONTACTORS_PIN, OUTPUT);
set(SECOND_BATTERY_CONTACTORS_PIN, OFF);
}
// Init BMS contactor
#if defined HW_STARK || defined HW_3LB // This hardware has dedicated pin, always enable on start
pinMode(BMS_POWER, OUTPUT); //LilyGo is omitted from this, only enabled if user selects PERIODIC_BMS_RESET
digitalWrite(BMS_POWER, HIGH);
#endif // HW with dedicated BMS pins
#ifdef BMS_POWER if (contactor_control_enabled_double_battery) {
if (periodic_bms_reset || remote_bms_reset) { auto second_contactors = esp32hal->SECOND_BATTERY_CONTACTORS_PIN();
pinMode(BMS_POWER, OUTPUT); if (!esp32hal->alloc_pins(contactors, second_contactors)) {
digitalWrite(BMS_POWER, HIGH); return false;
} }
#endif
pinMode(second_contactors, OUTPUT);
set(second_contactors, OFF);
}
// Init BMS contactor
if (periodic_bms_reset || remote_bms_reset || esp32hal->always_enable_bms_power()) {
auto pin = esp32hal->BMS_POWER();
if (!esp32hal->alloc_pins("BMS power", pin)) {
return false;
}
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
}
return true;
} }
static void dbg_contactors(const char* state) { static void dbg_contactors(const char* state) {
@ -144,9 +160,14 @@ void handle_contactors() {
datalayer.system.status.inverter_allows_contactor_closing = inverter->allows_contactor_closing(); datalayer.system.status.inverter_allows_contactor_closing = inverter->allows_contactor_closing();
} }
#ifdef BMS_POWER auto posPin = esp32hal->POSITIVE_CONTACTOR_PIN();
auto negPin = esp32hal->NEGATIVE_CONTACTOR_PIN();
auto prechargePin = esp32hal->PRECHARGE_PIN();
auto bms_power_pin = esp32hal->BMS_POWER();
if (bms_power_pin != GPIO_NUM_NC) {
handle_BMSpower(); // Some batteries need to be periodically power cycled handle_BMSpower(); // Some batteries need to be periodically power cycled
#endif }
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
handle_contactors_battery2(); handle_contactors_battery2();
@ -166,9 +187,9 @@ void handle_contactors() {
} }
if (contactorStatus == SHUTDOWN_REQUESTED) { if (contactorStatus == SHUTDOWN_REQUESTED) {
set(PRECHARGE_PIN, OFF); set(prechargePin, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set(negPin, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set(posPin, OFF, PWM_OFF_DUTY);
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
datalayer.system.status.contactors_engaged = false; datalayer.system.status.contactors_engaged = false;
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
@ -176,9 +197,9 @@ void handle_contactors() {
// After that, check if we are OK to start turning on the battery // After that, check if we are OK to start turning on the battery
if (contactorStatus == DISCONNECTED) { if (contactorStatus == DISCONNECTED) {
set(PRECHARGE_PIN, OFF); set(prechargePin, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set(negPin, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); set(posPin, OFF, PWM_OFF_DUTY);
datalayer.system.status.contactors_engaged = false; datalayer.system.status.contactors_engaged = false;
if (datalayer.system.status.battery_allows_contactor_closing && if (datalayer.system.status.battery_allows_contactor_closing &&
@ -210,7 +231,7 @@ void handle_contactors() {
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge // Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
switch (contactorStatus) { switch (contactorStatus) {
case START_PRECHARGE: case START_PRECHARGE:
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); set(negPin, ON, PWM_ON_DUTY);
dbg_contactors("NEGATIVE"); dbg_contactors("NEGATIVE");
prechargeStartTime = currentTime; prechargeStartTime = currentTime;
contactorStatus = PRECHARGE; contactorStatus = PRECHARGE;
@ -218,7 +239,7 @@ void handle_contactors() {
case PRECHARGE: case PRECHARGE:
if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) { if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
set(PRECHARGE_PIN, ON); set(prechargePin, ON);
dbg_contactors("PRECHARGE"); dbg_contactors("PRECHARGE");
negativeStartTime = currentTime; negativeStartTime = currentTime;
contactorStatus = POSITIVE; contactorStatus = POSITIVE;
@ -227,7 +248,7 @@ void handle_contactors() {
case POSITIVE: case POSITIVE:
if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) { if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) {
set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); set(posPin, ON, PWM_ON_DUTY);
dbg_contactors("POSITIVE"); dbg_contactors("POSITIVE");
prechargeCompletedTime = currentTime; prechargeCompletedTime = currentTime;
contactorStatus = PRECHARGE_OFF; contactorStatus = PRECHARGE_OFF;
@ -236,9 +257,9 @@ void handle_contactors() {
case PRECHARGE_OFF: case PRECHARGE_OFF:
if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) { if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) {
set(PRECHARGE_PIN, OFF); set(prechargePin, OFF);
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); set(negPin, ON, PWM_HOLD_DUTY);
set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); set(posPin, ON, PWM_HOLD_DUTY);
dbg_contactors("PRECHARGE_OFF"); dbg_contactors("PRECHARGE_OFF");
contactorStatus = COMPLETED; contactorStatus = COMPLETED;
datalayer.system.status.contactors_engaged = true; datalayer.system.status.contactors_engaged = true;
@ -269,9 +290,11 @@ This makes the BMS recalculate all SOC% and avoid memory leaks
During that time we also set the emulator state to paused in order to not try and send CAN messages towards the battery During that time we also set the emulator state to paused in order to not try and send CAN messages towards the battery
Feature is only used if user has enabled PERIODIC_BMS_RESET in the USER_SETTINGS */ Feature is only used if user has enabled PERIODIC_BMS_RESET in the USER_SETTINGS */
#ifdef BMS_POWER
void handle_BMSpower() { void handle_BMSpower() {
if (periodic_bms_reset || remote_bms_reset) { if (periodic_bms_reset || remote_bms_reset) {
auto bms_power_pin = esp32hal->BMS_POWER();
auto bms2_power_pin = esp32hal->BMS2_POWER();
// Get current time // Get current time
currentTime = millis(); currentTime = millis();
@ -285,10 +308,10 @@ void handle_BMSpower() {
// If power has been removed for 30 seconds, restore the power // If power has been removed for 30 seconds, restore the power
if (datalayer.system.status.BMS_reset_in_progress && currentTime - lastPowerRemovalTime >= powerRemovalDuration) { if (datalayer.system.status.BMS_reset_in_progress && currentTime - lastPowerRemovalTime >= powerRemovalDuration) {
// Reapply power to the BMS // Reapply power to the BMS
digitalWrite(BMS_POWER, HIGH); digitalWrite(bms_power_pin, HIGH);
#ifdef BMS_2_POWER if (bms2_power_pin != GPIO_NUM_NC) {
digitalWrite(BMS_2_POWER, HIGH); // Same for battery 2 digitalWrite(bms2_power_pin, HIGH); // Same for battery 2
#endif }
bmsPowerOnTime = currentTime; bmsPowerOnTime = currentTime;
datalayer.system.status.BMS_reset_in_progress = false; // Reset the power removal flag datalayer.system.status.BMS_reset_in_progress = false; // Reset the power removal flag
datalayer.system.status.BMS_startup_in_progress = true; // Set the BMS warmup flag datalayer.system.status.BMS_startup_in_progress = true; // Set the BMS warmup flag
@ -303,10 +326,12 @@ void handle_BMSpower() {
} }
} }
} }
#endif
void start_bms_reset() { void start_bms_reset() {
if (periodic_bms_reset || remote_bms_reset) { if (periodic_bms_reset || remote_bms_reset) {
auto bms_power_pin = esp32hal->BMS_POWER();
auto bms2_power_pin = esp32hal->BMS2_POWER();
if (!datalayer.system.status.BMS_reset_in_progress) { if (!datalayer.system.status.BMS_reset_in_progress) {
lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started lastPowerRemovalTime = currentTime; // Record the time when BMS reset was started
// we are now resetting at the correct time. We don't need to offset anymore // we are now resetting at the correct time. We don't need to offset anymore
@ -319,12 +344,11 @@ void start_bms_reset() {
// We try to keep contactors engaged during this pause, and just ramp power down to 0. // We try to keep contactors engaged during this pause, and just ramp power down to 0.
setBatteryPause(true, false, false, false); setBatteryPause(true, false, false, false);
#ifdef BMS_POWER digitalWrite(bms_power_pin, LOW); // Remove power by setting the BMS power pin to LOW
digitalWrite(BMS_POWER, LOW); // Remove power by setting the BMS power pin to LOW
#endif if (bms2_power_pin != GPIO_NUM_NC) {
#ifdef BMS_2_POWER digitalWrite(bms2_power_pin, LOW); // Same for battery 2
digitalWrite(BMS_2_POWER, LOW); // Same for battery 2 }
#endif
} }
} }
} }

View file

@ -36,9 +36,9 @@ void start_bms_reset();
* *
* @param[in] void * @param[in] void
* *
* @return void * @return true if contactor init was successful, false otherwise.
*/ */
void init_contactors(); bool init_contactors();
/** /**
* @brief Handle contactors * @brief Handle contactors

View file

@ -1,33 +1,45 @@
#include "comm_equipmentstopbutton.h" #include "comm_equipmentstopbutton.h"
#include "../../include.h" #include "../../include.h"
STOP_BUTTON_BEHAVIOR equipment_stop_behavior = stop_button_default_behavior;
// Parameters // Parameters
#ifdef EQUIPMENT_STOP_BUTTON
const unsigned long equipment_button_long_press_duration = const unsigned long equipment_button_long_press_duration =
15000; // 15 seconds for long press in case of MOMENTARY_SWITCH 15000; // 15 seconds for long press in case of MOMENTARY_SWITCH
const unsigned long equipment_button_debounce_duration = 200; // 200ms for debouncing the button const unsigned long equipment_button_debounce_duration = 200; // 200ms for debouncing the button
unsigned long timeSincePress = 0; // Variable to store the time since the last press unsigned long timeSincePress = 0; // Variable to store the time since the last press
DebouncedButton equipment_stop_button; // Debounced button object DebouncedButton equipment_stop_button; // Debounced button object
#endif // EQUIPMENT_STOP_BUTTON
// Initialization functions // Initialization functions
#ifdef EQUIPMENT_STOP_BUTTON bool init_equipment_stop_button() {
void init_equipment_stop_button() { if (equipment_stop_behavior == STOP_BUTTON_BEHAVIOR::NOT_CONNECTED) {
//using external pullup resistors NC return true;
pinMode(EQUIPMENT_STOP_PIN, INPUT); }
// Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time
initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration); auto pin = esp32hal->EQUIPMENT_STOP_PIN();
if (!esp32hal->alloc_pins("Equipment stop button", pin)) {
return false;
}
//using external pullup resistors NC
pinMode(pin, INPUT);
// Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time
initDebouncedButton(equipment_stop_button, pin, NC, equipment_button_debounce_duration);
return true;
} }
#endif // EQUIPMENT_STOP_BUTTON
// Main functions // Main functions
#ifdef EQUIPMENT_STOP_BUTTON
void monitor_equipment_stop_button() { void monitor_equipment_stop_button() {
if (equipment_stop_behavior == STOP_BUTTON_BEHAVIOR::NOT_CONNECTED) {
return;
}
ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress); ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress);
if (equipment_stop_behavior == LATCHING_SWITCH) { if (equipment_stop_behavior == STOP_BUTTON_BEHAVIOR::LATCHING_SWITCH) {
if (changed_state == PRESSED) { if (changed_state == PRESSED) {
// Changed to ON initiating equipment stop. // Changed to ON initiating equipment stop.
setBatteryPause(true, false, true); setBatteryPause(true, false, true);
@ -35,7 +47,7 @@ void monitor_equipment_stop_button() {
// Changed to OFF ending equipment stop. // Changed to OFF ending equipment stop.
setBatteryPause(false, false, false); setBatteryPause(false, false, false);
} }
} else if (equipment_stop_behavior == MOMENTARY_SWITCH) { } else if (equipment_stop_behavior == STOP_BUTTON_BEHAVIOR::MOMENTARY_SWITCH) {
if (changed_state == RELEASED) { // button is released if (changed_state == RELEASED) { // button is released
if (timeSincePress < equipment_button_long_press_duration) { if (timeSincePress < equipment_button_long_press_duration) {
@ -48,4 +60,3 @@ void monitor_equipment_stop_button() {
} }
} }
} }
#endif // EQUIPMENT_STOP_BUTTON

View file

@ -1,11 +1,7 @@
#ifndef _COMM_EQUIPMENTSTOPBUTTON_H_ #ifndef _COMM_EQUIPMENTSTOPBUTTON_H_
#define _COMM_EQUIPMENTSTOPBUTTON_H_ #define _COMM_EQUIPMENTSTOPBUTTON_H_
#include "../../include.h"
#ifdef EQUIPMENT_STOP_BUTTON
#include "../../devboard/utils/debounce_button.h" #include "../../devboard/utils/debounce_button.h"
#endif
/** /**
* @brief Initialization of equipment stop button * @brief Initialization of equipment stop button
@ -14,7 +10,7 @@
* *
* @return void * @return void
*/ */
void init_equipment_stop_button(); bool init_equipment_stop_button();
/** /**
* @brief Monitor equipment stop button * @brief Monitor equipment stop button
@ -25,4 +21,8 @@ void init_equipment_stop_button();
*/ */
void monitor_equipment_stop_button(); void monitor_equipment_stop_button();
enum class STOP_BUTTON_BEHAVIOR { NOT_CONNECTED = 0, LATCHING_SWITCH = 1, MOMENTARY_SWITCH = 2 };
extern STOP_BUTTON_BEHAVIOR equipment_stop_behavior;
#endif #endif

View file

@ -1,4 +1,5 @@
#include "comm_nvm.h" #include "comm_nvm.h"
#include "../../communication/can/comm_can.h"
#include "../../include.h" #include "../../include.h"
#include "../contactorcontrol/comm_contactorcontrol.h" #include "../contactorcontrol/comm_contactorcontrol.h"
@ -75,12 +76,14 @@ void init_stored_settings() {
user_selected_battery_type = (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None); user_selected_battery_type = (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None);
user_selected_inverter_protocol = (InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None); user_selected_inverter_protocol = (InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None);
user_selected_charger_type = (ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None); user_selected_charger_type = (ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None);
equipment_stop_behavior = (STOP_BUTTON_BEHAVIOR)settings.getUInt("EQSTOP", (int)STOP_BUTTON_BEHAVIOR::NOT_CONNECTED);
user_selected_second_battery = settings.getBool("DBLBTR", false); user_selected_second_battery = settings.getBool("DBLBTR", false);
contactor_control_enabled = settings.getBool("CNTCTRL", false); contactor_control_enabled = settings.getBool("CNTCTRL", false);
contactor_control_enabled_double_battery = settings.getBool("CNTCTRLDBL", false); contactor_control_enabled_double_battery = settings.getBool("CNTCTRLDBL", false);
pwm_contactor_control = settings.getBool("PWMCNTCTRL", false); pwm_contactor_control = settings.getBool("PWMCNTCTRL", false);
periodic_bms_reset = settings.getBool("PERBMSRESET", false); periodic_bms_reset = settings.getBool("PERBMSRESET", false);
remote_bms_reset = settings.getBool("REMBMSRESET", false); remote_bms_reset = settings.getBool("REMBMSRESET", false);
use_canfd_as_can = settings.getBool("CANFDASCAN", false);
#endif #endif
settings.end(); settings.end();

View file

@ -2,7 +2,15 @@
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h" #include "../../datalayer/datalayer_extended.h"
#include "../../include.h" #include "../../include.h"
#ifdef PRECHARGE_CONTROL #ifdef PRECHARGE_CONTROL
const bool precharge_control_enabled_default = true;
#else
const bool precharge_control_enabled_default = false;
#endif
bool precharge_control_enabled = precharge_control_enabled_default;
// Parameters // Parameters
#define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled #define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled
#define Precharge_default_PWM_Freq 11000 #define Precharge_default_PWM_Freq 11000
@ -25,19 +33,36 @@ static int32_t prev_external_voltage = 20000;
// Initialization functions // Initialization functions
void init_precharge_control() { bool init_precharge_control() {
if (!precharge_control_enabled) {
return true;
}
// Setup PWM Channel Frequency and Resolution // Setup PWM Channel Frequency and Resolution
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge control initialised\n"); logging.printf("Precharge control initialised\n");
#endif #endif
pinMode(HIA4V1_PIN, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW); auto hia4v1_pin = esp32hal->HIA4V1_PIN();
pinMode(INVERTER_DISCONNECT_CONTACTOR_PIN, OUTPUT); auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_PIN();
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, LOW);
if (!esp32hal->alloc_pins("Precharge control", hia4v1_pin, inverter_disconnect_contactor_pin)) {
return false;
}
pinMode(hia4v1_pin, OUTPUT);
digitalWrite(hia4v1_pin, LOW);
pinMode(inverter_disconnect_contactor_pin, OUTPUT);
digitalWrite(inverter_disconnect_contactor_pin, LOW);
return true;
} }
// Main functions // Main functions
void handle_precharge_control(unsigned long currentMillis) { void handle_precharge_control(unsigned long currentMillis) {
auto hia4v1_pin = esp32hal->HIA4V1_PIN();
auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_PIN();
int32_t target_voltage = datalayer.battery.status.voltage_dV; int32_t target_voltage = datalayer.battery.status.voltage_dV;
int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV; int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV;
@ -49,14 +74,14 @@ void handle_precharge_control(unsigned long currentMillis) {
break; break;
case AUTO_PRECHARGE_START: case AUTO_PRECHARGE_START:
freq = Precharge_default_PWM_Freq; freq = Precharge_default_PWM_Freq;
ledcAttachChannel(HIA4V1_PIN, freq, Precharge_PWM_Res, PWM_Precharge_Channel); ledcAttachChannel(hia4v1_pin, freq, Precharge_PWM_Res, PWM_Precharge_Channel);
ledcWriteTone(HIA4V1_PIN, freq); // Set frequency and set dutycycle to 50% ledcWriteTone(hia4v1_pin, freq); // Set frequency and set dutycycle to 50%
prechargeStartTime = currentMillis; prechargeStartTime = currentMillis;
datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING; datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Starting sequence\n"); logging.printf("Precharge: Starting sequence\n");
#endif #endif
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, OFF); digitalWrite(inverter_disconnect_contactor_pin, OFF);
break; break;
case AUTO_PRECHARGE_PRECHARGING: case AUTO_PRECHARGE_PRECHARGING:
@ -84,24 +109,24 @@ void handle_precharge_control(unsigned long currentMillis) {
logging.printf("Precharge: Target: %d V Extern: %d V Frequency: %u\n", target_voltage / 10, logging.printf("Precharge: Target: %d V Extern: %d V Frequency: %u\n", target_voltage / 10,
external_voltage / 10, freq); external_voltage / 10, freq);
#endif #endif
ledcWriteTone(HIA4V1_PIN, freq); ledcWriteTone(hia4v1_pin, freq);
} }
if ((datalayer.battery.status.real_bms_status != BMS_STANDBY && if ((datalayer.battery.status.real_bms_status != BMS_STANDBY &&
datalayer.battery.status.real_bms_status != BMS_ACTIVE) || datalayer.battery.status.real_bms_status != BMS_ACTIVE) ||
datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) { datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) {
pinMode(HIA4V1_PIN, OUTPUT); pinMode(hia4v1_pin, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW); digitalWrite(hia4v1_pin, LOW);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON); digitalWrite(inverter_disconnect_contactor_pin, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE; datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n"); logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n");
#endif #endif
} else if (currentMillis - prechargeStartTime >= MAX_PRECHARGE_TIME_MS || } else if (currentMillis - prechargeStartTime >= MAX_PRECHARGE_TIME_MS ||
datalayer.battery.status.real_bms_status == BMS_FAULT) { datalayer.battery.status.real_bms_status == BMS_FAULT) {
pinMode(HIA4V1_PIN, OUTPUT); pinMode(hia4v1_pin, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW); digitalWrite(hia4v1_pin, LOW);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON); digitalWrite(inverter_disconnect_contactor_pin, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF; datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n"); logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n");
@ -110,9 +135,9 @@ void handle_precharge_control(unsigned long currentMillis) {
// Add event // Add event
} else if (datalayer.system.status.battery_allows_contactor_closing) { } else if (datalayer.system.status.battery_allows_contactor_closing) {
pinMode(HIA4V1_PIN, OUTPUT); pinMode(hia4v1_pin, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW); digitalWrite(hia4v1_pin, LOW);
digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON); digitalWrite(inverter_disconnect_contactor_pin, ON);
datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED; datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n"); logging.printf("Precharge: Disabled (contacts closed) -> COMPLETED\n");
@ -134,8 +159,8 @@ void handle_precharge_control(unsigned long currentMillis) {
!datalayer.system.status.inverter_allows_contactor_closing || !datalayer.system.status.inverter_allows_contactor_closing ||
datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) { datalayer.system.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) {
datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE; datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE;
pinMode(HIA4V1_PIN, OUTPUT); pinMode(hia4v1_pin, OUTPUT);
digitalWrite(HIA4V1_PIN, LOW); digitalWrite(hia4v1_pin, LOW);
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.printf("Precharge: equipment stop activated -> IDLE\n"); logging.printf("Precharge: equipment stop activated -> IDLE\n");
#endif #endif
@ -146,4 +171,3 @@ void handle_precharge_control(unsigned long currentMillis) {
break; break;
} }
} }
#endif // PRECHARGE_CONTROL

View file

@ -12,7 +12,7 @@
* *
* @return void * @return void
*/ */
void init_precharge_control(); bool init_precharge_control();
/** /**
* @brief Handle contactors * @brief Handle contactors

View file

@ -3,19 +3,30 @@
#include <list> #include <list>
void init_rs485() { bool init_rs485() {
#ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT); auto en_pin = esp32hal->RS485_EN_PIN();
digitalWrite(RS485_EN_PIN, HIGH); auto se_pin = esp32hal->RS485_SE_PIN();
#endif // RS485_EN_PIN auto pin_5v_en = esp32hal->PIN_5V_EN();
#ifdef RS485_SE_PIN
pinMode(RS485_SE_PIN, OUTPUT); if (!esp32hal->alloc_pins_ignore_unused("RS485", en_pin, se_pin, pin_5v_en)) {
digitalWrite(RS485_SE_PIN, HIGH); return false;
#endif // RS485_SE_PIN }
#ifdef PIN_5V_EN
pinMode(PIN_5V_EN, OUTPUT); if (en_pin != GPIO_NUM_NC) {
digitalWrite(PIN_5V_EN, HIGH); pinMode(en_pin, OUTPUT);
#endif // PIN_5V_EN digitalWrite(en_pin, HIGH);
}
if (se_pin != GPIO_NUM_NC) {
pinMode(se_pin, OUTPUT);
digitalWrite(se_pin, HIGH);
}
if (pin_5v_en != GPIO_NUM_NC) {
pinMode(pin_5v_en, OUTPUT);
digitalWrite(pin_5v_en, HIGH);
}
// Inverters and batteries are expected to initialize their serial port in their setup-function // Inverters and batteries are expected to initialize their serial port in their setup-function
// for RS485 or Modbus comms. // for RS485 or Modbus comms.

View file

@ -6,9 +6,9 @@
* *
* @param[in] void * @param[in] void
* *
* @return void * @return true if init was successful, false otherwise.
*/ */
void init_rs485(); bool init_rs485();
// Defines an interface for any object that needs to receive a signal to handle RS485 comm. // Defines an interface for any object that needs to receive a signal to handle RS485 comm.
// Can be extended later for more complex operation. // Can be extended later for more complex operation.

View file

@ -227,8 +227,6 @@ typedef struct {
float CPU_temperature = 0; float CPU_temperature = 0;
/** array with type of battery used, for displaying on webserver */ /** array with type of battery used, for displaying on webserver */
char battery_protocol[64] = {0}; char battery_protocol[64] = {0};
/** array with type of inverter protocol used, for displaying on webserver */
char inverter_protocol[64] = {0};
/** array with type of battery used, for displaying on webserver */ /** array with type of battery used, for displaying on webserver */
char shunt_protocol[64] = {0}; char shunt_protocol[64] = {0};
/** array with type of inverter brand used, for displaying on webserver */ /** array with type of inverter brand used, for displaying on webserver */

View file

@ -0,0 +1,30 @@
#include "hal.h"
#include "../../../USER_SETTINGS.h"
#include "hw_3LB.h"
#include "hw_devkit.h"
#include "hw_lilygo.h"
#include "hw_stark.h"
extern Esp32Hal* esp32hal;
void init_hal() {
#if defined(HW_LILYGO)
esp32hal = new LilyGoHal();
#elif defined(HW_STARK)
esp32hal = new StarkHal();
#elif defined(HW_3LB)
esp32hal = new ThreeLBHal();
#elif defined(HW_DEVKIT)
esp32hal = new DevKitHal();
#else
#error "No HW defined."
#endif
}
unsigned long millis();
bool Esp32Hal::system_booted_up() {
return milliseconds(millis()) > BOOTUP_TIME();
}

View file

@ -1,16 +1,144 @@
#ifndef _HAL_H_ #ifndef _HAL_H_
#define _HAL_H_ #define _HAL_H_
#include "../../../USER_SETTINGS.h" #include <soc/gpio_num.h>
#include <chrono>
#include "../../../src/devboard/utils/types.h"
#if defined(HW_LILYGO) class Esp32Hal {
#include "hw_lilygo.h" public:
#elif defined(HW_STARK) virtual const char* name() = 0;
#include "hw_stark.h"
#elif defined(HW_3LB) // Time it takes before system is considered fully started up.
#include "hw_3LB.h" virtual duration BOOTUP_TIME() = 0;
#elif defined(HW_DEVKIT) virtual bool system_booted_up();
#include "hw_devkit.h"
#endif // Core assignment
virtual int CORE_FUNCTION_CORE() { return 1; }
virtual int MODBUS_CORE() { return 0; }
virtual int WIFI_CORE() { return 0; }
template <typename... Pins>
bool alloc_pins(const char* name, Pins... pins) {
std::vector<gpio_num_t> requested_pins = {static_cast<gpio_num_t>(pins)...};
for (gpio_num_t pin : requested_pins) {
if (pin < 0) {
// Event: {name} attempted to allocate pin that wasn't defined for the selected HW.
return false;
}
auto it = allocated_pins.find(pin);
if (it != allocated_pins.end()) {
// Event: GPIO conflict for pin {pin} between name and it->second.
//std::cerr << "Pin " << pin << " already allocated to \"" << it->second << "\".\n";
return false;
}
}
for (gpio_num_t pin : requested_pins) {
allocated_pins[pin] = name;
}
return true;
}
// Helper to forward vector to variadic template
template <typename Vec, size_t... Is>
bool alloc_pins_from_vector(const char* name, const Vec& pins, std::index_sequence<Is...>) {
return alloc_pins(name, pins[Is]...);
}
template <typename... Pins>
bool alloc_pins_ignore_unused(const char* name, Pins... pins) {
std::vector<gpio_num_t> valid_pins;
for (gpio_num_t pin : std::vector<gpio_num_t>{static_cast<gpio_num_t>(pins)...}) {
if (pin != GPIO_NUM_NC) {
valid_pins.push_back(pin);
}
}
return alloc_pins_from_vector(name, valid_pins, std::make_index_sequence<sizeof...(pins)>{});
}
virtual bool always_enable_bms_power() { return false; }
virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_NC; }
virtual gpio_num_t RS485_EN_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t RS485_SE_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t CAN_RX_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t CAN_SE_PIN() { return GPIO_NUM_NC; }
// CAN_ADDON
// SCK input of MCP2515
virtual gpio_num_t MCP2515_SCK() { return GPIO_NUM_NC; }
// SDI input of MCP2515
virtual gpio_num_t MCP2515_MOSI() { return GPIO_NUM_NC; }
// SDO output of MCP2515
virtual gpio_num_t MCP2515_MISO() { return GPIO_NUM_NC; }
// CS input of MCP2515
virtual gpio_num_t MCP2515_CS() { return GPIO_NUM_NC; }
// INT output of MCP2515
virtual gpio_num_t MCP2515_INT() { return GPIO_NUM_NC; }
// CANFD_ADDON defines for MCP2517
virtual gpio_num_t MCP2517_SCK() { return GPIO_NUM_NC; }
virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_NC; }
virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_NC; }
virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_NC; }
virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_NC; }
// CHAdeMO support pin dependencies
virtual gpio_num_t CHADEMO_PIN_2() { return GPIO_NUM_NC; }
virtual gpio_num_t CHADEMO_PIN_10() { return GPIO_NUM_NC; }
virtual gpio_num_t CHADEMO_PIN_7() { return GPIO_NUM_NC; }
virtual gpio_num_t CHADEMO_PIN_4() { return GPIO_NUM_NC; }
virtual gpio_num_t CHADEMO_LOCK() { return GPIO_NUM_NC; }
// Contactor handling
virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t BMS_POWER() { return GPIO_NUM_NC; }
virtual gpio_num_t BMS2_POWER() { return GPIO_NUM_NC; }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_NC; }
// Automatic precharging
virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_NC; }
// SMA CAN contactor pins
virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_LED_PIN() { return GPIO_NUM_NC; }
// SD card
virtual gpio_num_t SD_MISO_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t SD_MOSI_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t SD_SCLK_PIN() { return GPIO_NUM_NC; }
virtual gpio_num_t SD_CS_PIN() { return GPIO_NUM_NC; }
// LED
virtual gpio_num_t LED_PIN() { return GPIO_NUM_NC; }
virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; }
// Equipment stop pin
virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_NC; }
// Battery wake up pins
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_NC; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_NC; }
private:
std::unordered_map<gpio_num_t, std::string> allocated_pins;
};
extern Esp32Hal* esp32hal;
void init_hal();
#endif #endif

View file

@ -1,15 +1,16 @@
#ifndef __HW_3LB_H__ #ifndef __HW_3LB_H__
#define __HW_3LB_H__ #define __HW_3LB_H__
#include "hal.h"
class ThreeLBHal : public Esp32Hal {
public:
const char* name() { return "3LB board"; }
// Board boot-up time // Board boot-up time
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up duration BOOTUP_TIME() { return milliseconds(1000); }
};
// Core assignment /*// RS485
#define CORE_FUNCTION_CORE 1
#define MODBUS_CORE 0
#define WIFI_CORE 0
// RS485
//#define PIN_5V_EN 16 //#define PIN_5V_EN 16
//#define RS485_EN_PIN 17 // 17 /RE //#define RS485_EN_PIN 17 // 17 /RE
#define RS485_TX_PIN 1 // 21 #define RS485_TX_PIN 1 // 21
@ -78,7 +79,7 @@
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 #define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ // ----- Error checks below, don't change (can't be moved to separate file) -----
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED
#define HW_CONFIGURED #define HW_CONFIGURED
#else #else
@ -91,18 +92,6 @@
#endif #endif
#endif #endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef BMW_I3_BATTERY #ifdef BMW_I3_BATTERY
#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN1) #if defined(CONTACTOR_CONTROL) && defined(WUP_PIN1)
#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL #error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL
@ -111,5 +100,5 @@
#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL #error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL
#endif #endif
#endif #endif
*/
#endif #endif

View file

@ -14,10 +14,11 @@ The pin layout below supports the following:
// Board boot-up time // Board boot-up time
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up #define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
// Core assignment class DevKitHal : public Esp32Hal {
#define CORE_FUNCTION_CORE 1 public:
#define MODBUS_CORE 0 const char* name() { return "ESP32 DevKit V1"; }
#define WIFI_CORE 0 duration BOOTUP_TIME() { return milliseconds(1000); }
};
// RS485 // RS485
#define RS485_TX_PIN GPIO_NUM_1 #define RS485_TX_PIN GPIO_NUM_1

View file

@ -1,82 +1,84 @@
#ifndef __HW_LILYGO_H__ #ifndef __HW_LILYGO_H__
#define __HW_LILYGO_H__ #define __HW_LILYGO_H__
// Board boot-up time #include "hal.h"
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
// Core assignment class LilyGoHal : public Esp32Hal {
#define CORE_FUNCTION_CORE 1 public:
#define MODBUS_CORE 0 const char* name() { return "LilyGo T-CAN485"; }
#define WIFI_CORE 0 duration BOOTUP_TIME() { return milliseconds(1000); }
// RS485 virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_16; }
#define PIN_5V_EN 16 virtual gpio_num_t RS485_EN_PIN() { return GPIO_NUM_17; }
#define RS485_EN_PIN 17 // 17 /RE virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_22; }
#define RS485_TX_PIN 22 // 21 virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_21; }
#define RS485_RX_PIN 21 // 22 virtual gpio_num_t RS485_SE_PIN() { return GPIO_NUM_19; }
#define RS485_SE_PIN 19 // 22 /SHDN
// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_27; }
#define CAN_1_TYPE ESP32CAN virtual gpio_num_t CAN_RX_PIN() { return GPIO_NUM_26; }
virtual gpio_num_t CAN_SE_PIN() { return GPIO_NUM_23; }
// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB // CAN_ADDON
#define CAN_TX_PIN GPIO_NUM_27 // SCK input of MCP2515
#define CAN_RX_PIN GPIO_NUM_26 virtual gpio_num_t MCP2515_SCK() { return GPIO_NUM_12; }
#define CAN_SE_PIN 23 // SDI input of MCP2515
virtual gpio_num_t MCP2515_MOSI() { return GPIO_NUM_5; }
// SDO output of MCP2515
virtual gpio_num_t MCP2515_MISO() { return GPIO_NUM_34; }
// CS input of MCP2515
virtual gpio_num_t MCP2515_CS() { return GPIO_NUM_18; }
// INT output of MCP2515
virtual gpio_num_t MCP2515_INT() { return GPIO_NUM_35; }
// CAN2 defines below // CANFD_ADDON defines for MCP2517
virtual gpio_num_t MCP2517_SCK() { return GPIO_NUM_12; }
// CAN_ADDON defines virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_5; }
#define MCP2515_SCK 12 // SCK input of MCP2515 virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_34; }
#define MCP2515_MOSI 5 // SDI input of MCP2515 virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_18; }
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_35; }
#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
// CANFD_ADDON defines
#define MCP2517_SCK 12 // SCK input of MCP2517
#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
// CHAdeMO support pin dependencies // CHAdeMO support pin dependencies
#define CHADEMO_PIN_2 12 virtual gpio_num_t CHADEMO_PIN_2() { return GPIO_NUM_12; }
#define CHADEMO_PIN_10 5 virtual gpio_num_t CHADEMO_PIN_10() { return GPIO_NUM_5; }
#define CHADEMO_PIN_7 34 virtual gpio_num_t CHADEMO_PIN_7() { return GPIO_NUM_34; }
#define CHADEMO_PIN_4 35 virtual gpio_num_t CHADEMO_PIN_4() { return GPIO_NUM_35; }
#define CHADEMO_LOCK 18 virtual gpio_num_t CHADEMO_LOCK() { return GPIO_NUM_18; }
// Contactor handling // Contactor handling
#define POSITIVE_CONTACTOR_PIN 32 virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_32; }
#define NEGATIVE_CONTACTOR_PIN 33 virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_33; }
#define PRECHARGE_PIN 25 virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; }
#define BMS_POWER 18 // Note, this pin collides with CAN add-ons and Chademo virtual gpio_num_t BMS_POWER() { return GPIO_NUM_18; }
#define SECOND_BATTERY_CONTACTORS_PIN 15 //Note, this pin collides with SD card pins //virtual gpio_num_t BMS2_POWER() { return GPIO_NUM_NC; }
virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_15; }
// Automatic precharging // Automatic precharging
#define HIA4V1_PIN 25 virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_25; }
#define INVERTER_DISCONNECT_CONTACTOR_PIN 32 virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_32; }
// SMA CAN contactor pins // SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 5 virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_5; }
// virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_LED_PIN() { return GPIO_NUM_NC; }
// SD card // SD card
#define SD_MISO_PIN 2 virtual gpio_num_t SD_MISO_PIN() { return GPIO_NUM_2; }
#define SD_MOSI_PIN 15 virtual gpio_num_t SD_MOSI_PIN() { return GPIO_NUM_15; }
#define SD_SCLK_PIN 14 virtual gpio_num_t SD_SCLK_PIN() { return GPIO_NUM_14; }
#define SD_CS_PIN 13 virtual gpio_num_t SD_CS_PIN() { return GPIO_NUM_13; }
// LED // LED
#define LED_PIN 4 virtual gpio_num_t LED_PIN() { return GPIO_NUM_4; }
#define LED_MAX_BRIGHTNESS 40
// Equipment stop pin // Equipment stop pin
#define EQUIPMENT_STOP_PIN 35 virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_35; }
// BMW_I3_BATTERY wake up pin // Battery wake up pins
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; }
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; }
};
#define HalClass LilyGoHal
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ /* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED
@ -85,42 +87,4 @@
#error Multiple HW defined! Please select a single HW #error Multiple HW defined! Please select a single HW
#endif #endif
#if defined(CAN_ADDON) && defined(CANFD_ADDON)
// Check that user did not try to use dual can and fd-can on same hardware pins
#error CAN_ADDON AND CANFD_ADDON CANNOT BE USED SIMULTANEOUSLY
#endif
#if defined(SMA_BYD_H_CAN) || defined(SMA_BYD_HVS_CAN) || defined(SMA_TRIPOWER_CAN)
#if defined(CAN_ADDON) || defined(CANFD_ADDON)
#error Pin 5 used by both Enable line and for CAN-ADDON. Please reconfigure this, and remove this line to proceed
#endif
#endif
#ifdef CHADEMO_BATTERY
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef BMW_I3_BATTERY
#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN1)
#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL
#endif
#if defined(CONTACTOR_CONTROL) && defined(WUP_PIN2)
#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL
#endif
#endif
#endif #endif

View file

@ -1,6 +1,8 @@
#ifndef __HW_STARK_H__ #ifndef __HW_STARK_H__
#define __HW_STARK_H__ #define __HW_STARK_H__
#include "hal.h"
/* /*
Stark CMR v1 - DIN-rail module with 4 power outputs, 1 x rs485, 1 x can and 1 x can-fd channel. Stark CMR v1 - DIN-rail module with 4 power outputs, 1 x rs485, 1 x can and 1 x can-fd channel.
For more information on this board visit the project discord or contact johan@redispose.se For more information on this board visit the project discord or contact johan@redispose.se
@ -15,15 +17,13 @@ GPIOs on extra header
* GPIO 15 (JTAG TDO) * GPIO 15 (JTAG TDO)
*/ */
// Board boot-up time class StarkHal : public Esp32Hal {
#define BOOTUP_TIME 5000 // Time in ms it takes before system is considered fully started up public:
const char* name() { return "Stark CMR Module"; }
duration BOOTUP_TIME() { return milliseconds(5000); }
};
// Core assignment /*// RS485
#define CORE_FUNCTION_CORE 1
#define MODBUS_CORE 0
#define WIFI_CORE 0
// RS485
// #define PIN_5V_EN 16 // Not needed, GPIO 16 has hardware pullup for PSRAM compatibility // #define PIN_5V_EN 16 // Not needed, GPIO 16 has hardware pullup for PSRAM compatibility
// #define RS485_EN_PIN 17 // Not needed, GPIO 17 is used as SCK input of MCP2517 // #define RS485_EN_PIN 17 // Not needed, GPIO 17 is used as SCK input of MCP2517
#define RS485_TX_PIN 22 #define RS485_TX_PIN 22
@ -70,7 +70,7 @@ GPIOs on extra header
#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 #define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1
#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 #define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ // ----- Error checks below, don't change (can't be moved to separate file) -----
#ifndef HW_CONFIGURED #ifndef HW_CONFIGURED
#define HW_CONFIGURED #define HW_CONFIGURED
#else #else
@ -85,5 +85,6 @@ GPIOs on extra header
#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL #error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL
#endif #endif
#endif // BMW_I3_BATTERY #endif // BMW_I3_BATTERY
*/
#endif // __HW_STARK_H__ #endif // __HW_STARK_H__

View file

@ -246,13 +246,13 @@ static bool publish_common_info(void) {
doc["pause_status"] = get_emulator_pause_status(); doc["pause_status"] = get_emulator_pause_status();
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && esp32hal->system_booted_up()) {
set_battery_attributes(doc, datalayer.battery, "", battery->supports_charged_energy()); set_battery_attributes(doc, datalayer.battery, "", battery->supports_charged_energy());
} }
if (battery2) { if (battery2) {
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery) //only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) { if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && esp32hal->system_booted_up()) {
set_battery_attributes(doc, datalayer.battery2, "_2", battery2->supports_charged_energy()); set_battery_attributes(doc, datalayer.battery2, "_2", battery2->supports_charged_energy());
} }
} }

View file

@ -1,9 +1,6 @@
#include "sdcard.h" #include "sdcard.h"
#include "freertos/ringbuf.h" #include "freertos/ringbuf.h"
#if defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && \
defined(SD_MISO_PIN) // ensure code is only compiled if all SD card pins are defined
File can_log_file; File can_log_file;
File log_file; File log_file;
RingbufHandle_t can_bufferHandle; RingbufHandle_t can_bufferHandle;
@ -185,11 +182,20 @@ void init_logging_buffers() {
#endif // defined(LOG_TO_SD) #endif // defined(LOG_TO_SD)
} }
void init_sdcard() { bool init_sdcard() {
pinMode(SD_MISO_PIN, INPUT_PULLUP); auto miso_pin = esp32hal->SD_MISO_PIN();
auto mosi_pin = esp32hal->SD_MOSI_PIN();
auto miso_pin = esp32hal->SD_MISO_PIN();
auto sclk_pin = esp32hal->SD_SCLK_PIN();
SD_MMC.setPins(SD_SCLK_PIN, SD_MOSI_PIN, SD_MISO_PIN); if (!esp32hal->alloc_pins("SD Card", miso_pin, mosi_pin)) {
return false;
}
pinMode(miso_pin, INPUT_PULLUP);
SD_MMC.setPins(sclk_pin, mosi_pin, miso_pin);
if (!SD_MMC.begin("/root", true, true, SDMMC_FREQ_HIGHSPEED)) { if (!SD_MMC.begin("/root", true, true, SDMMC_FREQ_HIGHSPEED)) {
set_event_latched(EVENT_SD_INIT_FAILED, 0); set_event_latched(EVENT_SD_INIT_FAILED, 0);
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
@ -208,6 +214,8 @@ void init_sdcard() {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
log_sdcard_details(); log_sdcard_details();
#endif // DEBUG_LOG #endif // DEBUG_LOG
return true;
} }
void log_sdcard_details() { void log_sdcard_details() {
@ -245,4 +253,3 @@ void log_sdcard_details() {
logging.println(" MB"); logging.println(" MB");
} }
} }
#endif // defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && defined(SD_MISO_PIN)

View file

@ -6,14 +6,12 @@
#include "../hal/hal.h" #include "../hal/hal.h"
#include "../utils/events.h" #include "../utils/events.h"
#if defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && \
defined(SD_MISO_PIN) // ensure code is only compiled if all SD card pins are defined
#define CAN_LOG_FILE "/canlog.txt" #define CAN_LOG_FILE "/canlog.txt"
#define LOG_FILE "/log.txt" #define LOG_FILE "/log.txt"
void init_logging_buffers(); void init_logging_buffers();
void init_sdcard(); bool init_sdcard();
void log_sdcard_details(); void log_sdcard_details();
void add_can_frame_to_buffer(CAN_frame frame, frameDirection msgDir); void add_can_frame_to_buffer(CAN_frame frame, frameDirection msgDir);
@ -29,5 +27,4 @@ void pause_log_writing();
void add_log_to_buffer(const uint8_t* buffer, size_t size); void add_log_to_buffer(const uint8_t* buffer, size_t size);
void write_log_to_sdcard(); void write_log_to_sdcard();
#endif // defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && defined(SD_MISO_PIN)
#endif // SDCARD_H #endif // SDCARD_H

View file

@ -16,16 +16,23 @@ static const float heartbeat_peak1 = 0.80;
static const float heartbeat_peak2 = 0.55; static const float heartbeat_peak2 = 0.55;
static const float heartbeat_deviation = 0.05; static const float heartbeat_deviation = 0.05;
static LED led(datalayer.battery.status.led_mode); static LED* led;
void led_init(void) { bool led_init(void) {
led.init(); if (!esp32hal->alloc_pins("LED", esp32hal->LED_PIN())) {
return false;
}
led = new LED(datalayer.battery.status.led_mode, esp32hal->LED_PIN(), esp32hal->LED_MAX_BRIGHTNESS());
led->init();
return true;
} }
void led_exe(void) { void led_exe(void) {
led.exe(); led->exe();
} }
led_color led_get_color() { led_color led_get_color() {
return led.color; return led->color;
} }
void LED::exe(void) { void LED::exe(void) {
@ -61,7 +68,7 @@ void LED::exe(void) {
break; break;
case EVENT_LEVEL_ERROR: case EVENT_LEVEL_ERROR:
color = led_color::RED; color = led_color::RED;
pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness pixels.setPixelColor(0, COLOR_RED(esp32hal->LED_MAX_BRIGHTNESS())); // Red LED full brightness
break; break;
default: default:
break; break;
@ -126,7 +133,7 @@ void LED::heartbeat_run(void) {
brightness_f = map_float(period_pct, 0.55f, 1.00f, heartbeat_base + heartbeat_deviation * 2, heartbeat_base); brightness_f = map_float(period_pct, 0.55f, 1.00f, heartbeat_base + heartbeat_deviation * 2, heartbeat_base);
} }
brightness = (uint8_t)(brightness_f * LED_MAX_BRIGHTNESS); brightness = (uint8_t)(brightness_f * esp32hal->LED_MAX_BRIGHTNESS());
} }
uint8_t LED::up_down(float middle_point_f) { uint8_t LED::up_down(float middle_point_f) {
@ -138,7 +145,7 @@ uint8_t LED::up_down(float middle_point_f) {
if (ms < middle_point) { if (ms < middle_point) {
brightness = map_uint16(ms, 0, middle_point, 0, max_brightness); brightness = map_uint16(ms, 0, middle_point, 0, max_brightness);
} else { } else {
brightness = LED_MAX_BRIGHTNESS - map_uint16(ms, middle_point, LED_PERIOD_MS, 0, max_brightness); brightness = esp32hal->LED_MAX_BRIGHTNESS() - map_uint16(ms, middle_point, LED_PERIOD_MS, 0, max_brightness);
} }
return CONSTRAIN(brightness, 0, max_brightness); return CONSTRAIN(brightness, 0, max_brightness);
} }

View file

@ -8,14 +8,14 @@ class LED {
public: public:
led_color color = led_color::GREEN; led_color color = led_color::GREEN;
LED() LED(gpio_num_t pin, uint8_t maxBrightness)
: pixels(1, LED_PIN, NEO_GRB), : pixels(1, pin, NEO_GRB),
max_brightness(LED_MAX_BRIGHTNESS), max_brightness(maxBrightness),
brightness(LED_MAX_BRIGHTNESS), brightness(maxBrightness),
mode(led_mode_enum::CLASSIC) {} mode(led_mode_enum::CLASSIC) {}
LED(led_mode_enum mode) LED(led_mode_enum mode, gpio_num_t pin, uint8_t maxBrightness)
: pixels(1, LED_PIN, NEO_GRB), max_brightness(LED_MAX_BRIGHTNESS), brightness(LED_MAX_BRIGHTNESS), mode(mode) {} : pixels(1, pin, NEO_GRB), max_brightness(maxBrightness), brightness(maxBrightness), mode(mode) {}
void exe(void); void exe(void);
void init(void) { pixels.begin(); } void init(void) { pixels.begin(); }
@ -33,7 +33,7 @@ class LED {
uint8_t up_down(float middle_point_f); uint8_t up_down(float middle_point_f);
}; };
void led_init(void); bool led_init(void);
void led_exe(void); void led_exe(void);
led_color led_get_color(void); led_color led_get_color(void);

View file

@ -1,8 +1,12 @@
#ifndef _TYPES_H_ #ifndef _TYPES_H_
#define _TYPES_H_ #define _TYPES_H_
#include <chrono>
#include <string> #include <string>
using milliseconds = std::chrono::milliseconds;
using duration = std::chrono::duration<unsigned long, std::ratio<1, 1000>>;
enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 }; enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 };
enum real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 }; enum real_bms_status_enum { BMS_DISCONNECTED = 0, BMS_STANDBY = 1, BMS_ACTIVE = 2, BMS_FAULT = 3 };
enum battery_chemistry_enum { NCA, NMC, LFP }; enum battery_chemistry_enum { NCA, NMC, LFP };
@ -42,7 +46,20 @@ enum PrechargeState {
#define CAN_STILL_ALIVE 60 #define CAN_STILL_ALIVE 60
// Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event // Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event
typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CANFD_ADDON_MCP2518 = 3 } CAN_Interface; enum CAN_Interface {
// Native CAN port on the LilyGo & Stark hardware
CAN_NATIVE = 0,
// Native CANFD port on the Stark CMR hardware
CANFD_NATIVE = 1,
// Add-on CAN MCP2515 connected to GPIO pins
CAN_ADDON_MCP2515 = 2,
// Add-on CAN-FD MCP2518 connected to GPIO pins
CANFD_ADDON_MCP2518 = 3
};
extern const char* getCANInterfaceName(CAN_Interface interface); extern const char* getCANInterfaceName(CAN_Interface interface);
/* CAN Frame structure */ /* CAN Frame structure */

View file

@ -2,6 +2,7 @@
#include <Arduino.h> #include <Arduino.h>
#include "../../../src/communication/contactorcontrol/comm_contactorcontrol.h" #include "../../../src/communication/contactorcontrol/comm_contactorcontrol.h"
#include "../../charger/CHARGERS.h" #include "../../charger/CHARGERS.h"
#include "../../communication/can/comm_can.h"
#include "../../communication/nvm/comm_nvm.h" #include "../../communication/nvm/comm_nvm.h"
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
#include "../../include.h" #include "../../include.h"
@ -67,6 +68,19 @@ void render_checkbox(String& content, const char* label, bool enabled, const cha
content += " value='on'/>"; content += " value='on'/>";
} }
const char* name_for_button_type(STOP_BUTTON_BEHAVIOR behavior) {
switch (behavior) {
case STOP_BUTTON_BEHAVIOR::LATCHING_SWITCH:
return "Latching";
case STOP_BUTTON_BEHAVIOR::MOMENTARY_SWITCH:
return "Momentary";
case STOP_BUTTON_BEHAVIOR::NOT_CONNECTED:
return "Not connected";
default:
return nullptr;
}
}
String settings_processor(const String& var) { String settings_processor(const String& var) {
if (var == "X") { if (var == "X") {
String content = ""; String content = "";
@ -116,6 +130,12 @@ String settings_processor(const String& var) {
options_for_enum((ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None), name_for_charger_type); options_for_enum((ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None), name_for_charger_type);
content += "</select>"; content += "</select>";
content += "<label>Equipment stop button: </label><select style='max-width: 250px;' name='EQSTOP'>";
content +=
options_for_enum((STOP_BUTTON_BEHAVIOR)settings.getUInt("EQSTOP", (int)STOP_BUTTON_BEHAVIOR::NOT_CONNECTED),
name_for_button_type);
content += "</select>";
// TODO: Generalize settings: define settings in one place and use the definitions to render // TODO: Generalize settings: define settings in one place and use the definitions to render
// UI and handle load/save // UI and handle load/save
render_checkbox(content, "Double battery", settings.getBool("DBLBTR"), "DBLBTR"); render_checkbox(content, "Double battery", settings.getBool("DBLBTR"), "DBLBTR");
@ -124,6 +144,7 @@ String settings_processor(const String& var) {
render_checkbox(content, "PWM contactor control", settings.getBool("PWMCNTCTRL"), "PWMCNTCTRL"); render_checkbox(content, "PWM contactor control", settings.getBool("PWMCNTCTRL"), "PWMCNTCTRL");
render_checkbox(content, "Periodic BMS reset", settings.getBool("PERBMSRESET"), "PERBMSRESET"); render_checkbox(content, "Periodic BMS reset", settings.getBool("PERBMSRESET"), "PERBMSRESET");
render_checkbox(content, "Remote BMS reset", settings.getBool("REMBMSRESET"), "REMBMSRESET"); render_checkbox(content, "Remote BMS reset", settings.getBool("REMBMSRESET"), "REMBMSRESET");
render_checkbox(content, "Use CanFD as classic CAN", settings.getBool("CANFDASCAN"), "CANFDASCAN");
content += content +=
"<div style='grid-column: span 2; text-align: center; padding-top: 10px;'><button " "<div style='grid-column: span 2; text-align: center; padding-top: 10px;'><button "
@ -454,19 +475,19 @@ const char* getCANInterfaceName(CAN_Interface interface) {
case CAN_NATIVE: case CAN_NATIVE:
return "CAN"; return "CAN";
case CANFD_NATIVE: case CANFD_NATIVE:
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN if (use_canfd_as_can) {
return "CAN-FD Native (Classic CAN)"; return "CAN-FD Native (Classic CAN)";
#else } else {
return "CAN-FD Native"; return "CAN-FD Native";
#endif }
case CAN_ADDON_MCP2515: case CAN_ADDON_MCP2515:
return "Add-on CAN via GPIO MCP2515"; return "Add-on CAN via GPIO MCP2515";
case CANFD_ADDON_MCP2518: case CANFD_ADDON_MCP2518:
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN if (use_canfd_as_can) {
return "Add-on CAN-FD via GPIO MCP2518 (Classic CAN)"; return "Add-on CAN-FD via GPIO MCP2518 (Classic CAN)";
#else } else {
return "Add-on CAN-FD via GPIO MCP2518"; return "Add-on CAN-FD via GPIO MCP2518";
#endif }
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }

View file

@ -394,7 +394,8 @@ void init_webserver() {
bool newValue; bool newValue;
}; };
const char* boolSettingNames[] = {"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET"}; const char* boolSettingNames[] = {"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL",
"PERBMSRESET", "REMBMSRESET", "CANFDASCAN"};
#ifdef COMMON_IMAGE #ifdef COMMON_IMAGE
// Handles the form POST from UI to save certain settings: battery/inverter type and double battery on/off // Handles the form POST from UI to save certain settings: battery/inverter type and double battery on/off
@ -419,19 +420,10 @@ void init_webserver() {
} else if (p->name() == "charger") { } else if (p->name() == "charger") {
auto type = static_cast<ChargerType>(atoi(p->value().c_str())); auto type = static_cast<ChargerType>(atoi(p->value().c_str()));
settings.saveUInt("CHGTYPE", (int)type); settings.saveUInt("CHGTYPE", (int)type);
} /*else if (p->name() == "dblbtr") { } else if (p->name() == "EQSTOP") {
newDoubleBattery = p->value() == "on"; auto type = static_cast<STOP_BUTTON_BEHAVIOR>(atoi(p->value().c_str()));
} else if (p->name() == "contctrl") { settings.saveUInt("EQSTOP", (int)type);
settings.saveBool("CNTCTRL", p->value() == "on"); }
} else if (p->name() == "contctrldbl") {
settings.saveBool("CNTCTRLDBL", p->value() == "on");
} else if (p->name() == "pwmcontctrl") {
settings.saveBool("PWMCNTCTRL", p->value() == "on");
} else if (p->name() == "PERBMSRESET") {
settings.saveBool("PERBMSRESET", p->value() == "on");
} else if (p->name() == "REMBMSRESET") {
settings.saveBool("REMBMSRESET", p->value() == "on");
}*/
for (auto& boolSetting : boolSettings) { for (auto& boolSetting : boolSettings) {
if (p->name() == boolSetting.name) { if (p->name() == boolSetting.name) {
@ -929,19 +921,8 @@ String get_firmware_info_processor(const String& var) {
if (var == "X") { if (var == "X") {
String content = ""; String content = "";
static JsonDocument doc; static JsonDocument doc;
#ifdef HW_LILYGO
doc["hardware"] = "LilyGo T-CAN485";
#endif // HW_LILYGO
#ifdef HW_STARK
doc["hardware"] = "Stark CMR Module";
#endif // HW_STARK
#ifdef HW_3LB
doc["hardware"] = "3LB board";
#endif // HW_3LB
#ifdef HW_DEVKIT
doc["hardware"] = "ESP32 DevKit V1";
#endif // HW_DEVKIT
doc["hardware"] = esp32hal->name();
doc["firmware"] = String(version_number); doc["firmware"] = String(version_number);
serializeJson(doc, content); serializeJson(doc, content);
return content; return content;
@ -1022,7 +1003,7 @@ String processor(const String& var) {
// Display which components are used // Display which components are used
if (inverter) { if (inverter) {
content += "<h4 style='color: white;'>Inverter protocol: "; content += "<h4 style='color: white;'>Inverter protocol: ";
content += datalayer.system.info.inverter_protocol; content += inverter->name();
content += " "; content += " ";
content += datalayer.system.info.inverter_brand; content += datalayer.system.info.inverter_brand;
content += "</h4>"; content += "</h4>";

View file

@ -19,17 +19,10 @@
/* - ERROR CHECKS BELOW, DON'T TOUCH - */ /* - ERROR CHECKS BELOW, DON'T TOUCH - */
#if !defined(HW_CONFIGURED) #if !defined(HW_LILYGO) || !defined(HW_STARK) || !defined(HW_3LB) || !defined(HW_DEVKIT)
#error You must select a target hardware in the USER_SETTINGS.h file! #error You must select a target hardware in the USER_SETTINGS.h file!
#endif #endif
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
#if !defined(CANFD_ADDON)
// Check that user did not try to use classic CAN over FD, without FD component
#error PLEASE ENABLE CANFD_ADDON TO USE CLASSIC CAN OVER CANFD INTERFACE
#endif
#endif
#ifdef HW_LILYGO #ifdef HW_LILYGO
#if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) #if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET)
#if defined(CAN_ADDON) || defined(CANFD_ADDON) || defined(CHADEMO_BATTERY) #if defined(CAN_ADDON) || defined(CANFD_ADDON) || defined(CHADEMO_BATTERY)

View file

@ -169,8 +169,3 @@ void AforeCanInverter::transmit_can(unsigned long currentMillis) {
time_to_send_info = false; time_to_send_info = false;
} }
} }
void AforeCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class AforeCanInverter : public CanInverterProtocol { class AforeCanInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);
void update_values(); void update_values();

View file

@ -169,8 +169,3 @@ void BydCanInverter::send_initial_data() {
transmit_can_frame(&BYD_3D0_2, can_config.inverter); transmit_can_frame(&BYD_3D0_2, can_config.inverter);
transmit_can_frame(&BYD_3D0_3, can_config.inverter); transmit_can_frame(&BYD_3D0_3, can_config.inverter);
} }
void BydCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class BydCanInverter : public CanInverterProtocol { class BydCanInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);
void update_values(); void update_values();

View file

@ -145,17 +145,21 @@ void BydModbusInverter::verify_inverter_modbus() {
} }
} }
void BydModbusInverter::setup(void) { // Performs one time setup at startup over CAN bus bool BydModbusInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
// Init Static data to the RTU Modbus // Init Static data to the RTU Modbus
handle_static_data(); handle_static_data();
// Init Serial2 connected to the RTU Modbus // Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2); RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); auto rx_pin = esp32hal->RS485_RX_PIN();
auto tx_pin = esp32hal->RS485_TX_PIN();
if (!esp32hal->alloc_pins(Name, rx_pin, tx_pin)) {
return false;
}
Serial2.begin(9600, SERIAL_8N1, rx_pin, tx_pin);
// Register served function code worker for server // Register served function code worker for server
MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03); MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03);
MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06); MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06);
@ -163,5 +167,7 @@ void BydModbusInverter::setup(void) { // Performs one time setup at startup ove
MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23); MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23);
// Start ModbusRTU background task // Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE); MBserver.begin(Serial2, esp32hal->MODBUS_CORE());
return true;
} }

View file

@ -10,7 +10,8 @@
class BydModbusInverter : public ModbusInverterProtocol { class BydModbusInverter : public ModbusInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
bool setup() override;
void update_values(); void update_values();
static constexpr char* Name = "BYD 11kWh HVM battery over Modbus RTU"; static constexpr char* Name = "BYD 11kWh HVM battery over Modbus RTU";

View file

@ -365,8 +365,3 @@ void FerroampCanInverter::send_system_data() { //System equipment information
transmit_can_frame(&PYLON_4291, can_config.inverter); transmit_can_frame(&PYLON_4291, can_config.inverter);
#endif #endif
} }
void FerroampCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class FerroampCanInverter : public CanInverterProtocol { class FerroampCanInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -561,7 +561,3 @@ void FoxessCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
} }
} }
} }
void FoxessCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class FoxessCanInverter : public CanInverterProtocol { class FoxessCanInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -449,8 +449,3 @@ void GrowattHvInverter::transmit_can(unsigned long currentMillis) {
} }
} }
} }
void GrowattHvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class GrowattHvInverter : public CanInverterProtocol { class GrowattHvInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -202,8 +202,3 @@ void GrowattLvInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
void GrowattLvInverter::transmit_can(unsigned long currentMillis) { void GrowattLvInverter::transmit_can(unsigned long currentMillis) {
// No periodic sending for this battery type. Data is sent when inverter requests it // No periodic sending for this battery type. Data is sent when inverter requests it
} }
void GrowattLvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class GrowattLvInverter : public CanInverterProtocol { class GrowattLvInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -81,9 +81,9 @@ extern const char* name_for_inverter_type(InverterProtocolType type) {
#error "Compile time SELECTED_INVERTER_CLASS should not be defined with COMMON_IMAGE" #error "Compile time SELECTED_INVERTER_CLASS should not be defined with COMMON_IMAGE"
#endif #endif
void setup_inverter() { bool setup_inverter() {
if (inverter) { if (inverter) {
return; return true;
} }
switch (user_selected_inverter_protocol) { switch (user_selected_inverter_protocol) {
@ -160,6 +160,7 @@ void setup_inverter() {
break; break;
case InverterProtocolType::None: case InverterProtocolType::None:
return true;
case InverterProtocolType::Highest: case InverterProtocolType::Highest:
default: default:
inverter = nullptr; // Or handle as error inverter = nullptr; // Or handle as error
@ -167,23 +168,29 @@ void setup_inverter() {
} }
if (inverter) { if (inverter) {
inverter->setup(); return inverter->setup();
} }
return false;
} }
#else #else
void setup_inverter() { bool setup_inverter() {
if (inverter) { if (inverter) {
// The inverter is setup only once. // The inverter is setup only once.
return; return true;
} }
#ifdef SELECTED_INVERTER_CLASS #ifdef SELECTED_INVERTER_CLASS
inverter = new SELECTED_INVERTER_CLASS(); inverter = new SELECTED_INVERTER_CLASS();
if (inverter) { if (inverter) {
inverter->setup(); return inverter->setup();
} }
return false;
#else
return true;
#endif #endif
} }
#endif #endif

View file

@ -31,6 +31,6 @@ extern InverterProtocol* inverter;
#include "SUNGROW-CAN.h" #include "SUNGROW-CAN.h"
// Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected. // Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected.
void setup_inverter(); bool setup_inverter();
#endif #endif

View file

@ -34,7 +34,8 @@ enum class InverterInterfaceType { Can, Rs485, Modbus };
// The abstract base class for all inverter protocols // The abstract base class for all inverter protocols
class InverterProtocol { class InverterProtocol {
public: public:
virtual void setup() = 0; virtual const char* name() = 0;
virtual bool setup() {}
virtual const char* interface_name() = 0; virtual const char* interface_name() = 0;
virtual InverterInterfaceType interface_type() = 0; virtual InverterInterfaceType interface_type() = 0;

View file

@ -301,11 +301,18 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
} }
} }
void KostalInverterProtocol::setup(void) { // Performs one time setup at startup bool KostalInverterProtocol::setup(void) { // Performs one time setup at startup
datalayer.system.status.inverter_allows_contactor_closing = false; datalayer.system.status.inverter_allows_contactor_closing = false;
dbg_message("inverter_allows_contactor_closing -> false"); dbg_message("inverter_allows_contactor_closing -> false");
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
Serial2.begin(baud_rate(), SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); auto rx_pin = esp32hal->RS485_RX_PIN();
auto tx_pin = esp32hal->RS485_TX_PIN();
if (!esp32hal->alloc_pins(Name, rx_pin, tx_pin)) {
return false;
}
Serial2.begin(baud_rate(), SERIAL_8N1, rx_pin, tx_pin);
return true;
} }

View file

@ -19,7 +19,8 @@
class KostalInverterProtocol : public Rs485InverterProtocol { class KostalInverterProtocol : public Rs485InverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
bool setup() override;
void receive(); void receive();
void update_values(); void update_values();
static constexpr char* Name = "BYD battery via Kostal RS485"; static constexpr char* Name = "BYD battery via Kostal RS485";

View file

@ -352,8 +352,3 @@ void PylonInverter::send_system_data() { //System equipment information
transmit_can_frame(&PYLON_4291, can_config.inverter); transmit_can_frame(&PYLON_4291, can_config.inverter);
#endif #endif
} }
void PylonInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class PylonInverter : public CanInverterProtocol { class PylonInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -144,8 +144,3 @@ void PylonLvInverter::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&PYLON_35E, can_config.inverter); transmit_can_frame(&PYLON_35E, can_config.inverter);
} }
} }
void PylonLvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class PylonLvInverter : public CanInverterProtocol { class PylonLvInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -225,8 +225,3 @@ void SchneiderInverter::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&SE_333, can_config.inverter); transmit_can_frame(&SE_333, can_config.inverter);
} }
} }
void SchneiderInverter::setup(void) { // Performs one time setup
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class SchneiderInverter : public CanInverterProtocol { class SchneiderInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -69,16 +69,7 @@ void SmaBydHInverter::
SMA_158.data.u8[2] = 0x6A; SMA_158.data.u8[2] = 0x6A;
} }
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN control_contactor_led();
// Inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
} else {
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
LOW); // Turn off LED to indicate that SMA inverter does not allow contactor closing
}
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
// Check if Enable line is working. If we go too long without any input, raise an event // Check if Enable line is working. If we go too long without any input, raise an event
if (!datalayer.system.status.inverter_allows_contactor_closing) { if (!datalayer.system.status.inverter_allows_contactor_closing) {
@ -258,14 +249,3 @@ void SmaBydHInverter::transmit_can_init() {
transmit_can_frame(&SMA_518, can_config.inverter); transmit_can_frame(&SMA_518, can_config.inverter);
transmit_can_frame(&SMA_4D8, can_config.inverter); transmit_can_frame(&SMA_4D8, can_config.inverter);
} }
void SmaBydHInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT);
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}

View file

@ -2,23 +2,24 @@
#define SMA_BYD_H_CAN_H #define SMA_BYD_H_CAN_H
#include "../include.h" #include "../include.h"
#include "CanInverterProtocol.h" #include "SmaInverterBase.h"
#include "src/devboard/hal/hal.h" #include "src/devboard/hal/hal.h"
#ifdef SMA_BYD_H_CAN #ifdef SMA_BYD_H_CAN
#define SELECTED_INVERTER_CLASS SmaBydHInverter #define SELECTED_INVERTER_CLASS SmaBydHInverter
#endif #endif
class SmaBydHInverter : public CanInverterProtocol { class SmaBydHInverter : public SmaInverterBase {
public: public:
void setup(); SmaBydHInverter();
const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);
static constexpr char* Name = "BYD over SMA CAN"; static constexpr char* Name = "BYD over SMA CAN";
virtual bool controls_contactor() { return true; } virtual bool controls_contactor() { return true; }
virtual bool allows_contactor_closing() { return digitalRead(INVERTER_CONTACTOR_ENABLE_PIN) == 1; }
private: private:
static const int READY_STATE = 0x03; static const int READY_STATE = 0x03;

View file

@ -68,16 +68,7 @@ void SmaBydHvsInverter::
SMA_158.data.u8[2] = 0x6A; SMA_158.data.u8[2] = 0x6A;
} }
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN control_contactor_led();
// Inverter allows contactor closing
if (datalayer.system.status.inverter_allows_contactor_closing) {
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
} else {
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
LOW); // Turn off LED to indicate that SMA inverter does not allow contactor closing
}
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
// Check if Enable line is working. If we go too long without any input, raise an event // Check if Enable line is working. If we go too long without any input, raise an event
if (!datalayer.system.status.inverter_allows_contactor_closing) { if (!datalayer.system.status.inverter_allows_contactor_closing) {
@ -276,14 +267,3 @@ void SmaBydHvsInverter::transmit_can(unsigned long currentMillis) {
} }
} }
} }
void SmaBydHvsInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT);
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}

View file

@ -2,23 +2,22 @@
#define SMA_BYD_HVS_CAN_H #define SMA_BYD_HVS_CAN_H
#include "../include.h" #include "../include.h"
#include "CanInverterProtocol.h" #include "SmaInverterBase.h"
#include "src/devboard/hal/hal.h" #include "src/devboard/hal/hal.h"
#ifdef SMA_BYD_HVS_CAN #ifdef SMA_BYD_HVS_CAN
#define SELECTED_INVERTER_CLASS SmaBydHvsInverter #define SELECTED_INVERTER_CLASS SmaBydHvsInverter
#endif #endif
class SmaBydHvsInverter : public CanInverterProtocol { class SmaBydHvsInverter : public SmaInverterBase {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);
static constexpr char* Name = "BYD Battery-Box HVS over SMA CAN"; static constexpr char* Name = "BYD Battery-Box HVS over SMA CAN";
virtual bool controls_contactor() { return true; } virtual bool controls_contactor() { return true; }
virtual bool allows_contactor_closing() { return digitalRead(INVERTER_CONTACTOR_ENABLE_PIN) == 1; }
private: private:
static const int READY_STATE = 0x03; static const int READY_STATE = 0x03;

View file

@ -107,8 +107,3 @@ void SmaLvInverter::transmit_can(unsigned long currentMillis) {
} }
} }
} }
void SmaLvInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class SmaLvInverter : public CanInverterProtocol { class SmaLvInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -65,6 +65,8 @@ void SmaTripowerInverter::
SMA_4D8.data.u8[6] = READY_STATE; SMA_4D8.data.u8[6] = READY_STATE;
} }
control_contactor_led();
// Check if Enable line is working. If we go too long without any input, raise an event // Check if Enable line is working. If we go too long without any input, raise an event
if (!datalayer.system.status.inverter_allows_contactor_closing) { if (!datalayer.system.status.inverter_allows_contactor_closing) {
timeWithoutInverterAllowsContactorClosing++; timeWithoutInverterAllowsContactorClosing++;
@ -181,14 +183,3 @@ void SmaTripowerInverter::transmit_can_init() {
pushFrame(&SMA_4D8); pushFrame(&SMA_4D8);
pushFrame(&SMA_518, [this]() { this->completePairing(); }); pushFrame(&SMA_518, [this]() { this->completePairing(); });
} }
void SmaTripowerInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT);
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}

View file

@ -2,23 +2,22 @@
#define SMA_CAN_TRIPOWER_H #define SMA_CAN_TRIPOWER_H
#include "../include.h" #include "../include.h"
#include "CanInverterProtocol.h" #include "SmaInverterBase.h"
#include "src/devboard/hal/hal.h" #include "src/devboard/hal/hal.h"
#ifdef SMA_TRIPOWER_CAN #ifdef SMA_TRIPOWER_CAN
#define SELECTED_INVERTER_CLASS SmaTripowerInverter #define SELECTED_INVERTER_CLASS SmaTripowerInverter
#endif #endif
class SmaTripowerInverter : public CanInverterProtocol { class SmaTripowerInverter : public SmaInverterBase {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);
static constexpr char* Name = "SMA Tripower CAN"; static constexpr char* Name = "SMA Tripower CAN";
virtual bool controls_contactor() { return true; } virtual bool controls_contactor() { return true; }
virtual bool allows_contactor_closing() { return digitalRead(INVERTER_CONTACTOR_ENABLE_PIN) == 1; }
private: private:
const int READY_STATE = 0x03; const int READY_STATE = 0x03;

View file

@ -66,8 +66,3 @@ void SofarInverter::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&SOFAR_35A, can_config.inverter); transmit_can_frame(&SOFAR_35A, can_config.inverter);
} }
} }
void SofarInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class SofarInverter : public CanInverterProtocol { class SofarInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -207,8 +207,7 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
} }
} }
void SolaxInverter::setup(void) { // Performs one time setup at startup bool SolaxInverter::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
return true;
} }

View file

@ -10,7 +10,8 @@
class SolaxInverter : public CanInverterProtocol { class SolaxInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
bool setup();
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -352,8 +352,3 @@ void SungrowInverter::transmit_can(unsigned long currentMillis) {
} }
} }
} }
void SungrowInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, Name, 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -10,7 +10,7 @@
class SungrowInverter : public CanInverterProtocol { class SungrowInverter : public CanInverterProtocol {
public: public:
void setup(); const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame); void map_can_frame_to_variable(CAN_frame rx_frame);

View file

@ -0,0 +1,51 @@
#ifndef _SMA_INVERTER_BASE_H
#define _SMA_INVERTER_BASE_H
#include "../datalayer/datalayer.h"
#include "CanInverterProtocol.h"
#include "src/devboard/hal/hal.h"
class SmaInverterBase : public CanInverterProtocol {
public:
SmaInverterBase() { contactorEnablePin = esp32hal->INVERTER_CONTACTOR_ENABLE_PIN(); }
bool allows_contactor_closing() override { return digitalRead(contactorEnablePin) == 1; }
virtual bool setup() override {
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
if (!esp32hal->alloc_pins("SMA inverter", contactorEnablePin)) {
return false;
}
pinMode(contactorEnablePin, INPUT);
contactorLedPin = esp32hal->INVERTER_CONTACTOR_ENABLE_LED_PIN();
if (contactorLedPin != GPIO_NUM_NC) {
if (!esp32hal->alloc_pins("SMA inverter", contactorLedPin)) {
return false;
}
pinMode(contactorLedPin, OUTPUT);
digitalWrite(contactorLedPin, LOW); // Turn LED off, until inverter allows contactor closing
}
return true;
}
protected:
void control_contactor_led() {
if (contactorLedPin != GPIO_NUM_NC) {
if (datalayer.system.status.inverter_allows_contactor_closing) {
digitalWrite(contactorLedPin,
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
} else {
digitalWrite(contactorLedPin,
LOW); // Turn off LED to indicate that SMA inverter does not allow contactor closing
}
}
}
private:
gpio_num_t contactorEnablePin;
gpio_num_t contactorLedPin;
};
#endif