diff --git a/Software/Software.ino b/Software/Software.ino index b927a729..d9f47601 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -26,9 +26,15 @@ #include "src/datalayer/datalayer.h" #ifdef WEBSERVER -#include #include "src/devboard/webserver/webserver.h" -#endif +#ifdef MDNSRESPONDER +#include +#endif // MDNSRESONDER +#else // WEBSERVER +#ifdef MDNSRESPONDER +#error WEBSERVER needs to be enabled for MDNSRESPONDER! +#endif // MDNSRSPONDER +#endif // WEBSERVER Preferences settings; // Store user settings // The current software version, shown on webserver @@ -167,7 +173,9 @@ void loop() { void connectivity_loop(void* task_time_us) { // Init init_webserver(); +#ifdef MDNSRESPONDER init_mDNS(); +#endif #ifdef MQTT init_mqtt(); #endif @@ -284,7 +292,7 @@ void core_loop(void* task_time_us) { } } -#ifdef WEBSERVER +#ifdef MDNSRESPONDER // Initialise mDNS void init_mDNS() { @@ -303,7 +311,7 @@ void init_mDNS() { MDNS.addService("battery_emulator", "tcp", 80); } } -#endif +#endif // MDNSRESPONDER // Initialization functions void init_serial() { diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index 0171c248..207d523c 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -22,15 +22,15 @@ volatile CAN_Configuration can_config = { volatile uint8_t AccessPointEnabled = true; //Set to either true/false to enable direct wifi access point std::string ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 characters; std::string password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters; -const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters; +const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters, also used for device name on web interface const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection // MQTT #ifdef MQTT -const char* mqtt_user = "REDACTED"; -const char* mqtt_password = "REDACTED"; -#endif // USE_MQTT -#endif // WEBSERVER +const char* mqtt_user = "REDACTED"; // Set NULL for no username +const char* mqtt_password = "REDACTED"; // Set NULL for no password +#endif // USE_MQTT +#endif // WEBSERVER /* 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 67ea352f..58844c0d 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -55,6 +55,8 @@ //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) #define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. +#define WIFIAP //Disable this line to permanently disable WIFI AP mode (make sure to hardcode ssid and password of you home wifi network). When enabled WIFI AP can still be disabled by a setting in the future. +#define MDNSRESPONDER //Enable this line to enable MDNS, allows battery monitor te be found by .local address. Requires WEBSERVER to be enabled. #define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot (overrides Wifi/battery settings set below) //#define FUNCTION_TIME_MEASUREMENT // Enable this to record execution times and present them in the web UI (WARNING, raises CPU load, do not use for production) @@ -63,6 +65,9 @@ #define MQTT_SERVER "192.168.xxx.yyy" #define MQTT_PORT 1883 +/* Home Assistant options */ +#define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required + /* Event options*/ #define DUMMY_EVENT_ENABLED false //Enable this line to have a dummy event that gets logged to test the interface diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 67535cfd..87dcc413 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -12,7 +12,6 @@ WiFiClient espClient; PubSubClient client(espClient); char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; -int value = 0; static unsigned long previousMillisUpdateVal; MyTimer publish_global_timer(5000); @@ -25,13 +24,17 @@ static void publish_values(void) { publish_cell_voltages(); } +#ifdef HA_AUTODISCOVERY static String generateCellVoltageAutoConfigTopic(int cell_number, const char* hostname) { return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/cell_voltage" + String(cell_number) + "/config"; } +#endif // HA_AUTODISCOVERY static void publish_cell_voltages(void) { +#ifdef HA_AUTODISCOVERY static bool mqtt_first_transmission = true; +#endif // HA_AUTODISCOVERY static JsonDocument doc; static const char* hostname = WiFi.getHostname(); static String state_topic = String("battery-emulator_") + String(hostname) + "/spec_data"; @@ -40,7 +43,7 @@ static void publish_cell_voltages(void) { if (datalayer.battery.info.number_of_cells == 0u) { return; } - +#ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; String topic = "homeassistant/sensor/battery-emulator/cell_voltage"; @@ -71,6 +74,7 @@ static void publish_cell_voltages(void) { } doc.clear(); // clear after sending autoconfig } else { +#endif // HA_AUTODISCOVERY // If cell voltages haven't been populated... if (datalayer.battery.info.number_of_cells == 0u || datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) { @@ -87,12 +91,15 @@ static void publish_cell_voltages(void) { if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { #ifdef DEBUG_VIA_USB Serial.println("Cell voltage MQTT msg could not be sent"); -#endif +#endif // DEBUG_VIA_USB } doc.clear(); +#ifdef HA_AUTODISCOVERY } +#endif // HA_AUTODISCOVERY } +#ifdef HA_AUTODISCOVERY struct SensorConfig { const char* object_id; const char* name; @@ -117,12 +124,16 @@ SensorConfig sensorConfigs[] = { static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) { return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config"; } +#endif // HA_AUTODISCOVERY static void publish_common_info(void) { static JsonDocument doc; +#ifdef HA_AUTODISCOVERY static bool mqtt_first_transmission = true; +#endif // HA_AUTODISCOVERY static const char* hostname = WiFi.getHostname(); static String state_topic = String("battery-emulator_") + String(hostname) + "/info"; +#ifdef HA_AUTODISCOVERY if (mqtt_first_transmission == true) { mqtt_first_transmission = false; for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { @@ -149,6 +160,7 @@ static void publish_common_info(void) { } doc.clear(); } else { +#endif // HA_AUTODISCOVERY doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0; doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0; doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0; @@ -167,10 +179,12 @@ static void publish_common_info(void) { if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) { #ifdef DEBUG_VIA_USB Serial.println("Common info MQTT msg could not be sent"); -#endif +#endif // DEBUG_VIA_USB } doc.clear(); +#ifdef HA_AUTODISCOVERY } +#endif // HA_AUTODISCOVERY } /* If we lose the connection, get it back */ @@ -178,7 +192,7 @@ static void reconnect() { // attempt one reconnection #ifdef DEBUG_VIA_USB Serial.print("Attempting MQTT connection... "); -#endif +#endif // DEBUG_VIA_USB const char* hostname = WiFi.getHostname(); char clientId[64]; // Adjust the size as needed snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname); @@ -186,13 +200,13 @@ static void reconnect() { if (client.connect(clientId, mqtt_user, mqtt_password)) { #ifdef DEBUG_VIA_USB Serial.println("connected"); -#endif +#endif // DEBUG_VIA_USB } else { #ifdef DEBUG_VIA_USB Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); -#endif +#endif // DEBUG_VIA_USB // Wait 5 seconds before retrying } } @@ -201,7 +215,7 @@ void init_mqtt(void) { client.setServer(MQTT_SERVER, MQTT_PORT); #ifdef DEBUG_VIA_USB Serial.println("MQTT initialized"); -#endif +#endif // DEBUG_VIA_USB previousMillisUpdateVal = millis(); reconnect(); diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 449f1638..e6867499 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -37,12 +37,16 @@ unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor void init_webserver() { // Configure WiFi +#ifdef WIFIAP if (AccessPointEnabled) { WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection init_WiFi_AP(); } else { WiFi.mode(WIFI_STA); // Only Router connection } +#else + WiFi.mode(WIFI_STA); // Only Router connection +#endif // WIFIAP init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel); String content = index_html; @@ -181,7 +185,7 @@ void init_webserver() { request->send(200, "text/plain", "Updated successfully"); }); -#endif +#endif // TEST_FAKE_BATTERY #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Route for editing ChargerTargetV @@ -260,7 +264,7 @@ void init_webserver() { request->send(400, "text/plain", "Bad Request"); } }); -#endif +#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Send a GET request to /update server.on("/debug", HTTP_GET, @@ -283,22 +287,24 @@ void init_webserver() { #ifdef MQTT // Init MQTT init_mqtt(); -#endif +#endif // MQTT } +#ifdef WIFIAP void init_WiFi_AP() { #ifdef DEBUG_VIA_USB Serial.println("Creating Access Point: " + String(ssidAP)); Serial.println("With password: " + String(passwordAP)); -#endif +#endif // DEBUG_VIA_USB WiFi.softAP(ssidAP, passwordAP); IPAddress IP = WiFi.softAPIP(); #ifdef DEBUG_VIA_USB Serial.println("Access Point created."); Serial.print("IP address: "); Serial.println(IP); -#endif +#endif // DEBUG_VIA_USB } +#endif // WIFIAP String getConnectResultString(wl_status_t status) { switch (status) { @@ -331,7 +337,7 @@ void wifi_monitor() { if (status != WL_CONNECTED && status != WL_IDLE_STATUS) { #ifdef DEBUG_VIA_USB Serial.println(getConnectResultString(status)); -#endif +#endif // DEBUG_VIA_USB if (wifi_state == INIT) { //we haven't been connected yet, try the init logic init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel); } else { //we were connected before, try the reconnect logic @@ -339,7 +345,7 @@ void wifi_monitor() { last_wifi_attempt_time = currentMillis; #ifdef DEBUG_VIA_USB Serial.println("WiFi not connected, trying to reconnect..."); -#endif +#endif // DEBUG_VIA_USB wifi_state = RECONNECTING; WiFi.reconnect(); wifi_reconnect_interval = min(wifi_reconnect_interval * 2, MAX_WIFI_RETRY_INTERVAL); @@ -355,7 +361,7 @@ void wifi_monitor() { Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm"); Serial.println(" Channel: " + String(WiFi.channel())); Serial.println(" Hostname: " + String(WiFi.getHostname())); -#endif +#endif // DEBUG_VIA_USB } } @@ -373,7 +379,7 @@ void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_ch #ifdef DEBUG_VIA_USB Serial.print("Connecting to "); Serial.println(ssid); -#endif +#endif // DEBUG_VIA_USB WiFi.begin(ssid, password, wifi_channel); WiFi.setAutoReconnect(true); // Enable auto reconnect wl_status_t result = static_cast(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT)); @@ -408,10 +414,10 @@ String processor(const String& var) { // Show hardware used: #ifdef HW_LILYGO content += "

Hardware: LilyGo T-CAN485

"; -#endif +#endif // HW_LILYGO #ifdef HW_STARK content += "

Hardware: Stark CMR Module

"; -#endif +#endif // HW_STARK content += "

Uptime: " + uptime_formatter::getUptime() + "

"; #ifdef FUNCTION_TIME_MEASUREMENT // Load information @@ -431,7 +437,7 @@ String processor(const String& var) { content += "

CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us

"; content += "

CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us

"; content += "

OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us

"; -#endif +#endif // FUNCTION_TIME_MEASUREMENT wl_status_t status = WiFi.status(); // Display ssid of network connected to and, if connected to the WiFi, its own IP @@ -453,100 +459,100 @@ String processor(const String& var) { content += "

Inverter protocol: "; #ifdef BYD_CAN content += "BYD Battery-Box Premium HVS over CAN Bus"; -#endif +#endif // BYD_CAN #ifdef BYD_MODBUS content += "BYD 11kWh HVM battery over Modbus RTU"; -#endif +#endif // BYD_MODBUS #ifdef LUNA2000_MODBUS content += "Luna2000 battery over Modbus RTU"; -#endif +#endif // LUNA2000_MODBUS #ifdef PYLON_CAN content += "Pylontech battery over CAN bus"; -#endif +#endif // PYLON_CAN #ifdef SERIAL_LINK_TRANSMITTER content += "Serial link to another LilyGo board"; -#endif +#endif // SERIAL_LINK_TRANSMITTER #ifdef SMA_CAN content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus"; -#endif +#endif // SMA_CAN #ifdef SOFAR_CAN content += "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame) over CAN bus"; -#endif +#endif // SOFAR_CAN #ifdef SOLAX_CAN content += "SolaX Triple Power LFP over CAN bus"; -#endif +#endif // SOLAX_CAN content += "

"; content += "

Battery protocol: "; #ifdef BMW_I3_BATTERY content += "BMW i3"; -#endif +#endif // BMW_I3_BATTERY #ifdef BYD_ATTO_3_BATTERY content += "BYD Atto 3"; -#endif +#endif // BYD_ATTO_3_BATTERY #ifdef CHADEMO_BATTERY content += "Chademo V2X mode"; -#endif +#endif // CHADEMO_BATTERY #ifdef IMIEV_CZERO_ION_BATTERY content += "I-Miev / C-Zero / Ion Triplet"; -#endif +#endif // IMIEV_CZERO_ION_BATTERY #ifdef JAGUAR_IPACE_BATTERY content += "Jaguar I-PACE"; -#endif +#endif // JAGUAR_IPACE_BATTERY #ifdef KIA_HYUNDAI_64_BATTERY content += "Kia/Hyundai 64kWh"; -#endif +#endif // KIA_HYUNDAI_64_BATTERY #ifdef KIA_E_GMP_BATTERY content += "Kia/Hyundai EGMP platform"; -#endif +#endif // KIA_E_GMP_BATTERY #ifdef KIA_HYUNDAI_HYBRID_BATTERY content += "Kia/Hyundai Hybrid"; -#endif +#endif // KIA_HYUNDAI_HYBRID_BATTERY #ifdef MG_5_BATTERY content += "MG 5"; -#endif +#endif // MG_5_BATTERY #ifdef NISSAN_LEAF_BATTERY content += "Nissan LEAF"; -#endif +#endif // NISSAN_LEAF_BATTERY #ifdef RENAULT_KANGOO_BATTERY content += "Renault Kangoo"; -#endif +#endif // RENAULT_KANGOO_BATTERY #ifdef RENAULT_ZOE_GEN1_BATTERY content += "Renault Zoe Gen1 22/40"; -#endif +#endif // RENAULT_ZOE_GEN1_BATTERY #ifdef RENAULT_ZOE_GEN2_BATTERY content += "Renault Zoe Gen2 50"; -#endif +#endif // RENAULT_ZOE_GEN2_BATTERY #ifdef SANTA_FE_PHEV_BATTERY content += "Santa Fe PHEV"; -#endif +#endif // SANTA_FE_PHEV_BATTERY #ifdef SERIAL_LINK_RECEIVER content += "Serial link to another LilyGo board"; -#endif +#endif // SERIAL_LINK_RECEIVER #ifdef TESLA_MODEL_3_BATTERY content += "Tesla Model S/3/X/Y"; -#endif +#endif // TESLA_MODEL_3_BATTERY #ifdef VOLVO_SPA_BATTERY content += "Volvo / Polestar 78kWh battery"; -#endif +#endif // VOLVO_SPA_BATTERY #ifdef TEST_FAKE_BATTERY content += "Fake battery for testing purposes"; -#endif +#endif // TEST_FAKE_BATTERY #ifdef DOUBLE_BATTERY content += " (Double battery)"; -#endif +#endif // DOUBLE_BATTERY content += "

"; #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER content += "

Charger protocol: "; #ifdef CHEVYVOLT_CHARGER content += "Chevy Volt Gen1 Charger"; -#endif +#endif // CHEVYVOLT_CHARGER #ifdef NISSANLEAF_CHARGER content += "Nissan LEAF 2013-2024 PDM charger"; -#endif +#endif // NISSANLEAF_CHARGER content += "

"; -#endif +#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Close the block content += ""; @@ -558,7 +564,7 @@ String processor(const String& var) { #else // Start a new block with a specific background color. Color changes depending on system status content += "
Charger LVDC Output V: " + String(LVvol, 2) + ""; content += "

Charger AC Input V: " + String(ACvol, 2) + " VAC

"; content += "

Charger AC Input I: " + String(ACcur, 2) + " A

"; -#endif +#endif // CHEVYVOLT_CHARGER #ifdef NISSANLEAF_CHARGER float chgPwrDC = static_cast(charger_stat_HVcur * 100); charger_stat_HVcur = chgPwrDC / (datalayer.battery.status.voltage_dV / 10); // P/U=I @@ -772,10 +778,10 @@ String processor(const String& var) { content += "

Charger HVDC Output V: " + String(HVvol, 2) + " V

"; content += "

Charger HVDC Output I: " + String(HVcur, 2) + " A

"; content += "

Charger AC Input V: " + String(ACvol, 2) + " VAC

"; -#endif +#endif // NISSANLEAF_CHARGER // Close the block content += "
"; -#endif +#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER content += ""; content += " "; @@ -829,7 +835,7 @@ void onOTAProgress(size_t current, size_t final) { ota_progress_millis = millis(); #ifdef DEBUG_VIA_USB Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); -#endif +#endif // DEBUG_VIA_USB // Reset the "watchdog" ota_timeout_timer.reset(); } @@ -840,11 +846,11 @@ void onOTAEnd(bool success) { if (success) { #ifdef DEBUG_VIA_USB Serial.println("OTA update finished successfully!"); -#endif +#endif // DEBUG_VIA_USB } else { #ifdef DEBUG_VIA_USB Serial.println("There was an error during OTA update!"); -#endif +#endif // DEBUG_VIA_USB // If we fail without a timeout, try to restore CAN ESP32Can.CANInit(); diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index 93d17fde..b2394d71 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -54,6 +54,7 @@ void init_webserver(); */ void wifi_monitor(); +#ifdef WIFIAP /** * @brief Initialization function that creates a WiFi Access Point. * @@ -62,6 +63,7 @@ void wifi_monitor(); * @return void */ void init_WiFi_AP(); +#endif // WIFIAP /** * @brief Initialization function that connects to an existing network. diff --git a/platformio.ini b/platformio.ini index fe1d94e3..f7cca324 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,8 +14,8 @@ src_dir = ./Software [env:esp32dev] platform = espressif32 platform_packages= - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.2 - framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.2/esp32-arduino-libs-3.0.2.zip + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.4 + framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.4/esp32-arduino-libs-3.0.4.zip board = esp32dev monitor_speed = 115200 monitor_filters = default, time, log2file