The Arduino Nano 33 BLE Sense is a wonderful piece of equipment, chockfull of sensors and powerful enough to run machine learning models using TinyML. It is also equipped with Bluetooth LE (BLE) and can serve as either a central or peripheral device.
In this project, I’ll show you how you can control your Arduino Nano BLE and read sensor values and actuate controls, communicating with it via Bluetooth using Python, such as from a Windows, Mac or Linux machine like the Raspberry Pi. This will open up a lot of possibilities!
I’ll demonstrate using the onboard RGB LED of the Arduino Nano 33 BLE Sense, but it is chockfull of sensors, so you can utilize the same framework and add other sensors and actuators to the mix.
To house the Arduino Nano 33 BLE Sense and to allow for more components and circuitry in the future, I use the ProtoStax Enclosure for Breadboards/Custom Boards
Here's a short video on getting started and running the example:
Let's dive into the code - Peripheral Device aka Arduino NanoThe Generic Attribute Profile (or GATT) defines how Bluetooth LE devices can transfer data back and forth, using the Attribute Protocol (or ATT) which organizes the data hierarchically into Service and Characteristics.
For our purpose, we define a top level service, as well as 3 characteristics, corresponding to each of the 3 colors of our RGB LED:
BLEService nanoService("13012F00-F8C3-4F4A-A8F4-15CD926DA146");
BLEByteCharacteristic redLEDCharacteristic("13012F01-F8C3-4F4A-A8F4-15CD926DA146", BLERead | BLEWrite);
BLEByteCharacteristic greenLEDCharacteristic("13012F02-F8C3-4F4A-A8F4-15CD926DA146", BLERead | BLEWrite);
BLEByteCharacteristic blueLEDCharacteristic("13012F03-F8C3-4F4A-A8F4-15CD926DA146", BLERead | BLEWrite);
The UUIDs used above have been randomly generated. They must match between the peripheral device (the Nano) and the central device (the Python program).
In our setup function, apart from setting up the pinMode for the RGB LEDs, we also setup our BLE service. Here, we set the local name for our BLE peripheral - central devices will see this when trying to connect to advertising services.
We add the service and characteristics we defined above, set their initial values, and start advertising. Here, at this point, central devices that are listening will see our advertising packets and know that we are ready to accept connections.
// set advertised local name and service UUID:
BLE.setLocalName("Arduino Nano 33 BLE Sense");
BLE.setAdvertisedService(nanoService);
// add the characteristic to the service
nanoService.addCharacteristic(redLEDCharacteristic);
nanoService.addCharacteristic(greenLEDCharacteristic);
nanoService.addCharacteristic(blueLEDCharacteristic);
// add service
BLE.addService(nanoService);
// set the initial value for the characeristic:
redLEDCharacteristic.writeValue(0);
greenLEDCharacteristic.writeValue(0);
blueLEDCharacteristic.writeValue(0);
// start advertising
BLE.advertise();
In our main loop(), we wait for a central device to connect. We then wait to see if any of the characteristics are written to, and the value of that characteristic. In our example, a non-zero value written to the redLEDCharacteristic, for example, will cause us to turn on the RED LED. When we encounter a zero value, we turn off the LED.
void loop() {
BLEDevice central = BLE.central();
if (central) {
...
while (central.connected()) {
if (redLEDCharacteristic.written()) {
if (redLEDCharacteristic.value()) { // any non-zero value
Serial.println("RED LED on");
digitalWrite(RED_PIN, LOW); // LOW will turn the LED on
} else { // a zero value
Serial.println(F("RED LED off"));
digitalWrite(RED_PIN, HIGH); // HIGH will turn the LED off
}
}
.... do similar stuff for GREEN and BLUE
}
// when the central disconnects, print it out:
Serial.print(F("Disconnected from central: "));
Serial.println(central.address());
}
}
Here we have to note that the RGB LEDs on the Arduino Nano 33 BLE Sense follow a reverse logic, so setting the pin LOW turns ON the LED and setting it HIGH turns it OFF.
The onboard RGB LED also does not support PWM, so the states of the LEDs can either be ON or OFF, giving us 7 different colors:
R | G | B
OFF | OFF | OFF - OFF
ON | OFF | OFF - RED
OFF | ON | OFF - GREEN
OFF | OFF | ON - BLUE
ON | ON | OFF - ORANGE
ON | OFF | ON - PURPLE
OFF | ON | ON - CYAN
ON | ON | ON - WHITE
The Central device can also read the characteristic's value. This is automatically handled by the BLE library and we don't need to do anything more. We initialize the value appropriately on startup, and maintain the value, so it can be read anytime.
Let's dive into the code - Central Device aka PythonOn the Python side, we utilize the Python bleak library for BLE support - of course, the machine we are running it on needs to have Bluetooth support as well!
We also use the asyncio asynchronous framework for our program. Our main loop here is the run() function. It goes into discover mode and checks the local names of the Bluetooth devices that are advertising, and connects to the device with the local name of "Arduino Nano 33 BLE Sense" (the local name we chose).
It then reads the RED, GREEN and BLUE Characteristics to know the initial states of the LEDs, and then goes into the setColor() loop:
async def run():
global RED, GREEN, BLUE
print('ProtoStax Arduino Nano BLE LED Peripheral Central Service')
print('Looking for Arduino Nano 33 BLE Sense Peripheral Device...')
found = False
devices = await discover()
for d in devices:
if 'Arduino Nano 33 BLE Sense'in d.name:
print('Found Arduino Nano 33 BLE Sense Peripheral')
found = True
async with BleakClient(d.address) as client:
print(f'Connected to {d.address}')
val = await client.read_gatt_char(RED_LED_UUID)
if (val == on_value):
print ('RED ON')
RED = True
else:
print ('RED OFF')
RED = False
... do similar stuff for GREEN and BLUE
while True:
await setColor(client)
if not found:
print('Could not find Arduino Nano 33 BLE Sense Peripheral')
In setColor(), it prompts for user input, and checks the user input to see if the string has any 'r', 'g' or 'b' values in it. It then toggles the color and writes the appropriate value to the characteristic, and waits for further user input again. This continues until the user hits Control-C to stop the program.
async def setColor(client):
global RED, GREEN, BLUE
val = input('Enter rgb to toggle red, green and blue LEDs :')
print(val)
if ('r' in val):
RED = not RED
await client.write_gatt_char(RED_LED_UUID, getValue(RED))
... do similar stuff for GREEN and BLUE
Taking the Project FurtherThe Arduino Nano has a whole bunch of sensors onboard. You can extend the project by adding characteristics for the other sensors - for example, reading the temperature, humidity and pressure values (there is no need to write these values, so the characteristics can be defined read-only). You'll also need to modify the program to handle the read command - polling the sensor and writing the data with the current value of the sensor.
On the central device (Python Program), you can choose the store the data along with the timestamp of when it was read, to a database or comma-separated file. This way, your central device can act like a data logger, and you can even utilize the data to perform actions, or send the data to a cloud service like AWS to process and run analytics on. Having the central device on Python allows you to utilize the numerous packages and libraries and capabilities of Python.
Happy Coding! If you have any questions, feel free to ask them in the comments below! We'll also be happy to hear how you have taken the project further and what wonderful creations you have made!
Happy Making! 😊
Comments