If you’re developing for the Internet of Things (IoT) or robotics projects, chances are you’ll need to work with Bluetooth Low Energy (BLE) protocols. In this tutorial, we’ll walk you through creating a BLE GATT client with Brainy Pi and the Python library simplepyble. This code is intended for developers who are already familiar with Python and BLE protocols.
What is a BLE GATT Client?
A GATT client is a device that connects to a BLE peripheral device and reads or writes data to its characteristics. The Generic Attribute Profile (GATT) is a standard way of defining the structure of data exchanged between BLE devices. BrainyPi is a Raspberry Pi-compatible board designed for IoT and robotics projects. It has built-in Wi-Fi and Bluetooth, making it ideal for BLE development.
Prerequisites
Before you begin, you’ll need the following:
A Brainy Pi board
Rbian OS installed on the Brainy Pi board
The simplepyble library installed on the BrainyPi board
You can install simplepyble by running the following command:
pip installsimplepyble
Writing Code for a BLE GATT client
Initializing the Bluetooth Adapter on BrainyPi
The first step is to get the Bluetooth adapter available on Brainy Pi and initialize it. The following code imports simplepyble and initializes the Bluetooth adapter:
import simplepybleDEFAULT_BRAINYPI_ADAPTER_IDX=0defon_scan_start():"""Callback function which runs when Scan is started. """print("Scan started.")defon_scan_stopped():"""Callback function which runs when Scan is stopped. """print("Scan complete.")defon_device_found(peripheral):"""Callback function which runs when peripheral device is found while scanning. """print(f"Found {peripheral.identifier()} [{peripheral.address()}]")defmain():# List all Bluetooth Adapters on the deviceadapters=simplepyble.Adapter.get_adapters()iflen(adapters)==0:print("No adapters found")# Choose the default brainypi adapteradapter=adapters[DEFAULT_BRAINYPI_ADAPTER_IDX]print(f"Selected adapter: {adapter.identifier()} [{adapter.address()}]")# Set callback functions for Scanningadapter.set_callback_on_scan_start(on_scan_start)adapter.set_callback_on_scan_stop(on_scan_stopped)adapter.set_callback_on_scan_found(on_device_found)
Scan for BLE Peripheral Devices
The next step is to scan for BLE peripheral devices. The following code scans for five seconds and lists all the BLE peripheral devices found:
# Scan for 5 seconds
adapter.scan_for(5000)peripherals=adapter.scan_get_results()
Connect to a BLE Peripheral Device
Optionally, you can ask the user to select a peripheral to connect. If you know the MAC address of the device you are connecting to, you can omit this step. The following code connects to the selected BLE peripheral device:
# Ask the user to pick a device to connect to
print("Please select a peripheral:")fori,peripheralinenumerate(peripherals):print(f"{i}: {peripheral.identifier()} [{peripheral.address()}]")choice=int(input("Enter choice: "))# Connect to the selected peripheral deviceperipheral=peripherals[choice]print(f"Connecting to: {peripheral.identifier()} [{peripheral.address()}]")peripheral.connect()
Get Services and Characteristics of the BLE Device
Once connected to the BLE peripheral device, you can retrieve the services and characteristics it offers. The following code retrieves all the services:
# Get all the services
services=peripheral.services()
Optionally, you can ask the user to choose a service to interact with. If you know the UUID of the service and characteristic you want to work with, you can omit this step. The following code prompts the user to select a service and characteristic pair:
# Create a list of service and characteristic pairs
service_characteristic_pair=[]forserviceinservices:forcharacteristicinservice.characteristics():service_characteristic_pair.append((service.uuid(),characteristic.uuid()))# Ask the user to pick a service/characteristic pairprint("Please select a service/characteristic pair:")fori,(service_uuid,characteristic)inenumerate(service_characteristic_pair):print(f"{i}: {service_uuid}{characteristic}")choice=int(input("Enter choice: "))service_uuid,characteristic_uuid=service_characteristic_pair[choice]
Read Data from the Characteristic
Mow, to read data from a characteristic, you can use the read method. The following code reads the contents of a characteristic:
# Read the contents of the characteristic
contents=peripheral.read(service_uuid,characteristic_uuid)print(f"Contents: {contents}")
Write Data to the Characteristic
Now, to write data to a characteristic, you can use the write_request method. The following code prompts the user to enter the content to write and sends it to the characteristic:
# Ask the user for the content to write
content=input("Enter content to write: ")# Write the content to the characteristicperipheral.write_request(service_uuid,characteristic_uuid,str.encode(content))
Notify Characteristic Data Changes
If the characteristic supports notifications, you can subscribe to it and receive notifications when the data changes. The following code subscribes to the characteristic and waits for notifications for five seconds:
def on_notification(data):print(f"Notification: {data}")# Subscribe to the notification characteristicperipheral.notify(service_uuid,characteristic_uuid,on_notification)# Wait for notification from the deviceimporttimetime.sleep(5)
Example Code
Here’s an example code that puts everything together:
import simplepybleimporttimeDEFAULT_BRAINYPI_ADAPTER_IDX=0defon_scan_start():print("Scan started.")defon_scan_stopped():print("Scan complete.")defon_device_found(peripheral):print(f"Found {peripheral.identifier()} [{peripheral.address()}]")defon_notification(data):print(f"Notification: {data}")defmain():adapters=simplepyble.Adapter.get_adapters()iflen(adapters)==0:print("No adapters found")adapter=adapters[DEFAULT_BRAINYPI_ADAPTER_IDX]print(f"Selected adapter: {adapter.identifier()} [{adapter.address()}]")adapter.set_callback_on_scan_start(on_scan_start)adapter.set_callback_on_scan_stop(on_scan_stopped)adapter.set_callback_on_scan_found(on_device_found)adapter.scan_for(5000)peripherals=adapter.scan_get_results()print("Please select a peripheral:")fori,peripheralinenumerate(peripherals):print(f"{i}: {peripheral.identifier()} [{peripheral.address()}]")choice=int(input("Enter choice: "))peripheral=peripherals[choice]print(f"Connecting to: {peripheral.identifier()} [{peripheral.address()}]")peripheral.connect()services=peripheral.services()service_characteristic_pair=[]forserviceinservices:forcharacteristicinservice.characteristics():service_characteristic_pair.append((service.uuid(),characteristic.uuid()))print("Please select a service/characteristic pair:")fori,(service_uuid,characteristic)inenumerate(service_characteristic_pair):print(f"{i}: {service_uuid}{characteristic}")choice=int(input("Enter choice: "))service_uuid,characteristic_uuid=service_characteristic_pair[choice]contents=peripheral.read(service_uuid,characteristic_uuid)print(f"Contents: {contents}")content=input("Enter content to write: ")peripheral.write_request(service_uuid,characteristic_uuid,str.encode(content))peripheral.notify(service_uuid,characteristic_uuid,on_notification)time.sleep(5)if__name__=="__main__":main()
Running the code
Lets run the code
python3 ble_gatt_client.py
Conclusion
Congratulations! You’ve learned how to create a BLE GATT client with Brainy Pi and Python. You can now scan for BLE peripheral devices, connect to a device, read and write data to characteristics, and receive notifications when the data changes. This knowledge will be valuable for developing IoT and robotics projects that involve BLE communication.
Remember to ensure that you have the necessary hardware and software prerequisites, such as the Brainy Pi board with Rbian OS and the simplepyble library. Feel free to modify the code to suit your specific requirements and explore more advanced features of BLE communication.
Happy coding and have fun building innovative IoT and robotics projects with Brainy Pi and BLE!