After a brief introduction about this project, it is time to know more in detail about our RSL10 device. BTLE devices can reveal quite useful information which I planned to understand first in order to progress with the project. Since I'm planning to use a Raspberry Pi 4 to act as a hub for all the beacons, I wrote a Python program that allowed me to do just that; gather all the information available, in fact, such program turned out to be very useful as I was able to use it to debug my progress since it reveals all the information from the RSL10 device in a user-friendly way.

 

I started by using an existing example "Custom Service Firmware" available for RSL10-Sense devices in the On Semiconductor IDE which uses the ICS BLE Service Protocol to communicate (location highlighted in the screenshot below). To make things even better, the script can be used as is.

ON Semiconductor IDE - Import example (Custom Service Firmware)

When running the example with the Debug Probe connected and the RSL10 Sense and Mobile app, the information is right there which gives us a clue as to how the information flows. It uses the GATT protocol which basically uses a Master_Request/Slave_Response approach.
RSL10 Debug Probe with example - Custom Service Firmware

Connecting to the RSL10 with a Raspberry Pi 4

At this point of the project, a LM317 Adjustable Voltage Regulator comes in handy to power the RSL10-SENSE from the RPi during the tests and development. The LM317 outputs a voltage very close to 3V with  R1 = 240 Ohm and R2 = 330 Ohm

RSL10-SENSE-GEVK, Raspberry Pi, LM317 Adjustable Voltage Regulator

RSL10-SENSE-DB-GEVK, Raspberry Pi 4 and LM317 Adjustable Voltage Regulator

 

To start the testing, I used the following command which reveals all the BTLE devices around:

 

sudo hcitool lescan

 

Our RSL10 is right there in the list of devices

hcitool lescan

To look more in detail at the data structure of the BTLE device, I used the gatttool command, where I was able effectively see the last data from the device:

RSL10 gatttool interactive flag

GATT python script

Considering that the tools I just presented reveal the data in raw format, this requires extra steps and/or external tools to convert between ASCII and HEX for a user to understand better. At this point, I decided to go with my own approach with a Python script that will display the same data in a friendly manner. It turned out better than I expected as it allowed me to understand better how the LE devices interact and it also worked as Debug tool very well.

show_btle.py script

As you can see, there is a lot of useful information in the picture above, like the Handles that describe the purpose of each Characteristic and the Battery Level (0x2A19) which I'm surprised is not being used by the RSL10 Sense and Control Mobile app.

 

Source code

The source code is available for anyone to use. It uses bluepy, a Python module that allows communication with Bluetooth Low Energy devices.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Luis Ortiz - luislab.com
# March 8, 2020


from bluepy import btle
import binascii
import subprocess
import re


import sys


from textcolor import textColor


for pn in btle.Characteristic.propNames:
  btle.Characteristic.propNames[pn] = btle.Characteristic.propNames[pn].replace(" ", "_")


def format_handle_value(value:bin, uuid):
  if (uuid == btle.AssignedNumbers.batteryLevel):                              # Assigned Number
    string = color.yellow(str(int(binascii.hexlify(value).decode('utf-8'), 16)) + "%")
  elif (uuid == btle.AssignedNumbers.genericAttribute):
    string = color.yellow(int(value, 16))
  else:
    try:
      string = value.decode('utf-8')
      if string.isprintable():
        string = color.purple('\'' + string + '\'')
      else:
        string = color.cyan("0x" + str(binascii.b2a_hex(value).decode('utf-8')))
    except UnicodeDecodeError:
      string = color.lightcyan('0x' + binascii.b2a_hex(value).decode('utf-8'))
  return string


def print_handles(phe, handle, stopHandle):
  while handle <= stopHandle:
    val = phe.readCharacteristic(handle)
    print ((color.green("0x%04x") + ":    Handle <value=%s>") % (handle, format_handle_value(val, "")))
    handle += 1


def format_assigned_number(uuid):
  assigned_number = ""
  if not (uuid.getCommonName() == str(uuid)):
    result = re.match('([0-9A-F]{4}){2}(-)', str(uuid).upper())


    if result:
      assigned_number = " assignedNumber=" + color.grey("0x" + result.group(1))


  return assigned_number
      
color = textColor()


arguments = len(sys.argv) - 1
if arguments == 1:
  macAddr = sys.argv[1].strip()
  if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", macAddr.lower()):
    print (color.red("\nInvalid MAC address"));
    quit()
    
  print ("\nConnecting...")
elif arguments < 1:
  print (color.red("\nMissing MAC address"));
  quit()
else:
  print (color.red("\nWrong number of arguments"));
  quit()


try:
  p = btle.Peripheral(macAddr, btle.ADDR_TYPE_PUBLIC)
except btle.BTLEException:
  print(color.red("Failed to connecto to BluetoothLE device ") + color.grey("%s") % macAddr)
  quit()


print ("\nConnected [" + color.green(p.addr) + "]")


for gs in p.getServices():
  result = re.match('(.+uuid=)(.+)( handleStart=)([0-9]+)(.+handleEnd=)([0-9]+)(.*)', str(gs))
  service = result.group(1) + \
            color.grey(result.group(2)) + \
            format_assigned_number(gs.uuid) + \
            result.group(3) + \
            color.grey(result.group(4)) + \
            result.group(5) + \
            color.grey(result.group(6)) + \
            result.group(7) 
  print (("\n" + color.green("0x%04x") + ": %s") % (gs.hndStart, service))


  handle = gs.hndStart + 1
  for gc in gs.getCharacteristics():
    print_handles(p, handle, gc.getHandle()-1)


    strProperties = gc.propertiesToString().strip()
    print ((color.green("0x%04x") + \
            ":    Characteristic <uuid=" + color.grey("%s") + \
            format_assigned_number(gc.uuid) + \
            " properties=" + color.grey("0x%02x") + \
            " (" + color.red("%s") + ")") \
            % (gc.getHandle(), gc.uuid.getCommonName(), gc.properties, strProperties))


    if gc.supportsRead():
      val = gc.read()
      print ((color.green("      ") + ":                    value=%s>") \
             % (format_handle_value(val, gc.uuid)))


    handle = gc.getHandle() + 1


  print_handles(p, handle, gs.hndEnd)


p.disconnect()
print("\nDisconnected.")


try:
  while 1 == 0:
    print("")
except KeyboardInterrupt:
  #pass
  GPIO.cleanup()
  print("Bye")

 

Blogs in this series

  1. ON Sweet Home - Introduction
  2. ON Sweet Home - Getting to know your RSL10
  3. ON Sweet Home - Collecting the Beacon data
  4. ON Sweet Home - 3D printed parts
  5. ON Sweet Home - GUI is alive and the project is complete!