Writing to a BLE Characteristic

I'm using a MKR 1010 WiFi to send text commands to a device using a custom API. The commands are variable length HEX and upwards of 20 bytes. I've tried splitting them into 4 bytes and sending them sequentially, and storing an array and sending one byte at a time. The receiving device should be able to reconstruct commands sent in pieces using the presence of a total message length field and a footer. Reading the Characteristic after writing only shows the last byte sent and not the full message.
So, two questions:
Does the BLECharacteristic.writeValue() function have a limit or buffer for the length it can send?

Does Arduino actually store and send zero values? If I'm sending as part of the full command const long DEP2 = 0x00980080; is the leading byte of 0x00 dropped shifting the other bytes of the command?

Thanks,

#include <ArduinoBLE.h>

// Define ActiveLook advertising data
const char* deviceNamePrefix = "A.LooK";
const char* manufacturerName = "Microoled";
const char* serialNumber = "090105";   // Replace "xxxxxx" with the actual serial number
const int manufacturerDataLength = 9;  // Length of the manufacturer data

// Custom service UUIDs
const char* activeLookCommandsServiceUUID = "0783B03E-8535-B5A0-7140-A304D2495CB7";
const char* firmwareUpdateServiceUUID = "0000FEF5-0000-1000-8000-00805F9B34FB";

// ActiveLook command characteristic UUIDs
const char* txActiveLookCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CB8";
const char* rxActiveLookCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CBA";
const char* controlCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CB9";
const char* gestureEventCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CBB";
const char* touchEventCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CBC";

// ActiveLook command descriptor UUIDs

// BLE advertising parameters
const int initialAdvertisingInterval = 25;

BLEService activeLookCommandsService(activeLookCommandsServiceUUID);  //Creates the service
BLEService firmwareUpdateService(firmwareUpdateServiceUUID);

BLECharacteristic txActiveLookCharacteristic;
BLELongCharacteristic rxActiveLookCharacteristic(rxActiveLookCharacteristicUUID, BLEWrite | BLERead);  //Creates the RX characteristic
BLECharacteristic controlCharacteristic;
BLECharacteristic gestureEventCharacteristic;
BLECharacteristic touchEventCharacteristic;

BLEDescriptor ServerRXData("2901", "Server RX Data");  //Not sure if this is needed CJ

//Long and int variable types only hold 4 bytes, need to send 20 or more bytes

const long HELLO_TEXT = 0xFF3700140098008000020F68656C6C6F203000AA;  //20 Bytes, Demo hello command form AL API
int clearScreen[] = { 0xFF, 0x01, 0x00, 0x05, 0xAA };
const unsigned long CLEAR = 0xFF010005;                   //FF010005                        //5 Bytes, Clear Display command from AL API
const long CLEAR_B = 0b11111111000000010000000000000101;  //first 4 bytes clear command in binary
const long END = 0xAA;
const long END_B = 0b10101010;  // last byte of clear command (footer) of clear command in binary

//Approaching command in HEX array
long approaching[] = { 0xFF, 0x37, 0x00, 0x14, 0x00, 0x98, 0x00, 0x80, 0x00, 0x02, 0x0F, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x61, 0x63, 0x68, 0x69, 0x6E, 0x67, 0x20, 0x21, 0x00, 0xAA };

//Approaching Command, Separated into 4 bytes, 26 bytes total
//FF	37	00	14	00	98	00	80	00	02	0F	41	70	70	72	6F	61	63	68	69	6E	67	20	21	00	AA
const long APP1 = 0xFF370014;
const long APP2 = 0x00980080;
const long APP3 = 0x00020F41;
const long APP4 = 0x7070726F;
const long APP5 = 0x61636869;
const long APP6 = 0x6E672021;
const long APP7 = 0x00AA;

//Departing Command, Separated into 4 byte, 23 bytes total
//FF	37	00	14	00	98	00	80	00	02	0F	44	65	70	61	72	74	69	6E	67	20	00	AA
const long DEP1 = 0xFF370014;
const long DEP2 = 0x00980080;
const long DEP3 = 0x00020F44;
const long DEP4 = 0x65706172;
const long DEP5 = 0x74696E67;
const long DEP6 = 0x2000AA;

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");
    while (1)
      ;
  }

  Serial.println("Bluetooth® Low Energy Central - Peripheral Explorer");

  // start scanning for peripherals
  BLE.scan();
}

void loop() {
  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available();

  if (peripheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.print(peripheral.advertisedServiceUuid());
    Serial.println();

    // see if peripheral is a LED
    if (peripheral.localName() == "ENGO 2 090145") {
      // stop scanning
      BLE.stopScan();

      explorerPeripheral(peripheral);  //Connects only, removed all other functions
    }

    // Add services
    BLE.setAdvertisedService(activeLookCommandsService);
    BLE.setAdvertisedService(firmwareUpdateService);

    // Add characteristics to services
    activeLookCommandsService.addCharacteristic(txActiveLookCharacteristic);
    activeLookCommandsService.addCharacteristic(rxActiveLookCharacteristic);
    activeLookCommandsService.addCharacteristic(controlCharacteristic);
    activeLookCommandsService.addCharacteristic(gestureEventCharacteristic);
    activeLookCommandsService.addCharacteristic(touchEventCharacteristic);

    // Add descriptor(s) to rx characteristic
    rxActiveLookCharacteristic.addDescriptor(ServerRXData);


    if (peripheral.connected()) {
      //for (int i = 0; i < 26; i = i + 1){
      //rxActiveLookCharacteristic.writeValue(clearScreen[i]);
      //rxActiveLookCharacteristic.writeValue(CLEAR);
      //rxActiveLookCharacteristic.writeValue(END);
      //Serial.print(CLEAR, HEX);
      //approachingDisplay();
      clearDisplay();
    }
  }

  //Serial.println(rxActiveLookCharacteristic.value(), HEX);
  //Serial.println(APP2, HEX);
  //Serial.println(CLEAR, HEX);
}

void clearDisplay() {
  rxActiveLookCharacteristic.writeValueBE(CLEAR_B);
  rxActiveLookCharacteristic.writeValueBE(END_B);
}

void approachingDisplay() {

  for (int i = 0; i < 26; i = i + 1) {
    rxActiveLookCharacteristic.writeValueBE(approaching[i]);
    Serial.println(approaching[i], HEX);
  }
  Serial.println(rxActiveLookCharacteristic.value(), HEX);
  /*rxActiveLookCharacteristic.writeValue(APP1);
  rxActiveLookCharacteristic.writeValue(APP2);
  rxActiveLookCharacteristic.writeValue(APP3);
  rxActiveLookCharacteristic.writeValue(APP4);
  rxActiveLookCharacteristic.writeValue(APP5);
  rxActiveLookCharacteristic.writeValue(APP6);
  rxActiveLookCharacteristic.writeValue(APP7);*/
}

void departingDisplay() {
  rxActiveLookCharacteristic.writeValue(DEP1);
  rxActiveLookCharacteristic.writeValue(DEP2);
  rxActiveLookCharacteristic.writeValue(DEP3);
  rxActiveLookCharacteristic.writeValue(DEP4);
  rxActiveLookCharacteristic.writeValue(DEP5);
  rxActiveLookCharacteristic.writeValue(DEP6);
}

void explorerPeripheral(BLEDevice peripheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");

  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }
}

void canWrite(BLEDevice peripheral) {
  if (rxActiveLookCharacteristic.canWrite()) {
    Serial.println("characteristic is writable");
  } else {
    Serial.println("characteristic is NOT writable");
  }
}

void printServiceUUID(BLEDevice peripheral) {
  // print the advertised service UUIDs, if present
  if (peripheral.hasAdvertisedServiceUuid()) {
    Serial.print("Service UUIDs: ");
    for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) {
      Serial.print(peripheral.advertisedServiceUuid(i));
      Serial.print(" ");
    }
    Serial.println();
  }
}

Of course it does.

For questions about code, post the code, using code tags, and the details that forum members need to understand the problem.

Characteristics follow the basic rule of attributes which have a max length of 512 bytes.

As I understand it the large characteristic is sent in multiple packets of 20? data bytes.. The BLE library manages the chunks.

Does Arduino actually store and send zero values? If I'm sending as part of the full command const long DEP2 = 0x00980080; is the leading byte of 0x00 dropped shifting the other bytes of the command?

I think if you have a 4 byte long value with the highest byte as 00 it will be sent. Your issue is likely on the reading side.

What is reading the data? Are you using a standard phone app like LightBlue or nrfConnect?

Without posting code there is little this board can do to help.

Thanks for the response. Full code is posted above. It's the serial monitor that is not displaying the full values. So 0x00980080 is displayed as 980080. I don't know how to verify what is actually being sent.

I'm sending the commands to a pair of ActiveLook AR glasses. The commands are built using their custom API Activelook-API-Documentation/ActiveLook_API.md at main · ActiveLook/Activelook-API-Documentation · GitHub

Use LightBlue or nrfConnect to connect to the Arduino and read the Characteristic.

EDIT: I think your Arduino is a central reading and writing to the peripheral (Active Look Glasses). I'm not clear how the standard apps (LightBlue, nrfConnect) can be used as peripherals to test your Arduino central app but I do think it is possible.

That is exactly what the serial monitor is expected to show. By default, leading zeros are omitted. If the high byte is not zero, it will be shown.

Use snprintf() to display leading zeros. Something like:

char buf[12]={0};
uint32_t value = 0x980080;
snprintf(buf,sizeof(buf), "0x%08lX", value);
Serial.println(buf);

Result 0x00980080

[/quote]

Use LightBlue or nrfConnect to connect to the Arduino and read the Characteristic.

EDIT: I think your Arduino is a central reading and writing to the peripheral (Active Look Glasses). I'm not clear how the standard apps (LightBlue, nrfConnect) can be used as peripherals to test your Arduino central app but I do think it is possible.
[/quote]

That's correct. The Arduino is central and the peripheral is the glasses. I was able to use LightBlue to connect to the glasses but not as the same time as Arduino. I could send the CLEAR HEX command from above code and it did clear the display but the other commands were not working - which indicates the command might be incorrect.

I'm still not able to have the Arduino send the CLEAR command successfully. The full command is 5 bytes but a long only stores 4 bytes so I used two longs and called the .writeValue twice. Is that not the correct way to separate the message?

Try this

const byte clearCommand[5] = { 0xFF, 0x01, 0x00, 0x05, 0xAA };
rxActiveLookCharacteristic.writeValueBE(clearCommand,5,0);

Are you certain that the glasses are expecting big endian?

I received a lot of complier errors by adding
const byte clearCommand[5] = { 0xFF, 0x01, 0x00, 0x05, 0xAA };
rxActiveLookCharacteristic.writeValueBE(clearCommand,5,0);

I changed
BLELongCharacteristic rxActiveLookCharacteristic(rxActiveLookCharacteristicUUID, BLEWrite | BLERead);
to
BLECharacteristic rxActiveLookCharacteristic(rxActiveLookCharacteristicUUID, BLEWrite, 5);

It complied fine but still did not receive the clear message. The API for the glasses states they are big endian but changing the BLECharateristic type did not have a writeValueBE option only writeValue. I'm assuming it sends big endian by default? How can I verify the characteristic is set up correctly? Updated code posted below.

#include <ArduinoBLE.h>

// Define ActiveLook advertising data
const char* deviceNamePrefix = "A.LooK";
const char* manufacturerName = "Microoled";
const char* serialNumber = "090105";   // Replace "xxxxxx" with the actual serial number
const int manufacturerDataLength = 9;  // Length of the manufacturer data

// Custom service UUIDs
**const char* activeLookCommandsServiceUUID = "0783B03E-8535-B5A0-7140-A304D2495CB7";**
const char* firmwareUpdateServiceUUID = "0000FEF5-0000-1000-8000-00805F9B34FB";

