Bluetooth client speed and buffering issue

My first post, and sorry for probable mistakes in English!

I am working on a university project that receives ECG (electrocardiogram) signals, digitize them by ESP32 module, and sends data over Bluetooth to an android app. (i am currently testing on Huawei mate 10 with android 8.0)
The app is supposed to draw a graph, store data, and so on.

The data send rate should ideally be 100 numbers per second. although 50 is also acceptable.
Each number is a 12 bit reading from ADC on the ESP32 side. But I can map it to one byte (0-255).
The transmission is to be only one-way, always from ESP32 to the phone.
I program the ESP32 by Arduino IDE.

Everything is going nice except one thing:
The graph is not true real-time. It shows data with about 4-5 seconds delay. The Bluetooth client looks to be slow. Although no data is missed, but the data seem to be stored in an invisible buffer and goes in a queue to be displayed.
When I turn off the ESP32 (transmitter) , the graph continues to show data for a few seconds and then stops.
Clock timer is set to 5.

If I slow down the sending rate, say less than 50 sample/sec , it will run in real-time.

At first I thought the drawing bock of the AI2 is slow. So I deleted everything like canvas, calculations, etc. and rewrote it to just read BT and print the number in a textbox. But the result was the same: data keeps coming after I turn off the transmitter!

My assumptions are:
1- the BT reading procedure is not properly selected
2- the BT transmission protocol in ESP32 is not properly selected
3- the two protocols do not match properly
4- All of the above!

Can anybody please guide me on this?

ESP32 code:

#include "BluetoothSerial.h"
#define LED_BUILTIN 2 //pin with LED to turn on when BT connected
BluetoothSerial ESP_BT; // Object for Bluetooth
// global vars
long oldmillis;
boolean BT_cnx = false;
byte a;
void callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
if (event == ESP_SPP_SRV_OPEN_EVT) {
// Serial.println("Client Connected");
digitalWrite(LED_BUILTIN, HIGH);
BT_cnx = true;
}

if (event == ESP_SPP_CLOSE_EVT ) {
// Serial.println("Client disconnected");
digitalWrite(LED_BUILTIN, LOW);
BT_cnx = false;
ESP.restart();
}
}

void setup() {
// initialize digital pin 2 as an output.
pinMode(LED_BUILTIN, OUTPUT);

ESP_BT.register_callback(callback);
if (!ESP_BT.begin("SN_Ez_ECG")) {
//Serial.println("An error occurred initializing Bluetooth");
} else {
// Serial.println("Bluetooth initialized... Bluetooth Device is Ready to Pair...");
}
}

void loop() {

if (millis() - oldmillis >= 20)
{
oldmillis = millis();

a = map(analogRead(A0), 0, 4096.0, 0, 255);

ESP_BT.write(a);

}
}

Hi @Sirous_Nekooei, Welcome to the mit app inventor community, for the first time you create a topic you must read this first :grin:

Therein is your answer - I think, +- a little, you have hit the limit of your Mate 10 processing speed. The App has to process, display (and store?) the data - in a typical ESP32 project, we would allow a far more generous period of time for each data packet sent. Also, you might notice a reduction in speed over a short period of time, as the hardware temperature increases. However, you can experiment with both the send and receive speeds. App receive speed (Clock Timer) should be approx 20% faster than ESP32 send speed. Since you are sending only a single number at a time, that 20% is tweakable. You might need to fit a small cooling fan to the ESP32 if you have not already done so.

You are working with a phone, not a mainframe computer! Why is this the ideal? It is far faster than what the human eye can tolerate.

I think your expectation of an Android Phone and Bluetooth is probably too high. Is there a special reason to work with a phone rather than, say, a screen directly connected to the ESP32? Also, can you test a faster phone? For maximum transmission speed, both the ESP32 module and the phone should support the latest edition of BLE.

Your English by the way, is excellent.

Dear all,
probably the following will not help at all, but since I've developed a couple of applications receiving data on BT from an Arduino board, maybe taking a look to my blocks can be of some hint.
The following block receives a frame of 44 HEX bytes represented by ASCII characters (thus receivetext is used).
The sending rate is : one frame every 150 ms. Since the clock1 period is 100 ms it should be capable to get all data. As far as I can see on my application, the real time seems to be working fine. Please be aware that I show some received data in a numerical form, while some others in a graphical form. The app runs on a Lenovo tablet and on a "super-cheap chinese no brand" tablet, both featuring Android 9.
Hoping this does not increase ... entropy :grin:
Cheers

Here I am again ...
This block receives from an ELM327 some data coming from an engine contro unit of my car.
In this case the BT receiving block waits until the ASCII character ">" is received.
This means that if you want to trigger a receiving time slot, you can set on your Arduino sending code an "end of frame character" and you can trigger the App in order to stop waiting when it receives the ending character. In this way you should be able to synchronize the TX on Arduino and the Rx on the phone.
Else, you can always cross your fingers.... :upside_down_face: and hope it works..
(sorry for the joke).
Cheers.

Thank you for contribution, but my transmit speed (10 ms interval) is far away from yours.

Dear Chris,

Thank you so much for your attention and input. Couple of thoughts:

1-BT speed seems to be much faster than I need (at least theoretically). Consider when you play a music from your phone to BT speaker, thousands of Bytes/sec is transmitted. 100 Bytes/sec is nothing!

Actually we need 100 sample/sec because it is required by industry standards. If you are familiar with ECG, there is a QRS peak that is very sharp. If we sample less than 100/sec, we may miss some information specially at the top of the peaks.

Obviously we don't need to refresh the graph 100 times per second; this is not possible due to display refresh rate.
I decided to receive 4 numbers at once, then use "canvas drawShape" to connect 4 points at every screen refreshments. But unfortunately it didnt work because DrawShape "closes" the shape and I ended up with a bunch of triangles instead of a graph! (is there a way to draw a shape with open ends?)

Hi,
why don't you buffer on Arduino ? If you transmit 100 bytes per second, and you transmit every 10 ms, this leads to 1 byte each 10 ms. Am I wrong ?
If not, and since you don't need to have such a high display rate, but you plot only every 4 bytes (then 40 ms ?), why don't you transmit 4 bytes every 40 ms ? By using a 115200 bps tx rate, every byte is 100 microseconds (more or less), then to transmit 4 bytes you only need 400 us. then you have plenty of time to do the graph.
By using this phylosophy, why you don't buffer data on Arduino and you send only when you have enough data to plot, without missing the real time ? I..e. you buffer 50 bytes on Arduino, then you transmit them @115200 to the phone , which takes 100 microsecond for 1 byte, so 5 milliseconds for 50 bytes , then you have plenty of time to plot them on a graph. Still in other words: you can buffer on Arduino, then transmit an amount of data which is the best compromise between quantity and a fast drawing/plot). Definitely you transfer the Real time acquisition and buffering to Arduino, leaving to the app only the plotting task.
Here below how I plot the speed (or rpm) values of my car. I use a drawline instead of a drawshape.
When I reach the end of the canvas (the screen width) I clear the canvas and I restart to plot from the left edge of the screen.
Cheers

Yes! this is a good idea. Albeit if "drawline" is not very time consuming.

But i dont know how to package four bytes in the arduino side and send them together, and how to separate them in the in the app side.

If you know, pls help me with a small example.

Thanks so much

I think you could send as a stream of delimited values:

byte a[4];
int i = 1;

void loop() {

         if (millis() - oldmillis >= 10)
         {
               oldmillis = millis();

                    a[i] = map(analogRead(A0), 0, 4096.0, 0, 255);

                       i = i + 1;

               If(i > 4)
               {
                         i = 1;

                         ESP_BT.write(a[1]);
                         ESP_BT.write('|');
                         ESP_BT.write(a[2]);
                         ESP_BT.write('|');
                         ESP_BT.write(a[3]);
                         ESP_BT.write('|');
                         ESP_BT.write(a[4]);
               }
         }
}

......and split that string into individual values in the App:

All of the above not tested!

.... In actual fact, although I expect the string to work, if the value separator in the ESP32 Script is changed from '|' (bar) to ',' (comma), then the App should receive a list of bytes:

Thank you so much. I'll try both of them.
Shoud I define delimiter of the clock to "|" or ","?

If using the string version, use "|" - but I would try the Bytes version first, using commas, which would be faster as there is no need to split the data at the phone end.

Have not seen all your code, but as you can see from my code snippet, a Clock Timer may not be required if you use the 'Register for Bytes" Block instead.

... note also that I have shown a BLE solution using the MIT BLE extension. Your sending device may not be compatible with BLE. If not, the code can be adapted for Classic BT but you do then need a Clock Timer. The Script also needs to send an "End of Data Packet" character, and the App needs to know beforehand what that character is (usually we use Ascii char 10 = #LF (Line-feed).

If it is possible to use BLE, there should be speed benefits.

Yes, I noted you used BLE. Now, I am starting to learn BLE because I always wanted to learn it! (ESP32 supports BLE)
Meanwhile, I want to keep this existing version that use classical BT. Because some users phones may not have BLE.
So, could you pls write the code for standard BT?
I tried to change your code for standard BT, but i got error.
(the "byte" version is preferred)

Hi,
sorry not having answered before, but I read mails only in the evening (here in Italy is 8:20 PM).
I saw that Chris has already told you a lot of hints.
Anyway If you need pieces of my Arduino code, I will send them tomorrow morning.
Tonite I'm busy with... my family :slight_smile:
Have a great evening.

Kind of. I have nothing to test the code with right now, so no doubt you will need to fix my bugs! Note, if the connection works and data streams but starts to fail, tweak the milliseconds up in both the Script and the App. A faster phone could make a difference.

Script snippet:

ScriptSnippet.txt (732 Bytes)

byte a[4];
int i = 0;

void loop() {

         if (millis() - oldmillis >= 10)
         {
               oldmillis = millis();

                    a[i] = map(analogRead(A0), 0, 4096.0, 0, 255);

                       i = i + 1;

               If(i > 3)
               {
                         i = 0;

                         ESP_BT.write(a[0]);
                         ESP_BT.write(',');
                         ESP_BT.write(a[1]);
                         ESP_BT.write(',');
                         ESP_BT.write(a[2]);
                         ESP_BT.write(',');
                         ESP_BT.write(a[3]);
                         ESP_BT.write('\n');
               }
         }
}

App Code:
I have butchered my BT Basic Template that has been successfully used by many people, but that does not mean the butchered version will work as expected :upside_down_face

Note, Test as an APK, not via the Companion.

Tips:

  1. Do not run from the finished install panel displayed, run via the App Icon.
  2. If an eligible device is within 2-3 metres of your phone/tablet, it probably won't be found (Bluetooth flaw).

BT_4ValsStreamIn.aia (12.8 KB)

(Click on the image to view at full scale)

Edit: Script snippet BT packet array corrected.
Edit2: Script snippet file updated

Dear Chris,
maybe I'm wrong but pay attention to the size of the array: in C they start from element 0 so if the dimension declared is 4 , element 4 does not exist, and the array ends at 3.
You better declare: byte a[5];
It costs nothing...
Cheers.

Hi Uskiara

You are correct - in fact it's better to use element 0 of an array of 4. I'm using too many programming languages at the same time :innocent: