Battery and inverter can bus can be selected in the UI

This commit is contained in:
Jaakko Haakana 2025-06-29 20:33:45 +03:00
parent c63ae6eb23
commit e3de4e546c
29 changed files with 224 additions and 65 deletions

View file

@ -72,10 +72,6 @@ Logging logging;
void setup() { void setup() {
init_hal(); 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.
@ -87,18 +83,18 @@ void setup() {
#ifdef WIFI #ifdef WIFI
xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO, xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
&connectivity_loop_task, WIFI_CORE); &connectivity_loop_task, esp32hal->WIFICORE());
#endif #endif
if (!led_init()) {
return;
}
#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) #if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD)
xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO, xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO,
&logging_loop_task, WIFI_CORE); &logging_loop_task, esp32hal->WIFICORE());
#endif #endif
if (!init_CAN()) {
return;
}
if (!init_contactors()) { if (!init_contactors()) {
return; return;
} }
@ -114,6 +110,12 @@ void setup() {
} }
setup_battery(); setup_battery();
setup_can_shunt();
if (!init_CAN()) {
return;
}
if (!init_rs485()) { if (!init_rs485()) {
return; return;
} }
@ -122,7 +124,6 @@ void setup() {
return; return;
} }
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
pinMode(0, INPUT_PULLUP); pinMode(0, INPUT_PULLUP);
@ -131,7 +132,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 << esp32hal->CORE_FUNCTION_CORE()) | (1 << esp32hal->WIFI_CORE()), // Watch both cores .idle_core_mask = (1 << esp32hal->CORE_FUNCTION_CORE()) | (1 << esp32hal->WIFICORE()), // Watch both cores
.trigger_panic = true // Enable panic reset on timeout .trigger_panic = true // Enable panic reset on timeout
}; };
@ -141,7 +142,7 @@ 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,
esp32hal->WIFI_CORE()); esp32hal->WIFICORE());
#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,

View file

@ -5,7 +5,7 @@
#include "RS485Battery.h" #include "RS485Battery.h"
#if !defined(COMMON_IMAGE) && !defined(SELECTED_BATTERY_CLASS) #if !defined(COMMON_IMAGE) && !defined(SELECTED_BATTERY_CLASS)
#error No battery selected! Choose one from the USER_SETTINGS.h file #error No battery selected! Choose one from the USER_SETTINGS.h file or build COMMON_IMAGE.
#endif #endif
Battery* battery = nullptr; Battery* battery = nullptr;
@ -21,7 +21,7 @@ std::vector<BatteryType> supported_battery_types() {
return types; return types;
} }
extern const char* name_for_chemistry(battery_chemistry_enum chem) { const char* name_for_chemistry(battery_chemistry_enum chem) {
switch (chem) { switch (chem) {
case battery_chemistry_enum::LFP: case battery_chemistry_enum::LFP:
return "LFP"; return "LFP";
@ -34,7 +34,26 @@ extern const char* name_for_chemistry(battery_chemistry_enum chem) {
} }
} }
extern const char* name_for_battery_type(BatteryType type) { const char* name_for_comm_interface(comm_interface comm) {
switch (comm) {
case comm_interface::Modbus:
return "Modbus";
case comm_interface::RS485:
return "RS485";
case comm_interface::CanNative:
return "Native CAN";
case comm_interface::CanFdNative:
return "Native CAN FD";
case comm_interface::CanAddonMcp2515:
return "CAN MCP 2515 add-on";
case comm_interface::CanFdAddonMcp2518:
return "CAN FD MCP 2518 add-on";
default:
return nullptr;
}
}
const char* name_for_battery_type(BatteryType type) {
switch (type) { switch (type) {
case BatteryType::None: case BatteryType::None:
return "None"; return "None";
@ -119,7 +138,7 @@ const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum:
const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::NMC; const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::NMC;
#endif #endif
extern battery_chemistry_enum user_selected_battery_chemistry; battery_chemistry_enum user_selected_battery_chemistry = battery_chemistry_default;
#ifdef COMMON_IMAGE #ifdef COMMON_IMAGE
#ifdef SELECTED_BATTERY_CLASS #ifdef SELECTED_BATTERY_CLASS

View file

@ -188,12 +188,12 @@ void BmwPhevBattery::wake_battery_via_canbus() {
// Followed by a Recessive interval of at least ~3µs (min) and at most ~10µs (max) // Followed by a Recessive interval of at least ~3µs (min) and at most ~10µs (max)
// Then a second dominant pulse of similar timing. // Then a second dominant pulse of similar timing.
CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings slow_down_can();
ESP32Can.CANInit(); // ReInit native CAN module at new speed
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery);
transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery);
CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed
ESP32Can.CANInit(); // ReInit native CAN module at new speed resume_full_speed();
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("Sent magic wakeup packet to SME at 100kbps..."); logging.println("Sent magic wakeup packet to SME at 100kbps...");

View file

@ -2,6 +2,7 @@
#define BATTERY_H #define BATTERY_H
#include <vector> #include <vector>
#include "src/devboard/utils/types.h"
#include "src/devboard/webserver/BatteryHtmlRenderer.h" #include "src/devboard/webserver/BatteryHtmlRenderer.h"
enum class BatteryType { enum class BatteryType {
@ -48,6 +49,7 @@ enum class BatteryType {
extern std::vector<BatteryType> supported_battery_types(); extern std::vector<BatteryType> supported_battery_types();
extern const char* name_for_battery_type(BatteryType type); extern const char* name_for_battery_type(BatteryType type);
extern const char* name_for_chemistry(battery_chemistry_enum chem); extern const char* name_for_chemistry(battery_chemistry_enum chem);
extern const char* name_for_comm_interface(comm_interface comm);
extern BatteryType user_selected_battery_type; extern BatteryType user_selected_battery_type;
extern bool user_selected_second_battery; extern bool user_selected_second_battery;

View file

@ -75,7 +75,7 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
vehicle_can_initialized = true; vehicle_can_initialized = true;
vehicle_permission = digitalRead(CHADEMO_PIN_4); vehicle_permission = digitalRead(pin4);
x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0]; x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0];
@ -936,7 +936,7 @@ 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
if (!esp32hal->alloc_pins("CHADEMO", pin2, pin10, pin4, pin7, pin_lock)) { if (!esp32hal->alloc_pins(Name, pin2, pin10, pin4, pin7, pin_lock)) {
return; return;
} }

View file

@ -6,3 +6,11 @@ CanBattery::CanBattery() {
register_transmitter(this); register_transmitter(this);
register_can_receiver(this, can_interface); register_can_receiver(this, can_interface);
} }
void CanBattery::slow_down_can() {
::slow_down_can(can_interface);
}
void CanBattery::resume_full_speed() {
::resume_full_speed(can_interface);
}

View file

@ -5,6 +5,7 @@
#include "src/communication/Transmitter.h" #include "src/communication/Transmitter.h"
#include "src/communication/can/CanReceiver.h" #include "src/communication/can/CanReceiver.h"
#include "src/communication/can/comm_can.h"
#include "src/devboard/utils/types.h" #include "src/devboard/utils/types.h"
// Abstract base class for batteries using the CAN bus // Abstract base class for batteries using the CAN bus
@ -29,6 +30,9 @@ class CanBattery : public Battery, Transmitter, CanReceiver {
register_transmitter(this); register_transmitter(this);
register_can_receiver(this, can_interface); register_can_receiver(this, can_interface);
} }
void slow_down_can();
void resume_full_speed();
}; };
#endif #endif

View file

@ -3,7 +3,6 @@
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "../include.h" #include "../include.h"
#include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
const unsigned char crc8_table[256] = const unsigned char crc8_table[256] =
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies

View file

@ -2,11 +2,8 @@
#define KIA_E_GMP_BATTERY_H #define KIA_E_GMP_BATTERY_H
#include <Arduino.h> #include <Arduino.h>
#include "../include.h" #include "../include.h"
#include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#include "CanBattery.h" #include "CanBattery.h"
extern ACAN2517FD canfd;
#define ESTIMATE_SOC_FROM_CELLVOLTAGE #define ESTIMATE_SOC_FROM_CELLVOLTAGE
#ifdef KIA_E_GMP_BATTERY #ifdef KIA_E_GMP_BATTERY

View file

@ -1,6 +1,8 @@
#include "comm_can.h" #include "comm_can.h"
#include <algorithm>
#include <map> #include <map>
#include "../../include.h" #include "../../include.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" #include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h" #include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
#include "src/devboard/sdcard/sdcard.h" #include "src/devboard/sdcard/sdcard.h"
@ -295,7 +297,7 @@ void receive_frame_canfd_addon() { // This section checks if we have a complete
rx_frame.ID = MCP2518frame.id; rx_frame.ID = MCP2518frame.id;
rx_frame.ext_ID = MCP2518frame.ext; rx_frame.ext_ID = MCP2518frame.ext;
rx_frame.DLC = MCP2518frame.len; rx_frame.DLC = MCP2518frame.len;
memcpy(rx_frame.data.u8, MCP2518frame.data, MIN(rx_frame.DLC, 64)); memcpy(rx_frame.data.u8, MCP2518frame.data, std::min(rx_frame.DLC, (uint8_t)64));
//message incoming, pass it on to the handler //message incoming, pass it on to the handler
map_can_frame_to_variable(&rx_frame, CANFD_ADDON_MCP2518); map_can_frame_to_variable(&rx_frame, CANFD_ADDON_MCP2518);
map_can_frame_to_variable(&rx_frame, CANFD_NATIVE); map_can_frame_to_variable(&rx_frame, CANFD_NATIVE);
@ -416,3 +418,17 @@ void restart_can() {
canfd->begin(*settings2517, [] { can2515->isr(); }); canfd->begin(*settings2517, [] { can2515->isr(); });
} }
} }
void slow_down_can(CAN_Interface interface) {
if (interface == CAN_Interface::CAN_NATIVE) {
CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings
ESP32Can.CANInit(); // ReInit native CAN module at new speed
}
}
void resume_full_speed(CAN_Interface interface) {
if (interface == CAN_Interface::CAN_NATIVE) {
CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed
ESP32Can.CANInit(); // ReInit native CAN module at new speed
}
}