// ActiveLook command characteristic UUIDs
const char* txActiveLookCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CB8";
**const char* rxActiveLookCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CBA";**
const char* controlCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CB9";
const char* gestureEventCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CBB";
const char* touchEventCharacteristicUUID = "0783B03E-8535-B5A0-7140-A304D2495CBC";

// ActiveLook command descriptor UUIDs

// BLE advertising parameters
const int initialAdvertisingInterval = 25;

**BLEService activeLookCommandsService(activeLookCommandsServiceUUID);**  //Creates the service
BLEService firmwareUpdateService(firmwareUpdateServiceUUID);

BLECharacteristic txActiveLookCharacteristic;
**BLECharacteristic rxActiveLookCharacteristic(rxActiveLookCharacteristicUUID, BLEWrite, 5);**  //Creates the RX characteristic
BLECharacteristic controlCharacteristic;
BLECharacteristic gestureEventCharacteristic;
BLECharacteristic touchEventCharacteristic;

BLEDescriptor ServerRXData("2901", "Server RX Data");  //Not sure if this is needed CJ

//Long and int variable types only hold 4 bytes, need to send 20 or more bytes

const long HELLO_TEXT = 0xFF3700140098008000020F68656C6C6F203000AA;  //20 Bytes, Demo hello command form AL API
int clearScreen[] = { 0xFF, 0x01, 0x00, 0x05, 0xAA };
const unsigned long CLEAR = 0xFF010005;                   //FF010005                        //5 Bytes, Clear Display command from AL API
const long CLEAR_B = 0b11111111000000010000000000000101;  //first 4 bytes clear command in binary
const long END = 0xAA;
const long END_B = 0b10101010;  // last byte of clear command (footer) of clear command in binary

//Approaching command in HEX array
long approaching[] = { 0xFF, 0x37, 0x00, 0x1A, 0x00, 0x98, 0x00, 0x80, 0x00, 0x02, 0x0F, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x61, 0x63, 0x68, 0x69, 0x6E, 0x67, 0x20, 0x21, 0x00, 0xAA };

//Approaching Command, Separated into 4 bytes, 26 bytes total
//FF	37	00	1A	00	98	00	80	00	02	0F	41	70	70	72	6F	61	63	68	69	6E	67	20	21	00	AA
const long APP1 = 0xFF370014;
const long APP2 = 0x00980080;
const long APP3 = 0x00020F41;
const long APP4 = 0x7070726F;
const long APP5 = 0x61636869;
const long APP6 = 0x6E672021;
const long APP7 = 0x00AA;

//Departing Command, Separated into 4 byte, 23 bytes total
//FF	37	00	23	00	98	00	80	00	02	0F	44	65	70	61	72	74	69	6E	67	20	00	AA
const long DEP1 = 0xFF370014;
const long DEP2 = 0x00980080;
const long DEP3 = 0x00020F44;
const long DEP4 = 0x65706172;
const long DEP5 = 0x74696E67;
const long DEP6 = 0x2000AA;

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");
    while (1)
      ;
  }

  Serial.println("Bluetooth® Low Energy Central - Peripheral Explorer");

  // start scanning for peripherals
  BLE.scan();
}

void loop() {
  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available();

  if (peripheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.print(peripheral.advertisedServiceUuid());
    Serial.println();

    // see if peripheral is a LED
    if (peripheral.localName() == "ENGO 2 090145") {
      // stop scanning
      BLE.stopScan();

      explorerPeripheral(peripheral);  //Connects only, removed all other functions
    }

    // Add services
    //BLE.setAdvertisedService(activeLookCommandsService);
    //BLE.setAdvertisedService(firmwareUpdateService);

    // Add characteristics to services
    activeLookCommandsService.addCharacteristic(txActiveLookCharacteristic);
    **activeLookCommandsService.addCharacteristic(rxActiveLookCharacteristic);**
    // activeLookCommandsService.addCharacteristic(controlCharacteristic);
    //activeLookCommandsService.addCharacteristic(gestureEventCharacteristic);
    //activeLookCommandsService.addCharacteristic(touchEventCharacteristic);

    // Add descriptor(s) to rx characteristic
    //rxActiveLookCharacteristic.addDescriptor(ServerRXData);


    if (peripheral.connected()) {
      //for (int i = 0; i < 26; i = i + 1){
      //rxActiveLookCharacteristic.writeValue(clearScreen[i]);
      //rxActiveLookCharacteristic.writeValue(CLEAR);
      //rxActiveLookCharacteristic.writeValue(END);
      //Serial.print(CLEAR, HEX);
      //approachingDisplay();
      clearDisplay();
    }
  }
  **clearDisplay();**
  //Serial.println(rxActiveLookCharacteristic.value(), HEX);
  //Serial.println(APP2, HEX);
  //Serial.println(CLEAR, HEX);
}

void clearDisplay() {
  **const byte clearCommand[5] = { 0xFF, 0x01, 0x00, 0x05, 0xAA };**
**  rxActiveLookCharacteristic.writeValue(clearCommand, 5, 1);**
  Serial.println("clear sent");

  // rxActiveLookCharacteristic.writeValueBE(CLEAR_B);
  // rxActiveLookCharacteristic.writeValueBE(END_B);
}

void approachingDisplay() {

  for (int i = 0; i < 26; i = i + 1) {
    rxActiveLookCharacteristic.writeValue(approaching[i]);
    Serial.println(approaching[i], HEX);
  }
  //Serial.println(rxActiveLookCharacteristic.value(), HEX);
  /*rxActiveLookCharacteristic.writeValue(APP1);
  rxActiveLookCharacteristic.writeValue(APP2);
  rxActiveLookCharacteristic.writeValue(APP3);
  rxActiveLookCharacteristic.writeValue(APP4);
  rxActiveLookCharacteristic.writeValue(APP5);
  rxActiveLookCharacteristic.writeValue(APP6);
  rxActiveLookCharacteristic.writeValue(APP7);*/
}

void departingDisplay() {
  rxActiveLookCharacteristic.writeValue(DEP1);
  rxActiveLookCharacteristic.writeValue(DEP2);
  rxActiveLookCharacteristic.writeValue(DEP3);
  rxActiveLookCharacteristic.writeValue(DEP4);
  rxActiveLookCharacteristic.writeValue(DEP5);
  rxActiveLookCharacteristic.writeValue(DEP6);
}

void explorerPeripheral(BLEDevice peripheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");

  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }
}

void canWrite(BLEDevice peripheral) {
  if (rxActiveLookCharacteristic.canWrite()) {
    Serial.println("characteristic is writable");
  } else {
    Serial.println("characteristic is NOT writable");
  }
}

void printServiceUUID(BLEDevice peripheral) {
  // print the advertised service UUIDs, if present
  if (peripheral.hasAdvertisedServiceUuid()) {
    Serial.print("Service UUIDs: ");
    for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) {
      Serial.print(peripheral.advertisedServiceUuid(i));
      Serial.print(" ");
    }
    Serial.println();
  }
}

Your code will not compile with the ** . I assume you want to comment out some code. Can you do that with either // or /* */.

I also don't understand this residual line from code that looks like the Nano33BLE is a peripheral. It does not appear in any of my central code examples.

// Add characteristics to services
activeLookCommandsService.addCharacteristic(txActiveLookCharacteristic);

The API for the glasses states they are big endian but changing the BLECharateristic type did not have a writeValueBE option only writeValue. I'm assuming it sends big endian by default?

As I understand the library, the default is little endian.

I really don't know another better way to send an array than

const byte clearCommand[5] = { 0xFF, 0x01, 0x00, 0x05, 0xAA };
rxActiveLookCharacteristic.writeValueBE(clearCommand,5,0);

If there is an issue with the endianess you could try

const byte clearCommand[5] = { 0xAA, 0x05, 0x00, 0x01, 0xFF};
rxActiveLookCharacteristic.writeValue(clearCommand,5,0);

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.