Controlling Bluetooth LE Devices with techBASIC
Controlling Bluetooth LE Devices with techBASIC
Controlling Bluetooth LE Devices with techBASIC
The Internet of Things. It's a common buzzword, but how can you make it work for you? This blog gets you started with step-by-step instructions for setting up and accessing the key fob from the Texas Instruments Bluetooth Low Energy CC2540 Mini Development Kit. After setting up the key fob, we'll work through development of a Bluetooth Low Energy program to read and write data. All of the basic ideas for writing programs to access Bluetooth LE devices are covered, so even if you don't have the key fob, you can use this program as a roadmap to accessing absolutely any Bluetooth LE peripheral from your iPhone or iPad.
What Will It Do?
Bluetooth LE, also known as Bluetooth 4.0, BLE and Smart Bluetooth, is a new twist on the old Bluetooth standard. It's designed for ultra-low energy sensors that can run off of coin cell batteries. Some of the Bluetooth LE devices that are already available include sports fitness sensors like bicycle speedometers and pedometers built into Nike running shoes; health sensors like thermometers, blood pressure cuffs and heart rate monitors; and physics sensors like accelerometers and thermometers.
The key fob is a peripheral included in a Texas Instruments kit designed to help hardware and software engineers learn about, and begin building, Bluetooth LE peripherals and software. It has two push buttons and a 3-axis accelerometer, both of which will be read from our program. There is a battery indicator to show the battery level. You can also send information back to a Bluetooth LE device. Our program will send a signal to the key fob to sound an alarm that will flash an LED and beep.
Parts List
Monday, August 20, 2012
Setting Up the Key Fob
The key fob itself has a micro-controller that needs to be loaded with software. This must be done from the Windows operating system before the key fob is assembled. Go to http://www.ti.com/blestack to get the software itself. The download starts as soon as you finish registering with TI. This particular download contains the compiled program we'll load onto the key fob so it knows what it is supposed to do to communicate with the outside world.
The software to actually install the data file is found at http://focus.ti.com/docs/toolsw/folders/print/flash-programmer.html. Download and install this software.
iPhone 4s or later, or iPad 3 or later
Bluetooth 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 or an iPad 3 or later to access Bluetooth LE devices.
Bluetooth LE 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.
Bluetooth Low Energy CC2540 Mini Development Kit
This kit contains the key fob, a hardware device used to load programs onto the key fob, and a Bluetooth LE USB dongle. You can find it at various online retailers or order directly from Texas Instruments. The Texas Instruments web site also has additional information and a support community for the hardware.
techBASIC 2.3
The TI CC2540 Mini Development Kit
Writing Bluetooth LE Programs
The next step is to connect the key fob for programming. The key fob needs power, so start by inserting the battery. Use the small 8 conductor ribbon connector to connect the TI key fob to the small red circuit board. Be sure to connect it so the cable exits to the right. It's easy to get the connector on backwards when connecting it to the key fob. Plug the red circuit board into the CC Debugger, and connect the CC Debugger to your computer using the USB cable. If everything is connected properly, the LED on the CC Debugger will glow green. If it's not green, make sure you remembered to install the battery in the key fob and check all of the connections carefully, especially where the ribbon cable connects to the key fob.
After installation, run the SmartRF Flash Programmer and set up the options exactly as shown in the screen capture. The System-on-Chip section will be filled in automatically if you have correctly connected the key fob. Click on the ... button to set the Flash image to the file C:\Texas Instruments\BLE-CC254x-1.2.1\Accessories\HexFiles\CC2540MiniDkDemoSlave.hex, which is in the first data files download. With everything set up, click the Perform actions button to install the software on the key fob.
All that remains is to install the key fob circuit board into the plastic housing.
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. The user interface for the iPhone and iPad is shown above.
In our case, we're going to scan for any Bluetooth LE 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 LE device in the area. In the case of the key fob, it's time to press one of the buttons to tell it to start announcing its presence to anyone in the area looking for the services it offers. As the iOS device finds Bluetooth LE devices, it calls a subroutine in your program called BLEDiscoveredPeripheral. Here's the implementation from our program.
! Called when a peripheral is found. If it is a key fob, 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 = "Keyfobdemo" THEN
keyfob = peripheral
BLE.connect(keyfob)
BLE.stopScan
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 Keyfobdemo, 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 key fob, 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 LE devices, which can drain the battery of the iOS device and the Bluetooth LE devices. We do this with BLE.stopScan.
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.
peripheral.discoverServices(uuid)
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
peripheral.discoverCharacteristics(uuid, availableServices(a))
END IF
NEXT
NEXT
END IF
END SUB
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 simple program assumes success, and asks the peripheral for a list of any services it provides by calling the peripheral's discoverServices method. As the peripheral reports back on any services, the subroutine is called again with kind set to 4. This is a great spot to place a PRINT statement if you are not sure what services a device offers, and simply print the services as the device reports them.
In our case, we already know that we are interested in these services:
Before jumping into the code, though, let's stop and get an overview of how Bluetooth LE devices are designed. Bluetooth LE devices package information in services. In our program, we will use four of these services: the status of the push buttons, the accelerometer, the battery level and the proximity alert. Other devices might offer a temperature service, 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, like the battery level on the key fob, while others 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 read only characteristics to supply the acceleration along the X, Y and Z axis, and a write characteristic to turn the accelerometer on or off. Services can also have other sub-services imbedded in them; these are called included services. Characteristics can also have descriptors with additional information about the characteristic. The figure shows the services and characteristics we'll use on the TI key fob.
Almost all Bluetooth LE 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 key fob program.
Let's get going with the program. The first step in connecting to a Bluetooth LE device is to start the Bluetooth LE service with the call
BLE.startBLE
Next we begin scanning for devices. This allows our program to look for any Bluetooth LE 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.
Key Fob Characteristics
These are encapsulated in an array at the start of the program, defined with these lines.
! We will look for these four services.
DIM services(4) AS STRING
services(1) = "FFE0" : ! Push buttons
services(2) = "FFA0" : ! Accelerometer
services(3) = "180F" : ! Battery level
services(4) = "1802" : ! Proximity alert (buzzer)
The code in BLEPeripheralInfo checks to see if the service reported by the device is one of these and, if so, asks the service for a list of the available characteristics using the peripheral's discoverCharacteristics method.
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 = "FFE0" AND characteristics(i).uuid = "FFE1" THEN
! Found the buttons. Ask for notifications when a button is
! pressed.
peripheral.setNotify(characteristics(i), 1)
ELSE IF service.uuid = "FFA0" THEN
! Found the accelerometer.
SELECT CASE characteristics(i).uuid
CASE "FFA1"
! Turn on the accelerometer.
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
CASE "FFA3", "FFA4", "FFA5"
! Ask for notifications of changes in the acceleration
! along all three axis.
peripheral.setNotify(characteristics(i), 1)
END SELECT
ELSE IF service.uuid = "180F" THEN
! Found the battery level. Remember it, which starts a
! timing loop in our nullEvent subroutine. This updates the
! battery level periodically.
batteryCharacteristic = characteristics(i)
batteryFound = 1
ELSE IF service.uuid = "1802" THEN
! Found the buzzer. Remember it for use by the buzzer button.
buzzerCharacteristic = characteristics(i)
buzzerFound = 1
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.
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 first is seen when we handle the characteristic FFE1 for the service FFE0, which is a value indicating which button or buttons are currently down. Rather than constantly polling the device asking which buttons are down, we can ask it to notify us when something changes using the setNotify method. The BLECharacteristicInfo subroutine will get called whenever a button is pressed or released.
The next case is the accelerometer service, FFA0. Our program is interested in four characteristics of this service. It turns the accelerometer on by writing a value to the FFA1 characteristic, passing it a non-zero value using the writeCharacteristic method. As you can see, it does this with an array containing a single value. Why not pass an integer, instead? Actually, the value of a characteristic can be pretty much anything, from a single byte to a JPG image. As a result, all writes and reads are handled using arrays of integers, each of which represents a single byte in the value read or written. It's up to the program to know what the device expects or will return, and to handle the array in an appropriate way.
The other three characteristics of the acceleration service our program cares about are the accelerations along each axis. As with the buttons, the program asks to be notified when the value changes using the setNotify method.
The next service the subroutine handles is the battery level. Unlike the buttons or accelerometers, the battery level really doesn't change that often, so it doesn't respond to a setNotify call. This one will have to be called whenever we want the battery level. The program handles this by saving the characteristic in a global variable and setting a flag saying the battery level has been found. We'll look at the nullEvent subroutine later, which will check the battery level periodically.
The last characteristic is the buzzer. This is a command we send to the device, not a value it sends back to us. As with the battery level, we save the information for later processing.
The operating system calls the BLECharacteristicInfo subroutine when the device reports a change to the characteristic.
! 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 "FFE1"
! A button was pressed. Update the GUI to show a bright
! button for any that are held down.
lbutton.setHidden(NOT (value(1) BITAND 1))
rbutton.setHidden(NOT (value(1) BITAND 2))
CASE "FFA3"
! Update the X accelerometer value.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastX = p%/68.0 + 0.06
CASE "FFA4"
! Update the Y accelerometer value.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastY = p%/68.0 + 0.06
CASE "FFA5"
! Update the X accelerometer value.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastZ = p%/68.0 + 0.06
CASE "2A19"
! Update the battery level.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
setBatteryLevel(p%/100.0)
END SELECT
END IF
END SUB
The only kind of call we're interested in is a call telling the program the device returned a new value for the characteristic. Our program asks for values for five characteristics in various places, so the subroutine checks for those five characteristics.
The push buttons light up the buttons on the image of the key fob. The program deals with this by hiding or showing an image of the button that is brighter than normal. The image of the key fob sits behind the button images, and includes a button image that is not highlighted that shows through when the bright button image is hidden.
Each of the accelerometer readings comes back as a signed byte, which is a bit of an odd data type in BASIC. We do a few gyrations to sign extend the byte value to an integer by checking to see if the most significant bit of the byte is 1 and, if so, placing ones in the most significant 8 bits of the integer. With that done, we can scale the value to represent the actual acceleration. We'll see how these values are used in a moment.
The values 68.0 and 0.06 are determined experimentally, and may be different for your device. To calibrate the device, hold it so one axis is vertical and the other two are horizontal and check to make sure the plot shows a value of 1G or -1G. Turn it over to check in the other direction. If the range is 2G, but the maximum and minimum values are off, adjust the 0.06 value to raise or lower the range. If the range is not 2G, raise or lower the 68.0 value to coax the range to 2G.
Finally, we check the battery level, which is reported as an integer from 0 to 100. The setBatteryLevel subroutine takes a value from 0.0 to 1.0 and shows an image of a battery with an appropriate amount of green.
techBASIC supports a subroutine called nullEvent that is called when the program is not busy doing something else, like responding to a user action or passing information from a Bluetooth LE device. This is where we handle checking the battery level and updating the acceleration plot.
! Called when the program is not busy doing anything else, this
! subroutine updates the battery level and accelerometer plots.
!
! Parameters:
! time - The time when the call was made.
!
SUB nullEvent (time AS DOUBLE)
! If it has been more than 10 seconds since the battery level was
! checked, check it again.
IF time - batteryTime > 10.0 AND batteryFound THEN
batteryTime = time
keyfob.readCharacteristic(batteryCharacteristic)
END IF
! If it has been more than deltaTime seconds since the
! accelerometer plot was updated, update it with the most recent
! values reported by the device.
IF plotTime = 0 THEN
! This is the first call. Initialize the plot.
plotTime = time
ELSE IF time - plotTime > deltaTime THEN
! Update the plot with the most recent acceleration reported
! by the device.
WHILE plotTime < time
FOR i = 1 TO points% - 1
xAccel(i, 2) = xAccel(i + 1, 2)
yAccel(i, 2) = yAccel(i + 1, 2)
zAccel(i, 2) = zAccel(i + 1, 2)
NEXT
xAccel(points%, 2) = lastX
yAccel(points%, 2) = lastY
zAccel(points%, 2) = lastZ
plotTime = plotTime + deltaTime
WEND
accelXPlot.setPoints(xAccel)
accelYPlot.setPoints(yAccel)
accelZPlot.setPoints(zAccel)
accelPlot.repaint
END IF
END SUB
The program is pretty aggressive about checking the battery level, checking for changes every 10 seconds. It calls the device's readCharacteristic method if the time since the last nullEvent call is more than 10 seconds. You've already seen the code that handles any value send back by the device back in the discussion for the BLECharacteristicInfo subroutine.
The acceleration plot is updated ten times per second, which is about as fast as the data comes back from the key fob. The value of 0.1, for one tenth of a second, is stored in the global variable deltaTime. deltaTime is defined at the start of the program where it is easy to change to experiment with different values. Assuming the requisite amount of time has passed, the program updates three arrays of point values by sliding all of the old values one position earlier in the array and placing the new acceleration values at the end. The acceleration values are the Y values for the plot; the X values are initialized using this code when the user interface is set up.
totalTime = points%*deltaTime
FOR i = 1 TO points%
xAccel(i, 1) = i/totalTime - totalTime
yAccel(i, 1) = i/totalTime - totalTime
zAccel(i, 1) = i/totalTime - totalTime
NEXT
Once the points are updated, the three PointPlot objects that actually appear on the plot as the red, green and blue lines are updated with new arrays of points using the setPoints method, and the entire plot is updated by the call to repaint. The plots themselves are set up when the user interface is defined.
The only remaining Bluetooth LE related code in the program is the code that handles a press on the Sound Buzzer button. techBASIC calls touchUpInside when the user taps a button.
! Handle a tap on a button.
!
! Parameters:
! ctrl - The button that was tapped.
! time - The time stamp when the button was tapped.
!
SUB touchUpInside (ctrl AS Button, time AS DOUBLE)
IF ctrl = soundBuzzer THEN
IF buzzerFound THEN
DIM value(1) AS INTEGER
value = [2]
keyfob.writeCharacteristic(buzzerCharacteristic, value)
END IF
ELSE IF ctrl = quit THEN
STOP
END IF
END SUB
The subroutine checks to see which button was pressed. If is was the Sound Buzzer button, and if the device has given the program a characteristic for this service, it follows up with a call to writeCharacteristic. This works just like the call to turn on the accelerometer, but in this case, we pass a 2 to tell the device to start sounding the buzzer. Press a button on the key fob to turn the buzzer off.
The rest of the program deals with setting up the user interface and the accelerometer plot. Handling user interfaces and plots are covered in other blogs, 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.
Further Explorations
This program shows how to access Bluetooth LE devices, and covers the major commands and techniques used for all Bluetooth LE devices. There's more to learn, though. See the techBASIC Reference Manual for a host of other Bluetooth LE commands, as well as several complete sample programs. Check the index or do a search of the PDF for Bluetooth LE.
Complete Source
Here's the complete source for the key fob sample. There is also a download button after the source to download the program.
! This program shows how to connect to Bluetooth LE devices. It
! connects to the key fob from the Texas Instruments Bluetooth
! Low Energy CC2540 Mini Development Kit. Support is included for
! the buttons, accelerometer, proximity alert and battery level.
!
! The key fob can be loaded with different software for different
! purposes. This program is compatible with the key fob software
! found in CC2540MiniDkDemoSlave.hex.
!
! See the blog at http://www.byteworks.us for a complete
! description of this program.
! Set up variables to hold the peripheral and the characteristics
! for the battery and buzzer.
DIM keyfob AS BLEPeripheral, batteryCharacteristic AS BLECharacteristic
DIM buzzerCharacteristic AS BLECharacteristic
! We will look for these four services.
DIM services(4) AS STRING
services(1) = "FFE0" : ! Push buttons
services(2) = "FFA0" : ! Accelerometer
services(3) = "180F" : ! Battery level
services(4) = "1802" : ! Proximity alert (buzzer)
! Start the BLE service and begin scanning for devices.
BLE.startBLE
DIM uuid(0) AS STRING
BLE.startScan(uuid)
! Set up the user interface. Several globals are defined here and
! used by multiple subroutines.
DIM lbutton AS ImageView, rbutton AS ImageView
points% = 100
deltaTime = 0.1
DIM xAccel(points%, 2), yAccel(points%, 2), zAccel(points%, 2)
DIM lastX, lastY, lastZ
DIM accelPlot AS Plot
DIM accelXPlot AS PlotPoint, accelYPlot AS PlotPoint, accelZPlot AS PlotPoint
DIM plotTime AS DOUBLE
DIM batteryLevel AS Label, bx, by, bh, bw
DIM batteryTime AS DOUBLE, batteryFound AS INTEGER, buzzerFound AS INTEGER
DIM soundBuzzer AS Button, quit AS Button
setUpGUI
! Called when a peripheral is found. If it is a key fob, 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 = "Keyfobdemo" THEN
keyfob = peripheral
BLE.connect(keyfob)
BLE.stopScan
END IF
END SUB
! 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.
peripheral.discoverServices(uuid)
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
peripheral.discoverCharacteristics(uuid, availableServices(a))
END IF
NEXT
NEXT
END IF
END SUB
! 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 = "FFE0" AND characteristics(i).uuid = "FFE1" THEN
! Found the buttons. Ask for notifications when a button is
! pressed.
peripheral.setNotify(characteristics(i), 1)
ELSE IF service.uuid = "FFA0" THEN
! Found the accelerometer.
SELECT CASE characteristics(i).uuid
CASE "FFA1"
! Turn on the accelerometer.
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
CASE "FFA3", "FFA4", "FFA5"
! Ask for notifications of changes in the acceleration
! along all three axis.
peripheral.setNotify(characteristics(i), 1)
END SELECT
ELSE IF service.uuid = "180F" THEN
! Found the battery level. Remember it, which starts a
! timing loop in our nullEvent subroutine. This updates the
! battery level periodically.
batteryCharacteristic = characteristics(i)
batteryFound = 1
ELSE IF service.uuid = "1802" THEN
! Found the buzzer. Remember it for use by the buzzer button.
buzzerCharacteristic = characteristics(i)
buzzerFound = 1
END IF
NEXT
END IF
END SUB
! 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 "FFE1"
! A button was pressed. Update the GUI to show a bright
! button for any that are held down.
lbutton.setHidden(NOT (value(1) BITAND 1))
rbutton.setHidden(NOT (value(1) BITAND 2))
CASE "FFA3"
! Update the X accelerometer value.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastX = p%/68.0 + 0.06
CASE "FFA4"
! Update the Y accelerometer value.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastY = p%/68.0 + 0.06
CASE "FFA5"
! Update the X accelerometer value.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastZ = p%/68.0 + 0.06
CASE "2A19"
! Update the battery level.
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
setBatteryLevel(p%/100.0)
END SELECT
END IF
END SUB
! Called when the program is not busy doing anything else, this
! subroutine updates the battery level and accelerometer plots.
!
! Parameters:
! time - The time when the call was made.
!
SUB nullEvent (time AS DOUBLE)
! If it has been more than 10 seconds since the battery level was
! checked, check it again.
IF time - batteryTime > 10.0 AND batteryFound THEN
batteryTime = time
keyfob.readCharacteristic(batteryCharacteristic)
END IF
! If it has been more than deltaTime seconds since the
! accelerometer plot was updated, update it with the most recent
! values reported by the device.
IF plotTime = 0 THEN
! This is the first call. Initialize the plot.
plotTime = time
ELSE IF time - plotTime > deltaTime THEN
! Update the plot with the most recent acceleration reported
! by the device.
WHILE plotTime < time
FOR i = 1 TO points% - 1
xAccel(i, 2) = xAccel(i + 1, 2)
yAccel(i, 2) = yAccel(i + 1, 2)
zAccel(i, 2) = zAccel(i + 1, 2)
NEXT
xAccel(points%, 2) = lastX
yAccel(points%, 2) = lastY
zAccel(points%, 2) = lastZ
plotTime = plotTime + deltaTime
WEND
accelXPlot.setPoints(xAccel)
accelYPlot.setPoints(yAccel)
accelZPlot.setPoints(zAccel)
accelPlot.repaint
END IF
END SUB
! Look to see if this is an iPhone or iPad, and set the GUI up
! as appropriate.
!
SUB setUpGUI
! Set up the GUI.
IF System.device = 0 THEN
setUpiPhoneGUI
ELSE
setUpiPadGUI
END IF
! Switch to the graphics screen.
System.showGraphics
END SUB
! Set up the GUI for an iPad.
!
SUB setUpiPadGUI
! Draw the image of the key fob.
DIM keyfob AS ImageView
keyfob = Graphics.newImageView(100, 20)
keyfob.loadImage("keyfob250.png")
! Load images of the brightened buttons, but hide them until a
! button is pressed.
lbutton = Graphics.newImageView(149, 91)
lbutton.loadImage("lbutton240.png")
lbutton.setHidden(1)
rbutton = Graphics.newImageView(185, 91)
rbutton.loadImage("rbutton240.png")
rButton.setHidden(1)
! Set up the accelerometer plot.
DIM plotBackground AS Label
plotBackground = Graphics.newLabel(Graphics.width - 40, 300, 20, 300)
plotBackground.setBackgroundColor(0.886, 0.886, 0.886)
totalTime = points%*deltaTime
FOR i = 1 TO points%
xAccel(i, 1) = i/totalTime - totalTime
yAccel(i, 1) = i/totalTime - totalTime
zAccel(i, 1) = i/totalTime - totalTime
NEXT
accelPlot = Graphics.newPlot
accelXPlot = accelPlot.newPlot(xAccel)
accelXPlot.setColor(1, 0, 0)
accelXPlot.setPointColor(1, 0, 0)
accelYPlot = accelPlot.newPlot(yAccel)
accelYPlot.setColor(0, 1, 0)
accelYPlot.setPointColor(0, 1, 0)
accelZPlot = accelPlot.newPlot(zAccel)
accelZPlot.setColor(0, 0, 1)
accelZPlot.setPointColor(0, 0, 1)
accelPlot.setRect(20, 300, Graphics.width - 60, 300)
accelPlot.setView(-totalTime, -1.28, 0, 1.28, 0)
accelPlot.setTitle("Acceleration in Gravities")
accelPlot.setTitleFont("Sans-Serif", 22, 0)
accelPlot.setXAxisLabel("Time in Seconds")
accelPlot.setYAxisLabel("Acceleration")
accelPlot.setAxisFont("Sans-Serif", 18, 0)
! Create the battery level indicator.
newBattery((Graphics.width - 200)/2, 620, 200, 50)
! Add a Quit button.
quit = Graphics.newButton(Graphics.width - 92, Graphics.height - 57)
quit.setTitle("Quit")
quit.setBackgroundColor(1, 1, 1)
quit.setGradientColor(0.6, 0.6, 0.6)
! Add a button to sound the buzzer.
soundBuzzer = Graphics.newButton(410, 230, 140)
soundBuzzer.setTitle("Sound Alarm")
soundBuzzer.setBackgroundColor(1, 1, 1)
soundBuzzer.setGradientColor(0.6, 0.6, 0.6)
! Add some text to describe the program.
DIM title1 AS Label, title2 AS Label
title1 = Graphics.newLabel(280, 40, 400, 30)
title1.setText("Bluetooth LE Demo")
title1.setAlignment(2)
title1.setFont("Serif", 36, 1)
title2 = Graphics.newLabel(280, 90, 400, 30)
title2.setText("TI Key Fob")
title2.setAlignment(2)
title2.setFont("Serif", 30, 1)
END SUB
SUB setUpiPhoneGUI
! Get the size of the display.
height = Graphics.height
width = Graphics.width
! Draw the image of the key fob.
DIM keyfob AS ImageView
keyfob = Graphics.newImageView(20, 10)
keyfob.loadImage("keyfob120.png")
! Load images of the brightened buttons, but hide them until a
! button is pressed.
lbutton = Graphics.newImageView(42, 44)
lbutton.loadImage("lbutton120.png")
lbutton.setHidden(1)
rbutton = Graphics.newImageView(60, 44)
rbutton.loadImage("rbutton120.png")
rButton.setHidden(1)
! Set up the accelerometer plot.
totalTime = points%*deltaTime
FOR i = 1 TO points%
xAccel(i, 1) = i/totalTime - totalTime
yAccel(i, 1) = i/totalTime - totalTime
zAccel(i, 1) = i/totalTime - totalTime
NEXT
accelPlot = Graphics.newPlot
accelXPlot = accelPlot.newPlot(xAccel)
accelXPlot.setColor(1, 0, 0)
accelXPlot.setPointColor(1, 0, 0)
accelYPlot = accelPlot.newPlot(yAccel)
accelYPlot.setColor(0, 1, 0)
accelYPlot.setPointColor(0, 1, 0)
accelZPlot = accelPlot.newPlot(zAccel)
accelZPlot.setColor(0, 0, 1)
accelZPlot.setPointColor(0, 0, 1)
accelPlot.setRect(10, 135, width - 30, 170)
accelPlot.setView(-totalTime, -1.28, 0, 1.28, 0)
accelPlot.setBorderColor(1, 1, 1)
accelPlot.setTitle("Acceleration in Gravities")
accelPlot.setXAxisLabel("Time in Seconds")
accelPlot.setYAxisLabel("Acceleration")
! Create the battery level indicator.
newBattery(20, 314, 160, 35)
! Add a Quit button.
quit = Graphics.newButton(width - 92, height - 47)
quit.setTitle("Quit")
quit.setBackgroundColor(1, 1, 1)
quit.setGradientColor(0.6, 0.6, 0.6)
! Add a button to sound the buzzer.
soundBuzzer = Graphics.newButton(150, 90, 140)
soundBuzzer.setTitle("Sound Alarm")
soundBuzzer.setBackgroundColor(1, 1, 1)
soundBuzzer.setGradientColor(0.6, 0.6, 0.6)
! Add some text to describe the program.
DIM title1 AS Label, title2 AS Label
title1 = Graphics.newLabel(130, 25, 180, 20)
title1.setText("Bluetooth LE Demo")
title1.setAlignment(2)
title1.setFont("Serif", 20, 1)
title2 = Graphics.newLabel(150, 55, 140, 20)
title2.setText("TI Key Fob")
title2.setAlignment(2)
title2.setFont("Serif", 18, 1)
END SUB
! Create a battery level indicator using several stacked, colored
! labels.
!
! Parameters:
! x, y - Location for the indicator.
! width, height - Size of the indicator.
!
SUB newBattery (x, y, width, height)
DIM outline AS Label, pole AS Label, inside AS Label
outline = Graphics.newLabel(x, y, width*0.97, height)
outline.setBackgroundColor(0, 0, 0)
pole = Graphics.newLabel(x + width*0.97, y + height*0.25, width*0.03, height/2)
pole.setBackgroundColor(0, 0, 0)
bx = x + 2
by = y + 2
bw = width*0.97 - 4
bh = height - 4
inside = Graphics.newLabel(bx, by, bw, bh)
batteryLevel = Graphics.newLabel(bx, by, bw*0.01, bh)
batteryLevel.setBackgroundColor(0, 1, 0)
END SUB
! Change the battery level by changing the size of the green label
! in the battery level indicator set up by newBattery.
!
! Parameters:
! level - The new battery level, from 0.0 to 1.0.
!
SUB setBatteryLevel (level)
batteryLevel.setFrame(bx, by, bw*level, bh)
END SUB
! Handle a tap on a button.
!
! Parameters:
! ctrl - The button that was tapped.
! time - The time stamp when the button was tapped.
!
SUB touchUpInside (ctrl AS Button, time AS DOUBLE)
IF ctrl = soundBuzzer THEN
IF buzzerFound THEN
DIM value(1) AS INTEGER
value = [2]
keyfob.writeCharacteristic(buzzerCharacteristic, value)
END IF
ELSE IF ctrl = quit THEN
STOP
END IF
END SUB
Service UUID
FFE0
FFA0
180F
1802
Service Description
Push buttons
Accelerometer
Battery level
Proximity alert
Includes source code and images (for the key fob)