Parsing binary message sent via BLE from ESP32

Hi everyone,

I am trying to make a simple app on MIT App Inventor to be able to display multiple values sent from ESP32 on my android phone.

I use BLE on ESP32 to send the values (GPS data) to the phone.
The number of variable I would like to see on phone screen is pretty much, so instead of creating bunch of characteristics on BLE, I am sending a binary message I created with a binary structure in a single BLE characteristics. It includes different data types such as uint8 and float.

However, I struggle with finding the right way to create the blocks for parsing the binary message. I have seen in other topics that using built-in Lists and RegisterForFloats procedure for repeated only float values (up to 4) is an option, but I don't know if parsing such binary structure is even possible on App Inventor. At the end I would like to display around 20 different variables on my phone screen.

Here is a sample Arduino code I have to send the data from ESP32:

#include "HardwareSerial.h"
#include <BLEServer.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// BLE UUIDs
#define SERVICE_UUID "adc78270-9394-4fc6-9018-f3bb18a1d3e8"  // UART service UUID
#define CHARACTERISTIC_UUID_RX "adc78271-9394-4fc6-9018-f3bb18a1d3e8"  // WRITE
#define CHARACTERISTIC_UUID_TX "adc78272-9394-4fc6-9018-f3bb18a1d3e8"  // READ

// BLE SETUP
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;

uint8_t binaryMessage[100];  // Define the binary message buffer
uint8_t modeId = 1;
float currentTime = 0;
float groundSpeed = 0;
float latitude = 0;
float longitude = 0;

class ServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
    deviceConnected = true;
    pServer->updateConnParams(param->connect.remote_bda, 0, 0x20, 0x10, 2000);
  };

  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};

class MyCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic* pCharacteristic) {
    String rxCommand = pCharacteristic->getValue().c_str();
    if (!rxCommand.isEmpty()) {
      if (rxCommand.startsWith("#")) {
        try {
          // Attempt to print the command (excluding the '#' character)
          Serial.print("Received cmd: ");
          Serial.println(rxCommand.substring(1));
        } catch (...) {
          // If an exception occurs during command processing
          Serial.println("Command failed. ");
        }
      }
    }
  }
};

float generateRandomFloat(float minVal, float maxVal) {
  // Initialize the random number generator with a random seed
  randomSeed(analogRead(0));  // You can use any analog pin for randomSeed()

  // Generate a random integer within the desired range
  int randInt = random(RAND_MAX);

  // Map the random integer to a float within the specified range
  float randFloat = map(randInt, 0, RAND_MAX, minVal, maxVal);

  return randFloat;
}

void BLEconfig() {

  // Set unique BLE name based on ESP32 chip ID
  String BLEname = "ESP32";

  // Create the BLE Device
  BLEDevice::init(BLEname.c_str());

  // Create the BLE Server
  BLEServer* pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  // Create the BLE Service
  BLEService* pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID_TX,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  // Create a BLE Descriptor to Notify
  pCharacteristic->addDescriptor(new BLE2902());

  // Create a BLE Characteristic for Commands RX
  BLECharacteristic* pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID_RX,
    BLECharacteristic::PROPERTY_WRITE);

  pCharacteristic->setCallbacks(new MyCallbacks());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
}

void setup() {
  // Open serial communications
  Serial.begin(921600);
  BLEconfig();
}

void LogBinary() {

  // Calculate the total size of the binary message
  size_t messageSize = 4 * sizeof(float) + sizeof(uint8_t);

  // Create a buffer to hold the binary message
  uint8_t binaryMessage[messageSize];

  // Index to iterate through the binary message
  size_t index = 0;

  // Copy fixed-size data types
  memcpy(&binaryMessage[index], &modeId, sizeof(uint8_t));
  index += sizeof(uint8_t);
  memcpy(&binaryMessage[index], &currentTime, sizeof(float));
  index += sizeof(float);
  memcpy(&binaryMessage[index], &groundSpeed, sizeof(float));
  index += sizeof(float);
  memcpy(&binaryMessage[index], &latitude, sizeof(float));
  index += sizeof(float);
  memcpy(&binaryMessage[index], &longitude, sizeof(float));
  index += sizeof(float);
}

void loop() {

  // Disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500);                   // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising();  // restart advertising
    Serial.println("BLE reconnected.");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    oldDeviceConnected = deviceConnected;
  }

  // Generate a random float
  currentTime = generateRandomFloat(0.0, 86400.0);
  groundSpeed = generateRandomFloat(0.0, 400.0);
  latitude = generateRandomFloat(-90.0, 90.0);
  longitude = generateRandomFloat(-180.0, 180.0);

  // Generate the log
  LogBinary();

  // SEND Data Message via BLE
  pCharacteristic->setValue(const_cast<uint8_t*>(binaryMessage), sizeof(binaryMessage));
  pCharacteristic->notify();  // Send the value to the app!
}

Any help is appreciated!

Some people send temperature and humidity in separate messages with distinctive prefixes like "t:" (for temperature) and "h:" (for humidity).
(That's YAML format.)

The AI2 Charts component can recognize these and graph them. See Bluetooth Client Polling Rate - #12 by ABG

This format is easier to synchronize and easier to read.