iPhone 4s or later, or iPad 3 or later

iBluetooth Low Energy is physically different from the older Bluetooth devices. It requires completely new hardware that is not available in older model devices, so you will need an iPhone 4s or later, an iPad 3 or later, or the iPod Touch 5th generation (the one with the 4 inch retina display) or later to access Bluetooth low energy devices.

Writing Bluetooth low energy Programs


With the hardware in hand, it's time to write the software. The complete, fully commented program is available as a download at the end of the article, so you don't need to retype what you see here.


Before jumping into the code, though, let's stop and get an overview of how Bluetooth low energy devices are designed. Bluetooth low energy devices package information in services. In our program, we will use six of these services, one for each sensor. This article will show how to connect to one of them, the accelerometer, but the software download handles all six. The sensors themselves, and the details on how to use them, are covered in the six follow-on blogs.


Other devices might offer a heart rate service or a servo control service. Each service has characteristics, which work more or less like variables. Some can only be read, some can only be written, and some can be both read and written. There can be more than one characteristic for a service. For example, the accelerometer has three characteristics, one to start or stop the accelerometer, one to set the rate at which it will return data, and one to read the actual data. Services can also have other sub-services imbedded in them; these are called included services. Characteristics can have descriptors with additional information about the characteristic. The figure shows the services and characteristics we'll use on the SensorTag. This is a simplification of the complete specification for the communication protocol for the SensorTag. The full specification is called the GATT profile, available for download from the TI web site here. There is also a lot of supplemental information on each sensor in the wiki. You don't need the GATT profile or wiki to follow along, but learning to read these documents will help if you have to figure out another Bluetooth low energy device.


Almost all Bluetooth low energy calls are asynchronous, since it may take some time for the operating system to communicate with the device to carry out an operation. Your program can use that time to make new requests, handle information passed back from old requests, or simply to do something else while waiting for the results of a call. From the program's perspective, the program begins by making a call, then moves on. At some point in the future the operating system will call a subroutine in the program to report the results. You will see this pattern over and over in the SensorTag program.


Let's get going with the program. The first step in connecting to a Bluetooth low energy device is to start the Bluetooth low energy service with the call


BLE.startBLE


Next we begin scanning for devices. This allows our program to look for any Bluetooth low energy device in the area and connect to the one—or ones—that have the information or capabilities we want. In general, you should already know the kind of service you are looking for. Each service has a 16 or 128 bit identifier for the service, called the service UUID. The shorter 16 bit identifiers are supposed to be assigned by the Bluetooth standards committee. You can find a list of the standard services at http://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx. Anyone is free to create a service using a 128 bit UUID.


The values shown in the figure are actually only part of the 128 bit UUID for the characteristics and services. These values make up the second and third byte of a much longer UUID. The complete UUID for the accelerometer service looks like this:


F000AA00-0451-4000-B000-000000000000


The UUID string for other services and characteristics are formed by replacing the fourth through eighth digits with the appropriate value form the table, so the calibration UUID for the barometer looks like this:


F000AA43-0451-4000-B000-000000000000


In our case, we're going to scan for any Bluetooth low energy peripheral in the area by passing an empty array of UUIDs to the startScan method.


DIM uuid(0) AS STRING

BLE.startScan(uuid)


At this point, the iPhone or iPad starts looking around for any Bluetooth low energy device in the area. Bluetooth low energy devices advertise their presence with short, infrequent radio signals that our program is scanning for. As the iOS device finds Bluetooth low energy devices, it calls a subroutine in your program called BLEDiscoveredPeripheral. Here's the implementation from our program.


! Set up variables to hold the peripheral and the characteristics

! for the battery and buzzer.

DIM sensorTag AS BLEPeripheral


! Start the BLE service and begin scanning for devices.

debug = 1

BLE.startBLE

DIM uuid(0) AS STRING

BLE.startScan(uuid)


! Called when a peripheral is found. If it is a Sensor Tag, we

! initiate a connection to it and stop scanning for peripherals.

!

! Parameters:

!    time - The time when the peripheral was discovered.

!    peripheral - The peripheral that was discovered.

!    services - List of services offered by the device.

!    advertisements - Advertisements (information provided by the

!        device without the need to read a service/characteristic)

!    rssi - Received Signal Strength Indicator

!

SUB BLEDiscoveredPeripheral (time AS DOUBLE, peripheral AS BLEPeripheral, services() AS STRING, advertisements(,) AS STRING, rssi)

IF peripheral.bleName = "TI BLE Sensor Tag" THEN

  sensorTag = peripheral

  BLE.connect(sensorTag)

  BLE.stopScan

  IF debug THEN PRINT "Discovered SensorTag."

END IF

END SUB


Since we're more interested in a kind of device rather than a kind of service, we check here to see if we've found the device we're looking for by looking at the name of the peripheral the iPhone or iPad found. If it matches TI BLE Sensor Tag, we found what we are looking for. Ideally, things like the name of the peripheral and the peripheral UUID will be in the documentation, but in practice, you may have to write just this much of the program and print the name of any peripheral you find to figure out the name to use.


Once we find the device, we save the peripheral in a global variable. This is important—it keeps the memory manager from disposing of the peripheral's object when the subroutine ends, which would tell the operating system we're not interested in this peripheral. Next we attempt to connect to the peripheral using BLE.connect. The last step is to stop scanning for other Bluetooth low energy devices, which can drain the battery of the iOS device and the Bluetooth low energy devices. We do this with BLE.stopScan.


This tiny program is actually a complete, working program to find and connect to a SensorTag. To see it in action, run the program, then press the pairing button on the left side of the SensorTag. It will take a second or two for the iPhone and SensorTag to set up communications, then you should see


Discovered SensorTag.


printed to the console.


Connecting to the device does not happen right away. The operating system asks for access and, once it gets a response, calls another subroutine called BLEPeripheralInfo.


! Called to report information about the connection status of the

! peripheral or to report that services have been discovered.

!

! Parameters:

!    time - The time when the information was received.

!    peripheral - The peripheral.

!    kind - The kind of call. One of

!        1 - Connection completed

!        2 - Connection failed

!        3 - Connection lost

!        4 - Services discovered

!    message - For errors, a human-readable error message.

!    err - If there was an error, the Apple error number. If there

!        was no error, this value is 0.

!

SUB BLEPeripheralInfo (time AS DOUBLE, peripheral AS BLEPeripheral, kind AS INTEGER, message AS STRING, err AS LONG)

IF kind = 1 THEN

  ! The connection was established. Look for available services.

  IF debug THEN PRINT "Connection made."

  peripheral.discoverServices(uuid)

ELSE IF kind = 2 OR kind = 3 THEN

  IF debug THEN PRINT "Connection lost: "; kind

  BLE.connect(sensorTag)

ELSE IF kind = 4 THEN

  ! Services were found. If it is one of the ones we are interested

  ! in, begin discovery of its characteristics.

  DIM availableServices(1) AS BLEService

  availableServices = peripheral.services

  FOR s = 1 to UBOUND(services, 1)

    FOR a = 1 TO UBOUND(availableServices, 1)

      IF services(s) = availableServices(a).uuid THEN

        IF debug THEN PRINT "Discovering characteristics for "; services(s)

        peripheral.discoverCharacteristics(uuid, availableServices(a))

      END IF

    NEXT

  NEXT

END IF

END SUB


We also need to add some lines at the start of the program. The new lines are shown in blue.


! Set up variables to hold the peripheral and the characteristics

! for the battery and buzzer.

DIM sensorTag AS BLEPeripheral


! We will look for these services.

DIM servicesHeader AS STRING, services(1) AS STRING

servicesHeader = "-0451-4000-B000-000000000000"

services(1) = "F000AA10" & servicesHeader : ! Accelerometer

accel% = 1


! Start the BLE service and begin scanning for devices.

debug = 0

BLE.startBLE

DIM uuid(0) AS STRING

BLE.startScan(uuid)


This subroutine can get called for a variety of reasons. After a BLE.connect call, we can get back the "connection complete" response or be told that the connection failed. Later, the connection might be lost—perhaps we wondered too far away from the peripheral. The kind parameter tells us why the call was made. Our program asks the peripheral for a list of any services it provides by calling the peripheral's discoverServices method when a connection is made, and tries to reconnect if a connection is lost. As the peripheral reports back on any services, the subroutine is called again with kind set to 4. Since we've enabled the debug output, the program will print a list of the available services.


Eventually we'll expand the array to hold all six services, but for now we'll restrict our attention to the accelerometer, so the array only has one element.


The code in BLEPeripheralInfo checks to see if the service reported by the device is the accelerometer and, if so, asks the service for a list of the available characteristics using the peripheral's discoverCharacteristics method.


By this time, you probably have a good idea what will happen next. As with the services, the characteristics are reported to the program by calling a subroutine. In this case, the subroutine is BLEServiceInfo. The name might seem odd, but the information is about a service, not the characteristic. The operating system is telling us the service has information. Here's the implementation.


! Called to report information about a characteristic or included

! services for a service. If it is one we are interested in, start

! handling it.

!

! Parameters:

!    time - The time when the information was received.

!    peripheral - The peripheral.

!    service - The service whose characteristic or included

!        service was found.

!    kind - The kind of call. One of

!        1 - Characteristics found

!        2 - Included services found

!    message - For errors, a human-readable error message.

!    err - If there was an error, the Apple error number. If there

!        was no error, this value is 0.

!

SUB BLEServiceInfo (time AS DOUBLE, peripheral AS BLEPeripheral, service AS BLEService, kind AS INTEGER, message AS STRING, err AS LONG)

IF kind = 1 THEN

  ! Get the characteristics.

  DIM characteristics(1) AS BLECharacteristic

  characteristics = service.characteristics

  FOR i = 1 TO UBOUND(characteristics, 1)

    IF service.uuid = services(accel%) THEN

      ! Found the accelerometer.

      SELECT CASE characteristics(i).uuid

        CASE "F000AA11" & servicesHeader

          ! Tell the accelerometer to begin sending data.

          IF debug THEN PRINT "Start accelerometer."

          DIM value(2) as INTEGER

          value = [0, 1]

          peripheral.writeCharacteristic(characteristics(i), value, 0)

          peripheral.setNotify(characteristics(i), 1)

         

        CASE "F000AA12" & servicesHeader

          ! Turn the accelerometer sensor on.

          IF debug THEN PRINT "Accelerometer on."

          DIM value(1) as INTEGER

          value(1) = 1

          peripheral.writeCharacteristic(characteristics(i), value, 1)

         

        CASE "F000AA13" & servicesHeader

          ! Set the sample rate to 100ms.

          DIM value(1) as INTEGER

          value(1) = 100

          IF debug THEN PRINT "Setting accelerometer sample rate to "; value(1)

          peripheral.writeCharacteristic(characteristics(i), value, 1)

      END SELECT

    END IF

  NEXT

END IF

END SUB


We're only interested in the first kind of call, where the operating system is telling us the service has updated its list of characteristics. If kind is 1, we get the characteristics for the service with a call to the service's characteristics method and loop over them, looking for characteristics we're interested in. There's only one so far, but the list will grow in the final program.


But wait: It looks like the characteristics are part of the service, which we knew after the call to BLEPeripheralInfo. Why go to all this trouble? The reason is that the operating system doesn't ask the device for a list of characteristics until the discoverCharacteristics call, since it doesn't want to waste battery power asking for information unless it is really needed. You can call the service's characteristics method in BLEPeripheralInfo, but it will return an empty array.


There is more than one way to read a value from the device. The accelerometer uses a method called notification, where the device notifies the iPhone each time a new value is available. We turn notifications on and start them with the calls


          DIM value(2) as INTEGER

          value = [0, 1]

          peripheral.writeCharacteristic(characteristics(i), value, 0)

          peripheral.setNotify(characteristics(i), 1)


The first two lines set up the value to write to the peripheral. The accelerometer expects two bytes, a 0 and a 1, to turn on. The next line writes these values to the characteristic, telling it to report information using notifications. The last line actually starts the notifications.


The other method would look similar. If the accelerometer supported individual read commands, you could use the readCharacteristics call, like this:


          peripheral.readCharacteristic(characteristics(i))


The BLECharacteristicInfo subroutine gets called with either notifications or reads, the difference being that it will only be called once after a read, and will be called every time a new value is available for notifications.


The next two characteristics are used to turn the accelerometer on and set the sampling rate. They work pretty much like the write used earlier to tell the device to report information using notifications. It may seem odd to send information to the command using an array with only one element, but that's because the software making the call to the device really has no idea how many bytes to send, so it sends all of the bytes in any array you pass. In these cases, the device only expects a single byte. For the AA12 characteristic, a 1 turns the accelerometer on and a 0 turns it off. For the AA13 characteristic, the value is the number of tens of milliseconds between samples, so passing 100 tells the device to send back an acceleration once a second.


The operating system calls the BLECharacteristicInfo subroutine when the device reports a change to a characteristic. Here's a simplified version of the subroutine that appears in the complete program.


! Called to return information from a characteristic.

!

! Parameters:

!    time - The time when the information was received.

!    peripheral - The peripheral.

!    characteristic - The characteristic whose information

!        changed.

!    kind - The kind of call. One of

!        1 - Called after a discoverDescriptors call.

!        2 - Called after a readCharacteristics call.

!        3 - Called to report status after a writeCharacteristics

!            call.

!    message - For errors, a human-readable error message.

!    err - If there was an error, the Apple error number. If there

!        was no error, this value is 0.

!

SUB BLECharacteristicInfo (time AS DOUBLE, peripheral AS BLEPeripheral, characteristic AS BLECharacteristic, kind AS INTEGER, message AS STRING, err AS LONG)

IF kind = 2 THEN

  DIM value(1) AS INTEGER

  value = characteristic.value

  SELECT CASE characteristic.uuid

    CASE "F000AA11" & servicesHeader

      ! Update the accelerometer.

      c = 64.0

      p% = value(1)

      IF p% BITAND $0080 THEN p% = p% BITOR $FF00

      lastAccelX = p%/c


      p% = value(2)

      IF p% BITAND $0080 THEN p% = p% BITOR $FF00

      lastAccelY = p%/c

     

      p% = value(3)

      IF p% BITAND $0080 THEN p% = p% BITOR $FF00

      lastAccelZ = p%/c


      PRINT lastAccelX, lastAccelY, lastAccelZ

   

  END SELECT

ELSE IF kind = 3 AND err <> 0 THEN

  PRINT "Error writing "; characteristic.uuid; ": ("; err; ") "; message

END IF

END SUB


This subroutine is called with a kind of 2 when the accelerometer reports back with a new value. The value it returns is packed into a three-byte array, where each byte is the acceleration value along one axis. These are signed values with a range of -128 to 127, so we can't just use them as-is. Instead, the program checks to see if the value is negative and, if so, extends the sign bits to form a two-byte signed integer.


Run the program. It may connect to the peripheral right away if you have already run an earlier version of the program. If not, push the pairing button on the SensorTag to establish a connection. After a second or two, you should start to see a spew of accelerometer data on the console telling you the G forces experienced by the SensorTag. Twist it around slowly to verify that the value changes.


The Complete SensorTag Program


The complete program handles all six sensors, not just the accelerometer, and shows them with a spiffy graphical user interface. Handling user interfaces and plots are covered elsewhere, so we won't go through this part of the program line by line. See the blog Learn How to Use Controls in Your techBASIC iPhone and iPad Programs to learn more about setting up user interfaces. See techBASIC: The Power-User's Graphing Calculator for more about setting up plots like the accelerometer plot. Feel free to get in touch at support@byteworks.us if you have questions or issues.

There are six other short blogs that cover each of the other sensors. You may be able to figure them out on your own by now. The only really tricky one is the barometer, which passes back a series of calibration values that are used for the final calculation. The sensor blogs give a few details about the sensors themselves and step through the math needed to access each one.


Accelerometer

Thermometer

Humidity Sensor (Hygrometer)

Magnetometer

Barometer

Gyroscope


Further Explorations


This program shows how to access Bluetooth low energy devices and covers the major commands and techniques used for all Bluetooth low energy devices. There's more to learn, though. See the techBASIC Reference Manual for a host of other Bluetooth low energy commands, as well as several complete sample programs. Check the index or do a search of the PDF for Bluetooth low energy.



Here is the source code for the demo program developed in this blog, as well as the more advanced program that created the GUI display shown at the start of the blog.

 

TI SensorTag

The SensorTag is a Bluetooth low energy device with six sensors. It's physically rather small, with the slightly irregular shaped soft plastic case measuring about 2.75x1.5x0.5 inches. The circuit board is 1x2.25x0.25 inches. It runs on a single CR 2032 coin cell battery, and mine has been chugging along quite nicely for several weeks on the same battery. It's available directly from Texas Instruments at this link. A great place for additional information is the SensorTag wiki.

CC Debugger

The CC Debugger is used to load new firmware onto the SensorTag, as well as for connecting diagnostic software. The SensorTag comes with a basic set of firmware preinstalled, so technically this is optional, but you may need to upgrade the firmware eventually. For example, we changed the firmware to switch the accelerometer from ±2G to ±8G for our rocket flights. You may already have a CC Debugger from the older Bluetooth Low Energy CC2540 Mini Development Kit, which included the Key Fob. If not, I'd recommend getting one when you order the SensorTag. Like the SensorTag, it's available directly from Texas Instruments.


The software for the CC Debugger runs on a Windows operating system. You only need it when you install the firmware, but if you're a Mac person, plan ahead so you have access to a Windows computer when you need it.

Since you don't need to install firmware for this blog, I'm not going to cover it here. You can find information on the TI web site or in my older Key Fob blog.

techBASIC

Bluetooth low energy support is built into techBASIC starting with version 2.3. This is available as a free update to people who own older versions of techBASIC. New copies can be purchased on the App Store.


Programs in this blog use some features from techBASIC 2.4. We had expected Apple to release it by now. Once they do, it will be a free upgrade to the current version, techBASIC 2.3.