arduino-libraries / ArduinoBLE

ArduinoBLE library for Arduino
GNU Lesser General Public License v2.1
313 stars 205 forks source link

What is the correct approach to transmit rapidly changing real-time data? #35

Closed armsp closed 5 years ago

armsp commented 5 years ago

I have modified the BatteryMonitor example to send the accelerometer data instead - by removing the map function and the 200ms timer. It seems to work. And when I write the float values of accelerometer values to the characteristic using writeValue(x); I don't get any errors HOWEVER the value received on the other side i.e a central device like my App or WebBluetooth is always integer and positive. Any idea why that is happening?

More importantly is this the correct way to send float values?

I am also thinking if modifying the BatteryMonitor example is the correct approach to transmit rapidly changing real-time data. If it isn't then could you please guide me/point me to some resources so that I can contribute an example for the official library?

armsp commented 5 years ago

So, my bad. I can see that there is a BLEFloatCharacteristic too. Unfortunately the problem persists. I think i may have to check the receiving end if it can handle float properly or not. But the second question still stands -

if modifying the BatteryMonitor example is the correct approach to transmit rapidly changing real-time data.

alexisicte commented 5 years ago

First of all, I recommend to check the endianness on both sides. The BLEFloatCharacteristic is working fine as the others characteristic types. You can try the nrf connect app from Nordic, it is quite stable. A cool way, but not an optimized one, to send real time data is to create a String Characteristic per sensor (accelerometer + gyroscope) with a specific delimeter, e.g. acc_x_axis|acc_y_axis|acc_z_axis. It is working perfectly and the nrf connect app converts string characteristics instantly which is perfect for debugging.

armsp commented 5 years ago

@alexisicte I use the same app for debugging. I haven't tried the String Characteristic yet, but may have to do so future. I just wanted to get the Float characteristic working. Following is the observation i made -

Serial Monitor Received value in JavaScript
1.22 5.933522291385966e-39
1.16 1.0338756247714607e-38
0.92 8.854321548098327e-39
0.73 1.177619700203129e-38
1.28 1.528288335057325e-39
1.46 1.1822114750110285e-38
6.53 2.321725105554387e-38

Can we infer something from this?

alexisicte commented 5 years ago

I used this calculator , so: 5.933522291385966e-39 -> 0x00 40 9c 3f -> 0x3f 9c 40 00 -> 1.2207 which is the same as on serial monitor. I believe you can change that as stated on CTRL_REG8 [BLE] register on p.53 LSM9DS1 datasheet. As expected you are getting your data correctly.

sandeepmistry commented 5 years ago

I'm closing this for now, as it doesn't appear to be an issue with the library.

If you think something is wrong or documentation needs improvement, please provide a sketch to reproduce/outline the issues.

@alexisicte thanks so much for helping out here!

armsp commented 5 years ago

@alexisicte Oh, nice sleuthing over there. Let me figure out the register part.
Does it mean that while printing Serial.print does some calculations which don't apply when transmitting? But why does it only happen for float and not other data types? Just need a little bit of closure here.

alexisicte commented 5 years ago

@armsp No there not any calculations, we can say that both arduino core and LSM9DS1 are using the same endianness. If a MSB system (like your js app, therefore the machine that executing the js app) tries to read this values will occur the "misunderstanding". I am not able to answer why this is happening only on float values. A good point to start is BLEFloatCharacteristic

tigoe commented 5 years ago

On the realtime data, the best approach with BLE is to set up individual characteristics for each value, and to read them that way. It's more complex than serial was, but when you do it that way, simplifies debugging.

That said I had similar problems with the float values, and at first I suspected my receiving code (js in the browser, using p5.ble.js. But this thread made me want to test more. I don't have an answer yet, but I do have a test (below) and some interesting results.

The test code below is written to send three characteristics every three seconds when a central is connected. If an attached pushbutton is pressed, it stops sending, but maintains the connection. This lets me watch the values come in nice and slow, and check them in the nRF app, in BlueSee or LightBlue (because I'm working on a Mac), or in js if I want.

I haven't tested it with js yet, and all the test apps I'm using just show the raw bytes values in hex code, so this is not conclusive, but there's an interesting pattern: the second and third characteristics are the same value for awhile, even though they are different values in the Arduino's firmware, by a difference of 1.0. It seems to stay this way until the first characteristic reaches a value of about 1.38.

So, this needs more testing, but here is a test you can run:

pinging @sandeepmistry and @facchinm as well, since they might have input.

/*
  BLE Float Test
   send three characteristics every three seconds when a central is connected.
   If an attached pushbutton on pin 2 is pressed, it stops sending,
   but maintains the BLE connection.
   This lets you watch the values come in nice and slow,
   and check them in the central app.
   To see data coming through in realtime, comment out the following
   if statement:
     if (millis() - lastUpdate > 3000 ) {
     }

  created 7 Oct 2019
  by Tom Igoe
*/
#include <ArduinoBLE.h>

BLEService orientationService("23ce3f92-be01-11e9-9cb5-2a2ae2dbcce4");
const char localName[] = "myNano33IoT";

// create characteristics for firstVar, secondVar, thirdVar
// and allow remote device to read and get notifications about them:
BLEFloatCharacteristic firstCharacteristic("23ce450a-be01-11e9-9cb5-2a2ae2dbcce4", BLERead | BLENotify);
BLEFloatCharacteristic secondCharacteristic("23ce4276-be01-11e9-9cb5-2a2ae2dbcce4", BLERead | BLENotify);
BLEFloatCharacteristic thirdCharacteristic("23ce43ca-be01-11e9-9cb5-2a2ae2dbcce4", BLERead | BLENotify);

long lastUpdate = 0;  // timestamp for updates
float firstVar = 1.0;
float secondVar = 2.0;
float thirdVar = 3.0; // variables to test

void setup() {
  Serial.begin(9600);
  // begin BLE:
  if (BLE.begin()) {
    // set advertised local name and service UUID:
    BLE.setLocalName(localName);
    BLE.setAdvertisedService(orientationService);

    // add the characteristics to the service:
    orientationService.addCharacteristic(firstCharacteristic);
    orientationService.addCharacteristic(secondCharacteristic);
    orientationService.addCharacteristic(thirdCharacteristic);

    // add service and start advertising:
    BLE.addService(orientationService);
    BLE.advertise();
  } else {
    Serial.println("starting BLE failed.");
    // stop here if you can't access the BLE radio:
    while (true);
  }
  // there's a pushbutton on pin 2:
  pinMode(2, INPUT_PULLUP);
}

void loop() {
  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  // if a central is connected to this peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());

    // while the central is still connected to peripheral:
    while (central.connected()) {
      update();
    }

    // when the central disconnects, print it out:
    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
  }
}

// updates values every 3 seconds, if pushbutton is not pressed:
void update() {
  int pushButtonState = digitalRead(2);
  if (pushButtonState == HIGH) return;
  if (millis() - lastUpdate > 3000 ) {
    lastUpdate = millis();
    // update the firstVar, secondVar and thirdVar:
    thirdVar += 0.01;
    secondVar  += 0.01;
    firstVar += 0.01;
    Serial.print("values: ");
    Serial.print(firstVar);
    Serial.print(" ");
    Serial.print(secondVar);
    Serial.print(" ");
    Serial.println(thirdVar);

    // update the BLE characteristics with the orientation values:
    firstCharacteristic.writeValue(firstVar);
    secondCharacteristic.writeValue(secondVar);
    thirdCharacteristic.writeValue(thirdVar);
  }
}
sandeepmistry commented 5 years ago

For the JS side, @8bitkick has a nice demo app here to parse the floats: https://github.com/8bitkick/8bitkick.github.io/blob/master/ArduinoBLE-IMU.html#L102-L109

They are sent in little endian IEEE 754 format: https://en.wikipedia.org/wiki/Single-precision_floating-point_format

tigoe commented 5 years ago

That did the trick for me, thanks @sandeepmistry. When I added the littleEndian to the getFloat32() in p5.ble, everything worked fine. I'll get a PR into p5.ble and get things updated.

armsp commented 5 years ago

@tigoe So does that mean if you make the following changes - myBLE.read(myCharacteristic, "float32", gotValue); to js file and

case 'float32':
      result = data.getFloat32(0, 'littleEndian');
      break;

to parseData.js file inside utils it works for you?
Cause now I get an error which I never got before -

Uncaught (in promise) RangeError: Offset is outside the bounds of the DataView

And

p5.js says: text() was expecting String|Object|Array|Number|Boolean for parameter #0 (zero-based index), received an empty variable instead. If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html] at file:///home/xxx/xxx/xxx.html:120:7. [http://p5js.org/reference/#p5/text]

Please let me know if I am missing anything.

tigoe commented 5 years ago

I didn't make the first change you mentioned, only the change to the case 'float32' in the parseData.js. But I didn't set it to 'littleEndian', I set it to true as mentioned in the example @sandeepmistry cited.

I had already loaded p5.ble.min.js so I cheated and just searched out the change in the minified file. But the way you mention is more or less how it needs to change for the p5.ble library, except for the change from littleEndian to true.

armsp commented 5 years ago

@tigoe the first change stems from this doc where they say how to use different datatypes in the comments.
Moreover in the js file you can clearly see that by default it takes the datatype as uint8 which is certainly what we don't want and moreover it works fine with int and uint.

I think even I am cheating because I change the dataParse.js file in Chrome->Sources . But the errors are the same irrespective of littleEndian or true. I am not sure if there is an issue with my code....i don't think so because it gets the correct values when there are integers to pass around.

Let me make a repo and share the details with you in some time.

armsp commented 5 years ago

@tigoe I tested by manually downloading all the libraries and including them. Both 'littleEndian' and true work with getFloat32 and the values are as they appear in Serial Monitor. Thanks a lot @sandeepmistry, your input really helped to get this done.

Manually changing them in Chrome->Sources gives an uncaught syntaxerror unexpected token export in dataParse.js. So I just have to figure out how to make it work by including scripts from CDN.

I will publish and link the example that I am working with by tomorrow. Its a real time graphing thing I made which might be useful. _home_walker_Desktop_web_bluetooth_dygraph_plotting_web_bluetooth_dygraph_working_2 html (1)

armsp commented 5 years ago

@tigoe @sandeepmistry Here is an online demo for the real time accelerometer data plotting. My website also has info on how to get everything working as well as the sketch to upload on the Nano 33 BLE.

I will continually update the website with all the experiments I have been able to run on Nano 33 BLE. The community can find it very useful. Many more cool experiments coming soon.

Since the p5.ble.js library has made a new release that solves this, should I close this issue?

sandeepmistry commented 5 years ago

@armsp thank you for the update, as there are no issues with this library I will go ahead and close this issue.