View file

@ -1,13 +1,7 @@
#ifndef _COMM_CAN_H_ #ifndef _COMM_CAN_H_
#define _COMM_CAN_H_ #define _COMM_CAN_H_
#include "../../include.h" #include "../../devboard/utils/types.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/utils/value_mapping.h"
#include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern bool use_canfd_as_can; extern bool use_canfd_as_can;
@ -79,4 +73,7 @@ void stop_can();
// Restart CAN communication for all interfaces // Restart CAN communication for all interfaces
void restart_can(); void restart_can();
void slow_down_can(CAN_Interface interface);
void resume_full_speed(CAN_Interface interface);
#endif #endif

View file

@ -21,7 +21,7 @@ bool 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 }; enum class STOP_BUTTON_BEHAVIOR { NOT_CONNECTED = 0, LATCHING_SWITCH = 1, MOMENTARY_SWITCH = 2, Highest };
extern STOP_BUTTON_BEHAVIOR equipment_stop_behavior; extern STOP_BUTTON_BEHAVIOR equipment_stop_behavior;

View file

@ -79,6 +79,28 @@ void init_stored_settings() {
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);
auto readIf = [](const char* settingName) {
auto batt1If = (comm_interface)settings.getUInt(settingName, (int)comm_interface::CanNative);
switch (batt1If) {
case comm_interface::CanNative:
return CAN_Interface::CAN_NATIVE;
case comm_interface::CanFdNative:
return CAN_Interface::CANFD_NATIVE;
case comm_interface::CanAddonMcp2515:
return CAN_Interface::CAN_ADDON_MCP2515;
case comm_interface::CanFdAddonMcp2518:
return CAN_Interface::CANFD_ADDON_MCP2518;
}
return CAN_Interface::CAN_NATIVE;
};
can_config.battery = readIf("BATTCOMM");
can_config.battery_double = readIf("BATT2COMM");
can_config.inverter = readIf("INVCOMM");
can_config.charger = readIf("CHGCOMM");
can_config.shunt = readIf("SHUNTCOMM");
equipment_stop_behavior = (STOP_BUTTON_BEHAVIOR)settings.getUInt("EQSTOP", (int)STOP_BUTTON_BEHAVIOR::NOT_CONNECTED); 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);

View file

@ -7,7 +7,7 @@
#include "hw_lilygo.h" #include "hw_lilygo.h"
#include "hw_stark.h" #include "hw_stark.h"
extern Esp32Hal* esp32hal; Esp32Hal* esp32hal = nullptr;
void init_hal() { void init_hal() {
#if defined(HW_LILYGO) #if defined(HW_LILYGO)

View file

@ -3,6 +3,8 @@
#include <soc/gpio_num.h> #include <soc/gpio_num.h>
#include <chrono> #include <chrono>
#include <unordered_map>
#include "../../../src/devboard/utils/events.h"
#include "../../../src/devboard/utils/types.h" #include "../../../src/devboard/utils/types.h"
// Hardware Abstraction Layer base class. // Hardware Abstraction Layer base class.
@ -19,7 +21,7 @@ class Esp32Hal {
// Core assignment // Core assignment
virtual int CORE_FUNCTION_CORE() { return 1; } virtual int CORE_FUNCTION_CORE() { return 1; }
virtual int MODBUS_CORE() { return 0; } virtual int MODBUS_CORE() { return 0; }
virtual int WIFI_CORE() { return 0; } virtual int WIFICORE() { return 0; }
template <typename... Pins> template <typename... Pins>
bool alloc_pins(const char* name, Pins... pins) { bool alloc_pins(const char* name, Pins... pins) {
@ -27,6 +29,7 @@ class Esp32Hal {
for (gpio_num_t pin : requested_pins) { for (gpio_num_t pin : requested_pins) {
if (pin < 0) { if (pin < 0) {
set_event(EVENT_GPIO_NOT_DEFINED, (int)pin);
// Event: {name} attempted to allocate pin that wasn't defined for the selected HW. // Event: {name} attempted to allocate pin that wasn't defined for the selected HW.
return false; return false;
} }
@ -35,6 +38,7 @@ class Esp32Hal {
if (it != allocated_pins.end()) { if (it != allocated_pins.end()) {
// Event: GPIO conflict for pin {pin} between name and it->second. // Event: GPIO conflict for pin {pin} between name and it->second.
//std::cerr << "Pin " << pin << " already allocated to \"" << it->second << "\".\n"; //std::cerr << "Pin " << pin << " already allocated to \"" << it->second << "\".\n";
set_event(EVENT_GPIO_CONFLICT, (int)pin);
return false; return false;
} }
} }
@ -136,12 +140,18 @@ class Esp32Hal {
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_NC; } virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_NC; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_NC; } virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_NC; }
// Returns the available comm interfaces on this HW
virtual std::vector<comm_interface> available_interfaces() = 0;
private: private:
std::unordered_map<gpio_num_t, std::string> allocated_pins; std::unordered_map<gpio_num_t, std::string> allocated_pins;
}; };
extern Esp32Hal* esp32hal; extern Esp32Hal* esp32hal;
// Needed for AsyncTCPSock library.
#define WIFI_CORE (esp32hal->WIFICORE())
void init_hal(); void init_hal();
#endif #endif

View file

@ -72,6 +72,10 @@ class ThreeLBHal : public Esp32Hal {
// Battery wake up pins // Battery wake up pins
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; }
std::vector<comm_interface> available_interfaces() {
return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative};
}
}; };
#endif #endif

View file

@ -66,6 +66,14 @@ class DevKitHal : public Esp32Hal {
// Battery wake up pins // Battery wake up pins
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; }
std::vector<comm_interface> available_interfaces() {
return {
comm_interface::Modbus,
comm_interface::RS485,
comm_interface::CanNative,
};
}
}; };
#endif // __HW_DEVKIT_H__ #endif // __HW_DEVKIT_H__

View file

@ -76,6 +76,11 @@ class LilyGoHal : public Esp32Hal {
// Battery wake up pins // Battery wake up pins
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; }
std::vector<comm_interface> available_interfaces() {
return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanAddonMcp2515,
comm_interface::CanFdAddonMcp2518};
}
}; };
#define HalClass LilyGoHal #define HalClass LilyGoHal

View file

@ -69,6 +69,10 @@ class StarkHal : public Esp32Hal {
// Battery wake up pins // Battery wake up pins
virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; }
virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; }
std::vector<comm_interface> available_interfaces() {
return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanFdNative};
}
}; };
#endif // __HW_STARK_H__ #endif // __HW_STARK_H__

View file

@ -1,4 +1,6 @@
#include "safety.h"
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
#include "../../include.h"
#include "../utils/events.h" #include "../utils/events.h"
static uint16_t cell_deviation_mV = 0; static uint16_t cell_deviation_mV = 0;

