diff --git a/.github/workflows/compile-common-image-lilygo.yml b/.github/workflows/compile-common-image-lilygo.yml index 40cf16fc..02fd25f5 100644 --- a/.github/workflows/compile-common-image-lilygo.yml +++ b/.github/workflows/compile-common-image-lilygo.yml @@ -54,4 +54,5 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: + name: battery-emulator-lilygo.bin path: Software.ino.bin diff --git a/Software/Software.ino b/Software/Software.ino index 6939fbe4..4c00d6b5 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -17,33 +17,17 @@ #include "src/communication/precharge_control/precharge_control.h" #include "src/communication/rs485/comm_rs485.h" #include "src/datalayer/datalayer.h" +#include "src/devboard/mqtt/mqtt.h" #include "src/devboard/sdcard/sdcard.h" #include "src/devboard/utils/events.h" #include "src/devboard/utils/led_handler.h" #include "src/devboard/utils/logging.h" #include "src/devboard/utils/timer.h" #include "src/devboard/utils/value_mapping.h" -#include "src/include.h" -#ifndef AP_PASSWORD -#error \ - "Initial setup not completed, USER_SECRETS.h is missing. Please rename the file USER_SECRETS.TEMPLATE.h to USER_SECRETS.h and fill in the required credentials. This file is ignored by version control to keep sensitive information private." -#endif -#ifdef WIFI -#include "src/devboard/wifi/wifi.h" -#ifdef WEBSERVER #include "src/devboard/webserver/webserver.h" -#ifdef MDNSRESPONDER -#include -#endif // MDNSRESONDER -#else // WEBSERVER -#ifdef MDNSRESPONDER -#error WEBSERVER needs to be enabled for MDNSRESPONDER! -#endif // MDNSRSPONDER -#endif // WEBSERVER -#ifdef MQTT -#include "src/devboard/mqtt/mqtt.h" -#endif // MQTT -#endif // WIFI +#include "src/devboard/wifi/wifi.h" +#include "src/include.h" + #ifdef PERIODIC_BMS_RESET_AT #include "src/devboard/utils/ntp_time.h" #endif @@ -70,6 +54,8 @@ Logging logging; // Initialization void setup() { + init_hal(); + init_serial(); // We print this after setting up serial, such that is also printed to serial with DEBUG_VIA_USB set. @@ -79,35 +65,48 @@ void setup() { init_stored_settings(); -#ifdef WIFI - xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO, - &connectivity_loop_task, WIFI_CORE); -#endif + if (wifi_enabled) { + xTaskCreatePinnedToCore((TaskFunction_t)&connectivity_loop, "connectivity_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO, + &connectivity_loop_task, esp32hal->WIFICORE()); + } + + if (!led_init()) { + return; + } #if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) xTaskCreatePinnedToCore((TaskFunction_t)&logging_loop, "logging_loop", 4096, NULL, TASK_CONNECTIVITY_PRIO, - &logging_loop_task, WIFI_CORE); + &logging_loop_task, esp32hal->WIFICORE()); #endif -#ifdef PRECHARGE_CONTROL - init_precharge_control(); -#endif // PRECHARGE_CONTROL + if (!init_contactors()) { + return; + } + + if (!init_precharge_control()) { + return; + } setup_charger(); - setup_inverter(); + + if (!setup_inverter()) { + return; + } setup_battery(); setup_can_shunt(); // Init CAN only after any CAN receivers have had a chance to register. - init_CAN(); + if (!init_CAN()) { + return; + } - init_contactors(); + if (!init_rs485()) { + return; + } - init_rs485(); - -#ifdef EQUIPMENT_STOP_BUTTON - init_equipment_stop_button(); -#endif + if (!init_equipment_stop_button()) { + return; + } // BOOT button at runtime is used as an input for various things pinMode(0, INPUT_PULLUP); @@ -116,22 +115,26 @@ void setup() { // Initialize Task Watchdog for subscribed tasks esp_task_wdt_config_t wdt_config = { - .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 - .trigger_panic = true // Enable panic reset on timeout + .timeout_ms = INTERVAL_5_S, // If task hangs for longer than this, reboot + .idle_core_mask = + (uint32_t)(1 << esp32hal->CORE_FUNCTION_CORE()) | (uint32_t)(1 << esp32hal->WIFICORE()), // Watch both cores + .trigger_panic = true // Enable panic reset on timeout }; // Start tasks -#ifdef MQTT - init_mqtt(); + if (mqtt_enabled) { + if (!init_mqtt()) { + return; + } - xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task, - WIFI_CORE); -#endif + xTaskCreatePinnedToCore((TaskFunction_t)&mqtt_loop, "mqtt_loop", 4096, NULL, TASK_MQTT_PRIO, &mqtt_loop_task, + esp32hal->WIFICORE()); + } 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 bmsResetTimeOffset = getTimeOffsetfromNowUntil(PERIODIC_BMS_RESET_AT); if (bmsResetTimeOffset == 0) { @@ -164,35 +167,34 @@ void logging_loop(void*) { } #endif -#ifdef WIFI void connectivity_loop(void*) { esp_task_wdt_add(NULL); // Register this task with WDT // Init wifi init_WiFi(); -#ifdef WEBSERVER - // Init webserver - init_webserver(); -#endif -#ifdef MDNSRESPONDER - init_mDNS(); -#endif + if (webserver_enabled) { + init_webserver(); + } + + if (mdns_enabled) { + init_mDNS(); + } while (true) { START_TIME_MEASUREMENT(wifi); wifi_monitor(); -#ifdef WEBSERVER - ota_monitor(); -#endif + + if (webserver_enabled) { + ota_monitor(); + } + END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us); esp_task_wdt_reset(); // Reset watchdog delay(1); } } -#endif -#ifdef MQTT void mqtt_loop(void*) { esp_task_wdt_add(NULL); // Register this task with WDT @@ -204,7 +206,6 @@ void mqtt_loop(void*) { delay(1); } } -#endif static std::list transmitters; @@ -217,31 +218,31 @@ void core_loop(void*) { esp_task_wdt_add(NULL); // Register this task with WDT TickType_t xLastWakeTime = xTaskGetTickCount(); const TickType_t xFrequency = pdMS_TO_TICKS(1); // Convert 1ms to ticks - led_init(); while (true) { START_TIME_MEASUREMENT(all); START_TIME_MEASUREMENT(comm); -#ifdef EQUIPMENT_STOP_BUTTON + monitor_equipment_stop_button(); -#endif // Input, Runs as fast as possible receive_can(); // Receive CAN messages receive_rs485(); // Process serial2 RS485 interface END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us); -#ifdef WEBSERVER - START_TIME_MEASUREMENT(ota); - ElegantOTA.loop(); - END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us); -#endif // WEBSERVER + + if (webserver_enabled) { + START_TIME_MEASUREMENT(ota); + ElegantOTA.loop(); + END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us); + } // Process currentMillis = millis(); 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)); } previousMillis10ms = currentMillis; diff --git a/Software/USER_SECRETS.TEMPLATE.h b/Software/USER_SECRETS.TEMPLATE.h index b482d035..d2faf71f 100644 --- a/Software/USER_SECRETS.TEMPLATE.h +++ b/Software/USER_SECRETS.TEMPLATE.h @@ -1,5 +1,6 @@ /* This file should be renamed to USER_SECRETS.h to be able to use the software! It contains all the credentials that should never be made public */ +#ifndef COMMON_IMAGE //Password to the access point generated by the Battery-Emulator #define AP_PASSWORD "123456789" // Minimum of 8 characters; set to blank if you want the access point to be open @@ -18,3 +19,4 @@ It contains all the credentials that should never be made public */ #define MQTT_PORT 1883 // MQTT server port #define MQTT_USER "" // MQTT username, leave blank for no authentication #define MQTT_PASSWORD "" // MQTT password, leave blank for no authentication +#endif diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 7ddeb02c..edf41a9a 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -21,26 +21,40 @@ volatile CAN_Configuration can_config = { .shunt = CAN_NATIVE // (OPTIONAL) Which CAN is your shunt connected to? }; -std::string ssid = WIFI_SSID; // Set in USER_SECRETS.h -std::string password = WIFI_PASSWORD; // Set in USER_SECRETS.h -const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters, also used for device name on web interface -const char* passwordAP = AP_PASSWORD; // Set in USER_SECRETS.h -const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection +#ifdef COMMON_IMAGE +std::string ssid; +std::string password; +std::string passwordAP; +#else +std::string ssid = WIFI_SSID; // Set in USER_SECRETS.h +std::string password = WIFI_PASSWORD; // Set in USER_SECRETS.h +std::string passwordAP = AP_PASSWORD; // Set in USER_SECRETS.h +#endif + +const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection + +#ifdef COMMON_IMAGE +std::string http_username; +std::string http_password; +#else +std::string http_username = HTTP_USERNAME; // Set in USER_SECRETS.h +std::string http_password = HTTP_PASSWORD; // Set in USER_SECRETS.h +#endif -#ifdef WEBSERVER -const char* http_username = HTTP_USERNAME; // Set in USER_SECRETS.h -const char* http_password = HTTP_PASSWORD; // Set in USER_SECRETS.h // Set your Static IP address. Only used incase WIFICONFIG is set in USER_SETTINGS.h IPAddress local_IP(192, 168, 10, 150); IPAddress gateway(192, 168, 10, 1); IPAddress subnet(255, 255, 255, 0); -#endif // WEBSERVER // MQTT -#ifdef MQTT -const char* mqtt_user = MQTT_USER; // Set in USER_SECRETS.h -const char* mqtt_password = MQTT_PASSWORD; // Set in USER_SECRETS.h -#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME +#ifdef COMMON_IMAGE +std::string mqtt_user; +std::string mqtt_password; +#else +std::string mqtt_user = MQTT_USER; // Set in USER_SECRETS.h +std::string mqtt_password = MQTT_PASSWORD; // Set in USER_SECRETS.h +#endif + const char* mqtt_topic_name = "BE"; // Custom MQTT topic name. Previously, the name was automatically set to "battery-emulator_esp32-XXXXXX" const char* mqtt_object_id_prefix = @@ -49,15 +63,6 @@ const char* mqtt_device_name = "Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX" const char* ha_device_id = "battery-emulator"; // Custom device ID in Home Assistant. Previously, the ID was always "battery-emulator" -#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME -#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) */ volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cell charging of a 96s pack diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 25cf145f..049bd985 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -79,7 +79,7 @@ /* 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 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 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! @@ -110,7 +110,7 @@ //#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production) /* CAN options */ -//#define CAN_ADDON //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) +//#define CAN_ADDON //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) #define CRYSTAL_FREQUENCY_MHZ 8 //CAN_ADDON option, what is your MCP2515 add-on boards crystal frequency? //#define CANFD_ADDON //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board #define CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ \ @@ -197,9 +197,16 @@ extern volatile float CHARGER_END_A; 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 -typedef enum { LATCHING_SWITCH = 0, MOMENTARY_SWITCH = 1 } STOP_BUTTON_BEHAVIOR; -extern volatile STOP_BUTTON_BEHAVIOR equipment_stop_behavior; +const STOP_BUTTON_BEHAVIOR stop_button_default_behavior = STOP_BUTTON_BEHAVIOR::MOMENTARY_SWITCH; +#else +const STOP_BUTTON_BEHAVIOR stop_button_default_behavior = STOP_BUTTON_BEHAVIOR::NOT_CONNECTED; #endif #ifdef WIFICONFIG diff --git a/Software/src/battery/BATTERIES.cpp b/Software/src/battery/BATTERIES.cpp index 06e5dec1..2906327c 100644 --- a/Software/src/battery/BATTERIES.cpp +++ b/Software/src/battery/BATTERIES.cpp @@ -5,13 +5,12 @@ #include "RS485Battery.h" #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 Battery* battery = nullptr; Battery* battery2 = nullptr; -#ifdef COMMON_IMAGE std::vector supported_battery_types() { std::vector types; @@ -22,7 +21,39 @@ std::vector supported_battery_types() { return types; } -extern const char* name_for_battery_type(BatteryType type) { +const char* name_for_chemistry(battery_chemistry_enum chem) { + switch (chem) { + case battery_chemistry_enum::LFP: + return "LFP"; + case battery_chemistry_enum::NCA: + return "NCA"; + case battery_chemistry_enum::NMC: + return "NMC"; + default: + return nullptr; + } +} + +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) { case BatteryType::None: return "None"; @@ -36,10 +67,8 @@ extern const char* name_for_battery_type(BatteryType type) { return BydAttoBattery::Name; case BatteryType::CellPowerBms: return CellPowerBms::Name; -#ifdef CHADEMO_PIN_2 // Only support chademo for certain platforms case BatteryType::Chademo: return ChademoBattery::Name; -#endif case BatteryType::CmfaEv: return CmfaEvBattery::Name; case BatteryType::Foxess: @@ -102,8 +131,15 @@ extern const char* name_for_battery_type(BatteryType type) { return nullptr; } } + +#ifdef LFP_CHEMISTRY +const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::LFP; +#else +const battery_chemistry_enum battery_chemistry_default = battery_chemistry_enum::NMC; #endif +battery_chemistry_enum user_selected_battery_chemistry = battery_chemistry_default; + #ifdef COMMON_IMAGE #ifdef SELECTED_BATTERY_CLASS #error "Compile time SELECTED_BATTERY_CLASS should not be defined with COMMON_IMAGE" @@ -126,10 +162,8 @@ Battery* create_battery(BatteryType type) { return new BydAttoBattery(); case BatteryType::CellPowerBms: return new CellPowerBms(); -#ifdef CHADEMO_PIN_2 // Only support chademo for certain platforms case BatteryType::Chademo: return new ChademoBattery(); -#endif case BatteryType::CmfaEv: return new CmfaEvBattery(); case BatteryType::Foxess: @@ -179,7 +213,7 @@ Battery* create_battery(BatteryType type) { case BatteryType::SimpBms: return new SimpBmsBattery(); case BatteryType::TeslaModel3Y: - return new TeslaModel3YBattery(); + return new TeslaModel3YBattery(user_selected_battery_chemistry); case BatteryType::TeslaModelSX: return new TeslaModelSXBattery(); case BatteryType::TestFake: @@ -212,7 +246,7 @@ void setup_battery() { break; case BatteryType::BmwI3: 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; case BatteryType::KiaHyundai64: battery2 = new KiaHyundai64Battery(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2, @@ -221,6 +255,9 @@ void setup_battery() { case BatteryType::SantaFePhev: battery2 = new SantaFePhevBattery(&datalayer.battery2, can_config.battery_double); break; + case BatteryType::RenaultZoe1: + battery2 = new RenaultZoeGen1Battery(&datalayer.battery2, nullptr, can_config.battery_double); + break; case BatteryType::TestFake: battery2 = new TestFakeBattery(&datalayer.battery2, can_config.battery_double); break; @@ -236,7 +273,11 @@ void setup_battery() { void setup_battery() { // Instantiate the battery only once just in case this function gets called multiple times. if (battery == nullptr) { +#ifdef TESLA_MODEL_3Y_BATTERY + battery = new SELECTED_BATTERY_CLASS(user_selected_battery_chemistry); +#else battery = new SELECTED_BATTERY_CLASS(); +#endif } battery->setup(); @@ -245,7 +286,7 @@ void setup_battery() { #if defined(BMW_I3_BATTERY) battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allowed_contactor_closing, - can_config.battery_double, WUP_PIN2); + can_config.battery_double, esp32hal->WUP_PIN2()); #elif defined(KIA_HYUNDAI_64_BATTERY) battery2 = new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer_extended.KiaHyundai64_2, &datalayer.system.status.battery2_allowed_contactor_closing, diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 0ff906a7..81615f08 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -493,6 +493,10 @@ void BmwI3Battery::transmit_can(unsigned long currentMillis) { } 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); datalayer.system.info.battery_protocol[63] = '\0'; diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index c7ab356b..aaa0b94b 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -14,7 +14,7 @@ class BmwI3Battery : public CanBattery { public: // Use this constructor for the second battery. BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, CAN_Interface targetCan, - int wakeup) + gpio_num_t wakeup) : CanBattery(targetCan), renderer(*this) { datalayer_battery = datalayer_ptr; contactor_closing_allowed = contactor_closing_allowed_ptr; @@ -30,7 +30,7 @@ class BmwI3Battery : public CanBattery { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; contactor_closing_allowed = nullptr; - wakeup_pin = WUP_PIN1; + wakeup_pin = esp32hal->WUP_PIN1(); } virtual void setup(void); @@ -95,7 +95,7 @@ class BmwI3Battery : public CanBattery { // If not null, this battery listens to this boolean to determine whether contactor closing is 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 previousMillis100 = 0; // will store last time a 100ms CAN Message was send diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp index aea4c3f1..3fb51df1 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.cpp +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -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) // Then a second dominant pulse of similar timing. - CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings - ESP32Can.CANInit(); // ReInit native CAN module at new speed + auto original_speed = change_can_speed(CAN_Speed::CAN_SPEED_100KBPS); + 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 + + change_can_speed(original_speed); #ifdef DEBUG_LOG logging.println("Sent magic wakeup packet to SME at 100kbps..."); diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h index ebcff2d4..6abfa91e 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -2,11 +2,11 @@ #define BATTERY_H #include +#include "src/devboard/utils/types.h" #include "src/devboard/webserver/BatteryHtmlRenderer.h" enum class BatteryType { None = 0, - BmwSbox = 1, BmwI3 = 2, BmwIx = 3, BoltAmpera = 4, @@ -48,10 +48,14 @@ enum class BatteryType { extern std::vector supported_battery_types(); extern const char* name_for_battery_type(BatteryType type); +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 bool user_selected_second_battery; +extern battery_chemistry_enum user_selected_battery_chemistry; + // Abstract base class for next-generation battery implementations. // Defines the interface to call battery specific functionality. class Battery { diff --git a/Software/src/battery/CELLPOWER-BMS.h b/Software/src/battery/CELLPOWER-BMS.h index 25ae6c49..77eb9713 100644 --- a/Software/src/battery/CELLPOWER-BMS.h +++ b/Software/src/battery/CELLPOWER-BMS.h @@ -11,7 +11,7 @@ class CellPowerBms : public CanBattery { public: - CellPowerBms() : CanBattery(true) {} + CellPowerBms() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {} virtual void setup(void); virtual void handle_incoming_can_frame(CAN_frame rx_frame); diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 6136cb98..6791c452 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -4,8 +4,6 @@ #include "../include.h" #include "CHADEMO-SHUNTS.h" -#ifdef CHADEMO_PIN_2 // Only support chademo for certain platforms - //This function maps all the values fetched via CAN to the correct parameters used for the inverter void ChademoBattery::update_values() { @@ -93,7 +91,7 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) { vehicle_can_initialized = true; - vehicle_permission = digitalRead(CHADEMO_PIN_4); + vehicle_permission = digitalRead(pin4); x102_chg_session.ControlProtocolNumberEV = rx_frame.data.u8[0]; @@ -650,10 +648,11 @@ void ChademoBattery::transmit_can(unsigned long currentMillis) { */ void ChademoBattery::handle_chademo_sequence() { - precharge_low = digitalRead(PRECHARGE_PIN) == LOW; - positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH; + precharge_low = digitalRead(precharge) == LOW; + positive_high = digitalRead(positive_contactor) == HIGH; contactors_ready = precharge_low && positive_high; - vehicle_permission = digitalRead(CHADEMO_PIN_4); + + vehicle_permission = digitalRead(pin4); /* ------------------- State override conditions checks ------------------- */ /* ------------------------------------------------------------------------------ */ @@ -676,8 +675,8 @@ void ChademoBattery::handle_chademo_sequence() { switch (CHADEMO_Status) { case CHADEMO_IDLE: /* this is where we can unlock connector */ - digitalWrite(CHADEMO_LOCK, LOW); - plug_inserted = digitalRead(CHADEMO_PIN_7); + digitalWrite(pin_lock, LOW); + plug_inserted = digitalRead(pin7); if (!plug_inserted) { #ifdef DEBUG_LOG @@ -704,7 +703,7 @@ void ChademoBattery::handle_chademo_sequence() { /* If connection is detectable, jumpstart handshake by * 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 */ CHADEMO_Status = CHADEMO_INIT; @@ -715,9 +714,7 @@ void ChademoBattery::handle_chademo_sequence() { * with timers to have higher confidence of certain conditions hitting * a steady state */ -#ifdef DEBUG_LOG - logging.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization."); -#endif + DEBUG_PRINTLN("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization."); CHADEMO_Status = CHADEMO_IDLE; } break; @@ -726,9 +723,7 @@ void ChademoBattery::handle_chademo_sequence() { * Used for triggers/error handling elsewhere; * State change to CHADEMO_NEGOTIATE occurs in handle_incoming_can_frame_battery(..) */ -#ifdef DEBUG_LOG -// logging.println("Awaiting initial vehicle CAN to trigger negotiation"); -#endif + DEBUG_PRINTLN("Awaiting initial vehicle CAN to trigger negotiation"); evse_init(); break; case CHADEMO_NEGOTIATE: @@ -750,7 +745,7 @@ void ChademoBattery::handle_chademo_sequence() { // that pin 4 (j) reads high if (vehicle_permission) { //lock connector here - digitalWrite(CHADEMO_LOCK, HIGH); + digitalWrite(pin_lock, HIGH); //TODO spec requires test to validate solenoid has indeed engaged. // example uses a comparator/current consumption check around solenoid @@ -780,7 +775,7 @@ void ChademoBattery::handle_chademo_sequence() { if (x102_chg_session.s.status.StatusVehicleChargingEnabled) { if (get_measured_voltage() < 20) { - digitalWrite(CHADEMO_PIN_10, HIGH); + digitalWrite(pin10, HIGH); evse_permission = true; } else { logging.println("Insulation check measures > 20v "); @@ -898,8 +893,8 @@ void ChademoBattery::handle_chademo_sequence() { */ if (get_measured_current() <= 5 && get_measured_voltage() <= 10) { /* welding detection ideally here */ - digitalWrite(CHADEMO_PIN_10, LOW); - digitalWrite(CHADEMO_PIN_2, LOW); + digitalWrite(pin10, LOW); + digitalWrite(pin2, LOW); CHADEMO_Status = CHADEMO_IDLE; } @@ -916,8 +911,8 @@ void ChademoBattery::handle_chademo_sequence() { #ifdef DEBUG_LOG logging.println("CHADEMO fault encountered, tearing down to make safe"); #endif - digitalWrite(CHADEMO_PIN_10, LOW); - digitalWrite(CHADEMO_PIN_2, LOW); + digitalWrite(pin10, LOW); + digitalWrite(pin2, LOW); evse_permission = false; vehicle_permission = false; x209_sent = false; @@ -937,14 +932,18 @@ void ChademoBattery::handle_chademo_sequence() { void ChademoBattery::setup(void) { // Performs one time setup at startup - pinMode(CHADEMO_PIN_2, OUTPUT); - digitalWrite(CHADEMO_PIN_2, LOW); - pinMode(CHADEMO_PIN_10, OUTPUT); - digitalWrite(CHADEMO_PIN_10, LOW); - pinMode(CHADEMO_LOCK, OUTPUT); - digitalWrite(CHADEMO_LOCK, LOW); - pinMode(CHADEMO_PIN_4, INPUT); - pinMode(CHADEMO_PIN_7, INPUT); + if (!esp32hal->alloc_pins(Name, pin2, pin10, pin4, pin7, pin_lock)) { + return; + } + + pinMode(pin2, OUTPUT); + digitalWrite(pin2, LOW); + pinMode(pin10, OUTPUT); + 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); datalayer.system.info.battery_protocol[63] = '\0'; @@ -995,5 +994,3 @@ void ChademoBattery::setup(void) { // Performs one time setup at startup setupMillis = millis(); } - -#endif diff --git a/Software/src/battery/CHADEMO-BATTERY.h b/Software/src/battery/CHADEMO-BATTERY.h index 09c5a25b..23d56828 100644 --- a/Software/src/battery/CHADEMO-BATTERY.h +++ b/Software/src/battery/CHADEMO-BATTERY.h @@ -16,6 +16,18 @@ class ChademoBattery : public CanBattery { 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 handle_incoming_can_frame(CAN_frame rx_frame); virtual void update_values(); @@ -31,6 +43,7 @@ class ChademoBattery : public CanBattery { static constexpr const char* Name = "Chademo V2X mode"; private: + gpio_num_t pin2, pin10, pin4, pin7, pin_lock, precharge, positive_contactor; ChademoBatteryHtmlRenderer renderer; void process_vehicle_charging_minimums(CAN_frame rx_frame); diff --git a/Software/src/battery/CanBattery.cpp b/Software/src/battery/CanBattery.cpp index c2d2d3e0..ad6dadca 100644 --- a/Software/src/battery/CanBattery.cpp +++ b/Software/src/battery/CanBattery.cpp @@ -1,8 +1,12 @@ #include "CanBattery.h" #include "../../src/include.h" -CanBattery::CanBattery(bool halfSpeed) { +CanBattery::CanBattery(CAN_Speed speed) { can_interface = can_config.battery; register_transmitter(this); - register_can_receiver(this, can_interface, halfSpeed); + register_can_receiver(this, can_interface, speed); +} + +CAN_Speed CanBattery::change_can_speed(CAN_Speed speed) { + return ::change_can_speed(can_interface, speed); } diff --git a/Software/src/battery/CanBattery.h b/Software/src/battery/CanBattery.h index 104d3a48..a59fe3e2 100644 --- a/Software/src/battery/CanBattery.h +++ b/Software/src/battery/CanBattery.h @@ -5,6 +5,7 @@ #include "src/communication/Transmitter.h" #include "src/communication/can/CanReceiver.h" +#include "src/communication/can/comm_can.h" #include "src/devboard/utils/types.h" // Abstract base class for batteries using the CAN bus @@ -22,13 +23,15 @@ class CanBattery : public Battery, Transmitter, CanReceiver { protected: CAN_Interface can_interface; - CanBattery(bool halfSpeed = false); + CanBattery(CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS); - CanBattery(CAN_Interface interface, bool halfSpeed = false) { + CanBattery(CAN_Interface interface, CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS) { can_interface = interface; register_transmitter(this); - register_can_receiver(this, can_interface, halfSpeed); + register_can_receiver(this, can_interface, speed); } + + CAN_Speed change_can_speed(CAN_Speed speed); }; #endif diff --git a/Software/src/battery/DALY-BMS.cpp b/Software/src/battery/DALY-BMS.cpp index d949445a..485c68f9 100644 --- a/Software/src/battery/DALY-BMS.cpp +++ b/Software/src/battery/DALY-BMS.cpp @@ -70,7 +70,14 @@ void DalyBms::setup(void) { // Performs one time setup at startup datalayer.battery.info.total_capacity_Wh = BATTERY_WH_MAX; 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]) { diff --git a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp index 4516b86a..cd84ece5 100644 --- a/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp +++ b/Software/src/battery/JAGUAR-IPACE-BATTERY.cpp @@ -56,7 +56,7 @@ CAN_frame ipace_keep_alive = {.FD = false, .ID = 0x59e, .data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/ -static void print_units(char* header, int value, char* units) { +static void print_units(const char* header, int value, const char* units) { logging.print(header); logging.print(value); logging.print(units); @@ -226,7 +226,7 @@ void JaguarIpaceBattery::transmit_can(unsigned long currentMillis) { } void JaguarIpaceBattery::setup(void) { // Performs one time setup at startup - strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63); + strncpy(datalayer.system.info.battery_protocol, Name, 63); datalayer.system.info.battery_protocol[63] = '\0'; datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.cpp b/Software/src/battery/KIA-E-GMP-BATTERY.cpp index 43576212..368f748e 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.cpp +++ b/Software/src/battery/KIA-E-GMP-BATTERY.cpp @@ -3,7 +3,6 @@ #include "../datalayer/datalayer.h" #include "../devboard/utils/events.h" #include "../include.h" -#include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" const unsigned char crc8_table[256] = { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies diff --git a/Software/src/battery/KIA-E-GMP-BATTERY.h b/Software/src/battery/KIA-E-GMP-BATTERY.h index a0f4bba0..dcbc4890 100644 --- a/Software/src/battery/KIA-E-GMP-BATTERY.h +++ b/Software/src/battery/KIA-E-GMP-BATTERY.h @@ -2,11 +2,8 @@ #define KIA_E_GMP_BATTERY_H #include #include "../include.h" -#include "../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" #include "CanBattery.h" -extern ACAN2517FD canfd; - #define ESTIMATE_SOC_FROM_CELLVOLTAGE #ifdef KIA_E_GMP_BATTERY diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 38806e09..7704446b 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -1,13 +1,10 @@ #include "NISSAN-LEAF-BATTERY.h" -#include "../include.h" -#ifdef MQTT -#include "../devboard/mqtt/mqtt.h" -#endif #include "../communication/can/comm_can.h" #include "../datalayer/datalayer.h" #include "../datalayer/datalayer_extended.h" //For "More battery info" webpage #include "../devboard/utils/events.h" #include "../devboard/utils/logging.h" +#include "../include.h" #include "../charger/CanCharger.h" diff --git a/Software/src/battery/RJXZS-BMS.h b/Software/src/battery/RJXZS-BMS.h index bbdf995c..ff5eae70 100644 --- a/Software/src/battery/RJXZS-BMS.h +++ b/Software/src/battery/RJXZS-BMS.h @@ -11,7 +11,7 @@ class RjxzsBms : public CanBattery { public: - RjxzsBms() : CanBattery(true) {} + RjxzsBms() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {} virtual void setup(void); virtual void handle_incoming_can_frame(CAN_frame rx_frame); diff --git a/Software/src/battery/Shunt.h b/Software/src/battery/Shunt.h index 55b0c849..20e7f1eb 100644 --- a/Software/src/battery/Shunt.h +++ b/Software/src/battery/Shunt.h @@ -3,8 +3,11 @@ #include "src/communication/Transmitter.h" #include "src/communication/can/CanReceiver.h" +#include "src/communication/can/comm_can.h" #include "src/devboard/utils/types.h" +enum class ShuntType { None = 0, BmwSbox = 1, Highest }; + class CanShunt : public Transmitter, CanReceiver { public: virtual void setup() = 0; @@ -34,4 +37,8 @@ class CanShunt : public Transmitter, CanReceiver { extern CanShunt* shunt; +extern std::vector supported_shunt_types(); +extern const char* name_for_shunt_type(ShuntType type); +extern ShuntType user_selected_shunt_type; + #endif diff --git a/Software/src/battery/Shunts.cpp b/Software/src/battery/Shunts.cpp index 35a0b98c..1e0349b1 100644 --- a/Software/src/battery/Shunts.cpp +++ b/Software/src/battery/Shunts.cpp @@ -2,7 +2,35 @@ #include "Shunt.h" CanShunt* shunt = nullptr; +ShuntType user_selected_shunt_type = ShuntType::None; +#ifdef COMMON_IMAGE +#ifdef SELECTED_SHUNT_CLASS +#error "Compile time SELECTED_SHUNT_CLASS should not be defined with COMMON_IMAGE" +#endif + +void setup_can_shunt() { + if (shunt) { + return; + } + + switch (user_selected_shunt_type) { + case ShuntType::None: + shunt = nullptr; + return; + case ShuntType::BmwSbox: + shunt = new BmwSbox(); + break; + default: + return; + } + + if (shunt) { + shunt->setup(); + } +} + +#else void setup_can_shunt() { if (shunt) { return; @@ -15,3 +43,25 @@ void setup_can_shunt() { } #endif } +#endif + +extern std::vector supported_shunt_types() { + std::vector types; + + for (int i = 0; i < (int)ShuntType::Highest; i++) { + types.push_back((ShuntType)i); + } + + return types; +} + +extern const char* name_for_shunt_type(ShuntType type) { + switch (type) { + case ShuntType::None: + return "None"; + case ShuntType::BmwSbox: + return BmwSbox::Name; + default: + return nullptr; + } +} diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 65215b63..43bcb29c 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -1765,20 +1765,20 @@ void TeslaModel3YBattery::setup(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, Name, 63); datalayer.system.info.battery_protocol[63] = '\0'; -#ifdef LFP_CHEMISTRY - datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; - datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; - datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP; - datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_LFP; - datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_LFP; - datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_LFP; -#else // Startup in NCM/A mode - datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA; - datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA; - datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; - datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM; - datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM; -#endif // !LFP_CHEMISTRY + + if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_LFP; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_LFP; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_LFP; + } else { + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM; + } } void TeslaModelSXBattery::setup(void) { diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 9982eecf..ca5dacfa 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -508,7 +508,8 @@ class TeslaBattery : public CanBattery { class TeslaModel3YBattery : public TeslaBattery { public: - TeslaModel3YBattery() { + TeslaModel3YBattery(battery_chemistry_enum chemistry) { + datalayer.battery.info.chemistry = chemistry; #ifdef EXP_TESLA_BMS_DIGITAL_HVIL operate_contactors = true; #endif diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index c3c91f7a..2c2eb3bc 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -2,7 +2,7 @@ #include "../datalayer/datalayer.h" #include "../include.h" -static void print_units(char* header, int value, char* units) { +static void print_units(const char* header, int value, const char* units) { logging.print(header); logging.print(value); logging.print(units); diff --git a/Software/src/communication/can/CanReceiver.h b/Software/src/communication/can/CanReceiver.h index 1ecae876..9737568c 100644 --- a/Software/src/communication/can/CanReceiver.h +++ b/Software/src/communication/can/CanReceiver.h @@ -8,7 +8,4 @@ class CanReceiver { 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, bool halfSpeed = false); - #endif diff --git a/Software/src/communication/can/comm_can.cpp b/Software/src/communication/can/comm_can.cpp index 293aa179..5a8701c2 100644 --- a/Software/src/communication/can/comm_can.cpp +++ b/Software/src/communication/can/comm_can.cpp @@ -1,26 +1,21 @@ #include "comm_can.h" +#include #include #include "../../include.h" +#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" +#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" +#include "../../lib/pierremolinaro-acan2515/ACAN2515.h" +#include "comm_can.h" #include "src/devboard/sdcard/sdcard.h" #include "src/devboard/utils/logging.h" struct CanReceiverRegistration { CanReceiver* receiver; - bool halfSpeed; + CAN_Speed speed; }; static std::multimap can_receivers; -bool hasHalfSpeedReceivers(const CAN_Interface& iface) { - auto range = can_receivers.equal_range(iface); - for (auto it = range.first; it != range.second; ++it) { - if (it->second.halfSpeed) { - return true; - } - } - return false; -} - // Parameters CAN_device_t CAN_cfg; // CAN Config const uint8_t rx_queue_size = 10; // Receive Queue size @@ -29,104 +24,170 @@ volatile bool send_ok_2515 = 0; volatile bool send_ok_2518 = 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); -#ifdef CAN_ADDON +void register_can_receiver(CanReceiver* receiver, CAN_Interface interface, CAN_Speed speed) { + can_receivers.insert({interface, {receiver, speed}}); + DEBUG_PRINTF("CAN receiver registered, total: %d\n", can_receivers.size()); +} + static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h SPIClass SPI2515; -ACAN2515 can(MCP2515_CS, SPI2515, MCP2515_INT); + +ACAN2515* can2515; +ACAN2515Settings* settings2515; + static ACAN2515_Buffer16 gBuffer; -#endif //CAN_ADDON -#ifdef CANFD_ADDON + SPIClass SPI2517; -ACAN2517FD canfd(MCP2517_CS, SPI2517, MCP2517_INT); -#endif //CANFD_ADDON +ACAN2517FD* canfd; +ACAN2517FDSettings* settings2517; // Initialization functions -void init_CAN() { - DEBUG_PRINTF("init_CAN called\n"); -// CAN pins -#ifdef CAN_SE_PIN - pinMode(CAN_SE_PIN, OUTPUT); - digitalWrite(CAN_SE_PIN, LOW); -#endif // CAN_SE_PIN +bool native_can_initialized = false; - // Half-speed currently only supported for CAN_NATIVE - auto anyHalfSpeedNative = hasHalfSpeedReceivers(CAN_Interface::CAN_NATIVE); +bool init_CAN() { - CAN_cfg.speed = anyHalfSpeedNative ? CAN_SPEED_250KBPS : CAN_SPEED_500KBPS; - CAN_cfg.tx_pin_id = CAN_TX_PIN; - CAN_cfg.rx_pin_id = CAN_RX_PIN; - CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t)); - // Init CAN Module - ESP32Can.CANInit(); + auto nativeIt = can_receivers.find(CAN_NATIVE); + if (nativeIt != can_receivers.end()) { + auto se_pin = esp32hal->CAN_SE_PIN(); + auto tx_pin = esp32hal->CAN_TX_PIN(); + auto rx_pin = esp32hal->CAN_RX_PIN(); - DEBUG_PRINTF("init_CAN performed\n"); + if (se_pin != GPIO_NUM_NC) { + if (!esp32hal->alloc_pins("CAN", se_pin)) { + return false; + } + pinMode(se_pin, OUTPUT); + digitalWrite(se_pin, LOW); + } -#ifdef CAN_ADDON -#ifdef DEBUG_LOG - logging.println("Dual CAN Bus (ESP32+MCP2515) selected"); -#endif // DEBUG_LOG - gBuffer.initWithSize(25); - SPI2515.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI); - ACAN2515Settings settings2515(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s - settings2515.mRequestedMode = ACAN2515Settings::NormalMode; - const uint16_t errorCode2515 = can.begin(settings2515, [] { can.isr(); }); - if (errorCode2515 == 0) { -#ifdef DEBUG_LOG - logging.println("Can ok"); -#endif // DEBUG_LOG - } else { -#ifdef DEBUG_LOG - logging.print("Error Can: 0x"); - logging.println(errorCode2515, HEX); -#endif // DEBUG_LOG - set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515); + CAN_cfg.speed = (CAN_speed_t)nativeIt->second.speed; + + 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)); + // Init CAN Module + ESP32Can.CANInit(); + native_can_initialized = true; } -#endif // CAN_ADDON -#ifdef CANFD_ADDON + auto addonIt = can_receivers.find(CAN_ADDON_MCP2515); + if (addonIt != 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 DEBUG_LOG - logging.println("CAN FD add-on (ESP32+MCP2517) selected"); + logging.println("Dual CAN Bus (ESP32+MCP2515) selected"); #endif // DEBUG_LOG - SPI2517.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI); - ACAN2517FDSettings settings2517(CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000, - 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 -#else // not USE_CANFD_INTERFACE_AS_CLASSIC_CAN - settings2517.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD -#endif // USE_CANFD_INTERFACE_AS_CLASSIC_CAN - const uint32_t errorCode2517 = canfd.begin(settings2517, [] { canfd.isr(); }); - canfd.poll(); - if (errorCode2517 == 0) { + gBuffer.initWithSize(25); + + can2515 = new ACAN2515(cs_pin, SPI2515, int_pin); + + SPI2515.begin(sck_pin, miso_pin, mosi_pin); + + // CAN bit rate 250 or 500 kb/s + auto bitRate = (int)addonIt->second.speed * 1000UL; + + settings2515 = new ACAN2515Settings(QUARTZ_FREQUENCY, bitRate); + settings2515->mRequestedMode = ACAN2515Settings::NormalMode; + const uint16_t errorCode2515 = can2515->begin(*settings2515, [] { can2515->isr(); }); + if (errorCode2515 == 0) { #ifdef DEBUG_LOG - logging.print("Bit Rate prescaler: "); - logging.println(settings2517.mBitRatePrescaler); - logging.print("Arbitration Phase segment 1: "); - logging.print(settings2517.mArbitrationPhaseSegment1); - logging.print(" segment 2: "); - logging.print(settings2517.mArbitrationPhaseSegment2); - logging.print(" SJW: "); - logging.println(settings2517.mArbitrationSJW); - logging.print("Actual Arbitration Bit Rate: "); - logging.print(settings2517.actualArbitrationBitRate()); - logging.print(" bit/s"); - logging.print(" (Exact:"); - logging.println(settings2517.exactArbitrationBitRate() ? "yes)" : "no)"); - logging.print("Arbitration Sample point: "); - logging.print(settings2517.arbitrationSamplePointFromBitStart()); - logging.println("%"); + logging.println("Can ok"); #endif // DEBUG_LOG - } else { + } else { #ifdef DEBUG_LOG - logging.print("CAN-FD Configuration error 0x"); - logging.println(errorCode2517, HEX); + logging.print("Error Can: 0x"); + logging.println(errorCode2515, HEX); #endif // DEBUG_LOG - set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517); + set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515); + return false; + } } -#endif // CANFD_ADDON + + auto fdNativeIt = can_receivers.find(CANFD_NATIVE); + auto fdAddonIt = can_receivers.find(CANFD_ADDON_MCP2518); + + if (fdNativeIt != can_receivers.end() || fdAddonIt != can_receivers.end()) { + + auto speed = (fdNativeIt != can_receivers.end()) ? fdNativeIt->second.speed : fdAddonIt->second.speed; + + 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 + logging.println("CAN FD add-on (ESP32+MCP2517) selected"); +#endif // DEBUG_LOG + SPI2517.begin(sck_pin, sdo_pin, sdi_pin); + auto bitRate = (int)speed * 1000UL; + settings2517 = new ACAN2517FDSettings( + CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, bitRate, + DataBitRateFactor::x4); // Arbitration bit rate: 250/500 kbit/s, data bit rate: 1/2 Mbit/s + + // ListenOnly / Normal20B / NormalFD + settings2517->mRequestedMode = use_canfd_as_can ? ACAN2517FDSettings::Normal20B : ACAN2517FDSettings::NormalFD; + + const uint32_t errorCode2517 = canfd->begin(*settings2517, [] { canfd->isr(); }); + canfd->poll(); + if (errorCode2517 == 0) { +#ifdef DEBUG_LOG + logging.print("Bit Rate prescaler: "); + logging.println(settings2517->mBitRatePrescaler); + logging.print("Arbitration Phase segment 1: "); + logging.print(settings2517->mArbitrationPhaseSegment1); + logging.print(" segment 2: "); + logging.print(settings2517->mArbitrationPhaseSegment2); + logging.print(" SJW: "); + logging.println(settings2517->mArbitrationSJW); + logging.print("Actual Arbitration Bit Rate: "); + logging.print(settings2517->actualArbitrationBitRate()); + logging.print(" bit/s"); + logging.print(" (Exact:"); + logging.println(settings2517->exactArbitrationBitRate() ? "yes)" : "no)"); + logging.print("Arbitration Sample point: "); + logging.print(settings2517->arbitrationSamplePointFromBitStart()); + logging.println("%"); +#endif // DEBUG_LOG + } else { +#ifdef DEBUG_LOG + logging.print("CAN-FD Configuration error 0x"); + logging.println(errorCode2517, HEX); +#endif // DEBUG_LOG + set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517); + return false; + } + } + + return true; } void transmit_can_frame(CAN_frame* tx_frame, int interface) { @@ -156,7 +217,6 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) { } break; case CAN_ADDON_MCP2515: { -#ifdef CAN_ADDON //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2 CANMessage MCP2515Frame; MCP2515Frame.id = tx_frame->ID; @@ -167,17 +227,13 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) { MCP2515Frame.data[i] = tx_frame->data.u8[i]; } - send_ok_2515 = can.tryToSend(MCP2515Frame); + send_ok_2515 = can2515->tryToSend(MCP2515Frame); if (!send_ok_2515) { 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; case CANFD_NATIVE: case CANFD_ADDON_MCP2518: { -#ifdef CANFD_ADDON CANFDMessage MCP2518Frame; if (tx_frame->FD) { MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH; @@ -190,13 +246,10 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) { for (uint8_t i = 0; i < MCP2518Frame.len; 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) { 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; default: // Invalid interface sent with function call. TODO: Raise event that coders messed up @@ -206,13 +259,17 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) { // Receive functions void receive_can() { - receive_frame_can_native(); // Receive CAN messages from native CAN port -#ifdef CAN_ADDON - receive_frame_can_addon(); // Receive CAN messages on add-on MCP2515 chip -#endif // CAN_ADDON -#ifdef CANFD_ADDON - receive_frame_canfd_addon(); // Receive CAN-FD messages. -#endif // CANFD_ADDON + if (native_can_initialized) { + receive_frame_can_native(); // Receive CAN messages from native CAN port + } + + if (can2515) { + receive_frame_can_addon(); // Receive CAN messages on add-on MCP2515 chip + } + + if (canfd) { + receive_frame_canfd_addon(); // Receive CAN-FD messages. + } } void receive_frame_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port @@ -234,13 +291,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 CAN_frame rx_frame; // Struct with our CAN format CANMessage MCP2515frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library - if (can.available()) { - can.receive(MCP2515frame); + if (can2515->available()) { + can2515->receive(MCP2515frame); rx_frame.ID = MCP2515frame.id; rx_frame.ext_ID = MCP2515frame.ext ? CAN_frame_ext : CAN_frame_std; @@ -253,26 +309,23 @@ void receive_frame_can_addon() { // This section checks if we have a complete C 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 CANFDMessage MCP2518frame; int count = 0; - while (canfd.available() && count++ < 16) { - canfd.receive(MCP2518frame); + while (canfd->available() && count++ < 16) { + canfd->receive(MCP2518frame); CAN_frame rx_frame; rx_frame.ID = MCP2518frame.id; rx_frame.ext_ID = MCP2518frame.ext; 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 map_can_frame_to_variable(&rx_frame, CANFD_ADDON_MCP2518); map_can_frame_to_variable(&rx_frame, CANFD_NATIVE); } } -#endif // CANFD_ADDON // Support functions void print_can_frame(CAN_frame frame, frameDirection msgDir) { @@ -299,11 +352,6 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) { } } -void register_can_receiver(CanReceiver* receiver, CAN_Interface interface, bool halfSpeed) { - can_receivers.insert({interface, {receiver, halfSpeed}}); - DEBUG_PRINTF("CAN receiver registered, total: %d\n", can_receivers.size()); -} - void map_can_frame_to_variable(CAN_frame* rx_frame, CAN_Interface interface) { if (interface != CANFD_NATIVE) { //Avoid printing twice due to receive_frame_canfd_addon sending to both FD interfaces @@ -361,3 +409,45 @@ void dump_can_frame(CAN_frame& frame, frameDirection msgDir) { datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer } + +void stop_can() { + if (can_receivers.find(CAN_NATIVE) != can_receivers.end()) { + ESP32Can.CANStop(); + } + + if (can2515) { + can2515->end(); + SPI2515.end(); + } + + if (canfd) { + canfd->end(); + SPI2517.end(); + } +} + +void restart_can() { + if (can_receivers.find(CAN_NATIVE) != can_receivers.end()) { + ESP32Can.CANInit(); + } + + if (can2515) { + SPI2515.begin(); + can2515->begin(*settings2515, [] { can2515->isr(); }); + } + + if (canfd) { + SPI2517.begin(); + canfd->begin(*settings2517, [] { can2515->isr(); }); + } +} + +CAN_Speed change_can_speed(CAN_Interface interface, CAN_Speed speed) { + auto oldSpeed = (CAN_Speed)CAN_cfg.speed; + if (interface == CAN_Interface::CAN_NATIVE) { + CAN_cfg.speed = (CAN_speed_t)speed; + // ReInit native CAN module at new speed + ESP32Can.CANInit(); + } + return oldSpeed; +} diff --git a/Software/src/communication/can/comm_can.h b/Software/src/communication/can/comm_can.h index dcd5b4f5..b2327eb3 100644 --- a/Software/src/communication/can/comm_can.h +++ b/Software/src/communication/can/comm_can.h @@ -1,34 +1,42 @@ #ifndef _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" -#ifdef CAN_ADDON -#include "../../lib/pierremolinaro-acan2515/ACAN2515.h" -#endif //CAN_ADDON -#ifdef CANFD_ADDON -#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" -#endif //CANFD_ADDON +extern bool use_canfd_as_can; void dump_can_frame(CAN_frame& frame, frameDirection msgDir); void transmit_can_frame(CAN_frame* tx_frame, int interface); +class CanReceiver; + +enum class CAN_Speed { + CAN_SPEED_100KBPS = 100, + CAN_SPEED_125KBPS = 125, + CAN_SPEED_200KBPS = 200, + CAN_SPEED_250KBPS = 250, + CAN_SPEED_500KBPS = 500, + CAN_SPEED_800KBPS = 800, + CAN_SPEED_1000KBPS = 1000 +}; + +// Register a receiver object for a given CAN interface. +// By default receivers expect the CAN interface to be operated at "fast" speed. +// If halfSpeed is true, half speed is used. +void register_can_receiver(CanReceiver* receiver, CAN_Interface interface, + CAN_Speed speed = CAN_Speed::CAN_SPEED_500KBPS); + /** - * @brief Initialization function for CAN. + * @brief Initializes all CAN interfaces requested earlier by other modules (see register_can_receiver) * * @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. Respective CanReceivers are called. * * @param[in] void * @@ -72,4 +80,13 @@ void receive_frame_canfd_addon(); */ void print_can_frame(CAN_frame frame, frameDirection msgDir); +// Stop/pause CAN communication for all interfaces +void stop_can(); + +// Restart CAN communication for all interfaces +void restart_can(); + +// Change the speed of the CAN interface and return the old speed. +CAN_Speed change_can_speed(CAN_Interface interface, CAN_Speed speed); + #endif diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp index 870163ab..1b414f2b 100644 --- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp @@ -25,7 +25,7 @@ bool periodic_bms_reset = periodic_bms_reset_default; #ifdef REMOTE_BMS_RESET const bool remote_bms_reset_default = true; #else -const bool remote_bms_reset_default = true; +const bool remote_bms_reset_default = false; #endif bool remote_bms_reset = remote_bms_reset_default; @@ -92,41 +92,57 @@ void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) { // Initialization functions -void init_contactors() { +const char* contactors = "Contactors"; + +bool init_contactors() { // Init contactor pins 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) { // Setup PWM Channel Frequency and Resolution - ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Positive_Channel); - ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Negative_Channel); + ledcAttachChannel(posPin, PWM_Freq, PWM_Res, PWM_Positive_Channel); + ledcAttachChannel(negPin, PWM_Freq, PWM_Res, PWM_Negative_Channel); // Set all pins OFF (0% PWM) - ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); - ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); + ledcWrite(posPin, PWM_OFF_DUTY); + ledcWrite(negPin, PWM_OFF_DUTY); } else { //Normal CONTACTOR_CONTROL - pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT); - set(POSITIVE_CONTACTOR_PIN, OFF); - pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT); - set(NEGATIVE_CONTACTOR_PIN, OFF); + pinMode(posPin, OUTPUT); + set(posPin, OFF); + pinMode(negPin, OUTPUT); + set(negPin, OFF); } // Precharge never has PWM regardless of setting - pinMode(PRECHARGE_PIN, OUTPUT); - set(PRECHARGE_PIN, OFF); + pinMode(precPin, OUTPUT); + 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 (periodic_bms_reset || remote_bms_reset) { - pinMode(BMS_POWER, OUTPUT); - digitalWrite(BMS_POWER, HIGH); + if (contactor_control_enabled_double_battery) { + auto second_contactors = esp32hal->SECOND_BATTERY_CONTACTORS_PIN(); + if (!esp32hal->alloc_pins(contactors, second_contactors)) { + return false; + } + + pinMode(second_contactors, OUTPUT); + set(second_contactors, OFF); } -#endif + + // 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) { @@ -144,9 +160,14 @@ void handle_contactors() { datalayer.system.status.inverter_allows_contactor_closing = inverter->allows_contactor_closing(); } -#ifdef BMS_POWER - handle_BMSpower(); // Some batteries need to be periodically power cycled -#endif + 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 + } #ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY handle_contactors_battery2(); @@ -166,9 +187,9 @@ void handle_contactors() { } if (contactorStatus == SHUTDOWN_REQUESTED) { - set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); - set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(prechargePin, OFF); + set(negPin, OFF, PWM_OFF_DUTY); + set(posPin, OFF, PWM_OFF_DUTY); set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); 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) @@ -176,9 +197,9 @@ void handle_contactors() { // After that, check if we are OK to start turning on the battery if (contactorStatus == DISCONNECTED) { - set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); - set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY); + set(prechargePin, OFF); + set(negPin, OFF, PWM_OFF_DUTY); + set(posPin, OFF, PWM_OFF_DUTY); datalayer.system.status.contactors_engaged = false; 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 switch (contactorStatus) { case START_PRECHARGE: - set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); + set(negPin, ON, PWM_ON_DUTY); dbg_contactors("NEGATIVE"); prechargeStartTime = currentTime; contactorStatus = PRECHARGE; @@ -218,7 +239,7 @@ void handle_contactors() { case PRECHARGE: if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) { - set(PRECHARGE_PIN, ON); + set(prechargePin, ON); dbg_contactors("PRECHARGE"); negativeStartTime = currentTime; contactorStatus = POSITIVE; @@ -227,7 +248,7 @@ void handle_contactors() { case POSITIVE: if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) { - set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY); + set(posPin, ON, PWM_ON_DUTY); dbg_contactors("POSITIVE"); prechargeCompletedTime = currentTime; contactorStatus = PRECHARGE_OFF; @@ -236,9 +257,9 @@ void handle_contactors() { case PRECHARGE_OFF: if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) { - set(PRECHARGE_PIN, OFF); - set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); - set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY); + set(prechargePin, OFF); + set(negPin, ON, PWM_HOLD_DUTY); + set(posPin, ON, PWM_HOLD_DUTY); dbg_contactors("PRECHARGE_OFF"); contactorStatus = COMPLETED; datalayer.system.status.contactors_engaged = true; @@ -269,9 +290,10 @@ 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 Feature is only used if user has enabled PERIODIC_BMS_RESET in the USER_SETTINGS */ -#ifdef BMS_POWER void handle_BMSpower() { if (periodic_bms_reset || remote_bms_reset) { + auto bms_power_pin = esp32hal->BMS_POWER(); + // Get current time currentTime = millis(); @@ -285,10 +307,7 @@ void handle_BMSpower() { // If power has been removed for 30 seconds, restore the power if (datalayer.system.status.BMS_reset_in_progress && currentTime - lastPowerRemovalTime >= powerRemovalDuration) { // Reapply power to the BMS - digitalWrite(BMS_POWER, HIGH); -#ifdef BMS_2_POWER - digitalWrite(BMS_2_POWER, HIGH); // Same for battery 2 -#endif + digitalWrite(bms_power_pin, HIGH); bmsPowerOnTime = currentTime; 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 @@ -303,10 +322,11 @@ void handle_BMSpower() { } } } -#endif void start_bms_reset() { if (periodic_bms_reset || remote_bms_reset) { + auto bms_power_pin = esp32hal->BMS_POWER(); + if (!datalayer.system.status.BMS_reset_in_progress) { 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 @@ -319,12 +339,7 @@ void start_bms_reset() { // We try to keep contactors engaged during this pause, and just ramp power down to 0. setBatteryPause(true, false, false, false); -#ifdef BMS_POWER - digitalWrite(BMS_POWER, LOW); // Remove power by setting the BMS power pin to LOW -#endif -#ifdef BMS_2_POWER - digitalWrite(BMS_2_POWER, LOW); // Same for battery 2 -#endif + digitalWrite(bms_power_pin, LOW); // Remove power by setting the BMS power pin to LOW } } } diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h index e18e8431..b06a4770 100644 --- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.h +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.h @@ -36,9 +36,9 @@ void start_bms_reset(); * * @param[in] void * - * @return void + * @return true if contactor init was successful, false otherwise. */ -void init_contactors(); +bool init_contactors(); /** * @brief Handle contactors diff --git a/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp index 2a6a3d7d..9a402f75 100644 --- a/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp +++ b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.cpp @@ -1,33 +1,45 @@ #include "comm_equipmentstopbutton.h" #include "../../include.h" +STOP_BUTTON_BEHAVIOR equipment_stop_behavior = stop_button_default_behavior; + // Parameters -#ifdef EQUIPMENT_STOP_BUTTON const unsigned long equipment_button_long_press_duration = 15000; // 15 seconds for long press in case of MOMENTARY_SWITCH 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 DebouncedButton equipment_stop_button; // Debounced button object -#endif // EQUIPMENT_STOP_BUTTON // Initialization functions -#ifdef EQUIPMENT_STOP_BUTTON -void init_equipment_stop_button() { +bool init_equipment_stop_button() { + if (equipment_stop_behavior == STOP_BUTTON_BEHAVIOR::NOT_CONNECTED) { + return true; + } + + auto pin = esp32hal->EQUIPMENT_STOP_PIN(); + if (!esp32hal->alloc_pins("Equipment stop button", pin)) { + return false; + } + //using external pullup resistors NC - pinMode(EQUIPMENT_STOP_PIN, INPUT); + pinMode(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); + initDebouncedButton(equipment_stop_button, pin, NC, equipment_button_debounce_duration); + + return true; } -#endif // EQUIPMENT_STOP_BUTTON // Main functions -#ifdef 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); - if (equipment_stop_behavior == LATCHING_SWITCH) { + if (equipment_stop_behavior == STOP_BUTTON_BEHAVIOR::LATCHING_SWITCH) { if (changed_state == PRESSED) { // Changed to ON – initiating equipment stop. setBatteryPause(true, false, true); @@ -35,7 +47,7 @@ void monitor_equipment_stop_button() { // Changed to OFF – ending equipment stop. 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 (timeSincePress < equipment_button_long_press_duration) { @@ -48,4 +60,3 @@ void monitor_equipment_stop_button() { } } } -#endif // EQUIPMENT_STOP_BUTTON diff --git a/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h index f4f5a989..d7104d2d 100644 --- a/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h +++ b/Software/src/communication/equipmentstopbutton/comm_equipmentstopbutton.h @@ -1,11 +1,7 @@ #ifndef _COMM_EQUIPMENTSTOPBUTTON_H_ #define _COMM_EQUIPMENTSTOPBUTTON_H_ -#include "../../include.h" - -#ifdef EQUIPMENT_STOP_BUTTON #include "../../devboard/utils/debounce_button.h" -#endif /** * @brief Initialization of equipment stop button @@ -14,7 +10,7 @@ * * @return void */ -void init_equipment_stop_button(); +bool init_equipment_stop_button(); /** * @brief Monitor equipment stop button @@ -25,4 +21,8 @@ void init_equipment_stop_button(); */ void monitor_equipment_stop_button(); +enum class STOP_BUTTON_BEHAVIOR { NOT_CONNECTED = 0, LATCHING_SWITCH = 1, MOMENTARY_SWITCH = 2, Highest }; + +extern STOP_BUTTON_BEHAVIOR equipment_stop_behavior; + #endif diff --git a/Software/src/communication/nvm/comm_nvm.cpp b/Software/src/communication/nvm/comm_nvm.cpp index 105a1a85..d2adb904 100644 --- a/Software/src/communication/nvm/comm_nvm.cpp +++ b/Software/src/communication/nvm/comm_nvm.cpp @@ -1,4 +1,7 @@ #include "comm_nvm.h" +#include "../../communication/can/comm_can.h" +#include "../../devboard/mqtt/mqtt.h" +#include "../../devboard/wifi/wifi.h" #include "../../include.h" #include "../contactorcontrol/comm_contactorcontrol.h" @@ -15,6 +18,7 @@ void init_stored_settings() { // Always get the equipment stop status datalayer.system.settings.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false); if (datalayer.system.settings.equipment_stop_active) { + DEBUG_PRINTF("Equipment stop status set in boot."); set_event(EVENT_EQUIPMENT_STOP, 1); } @@ -25,7 +29,6 @@ void init_stored_settings() { settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active); #endif // LOAD_SAVED_SETTINGS_ON_BOOT -#ifdef WIFI char tempSSIDstring[63]; // Allocate buffer with sufficient size size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring)); if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID! @@ -38,7 +41,6 @@ void init_stored_settings() { password = tempPasswordString; } else { // Reading from settings failed. Do nothing with SSID. Raise event? } -#endif // WIFI temp = settings.getUInt("BATTERY_WH_MAX", false); if (temp != 0) { @@ -77,14 +79,56 @@ void init_stored_settings() { #ifdef COMMON_IMAGE user_selected_battery_type = (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None); + user_selected_battery_chemistry = + (battery_chemistry_enum)settings.getUInt("BATTCHEM", (int)battery_chemistry_enum::NCA); user_selected_inverter_protocol = (InverterProtocolType)settings.getUInt("INVTYPE", (int)InverterProtocolType::None); user_selected_charger_type = (ChargerType)settings.getUInt("CHGTYPE", (int)ChargerType::None); + user_selected_shunt_type = (ShuntType)settings.getUInt("SHUNTTYPE", (int)ShuntType::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); user_selected_second_battery = settings.getBool("DBLBTR", false); contactor_control_enabled = settings.getBool("CNTCTRL", false); contactor_control_enabled_double_battery = settings.getBool("CNTCTRLDBL", false); pwm_contactor_control = settings.getBool("PWMCNTCTRL", false); periodic_bms_reset = settings.getBool("PERBMSRESET", false); remote_bms_reset = settings.getBool("REMBMSRESET", false); + use_canfd_as_can = settings.getBool("CANFDASCAN", false); + + // WIFI AP is enabled by default unless disabled in the settings + wifiap_enabled = settings.getBool("WIFIAPENABLED", true); + passwordAP = settings.getString("APPASSWORD", "123456789").c_str(); + mqtt_enabled = settings.getBool("MQTTENABLED", false); + ha_autodiscovery_enabled = settings.getBool("HADISC", false); + + custom_hostname = settings.getString("HOSTNAME").c_str(); + + mqtt_server = settings.getString("MQTTSERVER").c_str(); + mqtt_port = settings.getUInt("MQTTPORT", 0); + mqtt_user = settings.getString("MQTTUSER").c_str(); + mqtt_password = settings.getString("MQTTPASSWORD").c_str(); + #endif settings.end(); @@ -103,14 +147,12 @@ void store_settings() { return; } -#ifdef WIFI if (!settings.putString("SSID", String(ssid.c_str()))) { set_event(EVENT_PERSISTENT_SAVE_INFO, 1); } if (!settings.putString("PASSWORD", String(password.c_str()))) { set_event(EVENT_PERSISTENT_SAVE_INFO, 2); } -#endif if (!settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh)) { set_event(EVENT_PERSISTENT_SAVE_INFO, 3); diff --git a/Software/src/communication/nvm/comm_nvm.h b/Software/src/communication/nvm/comm_nvm.h index 023a757b..6d47d13d 100644 --- a/Software/src/communication/nvm/comm_nvm.h +++ b/Software/src/communication/nvm/comm_nvm.h @@ -3,8 +3,10 @@ #include "../../include.h" +#include #include "../../datalayer/datalayer.h" #include "../../devboard/utils/events.h" +#include "../../devboard/utils/logging.h" #include "../../devboard/wifi/wifi.h" /** @@ -46,16 +48,48 @@ class BatteryEmulatorSettingsStore { ~BatteryEmulatorSettingsStore() { settings.end(); } + void clearAll() { + settings.clear(); + settingsUpdated = true; + } + uint32_t getUInt(const char* name, uint32_t defaultValue) { return settings.getUInt(name, defaultValue); } - void saveUInt(const char* name, uint32_t value) { settings.putUInt(name, value); } + void saveUInt(const char* name, uint32_t value) { + auto oldValue = settings.getUInt(name, std::numeric_limits::max()); + settings.putUInt(name, value); + settingsUpdated = settingsUpdated || value != oldValue; + } - bool getBool(const char* name) { return settings.getBool(name, false); } + bool settingExists(const char* name) { return settings.isKey(name); } - void saveBool(const char* name, bool value) { settings.putBool(name, value); } + bool getBool(const char* name, bool defaultValue = false) { return settings.getBool(name, defaultValue); } + + void saveBool(const char* name, bool value) { + auto oldValue = settings.getBool(name, false); + settings.putBool(name, value); + settingsUpdated = settingsUpdated || value != oldValue; + } + + String getString(const char* name) { return settings.getString(name, String()); } + + String getString(const char* name, const char* defaultValue) { + return settings.getString(name, String(defaultValue)); + } + + void saveString(const char* name, const char* value) { + auto oldValue = settings.getString(name); + settings.putString(name, value); + settingsUpdated = settingsUpdated || String(value) != oldValue; + } + + bool were_settings_updated() const { return settingsUpdated; } private: Preferences settings; + + // To track if settings were updated + bool settingsUpdated = false; }; #endif diff --git a/Software/src/communication/precharge_control/precharge_control.cpp b/Software/src/communication/precharge_control/precharge_control.cpp index a695a0cd..6498ae4d 100644 --- a/Software/src/communication/precharge_control/precharge_control.cpp +++ b/Software/src/communication/precharge_control/precharge_control.cpp @@ -2,7 +2,15 @@ #include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer_extended.h" #include "../../include.h" + #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 #define MAX_PRECHARGE_TIME_MS 15000 // Maximum time precharge may be enabled #define Precharge_default_PWM_Freq 11000 @@ -25,19 +33,36 @@ static int32_t prev_external_voltage = 20000; // Initialization functions -void init_precharge_control() { +bool init_precharge_control() { + if (!precharge_control_enabled) { + return true; + } + // Setup PWM Channel Frequency and Resolution #ifdef DEBUG_LOG logging.printf("Precharge control initialised\n"); #endif - pinMode(HIA4V1_PIN, OUTPUT); - digitalWrite(HIA4V1_PIN, LOW); - pinMode(INVERTER_DISCONNECT_CONTACTOR_PIN, OUTPUT); - digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, LOW); + + auto hia4v1_pin = esp32hal->HIA4V1_PIN(); + auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_PIN(); + + 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 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 external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV; @@ -49,14 +74,14 @@ void handle_precharge_control(unsigned long currentMillis) { break; case AUTO_PRECHARGE_START: freq = Precharge_default_PWM_Freq; - ledcAttachChannel(HIA4V1_PIN, freq, Precharge_PWM_Res, PWM_Precharge_Channel); - ledcWriteTone(HIA4V1_PIN, freq); // Set frequency and set dutycycle to 50% + ledcAttachChannel(hia4v1_pin, freq, Precharge_PWM_Res, PWM_Precharge_Channel); + ledcWriteTone(hia4v1_pin, freq); // Set frequency and set dutycycle to 50% prechargeStartTime = currentMillis; datalayer.system.status.precharge_status = AUTO_PRECHARGE_PRECHARGING; #ifdef DEBUG_LOG logging.printf("Precharge: Starting sequence\n"); #endif - digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, OFF); + digitalWrite(inverter_disconnect_contactor_pin, OFF); break; 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, external_voltage / 10, freq); #endif - ledcWriteTone(HIA4V1_PIN, freq); + ledcWriteTone(hia4v1_pin, freq); } if ((datalayer.battery.status.real_bms_status != BMS_STANDBY && datalayer.battery.status.real_bms_status != BMS_ACTIVE) || datalayer.battery.status.bms_status != ACTIVE || datalayer.system.settings.equipment_stop_active) { - pinMode(HIA4V1_PIN, OUTPUT); - digitalWrite(HIA4V1_PIN, LOW); - digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON); + pinMode(hia4v1_pin, OUTPUT); + digitalWrite(hia4v1_pin, LOW); + digitalWrite(inverter_disconnect_contactor_pin, ON); datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE; #ifdef DEBUG_LOG logging.printf("Precharge: Disabling Precharge bms not standby/active or equipment stop\n"); #endif } else if (currentMillis - prechargeStartTime >= MAX_PRECHARGE_TIME_MS || datalayer.battery.status.real_bms_status == BMS_FAULT) { - pinMode(HIA4V1_PIN, OUTPUT); - digitalWrite(HIA4V1_PIN, LOW); - digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON); + pinMode(hia4v1_pin, OUTPUT); + digitalWrite(hia4v1_pin, LOW); + digitalWrite(inverter_disconnect_contactor_pin, ON); datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF; #ifdef DEBUG_LOG 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 } else if (datalayer.system.status.battery_allows_contactor_closing) { - pinMode(HIA4V1_PIN, OUTPUT); - digitalWrite(HIA4V1_PIN, LOW); - digitalWrite(INVERTER_DISCONNECT_CONTACTOR_PIN, ON); + pinMode(hia4v1_pin, OUTPUT); + digitalWrite(hia4v1_pin, LOW); + digitalWrite(inverter_disconnect_contactor_pin, ON); datalayer.system.status.precharge_status = AUTO_PRECHARGE_COMPLETED; #ifdef DEBUG_LOG 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.settings.equipment_stop_active || datalayer.battery.status.bms_status != FAULT) { datalayer.system.status.precharge_status = AUTO_PRECHARGE_IDLE; - pinMode(HIA4V1_PIN, OUTPUT); - digitalWrite(HIA4V1_PIN, LOW); + pinMode(hia4v1_pin, OUTPUT); + digitalWrite(hia4v1_pin, LOW); #ifdef DEBUG_LOG logging.printf("Precharge: equipment stop activated -> IDLE\n"); #endif @@ -146,4 +171,3 @@ void handle_precharge_control(unsigned long currentMillis) { break; } } -#endif // PRECHARGE_CONTROL diff --git a/Software/src/communication/precharge_control/precharge_control.h b/Software/src/communication/precharge_control/precharge_control.h index 74883503..5270fe8c 100644 --- a/Software/src/communication/precharge_control/precharge_control.h +++ b/Software/src/communication/precharge_control/precharge_control.h @@ -12,7 +12,7 @@ * * @return void */ -void init_precharge_control(); +bool init_precharge_control(); /** * @brief Handle contactors diff --git a/Software/src/communication/rs485/comm_rs485.cpp b/Software/src/communication/rs485/comm_rs485.cpp index ba3048b4..807eb7c3 100644 --- a/Software/src/communication/rs485/comm_rs485.cpp +++ b/Software/src/communication/rs485/comm_rs485.cpp @@ -3,22 +3,35 @@ #include -void init_rs485() { -#ifdef RS485_EN_PIN - pinMode(RS485_EN_PIN, OUTPUT); - digitalWrite(RS485_EN_PIN, HIGH); -#endif // RS485_EN_PIN -#ifdef RS485_SE_PIN - pinMode(RS485_SE_PIN, OUTPUT); - digitalWrite(RS485_SE_PIN, HIGH); -#endif // RS485_SE_PIN -#ifdef PIN_5V_EN - pinMode(PIN_5V_EN, OUTPUT); - digitalWrite(PIN_5V_EN, HIGH); -#endif // PIN_5V_EN +bool init_rs485() { + + auto en_pin = esp32hal->RS485_EN_PIN(); + auto se_pin = esp32hal->RS485_SE_PIN(); + auto pin_5v_en = esp32hal->PIN_5V_EN(); + + if (!esp32hal->alloc_pins_ignore_unused("RS485", en_pin, se_pin, pin_5v_en)) { + return false; + } + + if (en_pin != GPIO_NUM_NC) { + pinMode(en_pin, OUTPUT); + 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 // for RS485 or Modbus comms. + + return true; } static std::list receivers; diff --git a/Software/src/communication/rs485/comm_rs485.h b/Software/src/communication/rs485/comm_rs485.h index 85a649fc..7158f462 100644 --- a/Software/src/communication/rs485/comm_rs485.h +++ b/Software/src/communication/rs485/comm_rs485.h @@ -6,9 +6,9 @@ * * @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. // Can be extended later for more complex operation. diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index f78a8659..08352dce 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -230,8 +230,6 @@ typedef struct { float CPU_temperature = 0; /** array with type of battery used, for displaying on webserver */ 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 */ char shunt_protocol[64] = {0}; /** array with type of inverter brand used, for displaying on webserver */ diff --git a/Software/src/devboard/hal/hal.cpp b/Software/src/devboard/hal/hal.cpp new file mode 100644 index 00000000..bdceb56b --- /dev/null +++ b/Software/src/devboard/hal/hal.cpp @@ -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" + +Esp32Hal* esp32hal = nullptr; + +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(); +} diff --git a/Software/src/devboard/hal/hal.h b/Software/src/devboard/hal/hal.h index 667fdb19..1bb48d97 100644 --- a/Software/src/devboard/hal/hal.h +++ b/Software/src/devboard/hal/hal.h @@ -1,16 +1,167 @@ #ifndef _HAL_H_ #define _HAL_H_ -#include "../../../USER_SETTINGS.h" +#include +#include +#include +#include "../../../src/devboard/utils/events.h" +#include "../../../src/devboard/utils/logging.h" +#include "../../../src/devboard/utils/types.h" -#if defined(HW_LILYGO) -#include "hw_lilygo.h" -#elif defined(HW_STARK) -#include "hw_stark.h" -#elif defined(HW_3LB) -#include "hw_3LB.h" -#elif defined(HW_DEVKIT) -#include "hw_devkit.h" -#endif +// Hardware Abstraction Layer base class. +// Derive a class to define board-specific parameters such as GPIO pin numbers +// This base class implements a mechanism for allocating GPIOs. +class Esp32Hal { + public: + virtual const char* name() = 0; + + // Time it takes before system is considered fully started up. + virtual duration BOOTUP_TIME() { return milliseconds(1000); } + virtual bool system_booted_up(); + + // Core assignment + virtual int CORE_FUNCTION_CORE() { return 1; } + virtual int MODBUS_CORE() { return 0; } + virtual int WIFICORE() { return 0; } + + template + bool alloc_pins(const char* name, Pins... pins) { + std::vector requested_pins = {static_cast(pins)...}; + + for (gpio_num_t pin : requested_pins) { + if (pin < 0) { + set_event(EVENT_GPIO_NOT_DEFINED, (int)pin); + allocator_name = name; + DEBUG_PRINTF("%s attempted to allocate pin %d that wasn't defined for the selected HW.\n", name, (int)pin); + return false; + } + + auto it = allocated_pins.find(pin); + if (it != allocated_pins.end()) { + allocator_name = name; + allocated_name = it->second.c_str(); + DEBUG_PRINTF("GPIO conflict for pin %d between %s and %s.\n", (int)pin, name, it->second.c_str()); + set_event(EVENT_GPIO_CONFLICT, (int)pin); + return false; + } + } + + for (gpio_num_t pin : requested_pins) { + allocated_pins[pin] = name; + } + + return true; + } + + // Helper to forward vector to variadic template + template + bool alloc_pins_from_vector(const char* name, const Vec& pins, std::index_sequence) { + return alloc_pins(name, pins[Is]...); + } + + template + bool alloc_pins_ignore_unused(const char* name, Pins... pins) { + std::vector valid_pins; + for (gpio_num_t pin : std::vector{static_cast(pins)...}) { + if (pin != GPIO_NUM_NC) { + valid_pins.push_back(pin); + } + } + + return alloc_pins_from_vector(name, valid_pins, std::make_index_sequence{}); + } + + 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 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; } + + // Returns the available comm interfaces on this HW + virtual std::vector available_interfaces() = 0; + + String failed_allocator() { return allocator_name; } + String conflicting_allocator() { return allocated_name; } + + private: + std::unordered_map allocated_pins; + + // For event logging, store the name of the allocator/allocated + // for failed gpio allocations. + String allocator_name; + String allocated_name; +}; + +extern Esp32Hal* esp32hal; + +// Needed for AsyncTCPSock library. +#define WIFI_CORE (esp32hal->WIFICORE()) + +void init_hal(); #endif diff --git a/Software/src/devboard/hal/hw_3LB.h b/Software/src/devboard/hal/hw_3LB.h index 8a808b9e..5db9ae29 100644 --- a/Software/src/devboard/hal/hw_3LB.h +++ b/Software/src/devboard/hal/hw_3LB.h @@ -1,115 +1,79 @@ #ifndef __HW_3LB_H__ #define __HW_3LB_H__ -// Board boot-up time -#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up +#include "hal.h" -// Core assignment -#define CORE_FUNCTION_CORE 1 -#define MODBUS_CORE 0 -#define WIFI_CORE 0 +class ThreeLBHal : public Esp32Hal { + public: + const char* name() { return "3LB board"; } -// RS485 -//#define PIN_5V_EN 16 -//#define RS485_EN_PIN 17 // 17 /RE -#define RS485_TX_PIN 1 // 21 -#define RS485_RX_PIN 3 // 22 -//#define RS485_SE_PIN 19 // 22 /SHDN + virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_1; } + virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_3; } -// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings -#define CAN_1_TYPE ESP32CAN + virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_27; } + virtual gpio_num_t CAN_RX_PIN() { return GPIO_NUM_26; } -// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB -#define CAN_TX_PIN GPIO_NUM_27 -#define CAN_RX_PIN GPIO_NUM_26 -//#define CAN_SE_PIN 23 + // CAN_ADDON + // SCK input of MCP2515 + virtual gpio_num_t MCP2515_SCK() { return GPIO_NUM_12; } + // 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_17; } + virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_23; } + virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_39; } + virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_21; } + virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_34; } -// CAN_ADDON defines -#define MCP2515_SCK 12 // SCK input of MCP2515 -#define MCP2515_MOSI 5 // SDI input of MCP2515 -#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors -#define MCP2515_CS 18 // CS input of MCP2515 -#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors + // CHAdeMO support pin dependencies + virtual gpio_num_t CHADEMO_PIN_2() { return GPIO_NUM_12; } + virtual gpio_num_t CHADEMO_PIN_10() { return GPIO_NUM_5; } + virtual gpio_num_t CHADEMO_PIN_7() { return GPIO_NUM_34; } + virtual gpio_num_t CHADEMO_PIN_4() { return GPIO_NUM_35; } + virtual gpio_num_t CHADEMO_LOCK() { return GPIO_NUM_18; } -// CANFD_ADDON defines -#define MCP2517_SCK 17 // SCK input of MCP2517 -#define MCP2517_SDI 23 // SDI input of MCP2517 -#define MCP2517_SDO 39 // SDO output of MCP2517 -#define MCP2517_CS 21 // CS input of MCP2517 //21 or 22 -#define MCP2517_INT 34 // INT output of MCP2517 //34 or 35 + // Contactor handling + virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_32; } + virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_33; } + virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; } + virtual gpio_num_t BMS_POWER() { return GPIO_NUM_2; } + virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_13; } -// CHAdeMO support pin dependencies -#define CHADEMO_PIN_2 12 -#define CHADEMO_PIN_10 5 -#define CHADEMO_PIN_7 34 -#define CHADEMO_PIN_4 35 -#define CHADEMO_LOCK 18 + // Automatic precharging + virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_25; } + virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_32; } -// Contactor handling -#define POSITIVE_CONTACTOR_PIN 32 -#define NEGATIVE_CONTACTOR_PIN 33 -#define PRECHARGE_PIN 25 -#define BMS_POWER 2 -#define SECOND_BATTERY_CONTACTORS_PIN 13 + // SMA CAN contactor pins + virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_36; } + virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_LED_PIN() { return GPIO_NUM_NC; } -// SMA CAN contactor pins -#define INVERTER_CONTACTOR_ENABLE_PIN 36 + // SD card + virtual gpio_num_t SD_MISO_PIN() { return GPIO_NUM_2; } + virtual gpio_num_t SD_MOSI_PIN() { return GPIO_NUM_15; } + virtual gpio_num_t SD_SCLK_PIN() { return GPIO_NUM_14; } + virtual gpio_num_t SD_CS_PIN() { return GPIO_NUM_13; } -// Automatic precharging -#define HIA4V1_PIN 25 -#define INVERTER_DISCONNECT_CONTACTOR_PIN 32 + // LED + virtual gpio_num_t LED_PIN() { return GPIO_NUM_4; } + virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; } -// SD card -//#define SD_MISO_PIN 2 -//#define SD_MOSI_PIN 15 -//#define SD_SCLK_PIN 14 -//#define SD_CS_PIN 13 + // Equipment stop pin + virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_35; } -// LED -#define LED_PIN 4 -#define LED_MAX_BRIGHTNESS 40 + // Battery wake up pins + virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } + virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } -// Equipment stop pin -#define EQUIPMENT_STOP_PIN 35 - -// BMW_I3_BATTERY wake up pin -#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 -#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) ----- */ -#ifndef HW_CONFIGURED -#define HW_CONFIGURED -#else -#error Multiple HW defined! Please select a single HW -#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 + std::vector available_interfaces() { + return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative}; + } +}; #endif diff --git a/Software/src/devboard/hal/hw_devkit.h b/Software/src/devboard/hal/hw_devkit.h index 107780ae..3ae7e6fb 100644 --- a/Software/src/devboard/hal/hw_devkit.h +++ b/Software/src/devboard/hal/hw_devkit.h @@ -11,83 +11,68 @@ The pin layout below supports the following: - 1x CANFD (via MCP2518FD (SPI)) */ -// Board boot-up time -#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up +class DevKitHal : public Esp32Hal { + public: + const char* name() { return "ESP32 DevKit V1"; } -// Core assignment -#define CORE_FUNCTION_CORE 1 -#define MODBUS_CORE 0 -#define WIFI_CORE 0 + virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_1; } + virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_3; } -// RS485 -#define RS485_TX_PIN GPIO_NUM_1 -#define RS485_RX_PIN GPIO_NUM_3 + virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_27; } + virtual gpio_num_t CAN_RX_PIN() { return GPIO_NUM_26; } -// CAN settings -#define CAN_1_TYPE ESP32CAN -//#define CAN_2_TYPE MCP2515 -//#define CAN_3_TYPE MCP2518FD + // CAN_ADDON + // SCK input of MCP2515 + virtual gpio_num_t MCP2515_SCK() { return GPIO_NUM_22; } + // SDI input of MCP2515 + virtual gpio_num_t MCP2515_MOSI() { return GPIO_NUM_21; } + // SDO output of MCP2515 + virtual gpio_num_t MCP2515_MISO() { return GPIO_NUM_19; } + // 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_23; } -// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB -#define CAN_TX_PIN GPIO_NUM_27 -#define CAN_RX_PIN GPIO_NUM_26 + // CANFD_ADDON defines for MCP2517 + virtual gpio_num_t MCP2517_SCK() { return GPIO_NUM_33; } + virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_32; } + virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_35; } + virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_25; } + virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_34; } -// CAN_ADDON defines -#define MCP2515_SCK GPIO_NUM_22 // SCK input of MCP2515 -#define MCP2515_MOSI GPIO_NUM_21 // SDI input of MCP2515 -#define MCP2515_MISO GPIO_NUM_19 // SDO output of MCP2515 -#define MCP2515_CS GPIO_NUM_18 // CS input of MCP2515 -#define MCP2515_INT GPIO_NUM_23 // INT output of MCP2515 + // Contactor handling + virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_5; } + virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_16; } + virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_17; } + virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_32; } -// CANFD_ADDON defines -#define MCP2517_SCK GPIO_NUM_33 // SCK input of MCP2517 -#define MCP2517_SDI GPIO_NUM_32 // SDI input of MCP2517 -#define MCP2517_SDO GPIO_NUM_35 // SDO output of MCP2517 | Pin 35 is input only, without pullup/down resistors -#define MCP2517_CS GPIO_NUM_25 // CS input of MCP2517 -#define MCP2517_INT GPIO_NUM_34 // INT output of MCP2517 | Pin 34 is input only, without pullup/down resistors + // Automatic precharging + virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_4; } + virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_5; } -// Contactor handling -#define POSITIVE_CONTACTOR_PIN GPIO_NUM_5 -#define NEGATIVE_CONTACTOR_PIN GPIO_NUM_16 -#define PRECHARGE_PIN GPIO_NUM_17 -#define SECOND_BATTERY_CONTACTORS_PIN GPIO_NUM_32 -// SMA CAN contactor pins -#define INVERTER_CONTACTOR_ENABLE_PIN GPIO_NUM_14 + // SMA CAN contactor pins + virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_14; } -// LED -#define LED_PIN GPIO_NUM_4 -#define LED_MAX_BRIGHTNESS 40 -#define INVERTER_CONTACTOR_ENABLE_LED_PIN GPIO_NUM_2 + virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_LED_PIN() { return GPIO_NUM_2; } -// Equipment stop pin -#define EQUIPMENT_STOP_PIN GPIO_NUM_12 + // LED + virtual gpio_num_t LED_PIN() { return GPIO_NUM_4; } + virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; } -// Automatic precharging -#define HIA4V1_PIN GPIO_NUM_17 -#define INVERTER_DISCONNECT_CONTACTOR_PIN GPIO_NUM_5 + // Equipment stop pin + virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_12; } -// BMW_I3_BATTERY wake up pin -#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 -#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 + // Battery wake up pins + virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } + virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } -/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ -#ifndef HW_CONFIGURED -#define HW_CONFIGURED -#else -#error Multiple HW defined! Please select a single HW -#endif // HW_CONFIGURED - -#ifdef CHADEMO_BATTERY -#error CHADEMO pins are not defined for this hardware. -#endif // CHADEMO_BATTERY - -#ifdef BMW_I3_BATTERY -#if defined(WUP_PIN1) && defined(CANFD_ADDON) -#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and a CANFD addon board using these pins. Choose between BMW_I3_BATTERY and CANFD_ADDON -#endif // defined(WUP_PIN1) && defined(CANFD_ADDON) -#if defined(WUP_PIN2) && defined(CANFD_ADDON) -#error GPIO PIN 32 cannot be used for both BMWi3 Wakeup and a CANFD addon board using these pins. Choose between BMW_I3_BATTERY and CANFD_ADDON -#endif // defined(WUP_PIN2) && defined(CANFD_ADDON) -#endif // BMW_I3_BATTERY + std::vector available_interfaces() { + return { + comm_interface::Modbus, + comm_interface::RS485, + comm_interface::CanNative, + }; + } +}; #endif // __HW_DEVKIT_H__ diff --git a/Software/src/devboard/hal/hw_lilygo.h b/Software/src/devboard/hal/hw_lilygo.h index 6b2463e1..14b15f5c 100644 --- a/Software/src/devboard/hal/hw_lilygo.h +++ b/Software/src/devboard/hal/hw_lilygo.h @@ -1,82 +1,87 @@ #ifndef __HW_LILYGO_H__ #define __HW_LILYGO_H__ -// Board boot-up time -#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up +#include "hal.h" -// Core assignment -#define CORE_FUNCTION_CORE 1 -#define MODBUS_CORE 0 -#define WIFI_CORE 0 +class LilyGoHal : public Esp32Hal { + public: + const char* name() { return "LilyGo T-CAN485"; } -// RS485 -#define PIN_5V_EN 16 -#define RS485_EN_PIN 17 // 17 /RE -#define RS485_TX_PIN 22 // 21 -#define RS485_RX_PIN 21 // 22 -#define RS485_SE_PIN 19 // 22 /SHDN + virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_16; } + virtual gpio_num_t RS485_EN_PIN() { return GPIO_NUM_17; } + virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_22; } + virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_21; } + virtual gpio_num_t RS485_SE_PIN() { return GPIO_NUM_19; } -// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings -#define CAN_1_TYPE ESP32CAN + virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_27; } + 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 -#define CAN_TX_PIN GPIO_NUM_27 -#define CAN_RX_PIN GPIO_NUM_26 -#define CAN_SE_PIN 23 + // CAN_ADDON + // SCK input of MCP2515 + virtual gpio_num_t MCP2515_SCK() { return GPIO_NUM_12; } + // 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; } + virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_5; } + virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_34; } + virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_18; } + virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_35; } -// CAN_ADDON defines -#define MCP2515_SCK 12 // SCK input of MCP2515 -#define MCP2515_MOSI 5 // SDI input of MCP2515 -#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors -#define MCP2515_CS 18 // CS input of MCP2515 -#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors + // CHAdeMO support pin dependencies + virtual gpio_num_t CHADEMO_PIN_2() { return GPIO_NUM_12; } + virtual gpio_num_t CHADEMO_PIN_10() { return GPIO_NUM_5; } + virtual gpio_num_t CHADEMO_PIN_7() { return GPIO_NUM_34; } + virtual gpio_num_t CHADEMO_PIN_4() { return GPIO_NUM_35; } + virtual gpio_num_t CHADEMO_LOCK() { return GPIO_NUM_18; } -// 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 + // Contactor handling + virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_32; } + virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_33; } + virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; } + virtual gpio_num_t BMS_POWER() { return GPIO_NUM_18; } + virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_15; } -// CHAdeMO support pin dependencies -#define CHADEMO_PIN_2 12 -#define CHADEMO_PIN_10 5 -#define CHADEMO_PIN_7 34 -#define CHADEMO_PIN_4 35 -#define CHADEMO_LOCK 18 + // Automatic precharging + virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_25; } + virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_32; } -// Contactor handling -#define POSITIVE_CONTACTOR_PIN 32 -#define NEGATIVE_CONTACTOR_PIN 33 -#define PRECHARGE_PIN 25 -#define BMS_POWER 18 // Note, this pin collides with CAN add-ons and Chademo -#define SECOND_BATTERY_CONTACTORS_PIN 15 //Note, this pin collides with SD card pins + // SMA CAN contactor pins + virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_5; } -// Automatic precharging -#define HIA4V1_PIN 25 -#define INVERTER_DISCONNECT_CONTACTOR_PIN 32 + // virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_LED_PIN() { return GPIO_NUM_NC; } -// SMA CAN contactor pins -#define INVERTER_CONTACTOR_ENABLE_PIN 5 + // SD card + virtual gpio_num_t SD_MISO_PIN() { return GPIO_NUM_2; } + virtual gpio_num_t SD_MOSI_PIN() { return GPIO_NUM_15; } + virtual gpio_num_t SD_SCLK_PIN() { return GPIO_NUM_14; } + virtual gpio_num_t SD_CS_PIN() { return GPIO_NUM_13; } -// SD card -#define SD_MISO_PIN 2 -#define SD_MOSI_PIN 15 -#define SD_SCLK_PIN 14 -#define SD_CS_PIN 13 + // LED + virtual gpio_num_t LED_PIN() { return GPIO_NUM_4; } -// LED -#define LED_PIN 4 -#define LED_MAX_BRIGHTNESS 40 + // Equipment stop pin + virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_35; } -// Equipment stop pin -#define EQUIPMENT_STOP_PIN 35 + // Battery wake up pins + virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } + virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } -// BMW_I3_BATTERY wake up pin -#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 -#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 + std::vector available_interfaces() { + return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanAddonMcp2515, + comm_interface::CanFdAddonMcp2518}; + } +}; + +#define HalClass LilyGoHal /* ----- Error checks below, don't change (can't be moved to separate file) ----- */ #ifndef HW_CONFIGURED @@ -85,42 +90,4 @@ #error Multiple HW defined! Please select a single HW #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 diff --git a/Software/src/devboard/hal/hw_stark.h b/Software/src/devboard/hal/hw_stark.h index 4c91dbe0..6ed745d9 100644 --- a/Software/src/devboard/hal/hw_stark.h +++ b/Software/src/devboard/hal/hw_stark.h @@ -1,6 +1,8 @@ #ifndef __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. For more information on this board visit the project discord or contact johan@redispose.se @@ -15,75 +17,61 @@ GPIOs on extra header * GPIO 15 (JTAG TDO) */ -// Board boot-up time -#define BOOTUP_TIME 5000 // Time in ms it takes before system is considered fully started up +class StarkHal : public Esp32Hal { + public: + const char* name() { return "Stark CMR Module"; } -// Core assignment -#define CORE_FUNCTION_CORE 1 -#define MODBUS_CORE 0 -#define WIFI_CORE 0 + // Not needed, GPIO 16 has hardware pullup for PSRAM compatibility + virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_NC; } -// RS485 -// #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_TX_PIN 22 -#define RS485_RX_PIN 21 -// #define RS485_SE_PIN 19 // Not needed, GPIO 19 is available as extra GPIO via pin header + // Not needed, GPIO 17 is used as SCK input of MCP2517 + virtual gpio_num_t RS485_EN_PIN() { return GPIO_NUM_NC; } + virtual gpio_num_t RS485_TX_PIN() { return GPIO_NUM_22; } + virtual gpio_num_t RS485_RX_PIN() { return GPIO_NUM_21; } + // Not needed, GPIO 19 is available as extra GPIO via pin header + virtual gpio_num_t RS485_SE_PIN() { return GPIO_NUM_NC; } -// CAN settings -#define CAN_1_TYPE ESP32CAN + virtual gpio_num_t CAN_TX_PIN() { return GPIO_NUM_27; } + virtual gpio_num_t CAN_RX_PIN() { return GPIO_NUM_26; } -// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB -#define CAN_TX_PIN GPIO_NUM_27 -#define CAN_RX_PIN GPIO_NUM_26 -// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK) + // (No function, GPIO 23 used instead as MCP_SCK) + virtual gpio_num_t CAN_SE_PIN() { return GPIO_NUM_NC; } -// CANFD_ADDON defines -#define MCP2517_SCK 17 // 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 + // CANFD_ADDON defines for MCP2517 + virtual gpio_num_t MCP2517_SCK() { return GPIO_NUM_17; } + virtual gpio_num_t MCP2517_SDI() { return GPIO_NUM_5; } + virtual gpio_num_t MCP2517_SDO() { return GPIO_NUM_34; } + virtual gpio_num_t MCP2517_CS() { return GPIO_NUM_18; } + virtual gpio_num_t MCP2517_INT() { return GPIO_NUM_35; } -// Contactor handling -#define POSITIVE_CONTACTOR_PIN 32 -#define NEGATIVE_CONTACTOR_PIN 33 -#define PRECHARGE_PIN 25 -#define BMS_POWER 23 -#define SECOND_BATTERY_CONTACTORS_PIN 19 //Available as extra GPIO via pin header + // Contactor handling + virtual gpio_num_t POSITIVE_CONTACTOR_PIN() { return GPIO_NUM_32; } + virtual gpio_num_t NEGATIVE_CONTACTOR_PIN() { return GPIO_NUM_33; } + virtual gpio_num_t PRECHARGE_PIN() { return GPIO_NUM_25; } + virtual gpio_num_t BMS_POWER() { return GPIO_NUM_23; } + virtual gpio_num_t SECOND_BATTERY_CONTACTORS_PIN() { return GPIO_NUM_19; } -// Automatic precharging -#define HIA4V1_PIN 19 //Available as extra GPIO via pin header -#define INVERTER_DISCONNECT_CONTACTOR_PIN 25 + // Automatic precharging + virtual gpio_num_t HIA4V1_PIN() { return GPIO_NUM_19; } + virtual gpio_num_t INVERTER_DISCONNECT_CONTACTOR_PIN() { return GPIO_NUM_25; } -// SMA CAN contactor pins -#define INVERTER_CONTACTOR_ENABLE_PIN 2 + // SMA CAN contactor pins + virtual gpio_num_t INVERTER_CONTACTOR_ENABLE_PIN() { return GPIO_NUM_2; } -// LED -#define LED_PIN 4 -#define LED_MAX_BRIGHTNESS 40 + // LED + virtual gpio_num_t LED_PIN() { return GPIO_NUM_4; } + virtual uint8_t LED_MAX_BRIGHTNESS() { return 40; } -// Equipment stop pin -#define EQUIPMENT_STOP_PIN 2 + // Equipment stop pin + virtual gpio_num_t EQUIPMENT_STOP_PIN() { return GPIO_NUM_2; } -// BMW_I3_BATTERY wake up pin -#define WUP_PIN1 GPIO_NUM_25 // Wake up pin for battery 1 -#define WUP_PIN2 GPIO_NUM_32 // Wake up pin for battery 2 + // Battery wake up pins + virtual gpio_num_t WUP_PIN1() { return GPIO_NUM_25; } + virtual gpio_num_t WUP_PIN2() { return GPIO_NUM_32; } -/* ----- Error checks below, don't change (can't be moved to separate file) ----- */ -#ifndef HW_CONFIGURED -#define HW_CONFIGURED -#else -#error Multiple HW defined! Please select a single HW -#endif // HW_CONFIGURED - -#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 // BMW_I3_BATTERY + std::vector available_interfaces() { + return {comm_interface::Modbus, comm_interface::RS485, comm_interface::CanNative, comm_interface::CanFdNative}; + } +}; #endif // __HW_STARK_H__ diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index bdda48ae..f1043941 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "../../../USER_SECRETS.h" #include "../../../USER_SETTINGS.h" @@ -13,6 +14,41 @@ #include "../utils/timer.h" #include "mqtt_client.h" +#ifdef MQTT +const bool mqtt_enabled_default = true; +#else +const bool mqtt_enabled_default = false; +#endif + +bool mqtt_enabled = mqtt_enabled_default; + +#ifdef HA_AUTODISCOVERY +const bool ha_autodiscovery_enabled_default = true; +#else +const bool ha_autodiscovery_enabled_default = false; +#endif + +bool ha_autodiscovery_enabled = ha_autodiscovery_enabled_default; + +#ifdef COMMON_IMAGE +const int mqtt_port_default = 0; +const char* mqtt_server_default = ""; +#else +const int mqtt_port_default = MQTT_PORT; +const char* mqtt_server_default = MQTT_SERVER; +#endif + +int mqtt_port = mqtt_port_default; +std::string mqtt_server = mqtt_server_default; + +#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME +const bool mqtt_manual_topic_object_name_default = true; +#else +const bool mqtt_manual_topic_object_name_default = false; +#endif + +bool mqtt_manual_topic_object_name = mqtt_manual_topic_object_name_default; + esp_mqtt_client_config_t mqtt_cfg; esp_mqtt_client_handle_t client; char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; @@ -59,7 +95,6 @@ static void publish_values(void) { #endif } -#ifdef HA_AUTODISCOVERY static bool ha_common_info_published = false; static bool ha_cell_voltages_published = false; static bool ha_events_published = false; @@ -176,7 +211,6 @@ void set_battery_voltage_attributes(JsonDocument& doc, int i, int cellNumber, co doc["unit_of_measurement"] = "V"; doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; } -#endif // HA_AUTODISCOVERY static String generateButtonTopic(const char* subtype) { return topic_name + "/command/" + String(subtype); @@ -230,8 +264,10 @@ static std::vector order_events; static bool publish_common_info(void) { static JsonDocument doc; static String state_topic = topic_name + "/info"; -#ifdef HA_AUTODISCOVERY - if (ha_common_info_published == false) { + + // if(ha_autodiscovery_enabled) { + + if (ha_autodiscovery_enabled && !ha_common_info_published) { for (auto& config : sensorConfigs) { if (!config.condition(battery)) { continue; @@ -260,18 +296,17 @@ static bool publish_common_info(void) { } } else { -#endif // HA_AUTODISCOVERY doc["bms_status"] = getBMSStatus(datalayer.battery.status.bms_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) - 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()); } if (battery2) { //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()); } } @@ -283,9 +318,7 @@ static bool publish_common_info(void) { return false; } doc.clear(); -#ifdef HA_AUTODISCOVERY } -#endif // HA_AUTODISCOVERY return true; } @@ -294,51 +327,51 @@ static bool publish_cell_voltages(void) { static String state_topic = topic_name + "/spec_data"; static String state_topic_2 = topic_name + "/spec_data_2"; -#ifdef HA_AUTODISCOVERY - bool failed_to_publish = false; - if (ha_cell_voltages_published == false) { + if (ha_autodiscovery_enabled) { + bool failed_to_publish = false; + if (ha_cell_voltages_published == false) { - // If the cell voltage number isn't initialized... - if (datalayer.battery.info.number_of_cells != 0u) { - - for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { - int cellNumber = i + 1; - set_battery_voltage_attributes(doc, i, cellNumber, state_topic, object_id_prefix, ""); - set_common_discovery_attributes(doc); - - serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); - if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true) == false) { - failed_to_publish = true; - return false; - } - } - doc.clear(); // clear after sending autoconfig - } - - if (battery2) { - // TODO: Combine this identical block with the previous one. // If the cell voltage number isn't initialized... - if (datalayer.battery2.info.number_of_cells != 0u) { + if (datalayer.battery.info.number_of_cells != 0u) { for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { int cellNumber = i + 1; - set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_", " 2"); + set_battery_voltage_attributes(doc, i, cellNumber, state_topic, object_id_prefix, ""); set_common_discovery_attributes(doc); serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); - if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) { + if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true) == false) { failed_to_publish = true; return false; } } doc.clear(); // clear after sending autoconfig } + + if (battery2) { + // TODO: Combine this identical block with the previous one. + // If the cell voltage number isn't initialized... + if (datalayer.battery2.info.number_of_cells != 0u) { + + for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) { + int cellNumber = i + 1; + set_battery_voltage_attributes(doc, i, cellNumber, state_topic_2, object_id_prefix + "2_", " 2"); + set_common_discovery_attributes(doc); + + serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); + if (mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true) == false) { + failed_to_publish = true; + return false; + } + } + doc.clear(); // clear after sending autoconfig + } + } + } + if (failed_to_publish == false) { + ha_cell_voltages_published = true; } } - if (failed_to_publish == false) { - ha_cell_voltages_published = true; - } -#endif // HA_AUTODISCOVERY // If cell voltages have been populated... if (datalayer.battery.info.number_of_cells != 0u && @@ -434,8 +467,7 @@ static bool publish_cell_balancing(void) { bool publish_events() { static JsonDocument doc; static String state_topic = topic_name + "/events"; -#ifdef HA_AUTODISCOVERY - if (ha_events_published == false) { + if (ha_autodiscovery_enabled && !ha_events_published) { doc["name"] = "Event"; doc["state_topic"] = state_topic; @@ -456,8 +488,6 @@ bool publish_events() { doc.clear(); } else { -#endif // HA_AUTODISCOVERY - const EVENTS_STRUCT_TYPE* event_pointer; //clear the vector @@ -481,7 +511,7 @@ bool publish_events() { doc["severity"] = String(get_event_level_string(event_handle)); doc["count"] = String(event_pointer->occurences); doc["data"] = String(event_pointer->data); - doc["message"] = String(get_event_message_string(event_handle)); + doc["message"] = get_event_message_string(event_handle); doc["millis"] = String(event_pointer->timestamp); serializeJson(doc, mqtt_msg); @@ -497,36 +527,34 @@ bool publish_events() { //clear the vector order_events.clear(); } -#ifdef HA_AUTODISCOVERY } -#endif // HA_AUTODISCOVERY return true; } static bool publish_buttons_discovery(void) { -#ifdef HA_AUTODISCOVERY - if (ha_buttons_published == false) { + if (ha_autodiscovery_enabled) { + if (ha_buttons_published == false) { #ifdef DEBUG_LOG - logging.println("Publishing buttons discovery"); + logging.println("Publishing buttons discovery"); #endif // DEBUG_LOG - static JsonDocument doc; - for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { - SensorConfig& config = buttonConfigs[i]; - doc["name"] = config.name; - doc["unique_id"] = object_id_prefix + config.object_id; - doc["command_topic"] = generateButtonTopic(config.object_id); - set_common_discovery_attributes(doc); - serializeJson(doc, mqtt_msg); - if (mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { - ha_buttons_published = true; - } else { - return false; + static JsonDocument doc; + for (int i = 0; i < sizeof(buttonConfigs) / sizeof(buttonConfigs[0]); i++) { + SensorConfig& config = buttonConfigs[i]; + doc["name"] = config.name; + doc["unique_id"] = object_id_prefix + config.object_id; + doc["command_topic"] = generateButtonTopic(config.object_id); + set_common_discovery_attributes(doc); + serializeJson(doc, mqtt_msg); + if (mqtt_publish(generateButtonAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true)) { + ha_buttons_published = true; + } else { + return false; + } + doc.clear(); } - doc.clear(); } } -#endif // HA_AUTODISCOVERY return true; } @@ -608,33 +636,59 @@ static void mqtt_event_handler(void* handler_args, esp_event_base_t base, int32_ } } -void init_mqtt(void) { -#ifdef HA_AUTODISCOVERY - create_battery_sensor_configs(); - create_global_sensor_configs(); -#endif // HA_AUTODISCOVERY -#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME - // Use custom topic name, object ID prefix, and device name from user settings - topic_name = mqtt_topic_name; - object_id_prefix = mqtt_object_id_prefix; - device_name = mqtt_device_name; - device_id = ha_device_id; +bool init_mqtt(void) { + if (ha_autodiscovery_enabled) { + create_battery_sensor_configs(); + create_global_sensor_configs(); + } + + if (mqtt_manual_topic_object_name) { +#ifdef COMMON_IMAGE + BatteryEmulatorSettingsStore settings; + topic_name = settings.getString("MQTTTOPIC", mqtt_topic_name); + object_id_prefix = settings.getString("MQTTOBJIDPREFIX", mqtt_object_id_prefix); + device_name = settings.getString("MQTTDEVICENAME", mqtt_device_name); + device_id = settings.getString("HADEVICEID", ha_device_id); + + if (topic_name.length() == 0) { + topic_name = mqtt_topic_name; + } + + if (object_id_prefix.length() == 0) { + object_id_prefix = mqtt_object_id_prefix; + } + + if (device_name.length() == 0) { + device_name = mqtt_device_name; + } + + if (device_id.length() == 0) { + device_id = ha_device_id; + } + #else - // Use default naming based on WiFi hostname for topic, object ID prefix, and device name - topic_name = "battery-emulator_" + String(WiFi.getHostname()); - object_id_prefix = String(WiFi.getHostname()) + String("_"); - device_name = "BatteryEmulator_" + String(WiFi.getHostname()); - device_id = "battery-emulator"; + // Use custom topic name, object ID prefix, and device name from user settings + topic_name = mqtt_topic_name; + object_id_prefix = mqtt_object_id_prefix; + device_name = mqtt_device_name; + device_id = ha_device_id; #endif + } else { + // Use default naming based on WiFi hostname for topic, object ID prefix, and device name + topic_name = "battery-emulator_" + String(WiFi.getHostname()); + object_id_prefix = String(WiFi.getHostname()) + String("_"); + device_name = "BatteryEmulator_" + String(WiFi.getHostname()); + device_id = "battery-emulator"; + } String clientId = String("BatteryEmulatorClient-") + WiFi.getHostname(); mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; - mqtt_cfg.broker.address.hostname = MQTT_SERVER; - mqtt_cfg.broker.address.port = MQTT_PORT; + mqtt_cfg.broker.address.hostname = mqtt_server.c_str(); + mqtt_cfg.broker.address.port = mqtt_port; mqtt_cfg.credentials.client_id = clientId.c_str(); - mqtt_cfg.credentials.username = MQTT_USER; - mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD; + mqtt_cfg.credentials.username = mqtt_user.c_str(); + mqtt_cfg.credentials.authentication.password = mqtt_password.c_str(); lwt_topic = topic_name + "/status"; mqtt_cfg.session.last_will.topic = lwt_topic.c_str(); mqtt_cfg.session.last_will.qos = 1; @@ -643,7 +697,16 @@ void init_mqtt(void) { mqtt_cfg.session.last_will.msg_len = strlen(mqtt_cfg.session.last_will.msg); mqtt_cfg.network.timeout_ms = MQTT_TIMEOUT; client = esp_mqtt_client_init(&mqtt_cfg); - esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client); + + if (client == nullptr) { + return false; + } + + if (esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client) != ESP_OK) { + return false; + } + + return true; } void mqtt_loop(void) { diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index 1ee57f30..f28684c8 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -42,8 +42,10 @@ extern const char* version_number; // The current software version, used for mqtt -extern const char* mqtt_user; -extern const char* mqtt_password; +extern std::string mqtt_server; +extern std::string mqtt_user; +extern std::string mqtt_password; +extern int mqtt_port; extern const char* mqtt_topic_name; extern const char* mqtt_object_id_prefix; extern const char* mqtt_device_name; @@ -51,8 +53,11 @@ extern const char* ha_device_id; extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; -void init_mqtt(void); +bool init_mqtt(void); void mqtt_loop(void); bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain); +extern bool mqtt_enabled; +extern bool ha_autodiscovery_enabled; + #endif diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 8c26de32..c7eb18fb 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -1,4 +1,6 @@ +#include "safety.h" #include "../../datalayer/datalayer.h" +#include "../../include.h" #include "../utils/events.h" static uint16_t cell_deviation_mV = 0; @@ -328,6 +330,7 @@ void update_machineryprotection() { //battery pause status begin void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bool store_settings) { + DEBUG_PRINTF("Battery pause begin %d %d %d %d\n", pause_battery, pause_CAN, equipment_stop, store_settings); // First handle equipment stop / resume if (equipment_stop && !datalayer.system.settings.equipment_stop_active) { @@ -394,17 +397,13 @@ void update_pause_state() { allowed_to_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL); if (previous_allowed_to_send_CAN && !allowed_to_send_CAN) { -#ifdef DEBUG_LOG - logging.printf("Safety: Pausing CAN sending\n"); -#endif + DEBUG_PRINTF("Safety: Pausing CAN sending\n"); //completely force stop the CAN communication - ESP32Can.CANStop(); //Note: This only stops the NATIVE_CAN port, it will no longer ACK messages + stop_can(); } else if (!previous_allowed_to_send_CAN && allowed_to_send_CAN) { //resume CAN communication -#ifdef DEBUG_LOG - logging.printf("Safety: Resuming CAN sending\n"); -#endif - ESP32Can.CANInit(); //Note: This only resumes the NATIVE_CAN port + DEBUG_PRINTF("Safety: Resuming CAN sending\n"); + restart_can(); } } diff --git a/Software/src/devboard/safety/safety.h b/Software/src/devboard/safety/safety.h index 4c70cdd0..c402176c 100644 --- a/Software/src/devboard/safety/safety.h +++ b/Software/src/devboard/safety/safety.h @@ -1,8 +1,6 @@ #ifndef SAFETY_H #define SAFETY_H -#include #include -#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" #define MAX_CAN_FAILURES 50 diff --git a/Software/src/devboard/sdcard/sdcard.cpp b/Software/src/devboard/sdcard/sdcard.cpp index db816643..47f5f142 100644 --- a/Software/src/devboard/sdcard/sdcard.cpp +++ b/Software/src/devboard/sdcard/sdcard.cpp @@ -1,9 +1,7 @@ #include "sdcard.h" +#include "../../include.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 log_file; RingbufHandle_t can_bufferHandle; @@ -185,17 +183,24 @@ void init_logging_buffers() { #endif // defined(LOG_TO_SD) } -void init_sdcard() { +bool init_sdcard() { + auto miso_pin = esp32hal->SD_MISO_PIN(); + auto mosi_pin = esp32hal->SD_MOSI_PIN(); + auto sclk_pin = esp32hal->SD_SCLK_PIN(); - pinMode(SD_MISO_PIN, INPUT_PULLUP); + if (!esp32hal->alloc_pins("SD Card", miso_pin, mosi_pin, sclk_pin)) { + return false; + } - SD_MMC.setPins(SD_SCLK_PIN, SD_MOSI_PIN, SD_MISO_PIN); + pinMode(miso_pin, INPUT_PULLUP); + + SD_MMC.setPins(sclk_pin, mosi_pin, miso_pin); if (!SD_MMC.begin("/root", true, true, SDMMC_FREQ_HIGHSPEED)) { set_event_latched(EVENT_SD_INIT_FAILED, 0); #ifdef DEBUG_LOG logging.println("SD Card initialization failed!"); #endif // DEBUG_LOG - return; + return false; } clear_event(EVENT_SD_INIT_FAILED); @@ -208,6 +213,8 @@ void init_sdcard() { #ifdef DEBUG_LOG log_sdcard_details(); #endif // DEBUG_LOG + + return true; } void log_sdcard_details() { @@ -245,4 +252,3 @@ void log_sdcard_details() { logging.println(" MB"); } } -#endif // defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && defined(SD_MISO_PIN) diff --git a/Software/src/devboard/sdcard/sdcard.h b/Software/src/devboard/sdcard/sdcard.h index 9f592cd3..745ab1bd 100644 --- a/Software/src/devboard/sdcard/sdcard.h +++ b/Software/src/devboard/sdcard/sdcard.h @@ -6,14 +6,12 @@ #include "../hal/hal.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 LOG_FILE "/log.txt" void init_logging_buffers(); -void init_sdcard(); +bool init_sdcard(); void log_sdcard_details(); 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 write_log_to_sdcard(); -#endif // defined(SD_CS_PIN) && defined(SD_SCLK_PIN) && defined(SD_MOSI_PIN) && defined(SD_MISO_PIN) #endif // SDCARD_H diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index c92b89d1..d7ddfbc0 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -1,5 +1,6 @@ #include "events.h" #include "../../datalayer/datalayer.h" +#include "../../include.h" #include "../../../USER_SETTINGS.h" @@ -124,6 +125,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_FAILED].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) { @@ -161,7 +165,7 @@ void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) { events.entries[event].MQTTpublished = true; } -const char* get_event_message_string(EVENTS_ENUM_TYPE event) { +String get_event_message_string(EVENTS_ENUM_TYPE event) { switch (event) { case EVENT_CANMCP2517FD_INIT_FAILURE: return "CAN-FD initialization failed. Check hardware or bitrate settings"; @@ -364,6 +368,12 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { 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 " "powered on"; + case EVENT_GPIO_CONFLICT: + return "GPIO Pin Conflict: The pin used by '" + esp32hal->failed_allocator() + "' is already allocated by '" + + esp32hal->conflicting_allocator() + "'. Please check your configuration and assign different pins."; + case EVENT_GPIO_NOT_DEFINED: + return "Missing GPIO Assignment: The component '" + esp32hal->failed_allocator() + + "' requires a GPIO pin that isn't configured. Please define a valid pin number in your settings."; default: return ""; } @@ -402,10 +412,8 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) { (events.entries[event].state != EVENT_STATE_ACTIVE_LATCHED)) { events.entries[event].occurences++; events.entries[event].MQTTpublished = false; -#ifdef DEBUG_LOG - logging.print("Event: "); - logging.println(get_event_message_string(event)); -#endif + + DEBUG_PRINTF("Event: %s\n", get_event_message_string(event).c_str()); } // We should set the event, update event info diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 86d52ae4..2433b6f4 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -1,8 +1,9 @@ #ifndef __EVENTS_H__ #define __EVENTS_H__ -#ifndef UNIT_TEST -#include "../../include.h" -#endif + +#include +#include +#include #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -107,6 +108,8 @@ XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_SUCCESS) \ XX(EVENT_PERIODIC_BMS_RESET_AT_INIT_FAILED) \ XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \ + XX(EVENT_GPIO_NOT_DEFINED) \ + XX(EVENT_GPIO_CONFLICT) \ XX(EVENT_NOF_EVENTS) typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE; @@ -144,7 +147,7 @@ struct EventData { }; const char* get_event_enum_string(EVENTS_ENUM_TYPE event); -const char* get_event_message_string(EVENTS_ENUM_TYPE event); +String get_event_message_string(EVENTS_ENUM_TYPE event); const char* get_event_level_string(EVENTS_ENUM_TYPE event); EVENTS_LEVEL_TYPE get_event_level(void); diff --git a/Software/src/devboard/utils/led_handler.cpp b/Software/src/devboard/utils/led_handler.cpp index e8e53de3..a4588ff3 100644 --- a/Software/src/devboard/utils/led_handler.cpp +++ b/Software/src/devboard/utils/led_handler.cpp @@ -16,16 +16,23 @@ static const float heartbeat_peak1 = 0.80; static const float heartbeat_peak2 = 0.55; static const float heartbeat_deviation = 0.05; -static LED led(datalayer.battery.status.led_mode); +static LED* led; -void led_init(void) { - led.init(); +bool led_init(void) { + 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) { - led.exe(); + led->exe(); } led_color led_get_color() { - return led.color; + return led->color; } void LED::exe(void) { @@ -61,7 +68,7 @@ void LED::exe(void) { break; case EVENT_LEVEL_ERROR: 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; default: 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 = (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) { @@ -138,7 +145,7 @@ uint8_t LED::up_down(float middle_point_f) { if (ms < middle_point) { brightness = map_uint16(ms, 0, middle_point, 0, max_brightness); } 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); } diff --git a/Software/src/devboard/utils/led_handler.h b/Software/src/devboard/utils/led_handler.h index be754ec6..15a91c02 100644 --- a/Software/src/devboard/utils/led_handler.h +++ b/Software/src/devboard/utils/led_handler.h @@ -8,14 +8,14 @@ class LED { public: led_color color = led_color::GREEN; - LED() - : pixels(1, LED_PIN, NEO_GRB), - max_brightness(LED_MAX_BRIGHTNESS), - brightness(LED_MAX_BRIGHTNESS), + LED(gpio_num_t pin, uint8_t maxBrightness) + : pixels(1, pin, NEO_GRB), + max_brightness(maxBrightness), + brightness(maxBrightness), mode(led_mode_enum::CLASSIC) {} - LED(led_mode_enum mode) - : pixels(1, LED_PIN, NEO_GRB), max_brightness(LED_MAX_BRIGHTNESS), brightness(LED_MAX_BRIGHTNESS), mode(mode) {} + LED(led_mode_enum mode, gpio_num_t pin, uint8_t maxBrightness) + : pixels(1, pin, NEO_GRB), max_brightness(maxBrightness), brightness(maxBrightness), mode(mode) {} void exe(void); void init(void) { pixels.begin(); } @@ -33,7 +33,7 @@ class LED { uint8_t up_down(float middle_point_f); }; -void led_init(void); +bool led_init(void); void led_exe(void); led_color led_get_color(void); diff --git a/Software/src/devboard/utils/logging.h b/Software/src/devboard/utils/logging.h index c1fc14b5..99ef4c31 100644 --- a/Software/src/devboard/utils/logging.h +++ b/Software/src/devboard/utils/logging.h @@ -20,8 +20,10 @@ extern Logging logging; #ifdef DEBUG_LOG #define DEBUG_PRINTF(fmt, ...) logging.printf(fmt, ##__VA_ARGS__) +#define DEBUG_PRINTLN(str) logging.println(str) #else #define DEBUG_PRINTF(fmt, ...) ((void)0) +#define DEBUG_PRINTLN(fmt, ...) ((void)0) #endif #endif // __LOGGING_H__ diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h index 98a73726..d1e15340 100644 --- a/Software/src/devboard/utils/types.h +++ b/Software/src/devboard/utils/types.h @@ -1,11 +1,26 @@ #ifndef _TYPES_H_ #define _TYPES_H_ +#include #include +using milliseconds = std::chrono::milliseconds; +using duration = std::chrono::duration>; + 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 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_mode_enum { CLASSIC, FLOW, HEARTBEAT }; enum PrechargeState { @@ -42,7 +57,20 @@ enum PrechargeState { #define CAN_STILL_ALIVE 60 // 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); /* CAN Frame structure */ diff --git a/Software/src/devboard/webserver/events_html.cpp b/Software/src/devboard/webserver/events_html.cpp index f3dab890..3171aa27 100644 --- a/Software/src/devboard/webserver/events_html.cpp +++ b/Software/src/devboard/webserver/events_html.cpp @@ -1,6 +1,7 @@ #include "events_html.h" #include #include "../../datalayer/datalayer.h" +#include "../../devboard/utils/logging.h" const char EVENTS_HTML_START[] = R"=====(
Event Type
Severity
Last Event
Count
Data
Message
@@ -59,7 +60,7 @@ String events_processor(const String& var) { content.concat("
" + String(current_timestamp - event_pointer->timestamp) + "
"); content.concat("
" + String(event_pointer->occurences) + "
"); content.concat("
" + String(event_pointer->data) + "
"); - content.concat("
" + String(get_event_message_string(event_handle)) + "
"); + content.concat("
" + get_event_message_string(event_handle) + "
"); content.concat("
"); // End of event row } diff --git a/Software/src/devboard/webserver/index_html.cpp b/Software/src/devboard/webserver/index_html.cpp index f22ad488..f67cc092 100644 --- a/Software/src/devboard/webserver/index_html.cpp +++ b/Software/src/devboard/webserver/index_html.cpp @@ -1,10 +1,6 @@ #include "index_html.h" -#define INDEX_HTML_HEADER \ - R"rawliteral(Battery Emulator)rawliteral" -#define INDEX_HTML_FOOTER R"rawliteral()rawliteral"; - -const char index_html[] = INDEX_HTML_HEADER "%X%" INDEX_HTML_FOOTER; +const char index_html[] = INDEX_HTML_HEADER COMMON_JAVASCRIPT "%X%" INDEX_HTML_FOOTER; const char index_html_header[] = INDEX_HTML_HEADER; const char index_html_footer[] = INDEX_HTML_FOOTER; diff --git a/Software/src/devboard/webserver/index_html.h b/Software/src/devboard/webserver/index_html.h index 8a095650..cd867244 100644 --- a/Software/src/devboard/webserver/index_html.h +++ b/Software/src/devboard/webserver/index_html.h @@ -1,6 +1,26 @@ #ifndef INDEX_HTML_H #define INDEX_HTML_H +#define INDEX_HTML_HEADER \ + R"rawliteral(Battery Emulator)rawliteral" +#define INDEX_HTML_FOOTER R"rawliteral()rawliteral"; + +#define COMMON_JAVASCRIPT \ + R"rawliteral( + +)rawliteral" + extern const char index_html[]; extern const char index_html_header[]; extern const char index_html_footer[]; diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 786dac91..ed562679 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -2,9 +2,11 @@ #include #include "../../../src/communication/contactorcontrol/comm_contactorcontrol.h" #include "../../charger/CHARGERS.h" +#include "../../communication/can/comm_can.h" #include "../../communication/nvm/comm_nvm.h" #include "../../datalayer/datalayer.h" #include "../../include.h" +#include "index_html.h" extern bool settingsUpdated; @@ -26,7 +28,8 @@ std::vector enum_values() { } template -std::vector> enum_values_and_names(Func name_for_type) { +std::vector> enum_values_and_names(Func name_for_type, + const EnumType* noneValue = nullptr) { auto values = enum_values(); std::vector> pairs; @@ -40,15 +43,18 @@ std::vector> 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; }); - 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; } template -String options_for_enum(TEnum selected, Func name_for_type) { +String options_for_enum_with_none(TEnum selected, Func name_for_type, TEnum noneValue) { String options; - auto values = enum_values_and_names(name_for_type); + TEnum none = noneValue; + auto values = enum_values_and_names(name_for_type, &none); for (const auto& [name, type] : values) { options += ("
+ +
+ +

Battery capacity: %BATTERY_WH_MAX% Wh

+ +

Rescale SOC: %SOC_SCALING% +

+ +

SOC max percentage: %SOC_MAX_PERCENTAGE%

+ +

SOC min percentage: %SOC_MIN_PERCENTAGE%

+ +

Max charge speed: %MAX_CHARGE_SPEED% A

+ +

Max discharge speed: %MAX_DISCHARGE_SPEED% A

+ +

Manual charge voltage limits: + %VOLTAGE_LIMITS% +

+ +

Target charge voltage: %CHARGE_VOLTAGE% V

+ +

Target discharge voltage: %DISCHARGE_VOLTAGE% V

+ +
+ +
+

Fake battery voltage: %BATTERY_VOLTAGE% V

+
+ + + +
+ +

Manual LFP balancing: %MANUAL_BALANCING% +

+ +

Balancing max time: %BAL_MAX_TIME% Minutes

+ +

Balancing float power: %BAL_POWER% W

+ +

Max battery voltage: %BAL_MAX_PACK_VOLTAGE% V

+ +

Max cell voltage: %BAL_MAX_CELL_VOLTAGE% mV

+ +

Max cell voltage deviation: %BAL_MAX_DEV_CELL_VOLTAGE% mV

+ +
+ +
+ +

+ Charger HVDC Enabled: %CHG_HV% + +

+ +

+ Charger Aux12VDC Enabled: %CHG_AUX12V% + +

+ +

Charger Voltage Setpoint: %CHG_VOLTAGE_SETPOINT% V

+ +

Charger Current Setpoint: %CHG_CURRENT_SETPOINT% A

+ +
+ + + + + +)rawliteral" + +const char settings_html[] = + INDEX_HTML_HEADER COMMON_JAVASCRIPT SETTINGS_STYLE SETTINGS_HTML_BODY SETTINGS_HTML_SCRIPTS INDEX_HTML_FOOTER; diff --git a/Software/src/devboard/webserver/settings_html.h b/Software/src/devboard/webserver/settings_html.h index b723130e..17043a37 100644 --- a/Software/src/devboard/webserver/settings_html.h +++ b/Software/src/devboard/webserver/settings_html.h @@ -8,6 +8,7 @@ extern std::string ssid; extern std::string password; #include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password +#include "../../communication/nvm/comm_nvm.h" /** * @brief Replaces placeholder with content section in web page @@ -16,7 +17,7 @@ extern std::string password; * * @return String */ -String settings_processor(const String& var); +String settings_processor(const String& var, BatteryEmulatorSettingsStore& settings); /** * @brief Maps the value to a string of characters * @@ -26,4 +27,6 @@ String settings_processor(const String& var); */ const char* getCANInterfaceName(CAN_Interface interface); +extern const char settings_html[]; + #endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index df400060..54dd5725 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -16,8 +16,28 @@ #include "../utils/timer.h" #include "esp_task_wdt.h" +#include +extern std::string http_username; +extern std::string http_password; + void transmit_can_frame(CAN_frame* tx_frame, int interface); +#ifdef WEBSERVER +const bool webserver_enabled_default = true; +#else +const bool webserver_enabled_default = false; +#endif + +bool webserver_enabled = webserver_enabled_default; // Global flag to enable or disable the webserver + +#ifndef COMMON_IMAGE +const bool webserver_auth_default = WEBSERVER_AUTH_REQUIRED; +#else +const bool webserver_auth_default = false; +#endif + +bool webserver_auth = webserver_auth_default; + // Create AsyncWebServer object on port 80 AsyncWebServer server(80); @@ -159,56 +179,54 @@ void canReplayTask(void* param) { vTaskDelete(NULL); } +void def_route_with_auth(const char* uri, AsyncWebServer& serv, WebRequestMethodComposite method, + std::function handler) { + serv.on(uri, method, [handler](AsyncWebServerRequest* request) { + if (webserver_auth && !request->authenticate(http_username.c_str(), http_password.c_str())) { + return request->requestAuthentication(); + } + handler(request); + }); +} + void init_webserver() { server.on("/logout", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(401); }); // Route for firmware info from ota update page - server.on("/GetFirmwareInfo", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); + def_route_with_auth("/GetFirmwareInfo", server, HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "application/json", get_firmware_info_html, get_firmware_info_processor); }); // Route for root / web page - server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - request->send(200, "text/html", index_html, processor); - }); + def_route_with_auth("/", server, HTTP_GET, + [](AsyncWebServerRequest* request) { request->send(200, "text/html", index_html, processor); }); // Route for going to settings web page - server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - request->send(200, "text/html", index_html, settings_processor); + def_route_with_auth("/settings", server, HTTP_GET, [](AsyncWebServerRequest* request) { + // Using make_shared to ensure lifetime for the settings object during send() lambda execution + auto settings = std::make_shared(true); + + request->send(200, "text/html", settings_html, + [settings](const String& content) { return settings_processor(content, *settings); }); }); // Route for going to advanced battery info web page - server.on("/advanced", HTTP_GET, [](AsyncWebServerRequest* request) { + def_route_with_auth("/advanced", server, HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/html", index_html, advanced_battery_processor); }); // Route for going to CAN logging web page - server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncWebServerResponse* response = request->beginResponse(200, "text/html", can_logger_processor()); - request->send(response); + def_route_with_auth("/canlog", server, HTTP_GET, [](AsyncWebServerRequest* request) { + request->send(request->beginResponse(200, "text/html", can_logger_processor())); }); // Route for going to CAN replay web page - server.on("/canreplay", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { - return request->requestAuthentication(); - } - AsyncWebServerResponse* response = request->beginResponse(200, "text/html", can_replay_processor()); - request->send(response); + def_route_with_auth("/canreplay", server, HTTP_GET, [](AsyncWebServerRequest* request) { + request->send(request->beginResponse(200, "text/html", can_replay_processor())); }); - server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { - return request->requestAuthentication(); - } - + def_route_with_auth("/startReplay", server, HTTP_GET, [](AsyncWebServerRequest* request) { // Prevent multiple replay tasks from being created if (isReplayRunning) { request->send(400, "text/plain", "Replay already running!"); @@ -224,18 +242,14 @@ void init_webserver() { }); // Route for stopping the CAN replay - server.on("/stopReplay", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { - return request->requestAuthentication(); - } - + def_route_with_auth("/stopReplay", server, HTTP_GET, [](AsyncWebServerRequest* request) { datalayer.system.info.loop_playback = false; request->send(200, "text/plain", "CAN replay stopped!"); }); // Route to handle setting the CAN interface for CAN replay - server.on("/setCANInterface", HTTP_GET, [](AsyncWebServerRequest* request) { + def_route_with_auth("/setCANInterface", server, HTTP_GET, [](AsyncWebServerRequest* request) { if (request->hasParam("interface")) { String canInterface = request->getParam("interface")->value(); @@ -363,23 +377,17 @@ void init_webserver() { #endif // Route for going to cellmonitor web page - server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); + def_route_with_auth("/cellmonitor", server, HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/html", index_html, cellmonitor_processor); }); // Route for going to event log web page - server.on("/events", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); + def_route_with_auth("/events", server, HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/html", index_html, events_processor); }); // Route for clearing all events - server.on("/clearevents", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); + def_route_with_auth("/clearevents", server, HTTP_GET, [](AsyncWebServerRequest* request) { reset_all_events(); // Send back a response that includes an instant redirect to /events String response = ""; @@ -388,23 +396,34 @@ void init_webserver() { request->send(200, "text/html", response); }); + def_route_with_auth("/factoryReset", server, HTTP_POST, [](AsyncWebServerRequest* request) { + // Reset all settings to factory defaults + BatteryEmulatorSettingsStore settings; + settings.clearAll(); + + request->send(200, "text/html", "OK"); + }); + +#ifdef COMMON_IMAGE struct BoolSetting { const char* name; bool existingValue; bool newValue; }; - const char* boolSettingNames[] = {"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET"}; + const char* boolSettingNames[] = { + "DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET", + "CANFDASCAN", "WIFIAPENABLED", "MQTTENABLED", "HADISC", "MQTTTOPICS", + }; -#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 settings of the common image server.on("/saveSettings", HTTP_POST, [boolSettingNames](AsyncWebServerRequest* request) { BatteryEmulatorSettingsStore settings; std::vector boolSettings; for (auto& name : boolSettingNames) { - boolSettings.push_back({name, settings.getBool(name), false}); + boolSettings.push_back({name, settings.getBool(name, name == std::string("WIFIAPENABLED")), false}); } int numParams = request->params(); @@ -413,25 +432,56 @@ void init_webserver() { if (p->name() == "inverter") { auto type = static_cast(atoi(p->value().c_str())); settings.saveUInt("INVTYPE", (int)type); + } else if (p->name() == "INVCOMM") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("INVCOMM", (int)type); } else if (p->name() == "battery") { auto type = static_cast(atoi(p->value().c_str())); settings.saveUInt("BATTTYPE", (int)type); + } else if (p->name() == "BATTCHEM") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("BATTCHEM", (int)type); + } else if (p->name() == "BATTCOMM") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("BATTCOMM", (int)type); } else if (p->name() == "charger") { auto type = static_cast(atoi(p->value().c_str())); settings.saveUInt("CHGTYPE", (int)type); - } /*else if (p->name() == "dblbtr") { - newDoubleBattery = p->value() == "on"; - } else if (p->name() == "contctrl") { - 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"); - }*/ + } else if (p->name() == "CHGCOMM") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("CHGCOMM", (int)type); + } else if (p->name() == "EQSTOP") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("EQSTOP", (int)type); + } else if (p->name() == "BATT2COMM") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("BATT2COMM", (int)type); + } else if (p->name() == "shunt") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("SHUNTTYPE", (int)type); + } else if (p->name() == "SHUNTCOMM") { + auto type = static_cast(atoi(p->value().c_str())); + settings.saveUInt("SHUNTCOMM", (int)type); + } else if (p->name() == "HOSTNAME") { + settings.saveString("HOSTNAME", p->value().c_str()); + } else if (p->name() == "MQTTSERVER") { + settings.saveString("MQTTSERVER", p->value().c_str()); + } else if (p->name() == "MQTTPORT") { + auto port = atoi(p->value().c_str()); + settings.saveUInt("MQTTPORT", port); + } else if (p->name() == "MQTTUSER") { + settings.saveString("MQTTUSER", p->value().c_str()); + } else if (p->name() == "MQTTPASSWORD") { + settings.saveString("MQTTPASSWORD", p->value().c_str()); + } else if (p->name() == "MQTTTOPIC") { + settings.saveString("MQTTTOPIC", p->value().c_str()); + } else if (p->name() == "MQTTOBJIDPREFIX") { + settings.saveString("MQTTOBJIDPREFIX", p->value().c_str()); + } else if (p->name() == "MQTTDEVICENAME") { + settings.saveString("MQTTDEVICENAME", p->value().c_str()); + } else if (p->name() == "HADEVICEID") { + settings.saveString("HADEVICEID", p->value().c_str()); + } for (auto& boolSetting : boolSettings) { if (p->name() == boolSetting.name) { @@ -446,16 +496,13 @@ void init_webserver() { } } - settingsUpdated = true; + settingsUpdated = settings.were_settings_updated(); request->redirect("/settings"); }); #endif // Route for editing SSID - server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - + def_route_with_auth("/updateSSID", server, HTTP_GET, [](AsyncWebServerRequest* request) { if (request->hasParam("value")) { String value = request->getParam("value")->value(); if (value.length() <= 63) { // Check if SSID is within the allowable length @@ -470,9 +517,7 @@ void init_webserver() { } }); // Route for editing Password - server.on("/updatePassword", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); + def_route_with_auth("/updatePassword", server, HTTP_GET, [](AsyncWebServerRequest* request) { if (request->hasParam("value")) { String value = request->getParam("value")->value(); if (value.length() > 8) { // Check if password is within the allowable length @@ -487,132 +532,88 @@ void init_webserver() { } }); + auto update_string = [](const char* route, std::function setter, + std::function validator = nullptr) { + def_route_with_auth(route, server, HTTP_GET, [&](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + + if (validator && !validator(value)) { + request->send(400, "text/plain", "Invalid value"); + return; + } + + setter(value); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + }; + + auto update_string_setting = [=](const char* route, std::function setter, + std::function validator = nullptr) { + update_string( + route, + [setter](String value) { + setter(value); + store_settings(); + }, + validator); + }; + + auto update_int_setting = [=](const char* route, std::function setter) { + update_string_setting(route, [setter](String value) { setter(value.toInt()); }); + }; + // Route for editing Sofar ID - server.on("/updateSofarID", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.sofar_user_specified_battery_id = value.toInt(); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/updateSofarID", + [](int value) { datalayer.battery.settings.sofar_user_specified_battery_id = value; }); // Route for editing Wh - server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.info.total_capacity_Wh = value.toInt(); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/updateBatterySize", [](int value) { datalayer.battery.info.total_capacity_Wh = value; }); // Route for editing USE_SCALED_SOC - server.on("/updateUseScaledSOC", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.soc_scaling_active = value.toInt(); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/updateUseScaledSOC", [](int value) { datalayer.battery.settings.soc_scaling_active = value; }); // Route for editing SOCMax - server.on("/updateSocMax", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.max_percentage = static_cast(value.toFloat() * 100); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/updateSocMax", [](String value) { + datalayer.battery.settings.max_percentage = static_cast(value.toFloat() * 100); }); // Route for pause/resume Battery emulator - server.on("/pause", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("p")) { - String valueStr = request->getParam("p")->value(); - setBatteryPause(valueStr == "true" || valueStr == "1", false); - request->send(200, "text/plain", "Updated successfully"); + update_string("/pause", [](String value) { setBatteryPause(value == "true" || value == "1", false); }); + + // Route for equipment stop/resume + update_string("/equipmentStop", [](String value) { + if (value == "true" || value == "1") { + setBatteryPause(true, false, true); //Pause battery, do not pause CAN, equipment stop on (store to flash) } else { - request->send(400, "text/plain", "Bad Request"); + setBatteryPause(false, false, false); } }); - // Route for equipment stop/resume - server.on("/equipmentStop", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("stop")) { - String valueStr = request->getParam("stop")->value(); - if (valueStr == "true" || valueStr == "1") { - setBatteryPause(true, false, true); //Pause battery, do not pause CAN, equipment stop on (store to flash) - } else { - setBatteryPause(false, false, false); - } - request->send(200, "text/plain", "Updated successfully"); + update_string("/equipmentStop", [](String value) { + if (value == "true" || value == "1") { + setBatteryPause(true, false, true); //Pause battery, do not pause CAN, equipment stop on (store to flash) } else { - request->send(400, "text/plain", "Bad Request"); + setBatteryPause(false, false, false); } }); // Route for editing SOCMin - server.on("/updateSocMin", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.min_percentage = static_cast(value.toFloat() * 100); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/updateSocMin", [](String value) { + datalayer.battery.settings.min_percentage = static_cast(value.toFloat() * 100); }); // Route for editing MaxChargeA - server.on("/updateMaxChargeA", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.max_user_set_charge_dA = static_cast(value.toFloat() * 10); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/updateMaxChargeA", [](String value) { + datalayer.battery.settings.max_user_set_charge_dA = static_cast(value.toFloat() * 10); }); // Route for editing MaxDischargeA - server.on("/updateMaxDischargeA", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.max_user_set_discharge_dA = static_cast(value.toFloat() * 10); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/updateMaxDischargeA", [](String value) { + datalayer.battery.settings.max_user_set_discharge_dA = static_cast(value.toFloat() * 10); }); for (const auto& cmd : battery_commands) { @@ -620,7 +621,7 @@ void init_webserver() { server.on( route.c_str(), HTTP_PUT, [cmd](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) { + if (webserver_auth && !request->authenticate(http_username.c_str(), http_password.c_str())) { return request->requestAuthentication(); } }, @@ -642,247 +643,88 @@ void init_webserver() { } // Route for editing BATTERY_USE_VOLTAGE_LIMITS - server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.user_set_voltage_limits_active = value.toInt(); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/updateUseVoltageLimit", + [](int value) { datalayer.battery.settings.user_set_voltage_limits_active = value; }); // Route for editing MaxChargeVoltage - server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast(value.toFloat() * 10); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/updateMaxChargeVoltage", [](String value) { + datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast(value.toFloat() * 10); }); // Route for editing MaxDischargeVoltage - server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast(value.toFloat() * 10); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/updateMaxDischargeVoltage", [](String value) { + datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast(value.toFloat() * 10); }); // Route for editing FakeBatteryVoltage - server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (!request->hasParam("value")) { - request->send(400, "text/plain", "Bad Request"); - } - - String value = request->getParam("value")->value(); - float val = value.toFloat(); - - battery->set_fake_voltage(val); - - request->send(200, "text/plain", "Updated successfully"); - }); + update_string_setting("/updateFakeBatteryVoltage", [](String value) { battery->set_fake_voltage(value.toFloat()); }); // Route for editing balancing enabled - server.on("/TeslaBalAct", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.user_requests_balancing = value.toInt(); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/TeslaBalAct", [](int value) { datalayer.battery.settings.user_requests_balancing = value; }); // Route for editing balancing max time - server.on("/BalTime", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.balancing_time_ms = static_cast(value.toFloat() * 60000); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/BalTime", [](String value) { + datalayer.battery.settings.balancing_time_ms = static_cast(value.toFloat() * 60000); }); // Route for editing balancing max power - server.on("/BalFloatPower", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.balancing_float_power_W = static_cast(value.toFloat()); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/BalFloatPower", [](String value) { + datalayer.battery.settings.balancing_float_power_W = static_cast(value.toFloat()); }); // Route for editing balancing max pack voltage - server.on("/BalMaxPackV", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.balancing_max_pack_voltage_dV = static_cast(value.toFloat() * 10); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/BalMaxPackV", [](String value) { + datalayer.battery.settings.balancing_max_pack_voltage_dV = static_cast(value.toFloat() * 10); }); // Route for editing balancing max cell voltage - server.on("/BalMaxCellV", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.balancing_max_cell_voltage_mV = static_cast(value.toFloat()); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/BalMaxCellV", [](String value) { + datalayer.battery.settings.balancing_max_cell_voltage_mV = static_cast(value.toFloat()); }); // Route for editing balancing max cell voltage deviation - server.on("/BalMaxDevCellV", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV = static_cast(value.toFloat()); - store_settings(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } + update_string_setting("/BalMaxDevCellV", [](String value) { + datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV = static_cast(value.toFloat()); }); if (charger) { // Route for editing ChargerTargetV - server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (!request->hasParam("value")) { - request->send(400, "text/plain", "Bad Request"); - } - - String value = request->getParam("value")->value(); - float val = value.toFloat(); - - if (!(val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV)) { - request->send(400, "text/plain", "Bad Request"); - } - - if (!(val * datalayer.charger.charger_setpoint_HV_IDC <= CHARGER_MAX_POWER)) { - request->send(400, "text/plain", "Bad Request"); - } - - datalayer.charger.charger_setpoint_HV_VDC = val; - - request->send(200, "text/plain", "Updated successfully"); - }); + update_string_setting( + "/updateChargeSetpointV", [](String value) { datalayer.charger.charger_setpoint_HV_VDC = value.toFloat(); }, + [](String value) { + float val = value.toFloat(); + return (val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV) && + (val * datalayer.charger.charger_setpoint_HV_IDC <= CHARGER_MAX_POWER); + }); // Route for editing ChargerTargetA - server.on("/updateChargeSetpointA", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (!request->hasParam("value")) { - request->send(400, "text/plain", "Bad Request"); - } - - String value = request->getParam("value")->value(); - float val = value.toFloat(); - - if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) { - request->send(400, "text/plain", "Bad Request"); - } - - if (!(val * datalayer.charger.charger_setpoint_HV_VDC <= CHARGER_MAX_POWER)) { - request->send(400, "text/plain", "Bad Request"); - } - - datalayer.charger.charger_setpoint_HV_IDC = value.toFloat(); - - request->send(200, "text/plain", "Updated successfully"); - }); + update_string_setting( + "/updateChargeSetpointA", [](String value) { datalayer.charger.charger_setpoint_HV_IDC = value.toFloat(); }, + [](String value) { + float val = value.toFloat(); + return (val <= CHARGER_MAX_A) && (val <= datalayer.battery.settings.max_user_set_charge_dA) && + (val * datalayer.charger.charger_setpoint_HV_VDC <= CHARGER_MAX_POWER); + }); // Route for editing ChargerEndA - server.on("/updateChargeEndA", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.charger.charger_setpoint_HV_IDC_END = value.toFloat(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_string_setting("/updateChargeEndA", + [](String value) { datalayer.charger.charger_setpoint_HV_IDC_END = value.toFloat(); }); // Route for enabling/disabling HV charger - server.on("/updateChargerHvEnabled", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.charger.charger_HV_enabled = (bool)value.toInt(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/updateChargerHvEnabled", + [](int value) { datalayer.charger.charger_HV_enabled = (bool)value; }); // Route for enabling/disabling aux12v charger - server.on("/updateChargerAux12vEnabled", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - datalayer.charger.charger_aux12V_enabled = (bool)value.toInt(); - request->send(200, "text/plain", "Updated successfully"); - } else { - request->send(400, "text/plain", "Bad Request"); - } - }); + update_int_setting("/updateChargerAux12vEnabled", + [](int value) { datalayer.charger.charger_aux12V_enabled = (bool)value; }); } // Send a GET request to /update - server.on("/debug", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); - request->send(200, "text/plain", "Debug: all OK."); - }); + def_route_with_auth("/debug", server, HTTP_GET, + [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); }); // Route to handle reboot command - server.on("/reboot", HTTP_GET, [](AsyncWebServerRequest* request) { - if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) - return request->requestAuthentication(); + def_route_with_auth("/reboot", server, HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Rebooting server..."); //Equipment STOP without persisting the equipment state before restart @@ -943,19 +785,8 @@ String get_firmware_info_processor(const String& var) { if (var == "X") { String content = ""; 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); serializeJson(doc, content); return content; @@ -977,7 +808,7 @@ String processor(const String& var) { content += ""; // Compact header - content += "

" + String(ssidAP) + "

"; + content += "

Battery Emulator

"; // Start content block content += "
"; @@ -1037,7 +868,7 @@ String processor(const String& var) { // Display which components are used if (inverter) { content += "

Inverter protocol: "; - content += datalayer.system.info.inverter_protocol; + content += inverter->name(); content += " "; content += datalayer.system.info.inverter_brand; content += "

"; @@ -1291,9 +1122,11 @@ String processor(const String& var) { content += " Cont. Pos.: "; content += ""; } - } else { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded + } else if ( + esp32hal->SECOND_BATTERY_CONTACTORS_PIN() != + GPIO_NUM_NC) { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded content += "

Cont. Neg.: "; - if (digitalRead(SECOND_BATTERY_CONTACTORS_PIN) == HIGH) { + if (digitalRead(esp32hal->SECOND_BATTERY_CONTACTORS_PIN()) == HIGH) { content += ""; } else { content += ""; @@ -1532,7 +1365,7 @@ String processor(const String& var) { content += " "; content += " "; content += ""; - if (WEBSERVER_AUTH_REQUIRED) + if (webserver_auth) content += ""; if (!datalayer.system.settings.equipment_stop_active) content += @@ -1558,16 +1391,7 @@ String processor(const String& var) { content += "function CANreplay() { window.location.href = '/canreplay'; }"; content += "function Log() { window.location.href = '/log'; }"; content += "function Events() { window.location.href = '/events'; }"; - content += - "function askReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If " - "emulator is handling contactors, they will open during reboot!')) { " - "reboot(); } }"; - content += "function reboot() {"; - content += " var xhr = new XMLHttpRequest();"; - content += " xhr.open('GET', '/reboot', true);"; - content += " xhr.send();"; - content += "}"; - if (WEBSERVER_AUTH_REQUIRED) { + if (webserver_auth) { content += "function logout() {"; content += " var xhr = new XMLHttpRequest();"; content += " xhr.open('GET', '/logout', true);"; @@ -1579,7 +1403,7 @@ String processor(const String& var) { content += "var xhr=new " "XMLHttpRequest();xhr.onload=function() { " - "window.location.reload();};xhr.open('GET','/pause?p='+pause,true);xhr.send();"; + "window.location.reload();};xhr.open('GET','/pause?value='+pause,true);xhr.send();"; content += "}"; content += "function estop(stop){"; content += diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 62d8d90c..71eacc2b 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -9,14 +9,10 @@ #include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h" #include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h" +extern bool webserver_enabled; + extern const char* version_number; // The current software version, shown on webserver -#include -extern const char* http_username; -extern const char* http_password; - -extern const char* ssidAP; - // Common charger parameters extern float charger_stat_HVcur; extern float charger_stat_HVvol; diff --git a/Software/src/devboard/wifi/wifi.cpp b/Software/src/devboard/wifi/wifi.cpp index b671af8b..545a6ed3 100644 --- a/Software/src/devboard/wifi/wifi.cpp +++ b/Software/src/devboard/wifi/wifi.cpp @@ -1,7 +1,43 @@ #include "wifi.h" +#include #include "../../include.h" #include "../utils/events.h" +#if defined(WIFI) || defined(WEBSERVER) +const bool wifi_enabled_default = true; +#else +const bool wifi_enabled_default = false; +#endif + +bool wifi_enabled = wifi_enabled_default; + +#ifdef COMMON_IMAGE +const bool wifiap_enabled_default = true; +#else +#ifdef WIFIAP +const bool wifiap_enabled_default = true; +#else +const bool wifiap_enabled_default = false; +#endif +#endif + +bool wifiap_enabled = wifiap_enabled_default; + +#ifdef MDNSRESPONDER +const bool mdns_enabled_default = true; +#else +const bool mdns_enabled_default = false; +#endif +bool mdns_enabled = mdns_enabled_default; + +#ifdef CUSTOM_HOSTNAME +std::string custom_hostname = CUSTOM_HOSTNAME; +#else +std::string custom_hostname; +#endif + +std::string ssidAP; + // Configuration Parameters static const uint16_t WIFI_CHECK_INTERVAL = 2000; // 1 seconds normal check interval when last connected static const uint16_t STEP_WIFI_CHECK_INTERVAL = 2000; // 3 seconds wait step increase in checks for normal reconnects @@ -27,17 +63,19 @@ static uint16_t current_check_interval = WIFI_CHECK_INTERVAL; static bool connected_once = false; void init_WiFi() { + DEBUG_PRINTF("init_Wifi enabled=%d, apå=%d, ssid=%s, password=%s\n", wifi_enabled, wifiap_enabled, ssid.c_str(), + password.c_str()); -#ifdef CUSTOM_HOSTNAME - WiFi.setHostname(CUSTOM_HOSTNAME); // Set custom hostname if defined in USER_SETTINGS.h -#endif + if (!custom_hostname.empty()) { + WiFi.setHostname(custom_hostname.c_str()); + } -#ifdef WIFIAP - WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection - init_WiFi_AP(); -#else - WiFi.mode(WIFI_STA); // Only Router connection -#endif // WIFIAP + if (wifiap_enabled) { + WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection + init_WiFi_AP(); + } else if (wifi_enabled) { + WiFi.mode(WIFI_STA); // Only Router connection + } // Set WiFi to auto reconnect WiFi.setAutoReconnect(true); @@ -47,17 +85,26 @@ void init_WiFi() { WiFi.config(local_IP, gateway, subnet); #endif + DEBUG_PRINTF("init_Wifi set event handlers\n"); + // Initialize Wi-Fi event handlers WiFi.onEvent(onWifiConnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED); WiFi.onEvent(onWifiDisconnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); WiFi.onEvent(onWifiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); // Start Wi-Fi connection + DEBUG_PRINTF("start Wifi\n"); connectToWiFi(); + + DEBUG_PRINTF("init_Wifi complete\n"); } // Task to monitor Wi-Fi status and handle reconnections void wifi_monitor() { + if (ssid.empty() || password.empty()) { + return; + } + unsigned long currentMillis = millis(); // Check if it's time to monitor the Wi-Fi status @@ -104,6 +151,11 @@ void wifi_monitor() { #ifdef DEBUG_LOG logging.println("No previous OK connection, force a full connection attempt..."); #endif + + wifiap_enabled = true; + WiFi.mode(WIFI_AP_STA); + init_WiFi_AP(); + FullReconnectToWiFi(); } } @@ -125,11 +177,18 @@ void FullReconnectToWiFi() { // Function to handle Wi-Fi connection void connectToWiFi() { + if (ssid.empty() || password.empty()) { + return; + } + if (WiFi.status() != WL_CONNECTED) { lastReconnectAttempt = millis(); // Reset the reconnect attempt timer #ifdef DEBUG_LOG logging.println("Connecting to Wi-Fi..."); #endif + + DEBUG_PRINTF("Connecting to Wi-Fi SSID: %s, password: %s, Channel: %d\n", ssid.c_str(), password.c_str(), + wifi_channel); WiFi.begin(ssid.c_str(), password.c_str(), wifi_channel); } else { #ifdef DEBUG_LOG @@ -143,12 +202,8 @@ void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) { clear_event(EVENT_WIFI_DISCONNECT); set_event(EVENT_WIFI_CONNECT, 0); connected_once = true; -#ifdef DEBUG_LOG - logging.print("Wi-Fi connected. RSSI: "); - logging.print(-WiFi.RSSI()); - logging.print(" dBm, IP address: "); - logging.println(WiFi.localIP().toString()); -#endif + DEBUG_PRINTF("Wi-Fi connected. RSSI: %d dBm, IP address: %s, SSID: %s\n", -WiFi.RSSI(), + WiFi.localIP().toString().c_str(), WiFi.SSID().c_str()); hasConnectedBefore = true; // Mark as successfully connected at least once reconnectAttempts = 0; // Reset the attempt counter current_full_reconnect_interval = INIT_WIFI_FULL_RECONNECT_INTERVAL; // Reset the full reconnect interval @@ -169,8 +224,10 @@ void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { // Event handler for Wi-Fi disconnection void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) { - if (connected_once) + + if (connected_once) { set_event(EVENT_WIFI_DISCONNECT, 0); + } #ifdef DEBUG_LOG logging.println("Wi-Fi disconnected."); #endif @@ -179,16 +236,16 @@ void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) { //normal reconnect retry start at first 2 seconds } -#ifdef MDNSRESPONDER // Initialise mDNS void init_mDNS() { // Calulate the host name using the last two chars from the MAC address so each one is likely unique on a network. // e.g batteryemulator8C.local where the mac address is 08:F9:E0:D1:06:8C String mac = WiFi.macAddress(); String mdnsHost = "batteryemulator" + mac.substring(mac.length() - 2); -#ifdef CUSTOM_HOSTNAME // If CUSTOM_HOSTNAME is defined, use the same hostname also for mDNS - mdnsHost = CUSTOM_HOSTNAME; -#endif + + if (!custom_hostname.empty()) { + mdnsHost = String(custom_hostname.c_str()); + } // Initialize mDNS .local resolution if (!MDNS.begin(mdnsHost)) { @@ -200,20 +257,15 @@ void init_mDNS() { MDNS.addService(mdnsHost, "tcp", 80); } } -#endif // MDNSRESPONDER -#ifdef WIFIAP void init_WiFi_AP() { -#ifdef DEBUG_LOG - logging.println("Creating Access Point: " + String(ssidAP)); - logging.println("With password: " + String(passwordAP)); -#endif - WiFi.softAP(ssidAP, passwordAP); + ssidAP = std::string("BatteryEmulator") + WiFi.macAddress().c_str(); + + DEBUG_PRINTF("Creating Access Point: %s\n", ssidAP.c_str()); + DEBUG_PRINTF("With password: %s\n", passwordAP.c_str()); + + WiFi.softAP(ssidAP.c_str(), passwordAP.c_str()); IPAddress IP = WiFi.softAPIP(); -#ifdef DEBUG_LOG - logging.println("Access Point created."); - logging.print("IP address: "); - logging.println(IP.toString()); -#endif + + DEBUG_PRINTF("Access Point created.\nIP address: %s\n", IP.toString().c_str()); } -#endif // WIFIAP diff --git a/Software/src/devboard/wifi/wifi.h b/Software/src/devboard/wifi/wifi.h index bed27416..a75ac703 100644 --- a/Software/src/devboard/wifi/wifi.h +++ b/Software/src/devboard/wifi/wifi.h @@ -5,15 +5,12 @@ #include #include "../../include.h" -#ifdef MDNSRESPONDER -#include -#endif // MDNSRESONDER - extern std::string ssid; extern std::string password; extern const uint8_t wifi_channel; -extern const char* ssidAP; -extern const char* passwordAP; +extern std::string ssidAP; +extern std::string passwordAP; +extern std::string custom_hostname; void init_WiFi(); void wifi_monitor(); @@ -23,13 +20,13 @@ void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info); void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info); void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info); -#ifdef WIFIAP void init_WiFi_AP(); -#endif // WIFIAP -#ifdef MDNSRESPONDER // Initialise mDNS void init_mDNS(); -#endif // MDNSRESPONDER + +extern bool wifi_enabled; +extern bool wifiap_enabled; +extern bool mdns_enabled; #endif diff --git a/Software/src/include.h b/Software/src/include.h index 6a72f9b4..8c849293 100644 --- a/Software/src/include.h +++ b/Software/src/include.h @@ -19,17 +19,10 @@ /* - 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! #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 #if defined(PERIODIC_BMS_RESET) || defined(REMOTE_BMS_RESET) #if defined(CAN_ADDON) || defined(CANFD_ADDON) || defined(CHADEMO_BATTERY) diff --git a/Software/src/inverter/AFORE-CAN.cpp b/Software/src/inverter/AFORE-CAN.cpp index 7cffecbe..87741953 100644 --- a/Software/src/inverter/AFORE-CAN.cpp +++ b/Software/src/inverter/AFORE-CAN.cpp @@ -169,8 +169,3 @@ void AforeCanInverter::transmit_can(unsigned long currentMillis) { 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'; -} diff --git a/Software/src/inverter/AFORE-CAN.h b/Software/src/inverter/AFORE-CAN.h index 15d71b00..cf0768fa 100644 --- a/Software/src/inverter/AFORE-CAN.h +++ b/Software/src/inverter/AFORE-CAN.h @@ -10,7 +10,7 @@ class AforeCanInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); void update_values(); diff --git a/Software/src/inverter/BYD-CAN.cpp b/Software/src/inverter/BYD-CAN.cpp index 915eae71..a41c6497 100644 --- a/Software/src/inverter/BYD-CAN.cpp +++ b/Software/src/inverter/BYD-CAN.cpp @@ -169,8 +169,3 @@ void BydCanInverter::send_initial_data() { transmit_can_frame(&BYD_3D0_2, 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'; -} diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h index dbb49fb4..0668b15d 100644 --- a/Software/src/inverter/BYD-CAN.h +++ b/Software/src/inverter/BYD-CAN.h @@ -10,7 +10,7 @@ class BydCanInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); void update_values(); diff --git a/Software/src/inverter/BYD-MODBUS.cpp b/Software/src/inverter/BYD-MODBUS.cpp index 26d704dc..89625560 100644 --- a/Software/src/inverter/BYD-MODBUS.cpp +++ b/Software/src/inverter/BYD-MODBUS.cpp @@ -145,17 +145,21 @@ void BydModbusInverter::verify_inverter_modbus() { } } -void 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'; - +bool BydModbusInverter::setup(void) { // Performs one time setup at startup over CAN bus // Init Static data to the RTU Modbus handle_static_data(); // Init Serial2 connected to the RTU Modbus 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 MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03); 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); // Start ModbusRTU background task - MBserver.begin(Serial2, MODBUS_CORE); + MBserver.begin(Serial2, esp32hal->MODBUS_CORE()); + + return true; } diff --git a/Software/src/inverter/BYD-MODBUS.h b/Software/src/inverter/BYD-MODBUS.h index 54280edb..3bcf378e 100644 --- a/Software/src/inverter/BYD-MODBUS.h +++ b/Software/src/inverter/BYD-MODBUS.h @@ -10,7 +10,8 @@ class BydModbusInverter : public ModbusInverterProtocol { public: - void setup(); + const char* name() override { return Name; } + bool setup() override; void update_values(); static constexpr const char* Name = "BYD 11kWh HVM battery over Modbus RTU"; diff --git a/Software/src/inverter/CanInverterProtocol.h b/Software/src/inverter/CanInverterProtocol.h index 2bce2a60..10b72c78 100644 --- a/Software/src/inverter/CanInverterProtocol.h +++ b/Software/src/inverter/CanInverterProtocol.h @@ -4,6 +4,7 @@ #include "InverterProtocol.h" #include "src/communication/can/CanReceiver.h" +#include "src/communication/can/comm_can.h" #include "src/devboard/utils/types.h" class CanInverterProtocol : public InverterProtocol, Transmitter, CanReceiver { diff --git a/Software/src/inverter/FERROAMP-CAN.cpp b/Software/src/inverter/FERROAMP-CAN.cpp index a9dd74b9..340fd14c 100644 --- a/Software/src/inverter/FERROAMP-CAN.cpp +++ b/Software/src/inverter/FERROAMP-CAN.cpp @@ -365,8 +365,3 @@ void FerroampCanInverter::send_system_data() { //System equipment information transmit_can_frame(&PYLON_4291, can_config.inverter); #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'; -} diff --git a/Software/src/inverter/FERROAMP-CAN.h b/Software/src/inverter/FERROAMP-CAN.h index 5d0f5e86..e5b5cd3c 100644 --- a/Software/src/inverter/FERROAMP-CAN.h +++ b/Software/src/inverter/FERROAMP-CAN.h @@ -10,7 +10,7 @@ class FerroampCanInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/FOXESS-CAN.cpp b/Software/src/inverter/FOXESS-CAN.cpp index 7fb0b96c..ccf29822 100644 --- a/Software/src/inverter/FOXESS-CAN.cpp +++ b/Software/src/inverter/FOXESS-CAN.cpp @@ -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'; -} diff --git a/Software/src/inverter/FOXESS-CAN.h b/Software/src/inverter/FOXESS-CAN.h index 18bb4e48..9f7e9c04 100644 --- a/Software/src/inverter/FOXESS-CAN.h +++ b/Software/src/inverter/FOXESS-CAN.h @@ -10,7 +10,7 @@ class FoxessCanInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/GROWATT-HV-CAN.cpp b/Software/src/inverter/GROWATT-HV-CAN.cpp index 7bfc0511..e9cb427f 100644 --- a/Software/src/inverter/GROWATT-HV-CAN.cpp +++ b/Software/src/inverter/GROWATT-HV-CAN.cpp @@ -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'; -} diff --git a/Software/src/inverter/GROWATT-HV-CAN.h b/Software/src/inverter/GROWATT-HV-CAN.h index feb5959c..5b0e0355 100644 --- a/Software/src/inverter/GROWATT-HV-CAN.h +++ b/Software/src/inverter/GROWATT-HV-CAN.h @@ -10,7 +10,7 @@ class GrowattHvInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/GROWATT-LV-CAN.cpp b/Software/src/inverter/GROWATT-LV-CAN.cpp index 4a5915fe..29fedac0 100644 --- a/Software/src/inverter/GROWATT-LV-CAN.cpp +++ b/Software/src/inverter/GROWATT-LV-CAN.cpp @@ -202,8 +202,3 @@ void GrowattLvInverter::map_can_frame_to_variable(CAN_frame rx_frame) { void GrowattLvInverter::transmit_can(unsigned long currentMillis) { // 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'; -} diff --git a/Software/src/inverter/GROWATT-LV-CAN.h b/Software/src/inverter/GROWATT-LV-CAN.h index d8ff144d..1d9877b7 100644 --- a/Software/src/inverter/GROWATT-LV-CAN.h +++ b/Software/src/inverter/GROWATT-LV-CAN.h @@ -10,7 +10,7 @@ class GrowattLvInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/INVERTERS.cpp b/Software/src/inverter/INVERTERS.cpp index 451e87b3..3019d0b0 100644 --- a/Software/src/inverter/INVERTERS.cpp +++ b/Software/src/inverter/INVERTERS.cpp @@ -84,9 +84,9 @@ extern const char* name_for_inverter_type(InverterProtocolType type) { #error "Compile time SELECTED_INVERTER_CLASS should not be defined with COMMON_IMAGE" #endif -void setup_inverter() { +bool setup_inverter() { if (inverter) { - return; + return true; } switch (user_selected_inverter_protocol) { @@ -167,6 +167,7 @@ void setup_inverter() { break; case InverterProtocolType::None: + return true; case InverterProtocolType::Highest: default: inverter = nullptr; // Or handle as error @@ -174,23 +175,29 @@ void setup_inverter() { } if (inverter) { - inverter->setup(); + return inverter->setup(); } + + return false; } #else -void setup_inverter() { +bool setup_inverter() { if (inverter) { // The inverter is setup only once. - return; + return true; } #ifdef SELECTED_INVERTER_CLASS inverter = new SELECTED_INVERTER_CLASS(); if (inverter) { - inverter->setup(); + return inverter->setup(); } + + return false; +#else + return true; #endif } #endif diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index 490e5cf5..ee6d3fca 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -32,6 +32,6 @@ extern InverterProtocol* inverter; #include "SUNGROW-CAN.h" // Call to initialize the build-time selected inverter. Safe to call even though inverter was not selected. -void setup_inverter(); +bool setup_inverter(); #endif diff --git a/Software/src/inverter/InverterProtocol.h b/Software/src/inverter/InverterProtocol.h index 5bd71edf..8620eff1 100644 --- a/Software/src/inverter/InverterProtocol.h +++ b/Software/src/inverter/InverterProtocol.h @@ -35,7 +35,8 @@ enum class InverterInterfaceType { Can, Rs485, Modbus }; // The abstract base class for all inverter protocols class InverterProtocol { public: - virtual void setup() = 0; + virtual const char* name() = 0; + virtual bool setup() { return true; } virtual const char* interface_name() = 0; virtual InverterInterfaceType interface_type() = 0; @@ -46,6 +47,8 @@ class InverterProtocol { virtual bool controls_contactor() { return false; } virtual bool allows_contactor_closing() { return false; } + + virtual bool supports_battery_id() { return false; } }; extern InverterProtocol* inverter; diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 94f2c910..e1f3829f 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -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; 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; } diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index e96cb5f6..e953656b 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -19,7 +19,8 @@ class KostalInverterProtocol : public Rs485InverterProtocol { public: - void setup(); + const char* name() override { return Name; } + bool setup() override; void receive(); void update_values(); static constexpr const char* Name = "BYD battery via Kostal RS485"; diff --git a/Software/src/inverter/PYLON-CAN.cpp b/Software/src/inverter/PYLON-CAN.cpp index 5de88ffa..243289a1 100644 --- a/Software/src/inverter/PYLON-CAN.cpp +++ b/Software/src/inverter/PYLON-CAN.cpp @@ -352,8 +352,3 @@ void PylonInverter::send_system_data() { //System equipment information transmit_can_frame(&PYLON_4291, can_config.inverter); #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'; -} diff --git a/Software/src/inverter/PYLON-CAN.h b/Software/src/inverter/PYLON-CAN.h index 64d6df08..cc47f143 100644 --- a/Software/src/inverter/PYLON-CAN.h +++ b/Software/src/inverter/PYLON-CAN.h @@ -10,7 +10,7 @@ class PylonInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/PYLON-LV-CAN.cpp b/Software/src/inverter/PYLON-LV-CAN.cpp index 21c9aeac..5701c9e2 100644 --- a/Software/src/inverter/PYLON-LV-CAN.cpp +++ b/Software/src/inverter/PYLON-LV-CAN.cpp @@ -144,8 +144,3 @@ void PylonLvInverter::transmit_can(unsigned long currentMillis) { 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'; -} diff --git a/Software/src/inverter/PYLON-LV-CAN.h b/Software/src/inverter/PYLON-LV-CAN.h index 8cf770dd..41a8baad 100644 --- a/Software/src/inverter/PYLON-LV-CAN.h +++ b/Software/src/inverter/PYLON-LV-CAN.h @@ -10,7 +10,7 @@ class PylonLvInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/SCHNEIDER-CAN.cpp b/Software/src/inverter/SCHNEIDER-CAN.cpp index d0579b76..f0df08f4 100644 --- a/Software/src/inverter/SCHNEIDER-CAN.cpp +++ b/Software/src/inverter/SCHNEIDER-CAN.cpp @@ -225,8 +225,3 @@ void SchneiderInverter::transmit_can(unsigned long currentMillis) { 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'; -} diff --git a/Software/src/inverter/SCHNEIDER-CAN.h b/Software/src/inverter/SCHNEIDER-CAN.h index 19b57ecb..7c986463 100644 --- a/Software/src/inverter/SCHNEIDER-CAN.h +++ b/Software/src/inverter/SCHNEIDER-CAN.h @@ -10,7 +10,7 @@ class SchneiderInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/SMA-BYD-H-CAN.cpp b/Software/src/inverter/SMA-BYD-H-CAN.cpp index 6407ebbe..17cdac45 100644 --- a/Software/src/inverter/SMA-BYD-H-CAN.cpp +++ b/Software/src/inverter/SMA-BYD-H-CAN.cpp @@ -69,16 +69,7 @@ void SmaBydHInverter:: SMA_158.data.u8[2] = 0x6A; } -#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN - // 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 + control_contactor_led(); // 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) { @@ -258,14 +249,3 @@ void SmaBydHInverter::transmit_can_init() { transmit_can_frame(&SMA_518, 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 -} diff --git a/Software/src/inverter/SMA-BYD-H-CAN.h b/Software/src/inverter/SMA-BYD-H-CAN.h index 52cbe6e9..ce234ba2 100644 --- a/Software/src/inverter/SMA-BYD-H-CAN.h +++ b/Software/src/inverter/SMA-BYD-H-CAN.h @@ -2,23 +2,22 @@ #define SMA_BYD_H_CAN_H #include "../include.h" -#include "CanInverterProtocol.h" +#include "SmaInverterBase.h" #include "src/devboard/hal/hal.h" #ifdef SMA_BYD_H_CAN #define SELECTED_INVERTER_CLASS SmaBydHInverter #endif -class SmaBydHInverter : public CanInverterProtocol { +class SmaBydHInverter : public SmaInverterBase { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); static constexpr const char* Name = "BYD over SMA CAN"; virtual bool controls_contactor() { return true; } - virtual bool allows_contactor_closing() { return digitalRead(INVERTER_CONTACTOR_ENABLE_PIN) == 1; } private: static const int READY_STATE = 0x03; diff --git a/Software/src/inverter/SMA-BYD-HVS-CAN.cpp b/Software/src/inverter/SMA-BYD-HVS-CAN.cpp index bd9dd8d0..60d2d7aa 100644 --- a/Software/src/inverter/SMA-BYD-HVS-CAN.cpp +++ b/Software/src/inverter/SMA-BYD-HVS-CAN.cpp @@ -68,16 +68,7 @@ void SmaBydHvsInverter:: SMA_158.data.u8[2] = 0x6A; } -#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN - // 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 + control_contactor_led(); // 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) { @@ -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 -} diff --git a/Software/src/inverter/SMA-BYD-HVS-CAN.h b/Software/src/inverter/SMA-BYD-HVS-CAN.h index d5f5eaa1..aa70c915 100644 --- a/Software/src/inverter/SMA-BYD-HVS-CAN.h +++ b/Software/src/inverter/SMA-BYD-HVS-CAN.h @@ -2,23 +2,22 @@ #define SMA_BYD_HVS_CAN_H #include "../include.h" -#include "CanInverterProtocol.h" +#include "SmaInverterBase.h" #include "src/devboard/hal/hal.h" #ifdef SMA_BYD_HVS_CAN #define SELECTED_INVERTER_CLASS SmaBydHvsInverter #endif -class SmaBydHvsInverter : public CanInverterProtocol { +class SmaBydHvsInverter : public SmaInverterBase { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); static constexpr const char* Name = "BYD Battery-Box HVS over SMA CAN"; virtual bool controls_contactor() { return true; } - virtual bool allows_contactor_closing() { return digitalRead(INVERTER_CONTACTOR_ENABLE_PIN) == 1; } private: static const int READY_STATE = 0x03; diff --git a/Software/src/inverter/SMA-LV-CAN.cpp b/Software/src/inverter/SMA-LV-CAN.cpp index c78157bf..2c0cc098 100644 --- a/Software/src/inverter/SMA-LV-CAN.cpp +++ b/Software/src/inverter/SMA-LV-CAN.cpp @@ -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'; -} diff --git a/Software/src/inverter/SMA-LV-CAN.h b/Software/src/inverter/SMA-LV-CAN.h index 7ea8025a..3eb345ba 100644 --- a/Software/src/inverter/SMA-LV-CAN.h +++ b/Software/src/inverter/SMA-LV-CAN.h @@ -10,7 +10,7 @@ class SmaLvInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp index f7dbeebf..100df1dc 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.cpp +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.cpp @@ -65,6 +65,8 @@ void SmaTripowerInverter:: 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 if (!datalayer.system.status.inverter_allows_contactor_closing) { timeWithoutInverterAllowsContactorClosing++; @@ -181,14 +183,3 @@ void SmaTripowerInverter::transmit_can_init() { pushFrame(&SMA_4D8); 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 -} diff --git a/Software/src/inverter/SMA-TRIPOWER-CAN.h b/Software/src/inverter/SMA-TRIPOWER-CAN.h index f2543cd2..d850f9fd 100644 --- a/Software/src/inverter/SMA-TRIPOWER-CAN.h +++ b/Software/src/inverter/SMA-TRIPOWER-CAN.h @@ -2,23 +2,22 @@ #define SMA_CAN_TRIPOWER_H #include "../include.h" -#include "CanInverterProtocol.h" +#include "SmaInverterBase.h" #include "src/devboard/hal/hal.h" #ifdef SMA_TRIPOWER_CAN #define SELECTED_INVERTER_CLASS SmaTripowerInverter #endif -class SmaTripowerInverter : public CanInverterProtocol { +class SmaTripowerInverter : public SmaInverterBase { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); static constexpr const char* Name = "SMA Tripower CAN"; virtual bool controls_contactor() { return true; } - virtual bool allows_contactor_closing() { return digitalRead(INVERTER_CONTACTOR_ENABLE_PIN) == 1; } private: const int READY_STATE = 0x03; diff --git a/Software/src/inverter/SOFAR-CAN.cpp b/Software/src/inverter/SOFAR-CAN.cpp index 004e5375..a5af6fbb 100644 --- a/Software/src/inverter/SOFAR-CAN.cpp +++ b/Software/src/inverter/SOFAR-CAN.cpp @@ -115,7 +115,7 @@ void SofarInverter::transmit_can(unsigned long currentMillis) { } } -void SofarInverter::setup(void) { // Performs one time setup at startup over CAN bus +bool SofarInverter::setup() { // Performs one time setup at startup over CAN bus // Dymanically set CAN ID according to which battery index we are on uint16_t base_offset = (datalayer.battery.settings.sofar_user_specified_battery_id << 12); auto init_frame = [&](CAN_frame& frame, uint16_t base_id) { @@ -140,9 +140,9 @@ void SofarInverter::setup(void) { // Performs one time setup at startup over CA init_frame(SOFAR_685, 0x685); init_frame(SOFAR_690, 0x690); - strncpy(datalayer.system.info.inverter_protocol, Name, 63); - datalayer.system.info.inverter_protocol[63] = '\0'; String tempStr(datalayer.battery.settings.sofar_user_specified_battery_id); strncpy(datalayer.system.info.inverter_brand, tempStr.c_str(), 7); datalayer.system.info.inverter_brand[7] = '\0'; + + return true; } diff --git a/Software/src/inverter/SOFAR-CAN.h b/Software/src/inverter/SOFAR-CAN.h index 803f2e75..3b8fa635 100644 --- a/Software/src/inverter/SOFAR-CAN.h +++ b/Software/src/inverter/SOFAR-CAN.h @@ -10,11 +10,13 @@ class SofarInverter : public CanInverterProtocol { public: - void setup(); + bool setup() override; + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); static constexpr const char* Name = "Sofar BMS (Extended) via CAN, Battery ID"; + bool supports_battery_id() { return true; } private: unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index 187ec772..34ec54db 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -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 - strncpy(datalayer.system.info.inverter_protocol, Name, 63); - datalayer.system.info.inverter_protocol[63] = '\0'; +bool SolaxInverter::setup(void) { // Performs one time setup at startup datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first + return true; } diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h index 504bb68c..4cd8a978 100644 --- a/Software/src/inverter/SOLAX-CAN.h +++ b/Software/src/inverter/SOLAX-CAN.h @@ -10,7 +10,8 @@ class SolaxInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } + bool setup(); void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/SUNGROW-CAN.cpp b/Software/src/inverter/SUNGROW-CAN.cpp index 40d2b8be..34a0062e 100644 --- a/Software/src/inverter/SUNGROW-CAN.cpp +++ b/Software/src/inverter/SUNGROW-CAN.cpp @@ -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'; -} diff --git a/Software/src/inverter/SUNGROW-CAN.h b/Software/src/inverter/SUNGROW-CAN.h index 46b4938b..edeebb90 100644 --- a/Software/src/inverter/SUNGROW-CAN.h +++ b/Software/src/inverter/SUNGROW-CAN.h @@ -10,7 +10,7 @@ class SungrowInverter : public CanInverterProtocol { public: - void setup(); + const char* name() override { return Name; } void update_values(); void transmit_can(unsigned long currentMillis); void map_can_frame_to_variable(CAN_frame rx_frame); diff --git a/Software/src/inverter/SmaInverterBase.h b/Software/src/inverter/SmaInverterBase.h new file mode 100644 index 00000000..a075cf4d --- /dev/null +++ b/Software/src/inverter/SmaInverterBase.h @@ -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