I'm trying to send sensor readings over BLE to an app I created using MIT app inventor and something is not working, my guess is I'm missing something in my sketch or misunderstanding on how BLE works.
This is the sketch I use:
#include <ArduinoBLE.h>
#define BLE_BUFFER_SIZES 20
#define BLE_DEVICE_NAME "DTD"
#define BLE_LOCAL_NAME "DTD_1"
int red = 11;
int green = 10;
int blue = 9;
float sensor = A0;
float cf = 19.5; // caliberation factor
float pressureReading;
BLEService BLESensors("00001101-0000-1000-8000-00805F9B34FB");
BLECharacteristic pressureBLE("2A19", BLERead | BLENotify | BLEBroadcast, BLE_BUFFER_SIZES);
void setup()
{
LedColor (0,255,0);
Serial.begin(9600);
while (!Serial);
// begin initialization
if (!BLE.begin())
{
Serial.println("starting BLE failed!");
while (1);
}
// set the local name peripheral advertises
BLE.setDeviceName(BLE_DEVICE_NAME);
BLE.setLocalName(BLE_LOCAL_NAME);
BLE.setAdvertisedService(BLESensors);
BLESensors.addCharacteristic(pressureBLE);
BLE.addService(BLESensors);
// start advertising
BLE.advertise();
Serial.println(("Bluetooth device active, waiting for connections..."));
}
void loop()
{
// listen for BLE peripherals to connect:
BLEDevice central = BLE.central();
// if a central is connected to 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())
{
LedColor (0,0,255);
for (int i=0;i<10;i++)
{
float Reading = analogRead(sensor);
pressureReading = pressureReading + Reading;
delay (50);
}
pressureReading = pressureReading / 10;
pressureReading = (pressureReading * 5.0) / 1023.0;
pressureReading = pressureReading * cf;
Serial.println(pressureReading);
pressureReading = 0;
}
// when the central disconnects, print it out:
Serial.print(F("Disconnected from central: "));
Serial.println(central.address());
LedColor (255,0,0);
}
}
void LedColor(int redL, int greenL, int blueL)
{
analogWrite(red, redL);
analogWrite(green, greenL);
analogWrite(blue, blueL);
}
and this is a small app I created to check if I recive the data:
Can you please modify your original post to ensure the source code is in a single box. It should look like this.
// Your example source code
You need three ' at the beginning and end in the edit window? When you click on this icon </> you get.
```
type or paste code here
```
This ensures we can get all the source code. Right now if you copy the code there are invisible characters that prevent successful compilation and parts might be missing.
From what I see, you do not update the characteristic with the data. See write().
Hint: Before you test any other BLE software, test your peripheral with a generic BLE app on an smartphone. I use BLE Scanner on iPhone but there are many other on iOS and Android. When that works you can look for issues in your code in MIT app inventor.
When you create a generic BLECharacteristic the last parameter is the valueSize not a buffer size. You have to write the correct amount of bytes to update the characteristic. For your case it seems better to use a characteristic of the correct type.
Here are a few other things I noted about your code:
the UUID for the characteristic is 16-bit, you should use a 128-bit random UUID
the UUID for the service is not random, you can use a few bits to group a few UUIDs but most bits should be random
you use while in loop and delay(), while this is keeps simple examples short it will prevent you from extending your sketch later without breaking what you already have. Try to ensure your loop() function is running as often as possible.
Have a look at the following example to learn to time control parts of your code
your variable naming could be improved avoid short variable names if they become cryptic or could mean different things e.g. you called a pin for a LED "red" and then you had to rename the function parameters to "readL" to avoid a naming collision. You could have use redLedPin for instance.
the following code is wrong (the compiler fixes the code in the background)
float sensor = A0; // wrong data type for pin
... analogRead(sensor) ...
I have written a little example that includes a few other techniques that might be useful.
BLE Sensor example (click to view)
/*
This example creates a BLE peripheral with a service and a characteristic.
The circuit:
- Arduino Nano 33 IoT.
You can use a generic BLE central app, like LightBlue (iOS and Android) or
nRF Connect (Android), to interact with the services and characteristics
created in this sketch.
This example code is in the public domain.
*/
#include <ArduinoBLE.h>
//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------
#define BLE_UUID_SENSOR_SERVICE "82D20000-87F8-C79D-9E63-A091F4171879"
#define BLE_UUID_PRESSURE "82D20001-87F8-C79D-9E63-A091F4171879"
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------
#define BLE_DEVICE_NAME "Arduino Nano 33 IoT"
#define BLE_LOCAL_NAME "Arduino Nano 33 IoT"
BLEService sensorService( BLE_UUID_SENSOR_SERVICE );
BLEShortCharacteristic pressureCharacteristic( BLE_UUID_PRESSURE, BLERead | BLENotify );
//----------------------------------------------------------------------------------------------------------------------
// I/O and App
//----------------------------------------------------------------------------------------------------------------------
#define BLE_LED_PIN LED_BUILTIN
#define PRESSURE_SENSOR_PIN A0
uint16_t averagePressure = 0;
bool updatePressureCharacteristic = false;
void setup()
{
Serial.begin( 9600 );
while ( !Serial );
pinMode( BLE_LED_PIN, OUTPUT );
pinMode( PRESSURE_SENSOR_PIN, INPUT );
analogReadResolution( 10 );
if ( !setupBleMode() )
{
Serial.println( "Failed to initialize BLE!" );
while ( 1 );
}
else
{
Serial.println( "BLE initialized. Waiting for clients to connect." );
}
}
void loop()
{
bleTask();
sensorTask();
}
void sensorTask()
{
#define SENSOR_SAMPLING_INTERVAL 50
#define SAMPLE_BUFFER_SIZE 10
static uint16_t sampleBuffer[SAMPLE_BUFFER_SIZE] = { 0 };
static uint32_t previousMillis = 0;
static uint32_t sampleIndex = 0;
uint32_t currentMillis = millis();
if ( currentMillis - previousMillis >= SENSOR_SAMPLING_INTERVAL )
{
previousMillis = currentMillis;
uint16_t currentPressure = analogRead( PRESSURE_SENSOR_PIN );
sampleBuffer[sampleIndex] = currentPressure;
sampleIndex = ( sampleIndex + 1 ) % SAMPLE_BUFFER_SIZE;
if ( sampleIndex == 0 )
{
uint32_t bufferTotal = 0;
for ( uint32_t i = 0; i < SAMPLE_BUFFER_SIZE; i++ )
{
bufferTotal += sampleBuffer[i];
}
averagePressure = (uint16_t) round( bufferTotal / SAMPLE_BUFFER_SIZE );
updatePressureCharacteristic = true;
}
}
}
bool setupBleMode()
{
if ( !BLE.begin() )
{
return false;
}
// set advertised local name and service UUID
BLE.setDeviceName( BLE_DEVICE_NAME );
BLE.setLocalName( BLE_LOCAL_NAME );
BLE.setAdvertisedService( sensorService );
// add characteristics
sensorService.addCharacteristic( pressureCharacteristic );
// add service
BLE.addService( sensorService );
// set the initial value for the characeristics
pressureCharacteristic.writeValue( averagePressure );
// set BLE event handlers
BLE.setEventHandler( BLEConnected, blePeripheralConnectHandler );
BLE.setEventHandler( BLEDisconnected, blePeripheralDisconnectHandler );
// start advertising
BLE.advertise();
return true;
}
void bleTask()
{
#define BLE_UPDATE_INTERVAL 10
static uint32_t previousMillis = 0;
uint32_t currentMillis = millis();
if ( currentMillis - previousMillis >= BLE_UPDATE_INTERVAL )
{
previousMillis = currentMillis;
BLE.poll();
}
if ( updatePressureCharacteristic )
{
updatePressureCharacteristic = false;
pressureCharacteristic.writeValue( averagePressure );
}
}
void blePeripheralConnectHandler( BLEDevice central )
{
digitalWrite( BLE_LED_PIN, HIGH );
Serial.print( "Connected to central: " );
Serial.println( central.address() );
}
void blePeripheralDisconnectHandler( BLEDevice central )
{
digitalWrite( BLE_LED_PIN, LOW );
Serial.print( "Disconnected from central: " );
Serial.println( central.address() );
}
Dear @Klaus_K , if the value that is being read by the pressure sensor is a float type, why should we use BLEByteCharacteristic instead of BLEFloatCharacteristic?
I´m facing the same problem of @darkpr0phet trying to send data from a load cell to mobile using a MIT app inventor developed app, and it´s not showing any data.
Checking the trasmitted data at NRFConnect, the values appear as Hexadecimal (?)
In the example from darkpr0phet he converted the pressure data to a byte. Maybe that was all that he required. Therefore, the right answer was to use a BLEByteCharacteristic.
Sensors usually do not create float data. The library may do that but that is often a compromise. Many microcontrollers must handle float in software. This costs a lot of memory and processor cycles compared to integer math. It makes writing software for beginner easier, and the drawback can be ignored because most beginners have simple sketches with limited processing requirements.
In most cases floating point data should be avoided for the BLE interface for the following reason, floating point data types can be different in one platform to another. Some implementations use IEEE-754 but others use IEEE-11073 (e.g., medical devices). Floating point HEX and Binary notation is non-intuitive for most users. Even beginners can learn to convert an integer HEX value by hand.
And the Bluetooth SIG has made use of fixed-point values for some characteristics to show good design practice e.g., temperature is defined as sint16 with a resolution of 0.01 degrees. So, for 21.5 C you would have a value of 2150.
Generic BLE apps just show you the bytes they find in a characteristic. They know how many bytes are in the characteristic, but they do not know what the format is (in most cases).
You can add descriptors to a characteristic to describe the data type, but this is most often not done and therefore BLE apps ignore that anyway.
On which platform are you using MIT app inventor. In played a bit with it but my phone uses iOS and BLE does not seem to be supported by the MIT app. I used many other BLE apps an they work (mostly ).
@Klaus_K , first of all, thanks for answering my both posts!
Ok, you got a point here. It´s all a matter of voltage variation and thus, I can set the number of significant digits by myself. The load cell signal is too low to be read directly, thus it passes through an HX711 module (amplifier and A/D converter). The sketch that I followed plugs the A/D converter output into a digital port of Arduino. Maybe if I plug it into an analogic imput (even receiving a digital signal) and map it to 0-1023 I can get a better precision and work with an integer value.
Ok, I´ll take a look on how to add descriptors to the characteristics.
Well, that´s another issue. My wife´s phone also uses IOS. I´m doing this project for her and I don´t make any idea of how to develop an app to Apple devices. This is why I considered the possibility to use a laptop. My phone uses Android and MIT Inventor can handle BLE if you download the appropriate add-on.
As I said, BLE apps ignore the presentation format descriptor so far. The User Description is shown by some apps making identifying the characteristics easier when use a generic BLE app.
I have written an example on how to implement the two most useful descriptors.
BLE Descriptor example (Click to open)
/*
This example shows how to use BLE descriptors.
The circuit:
- Arduino Nano 33 BLE and BLE Sense
- Arduino Nano RP2040 Connect
- Arduino Nano 33 IoT
You can use a generic BLE central app, like BLE Scanner (iOS and Android) or
nRF Connect (iOS and Android), to interact with the services and characteristics
created in this sketch.
This example code is in the public domain.
*/
#include <ArduinoBLE.h>
//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------
#define BLE_UUID_SENSOR_SERVICE "65C30000-8987-3D40-DF89-978B41A839C0"
#define BLE_UUID_SENSOR "65C30001-8987-3D40-DF89-978B41A839C0"
#define BLE_UUID_CHARACTERISTIC_USER_DESCRIPTION "2901"
#define BLE_UUID_PRESENTATION_FORMAT "2904"
#define BLE_UUID_GATT_UNIT_UNITLESS 0x2700
#define BLE_UUID_GATT_UNIT_VOLT 0x2728
//----------------------------------------------------------------------------------------------------------------------
// BLE Characteristic Format Descriptor
//----------------------------------------------------------------------------------------------------------------------
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Descriptors/org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
// Unfortunately, this descriptor is ignored by BLE apps so far.
typedef struct __attribute__( ( packed ) )
{
uint8_t format;
int8_t exponent;
uint16_t unit;
int8_t name_space;
int16_t description;
} presentation_format_t;
union presentation_format_u
{
struct __attribute__( ( packed ) )
{
presentation_format_t values;
};
uint8_t bytes[ sizeof( presentation_format_t ) ];
};
union presentation_format_u sensorPresentationFormat = { .values = { .format = 6, .exponent = -3, .unit = BLE_UUID_GATT_UNIT_VOLT, .name_space = 1, .description = 0 } };
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------
#define BLE_DEVICE_NAME "Arduino RP2040"
#define BLE_LOCAL_NAME "Arduino RP2040"
BLEService sensorService( BLE_UUID_SENSOR_SERVICE );
BLEUnsignedIntCharacteristic sensorCharacteristic( BLE_UUID_SENSOR, BLERead | BLENotify );
BLEDescriptor sensorPresentationFormatDescriptor( BLE_UUID_PRESENTATION_FORMAT, sensorPresentationFormat.bytes, sizeof sensorPresentationFormat.bytes );
BLEDescriptor sensorUserDescriptionDescriptor( BLE_UUID_CHARACTERISTIC_USER_DESCRIPTION, "Voltage with resolution 0.001 V" );
//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------
#define BLE_LED_PIN LED_BUILTIN
#define SENSOR_PIN A0
typedef struct __attribute__( ( packed ) )
{
uint16_t value;
bool updated;
} sensor_data_t;
sensor_data_t sensorData = { .value = 0, .updated = false };
void setup()
{
Serial.begin( 9600 );
while ( !Serial );
Serial.println( "ArduinoBLE Example - Descriptors" );
pinMode( BLE_LED_PIN, OUTPUT );
digitalWrite( BLE_LED_PIN, LOW );
pinMode( SENSOR_PIN, INPUT );
analogReadResolution( 12 );
if ( !setupBleMode() )
{
Serial.println( "Failed to initialize BLE!" );
while ( 1 );
}
else
{
Serial.println( "BLE initialized. Waiting for clients to connect." );
}
}
void loop()
{
bleTask();
sensorTask();
}
void sensorTask()
{
const uint32_t SENSOR_UPDATE_INTERVAL = 100;
static uint32_t previousMillis = 0;
uint32_t currentMillis = millis();
if ( currentMillis - previousMillis >= SENSOR_UPDATE_INTERVAL )
{
previousMillis = currentMillis;
uint16_t adcValue = analogRead( SENSOR_PIN );
sensorData.value = map( adcValue, 0, 4095, 0, 3300 );
sensorData.updated = true;
}
}
bool setupBleMode()
{
if ( !BLE.begin() )
{
return false;
}
// set advertised local name and service UUID
BLE.setDeviceName( BLE_DEVICE_NAME );
BLE.setLocalName( BLE_LOCAL_NAME );
BLE.setAdvertisedService( sensorService );
// BLE add characteristics
sensorService.addCharacteristic( sensorCharacteristic );
// BLE add descriptors
sensorCharacteristic.addDescriptor( sensorPresentationFormatDescriptor );
sensorCharacteristic.addDescriptor( sensorUserDescriptionDescriptor );
// add service
BLE.addService( sensorService );
// set the initial value for the Characteristic
sensorCharacteristic.writeValue( sensorData.value );
// set BLE event handlers
BLE.setEventHandler( BLEConnected, blePeripheralConnectHandler );
BLE.setEventHandler( BLEDisconnected, blePeripheralDisconnectHandler );
// start advertising
BLE.advertise();
return true;
}
void bleTask()
{
const uint32_t BLE_UPDATE_INTERVAL = 10;
static uint32_t previousMillis = 0;
uint32_t currentMillis = millis();
if ( currentMillis - previousMillis >= BLE_UPDATE_INTERVAL )
{
previousMillis = currentMillis;
BLE.poll();
}
if ( sensorData.updated )
{
sensorData.updated = false;
sensorCharacteristic.writeValue( sensorData.value );
}
}
void blePeripheralConnectHandler( BLEDevice central )
{
digitalWrite( BLE_LED_PIN, HIGH );
Serial.print( F( "Connected to central: " ) );
Serial.println( central.address() );
}
void blePeripheralDisconnectHandler( BLEDevice central )
{
digitalWrite( BLE_LED_PIN, LOW );
Serial.print( F( "Disconnected from central: " ) );
Serial.println( central.address() );
}
I see that I´m a little bit far from writing a good code. My programming level is a little below the necessary to understand all the statements of your code. Need to go back to the books...
Two questions that I believe are simple for now:
What does the "F" before the string to be printed?
That is a macro that tells the compiler to leave the String in Flash memory. It is important on old Arduinos because they have small RAMs. It is not needed in most new Arduinos because they have a lot more RAM. But when RAM gets filled up you can get some back.
The BLE stack needs to be called from time to time to see what is going on. Without it all the callback do not work. You might need to adjust the timing depending your application.
@darkpr0phet , we seem to be at the same point. I can find my device on Android NRFConnect and read data sent to it (in HEX format). But my developed app can´t get the data. If you solved the problem with your Android app would you mind sharing the blocks that read the data? I´m having doubts whether to use the block "callBLE.RegisterForxyz", the block "callBLE.Readxyz" or both of them...