View file

@ -1,8 +1,6 @@
#ifndef SAFETY_H #ifndef SAFETY_H
#define SAFETY_H #define SAFETY_H
#include <Arduino.h>
#include <string> #include <string>
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define MAX_CAN_FAILURES 50 #define MAX_CAN_FAILURES 50

View file

@ -1,4 +1,5 @@
#include "sdcard.h" #include "sdcard.h"
#include "../../include.h"
#include "freertos/ringbuf.h" #include "freertos/ringbuf.h"
File can_log_file; File can_log_file;
@ -183,13 +184,11 @@ void init_logging_buffers() {
} }
bool init_sdcard() { bool init_sdcard() {
auto miso_pin = esp32hal->SD_MISO_PIN(); auto miso_pin = esp32hal->SD_MISO_PIN();
auto mosi_pin = esp32hal->SD_MOSI_PIN(); auto mosi_pin = esp32hal->SD_MOSI_PIN();
auto miso_pin = esp32hal->SD_MISO_PIN();
auto sclk_pin = esp32hal->SD_SCLK_PIN(); auto sclk_pin = esp32hal->SD_SCLK_PIN();
if (!esp32hal->alloc_pins("SD Card", miso_pin, mosi_pin)) { if (!esp32hal->alloc_pins("SD Card", miso_pin, mosi_pin, sclk_pin)) {
return false; return false;
} }
@ -201,7 +200,7 @@ bool init_sdcard() {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("SD Card initialization failed!"); logging.println("SD Card initialization failed!");
#endif // DEBUG_LOG #endif // DEBUG_LOG
return; return false;
} }
clear_event(EVENT_SD_INIT_FAILED); clear_event(EVENT_SD_INIT_FAILED);

View file

@ -1,5 +1,6 @@
#include "events.h" #include "events.h"
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
#include "../../include.h"
#include "../../../USER_SETTINGS.h" #include "../../../USER_SETTINGS.h"
@ -132,6 +133,9 @@ void init_events(void) {
events.entries[EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS].level = EVENT_LEVEL_INFO; events.entries[EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS].level = EVENT_LEVEL_INFO;
events.entries[EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED].level = EVENT_LEVEL_WARNING; events.entries[EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING; events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_GPIO_CONFLICT].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_GPIO_NOT_DEFINED].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
} }
void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { void set_event(EVENTS_ENUM_TYPE event, uint8_t data) {
@ -373,6 +377,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED: case EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED:
return "Failed to syncronise with the NTP Server. BMS will reset every 24 hours from when the emulator was " return "Failed to syncronise with the NTP Server. BMS will reset every 24 hours from when the emulator was "
"powered on"; "powered on";
case EVENT_GPIO_CONFLICT:
return "There is a GPIO pin conflict between SW components.";
case EVENT_GPIO_NOT_DEFINED:
return "SW module requires GPIO that is not defined for this hardware.";
default: default:
return ""; return "";
} }

View file

@ -1,8 +1,7 @@
#ifndef __EVENTS_H__ #ifndef __EVENTS_H__
#define __EVENTS_H__ #define __EVENTS_H__
#ifndef UNIT_TEST
#include "../../include.h" #include <stdint.h>
#endif
#define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING, #define GENERATE_STRING(STRING) #STRING,
@ -107,6 +106,8 @@
XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS) \ XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS) \
XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED) \ XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED) \
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \ XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
XX(EVENT_GPIO_NOT_DEFINED) \
XX(EVENT_GPIO_CONFLICT) \
XX(EVENT_NOF_EVENTS) XX(EVENT_NOF_EVENTS)
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE; typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;

View file

