From c7602f0eb9ed6c6b9e3269c4b8bccd5549f7dc2f Mon Sep 17 00:00:00 2001 From: James Date: Thu, 25 Apr 2024 21:28:40 +0100 Subject: [PATCH] new --- ESPBMS.ino | 774 ++++++++---------------------------------------- old/ESPBMS.ino | 592 ++++++++++++++++++++++++++++++++++++ test/ESPBMS.ino | 66 ----- 3 files changed, 715 insertions(+), 717 deletions(-) create mode 100644 old/ESPBMS.ino delete mode 100644 test/ESPBMS.ino diff --git a/ESPBMS.ino b/ESPBMS.ino index 76530a3..181d3ec 100644 --- a/ESPBMS.ino +++ b/ESPBMS.ino @@ -1,651 +1,123 @@ - -// ==== CONFIGURATION ==== -// BMS -#define BMS_MAX_CELLS 15 // defines size of data types -#define BMS_POLLING_INTERVAL 10*60*1000 // data output interval (shorter = connect more often = more battery consumption from BMS) in ms - -// BLE -#define BLE_NAME "BMS 1.1" // name of BMS - -#define BLE_SCAN_DURATION 1 // duration of scan in seconds -#define BLE_REQUEST_DELAY 500 // package request delay after connecting - make this large enough to have the connection established in ms -#define BLE_TIMEOUT 600*1000 // timeout of scan + gathering packets (too short will fail collecting all packets) in ms - -#define BLE_CALLBACK_DEBUG true // send debug messages via MQTT & serial in callbacks (handy for finding your BMS address, name, RSSI, etc) - -// ==== MAIN CODE ==== -#include "datatypes.h" // for brevity the BMS stuff is in this file -#include // for BLE - -#include // to read ESP battery voltage -#include // to get reset reason - - -// Init BMS -static BLEUUID serviceUUID("0000ff00-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms service -static BLEUUID charUUID_rx("0000ff01-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms rx id -static BLEUUID charUUID_tx("0000ff02-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms tx id - -const byte cBasicInfo = 3; //datablock 3=basic info -const byte cCellInfo = 4; //datablock 4=individual cell info -packBasicInfoStruct packBasicInfo; -packCellInfoStruct packCellInfo; -unsigned long bms_last_update_time=0; -bool bms_status; -#define BLE_PACKETSRECEIVED_BEFORE_STANDBY 0b11 // packets to gather before disconnecting - -// Other stuff -float battery_voltage=0; // internal battery voltage -String debug_log_string=""; -hw_timer_t * wd_timer = NULL; - -void debug(String s){ - Serial.println(s); -} - -void setup(){ - Serial.begin(115200); -} - - -// === Main stuff ==== -void loop(){ - bleGatherPackets(); -} - -BLEScan* pBLEScan = nullptr; -BLEClient* pClient = nullptr; -BLEAdvertisedDevice* pRemoteDevice = nullptr; -BLERemoteService* pRemoteService = nullptr; -BLERemoteCharacteristic* pRemoteCharacteristic_rx = nullptr; -BLERemoteCharacteristic* pRemoteCharacteristic_tx = nullptr; - -boolean doScan = false; // becomes true when BLE is initialized and scanning is allowed -boolean doConnect = false; // becomes true when correct ID is found during scanning - -boolean ble_client_connected = false; // true when fully connected - -unsigned int ble_packets_requested = 0b00; // keeps track of requested packets -unsigned int ble_packets_received = 0b00; // keeps track of received packets - -void MyEndOfScanCallback(BLEScanResults pBLEScanResult){ - bms_status=false; // BMS not found - - if(BLE_CALLBACK_DEBUG){ - debug("BLE: scan finished"); - Serial.println("Scan finished."); - } -} - -class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks{ - // called for each advertising BLE server - - void onResult(BLEAdvertisedDevice advertisedDevice){ - // found a device - - - if(BLE_CALLBACK_DEBUG){ - debug( - String("BLE: found ") + - String(advertisedDevice.getName().c_str()) + - String(" with address ") + - String(advertisedDevice.getAddress().toString().c_str()) + - String(" and RSSI ") + - String(advertisedDevice.getRSSI()) - ); - - Serial.print("BLE: found "); - Serial.println(advertisedDevice.toString().c_str()); - } - - // Check if device is advertising the specific service UUID - if (!advertisedDevice.isAdvertisingService(serviceUUID)) { - debug("Device does not advertise the specified service UUID."); - return; - } - - if(BLE_CALLBACK_DEBUG){ - debug("BLE: target device found"); - } - - pBLEScan->stop(); - - // delete old remote device, create new one - if(pRemoteDevice != nullptr){ - delete pRemoteDevice; - } - pRemoteDevice = new BLEAdvertisedDevice(advertisedDevice); - - doConnect = true; - } -}; - -class MyClientCallback : public BLEClientCallbacks{ - // called on connect/disconnect - void onConnect(BLEClient* pclient){ - - if(BLE_CALLBACK_DEBUG){ - debug(String("BLE: connecting to ") + String(pclient->getPeerAddress().toString().c_str())); - } - } - - void onDisconnect(BLEClient* pclient){ - ble_client_connected = false; - doConnect = false; - - if(BLE_CALLBACK_DEBUG){ - debug(String("BLE: disconnected from ") + String(pclient->getPeerAddress().toString().c_str())); - } - - } -}; - -static void MyNotifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify){ - //this is called when BLE server sents data via notification - //hexDump((char*)pData, length); - if(!bleCollectPacket((char *)pData, length)){ - debug("ERROR: packet could not be collected."); - } -} - -void handleBLE(){ - static unsigned long prev_millis_standby = 0; - - prev_millis_standby = millis(); - - while(true){ // loop until we hit a timeout or gathered all packets - - if((ble_packets_received == BLE_PACKETSRECEIVED_BEFORE_STANDBY) || (millis()>prev_millis_standby+BLE_TIMEOUT)){ - if(ble_packets_received == BLE_PACKETSRECEIVED_BEFORE_STANDBY){ - debug("BLE: all packets received"); - bms_status=true; // BMS was connected, data up-to-date - printBasicInfo(); - printCellInfo(); - }else{ - debug("BLE: connection timeout"); - bms_status=false; // BMS not (fully) connected - } - - break; // we're done with BLE, exit while loop - } - else if (doConnect){ - - // found the desired BLE server, now connect to it - if (connectToServer()){ - ble_client_connected = true; - ble_packets_received=0; - ble_packets_requested=0; - - }else{ - ble_client_connected = false; - debug("BLE: failed to connect"); - } - - doConnect = false; - } - - if (ble_client_connected){ - debug("BLE: requesting packet 0b01"); - delay(5000); - bmsRequestBasicInfo(); - debug("BLE: requesting packet 0b10"); - delay(5000); - bmsRequestCellInfo(); - }else if ((!doConnect)&&(doScan)){ - // we are not connected, so we can scan for devices - debug("BLE: not connected, starting scan"); - Serial.print("BLE is not connected, starting scan"); - - // Disconnect client - if((pClient != nullptr)&&(pClient->isConnected())){ - pClient->disconnect(); - } - - // stop scan (if running) and start a new one - pBLEScan->setActiveScan(true); - pBLEScan->setInterval(1 << 8); // 160 ms - pBLEScan->setWindow(1 << 7); // 80 ms - pBLEScan->start(BLE_SCAN_DURATION, MyEndOfScanCallback, false); // non-blocking, use a callback - - doScan=false; - - debug("BLE: scan started"); - } - } -} - -void bleGatherPackets(){ - bleStart(); - handleBLE(); - blePause(); - BLEDevice::deinit(false); -} - -void bleStart(){ - Serial.print("Starting BLE... "); - - BLEDevice::init(""); - //esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); // release some unused memory - - // Retrieve a BLE client - pClient = BLEDevice::createClient(); - pClient->setClientCallbacks(new MyClientCallback()); - - // Retrieve a BLE scanner - pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); - - bleContinue(); - Serial.println("done"); -} - -void blePause(){ - // stop scanning and disconnect from all devices - doScan=false; - - // Disconnect client - if((pClient != nullptr)&&(pClient->isConnected())){ - pClient->disconnect(); - } - - delay(50); - - pBLEScan->stop(); - - ble_client_connected=false; - doConnect=false; - ble_packets_received=0; - ble_packets_requested=0; - -} - -void bleContinue(){ - // Prepare for scanning - ble_client_connected=false; - doConnect=false; - ble_packets_received=0; - - doScan=true; // start scanning for new devices -} - -bool connectToServer(){ - if(pRemoteDevice==nullptr){ - Serial.println("Invalid remote device, can't connect"); - return false; - } - - // Disconnect client - if((pClient != nullptr)&&(pClient->isConnected())){ - pClient->disconnect(); - } - - Serial.print("Forming a connection to "); - Serial.println(pRemoteDevice->getAddress().toString().c_str()); - - delay(100); - - // Connect to the remote BLE Server. - pClient->connect(pRemoteDevice); - if(!(pClient->isConnected())){ - debug(String("BLE: failed to connect")); - Serial.println("Failed to connect to server"); - pClient->disconnect(); - return false; - } - - Serial.println(" - Connected to server"); - - - // Get remote service - pRemoteService = pClient->getService(serviceUUID); - if (pRemoteService == nullptr){ - debug(String("BLE: failed to find service UUID")); - Serial.print("Failed to find our service UUID: "); - Serial.println(serviceUUID.toString().c_str()); - pClient->disconnect(); - return false; - } - Serial.println(" - Found our service"); - - - // Get BMS receive characteristic - pRemoteCharacteristic_rx = pRemoteService->getCharacteristic(charUUID_rx); - if (pRemoteCharacteristic_rx == nullptr){ - debug(String("BLE: failed to find RX UUID")); - Serial.print("Failed to find rx UUID: "); - Serial.println(charUUID_rx.toString().c_str()); - pClient->disconnect(); - return false; - } - Serial.println(" - Found RX characteristic"); - - - // Register callback for remote characteristic (receive channel) - if (pRemoteCharacteristic_rx->canNotify()){ - pRemoteCharacteristic_rx->registerForNotify(MyNotifyCallback); - }else{ - debug(String("BLE: failed to register notification of remote characteristic")); - Serial.println("Failed to register notification of remote characteristic"); - pClient->disconnect(); - return false; - } - Serial.println(" - Registered remote characteristic for notification"); - - - // Get BMS transmit characteristic - pRemoteCharacteristic_tx = pRemoteService->getCharacteristic(charUUID_tx); - if (pRemoteCharacteristic_tx == nullptr){ - debug(String("BLE: failed to find TX UUID")); - Serial.print("Failed to find tx UUID: "); - Serial.println(charUUID_tx.toString().c_str()); - pClient->disconnect(); - return false; - } - Serial.println(" - Found TX characteristic"); - - - // Check whether tx is writeable - if (!(pRemoteCharacteristic_tx->canWriteNoResponse())){ - debug(String("BLE: failed TX remote characteristic is not writable")); - Serial.println("Failed TX remote characteristic is not writable"); - pClient->disconnect(); - return false; - } - Serial.println(" - TX is writable"); - - - delay(BLE_REQUEST_DELAY); // wait, otherwise writeValue doesn't work for some reason - // to do: fix this ugly hack - - debug(String("BLE: connected")); - - return true; -} - -bool sendCommand(uint8_t *data, uint32_t dataLen){ - if((pClient!=nullptr)&&(pClient->isConnected())){ - pRemoteCharacteristic_tx->writeValue(data, dataLen, false); - return true; - }else{ - return false; - } -} - -bool isPacketValid(byte *packet) //check if packet is valid -{ - if (packet == nullptr){ - return false; - } - - bmsPacketHeaderStruct *pHeader = (bmsPacketHeaderStruct *)packet; - int checksumPos = pHeader->dataLen + 2; // status + data len + data - - int offset = 2; // header 0xDD and command type are not in data length - - if (packet[0] != 0xDD){ - // start bit missing - return false; - } - - if (packet[offset + checksumPos + 2] != 0x77){ - // stop bit missing - return false; - } - - byte checksum = 0; - for (int i = 0; i < checksumPos; i++){ - checksum += packet[offset + i]; - } - checksum = ((checksum ^ 0xFF) + 1) & 0xFF; - - if (checksum != packet[offset + checksumPos + 1]){ - return false; - } - - return true; -} - -bool processBasicInfo(packBasicInfoStruct *output, byte *data, unsigned int dataLen) -{ - // Expected data len - if (dataLen != 0x1B) - { - Serial.printf("bad data len %d!\r\n",dataLen); - //return false; - } - - output->Volts = ((uint32_t)two_ints_into16(data[0], data[1])) * 10; // Resolution 10 mV -> convert to milivolts eg 4895 > 48950mV - output->Amps = ((int32_t)two_ints_into16(data[2], data[3])) * 10; // Resolution 10 mA -> convert to miliamps - - output->Watts = output->Volts * output->Amps / 1000000; // W - - output->CapacityRemainAh = ((uint16_t)two_ints_into16(data[4], data[5])) * 10; - output->CapacityRemainPercent = ((uint8_t)data[19]); - - output->Temp1 = (((uint16_t)two_ints_into16(data[23], data[24])) - 2731); - output->Temp2 = (((uint16_t)two_ints_into16(data[25], data[26])) - 2731); - output->BalanceCodeLow = (two_ints_into16(data[12], data[13])); - output->BalanceCodeHigh = (two_ints_into16(data[14], data[15])); - output->MosfetStatus = ((byte)data[20]); - - printBasicInfo(); - - return true; -} - -bool processCellInfo(packCellInfoStruct *output, byte *data, unsigned int dataLen) -{ - uint16_t _cellSum; - uint16_t _cellMin = 5000; - uint16_t _cellMax = 0; - uint16_t _cellAvg; - uint16_t _cellDiff; - - output->NumOfCells = dataLen / 2; // data contains 2 bytes per cell - - //go trough individual cells - for (byte i = 0; i < dataLen / 2; i++) - { - output->CellVolt[i] = ((uint16_t)two_ints_into16(data[i * 2], data[i * 2 + 1])); // Resolution 1 mV - _cellSum += output->CellVolt[i]; - if (output->CellVolt[i] > _cellMax) - { - _cellMax = output->CellVolt[i]; - } - if (output->CellVolt[i] < _cellMin) - { - _cellMin = output->CellVolt[i]; - } - } - - output->CellMin = _cellMin; - output->CellMax = _cellMax; - output->CellDiff = _cellMax - _cellMin; - output->CellAvg = _cellSum / output->NumOfCells; - - printCellInfo(); - - return true; -} - -bool bmsProcessPacket(byte *packet) -{ - bool isValid = isPacketValid(packet); - - if (isValid != true) - { - Serial.println("Invalid packer received"); - return false; - } - - bmsPacketHeaderStruct *pHeader = (bmsPacketHeaderStruct *)packet; - byte *data = packet + sizeof(bmsPacketHeaderStruct); // TODO Fix this ugly hack - unsigned int dataLen = pHeader->dataLen; - - bool result = false; - - // find packet type (basic info or cell info) - switch (pHeader->type) - { - case cBasicInfo: - { - // Process basic info - result = processBasicInfo(&packBasicInfo, data, dataLen); - if(result==true){ - ble_packets_received |= 0b01; - bms_last_update_time=millis(); - } - - break; - } - - case cCellInfo: - { - // Process cell info - result = processCellInfo(&packCellInfo, data, dataLen); - if(result==true){ - ble_packets_received |= 0b10; - bms_last_update_time=millis(); - } - break; - } - - default: - result = false; - Serial.printf("Unsupported packet type detected. Type: %d", pHeader->type); - } - - return result; -} - -bool bleCollectPacket(char *data, uint32_t dataSize) // reconstruct packet, called by notifyCallback function -{ - static uint8_t packetstate = 0; //0 - empty, 1 - first half of packet received, 2- second half of packet received - - // packet sizes: - // (packet ID 03) = 4 (header) + 23 + 2*N_NTCs + 2 (checksum) + 1 (stop) - // (packet ID 04) = 4 (header) + 2*NUM_CELLS + 2 (checksum) + 1 (stop) - static uint8_t packetbuff[4 + 2*25 + 2 + 1] = {0x0}; // buffer size suitable for up to 25 cells - - static uint32_t totalDataSize = 0; - bool retVal = false; - hexDump(data,dataSize); - - if(totalDataSize + dataSize > sizeof(packetbuff)){ - Serial.printf("ERROR: datasize is overlength."); - - debug( - String("ERROR: datasize is overlength. ") + - String("allocated=") + - String(sizeof(packetbuff)) + - String(", size=") + - String(totalDataSize + dataSize) - ); - - totalDataSize = 0; - packetstate = 0; - - retVal = false; - } - else if (data[0] == 0xdd && packetstate == 0) // probably got 1st half of packet - { - Serial.println("PKT1"); - packetstate = 1; - for (uint8_t i = 0; i < dataSize; i++) - { - packetbuff[i] = data[i]; - } - totalDataSize = dataSize; - retVal = true; - - if (data[dataSize - 1] == 0x77) { - //its full packets - packetstate = 2; - } - } - else if (data[dataSize - 1] == 0x77 && packetstate == 1) //probably got 2nd half of the packet - { - Serial.println("PKT2"); - packetstate = 2; - for (uint8_t i = 0; i < dataSize; i++) - { - packetbuff[i + totalDataSize] = data[i]; - } - totalDataSize += dataSize; - retVal = true; - } - - if (packetstate == 2) //got full packet - { - Serial.println("PKT3"); - uint8_t packet[totalDataSize]; - memcpy(packet, packetbuff, totalDataSize); - - bmsProcessPacket(packet); //pass pointer to retrieved packet to processing function - packetstate = 0; - totalDataSize = 0; - retVal = true; - } - return retVal; -} - -bool bmsRequestBasicInfo(){ - // header status command length data checksum footer - // DD A5 03 00 FF FD 77 - uint8_t data[7] = {0xdd, 0xa5, cBasicInfo, 0x0, 0xff, 0xfd, 0x77}; - return sendCommand(data, sizeof(data)); -} - -bool bmsRequestCellInfo(){ - // header status command length data checksum footer - // DD A5 04 00 FF FC 77 - uint8_t data[7] = {0xdd, 0xa5, cCellInfo, 0x0, 0xff, 0xfc, 0x77}; - return sendCommand(data, sizeof(data)); -} - -void printBasicInfo() //debug all data to uart -{ - Serial.printf("Total voltage: %f\r\n", (float)packBasicInfo.Volts / 1000); - Serial.printf("Amps: %f\r\n", (float)packBasicInfo.Amps / 1000); - Serial.printf("CapacityRemainAh: %f\r\n", (float)packBasicInfo.CapacityRemainAh / 1000); - Serial.printf("CapacityRemainPercent: %d\r\n", packBasicInfo.CapacityRemainPercent); - Serial.printf("Temp1: %f\r\n", (float)packBasicInfo.Temp1 / 10); - Serial.printf("Temp2: %f\r\n", (float)packBasicInfo.Temp2 / 10); - Serial.printf("Balance Code Low: 0x%x\r\n", packBasicInfo.BalanceCodeLow); - Serial.printf("Balance Code High: 0x%x\r\n", packBasicInfo.BalanceCodeHigh); - Serial.printf("Mosfet Status: 0x%x\r\n", packBasicInfo.MosfetStatus); -} - -void printCellInfo() //debug all data to uart -{ - Serial.printf("Number of cells: %u\r\n", packCellInfo.NumOfCells); - for (byte i = 1; i <= packCellInfo.NumOfCells; i++) - { - Serial.printf("Cell no. %u", i); - Serial.printf(" %f\r\n", (float)packCellInfo.CellVolt[i - 1] / 1000); - } - Serial.printf("Max cell volt: %f\r\n", (float)packCellInfo.CellMax / 1000); - Serial.printf("Min cell volt: %f\r\n", (float)packCellInfo.CellMin / 1000); - Serial.printf("Difference cell volt: %f\r\n", (float)packCellInfo.CellDiff / 1000); - Serial.printf("Average cell volt: %f\r\n", (float)packCellInfo.CellAvg / 1000); - Serial.println(); -} - -void hexDump(const char *data, uint32_t dataSize) //debug function -{ - Serial.println("HEX data:"); - - for (int i = 0; i < dataSize; i++) - { - Serial.printf("0x%x, ", data[i]); - } - Serial.println(""); -} - -int16_t two_ints_into16(int highbyte, int lowbyte) // turns two bytes into a single long integer -{ - int16_t result = (highbyte); - result <<= 8; //Left shift 8 bits, - result = (result | lowbyte); //OR operation, merge the two - return result; -} +#include "BLEDevice.h" + +static BLEUUID serviceUUID("0000ff00-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms service +static BLEUUID charUUID_rx("0000ff01-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms rx id +static BLEUUID charUUID_tx("0000ff02-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms tx id + +void setup() { + Serial.begin(115200); + BLEDevice::init(""); // Initialize BLE device +} + +void loop() { + BLEScan* pBLEScan = BLEDevice::getScan(); // Create new scan + pBLEScan->setActiveScan(true); // Active scan uses more power, but get results faster + BLEScanResults foundDevices = pBLEScan->start(30); // Scan for 30 seconds + + Serial.println("Scanning completed"); + Serial.println("Devices found: " + String(foundDevices.getCount())); + + for (int i = 0; i < foundDevices.getCount(); i++) { + BLEAdvertisedDevice advertisedDevice = foundDevices.getDevice(i); + Serial.println("\nFound Device: " + String(advertisedDevice.toString().c_str())); + + if(!advertisedDevice.isAdvertisingService(serviceUUID)) { + Serial.println("Device does not advertise the specified service UUID."); + continue; + } + + BLEClient* pClient = BLEDevice::createClient(); + Serial.println("Connecting to: " + String(advertisedDevice.getAddress().toString().c_str())); + + if(!pClient->connect(&advertisedDevice)) { + Serial.println("Failed to connect."); + continue; + } + + // Get remote service + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr){ + Serial.println(String("BLE: failed to find service UUID")); + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + pClient->disconnect(); + continue; + } + Serial.println(" - Found our service"); + + // Get BMS receive characteristic + BLERemoteCharacteristic* pRemoteCharacteristic_rx = pRemoteService->getCharacteristic(charUUID_rx); + if (pRemoteCharacteristic_rx == nullptr){ + Serial.println(String("BLE: failed to find RX UUID")); + Serial.print("Failed to find rx UUID: "); + Serial.println(charUUID_rx.toString().c_str()); + pClient->disconnect(); + continue; + } + Serial.println(" - Found RX characteristic"); + + // Register callback for remote characteristic (receive channel) + if (pRemoteCharacteristic_rx->canNotify()){ + pRemoteCharacteristic_rx->registerForNotify(MyNotifyCallback); + }else{ + Serial.println(String("BLE: failed to register notification of remote characteristic")); + Serial.println("Failed to register notification of remote characteristic"); + pClient->disconnect(); + continue; + } + Serial.println(" - Registered remote characteristic for notification"); + + // Get BMS transmit characteristic + BLERemoteCharacteristic* pRemoteCharacteristic_tx = pRemoteService->getCharacteristic(charUUID_tx); + if (pRemoteCharacteristic_tx == nullptr){ + Serial.println(String("BLE: failed to find TX UUID")); + Serial.print("Failed to find tx UUID: "); + Serial.println(charUUID_tx.toString().c_str()); + pClient->disconnect(); + continue; + } + Serial.println(" - Found TX characteristic"); + + // Check whether tx is writeable + if (!(pRemoteCharacteristic_tx->canWriteNoResponse())){ + Serial.println(String("BLE: failed TX remote characteristic is not writable")); + Serial.println("Failed TX remote characteristic is not writable"); + pClient->disconnect(); + continue; + } + Serial.println(" - TX is writable"); + + // REQUEST BASIC INFO + // header status command length data checksum footer + // DD A5 03 00 FF FD 77 + uint8_t a_data[7] = {0xdd, 0xa5, 3, 0x0, 0xff, 0xfd, 0x77}; + pRemoteCharacteristic_tx->writeValue(a_data, sizeof(a_data), false); + + // REQUEST CELL INFO + // header status command length data checksum footer + // DD A5 03 00 FF FD 77 + uint8_t b_data[7] = {0xdd, 0xa5, 4, 0x0, 0xff, 0xfc, 0x77}; + pRemoteCharacteristic_tx->writeValue(b_data, sizeof(b_data), false); + + while(1){ + } + } +} + +static void MyNotifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify){ + hexDump((char*)pData, length); + /*if(!bleCollectPacket((char *)pData, length)){ + Serial.println("ERROR: packet could not be collected."); +}*/ +} + +void hexDump(const char *data, uint32_t dataSize) +{ + Serial.println("HEX data:"); + + for (int i = 0; i < dataSize; i++) + { + Serial.printf("0x%x, ", data[i]); + } + Serial.println(""); +} diff --git a/old/ESPBMS.ino b/old/ESPBMS.ino new file mode 100644 index 0000000..1e5a6a8 --- /dev/null +++ b/old/ESPBMS.ino @@ -0,0 +1,592 @@ + +// ==== CONFIGURATION ==== +// BMS +#define BMS_MAX_CELLS 15 // defines size of data types +#define BMS_POLLING_INTERVAL 10*60*1000 // data output interval (shorter = connect more often = more battery consumption from BMS) in ms + +// BLE +#define BLE_SCAN_DURATION 1 // duration of scan in seconds +#define BLE_REQUEST_DELAY 500 // package request delay after connecting - make this large enough to have the connection established in ms +#define BLE_TIMEOUT 600*1000 // timeout of scan + gathering packets (too short will fail collecting all packets) in ms + +#define BLE_CALLBACK_DEBUG true // send debug messages via MQTT & serial in callbacks (handy for finding your BMS address, name, RSSI, etc) + +// ==== MAIN CODE ==== +#include "datatypes.h" // for brevity the BMS stuff is in this file +#include // for BLE + +#include // to read ESP battery voltage +#include // to get reset reason + + +// Init BMS +static BLEUUID serviceUUID("0000ff00-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms service +static BLEUUID charUUID_rx("0000ff01-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms rx id +static BLEUUID charUUID_tx("0000ff02-0000-1000-8000-00805f9b34fb"); //xiaoxiang bms tx id + +const byte cBasicInfo = 3; //datablock 3=basic info +const byte cCellInfo = 4; //datablock 4=individual cell info +packBasicInfoStruct packBasicInfo; +packCellInfoStruct packCellInfo; +unsigned long bms_last_update_time=0; +bool bms_status; +#define BLE_PACKETSRECEIVED_BEFORE_STANDBY 0b11 // packets to gather before disconnecting + +// Other stuff +float battery_voltage=0; // internal battery voltage +String debug_log_string=""; +hw_timer_t * wd_timer = NULL; + +void debug(String s){ + Serial.println(s); +} + +void setup(){ + Serial.begin(115200); +} + + +// === Main stuff ==== +void loop(){ + bleGatherPackets(); +} + +BLEScan* pBLEScan = nullptr; +BLEClient* pClient = nullptr; +BLEAdvertisedDevice* pRemoteDevice = nullptr; +BLERemoteService* pRemoteService = nullptr; +BLERemoteCharacteristic* pRemoteCharacteristic_rx = nullptr; +BLERemoteCharacteristic* pRemoteCharacteristic_tx = nullptr; + +boolean doScan = false; // becomes true when BLE is initialized and scanning is allowed +boolean doConnect = false; // becomes true when correct ID is found during scanning + +boolean ble_client_connected = false; // true when fully connected + +unsigned int ble_packets_requested = 0b00; // keeps track of requested packets +unsigned int ble_packets_received = 0b00; // keeps track of received packets + +void MyEndOfScanCallback(BLEScanResults pBLEScanResult){ + bms_status=false; // BMS not found + + if(BLE_CALLBACK_DEBUG){ + debug("BLE: scan finished"); + Serial.println("Scan finished."); + } +} + +class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks{ + // called for each advertising BLE server + + void onResult(BLEAdvertisedDevice advertisedDevice){ + // found a device + + + if(BLE_CALLBACK_DEBUG){ + debug( + String("BLE: found ") + + String(advertisedDevice.getName().c_str()) + + String(" with address ") + + String(advertisedDevice.getAddress().toString().c_str()) + + String(" and RSSI ") + + String(advertisedDevice.getRSSI()) + ); + + Serial.print("BLE: found "); + Serial.println(advertisedDevice.toString().c_str()); + } + + // Check if device is advertising the specific service UUID + if (!advertisedDevice.isAdvertisingService(serviceUUID)) { + debug("Device does not advertise the specified service UUID."); + return; + } + + if(BLE_CALLBACK_DEBUG){ + debug("BLE: target device found"); + } + + pBLEScan->stop(); + + // delete old remote device, create new one + if(pRemoteDevice != nullptr){ + delete pRemoteDevice; + } + pRemoteDevice = new BLEAdvertisedDevice(advertisedDevice); + + doConnect = true; + } +}; + +class MyClientCallback : public BLEClientCallbacks{ + // called on connect/disconnect + void onConnect(BLEClient* pclient){ + + if(BLE_CALLBACK_DEBUG){ + debug(String("BLE: connecting to ") + String(pclient->getPeerAddress().toString().c_str())); + } + } + + void onDisconnect(BLEClient* pclient){ + ble_client_connected = false; + doConnect = false; + + if(BLE_CALLBACK_DEBUG){ + debug(String("BLE: disconnected from ") + String(pclient->getPeerAddress().toString().c_str())); + } + + } +}; + +static void MyNotifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify){ + //this is called when BLE server sents data via notification + //hexDump((char*)pData, length); + if(!bleCollectPacket((char *)pData, length)){ + debug("ERROR: packet could not be collected."); + } +} + +void handleBLE(){ + static unsigned long prev_millis_standby = 0; + + prev_millis_standby = millis(); + + while(true){ // loop until we hit a timeout or gathered all packets + + if((ble_packets_received == BLE_PACKETSRECEIVED_BEFORE_STANDBY) || (millis()>prev_millis_standby+BLE_TIMEOUT)){ + if(ble_packets_received == BLE_PACKETSRECEIVED_BEFORE_STANDBY){ + debug("BLE: all packets received"); + bms_status=true; // BMS was connected, data up-to-date + printBasicInfo(); + printCellInfo(); + }else{ + debug("BLE: connection timeout"); + bms_status=false; // BMS not (fully) connected + } + + break; // we're done with BLE, exit while loop + } + else if (doConnect){ + + // found the desired BLE server, now connect to it + if (connectToServer()){ + ble_client_connected = true; + ble_packets_received=0; + ble_packets_requested=0; + + }else{ + ble_client_connected = false; + debug("BLE: failed to connect"); + } + + doConnect = false; + } + + if (ble_client_connected){ + debug("BLE: requesting packet 0b01"); + delay(5000); + bmsRequestBasicInfo(); + debug("BLE: requesting packet 0b10"); + delay(5000); + bmsRequestCellInfo(); + }else if ((!doConnect)&&(doScan)){ + // we are not connected, so we can scan for devices + debug("BLE: not connected, starting scan"); + Serial.print("BLE is not connected, starting scan"); + + // Disconnect client + if((pClient != nullptr)&&(pClient->isConnected())){ + pClient->disconnect(); + } + + // stop scan (if running) and start a new one + pBLEScan->setActiveScan(true); + pBLEScan->setInterval(1 << 8); // 160 ms + pBLEScan->setWindow(1 << 7); // 80 ms + pBLEScan->start(BLE_SCAN_DURATION, MyEndOfScanCallback, false); // non-blocking, use a callback + + doScan=false; + + debug("BLE: scan started"); + } + } +} + +void bleGatherPackets(){ + bleStart(); + handleBLE(); + blePause(); + BLEDevice::deinit(false); +} + +void bleStart(){ + Serial.print("Starting BLE... "); + + BLEDevice::init(""); + //esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); // release some unused memory + + // Retrieve a BLE client + pClient = BLEDevice::createClient(); + pClient->setClientCallbacks(new MyClientCallback()); + + // Retrieve a BLE scanner + pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + + bleContinue(); + Serial.println("done"); +} + +void blePause(){ + // stop scanning and disconnect from all devices + doScan=false; + + // Disconnect client + if((pClient != nullptr)&&(pClient->isConnected())){ + pClient->disconnect(); + } + + delay(50); + + pBLEScan->stop(); + + ble_client_connected=false; + doConnect=false; + ble_packets_received=0; + ble_packets_requested=0; + +} + +void bleContinue(){ + // Prepare for scanning + ble_client_connected=false; + doConnect=false; + ble_packets_received=0; + + doScan=true; // start scanning for new devices +} + +bool connectToServer(){ + if(pRemoteDevice==nullptr){ + Serial.println("Invalid remote device, can't connect"); + return false; + } + + // Disconnect client + if((pClient != nullptr)&&(pClient->isConnected())){ + pClient->disconnect(); + } + + Serial.print("Forming a connection to "); + Serial.println(pRemoteDevice->getAddress().toString().c_str()); + + delay(100); + + // Connect to the remote BLE Server. + pClient->connect(pRemoteDevice); + if(!(pClient->isConnected())){ + debug(String("BLE: failed to connect")); + Serial.println("Failed to connect to server"); + pClient->disconnect(); + return false; + } + + Serial.println(" - Connected to server"); + + + delay(BLE_REQUEST_DELAY); // wait, otherwise writeValue doesn't work for some reason + // to do: fix this ugly hack + + debug(String("BLE: connected")); + + return true; +} + +bool sendCommand(uint8_t *data, uint32_t dataLen){ + if((pClient!=nullptr)&&(pClient->isConnected())){ + pRemoteCharacteristic_tx->writeValue(data, dataLen, false); + return true; + }else{ + return false; + } +} + +bool isPacketValid(byte *packet) //check if packet is valid +{ + if (packet == nullptr){ + return false; + } + + bmsPacketHeaderStruct *pHeader = (bmsPacketHeaderStruct *)packet; + int checksumPos = pHeader->dataLen + 2; // status + data len + data + + int offset = 2; // header 0xDD and command type are not in data length + + if (packet[0] != 0xDD){ + // start bit missing + return false; + } + + if (packet[offset + checksumPos + 2] != 0x77){ + // stop bit missing + return false; + } + + byte checksum = 0; + for (int i = 0; i < checksumPos; i++){ + checksum += packet[offset + i]; + } + checksum = ((checksum ^ 0xFF) + 1) & 0xFF; + + if (checksum != packet[offset + checksumPos + 1]){ + return false; + } + + return true; +} + +bool processBasicInfo(packBasicInfoStruct *output, byte *data, unsigned int dataLen) +{ + // Expected data len + if (dataLen != 0x1B) + { + //Serial.printf("bad data len %d!\r\n",dataLen); + //return false; + } + + output->Volts = ((uint32_t)two_ints_into16(data[0], data[1])) * 10; // Resolution 10 mV -> convert to milivolts eg 4895 > 48950mV + output->Amps = ((int32_t)two_ints_into16(data[2], data[3])) * 10; // Resolution 10 mA -> convert to miliamps + + output->Watts = output->Volts * output->Amps / 1000000; // W + + output->CapacityRemainAh = ((uint16_t)two_ints_into16(data[4], data[5])) * 10; + output->CapacityRemainPercent = ((uint8_t)data[19]); + + output->Temp1 = (((uint16_t)two_ints_into16(data[23], data[24])) - 2731); + output->Temp2 = (((uint16_t)two_ints_into16(data[25], data[26])) - 2731); + + output->BalanceCodeLow = (two_ints_into16(data[12], data[13])); + output->BalanceCodeHigh = (two_ints_into16(data[14], data[15])); + output->MosfetStatus = ((byte)data[20]); + + printBasicInfo(); + + return true; +} + +bool processCellInfo(packCellInfoStruct *output, byte *data, unsigned int dataLen) +{ + uint16_t _cellSum; + uint16_t _cellMin = 5000; + uint16_t _cellMax = 0; + uint16_t _cellAvg; + uint16_t _cellDiff; + + output->NumOfCells = dataLen / 2; // data contains 2 bytes per cell + + //go trough individual cells + for (byte i = 0; i < dataLen / 2; i++) + { + output->CellVolt[i] = ((uint16_t)two_ints_into16(data[i * 2], data[i * 2 + 1])); // Resolution 1 mV + _cellSum += output->CellVolt[i]; + if (output->CellVolt[i] > _cellMax) + { + _cellMax = output->CellVolt[i]; + } + if (output->CellVolt[i] < _cellMin) + { + _cellMin = output->CellVolt[i]; + } + } + + output->CellMin = _cellMin; + output->CellMax = _cellMax; + output->CellDiff = _cellMax - _cellMin; + output->CellAvg = _cellSum / output->NumOfCells; + + printCellInfo(); + + return true; +} + +bool bmsProcessPacket(byte *packet) +{ + bool isValid = isPacketValid(packet); + + if (isValid != true) + { + Serial.println("Invalid packer received"); + return false; + } + + bmsPacketHeaderStruct *pHeader = (bmsPacketHeaderStruct *)packet; + byte *data = packet + sizeof(bmsPacketHeaderStruct); // TODO Fix this ugly hack + unsigned int dataLen = pHeader->dataLen; + + bool result = false; + + // find packet type (basic info or cell info) + switch (pHeader->type) + { + case cBasicInfo: + { + // Process basic info + result = processBasicInfo(&packBasicInfo, data, dataLen); + if(result==true){ + ble_packets_received |= 0b01; + bms_last_update_time=millis(); + } + + break; + } + + case cCellInfo: + { + // Process cell info + result = processCellInfo(&packCellInfo, data, dataLen); + if(result==true){ + ble_packets_received |= 0b10; + bms_last_update_time=millis(); + } + break; + } + + default: + result = false; + Serial.printf("Unsupported packet type detected. Type: %d", pHeader->type); + } + + return result; +} + +bool bleCollectPacket(char *data, uint32_t dataSize) // reconstruct packet, called by notifyCallback function +{ + static uint8_t packetstate = 0; //0 - empty, 1 - first half of packet received, 2- second half of packet received + + // packet sizes: + // (packet ID 03) = 4 (header) + 23 + 2*N_NTCs + 2 (checksum) + 1 (stop) + // (packet ID 04) = 4 (header) + 2*NUM_CELLS + 2 (checksum) + 1 (stop) + static uint8_t packetbuff[4 + 2*25 + 2 + 1] = {0x0}; // buffer size suitable for up to 25 cells + + static uint32_t totalDataSize = 0; + bool retVal = false; + hexDump(data,dataSize); + + if(totalDataSize + dataSize > sizeof(packetbuff)){ + Serial.printf("ERROR: datasize is overlength."); + + debug( + String("ERROR: datasize is overlength. ") + + String("allocated=") + + String(sizeof(packetbuff)) + + String(", size=") + + String(totalDataSize + dataSize) + ); + + totalDataSize = 0; + packetstate = 0; + + retVal = false; + } + else if (data[0] == 0xdd && packetstate == 0) // probably got 1st half of packet + { + Serial.println("PKT1"); + packetstate = 1; + for (uint8_t i = 0; i < dataSize; i++) + { + packetbuff[i] = data[i]; + } + totalDataSize = dataSize; + retVal = true; + + if (data[dataSize - 1] == 0x77) { + //its full packets + packetstate = 2; + } + } + else if (data[dataSize - 1] == 0x77 && packetstate == 1) //probably got 2nd half of the packet + { + Serial.println("PKT2"); + packetstate = 2; + for (uint8_t i = 0; i < dataSize; i++) + { + packetbuff[i + totalDataSize] = data[i]; + } + totalDataSize += dataSize; + retVal = true; + } + + if (packetstate == 2) //got full packet + { + Serial.println("PKT3"); + uint8_t packet[totalDataSize]; + memcpy(packet, packetbuff, totalDataSize); + + bmsProcessPacket(packet); //pass pointer to retrieved packet to processing function + packetstate = 0; + totalDataSize = 0; + retVal = true; + } + return retVal; +} + +bool bmsRequestBasicInfo(){ + // header status command length data checksum footer + // DD A5 03 00 FF FD 77 + uint8_t data[7] = {0xdd, 0xa5, cBasicInfo, 0x0, 0xff, 0xfd, 0x77}; + return sendCommand(data, sizeof(data)); +} + +bool bmsRequestCellInfo(){ + // header status command length data checksum footer + // DD A5 04 00 FF FC 77 + uint8_t data[7] = {0xdd, 0xa5, cCellInfo, 0x0, 0xff, 0xfc, 0x77}; + return sendCommand(data, sizeof(data)); +} + +void printBasicInfo() //debug all data to uart +{ + Serial.printf("Total voltage: %f\r\n", (float)packBasicInfo.Volts / 1000); + Serial.printf("Amps: %f\r\n", (float)packBasicInfo.Amps / 1000); + Serial.printf("CapacityRemainAh: %f\r\n", (float)packBasicInfo.CapacityRemainAh / 1000); + Serial.printf("CapacityRemainPercent: %d\r\n", packBasicInfo.CapacityRemainPercent); + Serial.printf("Temp1: %f\r\n", (float)packBasicInfo.Temp1 / 10); + Serial.printf("Temp2: %f\r\n", (float)packBasicInfo.Temp2 / 10); + Serial.printf("Balance Code Low: 0x%x\r\n", packBasicInfo.BalanceCodeLow); + Serial.printf("Balance Code High: 0x%x\r\n", packBasicInfo.BalanceCodeHigh); + Serial.printf("Mosfet Status: 0x%x\r\n", packBasicInfo.MosfetStatus); +} + +void printCellInfo() //debug all data to uart +{ + Serial.printf("Number of cells: %u\r\n", packCellInfo.NumOfCells); + for (byte i = 1; i <= packCellInfo.NumOfCells; i++) + { + Serial.printf("Cell no. %u", i); + Serial.printf(" %f\r\n", (float)packCellInfo.CellVolt[i - 1] / 1000); + } + Serial.printf("Max cell volt: %f\r\n", (float)packCellInfo.CellMax / 1000); + Serial.printf("Min cell volt: %f\r\n", (float)packCellInfo.CellMin / 1000); + Serial.printf("Difference cell volt: %f\r\n", (float)packCellInfo.CellDiff / 1000); + Serial.printf("Average cell volt: %f\r\n", (float)packCellInfo.CellAvg / 1000); + Serial.println(); +} + +void hexDump(const char *data, uint32_t dataSize) //debug function +{ + Serial.println("HEX data:"); + + for (int i = 0; i < dataSize; i++) + { + Serial.printf("0x%x, ", data[i]); + } + Serial.println(""); +} + +int16_t two_ints_into16(int highbyte, int lowbyte) // turns two bytes into a single long integer +{ + int16_t result = (highbyte); + result <<= 8; //Left shift 8 bits, + result = (result | lowbyte); //OR operation, merge the two + return result; +} diff --git a/test/ESPBMS.ino b/test/ESPBMS.ino deleted file mode 100644 index 292a4a8..0000000 --- a/test/ESPBMS.ino +++ /dev/null @@ -1,66 +0,0 @@ -#include "BLEDevice.h" - -void setup() { - Serial.begin(115200); - BLEDevice::init(""); // Initialize BLE device - - BLEScan* pBLEScan = BLEDevice::getScan(); // Create new scan - pBLEScan->setActiveScan(true); // Active scan uses more power, but get results faster - BLEScanResults foundDevices = pBLEScan->start(30); // Scan for 30 seconds - - Serial.println("Scanning completed"); - Serial.println("Devices found: " + String(foundDevices.getCount())); - - for (int i = 0; i < foundDevices.getCount(); i++) { - BLEAdvertisedDevice advertisedDevice = foundDevices.getDevice(i); - Serial.println("\nFound Device: " + String(advertisedDevice.toString().c_str())); - - // Now connect to the device and then discover its services and characteristics - BLEClient* pClient = BLEDevice::createClient(); - Serial.println("Connecting to: " + String(advertisedDevice.getAddress().toString().c_str())); - - if (pClient->connect(&advertisedDevice)) { - Serial.println("Connected to device!"); - // Obtain references to all services - std::map* pRemoteServices = pClient->getServices(); - for (auto &myService : *pRemoteServices) { - Serial.println("Service: " + String(myService.first.c_str())); - BLERemoteService* pRemoteService = myService.second; - - // List all characteristics of this service - std::map* pChars = pRemoteService->getCharacteristics(); - for (auto &myChar : *pChars) { - String properties = getCharacteristicProperties(myChar.second); - Serial.print("\tCharacteristic: " + String(myChar.first.c_str()) + " | Properties: " + properties); - - // Read descriptors of the characteristic - std::map* pDescs = myChar.second->getDescriptors(); - for (auto &desc : *pDescs) { - std::string descValue = desc.second->readValue(); - Serial.print(" | Descriptor: " + String(desc.first.c_str()) + " Value: " + String(descValue.c_str())); - } - - Serial.println(); - } - } - pClient->disconnect(); - } else { - Serial.println("Failed to connect."); - } - } -} - -String getCharacteristicProperties(BLERemoteCharacteristic* pChar) { - String propDesc = ""; - if (pChar->canRead()) propDesc += "read "; - if (pChar->canWrite()) propDesc += "write "; - if (pChar->canWriteNoResponse()) propDesc += "write_no_resp "; - if (pChar->canNotify()) propDesc += "notify "; - if (pChar->canIndicate()) propDesc += "indicate "; - propDesc.trim(); - return propDesc; -} - -void loop() { - // Nothing to do here -}