Broadcast BLE data read from peripheral is incorrect

Occasionally the broadcast data read from the peripheral is incorrect.

The same sketch running on an UNO R4 WiFi always reads the data correctly. The only differences between the sketches is the libraries and code used for the different LCDs.

The data is being read from a Victron BMV-700 battery monitor. This broadcasts an identifier, a message counter and the encrypted data over BLE. There are multiple transmissions per second but the message counter, which increments every time the data has been refreshed, only changes about once per second.

The received data is decrypted for display and is checked to see if the any of the values just read are outside of the saved minimum or maximum for that value. If it is, the value just read is saved as the new minimum or maximum. The photo below shows the invalid values (circled in red) displayed on the Giga Display Shield with the correct values (circled in green) shown on the UNO R4 WiFi LCD.

For testing the encrypted data is also logged through the serial port. This shows that the encrypted data read by the Giga is different to the encrypted data read by the UNO R4 WiFi. This indicates that the error is not occurring during the decryption. The WinMerge screen shot below shows that 2 bytes of data are different in message 09BD.


It also shows that the Giga is not receiving all the data being broadcast. On the photo above it can be seen that the RSSI shown on the Giga is -83dBm while the R4 shows -69dBm. The device being read is about 10 meters from the Arduinos.

The Bluetooth code is based on the ArduinoBLE - Central - ScanCallback example.

Here is the main sketch for the Giga:

/*Victron Display
  Display the data read from a Victron BMV-700 using bluetooth on an LCD.
*/

/*******************************************************************************
SCREEN RELATED
Standard fonts are 6 pixels wide by 8 pixels high.
*******************************************************************************/
//Screen size constants
/*******************************************************************************
Values for PICSimlab and XC4630: 320 x 240
#include <Adafruit_GFX.h>
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
const int scnW = 320;
const int scnH = 240;
const int MESSAGE_X_POS = 0;
const int MESSAGE_Y_POS = 220;
const int MESSAGE_FS = 2;
*******************************************************************************/

/*******************************************************************************
Values for Giga Display Shield: 800 x 480
*******************************************************************************/
#include <Arduino_GigaDisplay_GFX.h>
#include <Arduino_GigaDisplay.h>
GigaDisplay_GFX tft;
const int scnW = 800;
const int scnH = 480;
const int MESSAGE_X_POS = 0;
const int MESSAGE_Y_POS = 440;
const int MESSAGE_FS = 4;

/*******************************************************************************
Values for Adafruit P2050: 480 x 320
const int scnW = 480;
const int scnH = 320;
*******************************************************************************/

// Assign human-readable names to some common 16-bit color values:
#define BLACK       0x0000
#define BLUE        0x001F
#define RED         0xF800
#define GREEN       0x07E0
#define CYAN        0x07FF
#define MAGENTA     0xF81F
#define YELLOW      0xFFE0
#define ORANGE      0xFD20
#define WHITE       0xFFFF
#define GREY        0x8410

//Define constants for item colours
const int VICTRON_COLOUR = 0x051B;

//Screen constants
#define scnNONE   0
#define scnVICTRONHISTORYDETAILS  16

//The current screen displayed, defaults to none (0)
int curScreen = scnNONE;

//Time variables used for display
int timeSecond;
int timeMinute;
int timeHour;

//Timestamp variable for daily maximum and minimum values
long secondsToday = 0;

//Variables for daily maximum and minimum values and timestamps
float batterySoc_Vmax = 0;
long batterySoc_VmaxT = 0;
float batterySoc_Vmin = 99;
long batterySoc_VminT = 0;

float batteryV_Vmax = 0;
long batteryV_VmaxT = 0;
float batteryV_Vmin = 99;
long batteryV_VminT = 0;

float batteryA_Vmax = -99;
long batteryA_VmaxT = 0;
float batteryA_Vmin = 99;
long batteryA_VminT = 0;

float batteryAh_Vmax = -99;
long batteryAh_VmaxT = 0;
float batteryAh_Vmin = 99;
long batteryAh_VminT = 0;

//For measuring time between samples
unsigned long lastUpdate; 
#define SAMPLET 1000

//Includes for BMV700.ino
#include <ArduinoBLE.h>
const String BMVNAME = "BMV-700";

//Values read from the Victron Battery Monitor
float batterySoc_V;
float batteryV_V;
float batteryA_V;
float batteryAh_V;
int rssi_V;
int iCount = 0;

String tempString;
const int TEMP_STRING_LENGTH = 200;
String textToDisplay;
String textToLCD;
const int MAX_TEXT_LENGTH = 40;

void setup() {
  Serial.begin(115200);
  
  //Reserve space for global strings
  tempString.reserve(TEMP_STRING_LENGTH);
  textToDisplay.reserve(MAX_TEXT_LENGTH);
  textToLCD.reserve(MAX_TEXT_LENGTH);

/*******************************************************************************
Values for PICSimlab and XC4630: 320 x 240
  tft.reset();
  uint16_t ID = tft.readID();
  Serial.print("TFT ID = 0x");
  Serial.println(ID, HEX);
  //PICSimLab returns ID 0
  if (ID == 0) {
    tft.begin(0x9341);
    tft.setRotation(3);
  } else {
    tft.begin(ID);
    tft.setRotation(1);
  }
  tft.setTextSize(1);
*******************************************************************************/


/*******************************************************************************
Values for Giga Display Shield: 800 x 480
*******************************************************************************/
  tft.begin();
  tft.setRotation(1);
  tft.setTextSize(3);


  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE);

  //Print sketch information, from: https://forum.arduino.cc/t/sketch-name-as-variable/245271/5?u=dl144
  Serial.println(F("Sketch: " __FILE__ ", " __DATE__ ", " __TIME__));
  tft.println("Sketch: ");
  tft.println(__FILE__);
  tft.println();
  tft.print("Compiled: ");
  tft.print(__DATE__);
  tft.print(", ");
  tft.println(__TIME__);
  tft.println();
  Serial.println(F("Victron BMV-700 BLE test"));
  tft.println(F("Victron BMV-700 BLE test"));
  
  // begin initialization
  if (!BLE.begin()) {
    Serial.println("Starting Bluetooth® Low Energy module failed!");
    tft.setTextColor(RED);
    tft.println("Starting Bluetooth® Low Energy module failed!");
    while (1);
  }

  Serial.println("Bluetooth® Low Energy Central scan callback");
  tft.println("Bluetooth® Low Energy Central scan callback");
  
  // set the discovered event handle
  BLE.setEventHandler(BLEDiscovered, bleCentralDiscoverHandler);

  BLE.scanForName(BMVNAME, true);
  
  tft.print(F("Scanning for: "));
  tft.println(BMVNAME);
  delay(2000);
  //Turn off text wrapping
  tft.setTextWrap(false);
}

void loop() {
  getTime();
  getBMV700Data();
  victronScreenHistoryDetails();
}

void getTime() {
  //Create timestamp value
  secondsToday = millis() / 1000;
  //Convert time in seconds to hours, minutes and seconds
  timeHour = secondsToday / 3600;
  timeMinute = (secondsToday / 60) % 60;
  timeSecond = secondsToday % 60;
}

The sketch for the UNO R4 WiFi is the same with only the code for the XC4630 LCD uncommented and the code for the Giga Display Shield commented out.

Here is the code that checks and processes the Bluetooth data:

/*Victron BMV BLE Monitor
  Uses the Bluetooth BLE Product Advertisement beacon to read data from
  the Victron BMV-700 battery monitor.
  The data is stored in the Victron Manufacturer Data record type 0x10.
*/

#include <Crypto.h>
#include <AES.h>
#include <CTR.h>

//key[16] contains 16 byte key(128 bit) for encryption
byte key[16]={0xFF, 0xCD, 0x25, 0x03, 0x5B, 0xFC, 0x6F, 0x84, 0xB7, 0xBD, 0x23, 0xD6, 0x8A, 0x37, 0x11, 0x21};

//encryptedText[16] contains the encrypted data from the BMV-700
byte encryptedText[16];

//decryptedText[16] stores the text after decryption
byte decryptedText[16];

//Create an object of AES128-CTR class
CTR<AES128> ctraes128;

//This is where the encrypted data starts in the Manufacturers Data string
const int DATA_OFFSET = 10;

const int SCANDELAY = 300;

//bmvManufacturerData is the 25 byte data array the data will get copied into
int const bmvDataLen = 25;
uint8_t bmvManufacturerData[bmvDataLen] = {};

unsigned long bleLastScan = 0;
uint8_t lastCounter;

void getBMV700Data() {
    if (millis() - bleLastScan > SCANDELAY) {
      bleLastScan = millis();
      BLE.poll();
    }
} //void getBMV700Data()

void bleCentralDiscoverHandler(BLEDevice peripheral) {
  rssi_V = peripheral.rssi();
  peripheral.manufacturerData(bmvManufacturerData, bmvDataLen);
  //Check for updated data
  if (bmvManufacturerData[7] != lastCounter) {
    //Decrypt the data
    printData();
    decryptData();
    //Save the values encoded in the decrypted data
    convertData();    
    lastCounter = bmvManufacturerData[7];
    iCount = lastCounter;
  } //if (bmvManufacturerData[7] != lastCounter)
} //void bleCentralDiscoverHandler(BLEDevice peripheral)()

void decryptData() {
//Extract the required information from the Maunufacturers Data and decrypt it
  //Get IV value
  uint8_t nonce_counter[16] = {bmvManufacturerData[7], bmvManufacturerData[8], 0};
  ctraes128.setIV(nonce_counter, 16);
  //Set Key for AES
  ctraes128.setKey(key, 16);
  //Copy the encrypted data from the BMV-700
  for (int x = 0; x < 15; x++) {
    encryptedText[x] = bmvManufacturerData[x + DATA_OFFSET];
  }
  //Add a 16th byte as the BMV-700 only provides 15 bytes of data
  // encryptedText[15] = 0xFF;
  //Decrypt the data from the BMV-700
  // ctraes128.decrypt(decryptedText, encryptedText, 16);
  ctraes128.decrypt(decryptedText, encryptedText, 15);
}