@ -9,7 +9,18 @@ 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 = 1, NMC = 2, LFP = 3, Highest };
enum class comm_interface {
Modbus = 1,
RS485 = 2,
CanNative = 3,
CanFdNative = 4,
CanAddonMcp2515 = 5,
CanFdAddonMcp2518 = 6,
Highest
};
enum led_color { GREEN, YELLOW, RED, BLUE }; enum led_color { GREEN, YELLOW, RED, BLUE };
enum led_mode_enum { CLASSIC, FLOW, HEARTBEAT }; enum led_mode_enum { CLASSIC, FLOW, HEARTBEAT };
enum PrechargeState { enum PrechargeState {

View file

@ -27,7 +27,8 @@ std::vector<EnumType> enum_values() {
} }
template <typename EnumType, typename Func> template <typename EnumType, typename Func>
std::vector<std::pair<String, EnumType>> enum_values_and_names(Func name_for_type) { std::vector<std::pair<String, EnumType>> enum_values_and_names(Func name_for_type,
const EnumType* noneValue = nullptr) {
auto values = enum_values<EnumType>(); auto values = enum_values<EnumType>();
std::vector<std::pair<String, EnumType>> pairs; std::vector<std::pair<String, EnumType>> pairs;
@ -41,15 +42,31 @@ std::vector<std::pair<String, EnumType>> enum_values_and_names(Func name_for_typ
std::sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); std::sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
pairs.insert(pairs.begin(), std::pair(name_for_type(EnumType::None), EnumType::None)); if (noneValue) {
pairs.insert(pairs.begin(), std::pair(name_for_type(*noneValue), *noneValue));
}
return pairs; return pairs;
} }
template <typename TEnum, typename Func>
String options_for_enum_with_none(TEnum selected, Func name_for_type, TEnum noneValue) {
String options;
TEnum none = noneValue;
auto values = enum_values_and_names<TEnum>(name_for_type, &none);
for (const auto& [name, type] : values) {
options +=
("<option value=\"" + String(static_cast<int>(type)) + "\"" + (selected == type ? " selected" : "") + ">");
options += name;
options += "</option>";
}
return options;
}
template <typename TEnum, typename Func> template <typename TEnum, typename Func>
String options_for_enum(TEnum selected, Func name_for_type) { String options_for_enum(TEnum selected, Func name_for_type) {
String options; String options;
auto values = enum_values_and_names<TEnum>(name_for_type); auto values = enum_values_and_names<TEnum>(name_for_type, nullptr);
for (const auto& [name, type] : values) { for (const auto& [name, type] : values) {
options += options +=
("<option value=\"" + String(static_cast<int>(type)) + "\"" + (selected == type ? " selected" : "") + ">"); ("<option value=\"" + String(static_cast<int>(type)) + "\"" + (selected == type ? " selected" : "") + ">");
@ -106,7 +123,7 @@ String settings_processor(const String& var) {
"onclick='editPassword()'>Edit</button></h4>"; "onclick='editPassword()'>Edit</button></h4>";
#ifdef COMMON_IMAGE #ifdef COMMON_IMAGE
BatteryEmulatorSettingsStore settings; BatteryEmulatorSettingsStore settings(true);
// It's important that we read/write settings directly to settings store instead of the run-time values // It's important that we read/write settings directly to settings store instead of the run-time values
// since the run-time values may have direct effect on operation. // since the run-time values may have direct effect on operation.
@ -118,29 +135,40 @@ String settings_processor(const String& var) {
"align-items: center;'>"; "align-items: center;'>";
content += "<label>Battery: </label><select style='max-width: 250px;' name='battery'>"; content += "<label>Battery: </label><select style='max-width: 250px;' name='battery'>";
content += content += options_for_enum_with_none((BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None),
options_for_enum((BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None), name_for_battery_type); name_for_battery_type, BatteryType::None);
content += "</select>"; content += "</select>";
content += "<label>Battery chemistry: </label><select style='max-width: 250px;' name='battery'>"; content += "<label>Battery comm I/F: </label><select style='max-width: 250px;' name='BATTCOMM'>";
content += options_for_enum((comm_interface)settings.getUInt("BATTCOMM", (int)comm_interface::CanNative),
name_for_comm_interface);
content += "</select>";
content += "<label>Battery chemistry: </label><select style='max-width: 250px;' name='BATTCHEM'>";
content += options_for_enum((battery_chemistry_enum)settings.getUInt("BATTCHEM", (int)battery_chemistry_enum::NCA), content += options_for_enum((battery_chemistry_enum)settings.getUInt("BATTCHEM", (int)battery_chemistry_enum::NCA),
name_for_chemistry); name_for_chemistry);
content += "</select>"; content += "</select>";
content += "<label>Inverter protocol: </label><select style='max-width: 250px;' name='inverter'>"; content += "<label>Inverter protocol: </label><select style='max-width: 250px;' name='inverter'>";
content += options_for_enum((InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None), content +=
name_for_inverter_type); options_for_enum_with_none((InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None),
name_for_inverter_type, InverterProtocolType::None);
content += "</select>";
content += "<label>Inverter comm I/F: </label><select style='max-width: 250px;' name='INVCOMM'>";
content += options_for_enum((comm_interface)settings.getUInt("INVCOMM", (int)comm_interface::CanNative),
name_for_comm_interface);
content += "</select>"; content += "</select>";
content += "<label>Charger: </label><select style='max-width: 250px;' name='charger'>"; content += "<label>Charger: </label><select style='max-width: 250px;' name='charger'>";
content += content += options_for_enum_with_none((ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None),
options_for_enum((ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None), name_for_charger_type); name_for_charger_type, ChargerType::None);
content += "</select>"; content += "</select>";
content += "<label>Equipment stop button: </label><select style='max-width: 250px;' name='EQSTOP'>"; content += "<label>Equipment stop button: </label><select style='max-width: 250px;' name='EQSTOP'>";
content += content += options_for_enum_with_none(
options_for_enum((STOP_BUTTON_BEHAVIOR)settings.getUInt("EQSTOP", (int)STOP_BUTTON_BEHAVIOR::NOT_CONNECTED), (STOP_BUTTON_BEHAVIOR)settings.getUInt("EQSTOP", (int)STOP_BUTTON_BEHAVIOR::NOT_CONNECTED),
name_for_button_type); name_for_button_type, STOP_BUTTON_BEHAVIOR::NOT_CONNECTED);
content += "</select>"; 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

View file

@ -417,12 +417,30 @@ void init_webserver() {
} else if (p->name() == "battery") { } else if (p->name() == "battery") {
auto type = static_cast<BatteryType>(atoi(p->value().c_str())); auto type = static_cast<BatteryType>(atoi(p->value().c_str()));
settings.saveUInt("BATTTYPE", (int)type); settings.saveUInt("BATTTYPE", (int)type);
} else if (p->name() == "BATTCHEM") {
auto type = static_cast<battery_chemistry_enum>(atoi(p->value().c_str()));
settings.saveUInt("BATTCHEM", (int)type);
} 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() == "EQSTOP") { } else if (p->name() == "EQSTOP") {
auto type = static_cast<STOP_BUTTON_BEHAVIOR>(atoi(p->value().c_str())); auto type = static_cast<STOP_BUTTON_BEHAVIOR>(atoi(p->value().c_str()));
settings.saveUInt("EQSTOP", (int)type); settings.saveUInt("EQSTOP", (int)type);
} else if (p->name() == "BATTCOMM") {
auto type = static_cast<comm_interface>(atoi(p->value().c_str()));
settings.saveUInt("BATTCOMM", (int)type);
} else if (p->name() == "BATT2COMM") {
auto type = static_cast<comm_interface>(atoi(p->value().c_str()));
settings.saveUInt("BATT2COMM", (int)type);
} else if (p->name() == "INVCOMM") {
auto type = static_cast<comm_interface>(atoi(p->value().c_str()));
settings.saveUInt("INVCOMM", (int)type);
} else if (p->name() == "CHGCOMM") {
auto type = static_cast<comm_interface>(atoi(p->value().c_str()));
settings.saveUInt("CHGCOMM", (int)type);
} else if (p->name() == "SHUNTCOMM") {
auto type = static_cast<comm_interface>(atoi(p->value().c_str()));
settings.saveUInt("SHUNTCOMM", (int)type);
} }
for (auto& boolSetting : boolSettings) { for (auto& boolSetting : boolSettings) {

View file

@ -19,7 +19,7 @@
/* - ERROR CHECKS BELOW, DON'T TOUCH - */ /* - ERROR CHECKS BELOW, DON'T TOUCH - */
#if !defined(HW_LILYGO) || !defined(HW_STARK) || !defined(HW_3LB) || !defined(HW_DEVKIT) #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

View file

@ -11,8 +11,6 @@
class SmaBydHInverter : public SmaInverterBase { class SmaBydHInverter : public SmaInverterBase {
public: public:
SmaBydHInverter();
const char* name() override { return Name; } const char* name() override { return Name; }
void update_values(); void update_values();
void transmit_can(unsigned long currentMillis); void transmit_can(unsigned long currentMillis);