Hub to PC Communication
This project shows how you can exchange data between the hub and a script running on your computer.
Standard input and output
When you run a program, input and output is normally handled via the Pybricks Code terminal pane, as demonstrated here.
But once disconnected from Pybricks Code, you can use other devices and programs to connect to the hub. In this project, you’ll learn how to connect your computer to the hub and send data to it using a Python script.
Pybricks BLE characteristics
The Pybricks firmware supports three Bluetooth Low Energy (BLE) characteristics:
- Pybricks Command/Event Characteristic
- Pybricks Hub Capabilities Characteristic
- Nordic UART Service
The first two are specific to Pybricks. They are used to download programs to the hub and run them.
The Nordic UART Service (NUS) is a standard
characteristic that can be thought of as a BLE serial port. The Pybricks
firmware uses it for things like
Fortunately, you don’t need to know about the two Pybricks characteristics if you use Pybricks Code to download a program to the hub in advance. Then the script on your computer only has to deal with the Nordic UART Service. This works as follows:
- Create a Pybricks program that handles input and output as you like. We’ll show you an example below.
- Disconnect from Pybricks Code.
- Write a script to find the hub and connect to it. We’ll show you an example for this too.
- Start your (previously loaded) Pybricks script with the button.
- Exchange data with the hub.
The next sections take you through this step by step.
Handling input and output on the hub
From the hub’s point of view, all input and output happens via the
usys.stdout files, whether you use Pybricks Code or any
In this example, we’ll make the hub listen for incoming data via
use it to control the direction of a motor:
- If the hub receives
fwd, the motor goes forward.
- If the hub receives
rev, the motor goes in reverse.
- If the hub receives
bye, the hub ends the program.
- If the hub receives something else, the motor stops.
For each successful motor command, the hub will respond with
stdout. In this example,
print("OK") would have achieved the same
To try it out, run the following program on the hub and type
in the terminal pane. You should see the motor respond accordingly, and get
OK in response.
Now end the program by pressing the button or by typing
# NOTE: Run this program with the latest # firmware provided via https://beta.pybricks.com/ from pybricks.pupdevices import Motor from pybricks.parameters import Port from pybricks.tools import wait # Standard MicroPython modules from usys import stdin, stdout from uselect import poll motor = Motor(Port.A) # Optional: Register stdin for polling. This allows # you to wait for incoming data without blocking. keyboard = poll() keyboard.register(stdin) while True: # Optional: Check available input. while not keyboard.poll(0): # Optional: Do something here. wait(10) # Read three bytes. cmd = stdin.buffer.read(3) # Decide what to do based on the command. if cmd == b"fwd": motor.dc(50) elif cmd == b"rev": motor.dc(-50) elif cmd == b"bye": break else: motor.stop() # Send a response. stdout.buffer.write(b"OK")
This program works on all hubs except for the BOOST Move Hub, since it does not
Sending and receiving data from a PC
The next step is to get your computer ready to connect to the hub and exchange some data. In essence, your script will play the role that the Pybricks Code terminal pane normally would.
To accomplish this, we need to:
- Scan for the hub.
- Connect to the hub.
- Get the NUS characteristic.
- Read and write data.
Since this is not specific to Pybricks, you can use any programming language or device that supports the Nordic UART Service. You could even do this from another MicroPython board.
In this example, we’ll show you how
to do it with Python and a BLE library called
pip install bleak
main() function in the example below will scan for the hub
and connect to it. Note that it can only find the hub if no other apps (like
Pybricks Code) are connected, and if no program is currently running.
Once it finds the hub and establishes the connection, you can start the previously loaded program on the hub by pressing the button.
At this point, the script sends several
rev commands. The hub
should respond by making the motor turn and by returning
OK. After doing
this five times, sending
bye causes the program on the hub to terminate.
# SPDX-License-Identifier: MIT # Copyright (c) 2020 Henrik Blidh # Copyright (c) 2022 The Pybricks Authors import asyncio from bleak import BleakScanner, BleakClient UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" # Replace this with the name of your hub if you changed # it when installing the Pybricks firmware. HUB_NAME = "Pybricks Hub" def hub_filter(device, ad): return device.name and device.name.lower() == HUB_NAME.lower() def handle_disconnect(_): print("Hub was disconnected.") def handle_rx(_, data: bytearray): print("Received:", data) async def main(): # Find the device and initialize client. device = await BleakScanner.find_device_by_filter(hub_filter) client = BleakClient(device, disconnected_callback=handle_disconnect) # Shorthand for sending some data to the hub. async def send(client, data): await client.write_gatt_char(rx_char, data) try: # Connect and get services. await client.connect() await client.start_notify(UART_TX_CHAR_UUID, handle_rx) nus = client.services.get_service(UART_SERVICE_UUID) rx_char = nus.get_characteristic(UART_RX_CHAR_UUID) # Tell user to start program on the hub. print("Start the program on the hub now with the button.") # Send a few messages to the hub. for i in range(5): await send(client, b"fwd") await asyncio.sleep(1) await send(client, b"rev") await asyncio.sleep(1) # Send a message to indicate stop. await send(client, b"bye") except Exception as e: # Handle exceptions. print(e) finally: # Disconnect when we are done. await client.disconnect() # Run the main async program. asyncio.run(main())
When you run the program, the expected output should be similar to:
python ./demo.py Start the pre-load program on the hub now with the button. Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Received: bytearray(b'OK') Hub was disconnected.
Further exploration: BLE terminal
In the aforementioned examples, you prepared the hub to handle commands with a specific set of actions and response messages. Using a small set of messages and commands is usually the quickest and most reliable way to do it.
If you want to make it more generic, it is also possible to programatically interact with the REPL so you can access the entire Pybricks API remotely. The REPL is normally activated with the REPL button in Pybricks Code, but you can also enter it from a program on the hub as follows:
On the computer, you can run a script like this to emulate a UART terminal. You may need to modify the scanning filters to connect to the hub.
Further exploration: Automatic start
So far, we had to load a script on the hub in advance to avoid dealing with the two Pybricks BLE characteristics.
If you want to automate the process of loading the script as well, you can use the pybricksdev library, which implements both Pybricks characteristics.
This project was submitted by The Pybricks Team.