Making an Android app for LEGO Powered Up – explanation

So how does this “App Inventor”-thing work?

To create an app for Android we just need a browser to access the online app creator tool. We have a Design view where we add our components like buttons and sliders and a Blocks view where we use the method blocks provided  by each component we added to our Design.

We can build our app and download an ‘apk’ file to install on our Android phone or tablet but that’s not practical while we are still testing some ideas and debugging the way our blocks work. So the best way is installing the MIT AI2 Companion App on our Android device so every action we make in the online tool is synchronized to it through USB or wi-fi.

App Inventor 2 provides a Palette with lots of usefull components but to use LEGO Powered Up we need to add an Extension that allows AI2 to talk with Bluetooth Low Energy devices. At the moment BluetoothLE is the only supported extension and I must say they have been doing a great work this last year (they even gave me access to a beta version a few months ago while fixing a bug I was dealing with) including writing a good explanation of all the blocks provided with some examples for Arduino and BBC micro:bit.

After importing the extension  we just drag it to the Viewer to get a ‘BluetoothLE1′ component at the bottom of the Viewer, on the “Non-visible components” area. Doesn’t seem much but if we change to Blocks view and look to this component we’ll see lots of blocks.

So let’s start creating our App!

We will use some BluetoothLE blocks to communicate with our LEGO “HUB NO.4” device. This blocks require a ServiceUUID and a CharUUID that we will store in global variables:

Currently the ServiceUUID and CharUUID are the same as LEGO BOOST hub but please note that there is no guarantee that future firmware releases will keep them allways equal.

We will also create a list of our devices:

Yes, a list of one device is silly but I have more devices like my BOOST Vernie and of course I’m planning to have a few more Powered Up devices in a near future so a list is usefull. You can call whatever you want to your devices but you need the Bluetooth address of it (so “PUP#1” is just an easy to remember tag that I choose for my “90:84:2B:06:AB:5D” hub).

You can get your BT address installing Nordic nRF Connect for Mobile app on your Android device and scanning nearby BLE devices while turning your LEGO hub on. If you changed its name with the LEGO App you will see a BLE device with that name, if not you will see “HUB NO.4” or “Smart Hub”, depending on the firmware version of your hub:

Now we go to Design View and add all components needed:

From the ‘User Interface’ section:

  • two buttons: ‘BtnConnection’ and ‘BtnRst’
  • one ListPicker

From the ‘Drawing and Animation’ section:

  • one Canvas
  • one ImgSprite

And from the ‘Sensors’ section:

  • one Clock

I added a few others that are really not needed, just used them for aligning and cosmetic purposes.  I also renamed the buttons names to better remember their purpose and changed some of their properties.

Now back to our Blocks view, we need to take care of what happens when our App starts:

We start a Bluetooth LE scan to find all BLE devices nearby. This is necessary later on when we want to connect to our LEGO device.

Then we create our list of Hubs (seen above), set the text of the Button used for the BLE connection, draw our Joystick at the center of the canvas and initialize our Clock.

This Clock will be used to keep the BLE connection active after we connect: we need to keep talking with the LEGO hub because after a pre-defined period without communication it will shutdown to prevent battery draining.

For now we just keep the clock disabled and set the period  to match the global variable “TRACKSPERIOD”.

We also define what happens when we click our ListPicker:

(we change the text to the friendly name of the chosen LEGO hub)

Now we define what happens when we click on our connection Button – we want it to connect to the device we chose whenever there is no connection yet and to disconnect when already is:

(we also activate or deactivate our Clock at the sametime)

I will not explain the blocks related to the Joystick – they are used to calculate the duty cycle (speed) of the two motors from the position of the joystick. These values are store in two global variables: ‘SpeedA’ and ‘SpeedB’.

The next important part is sending the calculated values to the motors.

The hexadecimal command used to control a WeDo 2 Motor (we should probably call it a “Powered Up Medium Motor) is:

0800810 p 115100 dt

where:

  • ‘p’ is ‘0’ if we are using ‘Port A’ and ‘1’ if using ‘Port B’
  • dt is the hexadecimal representation of the duty cycle (a percentage value)

The AI2 BuetoothLE extension uses a list of decimal values so

8 0 129 0/1 17 81 0 dt

Lucky for us the extension also accepts signed or unsigned values so we don’t need to take care of the conversion when duty cycle is negative:

so every time the Clock reaches our “TRACKSPERIOD” value it sends the commands for the two motors. Setting a smaller period allows a better control but also increases the activity of the App so for too small periods it might not work properly. And, of course, setting a larger period will increase the latency of our control and might also cause our connection to drop.

Hope this explanation is clear enough for anyone that wants to try their own app. Feel free to make questions on my YouTube channel, I’ll try to answer the best I can.

Making an Android app for LEGO Powered Up

The new LEGO trains are now Powered Up based.

Like WeDo 2 and BOOST, this is a Bluetooth 4 Low Energy (BLE) device that uses the new type of 6-pin plug that LEGO announced a couple of years ago with the WeDo 2. At that time this new design was referenced as Power Functions 2 or PF2 but the final name is now Powered Up.

Unlike BOOST, the new smart hub included with the trains doesn’t include motors. LEGO found a way to arrange 6 AAA batteries inside it in such a compact way that the final size is exactly the same of the Power Functions LiPo or AAA batteries… with all the new electronics included:

Others like Sariel and Hispabrick already reviewed this device so I’ll just show how to to use it.

This new hub announces itself as “HUB NO.4”.  It will probably have a much better name but for now I will call it this way.

A good thing with “HUB NO.4” is that it  provides the same UUID services as BOOST. So most of the examples I wrote for BOOST work with Powered Up with just a few modifications.

For instance, the LEGO WeDo 2 motors can be used the same way as with BOOST:

gatttool -b 90:84:2B:06:AB:5D --char-write-req --handle 0x0e --value 0800810011510060

The handle (“0Eh”) is the same. The payload is also simillar, with the same 3 initial bytes (“080081”, hexadecimal) followed by a fourth byte that selects the output port (“00” = port A, “01” = port B), followed by the same 3 bytes (“115100”) and finally the last byte is the duty cycle (or speed) applied to the motor (“60h” = 100d = 100%).

Since I already had an Android app made with MIT App Inventor 2 for the  Vernie model of the BOOST set I made just a few modifications to make it work with “HUB NO.4”:

  • removed the blocks that controlled the head and cannon trigger
  • removed the blocks that sensed the colors
  • added the BT address of my “HUB NO.4” to the list of devices to pick
  • changed the motor commands used by BOOST (simultaneous control of pair A+B) by the motor commands of two WeDo 2 motors

That’s it!

This the Designer view:

and this is the Blocks view:

If you’re interested, I exported the project as an “.aia” file. It’s not polished (for instance the blocks still make references to Vernie) but you get a good base to start.

In my next article I try to explain how this App was created.

Virtual Mindstorms – using LEGO EV3 software on Linux

Yesterday Marc-André Bazergui incentivized me to make a video showing how to use LEGO MINDSTORMS EV3 Software inside a virtual machine. It is a shame that a product running Linux inside can only be used on PC or Mac – and that’s one of the reasons I started using ev3dev as I only have linux systems (laptops, Raspberry Pi’s, old DIY desktops without a Windows license…).

I got my first EV3 exactly 3 years ago as a birthday gift from my wife. I don’t remember if I ever installed the Windows software on a VM before – I did installed one or twice in Ubuntu with Wine (not sure why) and I did installed a Microsoft Robotics Developer Studio in a VMware Workstation virtual machine and do remember having connected it to the the EV3 thorough a bluetooth USB dongle (most modern hypervisors have this nice feature to allow a local device on the host to be passed-through into the guest).

I no longer have VMware Workstation but I have used Innotek VirtualBox in the past and knew that Oracle somehow managed to keep it alive after buying it (Oracle has the morbid habit of poisoning every good thing it owns – Java, Solaris, OpenOffice, MySQL…).

So I installed Oracle VM VirtualBox 5.1.4 (there is even a x64 .deb package for Ubuntu 16.04 “Xenial”) and after that the VirtualBox 5.1.4 Oracle VM VirtualBox Extension Pack.

It was quite easy and also very fast. After that I got a licensed version of Microsoft Windows 8 Professional (x64 also) – this is my work laptop so people immediatlely started making fun of me – hey, he is installing Windows on his laptop, finally!

The rest of the process was also quite easy after all – like I thought, it is possible to use a Bluetooth USB dongle and also just the direct USB cable connection:

  • create a Virtual Machine
  • make sure “Enable USB Controller” is checked and USB 2.0 (EHCI) Controller is selected – it might also work with USB 3.0
  • add an USB Device Filter for each USB device you want to passthrough into the VM (the EV3 itself and/or the Bluetooth dongle)
  • install Windows
  • present VirtualBox Guest Additions CD Image and install
  • define a Shared Folder so you can pass drivers and binaries into the VM
  • if the Bluetooth dongle is not automatic configured, install the proper drivers
  • pair the EV3 (or plug the USB cable)
  • install LEGO Mindstorms EV3 software and run it

I made a video showing every step (just skipped the LEGO Software as it’s pretty straightfoward):

Just one note: although USB cable connection seems to work fine, i tried to upgrade my EV3 firmware several times with no success – every single time it hangs at 0%. Perhaps it behaves better with another Windows version… who knows?

Edit: Laurens Valk and David Lechner know. So I made a second post showing how to upgrade the firmware.

Running ev3dev on a Raspberry Pi 3

A few days ago the ev3dev project launched a great feature: nightly image builds. Right after that I got a received a notice that they included in the image for Raspberry Pi 2/3 support for onboard the Bluetooth and needed to test it.

So I did test it. And found out that onboard Bluetooth indeed works… as also onboard Wi-Fi… as also the Brick Pi, no need to disable BT. Yeah, no more USB dongles!

The procedure is very simple – the really important step is freeing the hardware serial port for the BrickPi (both the onboard Bluetooth and the BrickPi need a UART so a soft UART (“miniuart”) is used for BT instead of the default one.

  • get the latest nightly image build for the Pi2/Pi3 (mine was 26 July 2016) and restore it to a microSD card
  • insert the card in the Pi3
  • connect an USB keyboard and a HDMI display to the Pi3
  • power up the Pi
  • login (robot + maker) – if you cannot see the login prompt change to the proper console with Alt+F1 or Alt+F2 or Alt+F[n]
  • run ‘sudo connmanctl’ to configure BT and Wi-Fi (see this tutorial on how to configure Wi-Fi from command line; for BT just run ‘sudo connmanctl enable bluetooth’)
  • edit the ‘/boot/flash/config.txt’ and uncomment these 4 lines:
    • dtoverlay=brickpi
    • init_uart_clock=32000000
    • dtoverlay=pi3-miniuart-bt
    • core_freq=250
  • sudo reboot
  • remove the display and the keyboard and from now on just connect through Wi-Fi

To test that both Bluetooth and the BrickPi work properly I used a python script to read the NXT ultrasonic sensor (in the first input port) and change the color of my WeDo 2.0 Smart Hub from green to red:

#!/usr/bin/python

# run with sudo
# assumes NXT Ultrasonic at INPUT #1

from ev3dev.auto import *
from gattlib import GATTRequester
from time import sleep

BTdevice = "hci0"       # BlueTooth 4.0 BLE capable device

WeDo2HubAddress  = "A0:E6:F8:1E:58:57"

InputCommand_hnd = 0x3a
OutputCommand_hnd  = 0x3d

RGBAbsoluteMode_cmd = str(bytearray([01,02,06,17,01,01,00,00,00,02,01]))
RGBAbsoluteOutput_cmd = str(bytearray([06,04,03]))  # or "\x06\x04\x03"

DELAY      = 0.3

# DO NOT FORGET TO CONFIG FOR US sensor:
# sudo echo nxt-i2c > /sys/class/lego-port/port0/mode
# sudo echo "lego-nxt-us 0x01" > /sys/class/lego-port/port0/set_device
#
us = UltrasonicSensor('ttyAMA0:S1:i2c1')
assert us.connected

req = GATTRequester(WeDo2HubAddress,True,BTdevice)
sleep(DELAY)

# configure RBG LED to Absolute Mode (accepts 3 bytes for RGB instead of default Index Mode)
req.write_by_handle(InputCommand_hnd,RGBAbsoluteMode_cmd)

while(True):
  if (us.value() < 10):
    print("!")
    req.write_by_handle(OutputCommand_hnd, RGBAbsoluteOutput_cmd+chr(255)+chr(0)+chr(0))
    sleep(DELAY)
  else:
    print("-")
    req.write_by_handle(OutputCommand_hnd, RGBAbsoluteOutput_cmd+chr(0)+chr(255)+chr(0))
    sleep(DELAY)

My script need the gattlib library to talk with Bluetooth Low Energy devices. You can install this library with ‘pip’ but first need to install some dependencies:

sudo apt-get install pkg-config libboost-python-dev libboost-thread-dev libbluetooth-dev libglib2.0-dev python-dev

then

sudo pip install gattlib

LEGO WeDo 2.0 – playing sound

Este artigo é a parte 4 de 6 da série  WeDo 2.0 - reverse engineering

Great news – LEGO Eduction released the WeDo 2.0 SDK today!

After digging into it, I found the information needed to control the Piezo: as expected, it’s controlled by the same handle that is used for controlling the motor and the RGB LED (0x003d). The “port” is “05” and the “command” to activate the Piezo is “02”, followed by a payload of “04” bytes containing:

  • the Frequency in Hz (2 bytes, reversed)
  • the duration in ms (2 bytes, reversed)

So to play a “C” (or “Do”, 261 Hz) during 1/8 of a second (125 ms) we use this command:

[A0:E6:F8:1E:58:57][LE]> char-write-cmd 003d 050204B801E803

So let’s hear the very first music played by a WeDo 2.0 from a linux shell script:

#!/usr/bin/env bash

# In Ubuntu run this script with sudo
# "Imperial March on a WeDo 2.0" was inspired by https://gist.github.com/tagliati/1804108

# command: gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204

# beep(a, 500) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801F401
sleep 0.5

# beep(a, 500) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801F401
sleep 0.5

# beep(a, 500) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801F401
sleep 0.5

# beep(f, 350)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D015E01
sleep 0.35

# beep(cH, 150)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B029600
sleep 0.15

# beep(a, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801F401
sleep 0.5

# beep(f, 350)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D015E01
sleep 0.35

# beep(cH, 150)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B029600
sleep 0.15

# beep(a, 1000)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801E803
sleep 1.0

# beep(eH, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049302F401
sleep 0.5
    
# beep(eH, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049302F401
sleep 0.5

# beep(eH, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049302F401
sleep 0.5

# beep(fH, 350) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204BA025E01
sleep 0.35

# beep(cH, 150)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B029600
sleep 0.15

# beep(gS, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049F01F401
sleep 0.5    

# beep(f, 350)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D015E01
sleep 0.35

# beep(cH, 150)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B029600
sleep 0.15

# beep(a, 1000)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801E803
sleep 1.0
 
# beep(aH, 500)   
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502047003F401
sleep 0.5

# beep(a, 350) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B8015E01
sleep 0.35

# beep(a, 150)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B8019600
sleep 0.15

# beep(aH, 500)   
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502047003F401
sleep 0.5

# beep(gSH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502043E03FA00
sleep 0.25

# beep(gH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502041003FA00
sleep 0.25

# beep(fSH, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204E4027D00
sleep 0.125

# beep(fH, 125) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204BA027D00
sleep 0.125  
   
# beep(fSH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204E402FA00
sleep 0.25

# delay(250)
sleep 0.25

# beep(aS, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204C701FA00
sleep 0.25

# beep(dSH, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502046E02F401
sleep 0.5

# beep(dH, 250)  
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502044B02FA00
sleep 0.25

# beep(cSH, 250)  
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502042A02FA00
sleep 0.25

# beep(cH, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B027D00
sleep 0.125

# beep(b, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204D2017D00
sleep 0.125

# beep(cH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B02FA00
sleep 0.25
      
# delay(250)
sleep 0.25

# beep(f, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D017D00
sleep 0.125

# beep(gS, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049F01F401
sleep 0.5

# beep(f, 375) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D017701
sleep 0.375

# beep(a, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B8017D00
sleep 0.125
  
# beep(cH, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B02F401
sleep 0.5

# beep(a, 375)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B8017701
sleep 0.375

# beep(cH, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B027D00
sleep 0.125

# beep(eH, 1000)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049302E803
sleep 1.0

# beep(aH, 500)   
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502047003F401
sleep 0.5

# beep(a, 350) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B8015E01
sleep 0.35

# beep(a, 150)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B8019600
sleep 0.15

# beep(aH, 500)   
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502047003F401
sleep 0.5

# beep(gSH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502043E03FA00
sleep 0.25

# beep(gH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502041003FA00
sleep 0.25

# beep(fSH, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204E4027D00
sleep 0.125
    
# beep(fH, 125) 
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204BA027D00
sleep 0.125

# beep(fSH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204E402FA00
sleep 0.25

# delay(250)
sleep 0.25

# beep(aS, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204C701FA00
sleep 0.25

# beep(dSH, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502046E02F401
sleep 0.5

# beep(dH, 250)  
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502044B02FA00
sleep 0.25

# beep(cSH, 250)  
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502042A02FA00
sleep 0.25

# beep(cH, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B027D00
sleep 0.125

# beep(b, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204D2017D00
sleep 0.125

# beep(cH, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B02FA00
sleep 0.25

# delay(250)
sleep 0.25

# beep(f, 250)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D01FA00
sleep 0.25

# beep(gS, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502049F01F401
sleep 0.5 
  
# beep(f, 375)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D017701
sleep 0.375

# beep(cH, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502040B027D00
sleep 0.125
           
# beep(a, 500)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801F401
sleep 0.5

# beep(f, 375)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 0502045D017701
sleep 0.375

# beep(c, 125)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 05020405017D00
sleep 0.125

# beep(a, 1000)
gatttool -i hci0 -b A0:E6:F8:1E:58:57 --char-write-req -a 0x003d -n 050204B801E803
sleep 1.0
 

 

 

WeDo 2.0 – reverse engineering

Este artigo é a parte 1 de 6 da série  WeDo 2.0 - reverse engineering

This post tries to gather all the information I collected in the last weeks related to the WeDo 2.0.

I’m not a programmer but I’m a very stubborn guy so after I managed to get my linux systems (my Ubuntu laptop, some Raspberry Pi’s and my two Mindstorms EV3) controlling the SBrick I told to myself: I’m gonna make the same with this new WeDo 2.0 system no matter what it takes.

So first things first: let’s use my Android mobile to inspect the WeDo 2.0 Hub. Nordic has a very good (and free) app that I like to use: nRF Master Control Panel (recently renamed to nRF Connect). After connecting to the Hub we find 6 services:

BLE services

Some of this services, like “Battery Service” are known BLE services so [hopefully] it will be easy to use.

There’s also a very important finding at the top:

“U0001 A0:E6:F8:1E:58:57

“A0:E6:F8:1E:58:57” is the Bluetooth address of my Hub (similar to the MAC Address of every network device). It will be used *a lot* in the rest of this post.

And “U0001” is the friendly name that the Hub advertises – that’s the name that shows up in the LEGO Education WeDo 2.0 App when connecting to the Hub:

WeDo 2.0 making a connection

A note about this name: before I finally managed to make the LEGO Education WeDo 2.0 App work in my Android phone, my Hub advertised itself as “LPF2 Smart Hub 2 I/O”. So the LEGO App changed it to “u0001”, probably at the first time it connected to it (but since my Hub was first used by 3 other AFOL’s at Paredes de Coura I’m not sure if the process is automatic or the user is given some kind of option).

So the default (factory set) name of the Hub is “LPF2 Smart Hub 2 I/O” – LEGO Power Functions 2 Smart Hub 2 I/O”. Not much to speculate here: LEGO announced that Power Functions and Mindstorms will adopt a new plug type so new devices are expected, this is just the first one. But “Smart Hub 2 I/O” is interesting… does that means that there will be other Smart Hubs? Perhaps even a “Smart Hub 4 I/O”? That would answer some of the points I have been discussing with Fernando Conchas like “what’s the use for a 4.5 Volt device in LEGO Technic unless there’s also another device with better power features just waiting to come out”?

Now let’s look deeper to those BLE services…

I can use the nRF App and take a lot of screenshots but now that I know the BT address I will switch to my Ubuntu laptop and use 2 of the available BlueZ (the native Linux Bluetooth stack) functions, ‘hcitool’ and ‘gatttool’

$ sudo hcitool -i hci0 lescan
LE Scan ...
A0:E6:F8:1E:58:57 (unknown)
A0:E6:F8:1E:58:57 LPF2 Smart Hub 2 I/O
sudo gatttool -i hci0 -b A0:E6:F8:1E:58:57 --primary
attr handle = 0x0001, end grp handle = 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle = 0x0008, end grp handle = 0x000b uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle = 0x000c, end grp handle = 0x002f uuid: 00001523-1212-efde-1523-785feabcd123
attr handle = 0x0030, end grp handle = 0x003e uuid: 00004f0e-1212-efde-1523-785feabcd123
attr handle = 0x003f, end grp handle = 0x0045 uuid: 0000180a-0000-1000-8000-00805f9b34fb
attr handle = 0x0046, end grp handle = 0xffff uuid: 0000180f-0000-1000-8000-00805f9b34fb

The ‘gatttool’ command is a powerfull tool for BLE – in the past, without a proper BLE library for python, I used it (through python system calls) to talk with the SBrick. Clumsy but… hey, I said I’m not a programmer 😉

The ‘gatttool’ can run in an interactive mode that allows us to establish a connection and keep it until we disconnect instead of making a new connection each time we want to test a command:

$ sudo gatttool -i hci0 -I
[                 ][LE]> connect A0:E6:F8:1E:58:57
Attempting to connect to A0:E6:F8:1E:58:57
Connection successful
[A0:E6:F8:1E:58:57][LE]>

In this “interactive” session we just send ‘primary’ to get the same output as using the command with ‘–primary’ option [but sometimes the commands differ a bit, so use ‘help’ and ‘–help’ to know what to use.

So the ‘primary’ command gets a list of the primary services offered by the WeDo 2.0 Hub. Of course, those are the same 6 services found by the Nordic app but that screenshot looks much better as Nordic developers added lots of intelligence to it.

First service:

attr handle = 0x0001, end grp handle = 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb

The Nordic app shows just:

Generic Access
UUID: 0x1800
PRIMARY SERVICE

So this service has 7 handles assigned (from 0x0001 to 0x0007) and serves a well know service (the ‘Generic Access‘) so it’s UUID is shortened to just 0x1800 instead of ‘00001800-0000-1000-8000-00805f9b34fb’.

Bluetooth specification for ‘Generic Access’ defines 5 properties:

  • Device Name (0x2A00)
  • Appearance (0x2A01)
  • Peripheral Privacy Flag (0x2A02)
  • Reconnection Address (0x2A03)
  • Peripheral Preferred Connection Parameters (0x2A04)

From these list, only ‘Device Name’ and ‘Appearance’ are defined as ‘Mandatory’.

With the gatttool we read these properties with command ‘char-read-uuid’:

[A0:E6:F8:1E:58:57][LE]> char-read-uuid 0x2A00
handle: 0x0003      value: 75 30 30 30 31 
[A0:E6:F8:1E:58:57][LE]> char-read-uuid 0x2A01
handle: 0x0005      value: 00 00 
[A0:E6:F8:1E:58:57][LE]> char-read-uuid 0x2A02
Error: Read characteristics by UUID failed: No attribute found within the given range
[A0:E6:F8:1E:58:57][LE]> char-read-uuid 0x2A03
Error: Read characteristics by UUID failed: No attribute found within the given range
[A0:E6:F8:1E:58:57][LE]> char-read-uuid 0x2A04
handle: 0x0007      value: 50 00 a0 00 00 00 e8 03

So ‘Peripheral Privacy Flag’ and ‘Reconnection Address’ were not implemented by LEGO. But what’s the meaning of this hexadecimal values?

Device Name‘ is a string so we just convert it to ASCII (we can use an online tool like RapidTables):

75 30 30 30 31 -> u0001

Ah-ha!

Appearance‘ is two-byte value “composed of a category (10-bits) and sub-categories (6-bits)” that classifies the device. Since it is ‘0’, it’s not classified in any known category (so it is ‘Unknown’).

Peripheral Preferred Connection Parameters‘ is an 8-byte value containing 4 parameters (each one is 2-byte):

Minimum Connection Interval
Maximum Connection Interval
Slave Latency
Connection Supervision Timeout Multiplier

Each value is written in reverse order so

Minimum Connection Interval =005h = 80d
Maximum Connection Interval = 000ah = 160
Slave Latency = 0
Connection Supervision Timeout Multiplier = 03e8h=1000d

According to definition, the Min/Max Connection Interval values should be multiplied by 1.25 so the range is in fact 100~200 [ms].

So let’s return to the Android and validate these:

WeDo 2.0 'Generic Access' primary service

Yes, it certainly looks good!

Next post we’ll see another well-know service: the ‘Battery Service’.

 

WeDo 2.0 colors with python

Now that I can finally use LEGO Education WeDo 2.0 app let’s use my latest crazy aquisition: the Ubertooth One (an open source Bluetooth sniffer).

I used a simple project to change the WeDo 2.0 Hub color to ‘pink’ (‘1’) each time I click the ‘Play’ block:

WeDo 2.0 - change color

After capturing a few packets I selected just the interesting part:

1    0.000000000            LE LL    33    Empty PDU
2    0.037564000            ATT    44    UnknownDirection Write Command, Handle: 0x003d
3    0.074999800            LE LL    33    Empty PDU
4    0.187500200            LE LL    33    Empty PDU
5    0.225000800            LE LL    33    Empty PDU
6    0.262503000            LE LL    33    Empty PDU
7    0.262732400            LE LL    33    Empty PDU
8    0.375069400            ATT    44    UnknownDirection Write Command, Handle: 0x003d
9    0.412505800            LE LL    33    Empty PDU

Most frames are empty but 2 frames (‘2’ and ‘8’) show some writing to the 0x003d handle (the same handle that is used to control the motors). Let’s inspect those frames:

Frame 2: 44 bytes on wire (352 bits), 44 bytes captured (352 bits) on interface 0
PPI version 0, 24 bytes
DLT: 147, Payload: btle (Bluetooth Low Energy Link Layer)
Bluetooth Low Energy Link Layer
Bluetooth L2CAP Protocol
Bluetooth Attribute Protocol
    Opcode: Write Command (0x52)
        0... .... = Authentication Signature: False
        .1.. .... = Command: True
        ..01 0010 = Method: Write Request (0x12)
    Handle: 0x003d
    Value: 06040101
Frame 8: 44 bytes on wire (352 bits), 44 bytes captured (352 bits) on interface 0
PPI version 0, 24 bytes
DLT: 147, Payload: btle (Bluetooth Low Energy Link Layer)
Bluetooth Low Energy Link Layer
Bluetooth L2CAP Protocol
Bluetooth Attribute Protocol
    Opcode: Write Command (0x52)
        0... .... = Authentication Signature: False
        .1.. .... = Command: True
        ..01 0010 = Method: Write Request (0x12)
    Handle: 0x003d
    Value: 06040103

So the App is writing “06040101” to the handle and then “06040103”.

I already know something about this handler from the motor control examples I found before:

  • first byte is the “port” identifier (’01’ and ’02’ are the physical plugs to connect motors or sensors so ’06’ is the LED port)
  • second byte is the command sent to the port so ’04’ means “change color”
  • third byte is the length of the arguments of the command so ’01’ means that the color is just one byte length – the fourth

So ’01’ and ’03’ must be some colors right? And I already know that ‘1’ in the App means ‘red’ so probably “06040101” means “change color to pink” and “06040103” means “change color to blue” (blue is the color of the WeDo Hub while waiting for commands).

So let’s test it with gatttool:

char-write-cmd 3d 06040101

Yes! It turns red indeed!

After testing other values I got these 11 values:

00 off
01 pink
02 purple
03 blue
04 cyan
05 light green
06 green
07 yellow
08 orange
09 red
0A light blue

(the real color labels may differ as I’m not very good with colors)

Now a small python script to cycle through all those colors:

#!/usr/bin/python

from gattlib import GATTRequester
from time import sleep

colors = ['\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0A']

req = GATTRequester("A0:E6:F8:1E:58:57",True,"hci0")

while True:
  for color in colors:
    req.write_by_handle(0x3d, "\x06\x04\x01" + color)
    sleep(2)

And, of course, the video: