LEGO Dimensions + EV3: reading tags

I started fiddling with LEGO Dimensions Toy Pad and the Mindstorms EV3. Later I’ll post better and more organized information but for now here is the script I used with the following video – it shows how to read the NGC tags and change the RGB color of each pad.

All my code is based on woodenphone work.

#!/usr/bin/python

import usb.core
import usb.util
from time import sleep

import lego_dimensions_gateway

# UIDs can be retrieved with Android App (probably in hexadecimal)
uidDarthVader = (4, 161, 158, 210, 227, 64 , 128)

pad1_red = [0x55, 0x0e, 0xc8, 0x06, 0x01, 0xff, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # left:off center:red right:off

pad1_green = [0x55, 0x0e, 0xc8, 0x06, 0x01, 0x00, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # left:off center:green

pad2_red = [0x55, 0x0e, 0xc8, 0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # left:red center:off right:off

pad2_green = [0x55, 0x0e, 0xc8, 0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # left:green center:off right:off

pad3_red = [0x55, 0x0e, 0xc8, 0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # left:off center:off right:red

pad3_green = [0x55, 0x0e, 0xc8, 0x06, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # left:off center:off right:green

pads_off = [0x55, 0x06, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x00, 29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] # Switch all pads off


def init_usb():
    global dev
    # find our device
    dev = usb.core.find(idVendor=0x0e6f)# 0x0e6f Logic3 (made lego dimensions portal hardware)

    # was it found?
    if dev is None:
        raise ValueError('Device not found')

    reattach = False
    if dev.is_kernel_driver_active(0):
        reattach = True
        dev.detach_kernel_driver(0)

    # set the active configuration. With no arguments, the first
    # configuration will be the active one
    dev.set_configuration()

    # Initialise portal
    dev.write(1, [0x55, 0x0f, 0xb0, 0x01, 0x28, 0x63, 0x29, 0x20, 0x4c, 0x45, 0x47, 0x4f, 0x20, 0x32, 0x30, 0x31, 0x34, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])# Startup
    return dev

def uid_compare(uid1, uid2):
  match = True
  for i in range(0,7):
    if (uid1[i] != uid2[i]) :
       match = False
  return match 

def demo():
    while True:
        try:
            inwards_packet = dev.read(0x81, 32, timeout = 10)
            bytelist = list(inwards_packet)

            if not bytelist:# We need a packet
                pass
            elif bytelist[0] != 0x56: # Only listen to NFC packets
                pass
            else:
                pad_num = bytelist[2]
                uid_bytes = bytelist[6:13]
                match = uid_compare(uid_bytes, uidDarthVader)
                action = bytelist[5]
                if action == 0 : #IN
                  if match:
                  # Darth Vader IN
                      if pad_num ==1 :
                          command = pad1_red
                      elif pad_num == 2 :
                          command = pad2_red
                      elif pad_num == 3 :
                          command = pad3_red
                      else :
                          pass
                  else:
                  # some other tag IN
                      if pad_num ==1 :
                          command = pad1_green
                      elif pad_num == 2 :
                          command = pad2_green
                      elif pad_num == 3 :
                          command = pad3_green
                      else :
                          pass                      
                  dev.write(1, command)
                elif action == 1 : # OUT
                  dev.write(1, pads_off)

        except usb.USBError, err:
            pass

def main():
    init_usb()
    demo()
    return

if __name__ == '__main__':
    main()

ev3dev – using IRLink with python

I got myself a HiTechnic IRLink sensor.

As of today (August 2016) ev3dev already recognizes the IRLink as a nxt-i2c sensor but there’s no language support for it. David Lechner suggested me using the “direct” attribute to communicate directly with the IRLink at I2C level.

Last time I wrote something mildly related to I2C was about 20 years ago for a Microchip PIC project but well… why not?

So after lots of trial and error, reading several times the LEGO Power Functions RC Protocol and shamelessly copying code from Mike Hatton (“Parax”), Xander Soldaat and Lawrie Griffiths I found on GitHub, RobotC forum and LeJOS forum I fanally managed to control a PF motor in ComboPWM mode.

In the following video, I’m increasing the motor speed (all 7 steps) then decreasing it again until it stops:

This is the python script running in the EV3:

#!/usr/bin/python

#
# based mainly on RobotC code from Mike Hatton ("Parax") and Xander Soldaat
# but also on LeJOS code from Lawrie Griffiths
#

# assumes IRLink at Input 1 as sensor0

import sys
from time import sleep

# channel: 0..3
# motorA, motorB: 0..7

channel = 0
for motorA in (1,1,2,2,3,3,4,4,5,5,6,6,7,7,6,6,5,5,4,4,3,3,2,2,1,1,0,0):

  motorB = motorA

  iBufferSize=2
  iBuffer = bytearray(iBufferSize)

  iBuffer[0] = ((0x04 | channel) << 4) | motorB
  iBuffer[1] = motorA << 4
  check = 0xF ^ (0x04 | channel) ^ motorB ^ motorA
  iBuffer[1] = iBuffer[1] | check

  oBufferSize=14
  oBuffer = bytearray(oBufferSize)

  # clear all positions
  for i in range (0,oBufferSize):
    oBuffer[i]=0

  oBuffer[0]=0x80    # Start Bit

  oBufferIdx = 0

  for iBufferByte in range (0,2):
    for iBufferIdx in range (0,8):
      oBuffer[1 + (oBufferIdx / 8)] |= (0x80 >> (oBufferIdx % 8) )
      if ( ( ( iBuffer[iBufferByte] ) & (0x80 >> (iBufferIdx % 8) ) ) != 0 ) :
        oBufferIdx = oBufferIdx + 6
      else:
        oBufferIdx = oBufferIdx + 3

# Stop bit
  oBuffer[1+ (oBufferIdx / 8)] |= (0x80 >> (oBufferIdx % 8) )

  tailIdx = 1 + (oBufferIdx / 8) + 1

  # Tail


  if (tailIdx == 10):
    oBuffer[tailIdx]= 0x10 # IRLink message payload length
    register = 0x43
  else:
    oBuffer[tailIdx]= 0x11
    register = 0x42

  oBuffer[tailIdx+1]= 0x02 # IRLink in Power Functions Mode
  oBuffer[tailIdx+2]= 0x01 # IRLInk Start transmission 


# clear IRLink (not sure if correct but seems to improve)

  fd = open("/sys/class/lego-sensor/sensor0/direct", 'wb',0)
  fd.seek(0x41)
  fd.write(chr(0x46))
  fd.write(chr(0x44))
  fd.write(chr(0x4C))
  fd.write(chr(0x50))
  fd.close()
  sleep(0.1)

  for i in range(0,5):
    fd = open("/sys/class/lego-sensor/sensor0/direct", 'wb',0)
    fd.seek(register)
    for oBufferIdx in range (0,oBufferSize):
      fd.write(chr(oBuffer[oBufferIdx]))
    fd.close()

    # Power Functions timings (for a 5-command burst)
    if (i==1):
      sleep(0.064)
    elif (i==5):
      sleep(0.096)
    else:
      sleep(0.080)

 

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

WeDo 2.0 Tilt Sensor

This post is part 6 of 6 of  WeDo 2.0 - reverse engineering

 

 

Thanks to Google, I already knew how to set the Tilt Sensor to «mode 1» and then read it. Unfortunately I was not able to understand the data format used by the sensor until I read the recently released SDK.

So the Tilt Sensor has 3 operating modes:

  • Angle (the default mode) = 0
  • Tilt = 1
  • Crash = 2

In this post I’ll explain how to read just the Tilt Mode as is similar to the original WeDo Tilt Sensor operating mode (and also because I still have to organize all my SDK notes).

The WeDo 2.0 ports can also be configured to read analog values in 3 formats: RAW (0), PERCENTAGE (1) and SI (2). Again, I will use just the SI format.

To change the operating mode to Tilt Mode we write this command to the Input Command characteristic (0x003a):

0102012201010000000201

The meaning is similar to the command used in last post for setting the RGB LED to Absolute Mode, the only difference is the Device Type (22h is the Tilt Sensor).

In Tilt Mode and SI Format there are 6 possible values we can read from the Tilt Sensor:

TILT_SENSOR_DIRECTION_NEUTRAL = 0
TILT_SENSOR_DIRECTION_BACKWARD = 3
TILT_SENSOR_DIRECTION_RIGHT = 5
TILT_SENSOR_DIRECTION_LEFT = 7
TILT_SENSOR_DIRECTION_FORWARD = 9
TILT_SENSOR_DIRECTION_UNKNOWN = 10

These values are read from the SensorValue characteristic (0x0032) in a 6-byte format where the first 2 bytes are always “0201” and the other 4 bytes are a IEEE754 float format representation of the value – and now I finally know how to understand those values!

So with just a crude python script we can use the WeDo 2.0 Hub with a Tilt Sensor to control a Mindstorms EV3 car:

#!/usr/bin/env python

# run with sudo

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"

SensorValue_hnd  = 0x32
InputCommand_hnd = 0x3a

# assume TiltSensor at Port #1, change to Mode 1 (Tilt) and Unit = SI
# TiltCmdMode1= "0102012201010000000201"
TiltCmdMode1 = str(bytearray([01,02,01,22,01,01,00,00,00,02,01]))

# Sensor value should read:
# "0201" + a 4 byte value (LITTLE_ENDIAN)
# representing a float in IEEE754 format
# http://www.h-schmidt.net/FloatConverter/IEEE754.html

TILT_SENSOR_DIRECTION_NEUTRAL  = "0000" # 0
TILT_SENSOR_DIRECTION_BACKWARD = "4040" # 3
TILT_SENSOR_DIRECTION_RIGHT    = "a040" # 5
TILT_SENSOR_DIRECTION_LEFT     = "e040" # 7
TILT_SENSOR_DIRECTION_FORWARD  = "1041" # 9
TILT_SENSOR_DIRECTION_UNKNOWN  = "2041" # 10

DELAY      = 0.3
STEP       = 0.175
DT         = 75

tilt_data=[0,0,0,0,0,0]

m1 = LargeMotor(OUTPUT_A)
m2 = LargeMotor(OUTPUT_B)

m1.run_direct()
m2.run_direct()
m1.duty_cycle_sp=0
m2.duty_cycle_sp=0

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


# try reset - none works
# req.write_by_handle(InputCommand_hnd,"\x22\x44\x11\xAA")
# req.write_by_handle(InputCommand_hnd,"\x44\x11\xAA")
# sleep(DELAY)

# Configure Tilt Sensor
req.write_by_handle(InputCommand_hnd,TiltCmdMode1)
sleep(DELAY)

while True:
  # read Sensor
  data=req.read_by_handle(SensorValue_hnd)[0]

  if data:
    for i,b in enumerate(data):
      tilt_data[i]=ord(b)

    if (tilt_data[4] == 0x00) and (tilt_data[5] == 0x00):
      print("NEUTRAL")
    elif (tilt_data[4] == 0x40) and (tilt_data[5] == 0x40):
      print("BACKWARD")
      m1.duty_cycle_sp=-DT
      m2.duty_cycle_sp=-DT
      sleep(STEP)
      m1.duty_cycle_sp=0
      m2.duty_cycle_sp=0
    elif (tilt_data[4] == 0xa0) and (tilt_data[5] == 0x40):
      print("RIGHT")
      m1.duty_cycle_sp=-DT
      m2.duty_cycle_sp=DT
      sleep(STEP)
      m1.duty_cycle_sp=0
      m2.duty_cycle_sp=0
    elif (tilt_data[4] == 0xe0) and (tilt_data[5] == 0x40):
      print("LEFT")
      m1.duty_cycle_sp=DT
      m2.duty_cycle_sp=-DT
      sleep(STEP)
      m1.duty_cycle_sp=0
      m2.duty_cycle_sp=0
    elif (tilt_data[4] == 0x10) and (tilt_data[5] == 0x41):
      print("FORWARD")
      m1.duty_cycle_sp=DT
      m2.duty_cycle_sp=DT
      sleep(STEP)
      m1.duty_cycle_sp=0
      m2.duty_cycle_sp=0
    elif (tilt_data[4] == 0x20) and (tilt_data[5] == 0x41):
      print("UNKNOWN")

  else:
    print("No data")

  sleep(0.1)

Just a warning note: I ran a first version of this script in my Ubuntu laptop (without the EV3 parts) and it always fails at first run (“No data”, no matter what I do with the Tilt Sensor) but if I abort the script and run it again while the Hub is still in discovering mode then it always works. But when the same script runs in the EV3 it almost always works at first try.

And of course I do have a good explanation: gremlins! What else?

WeDo 2.0 colors with python (again)

This post is part 5 of 6 of  WeDo 2.0 - reverse engineering

After some head aches with the WeDo 2.0 SDK I found out that the WeDo 2.0 Hub has 2 modes for the RGB LED device:

  • Indexed
  • Absolute

“Indexed” is the one I used before – only 10 colors are available. This the mode used in the WeDo 2.0 App, and when in this mode the command used to write to the RGB LED accepts only one byte as argument, which works as an “index” to 10 predefined colors. Why only 10 if the same byte can address up to 255 colors? Internal memory limitations?

The same command accepts  also 3 one-byte arguments (Red, Green and Blue) but only when the RGB LED mode is “Absolute”. This is clear in the SDK… what is not so clear is how to change from default (power on) Indexed mode to Absolute?

After reading many Java files I found out how: we use the “Input Command” characteristic (handle 0x3a) and send it this command:

0102061701010000000201

I’ll explain the format of the “Input Command” in another post, but for now this is the meaning:

  • first two bytes (0102) is the header used to set definitions
  • the third is the port of the device (06 is the RGB LED)
  • the fourth is the type of the device (17h a RGB LED)
  • the fifth is the mode (01 is Absolute, 00 is Indexed)
  • the sixth to nineth bytes is the delta for notifications to be noticed (so 01 00 00 00 = 1d is the minimum)
  • the tenth byte is the unit format to be used (02 is “SI”, internation standard)
  • the eleventh and last byte is to disable or enable notifications (01)

So we can now use any of the 16777216 color tones available:

The pyhton script used for the video above (the video just shows a small part)

#!/usr/bin/python
# run with sudo

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]))

DELAY      = 0.3

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

# configure RBG LED to Absolute Mode
req.write_by_handle(InputCommand_hnd,RGBAbsoluteMode_cmd)

# loop all colors
while True:
  for blue in range (0,256,16):
    for green in range (0,256,16):
      for red in range (0,256,16):
        req.write_by_handle(OutputCommand_hnd, RGBAbsoluteOutput_cmd+chr(red)+chr(green)+chr(blue))

 

LEGO WeDo 2.0 – playing sound

This post is part 4 of 6 of  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 LED Button Service

This post is part 3 of 6 of  WeDo 2.0 - reverse engineering

And we continue digging into the  WeDo 2.0 Hub primary services, now with the “Nordic LED Button Service”

WeDo 2.0 Hub BLE services

[A0:E6:F8:1E:58:57][LE]> primary
...
attr handle: 0x000c, end grp handle: 0x002f uuid: 00001523-1212-efde-1523-785feabcd123
...

So this Primary service uses handles from 0x000c to 0x002f. Let’s look deeper:

[A0:E6:F8:1E:58:57][LE]> characteristics 000c 002f
handle: 0x000d, char properties: 0x0a, char value handle: 0x000e, uuid: 00001524-1212-efde-1523-785feabcd123
handle: 0x0010, char properties: 0x12, char value handle: 0x0011, uuid: 00001526-1212-efde-1523-785feabcd123
handle: 0x0014, char properties: 0x10, char value handle: 0x0015, uuid: 00001527-1212-efde-1523-785feabcd123
handle: 0x0018, char properties: 0x12, char value handle: 0x0019, uuid: 00001528-1212-efde-1523-785feabcd123
handle: 0x001c, char properties: 0x12, char value handle: 0x001d, uuid: 00001529-1212-efde-1523-785feabcd123
handle: 0x0020, char properties: 0x12, char value handle: 0x0021, uuid: 0000152a-1212-efde-1523-785feabcd123
handle: 0x0024, char properties: 0x08, char value handle: 0x0025, uuid: 0000152b-1212-efde-1523-785feabcd123
handle: 0x0027, char properties: 0x0a, char value handle: 0x0028, uuid: 0000152c-1212-efde-1523-785feabcd123
handle: 0x002a, char properties: 0x02, char value handle: 0x002b, uuid: 0000152d-1212-efde-1523-785feabcd123
handle: 0x002d, char properties: 0x08, char value handle: 0x002e, uuid: 0000152e-1212-efde-1523-785feabcd123

[A0:E6:F8:1E:58:57][LE]> char-desc 000c 002f
handle: 0x000c, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000e, uuid: 00001524-1212-efde-1523-785feabcd123
handle: 0x000f, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x0010, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0011, uuid: 00001526-1212-efde-1523-785feabcd123
handle: 0x0012, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0013, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0015, uuid: 00001527-1212-efde-1523-785feabcd123
handle: 0x0016, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x0018, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0019, uuid: 00001528-1212-efde-1523-785feabcd123
handle: 0x001a, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x001b, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x001c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001d, uuid: 00001529-1212-efde-1523-785feabcd123
handle: 0x001e, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x001f, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x0020, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0021, uuid: 0000152a-1212-efde-1523-785feabcd123
handle: 0x0022, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0023, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x0024, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0025, uuid: 0000152b-1212-efde-1523-785feabcd123
handle: 0x0026, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x0027, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0028, uuid: 0000152c-1212-efde-1523-785feabcd123
handle: 0x0029, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x002a, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002b, uuid: 0000152d-1212-efde-1523-785feabcd123
handle: 0x002c, uuid: 00002901-0000-1000-8000-00805f9b34fb
handle: 0x002d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002e, uuid: 0000152e-1212-efde-1523-785feabcd123
handle: 0x002f, uuid: 00002901-0000-1000-8000-00805f9b34fb

Lot of things here: 10 characteristics and 36 handles!

The first handle (0x000c) just marks the begin of the service (UUID 2800).

Then we see that there are 10 handles with UUID 2803 (Characteristic Declaration Attribute) and also 10 handles with UUID 2901 (Characteristic User Description) – these handles mark the begin of each characteristic and also defines its properties, so let’s read them all  (char-read-hnd) and translate it:

Name Char               0x000e  read+write
Button Char             0x0011  notify+read
Port Type Char          0x0015  notify
Low Voltage alert       0x0019  notify+read
High Current alert      0x001d  notify+read
Low Signal alert        0x0021  notify+read
Turn off device         0x0025  write
Vcc port control        0x0028  read+write
Battery type Indicator  0x002b  read
Disconnect Char         0x002e  write

Some characteristics also have an handle with UUID 2902 (Client Characteristic Configuration). That’s used for Notifications – the 5 characteristics that support this feature use this extra handle to activate or deactivate it.

So let’s read the 7 handles that are readable:

Name Char               75 30 30 30 31 = u0001
Button Char             00
Low Voltage alert       00
High Current alert      00
Low Signal alert        00
Vcc port control        01
Battery type Indicator  00

So ‘Name char’ contains the friendly name of my Hub. And it is writable, so what happens if I change it?

[A0:E6:F8:1E:58:57][LE]> char-write-cmd 0x000e 4d616a6f72
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0x00e
Characteristic value/descriptor: 4d 61 6a 6f 72

Well it retains what I wrote.

And if I now read the ‘Device Name’ it is no longer ‘u0001’:

[A0:E6:F8:1E:58:57][LE]> char-read-uuid 0x2A00
handle: 0x0003      value: 4d 61 6a 6f 72

So now my Hub announces a new name:

$ sudo hcitool -i hci0 lescan
LE Scan ...
A0:E6:F8:1E:58:57 (unknown)
A0:E6:F8:1E:58:57 Major

Changing 'Device Name' attribute

Cool, isn’t it?

Now the ‘Button Char’ just reads zero. But if I press the Hub’s button while I read it:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0011
Characteristic value/descriptor: 01

it reads ‘1’.

And what about ‘Port Type Char’? It’s not defined as readable (just ‘notify’) but I can try to read it anyway:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0015
Characteristic value/descriptor:

Hmm… null value.

But I have nothing connected to the Hub. Let’s try it again with a motor at port 1:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0015
Characteristic value/descriptor: 01 01 00 01 01 00 00 00 01 00 00 00

And removing the motor again:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0015
Characteristic value/descriptor: 01 00

Interesting – it’s not null now.

Let’s do the same again but on port 2:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0015
Characteristic value/descriptor: 02 01 01 01 01 00 00 00 01 00 00 00

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0015
Characteristic value/descriptor: 02 00

So the first byte seems to identify the Port (’01’ or ’02’) and the second byte the action (’01’ might be ‘device plugged’ and ’00’ might be ‘device removed’). I have no explanation for the third byte and the other 8 bytes bytes seem to be some kind of identification of the motor.

Now let’s connect a tilt sensor in port 1 (and nothing in port 2):

01 01 00 22 00 00 00 10 00 00 00 10

and then move it to port 2:

02 01 01 22 00 00 00 10 00 00 00 10

Now just a distance sensor in port 1:

01 01 00 23 00 00 00 10 00 00 00 10

and move it to port 2:

02 01 01 23 00 00 00 10 00 00 00 10

Now just the motor again in port 1 and then the tilt sensor in port 2 (two readings):

01 01 00 01 01 00 00 00 01 00 00 00
02 01 01 22 00 00 00 10 00 00 00 10

So ‘Port Type Char’ doesn’t describe the ports state, it just shows the last action detected at ports. I think that’s the reason it’s not defined as ‘notify + read’, it’s purpose is just to notify that a change ocurred on ports.

Now we have 3 characteristics named “Alert”: Low Voltage Alert, High Current Alert and Low Signal Alert. Their names seem clear enough to understand their purpose and since all read zero everything seems to be OK. But let’s try to force an “High Current Alert” – make the motor run, block it and read the handle:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 00 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 00 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 00 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 00 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 01 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 01 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 01 
[A0:E6:F8:1E:58:57][LE]> char-read-hnd 001d
Characteristic value/descriptor: 00

When I block the motor with my hand, the Hub beeps and flashes the LED twice (in yellow). And sometimes the Alert reads ’01’.

But instead of reading several times the handler, we can activate ‘Notifications’ by turning ON the ‘notification’ flag in the ‘Client Characteristic Configuration’ and then just wait for the Hub to send the notification when the High Current Alert changes it’s state:

[A0:E6:F8:1E:58:57][LE]> char-write-req 001e 0100 -listen
Characteristic value was written successfully
[A0:E6:F8:1E:58:57][LE]> char-write-cmd 003d 01010164
Notification handle = 0x001d value: 01 
Notification handle = 0x001d value: 00

We used a different write command: ‘char-write-req’ instead of ‘char-write-cmd’ expects an answer from the device and ‘-listen’ keeps listening for future ‘answers’.

Now the ‘Vcc port control’ name isn’t much clear about it’s purpose. It reads ‘1’ but it’s also writable… does it allows us to control power in the ports? No matter what I write to 0x003d it seems to retain only ‘0’ or ‘1’ and when I write ‘0’ to it while the motor is running it keeps running.

‘Battery type indicator’ reads zero, I guess that it identifies the kind of battery being used. I’m using 2 AA alcaline batteries, will try again with the LiPo battery when I get it.

And finally the 2 writable-only characteristics: ‘Disconnect Char’ and ‘Turn off device’. They also seem quite clear about their purpose:

[A0:E6:F8:1E:58:57][LE]> char-write-cmd 002e 01
[A0:E6:F8:1E:58:57][LE]> 
(gatttool:8257): GLib-WARNING **: Invalid file descriptor.

[A0:E6:F8:1E:58:57][LE]> connect
Attempting to connect to A0:E6:F8:1E:58:57
Connection successful

Writing to ‘Disconnect Char’ makes the Hub disconnects but keeps it in discovery mode so I cpuld reconnect to it.

[A0:E6:F8:1E:58:57][LE]> char-write-cmd 0025 01
[A0:E6:F8:1E:58:57][LE]> 
(gatttool:8257): GLib-WARNING **: Invalid file descriptor.

[A0:E6:F8:1E:58:57][LE]> connect
Attempting to connect to A0:E6:F8:1E:58:57
[A0:E6:F8:1E:58:57][LE]> 
Error: connect error: Connection refused (111)

Writing to ‘Turn off device’ really turns it off.

So that’s it for ‘Nordic LED button service’. What LED, you ask? Well, the Android App is from Nordic and they have some examples on the Net with this UUID so my explanation is that LEGO used part of their code for the ‘button’ part without changing the UUID so the App recognizes it this way.

Next post: Motors and LED.

WeDo 2.0 Battery Service

This post is part 2 of 6 of  WeDo 2.0 - reverse engineering

In first post we have seen that the WeDo 2.0 Hub offers a “Battery Service” Primary Service:

[A0:E6:F8:1E:58:57][LE]> primary
...
attr handle: 0x0046, end grp handle: 0xffff uuid: 0000180f-0000-1000-8000-00805f9b34fb
...

‘180F’ is defined in the Bluetooth Standard as a ‘Battery Service‘. LEGO assigned all handles from 0x0046 to 0xFFFF to this service (not quite true: since there is no other Primary Service, gatttool assumes 0xFFFF as the last handle).

What handles and characteristics?

[A0:E6:F8:1E:58:57][LE]> characteristics 0x0046 0xffff
handle: 0x0047, char properties: 0x12, char value handle: 0x0048, uuid: 00002a19-0000-1000-8000-00805f9b34fb
[A0:E6:F8:1E:58:57][LE]> char-desc 0x0046 0xffff
handle: 0x0046, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0047, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0048, uuid: 00002a19-0000-1000-8000-00805f9b34fb
handle: 0x0049, uuid: 00002902-0000-1000-8000-00805f9b34fb

So there is  just one useful characterist, the one with UUID ‘00002a19-0000-1000-8000-00805f9b34fb’ or just ‘2a19’ assigned to handle 0x0048.

There are 3 other characteristics (‘2800’, ‘2803’ and ‘2902’) each one assigned to one handle (‘0x0046’, ‘0x0048’ and ‘0x49’). What’s their purpose?

The first characteristic (‘2800’) contains the UUID of the Primary Service (we already know that it is ‘180F’):

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0x046
Characteristic value/descriptor: 0f 18

The second characteristic (‘2803’) describes the Service – something we already know from the ‘characteristics’ command:

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0x047
Characteristic value/descriptor: 12 48 00 19 2a

The first byte is the ‘characteristic properties’. Each bit has a meaning:

Broadcast: 01h
Read: 02h
Write without response: 04h
Write: 08h
Notify:10h
Indicate: 20h
Authenticated Signed Writes:40h
Extended Properties: 80h

So ’12h’ means ‘notify’ + ‘read’ so we can read and the battery level and we can also ask for notifications.

The second and third bytes specify the handle associated to the battery level: ‘0048h’.

The fourth and fifth bytes specify the characteristic UUID of the battery level: ‘2a19’.

Now the last characteristic (‘2902’) is used for notification and indication control.

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0x049
Characteristic value/descriptor: 00 00

Since it’s value is ‘0’ it means ‘Notifications and indications disabled’

Now let’s read the battery level, at the third characteristic (UUID: ‘2a19’/ Handle: 0x0048)

[A0:E6:F8:1E:58:57][LE]> char-read-hnd 0x048
Characteristic value/descriptor: 64

64h = 100d so battery level is 100%

We can also use this other command:

[A0:E6:F8:1E:58:57][LE]> char-read-uuid 2a19
handle: 0x0048      value: 64

Now let’s check with Android nRF Connect App:

WeDo 2.0 Battery Service

Seems ok once again.

Until now we just accessed information defined in the Bluetooth standard. Maybe in the future the Bluetooth SIG decide to include motors, lights and other devices in the standard but for now each vendor (like LEGO) decides how to communicate with its gadgets and if it doesn’t release that information to the public it’s almost impossible to develop third-party applications for it.

LEGO promised to release a SDK in the first half of 2016. That’s a good decision, everyone (including LEGO) will benefit if everyone can create it’s own application and even integrate it with other tools or systems. But we are already in the second half of 2016 and no SDK…

Luckily there are already a few pieces of information scattered around the Web…

Next post: the WeDo 2.0 Hub button.

WeDo 2.0 – reverse engineering

This post is part 1 of 6 of  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: