Arduino Internet of Things Part 4: Connecting Bluetooth Nodes to the Raspberry Pi Using Python's Bluepy Library
In the previous three installments of the Arduino Internet of Things series, I walked through building an inexpensive, $3, breadboard Arduino (Part 1); I demonstrated how to record temperature and send the data via Bluetooth using the Arduino (Part 2); and I instructed how to use interrupt techniques with a motion sensor to create a Bluetooth node that will last for months on a single LiPo battery charge (Part 3). In this tutorial I explain how to connect all of these devices via Bluetooth Low Energy (BLE) and communicate with a Raspberry Pi to prepare the system for integration into the Internet of Things (IoT). At the end of this tutorial, the user's Raspberry Pi will be able to listen for multiple Bluetooth devices and record the data transmitted by each node. I will start by implementing the Python-enabled Bluetooth library, Bluepy, and showing the user how to scan for BLE devices.
Downloading and Testing Bluepy for Python on Raspberry Pi
Bluepy is a Bluetooth Low Energy interface built on Raspberry Pi for Python. The GitHub page can be found here. I have used Bluepy for many different Bluetooth LE projects ranging from iBeacons to multi-node IoT frameworks. It's great for IoT applications because of its integration with Raspberry Pi and Python. Below is the outline of the install process for Python 3.xx via the command window. As of writing, this method worked for the Raspberry Pi 3 Model B+.
sudo apt-get install python3-pip libglib2.0-dev sudo pip3 install bluepy
Once the Pi is restarted and a text editor is opened, copy the following code from the Bluepy documentation (docs here) into the file and save it as 'ble_scanner.py'
from bluepy.btle import Scanner, DefaultDelegate class ScanDelegate(DefaultDelegate): def __init__(self): DefaultDelegate.__init__(self) def handleDiscovery(self, dev, isNewDev, isNewData): if isNewDev: print "Discovered device", dev.addr elif isNewData: print "Received new data from", dev.addr scanner = Scanner().withDelegate(ScanDelegate()) devices = scanner.scan(10.0) 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)
After saving the code above, open the command window and navigate to the file's directory. Then, start the file in the command window. The command window needs to be user for permissions reasons.
sudo python3 ble_scanner.py
You should be seeing results that look something like this:
The printout above shows the Each Bluetooth Low Energy device address that the Raspberry Pi was able to read during its scan. The six hex values in 'Device xx:xx:xx:xx:xx:xx' is the unique address for each BLE device. You can also see information about received signal strength (RSSI) and other BLE information. This step is only to test that Bluepy is working correctly on your RPi. In the next step, we will investigate how to read data from specific devices.
Finding BLE Addresses with Arduino Uno
In order to connect the BLE nodes to the Raspberry Pi, we need to ensure that we have the BLE addresses for each node. Another alternative would be to name each device and then conduct a scan for specific keywords, but that is a much more complicated problem that can be explored once the user has a basic understanding of the Bluepy protocol and BLE behavior. To acquire the Bluetooth address of each BLE device, you will need to connect the TX/RX pins of the HM-10 to the TX/RX of the Arduino Uno board (as shown in Figure 1 below, taken from my HM-10 iBeacon tutorial).
In order to communicate with the HM-10 the user will need to utilize the AT command set. AT commands are a specific protocol for instructing the BLE module. First, the letters AT are used, then a + sign, then the specific command. For example, to instruct the module to print its Bluetooth address, you would type into the Arduino Serial window: 'AT+ADDR?' and the response should be 'AT+ADDR:XXXXXXXXXXXX' where the Xs are 12 unique numbers and letters that designate the Bluetooth address of the HM-10 (six hex values).
To start, it's always a good idea to type in 'AT' to which you should receive 'OK' as a response. This tells you that the communication line is established between the Arduino and the HM-10. If you receive nothing, try switching the TX/RX wires. If there's still nothing, then try going to the bottom of the window and clicking where it says 'No line ending' and select 'Both NL & CR' - this will put a newline and carriage return at the end of each command. This function works for some CC254x modules that do not have the full HM-10 firmware installed (with the crystal). The 'Both NL & CR' is selected in my example in Figure 2.
Listening for and Reading from A Specific BLE Module
Now that we have installed Bluepy and acquired the address of each BLE module, we can create a script in Python to read data from any node. Bluepy's official documentation is great for getting started (find it here). Essentially, Bluepy handles a lot of the Bluetooth protocols, but we still need to tell it what to listen for and how to handle the data. Below is a (slightly) modified version from Bluepy's documentation:
from bluepy import btle import struct class MyDelegate(btle.DefaultDelegate): def __init__(self,params): btle.DefaultDelegate.__init__(self) def handleNotification(self,cHandle,data): print("handling notification...") ## print(self) ## print(cHandle) print(struct.unpack("b",data)) p = btle.Peripheral('00:15:87:00:4e:d4') p.setDelegate(MyDelegate(0)) while True: if p.waitForNotifications(1.0): continue print("waiting...")
The code above tells Python to look for the device at address '00:15:87:00:4E:D4' and wait for intervals of 1 second for data to be received. When the BLE device wakes, the Python program should print 'waiting...' until data is received. The program will also exit with an error stating that the 'Device disconnected.' This is okay in our case because we actually ARE disconnecting the device on the Arduino end. But for testing purposes, this code functions just fine. You should receive data (assuming you're using temperature data from the Arduino IoT node) that looks like the following:
waiting... waiting... waiting... waiting... waiting... handling notification... (23,) handling notification... (50,) waiting... waiting... waiting...
In the results above, the temperature data can be read as '23.50 degrees Celsius.' If you decide to program the data in a different way (on the Arduino side), your 'struct.unpack("b",data) will look a bit different. More on the 'struct' class in Python can be found here.
Looping and Listening for Multiple Devices
The full coop code can be found here. In short, it uses the 'concurrent futures' Python library (see here) to parallel-ize listening and reading for multiple Bluetooth Low Energy devices that are transmitting data. The loop also sends a byte of data back to the BLE module to be read on the device's end. The BLE devices can be programmed to read on the Arduino end and react accordingly. If you're using the Arduino IDE serial port, you can also listen for the transmitted data and verify the two-way communication using only an HM-10/CC2541 module (this is the only one I tested).
Products from our Shop relevant to this tutorial: