From 2411e91f8d9ef4a415a9a9cb323abf155b94e203 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 4 Jun 2026 01:48:26 -0600 Subject: [PATCH] Add serial BLE scan and BMS selection --- firmware/esp32/xterra-controller/bms.cpp | 60 ++++++++++++++++ firmware/esp32/xterra-controller/bms.h | 11 +++ .../xterra-controller/xterra-controller.ino | 71 +++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/firmware/esp32/xterra-controller/bms.cpp b/firmware/esp32/xterra-controller/bms.cpp index 882d63b..d2dddac 100644 --- a/firmware/esp32/xterra-controller/bms.cpp +++ b/firmware/esp32/xterra-controller/bms.cpp @@ -21,6 +21,9 @@ static const unsigned long READ_INTERVAL_MS = 5000; BmsData bmsData; +static BleScanResult bleScanResults[MAX_BLE_SCAN_RESULTS]; +static int bleScanResultCount = 0; + static uint8_t JBD_STATUS_REQUEST[] = { 0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77 }; @@ -62,6 +65,63 @@ static void handleNotify( } } + +int getBleScanResultCount() { + return bleScanResultCount; +} + +const BleScanResult* getBleScanResult(int index) { + if (index < 0 || index >= bleScanResultCount) { + return nullptr; + } + + return &bleScanResults[index]; +} + +int scanBleDevices(uint32_t scanSeconds) { + Serial.println("BLE: Starting scan..."); + + if (client && client->isConnected()) { + Serial.println("BLE: Disconnecting BMS before scan"); + client->disconnect(); + bmsData.connected = false; + } + + bleScanResultCount = 0; + + NimBLEScan* scan = NimBLEDevice::getScan(); + scan->setActiveScan(true); + scan->setInterval(100); + scan->setWindow(99); + + NimBLEScanResults results = scan->getResults(scanSeconds, false); + + int resultCount = results.getCount(); + + for (int i = 0; i < resultCount && bleScanResultCount < MAX_BLE_SCAN_RESULTS; i++) { + const NimBLEAdvertisedDevice* device = results.getDevice(i); + + String address = device->getAddress().toString().c_str(); + String name = ""; + + if (device->haveName()) { + name = device->getName().c_str(); + } + + bleScanResults[bleScanResultCount].address = address; + bleScanResults[bleScanResultCount].name = name; + bleScanResults[bleScanResultCount].rssi = device->getRSSI(); + bleScanResultCount++; + } + + scan->clearResults(); + + Serial.print("BLE: Devices found: "); + Serial.println(bleScanResultCount); + + return bleScanResultCount; +} + static bool connectBms() { if (client && client->isConnected()) { bmsData.connected = true; diff --git a/firmware/esp32/xterra-controller/bms.h b/firmware/esp32/xterra-controller/bms.h index 1136480..c510f32 100644 --- a/firmware/esp32/xterra-controller/bms.h +++ b/firmware/esp32/xterra-controller/bms.h @@ -1,6 +1,13 @@ #pragma once #define BMS_MAX_CELLS 8 +#define MAX_BLE_SCAN_RESULTS 10 + +struct BleScanResult { + String name; + String address; + int rssi = 0; +}; struct BmsData { bool connected = false; @@ -28,3 +35,7 @@ extern BmsData bmsData; void initBms(); void updateBms(); + +int scanBleDevices(uint32_t scanSeconds = 10); +int getBleScanResultCount(); +const BleScanResult* getBleScanResult(int index); diff --git a/firmware/esp32/xterra-controller/xterra-controller.ino b/firmware/esp32/xterra-controller/xterra-controller.ino index 20807d2..98f70af 100644 --- a/firmware/esp32/xterra-controller/xterra-controller.ino +++ b/firmware/esp32/xterra-controller/xterra-controller.ino @@ -355,6 +355,77 @@ void handleDebugSerial() { return; } + if (command == "scan ble") { + int count = scanBleDevices(10); + + if (count == 0) { + Serial.println("No BLE devices found."); + return; + } + + Serial.println("BLE devices:"); + + for (int i = 0; i < count; i++) { + const BleScanResult* result = getBleScanResult(i); + + if (!result) { + continue; + } + + Serial.print(i + 1); + Serial.print(") "); + + if (result->name.length() > 0) { + Serial.print(result->name); + } else { + Serial.print("(no name)"); + } + + Serial.print(" | "); + Serial.print(result->address); + Serial.print(" | RSSI "); + Serial.println(result->rssi); + } + + Serial.println("Use: select bms "); + return; + } + + if (command.startsWith("select bms ")) { + int selected = command.substring(11).toInt(); + + if (selected < 1 || selected > getBleScanResultCount()) { + Serial.println("Invalid BMS selection. Run: scan ble"); + return; + } + + const BleScanResult* result = getBleScanResult(selected - 1); + + if (!result) { + Serial.println("Invalid BMS selection. Run: scan ble"); + return; + } + + appConfig.bms.enabled = true; + appConfig.bms.address = result->address; + appConfig.bms.addressType = "public"; + + if (result->name.length() > 0) { + appConfig.bms.name = result->name; + } else { + appConfig.bms.name = "BMS"; + } + + saveConfig(); + + Serial.print("OK selected BMS: "); + Serial.print(appConfig.bms.name); + Serial.print(" / "); + Serial.println(appConfig.bms.address); + Serial.println("BMS will reconnect on next update cycle."); + return; + } + if (command.length() > 0) { Serial.print("Unknown command: "); Serial.println(command);