Sending Lat and Long via BLE

Hi there,

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);

}

Hello @drgodfrey2000,

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 put your numbers into this Decimal to Hexadecimal Converter it recommends multiplying your number by 16^8 to remove the decimal separator which might help (not sure I get it though). Also if you put the hex you are getting here https://www.scadacore.com/tools/programming-calculators/online-hex-converter/ you can see that you are (kinda) getting the right numbers but with no points (or orientation).

image

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.

Hope that helps,
Matt

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:

https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.temperature.xml

With some URL hacking you can find

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.longitude.xml

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:

https://www.bluetooth.com/specifications/specs/

and you should find a few documents that could be useful. Look for

Location and Navigation Profile 1.0

Location and Navigation Service 1.0

Here is a link to an example for the Environmental Sensing Service I wrote

https://forum.arduino.cc/t/bluetooth-communications/902610/2

Maybe this can help you create a similar example for a service for navigation. If you get stuck, please post your new code and I will have a look.

@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:

  1. Can you use a standard charateristic in a custom service and have the same outcome or do standard charateristics inherit from the profiles?
  2. 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.

Thanks a million again

Dave

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.

@Klaus_K Thanks again for your help!

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. :slight_smile:

@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() );
}`
2 Likes