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)