Controlling LEGO Wedo Motor with Python GUI App in Linux

LEGO Wedo 2.0 is a good start for learning robotics. I created a simple GUI app controlling the LEGO motor using Python. In this article, I will share my experience in how to select the development environment and how to build the Python app.

Learning Resources

When I searched for `LEGO Wedo Python’ in Google, I found two informative articles: Controlling a WeDo 2.0 motor and WeDo 2.0 – reverse engineering. Both of them mentioned pygattlib, which is a Python library (Linux only) to use the GATT Protocol for BLE (Bluetooth Low Energy) devices. The smart hub included in LEGO Wedo 2.0 is a BLE device.

Command Line Tools and The Development Environment

Before writing code, we can use hcitool and gatttool to scan the Bluetooth device and test the connection.

Get the BLE address and device name:

sudo hcitool -i hci0 lescan
LE Scan ...
98:07:2D:DD:98:56 (unknown)
98:07:2D:DD:98:56 LPF2 Smart Hub

Connect to the device:

gatttool -I
[                 ][LE]> connect <ble address>

Can I use Windows Subsystem for Linux(WSL) for development?

No. You will get the following error message:

Invalid device: Address family not supported by protocol

Can I use Linux in VMWare Workstation for development?

No. You will fail to run hcitool in VMWare virtual machine.

hcitool lescan fail

According to the answer from StackOverflow, VMWare does not support BLE in Linux guests.

Building a Simple Python GUI App to Control LEGO Wedo Motor on Raspberry Pi

I finally decided to build the Python app on Raspberry Pi because I don’t have a Linux PC.

Install dependent libraries and gattlib:

sudo apt-get update
sudo apt-get install libbluetooth-dev bluez bluez-hcidump libboost-python-dev libboost-thread-dev libglib2.0-dev
sudo pip install gattlib

Create the UI Window that includes a label and some buttons:

import Tkinter as tk
from gattlib import DiscoveryService
from gattlib import GATTRequester
from time import sleep

def run():
    global button_run
    button_run.after(DELAY, motor_run)

def stop():
    global button_stop
    button_stop.after(DELAY, motor_stop)

def connect():
    global button_disconnect
    button_disconnect.after(DELAY, smart_hub_connect)

def disconnect():
    global button_disconnect
    button_disconnect.after(DELAY, smart_hub_disconnect)

def up():
    global button_up
    button_up.after(DELAY, motor_up)

def down():
    global button_down
    button_down.after(DELAY, motor_down)

root = tk.Tk()
root.title("Lego Wedo 2.0 Motor Control")

label = tk.Label(root, fg="dark green", text='N/A')
label.pack()

button_connect = tk.Button(root, text='Connect Smart Hub', width=BUTTON_WIDTH, command=connect)
button_connect.pack()

button_disconnect = tk.Button(root, text='Disconnect Smart Hub', width=BUTTON_WIDTH, command=disconnect, state='disabled')
button_disconnect.pack()

button_run = tk.Button(root, text='Run motor', width=BUTTON_WIDTH, command=run, state='disabled')
button_run.pack()

button_up = tk.Button(root, text='Speed up', width=BUTTON_WIDTH, command=up, state='disabled')
button_up.pack()

button_down = tk.Button(root, text='Speed down', width=BUTTON_WIDTH, command=down, state='disabled')
button_down.pack()

button_stop = tk.Button(root, text='Stop motor', width=BUTTON_WIDTH, command=stop, state='disabled')
button_stop.pack()

root.mainloop()

When press the connect button, use DiscoveryService to get all available devices and use GATTRequester to connect the device. My device name contains `Smart Hub’:

def smart_hub_connect():
    service = DiscoveryService("hci0")
    devices = service.discover(2)

    for address, name in devices.items():
        if name != '' and 'Smart Hub' in name:
            label['text'] = address

            global button_run, button_stop, button_disconnect, req
            button_connect['state'] = 'disabled'
            button_run['state'] = 'normal'
            button_stop['state'] = 'normal'
            button_disconnect['state'] = 'normal'
            button_up['state'] = 'normal'
            button_down['state'] = 'normal'

            req = GATTRequester(address, True, "hci0")
            break

To run the motor, we just need to send a sequence of 4 bytes to a specific handler:

def motor_run():
    global req
    if req != None:
        req.write_by_handle(0x3d, str(bytearray([0x01, 0x01, 0x01, 0x64]))
)

The first byte represents the port. The value is 01 or 02.

lego wedo motor port

Change the hardware port to see what will happen via gatttool:

[98:07:2D:DD:98:56][LE]> char-read-hnd 0015                                                                     
Characteristic value/descriptor: 01 01 00 01 01 00 00 00 01 00 00 00                                                    
[98:07:2D:DD:98:56][LE]> char-read-hnd 0015                                                                             
Characteristic value/descriptor: 02 01 01 01 01 00 00 00 01 00 00 00

The last byte defines the motor speed. Here is the code for adjusting the speed:

MAX_SPEED = 100
MIN_SPEED = 1
SPEED_CHANGE = 4

current_speed = 100
req = None

def motor_up():
    global req, current_speed
    if req != None:
        if current_speed == MAX_SPEED:
            return

        current_speed += SPEED_CHANGE
        req.write_by_handle(HANDLE, str(bytearray([0x01, 0x01, 0x01, current_speed])))
        sleep(WEDO_DELAY)

def motor_down():
    global req, current_speed
    if req != None:
        if current_speed == MIN_SPEED:
            return

        current_speed -= SPEED_CHANGE
        req.write_by_handle(HANDLE, str(bytearray([0x01, 0x01, 0x01, current_speed])))
        sleep(WEDO_DELAY)

You will fail to run the app if you don’t have a root privilege:

python lego wedo fail

Should you run the app with sudo'? No, you have to run the app with gksudo’:

gksudo python app.py

python lego wedo success

In Windows, we can use SmarTTY to display GUI for the remote Linux app.

LEGO Wedo 2.0 motor control

Source Code

https://github.com/yushulx/lego-wedo-motor-control