From 817db651ce069c8b10f345dd77d8ce73c85aa543 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 4 Jun 2026 00:59:51 -0600 Subject: [PATCH] Add ESP32 alarms system status and debug commands --- firmware/esp32/xterra-controller/alarms.cpp | 13 ++++ firmware/esp32/xterra-controller/alarms.h | 14 ++++ firmware/esp32/xterra-controller/bms.cpp | 15 +++++ .../esp32/xterra-controller/system_status.h | 4 ++ .../xterra-controller/xterra-controller.ino | 65 +++++++++++++++++++ 5 files changed, 111 insertions(+) create mode 100644 firmware/esp32/xterra-controller/alarms.cpp create mode 100644 firmware/esp32/xterra-controller/alarms.h create mode 100644 firmware/esp32/xterra-controller/system_status.h diff --git a/firmware/esp32/xterra-controller/alarms.cpp b/firmware/esp32/xterra-controller/alarms.cpp new file mode 100644 index 0000000..69b8991 --- /dev/null +++ b/firmware/esp32/xterra-controller/alarms.cpp @@ -0,0 +1,13 @@ +#include "alarms.h" +#include "bms.h" + +AlarmState alarms; + +void updateAlarms() { + alarms.lowSoc = bmsData.valid && bmsData.soc < 20; + alarms.criticalSoc = bmsData.valid && bmsData.soc < 10; + alarms.lowVoltage = bmsData.valid && bmsData.voltage < 12.0; + alarms.highBatteryTemp = bmsData.valid && bmsData.temperatureF > 120.0; + alarms.cellImbalance = bmsData.cellsValid && bmsData.cellDeltaMv > 20; + alarms.bmsDisconnected = !bmsData.connected; +} diff --git a/firmware/esp32/xterra-controller/alarms.h b/firmware/esp32/xterra-controller/alarms.h new file mode 100644 index 0000000..30ab805 --- /dev/null +++ b/firmware/esp32/xterra-controller/alarms.h @@ -0,0 +1,14 @@ +#pragma once + +struct AlarmState { + bool lowSoc = false; + bool criticalSoc = false; + bool lowVoltage = false; + bool highBatteryTemp = false; + bool cellImbalance = false; + bool bmsDisconnected = false; +}; + +extern AlarmState alarms; + +void updateAlarms(); diff --git a/firmware/esp32/xterra-controller/bms.cpp b/firmware/esp32/xterra-controller/bms.cpp index 0b8a391..882d63b 100644 --- a/firmware/esp32/xterra-controller/bms.cpp +++ b/firmware/esp32/xterra-controller/bms.cpp @@ -64,9 +64,19 @@ static void handleNotify( static bool connectBms() { if (client && client->isConnected()) { + bmsData.connected = true; return true; } + if (client && !client->isConnected()) { + Serial.println("BMS: Disconnected, resetting client"); + NimBLEDevice::deleteClient(client); + client = nullptr; + notifyChar = nullptr; + writeChar = nullptr; + bmsData.connected = false; + } + Serial.println("BMS: Connecting..."); NimBLEAddress address(std::string(BMS_ADDRESS), BLE_ADDR_PUBLIC); @@ -76,6 +86,8 @@ static bool connectBms() { if (!client->connect(address)) { Serial.println("BMS: Connect failed"); bmsData.connected = false; + NimBLEDevice::deleteClient(client); + client = nullptr; return false; } @@ -116,6 +128,9 @@ static bool requestPacket(uint8_t* command, size_t commandLength) { if (!ok) { Serial.println("BMS: Write failed"); bmsData.connected = false; + if (client) { + client->disconnect(); + } return false; } diff --git a/firmware/esp32/xterra-controller/system_status.h b/firmware/esp32/xterra-controller/system_status.h new file mode 100644 index 0000000..8321dc5 --- /dev/null +++ b/firmware/esp32/xterra-controller/system_status.h @@ -0,0 +1,4 @@ +#pragma once + +#define FIRMWARE_VERSION "0.3.0" +#define FIRMWARE_NAME "xterra-controller" diff --git a/firmware/esp32/xterra-controller/xterra-controller.ino b/firmware/esp32/xterra-controller/xterra-controller.ino index d96d246..ce5f109 100644 --- a/firmware/esp32/xterra-controller/xterra-controller.ino +++ b/firmware/esp32/xterra-controller/xterra-controller.ino @@ -7,6 +7,8 @@ #include "relays.h" #include "sensors.h" #include "bms.h" +#include "alarms.h" +#include "system_status.h" WebServer server(80); HardwareSerial DashboardSerial(2); @@ -56,6 +58,7 @@ void buildStatusDocument(JsonDocument& doc) { battery["cell_min_voltage"] = bmsData.cellMinVoltage; battery["cell_max_voltage"] = bmsData.cellMaxVoltage; battery["cell_delta_mv"] = bmsData.cellDeltaMv; + battery["cells_valid"] = bmsData.cellsValid; } else { battery["source"] = "placeholder"; battery["connected"] = false; @@ -113,6 +116,21 @@ void buildStatusDocument(JsonDocument& doc) { JsonObject network = doc.createNestedObject("network"); network["wifi_enabled"] = true; network["uart_connected"] = true; + + JsonObject alarmObj = doc.createNestedObject("alarms"); + alarmObj["low_soc"] = alarms.lowSoc; + alarmObj["critical_soc"] = alarms.criticalSoc; + alarmObj["low_voltage"] = alarms.lowVoltage; + alarmObj["high_battery_temp"] = alarms.highBatteryTemp; + alarmObj["cell_imbalance"] = alarms.cellImbalance; + alarmObj["bms_disconnected"] = alarms.bmsDisconnected; + + JsonObject system = doc.createNestedObject("system"); + system["firmware_name"] = FIRMWARE_NAME; + system["firmware_version"] = FIRMWARE_VERSION; + system["build_date"] = __DATE__; + system["build_time"] = __TIME__; + system["uptime_seconds"] = millis() / 1000; } void sendStatus(Stream& output, bool pretty = false) { @@ -214,6 +232,51 @@ void pollDashboardUart() { } } + +void handleDebugSerial() { + if (!Serial.available()) { + return; + } + + String command = Serial.readStringUntil('\n'); + command.trim(); + + if (command == "status") { + sendStatus(Serial, true); + return; + } + + if (command == "relay starlink on") { + setRelayByName("starlink", true); + Serial.println("OK starlink on"); + return; + } + + if (command == "relay starlink off") { + setRelayByName("starlink", false); + Serial.println("OK starlink off"); + return; + } + + if (command == "relay fridge on") { + setRelayByName("fridge", true); + Serial.println("OK fridge on"); + return; + } + + if (command == "relay fridge off") { + setRelayByName("fridge", false); + Serial.println("OK fridge off"); + return; + } + + if (command.length() > 0) { + Serial.print("Unknown command: "); + Serial.println(command); + Serial.println("Commands: status, relay starlink on/off, relay fridge on/off"); + } +} + void handleStatus() { DynamicJsonDocument doc(2048); buildStatusDocument(doc); @@ -305,7 +368,9 @@ void setup() { void loop() { server.handleClient(); + handleDebugSerial(); updateBms(); + updateAlarms(); pollDashboardUart(); static unsigned long lastSensorUpdate = 0;