void convertData() {
//Extract the values encoded in the decrypted string
bool neg;
  //Battery Voltage conversion
          neg       =  (decryptedText[3] & 0x80) >> 7;                     // extract sign bit for signed int
  int32_t batt_mV10 = ((decryptedText[3] & 0x7F) << 8) | decryptedText[2]; // exclude sign bit from byte 3
  if (neg) batt_mV10 = batt_mV10 - 32768; // 2's complement = val - 2^(b-1) b = bit# = 16
  float fbatteryVoltage = batt_mV10 / 100.0;
  
  
  //Battery Current conversion
          neg =  (decryptedText[10] & 0x80) >> 7;                                              // bit  21
  int32_t mA  = ((decryptedText[8]  & 0xFC) >> 2) + ((decryptedText[9]  & 0x03) << 6)       |  // bits  0 - 7 
               (((decryptedText[9]  & 0xFC) >> 2) + ((decryptedText[10] & 0x03) << 6) << 8) |  // bits  8 - 15
               (((decryptedText[10] & 0x7C) >> 2)                                     << 16);  // bits 16 - 20
  if (neg) mA = mA - 2097152; // 2's complement = val - 2^(b-1) where b = bits = 22
  float fbatteryCurrent = mA / 1000.0;

  //Consumed Ah conversion
  uint32_t mAh100 = decryptedText[11]            |            // bits  0 - 7 
                        (decryptedText[12] << 8) |            // bits  8 - 15
                       ((decryptedText[13] & 0x0F) << 16);    // bits 16 - 19
  float fconsumedAh = mAh100 / 10.0;
  
  //State of Charge conversion 
  uint16_t soc01 = ((decryptedText[13] & 0xF0) >> 4) |   // bits 0 - 3
                   ((decryptedText[14] & 0x0F) << 4) |   // bits 4 - 7
                   ((decryptedText[14] & 0x30) << 4);    // bits 8 - 9
  float fsOC = soc01 / 10.0;

  //Copy the converted values to the global variables for display
  batteryV_V = fbatteryVoltage;
  batteryA_V = fbatteryCurrent;
  batteryAh_V = fconsumedAh * - 1.0;
  batterySoc_V = fsOC;
  
  //Check for new maxima and minima
  if (batterySoc_V > batterySoc_Vmax) {
    batterySoc_Vmax = batterySoc_V;
    batterySoc_VmaxT = secondsToday;
  }
  if (batterySoc_V < batterySoc_Vmin) {
    batterySoc_Vmin = batterySoc_V;
    batterySoc_VminT = secondsToday;
  }
  if (batteryV_V > batteryV_Vmax) {
    batteryV_Vmax = batteryV_V;
    batteryV_VmaxT = secondsToday;
  }
  if (batteryV_V < batteryV_Vmin) {
    batteryV_Vmin = batteryV_V;
    batteryV_VminT = secondsToday;
  }
  if (batteryA_V > batteryA_Vmax) {
    batteryA_Vmax = batteryA_V;
    batteryA_VmaxT = secondsToday;
  }
  if (batteryA_V < batteryA_Vmin) {
    batteryA_Vmin = batteryA_V;
    batteryA_VminT = secondsToday;
  }
  if (batteryAh_V > batteryAh_Vmax) {
    batteryAh_Vmax = batteryAh_V;
    batteryAh_VmaxT = secondsToday;
  }
  if (batteryAh_V < batteryAh_Vmin) {
    batteryAh_Vmin = batteryAh_V;
    batteryAh_VminT = secondsToday;
  }
}

void printData() {
  //Print the counter value
  for (int x = 8; x > 6; x--) {
    if (bmvManufacturerData[x] < 0x10) {
      Serial.print("0");
    }
    Serial.print(bmvManufacturerData[x], HEX);
  }
  //Print the encrypted data
  Serial.print(" Enc: ");
  for (int x = 0; x < bmvDataLen; x++) {
    if (bmvManufacturerData[x] < 0x10) {
      Serial.print("0x0");
    } else {
      Serial.print("0x");
    }
    Serial.print(bmvManufacturerData[x], HEX);
    Serial.print(",");
  }
  Serial.println();
}

The tests were done using IDE 2.3.6, Giga Board 4.3.1, UNO R4 Board 1.4.1 and ArduinoBLE 1.4.0.

Any ideas why the Bluetooth data read by the Giga is sometimes incorrect?

thanks,
Michael

What antenna are you using on your giga board? -83dBm Vs -69dBm is not an insignificant difference.

I'm using the plug in external antenna that came with the board.

Maybe carefully disconnect and reconnect the antenna. Is the antenna attached to any metal casing, perhaps. Does not make sense why the rssi so much poorer.

I have disconnected and reconnected the antenna, the RSSI for Bluetooth is still around -80dBm on the Giga versus -60dBm on the R4.
The WiFi RSSI on the Giga is around -15dBm when connecting to my phone about 20cm away.
The reconnected antenna is now free in the air, previously the wire was laying across the board with the antenna located in the gap between the SCL21 and DI22 pins.