MotionSense System

MotionSense is a system developed to track full-body motion. It involves gathering data from multiple inertial-measurement-unit (IMU) sensors by a software to compute the motions of body segments.

We are developing 9-axis IMU sensor for the MotionSense system and have decided to go with BLE connectivity. From references, we’ve set the sampling rate of IMU sensor to 20-Hz, i.e. 20 IMU samples per second, hopefully it will be fast enough to track body motion.

It is a 9-axis sensor (acceleration, gyro, and magnetometer), in which a sample of each axis consists of 2 bytes. Therefore, each IMU sample consists of 18 bytes. At 20-Hz, each sensor will send out data at a rate of 360 bytes per second, or 2.88-kbps.

The Need of a Resens BLE Gateway in MotionSense

MotionSense gathers data from multiple BLE-based IMU sensors at the same time. Therefore, it is better to build a BLE gateway or concentrator for this purpose. This post explains the source code of this BLE gateway we’ve written in Python and deployed on a Raspberry Pi board for testing.

BLE Gateway in Python

We’ve used bluepy as Python interface for BLE on Linux.

First, we need a delegate class, ble_delegate.py, as BLE devices needs to communicate asynchronously with the main process.

from bluepy import btle
from bluepy.btle import Scanner, DefaultDelegate
class ble_delegate(DefaultDelegate):
def __init__(self, params):
btle.DefaultDelegate.__init__(self)
# … initialise here
def handleDiscovery(self, dev, isNewDev, isNewData):
if isNewDev:
print('Discovered device %s' % dev.addr)
elif isNewData:
print('Received new data from %s' % dev.addr)
def handleNotification(self, cHandle, data):
# 9-axis IMU sample, 16-bit sample each
if (len(data) == 18):
samples_tuple = struct.unpack('<hhhhhhhhh', data)
print(samples_tuple)

There are two handles in this class, handleDiscovery is for when the gateway discovers a BLE device while scanning. handleNotification is for when the gateway receives a notification from the connected BLE device.

Then, in main.py, use the above class as follows:

from bluepy import btle
from bluepy.btle import Scanner, DefaultDelegate
from ble_delegate import ble_delegate
# delegate instance
params = [None]
_ble_delegate = ble_delegate(params) # params is whatever you need
# assign the delegate to the scanner
scanner = Scanner().withDelegate(_ble_delegate)
devices = scanner.scan(10.0)
# print discovered devices
for dev in devices:
print('Device %s (%s), RSSI=%d dB' % (dev.addr, dev.addrType, dev.rssi))
for (adtype, desc, value) in dev.getScanData():
print(' %s = %s' % (desc, value))

This code will scan for 10 seconds and printout discovered BLE devices in nearby. Below is an example screenshot.

scanner

Connecting to a specific IMU sensor is as follows:

# attempt connecting
try:
print('Connecting to ' + device_addr)
_ble_peripheral = btle.Peripheral(device_addr, addrType=btle.ADDR_TYPE_RANDOM)
_ble_peripheral.withDelegate(_ble_delegate)
# Setup to turn notifications on, e.g.
enable_notify(_ble_peripheral, IMU_SERVICE_UUID, IMU_SAMPLE_CHAR_UUID)
except Exception as e:
print(e)
# Main loop ——–
while True:
if _ble_peripheral.waitForNotifications(0.007):
# handleNotification() was called in delegate
continue
# inform user main loop is running
sys.stdout.write('.')
sys.stdout.flush()

In the above code, enable_notify() attempts to enable notification by writing to the CCCD handle of the IMU data characteristic in the corresponding service of the connected device. Its code is below:

def enable_notify(peripheral, service_uuid, char_uuid):
setup_data = b"\x01\x00"
svc = peripheral.getServiceByUUID(service_uuid)
print(svc)
ch = peripheral.getCharacteristics(uuid=char_uuid)[0]
print(ch)
print('Characteristic handle: %d' % ch.valHandle)
notify_handle = ch.getHandle() + 1
peripheral.writeCharacteristic(notify_handle, setup_data, withResponse=True)

Data sending by IMU sensors is asynchronously handled in handleNotification in the delegate class. At the moment, it just prints the received data to the command line.

Deploy to Raspberry Pi

First, we need to install bluepy following this guide

$ sudo apt-get install python3-pip libglib2.0-dev
$ sudo pip3 install bluepy

Then, run main.py as root

$ sudo python main.py

bluepy requires the BLE code to run as root.