First time poster so please be gentle! I am building a simple distance measuring and location device that is required to send three bits of data via BLE these are: Distance (as an Int), Latitude(as a float), Longtitude (as a float).
I am successfully broadcasting via BLE the int value and once I worded out it was little endian I was able to read the correct data, the LIDAR is fully connected and sending live updates successfully. My issue comes when working with the Float Values and in particular the negative float values, I've not yet connected the GPS as I wanted to test the data flow first, hence no GPS in the code below, though I will be using an Neo-M9n.
Current Output on iOS BLE Scanner App:
float lat = 51.222223 = 0x cf 96 0d 03
float lng = -2.444444 = 0x 1c fe 74 01
I have used a custom charateristic and service but have played with the Location and Speed Standard GATT libary but I couldnt make that work (my lack of knowledge) [GATT Specification Supliment](file:///C:/Users/drgod/Downloads/GATT_Specification_Supplement_v5%20(1).pdf)
I've hit the limit if my knowledge and forum searching, so any help will be gratefully received
Dave
#include <ArduinoBLE.h>
#include <TFmini_plus.h>
TFminiPlus lidar;
// BLE Mound Data Servic
BLEService moundData("19B10000-E8F2-537E-4F6C-D104768A1214");
// BLE Characteristics
BLEFLoatCharacteristic latChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEFloatCharacteristic lngChar("19B10002-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
BLEIntCharacteristic distChar("19B10003-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
//test values
float lat = 51.222223;
float lng = -2.444444;
long dist = 3355;
void setup() {
Serial.begin(9600);
// initialize serial communication
while (!Serial);
Wire.begin();
lidar.begin(0x10);
pinMode(LED_BUILTIN, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected
// begin initialization
if (!BLE.begin()) {
Serial.println("starting BLE failed!");
while (1);
}
BLE.setLocalName("Metsa Test");
BLE.setAdvertisedService(moundData); // add the service UUID
moundData.addCharacteristic(latChar);
moundData.addCharacteristic(lngChar);
moundData.addCharacteristic(distChar);
BLE.addService(moundData); // Add the service
latChar.writeValue(lat); // set initial value for this characteristic
lngChar.writeValue((long)lng); // set initial value for this characteristic
distChar.writeValue((long)dist); // set initial value for this characteristic
BLE.advertise();
Serial.println("Bluetooth device active, waiting for connections...");
}
void loop() {
// wait for a BLE central
BLEDevice central = BLE.central();
// if a central is connected to the peripheral:
if (central) {
Serial.print("Connected to central: ");
// print the central's BT address:
Serial.println(central.address());
// turn on the LED to indicate the connection:
digitalWrite(LED_BUILTIN, HIGH);
// check the battery level every 200ms
// while the central is connected:
while (central.connected()) {
getData();
}
// when the central disconnects, turn off the LED:
digitalWrite(LED_BUILTIN, LOW);
Serial.print("Disconnected from central: ");
Serial.println(central.address());
}
}
void getData() {
tfminiplus_data_t data;
bool result = lidar.read_data(data, true);
long lidarDistance = data.distance;
String output = "Distance: " + String(lidarDistance) + " mm\t";
Serial.println(output);
distChar.writeValue(lidarDistance);
latChar.writeValue(lat);
lngChar.writeValue(lng);
}
I'm no expert on unpacking little-endian hex, but I have done a lot of BLE data transfer (chars and whatnot). I have transferred floats as chars so literally "5" "1" "." "2" etc. as a series of bytes on one characteristic and then used things like atof() to convert it back. However, that was part of sending mixed packets of comma-separated data so made sense then. That could also work here.
If you wanted a quick fix, I would set all values to positive, multiply the float by enough places to remove the point (although that is what the BLE is effectively doing anyway) and set additional characteristics for the orientation (e.g., latOr and lonOr). So when your code reads the BLE you just have to get the orientation characteristic and the number and you then divide it back to the right number. Bit blunt but will do the job.
Can you please describe why you want to use floating data type?
In general floating data are not a good idea for interfaces. There are several floating point standards and they are not intuitive for most humans. The Bluetooth SIG has shown some nice alternative with some characteristics e.g., temperature for the Environmental Sensing Service.
You simply store the value as a signed or unsigned integer with a known exponent. See here:
This suggests the Bluetooth SIG may have specified a service and characteristic format you could use. Any application that knows them could then connect to your device and use the data. One quick look at the page:
@matt_lee_smith Wow! Thank you for your quick response, and super help links. I'm working through this again over the next day or so and will revery with how I got on !
@Klaus_K thank you, your example is super helpful. I'm going to be working through this today with your suggestions. The reason for a float was the formatting of a Latiude or longtitude but having read your adivise and that of @matt_lee_smith I am going to apporach this differently. perhaps I was over complicating it.
two quick supplamentary questions if I may:
Can you use a standard charateristic in a custom service and have the same outcome or do standard charateristics inherit from the profiles?
The latitude and longtitude charateristic require an sint32 data type, can I use the standard int in ardunio as you suggest:
You simply store the value as a signed or unsigned integer with a known exponent.
I will keep you updated with the coding as I get on.
The service and characteristic UUID are independent. This can be handled differently by any app. Some apps may decode the UUID in an hierarchy and not show the characteristic fully decoded if the service is unknown. Others may just look through the characteristics they know and decode/display it properly. Try a few generic BLE apps and see what happens.
The Nano 33 BLE is based on an Arm Cortex-M4 processor and therefore int will be 32-bit. I prefer to use the explicit notation like uint32_t and int32_t to make sure I get the type size I expect. sint32 is just the Bluetooth SIG way of making signed explicit in the specification, while for C/C++ compilers that is the assumed standard and unsigned must be specified explicitly.
I've coded the LocNav service using your example as a basis. It compiles with no errors, runs through to the point of waiting for the central to connect and then when I try and connect using eithe BLE Scanner (iOS) or LightBlue the connection times out or hangs and I have to re-upload to unfreeze.
Something in the setupBLE func seems to be tripping, I've aslo tried it with a generic service UUID in case this was the issue, perhaps the event hnadler - I'll come at it with fresh eyes tomorrow, but any thoughts welcome!
/*
This example creates a BLE peripheral with a Location and Navigation Service
that contains a temperature, humidity and pressure characteristic.
The yellow LED shows the BLE module is connected to a central.
The circuit:
- Arduino Nano 33 BLE Sense
You can use a generic BLE central app, like BLE Scanner (iOS and Android) or
nRF Connect, to interact with the services and characteristics
created in this sketch.
This example code is in the public domain.
*/
#include <ArduinoBLE.h>
#include <Wire.h> //Needed for I2C to GNSS
#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS
#include <TFmini_plus.h>
//----------------------------------------------------------------------------------------------------------------------
// Sparkfun GPS and TFMiniPlus LIDAR
//----------------------------------------------------------------------------------------------------------------------
SFE_UBLOX_GNSS myGNSS;
TFminiPlus lidar;
//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------
// https://www.bluetooth.com/specifications/assigned-numbers/environmental-sensing-service-characteristics/
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.latitude.xml
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.longtitude.xml
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.elevation.xml
#define BLE_UUID_LOC_NAV_SERVICE "1819" // Location and Navigation Service
#define BLE_UUID_LATITUDE "2AAE" // sint32 WGS84 North coordinate
#define BLE_UUID_LONGITUDE "2AAf" // sint32 WGS84 East coordinate
#define BLE_UUID_ELEVATION "2A6C" // sint24 used in this as the LIDAR distance 0.01m resolution
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------
#define BLE_DEVICE_NAME "Arduino Nano 33 BLE"
#define BLE_LOCAL_NAME "Arduino Nano 33 BLE"
BLEService locNavService( BLE_UUID_LOC_NAV_SERVICE );
BLEUnsignedLongCharacteristic latitudeCharacteristic( BLE_UUID_LATITUDE, BLERead | BLENotify );
BLEUnsignedLongCharacteristic longitudeCharacteristic( BLE_UUID_LONGITUDE, BLERead | BLENotify );
BLEUnsignedLongCharacteristic elevationCharacteristic( BLE_UUID_ELEVATION, BLERead | BLENotify );
//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------
#define SENSOR_UPDATE_INTERVAL (5000)
typedef struct __attribute__( ( packed ) )
{
float latitude;
float longitude;
float elevation;
bool updated = false;
} sensor_data_t;
sensor_data_t sensorData;
#define BLE_LED_PIN LED_BUILTIN
void setup()
{
Serial.begin( 9600 );
while ( !Serial );
Wire.begin();
lidar.begin(0x10);
Serial.println( "BLE Example - Location Navigation Service (LNS)" );
if ( !setupBleMode() )
{
Serial.println( "Failed to initialize BLE!" );
while ( 1 );
}
else
{
Serial.println( "BLE initialized. Waiting for clients to connect." );
}
pinMode( BLE_LED_PIN, OUTPUT );
digitalWrite( BLE_LED_PIN, LOW );
myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise)
myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR
}
void loop()
{
bleTask();
if ( sensorTask() )
{
printTask();
}
}
bool sensorTask()
{
static uint32_t previousMillis = 0;
uint32_t currentMillis = millis();
if ( currentMillis - previousMillis < SENSOR_UPDATE_INTERVAL )
{
return false;
}
previousMillis = currentMillis;
tfminiplus_data_t myLidar;
sensorData.latitude = myGNSS.getLatitude();
sensorData.longitude = myGNSS.getLongitude();
sensorData.elevation = myLidar.distance;
sensorData.updated = true;
return sensorData.updated;
}
void printTask()
{
Serial.print( "Latitude = " );
Serial.print( sensorData.latitude);
Serial.println( " °" );
Serial.print( "Longitude = " );
Serial.print( sensorData.longitude);
Serial.println( " °" );
Serial.print( "Lidar Distance = " );
Serial.print( sensorData.elevation);
Serial.println( " mm" );
}
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( locNavService );
// BLE add characteristics
locNavService.addCharacteristic( latitudeCharacteristic );
locNavService.addCharacteristic( longitudeCharacteristic );
locNavService.addCharacteristic( elevationCharacteristic );
// add service
BLE.addService( locNavService );
// set the initial value for the characeristic
latitudeCharacteristic.writeValue( 0 );
longitudeCharacteristic.writeValue( 0 );
elevationCharacteristic.writeValue( 0 );
// 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 )
{
// BLE defines LATITUDE UUID 2AAE Type sint32 ( see XML links )
// Unit is in degrees and will need to divide the numbers by 10,000,000 when received
uint32_t latitude = round(sensorData.latitude);
latitudeCharacteristic.writeValue( latitude );
// BLE defines LONGITUDE UUID 2AAF Type sint32
// Unit is in degrees and will need to divide the numbers by 10,000,000 when received
uint32_t humidity = round( sensorData.longitude );
longitudeCharacteristic.writeValue( humidity );
// BLE defines Eleavtion UUID 2A6C Type sint24
// Unit is in meters with a resolution of 0.1 m
uint32_t elevation = round( sensorData.elevation );
elevationCharacteristic.writeValue( elevation );
sensorData.updated = false;
}
}
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() );
}
First try to comment the sensor task call. BLE should still work and just show zero values when you connect and read the characteristics.
Then I am not sure why you have a local myLidar and the global lidar. I would expect you to get the data from lidar? But I do not know the library. Try to test this separately.
Regarding your characteristics I would expect them to be signed values. So you need to remove unsigned and also change the local variables in bleTask to int32_t.
You missed the local humidity variable. Rename it to longitude when you change the type.
@Klaus_K@matt_lee_smith Thanks both for your help the below example is now functioning, amongst the other cut and paste typos, I had not intiallised the GPS correctly and it was causing it to hang during setup. Its all working as exepcted ... next to decode this in Android Studio!
Thanks a million for your help and support, its very much appreciated and was just the nudge required to get over the line!
`/*
This example creates a BLE peripheral with a Location and Navigation Service
that contains a latitude, longtude and elevation characteristic.
The yellow LED shows the BLE module is connected to a central.
The circuit:
- Arduino Nano 33 BLE Sense
You can use a generic BLE central app, like BLE Scanner (iOS and Android) or
nRF Connect, to interact with the services and characteristics
created in this sketch.
This example code is in the public domain.
*/
#include <ArduinoBLE.h>
#include <Wire.h> //Needed for I2C to GNSS
#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS
#include <TFmini_plus.h>
//----------------------------------------------------------------------------------------------------------------------
// Sparkfun GPS and TFMiniPlus LIDAR
//----------------------------------------------------------------------------------------------------------------------
SFE_UBLOX_GNSS myGNSS;
TFminiPlus lidar;
//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------
// https://www.bluetooth.com/specifications/assigned-numbers/
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.latitude.xml
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.longtitude.xml
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.elevation.xml
#define BLE_UUID_LOC_NAV_SERVICE "1819" // Location and Navigation Service
#define BLE_UUID_LATITUDE "2AAE" // sint32 WGS84 North coordinate
#define BLE_UUID_LONGITUDE "2AAF" // aint32 WGS84 East coordinate
#define BLE_UUID_ELEVATION "2A6C" // sint24 used in this as the LIDAR distance 0.01m resolution
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------
#define BLE_DEVICE_NAME "Arduino Nano 33 BLE"
#define BLE_LOCAL_NAME "Arduino Nano 33 BLE"
BLEService locNavService( BLE_UUID_LOC_NAV_SERVICE );
BLELongCharacteristic latitudeCharacteristic( BLE_UUID_LATITUDE, BLERead | BLENotify );
BLELongCharacteristic longitudeCharacteristic( BLE_UUID_LONGITUDE, BLERead | BLENotify );
BLELongCharacteristic elevationCharacteristic( BLE_UUID_ELEVATION, BLERead | BLENotify );
//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------
#define SENSOR_UPDATE_INTERVAL (1000)
typedef struct __attribute__( ( packed ) )
{
float latitude;
float longitude;
float elevation;
bool updated = false;
} sensor_data_t;
sensor_data_t sensorData;
#define BLE_LED_PIN LED_BUILTIN
void setup()
{
Serial.begin( 9600 );
while ( !Serial );
Wire.begin();
lidar.begin(0x10);
myGNSS.begin();
Serial.println( "BLE Example - Location Navigation Service (LNS)" );
if ( !setupBleMode() )
{
Serial.println( "Failed to initialize BLE!" );
while ( 1 );
}
else
{
Serial.println( "BLE initialized. Waiting for clients to connect." );
}
pinMode( BLE_LED_PIN, OUTPUT );
digitalWrite( BLE_LED_PIN, LOW );
myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise)
myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR
}
void loop()
{
bleTask();
if ( sensorTask() )
{
printTask();
}
}
bool sensorTask()
{
static uint32_t previousMillis = 0;
uint32_t currentMillis = millis();
if ( currentMillis - previousMillis < SENSOR_UPDATE_INTERVAL )
{
return false;
}
previousMillis = currentMillis;
tfminiplus_data_t data;
bool result = lidar.read_data(data, true);
sensorData.latitude = myGNSS.getLatitude();
sensorData.longitude = myGNSS.getLongitude();
sensorData.elevation = data.distance;
sensorData.updated = true;
return sensorData.updated;
}
void printTask()
{
Serial.print( "Latitude = " );
Serial.print( sensorData.latitude);
Serial.println( " °" );
Serial.print( "Longitude = " );
Serial.print( sensorData.longitude);
Serial.println( " °" );
Serial.print( "Lidar Distance = " );
Serial.print( sensorData.elevation);
Serial.println( " mm" );
}
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( locNavService );
// BLE add characteristics
locNavService.addCharacteristic( latitudeCharacteristic );
locNavService.addCharacteristic( longitudeCharacteristic );
locNavService.addCharacteristic( elevationCharacteristic );
// add service
BLE.addService( locNavService );
// set the initial value for the characeristic
latitudeCharacteristic.writeValue( 0 );
longitudeCharacteristic.writeValue( 0 );
elevationCharacteristic.writeValue( 0 );
// 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 )
{
// BLE defines LATITUDE UUID 2AAE Type sint32 ( see XML links )
// Unit is in degrees and will need to divide the numbers by 10,000,000 when received
int32_t latitude = round(sensorData.latitude);
latitudeCharacteristic.writeValue( latitude );
// BLE defines LONGITUDE UUID 2AAF Type sint32
// Unit is in degrees and will need to divide the numbers by 10,000,000 when received
int32_t longitude = round( sensorData.longitude );
longitudeCharacteristic.writeValue( longitude );
// BLE defines Eleavtion UUID 2A6C Type sint24
// Unit is in meters with a resolution of 0.1 m
int32_t elevation = round( sensorData.elevation );
elevationCharacteristic.writeValue( elevation );
sensorData.updated = false;
}
}
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() );
}`