Raspberry Pi, meet Arduino. Arduino, meet Raspberry Pi. Let’s talk Bluetooth (LE).

Some months back, we had a brief look at the Arduino Nano 33 IoT board. It had Bluetooth built-in so what we did was hook up a switch to one of it’s digital input pin and then programmed the Arduino board so that every time the switch is pressed or depressed, the state of the switch is updated. We also added a Bluetooth service to allow a user (or another device) to be notified when the state of the switch changes.

In this blog post, we will take a look at the raspberry pi and how we can get it to communicate with the Arduino board via Bluetooth.

There are quite a few good BLE helper libraries out there that can make our job easier. But not all are created equal, some may be perfect for building peripheral BLE applications however may not be the best for central BLE applications. As we are going to make the raspberry pi the client, we will be using Bleak. Bleak is a GATT client software, capable of connecting to BLE devices acting as GATT servers. It is designed to provide a asynchronous, cross-platform Python API to connect and communicate with e.g. sensors.

A high level sequence diagram

The above diagram consists of 3 main elements:

  • A Raspberry Pi
  • An Arduino board
  • A Switch

The raspberry pi here can act as a gateway between the local network and the cloud. The reason we want to use a raspberry pi is that it is not resource constraint as an Arduino board. The raspberry pi is a single board computer (SBC) with an operating system, and because it is managed by an operating system we get the same benefits as we do on a PC such as multi-tasking, develop applications using higher languages such as Python and C#, hosting .NET applications and the list goes on. On the other hand, being managed by an operating system means access to lower level services and hardware resources are not as simple.

The Arduino board on the other hand acts as a bridge between the raspberry pi and the Switch. It is a microcontroller, has no operating system and host a single program written using a lower level language such as C/C++. The Arduino board makes it very easy to work with other electronic devices which is why I see the raspberry pi and Arduino board as complementary devices rather than competing devices. Which is, use the raspberry pi to talk to the cloud, use the Arduino board to talk to the sensors/actuators, and use the raspberry pi and the Arduino board as a bridge for the cloud to communicate with the sensors/actuators.

Finally, the Switch is acting as a sensor. It signals the Arduino board when it is pressed or depressed and in turn the Arduino board will signal the raspberry pi.

Setting up Python environment on Raspberry Pi

Bleak is a Python library, so let’s set up a Python 3 virtual environment on the raspberry pi. If you are not familiar with Python’s virtual environments, have a look at my previous blog post Managing Python Environments and Dependencies.

Setting up Python virtual environment on raspberry pi

Now pip install Bleak.

(venv) pi@raspberrypi:~/Workspace/ble_client $ pip install bleak
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting bleak
  Using cached https://www.piwheels.org/simple/bleak/bleak-0.12.1-py2.py3-none-any.whl (123 kB)
Collecting dbus-next
  Using cached https://www.piwheels.org/simple/dbus-next/dbus_next-0.2.3-py3-none-any.whl (57 kB)
Installing collected packages: dbus-next, bleak
Successfully installed bleak-0.12.1 dbus-next-0.2.3
(venv) pi@raspberrypi:~/Workspace/ble_client $ pip list
Package    Version
---------- -------
bleak      0.12.1
dbus-next  0.2.3
pip        21.2.4
setuptools 57.4.0
wheel      0.37.0
venv) pi@raspberrypi:~/Workspace/ble_client $

Get the Arduino board set up

Load the following sketch onto the Arduino board.

#include <ArduinoBLE.h>
#define SWITCH_PIN 2u
static bool isPressed;
BLEService switchService("8158b2fd-94e4-4ff5-a99d-9a7980e998d7");
BLEByteCharacteristic switchServiceCharacteristic("8158b2fe-94e4-4ff5-a99d-9a7980e998d7", BLERead | BLENotify);
void setup()
   pinMode(SWITCH_PIN, INPUT);
   isPressed = false;
   if (!BLE.begin())
      /* Just keep looping until BLE module is up and running. */
      while (1);
   /* BLE module is up and running, now add our service and characteristic to it. */
   BLE.setLocalName("My Arduino");
   Serial.println("My Arduino started");
void loop()
   if (digitalRead(SWITCH_PIN) == HIGH)
      if (!isPressed)
         digitalWrite(LED_BUILTIN, HIGH);
         isPressed = true;
         Serial.println("Switch is pressed");
      if (isPressed)
         digitalWrite(LED_BUILTIN, LOW);
         isPressed = false;
         Serial.println("Switch is depressed");

Now if we connect to the Arduino serial port using a terminal such as Putty for example, we should be able to see some console output when the switch is pressed or depressed.

Right I think we are now ready to interact with the Arduino board via BLE.

Let’s talk BLE

From the sketch above, we gave the Arduino board a Bluetooth device name My Arduino. We then added a service with one characteristic and we also state that this characteristic can be read or registered to be notified when the value changes.

Just as a side note, because the raspberry pi initiated the Bluetooth connection, it is considered as a BLE central device (a client). The Arduino board on the other hand accepts connection requests and is providing a service, it is considered as a BLE peripheral device (a server).

Let’s add some Python code to the raspberry pi to start receiving BLE updates from out Arduino board.

import asyncio
import sys

from bleak import BleakScanner
from bleak.backends.bluezdbus.client import BleakClientBlueZDBus

device_name = "My Arduino"
switch_status_char_uuid = "8158b2fe-94e4-4ff5-a99d-9a7980e998d7"

def notification_handler(sender, data):
    print("Switch is active: {}".format(bool(data[0])))

async def run():
    client = None
    device = await BleakScanner.find_device_by_filter(
        lambda d, ad: d.name and d.name.lower() == device_name.lower()

    if device is None:
        print("{} not found".format(device_name))
        print("{} found".format(device))

    client = BleakClientBlueZDBus(device)

    while True:
        if not client.is_connected:
                if await client.connect():
                    print("Connected to {}".format(device_name))
                    await client.start_notify(switch_status_char_uuid, notification_handler)
                print("Connected to {} failed or lost".format(device_name))
                await asyncio.sleep(1)
                client = BleakClientBlueZDBus(device)
            await asyncio.sleep(1)

loop = asyncio.get_event_loop()

Basically what the program above does is scan for a specific BLE device with the name of “My Arduino”. If one was in range it will connect to it. The program then start observing the switch status characteristic and when an update is received, it will be handled by notification_handler().

Let’s see them in action

Below is a short clip showing:

  • Arduino Nano 33 IoT powered by a portable USB charger
  • A switch connected to the Arduino
  • Phone connected to Raspberry Pi via SSH (Using Termius)

As we can see, when the switch is pressed or depressed, we can see that the phone received a BLE notification.

That is basically it, we can connect whatever sensor we like (analog or digital) and add the necessary BLE services to our Arduino device as required.

Categories: Arduino, Bluetooth, Internet of things, Python, Raspberry Pi

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: