Sunday, July 12, 2015

TI SimpleLink SensorTag 2015 - Python

Code for these examples is on GitHub in the dhSensorTag2015 repository.

Having successfully accessed data on the sensor tag I decided to try my hand at programmatically accessing data from the sensor tag.  I started out with the same two examples I had developed in bash: 1) to get the device name; and 2) get the humidty reading from the humidity sensor.

Surprisingly there are not too many options when it comes to Bluetooth Low Engergy APOs for Windows 7 or Linux. One of the few that exists is Ian Harvey’s bluepy for python on Linux.

Python isn’t something I use very much.  I do most of my work in Perl and C with a bit of Java thrown in for Android.  Fortunately there are a set of pretty good set of instructions for setting up bluepy on the Raspberry Pi that can be found at: http://www.elinux.org/RPi_Bluetooth_LE.  The output from these instructions is below:

pi@raspberrypi ~ $ git clone https://github.com/IanHarvey/bluepy.git
Cloning into 'bluepy'...
remote: Counting objects: 459, done.
remote: Total 459 (delta 0), reused 0 (delta 0), pack-reused 459
Receiving objects: 100% (459/459), 1.51 MiB | 556 KiB/s, done.
Resolving deltas: 100% (172/172), done.
pi@raspberrypi ~ $ cd bluepy/bluepy
pi@raspberrypi ~/bluepy/bluepy $ make
gcc -L. -O0 -g -DHAVE_CONFIG_H -I../bluez-5.4/attrib -I../bluez-5.4 -I../bluez-5.4/lib -I../bluez-5.4/src -I../bluez-5.4/gdbus -I../bluez-5.4/btio `pkg-config glib-2.0 dbus-1 --cflags` -o bluepy-helper bluepy-helper.c ../bluez-5.4/lib/bluetooth.c ../bluez-5.4/lib/hci.c ../bluez-5.4/lib/sdp.c ../bluez-5.4/lib/uuid.c ../bluez-5.4/attrib/att.c ../bluez-5.4/attrib/gatt.c ../bluez-5.4/attrib/gattrib.c ../bluez-5.4/attrib/utils.c ../bluez-5.4/btio/btio.c ../bluez-5.4/src/log.c `pkg-config glib-2.0 --libs`
pi@raspberrypi ~/bluepy/bluepy $ python btle.py B0:B4:48:B9:2C:82

Running the script generates the a dump of the services and characteristics.

Connecting to: B0:B4:48:B9:2C:82, address type: public
Service <uuid=Generic Access handleStart=1 handleEnd=7> :
    Characteristic <Device Name>, supports READ
    -> 'SensorTag 2.0'
    Characteristic <Appearance>, supports READ
    -> '\x00\x00'
    Characteristic <Peripheral Preferred Connection Parameters>, supports READ
    -> 'P\x00\xa0\x00\x00\x00\xe8\x03'
Service <uuid=f000aa70-0451-4000-b000-000000000000 handleStart=63 handleEnd=70> :
    Characteristic <f000aa71-0451-4000-b000-000000000000>, supports NOTIFY READ
    -> '\x00\x00'
    Characteristic <f000aa72-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00'
    Characteristic <f000aa73-0451-4000-b000-000000000000>, supports READ WRITE
    -> 'P'
Service <uuid=f000ac00-0451-4000-b000-000000000000 handleStart=81 handleEnd=88> :
    Characteristic <f000ac01-0451-4000-b000-000000000000>, supports NOTIFY READ WRITE
    -> '\x00\x00'
    Characteristic <f000ac02-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x02\x02\x00\x00\x00'
    Characteristic <f000ac03-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00D'
Service <uuid=Generic Attribute handleStart=8 handleEnd=11> :
    Characteristic <Service Changed>, supports INDICATE
Service <uuid=ffe0 handleStart=71 handleEnd=75> :
    Characteristic <ffe1>, supports NOTIFY
Service <uuid=f000aa64-0451-4000-b000-000000000000 handleStart=76 handleEnd=80> :
    Characteristic <f000aa65-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x7f'
    Characteristic <f000aa66-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00'
Service <uuid=f000aa00-0451-4000-b000-000000000000 handleStart=31 handleEnd=38> :
    Characteristic <f000aa01-0451-4000-b000-000000000000>, supports NOTIFY READ
    -> '\x00\x00\x00\x00'
    Characteristic <f000aa02-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00'
    Characteristic <f000aa03-0451-4000-b000-000000000000>, supports READ WRITE
    -> 'd'
Service <uuid=f000ffc0-0451-4000-b000-000000000000 handleStart=97 handleEnd=65535> :
    Characteristic <f000ffc1-0451-4000-b000-000000000000>, supports NOTIFY WRITE NO RESPONSE WRITE
    Characteristic <f000ffc2-0451-4000-b000-000000000000>, supports NOTIFY WRITE NO RESPONSE WRITE
Service <uuid=f000aa20-0451-4000-b000-000000000000 handleStart=39 handleEnd=46> :
    Characteristic <f000aa21-0451-4000-b000-000000000000>, supports NOTIFY READ
    -> '\x00\x00\x00\x00'
    Characteristic <f000aa22-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00'
    Characteristic <f000aa23-0451-4000-b000-000000000000>, supports READ WRITE
    -> 'd'
Service <uuid=Device Information handleStart=12 handleEnd=30> :
    Characteristic <System ID>, supports READ
    -> '\x82,\xb9\x00\x00H\xb4\xb0'
    Characteristic <Model Number String>, supports READ
    -> 'CC2650 SensorTag\x00'
    Characteristic <Serial Number String>, supports READ
    -> 'N.A.\x00'
    Characteristic <Firmware Revision String>, supports READ
    -> '1.01 (Mar 13 2015)\x00'
    Characteristic <Hardware Revision String>, supports READ
    -> 'PCB 1.2\x00'
    Characteristic <Software Revision String>, supports READ
    -> 'N.A.\x00'
    Characteristic <Manufacturer Name String>, supports READ
    -> 'Texas Instruments\x00'
    Characteristic <IEEE 11073-20601 Regulatory Cert. Data List>, supports READ
    -> '\xfe\x00experimental'
    Characteristic <PnP ID>, supports READ
    -> '\x01\r\x00\x00\x00\x10\x01'
Service <uuid=f000aa80-0451-4000-b000-000000000000 handleStart=55 handleEnd=62> :
    Characteristic <f000aa81-0451-4000-b000-000000000000>, supports NOTIFY READ
    -> '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    Characteristic <f000aa82-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00\x02'
    Characteristic <f000aa83-0451-4000-b000-000000000000>, supports READ WRITE
    -> 'n'
Service <uuid=f000ccc0-0451-4000-b000-000000000000 handleStart=89 handleEnd=96> :
    Characteristic <f000ccc1-0451-4000-b000-000000000000>, supports NOTIFY READ
    -> '6\x00\x00\x00d\x00'
    Characteristic <f000ccc2-0451-4000-b000-000000000000>, supports WRITE
    Characteristic <f000ccc3-0451-4000-b000-000000000000>, supports WRITE
Service <uuid=f000aa40-0451-4000-b000-000000000000 handleStart=47 handleEnd=54> :
    Characteristic <f000aa41-0451-4000-b000-000000000000>, supports NOTIFY READ
    -> '\x00\x00\x00\x00\x00\x00'
    Characteristic <f000aa42-0451-4000-b000-000000000000>, supports READ WRITE
    -> '\x00'
    Characteristic <f000aa44-0451-4000-b000-000000000000>, supports READ WRITE
    -> 'd'

At this point I created a directory SensorTag2015 off my home directory to work in.  I copied the bluepy python files into a subdirectory bluepy.  This gave me a setup that looked like this.

image

In the SensorTag2015 directory I created and ran my python examples.  I started with a very simple script that expects an address for the sensor tag on the command line.

pi@raspberrypi ~/SensorTag2015 $ python getDeviceName.py 00:00:00:00:00:00
SensorTag 2.0
pi@raspberrypi ~/SensorTag2015 $

The script imports two methods from the bluepy.btle fileand then it defines a UUID for the device name.  It checks to make sure that at least one argument is passed and then establishes a connection to the sensor tag.  If the script fails to connect to the sensor tag check to make sure that the sensor tag is advertising.  If it isn't push the left button and check that the green LED is flashing.  Once connected the script checks to make sure that the device name characteristic is readable, reads it and prints it out.

getDeviceName.py

import sys
from bluepy.btle import UUID, Peripheral

temp_uuid = UUID(0x2A00)

if len(sys.argv) != 2:
  print "Fatal, must pass device address:", sys.argv[0], ""
  quit()

p = Peripheral(sys.argv[1])

try:
    ch = p.getCharacteristics(uuid=temp_uuid)[0]
    if (ch.supportsRead()):
            print ch.read()

finally:
    p.disconnect()

Next I developed a slightly more complicated script for reading the Humidity sensor.  The template I developed with this script is what I used for most of the examples.

The script expects the address of the sensor tag to be passed on the command line as with the script above.  Then it defines TI unique UUIDs to configure the sensor and to get data from the sensor.  Next it sets up the values that get written to the sensor's configure UUID to turn the sensor on or off.  Note you must turn a sensor on before you are able to read it and get values other than zero back.

After these variables are setup the script connects to the sensor tag.  Once connected the script writes the value to turn on the sensor to the configuration UUID.  Then it reads the raw data from the sensor tag, converts it to values and calculates the sensor value.  The formulas for calculating the sensor values were pulled from the Android App source code. After the values are printed out the script turns of the sensor and disconnects from the device.

getHumidity.py

#
# TI SimpleLink SensorTag 2015
# Date: 2015 07 06
#
# Sensor: Humidity Temperature
# Values: Temperature and Humidity
#
import struct, sys, traceback
from bluepy.btle import UUID, Peripheral, BTLEException

def TI_UUID(val):
    return UUID("%08X-0451-4000-b000-000000000000" % (0xF0000000+val))

config_uuid = TI_UUID(0xAA22)
data_uuid = TI_UUID(0xAA21)

sensorOn  = struct.pack("B", 0x01)
sensorOff = struct.pack("B", 0x00)

if len(sys.argv) != 2:
  print "Fatal, must pass device address:", sys.argv[0], ""
  quit()

try:
  print "Info, trying to connect to:", sys.argv[1]
  p = Peripheral(sys.argv[1])

except BTLEException:
  print "Fatal, unable to connect!"
  
except:
  print "Fatal, unexpected error!"
  traceback.print_exc()
  raise

else:

  try:
    print "Info, connected and turning sensor on!"
    ch = p.getCharacteristics(uuid=config_uuid)[0]
    ch.write(sensorOn, withResponse=True)
    
    print "Info, reading values!"
    ch = p.getCharacteristics(uuid=data_uuid)[0]
    
    # IR Temperature sensor returns 4 bytes object(LSB),
    # object(MSB), ambient(LSB) and ambient(MSB).
    # Python unpack using "<" which denotes little-endian format
    # and "hh" which denotes 2 unsigned short (2 byte/16 bit) values.
    rawVals=ch.read()
    #(tempVal, humidVal) = struct.unpack('<HH', ch.read())
    tempVal = (ord(rawVals[1])<<8)+ord(rawVals[0])
    humidVal = (ord(rawVals[3])<<8)+ord(rawVals[2])
    
    #object temp and ambient temp are calculated as shown below
    print "Temp: %.2f F" % float((tempVal / 65536.0 * 165 - 40) * 1.8 + 32)
    print "Humidity: %.2f %%RH" % float(humidVal / 65536.0 * 100)
    
    print "Info, turning sensor off!"
    ch = p.getCharacteristics(uuid=config_uuid)[0]
    ch.write(sensorOff, withResponse=True)
    
  except:
    print "Fatal, unexpected error!"
    traceback.print_exc()
    raise

  finally:
    print "Info, disconnecting!"
    p.disconnect()
    
finally:
  quit()

On GitHub in the dhSensorTag2015 repository you will find these examples as well as additional examples to read the other sensors. 

A couple of things to bear in mind with these examples:

  • These examples were written using python 2.7.10 and were developed using V1.12 (Jun 23 2015) of TI’s firmware.
  • The barometer example calculation in the Andriod App doesn’t appear to be correct and therefore the example uses an updated calculation.
  • Data from all of the sensor examples, except the movement sensor, has been validated.

1 comment:

JaronRH said...

Thanks for your post! It really helped me get my SensorTag working with my Pi 2 (not to mention the painful bluetooth functionality). While I haven't used the motion sensor, buttons, or magnetic sensors yet, I have combined the temperature sensors, barometer, humidity, and luxometer sensors together to make a functioning weather station. Thanks again!