R4T1NH0 – a LEGO micro rover

At home with just the kids, used the SBrick for a RC micro rover:

It’s very fragile but I’m certain it is possible to make it even smaller and sturdy, just a matter of time and skill.



[actualized on the next day]

A slightly better version witj less friction at the connection between the weels and the micro-motors. Also tried 3 CR2430 lithium batteries to supply 9 Volt. The batteries behave better than expected.


Almost out of nowhere I now have a table robot with great potential for Snap!

[new actualization]

Robot consumption with 3 fresh CR2430 batteries goes from 60 mA (moving forward) to 90 mA (rotating over itself).

SBrick beta testing

At SBrick (or SmartBrick) Kickstarter campaign I pledged for beta tester. My beta SBrick arrived at last from Hungary:

As it’s still an early version it has some issues (one of  the four channels seems  to be damaged and LEGO Power Function cables don’t fit tight to another channel’s plug) but it’s good enough for testing connectivity with Linux, particularly with ev3dev – my main goal as beta tester is to help in connecting LEGO Mindstorms EV3 to the Sbrick.

SBrick exposes 6 Bluetooth 4.0 Low Energy «services» (I am not familiar with the BLE jargon yet): Generic Access, Device Information and another 4 specific from the vendor. The first and second services expose this information:

Device Name = SBrick
Appearance = Generic Remote Control
Model Number = 4.0
Firmware Revision = 4.1
Hardware Revision = 4.0
Software Revision = 4.1
Manufacturer Name String = Vengit Ltd.

The other 4 services are specific from Vengit and expose a total of 8 «fields»:
– 5 Read Only
– 1 Write Only
– 1 Read/Write
– 1 Unknown

Meanwhile I got from Vengit the minimal information needed for controlling a motor connected to one of the 4 channels: the reomote control service UUID is ‘4dc591b0-857c-41de-b5f1-15abda665b0c’ and the remote control characteristic is ‘2b8cbcc-0e25-4bda-8790-a15f53e6010f’.

For practical uses, using the ‘gatttool’ command from BlueZ 5 (the Linux Bluetooth stack), the above information translates to writing to handle 0x0025 the commands supported by the SBrick firmware.

I only know two of those commands (00h = BRAKE e 01h = DRIVE)

  • BRAKE Channel
  • DRIVE Channel Direction DutyCycle

‘Channel’ is one of the 4 output ports available and can be 00h, 01h, 02h or 03h.

‘Direction’ can be clockwise (00h) or anticlockwise (01h).

‘DutyCycle’ is the power pretended for the motor, can go from 00h (none or “Coast”) to FFh (“full power”).

So to send a command from Linux (a Ubuntu PC, a Raspberry Pi with Raspbian or a Mindstorms EV3 with ev3dev) with a USB BT4.0/BLE dongle one just need to use the ‘gatttool’:

$ gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010000FF

The exemple above, sends to the Bluetooth device with address ’00:07:80:7F:28:E1′ (my SBrick) from the BT controller ‘hci0’ (my dongle) the command

DRIVE Channel#0 Clockwise 100%

As this command doesn’t keep the bluetooth connection open, the motor spins for around  3 secondss, then the connection drops and it stops (but if we use ‘gatttool’ in interactive mode with option “-I” or “–interactive” and send the equivalent commands, the motor will keep spinning).

Next video shows 2 motors spinning in opposite directions:

  • DRIVE Channel#0 Clockwise 22%
  • DRIVE Channel#2 Anticlockwise 17%

 

Next video shows an EV3 motor actiing as the reference for a Power Functionn Servo:

And a little better version of the same example, with a scale factor for matching the motor positions and a limitation of range to [-90º,+90º].

 

For that last video it was used this python script:

import traceback
from sys import exit
from time import sleep
from subprocess import call
from subprocess import check_output
from math import trunc

def reset_ev3motor():
  call ("echo 0 > /sys/class/tacho-motor/motor0/position", shell=True);
  return;

def rd_ev3motor():

  v=check_output("cat /sys/class/tacho-motor/motor0/position",shell=True);
  return(trunc(float(v)));

def sbrick_drive(channel,direction,dutycycle):
  " calls gatttool command from BlueZ, the official Linux Bluetooth protocol stack"

  if(dutycycle > 255):
    dt_hexa="0xFF";
  else:
    dt_hexa=str(hex(int(dutycycle)));

  command="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01"+channel+direction+dt_hexa[2:];
#  print(command);
  call (command, shell=True);

  return;

def main():
  try:
    pos=0.0;
    SCALE=255.0/90.0;

    reset_ev3motor();

    while(True):
      pos=rd_ev3motor()*SCALE;

      if(pos>0):
        sbrick_drive("02","00",pos);
      else:
        sbrick_drive("02","01",-pos)

      sleep(0.1);

  except (KeyboardInterrupt, SystemExit):
    print "Exiting...";
  except Exception:
    traceback.print_exc(file=sys.stdout);

  exit(0);

if __name__ == "__main__":
  main()

I use the ‘call’ function from library ‘subprocess’ to get to ‘gatttool’. It’s an ugly trick but it works, I did’t find a python bluetooth BLE library yet.

SBrick beta testing

Na campanha Kickstarter do SBrick (ou SmartBrick) inscrevi-me como beta tester. O meu SBrick chegou finalmente esta semana, vindo da Hungria numa encomenda verdadeiramente espartana, sem qualquer folheto ou instruções:

É ainda uma versão muito tosca e com alguns defeitos (um dos quatro canais parece estar avariado e a ficha de outro não encaixa com firmeza nos cabos Power Functions) mas já permite ensaiar a conectividade com o Linux e em especial com o ev3dev – o meu propósito como beta tester é sobretudo ajudar na ligação entre o Mindstorms EV3 e o Sbrick.

O SBrick expõe 6 serviços Bluetooth BT4.0/BLE: Generic Access, Device Information e mais 4 específicos do fabricante. Os 2 primeiros expõem esta informação:

Device Name = SBrick
Appearance = Generic Remote Control
Model Number  = 4.0
Firmware Revision = 4.1
Hardware Revision = 4.0
Software Revision = 4.1
Manufacturer Name String = Vengit Ltd.

Os outros 4 serviços são específicos da Vengit e expõem ao todo 8 campos:
– 5 Read Only
– 1 Write Only
– 1 Read/Write
– 1 Unknown

Entretanto recebi de um dos engenheiros da Vengit a informação mínima mas suficiente para controlar um motor ligado a um dos 4 canais: o UUID do serviço de controlo remoto é ‘4dc591b0-857c-41de-b5f1-15abda665b0c’, sendo a caracteristica do controlo remoto ‘2b8cbcc-0e25-4bda-8790-a15f53e6010f’.

Isso em termos práticos, utilizando o comando gatttool que vem com o BlueZ 5 (o stack de Bluetooth para Linux) corresponde a escrever no handle 0x0025 os comandos reconhecidos pelo firmware.

Até agora apenas me foram apresentados dois comandos (00h = BRAKE e 01h = DRIVE)

  • BRAKE Channel
  • DRIVE Channel Direction DutyCycle

Channel corresponde a uma das 4 portas disponíveis, podendo ser 00h, 01h, 02h ou 03h.

Direction corresponde ao sentido da rotação, podendo ser clokwise (00h) ou anticlockwise (01h).

DutyCycle corresponde à potência transmitida ao motor, variando entre 00h (“Coast”) e FFh (full power).

Assim para enviar um comando a partir de um sistema Linux (PC com Ubuntu, Raspberry Pi com Raspbian ou Mindstorms EV3 com ev3dev) equipado com dongle USB BT4.0/BLE basta invocar o comando gatttool:

$ gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010000FF

No exemplo acima é enviado para o dispositivo BT com identificativo 00:07:80:7F:28:E1 (o meu SBrick) a partir do device hci0 (o meu dongle BT4.0/BLE) o comando

DRIVE Channel#0 Clockwise 100%

Como este comando não mantém a sessão bluetooth aberta, ao fim de cerca de 3 segundos ela fecha e os canais deixam de comandar os motores (se invocarmos o comando gatttool em modo interactivo com a opção “-I” ou “–interactive” e dermos a partir daí os comandos equivalentes a sessão persiste e os motores continuam activos indefinidamente).

No video abaixo são mostrados dois motores a rodar em sentidos diferentes:

  • DRIVE Channel#0 Clockwise 22%
  • DRIVE Channel#2 Anticlockwise 17%

 

No próximo video é mostrado um motor EV3 a servir de referência a um motor Servo Power Functions:

E finalmente uma versão um pouco mais refinada do mesmo exemplo com introdução de um factor de escala de modo a que os movimentos sejam equiparáveis e ainda uma limitação ao intervalo [-90º,+90º]

 

Para este último video foi usado o script abaixo, em python:

import traceback
from sys import exit
from time import sleep
from subprocess import call
from subprocess import check_output
from math import trunc

def reset_ev3motor():
  call ("echo 0 > /sys/class/tacho-motor/motor0/position", shell=True);
  return;

def rd_ev3motor():

  v=check_output("cat /sys/class/tacho-motor/motor0/position",shell=True);
  return(trunc(float(v)));

def sbrick_drive(channel,direction,dutycycle):
  " calls gatttool command from BlueZ, the official Linux Bluetooth protocol stack"

  if(dutycycle > 255):
    dt_hexa="0xFF";
  else:
    dt_hexa=str(hex(int(dutycycle)));

  command="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01"+channel+direction+dt_hexa[2:];
#  print(command);
  call (command, shell=True);

  return;

def main():
  try:
    pos=0.0;
    SCALE=255.0/90.0;

    reset_ev3motor();

    while(True):
      pos=rd_ev3motor()*SCALE;

      if(pos>0):
        sbrick_drive("02","00",pos);
      else:
        sbrick_drive("02","01",-pos)

      sleep(0.1);

  except (KeyboardInterrupt, SystemExit):
    print "Exiting...";
  except Exception:
    traceback.print_exc(file=sys.stdout);

  exit(0);

if __name__ == "__main__":
  main()

Recorro à função ‘call’ da library ‘subprocess’ para invocar o comando gatttool. É feio mas funciona, ainda não tive tempo de procurar uma library específica para bluetooth BLE.

SBrick – controlo remoto com um gamepad

Apresento o meu script em python para controlar o SBrick com um gamepad a partir do Linux. Recorro à biblioteca PyGame para ler o gamepad (assumindo que o gamepad é suportado nativamente pelo Linux, ver também o meu artigo sobre como utiizar um gamepad com ev3dev) e ao comando gatttool do BlueZ 5.0 para comunicar via Bluetooth BLE com o SBrick (assumindo também a presença de um dongle Bluetooth 4.0).

 

Este script funciona bem com Ubuntu mas deverá também funcionar em qualquer variante de Debian incluindo Raspbian (no Raspberry Pi) e ev3dev (no LEGO Mindstorms EV3, onde utilizei uma versão inicial deste script).

#!/usr/bin/env python

# sudo apt-get install python-pygame

import sys, traceback, os
os.environ['SDL_VIDEODRIVER'] = 'dummy'
from math import log10
from subprocess import call
from time import sleep
from pygame import joystick, event, display

### buttons ###
B_TRIANG = 0
B_CIRC = 1
B_CROSS = 2
B_SQUARE = 3
B_LTRIG2 = 4
B_RTRIG2 = 5
B_LTRIG = 6
B_RTRIG = 7
B_SELECT = 8
B_LJOY = 10
B_RJOY = 11
B_START = 9


def main():
  try:
    display.init();
    joystick.init();
    js=joystick.Joystick(0);
    js.init();

    DRIVE_A="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0102"
    DRIVE_B="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0103"
    COAST_A="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01020000"
    COAST_B="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01030000"
    BREAK_A="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0002"
    BREAK_B="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0003"

    ### starts in Joystick mode ###
    control_by_JOYSTICK=True;

    num_axes=js.get_numaxes();
    num_buttons=js.get_numbuttons();
    num_hats=js.get_numhats();

    ### assuming 4 axes, 13 buttons and 1 hat

    flag=False;

    while True:
      x=y=motor_r=motor_l=0.0;
      event.pump();

      button_mode=js.get_button(B_SELECT);
      button_shot=js.get_button(B_SQUARE);

      if button_mode ==1:

        if control_by_JOYSTICK==True:
          control_by_JOYSTICK=False;
          print 'Control Mode=HAT';
        else:
          control_by_JOYSTICK=True;
          print 'Control Mode=JOYSTICK';


      ### joysticks axis [-1, +1]
      ### x=axis2 , y=-axis3
      ### ignore less than 0.2 (dead zone)
      ### apply log10(100x) (to reforce lower values)
      ### result is less or equal than 2 = log10(100)

      if control_by_JOYSTICK==True:

            # Control by Right Joystick, Axis 2 e 3

            axis2=js.get_axis(2);
            axis3=js.get_axis(3);

        if axis2>0:
          if axis2<0.2:
            x=0;
          else:
            x=log10(axis2*100);
        elif axis2<0:
          if axis2>-0.2:
            x=0;
          else:
            x=-log10(-axis2*100);
            else:
              x=0;

        if axis3>0:
          if axis3<0.2:
            y=0;
          else:
            y=-log10(axis3*100);
        elif axis3<0:
          if axis3>-0.2:
            y=0;
          else:
            y=log10(-axis3*100);
        else:
          y=0;

        if y<>0:
          if x<0:
            motor_r=100*y;
            # turn left => slow motor_l
               motor_l=y*(100+25*x);
          else:
            motor_el=100*y;
            # turn right => slow motor_r
            motor_r=y*(100-25*x);
        elif x<>0:
           # y=0, just turn
           motor_l=100*x;
           motor_r=-motor_l;

      else:

         # Control by HAT keys

         hat=js.get_hat(0);

         if hat==(0,1):
#            print 'FRONT';
            motor_r=100;
            motor_l=100;
          elif hat==(1,0):
#            print 'RIGHT';
            motor_l=100;
            motor_r=-100;
         elif hat==(0,-1):
#            print 'BACK';
            motor_r=-100;
            motor_l=-100;
         elif hat==(-1,0):
#            print 'LEFT';
            motor_l=-100;
            motor_r=100;
         elif hat==(1,1):
#            print 'FRONT+RIGHT';
            motor_l=100;
            motor_r=50;
         elif hat==(-1,1):
#            print 'FRONT+LEFT';
            motor_l=50;
            motor_r=100;
         elif hat==(-1,-1):
#            print 'BACK+LEFT';
            motor_l=-100;
            motor_r=-50;
         elif hat==(1,-1):
#            print 'BACK+RIGHT';
            motor_l=-50;
            motor_r=-100;


       # get direction and duty cycle

      if (motor_l<0):
     dir_l="00"
     duty_l=str(hex(int(-motor_l)))
      else:
         dir_l="01"
         duty_l=str(hex(int(motor_l)))

      if (motor_r<0):
     dir_r="01"
     duty_r=str(hex(int(-motor_r)))
      else:
         dir_r="00"
         duty_r=str(hex(int(motor_r)))

      # command+direction+dutycyle

      command_A=DRIVE_A+dir_r+duty_r[2:]
      command_B=DRIVE_B+dir_l+duty_l[2:]
      call(command_A, shell=True);
      call(command_B, shell=True);
      sleep(0.1)
#      call(BREAK_A,shell=True);
#      call(BREAK_B,shell=True);
      call(COAST_A,shell=True);
      call(COAST_B,shell=True);

    # end while

  except (KeyboardInterrupt, SystemExit):
      print "Exiting...";
  except Exception:
      traceback.print_exc(file=sys.stdout);

  js.quit();
  joystick.quit();
  display.quit();
  sys.exit(0);

if __name__ == "__main__":
    main()

SBrick – remote control with a wireless gamepad

Here is my python script for controlling SBrick with a gamepad from Linux. It uses pygame for reading the gamepad (as long as it’s supported by the kernel, see also my post about using a gamepad with ev3dev) and gatttool from BlueZ 5.x to talk to the SBrick (you need a BT 4.0 USB dongle)

It should work in Ubuntu and other Debian variants including Raspbian (Raspberry Pi) or ev3dev (LEGO Mindstorms EV3)

#!/usr/bin/env python

# sudo apt-get install python-pygame

import sys, traceback, os
os.environ['SDL_VIDEODRIVER'] = 'dummy'
from math import log10
from subprocess import call
from time import sleep
from pygame import joystick, event, display

### buttons ###
B_TRIANG = 0
B_CIRC = 1
B_CROSS = 2
B_SQUARE = 3
B_LTRIG2 = 4
B_RTRIG2 = 5
B_LTRIG = 6
B_RTRIG = 7
B_SELECT = 8
B_LJOY = 10
B_RJOY = 11
B_START = 9


def main():
  try:
    display.init();
    joystick.init();
    js=joystick.Joystick(0);
    js.init();

    DRIVE_A="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0102"
    DRIVE_B="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0103"
    COAST_A="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01020000"
    COAST_B="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01030000"
    BREAK_A="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0002"
    BREAK_B="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0003"

    ### starts in Joystick mode ###
    control_by_JOYSTICK=True;

    num_axes=js.get_numaxes();
    num_buttons=js.get_numbuttons();
    num_hats=js.get_numhats();

    ### assuming 4 axes, 13 buttons and 1 hat

    flag=False;

    while True:
      x=y=motor_r=motor_l=0.0;
      event.pump();

      button_mode=js.get_button(B_SELECT);
      button_shot=js.get_button(B_SQUARE);

      if button_mode ==1:

        if control_by_JOYSTICK==True:
          control_by_JOYSTICK=False;
          print 'Control Mode=HAT';
        else:
          control_by_JOYSTICK=True;
          print 'Control Mode=JOYSTICK';


      ### joysticks axis [-1, +1]
      ### x=axis2 , y=-axis3
      ### ignore less than 0.2 (dead zone)
      ### apply log10(100x) (to reforce lower values)
      ### result is less or equal than 2 = log10(100)

      if control_by_JOYSTICK==True:

            # Control by Right Joystick, Axis 2 e 3

            axis2=js.get_axis(2);
            axis3=js.get_axis(3);

        if axis2>0:
          if axis2<0.2:
            x=0;
          else:
            x=log10(axis2*100);
        elif axis2<0:
          if axis2>-0.2:
            x=0;
          else:
            x=-log10(-axis2*100);
            else:
              x=0;

        if axis3>0:
          if axis3<0.2:
            y=0;
          else:
            y=-log10(axis3*100);
        elif axis3<0:
          if axis3>-0.2:
            y=0;
          else:
            y=log10(-axis3*100);
        else:
          y=0;

        if y<>0:
          if x<0:
            motor_r=100*y;
            # turn left => slow motor_l
               motor_l=y*(100+25*x);
          else:
            motor_el=100*y;
            # turn right => slow motor_r
            motor_r=y*(100-25*x);
        elif x<>0:
           # y=0, just turn
           motor_l=100*x;
           motor_r=-motor_l;

      else:

         # Control by HAT keys

         hat=js.get_hat(0);

         if hat==(0,1):
#            print 'FRONT';
            motor_r=100;
            motor_l=100;
          elif hat==(1,0):
#            print 'RIGHT';
            motor_l=100;
            motor_r=-100;
         elif hat==(0,-1):
#            print 'BACK';
            motor_r=-100;
            motor_l=-100;
         elif hat==(-1,0):
#            print 'LEFT';
            motor_l=-100;
            motor_r=100;
         elif hat==(1,1):
#            print 'FRONT+RIGHT';
            motor_l=100;
            motor_r=50;
         elif hat==(-1,1):
#            print 'FRONT+LEFT';
            motor_l=50;
            motor_r=100;
         elif hat==(-1,-1):
#            print 'BACK+LEFT';
            motor_l=-100;
            motor_r=-50;
         elif hat==(1,-1):
#            print 'BACK+RIGHT';
            motor_l=-50;
            motor_r=-100;


       # get direction and duty cycle

      if (motor_l<0):
     dir_l="00"
     duty_l=str(hex(int(-motor_l)))
      else:
         dir_l="01"
         duty_l=str(hex(int(motor_l)))

      if (motor_r<0):
     dir_r="01"
     duty_r=str(hex(int(-motor_r)))
      else:
         dir_r="00"
         duty_r=str(hex(int(motor_r)))

      # command+direction+dutycyle

      command_A=DRIVE_A+dir_r+duty_r[2:]
      command_B=DRIVE_B+dir_l+duty_l[2:]
      call(command_A, shell=True);
      call(command_B, shell=True);
      sleep(0.1)
#      call(BREAK_A,shell=True);
#      call(BREAK_B,shell=True);
      call(COAST_A,shell=True);
      call(COAST_B,shell=True);

    # end while

  except (KeyboardInterrupt, SystemExit):
      print "Exiting...";
  except Exception:
      traceback.print_exc(file=sys.stdout);

  js.quit();
  joystick.quit();
  display.quit();
  sys.exit(0);

if __name__ == "__main__":
    main()

 

 

SBrick Remote Control

Uma incursão rápida em Python e Tkinter (uma library para aplicações GUI muito fácil de usar no modo Google-Copy-Paste) para poder comunicar com o SBrick a partir do meu laptop Ubuntu sem usar a linha de comando:

SBrick-RemoteControl

#!/usr/bin/env python 

from Tkinter import *
from time import sleep
from subprocess import call

def quit():
  global Window
  Window.destroy()

  return;

def STOP():

  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0002",shell=True)
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=0003",shell=True)
  return

def NORTH():

  time=scale.get()
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010301FF",shell=True)
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010200FF",shell=True)
  sleep(time)
  STOP()
  return

def SOUTH():

  time=scale.get()
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010300FF",shell=True)
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010201FF",shell=True)
  print("Backward")
  sleep(time)
  STOP()
  return

def EAST():

  time=scale.get()
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010301FF",shell=True)
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010201FF",shell=True)
  print("RIGHT")
  sleep(time)
  STOP()
  return

def WEST():

  time=scale.get()
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010200FF",shell=True)
  call("gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010300FF",shell=True)
  sleep(time)
  STOP()
  return


Window = Tk()
Window.title("SBrick Remote Control v0.1")

B_North = Button(text = "N", command = NORTH)
B_North.grid(row=0, column=1)

B_West = Button(text = "W", command = WEST)
B_West.grid(row=1, column=0)

B_STOP = Button(text = "STOP", command = STOP)
B_STOP.grid(row=1, column=1)

B_East = Button(text = "E", command = EAST)
B_East.grid(row=1, column=2)

B_South = Button(text = "S", command = SOUTH)
B_South.grid(row=2, column=1)

scale = Scale(Window, from_=0.125, to=2.5, digits=3, resolution=0.125, orient=HORIZONTAL, length=250, label="Time")
scale.grid(row=3,column=0,columnspan=3)

B_QUIT = Button(text = "QUIT", command = quit)
B_QUIT.grid(row=4, column=1, )

mainloop()

 

 

LEGO EV3 + TI SensorTag

Chegou hoje o SensorTag da Texas Instruments.

É um dispositivo BLE (Bluetooth 4.0 Low Energy) com vários sensores (temperatura, humidade, aceleração/orientação e pressão atmosférica, bem como 2 botões de pressão).

Com o ev3dev foi facílimo ler a temperatura ambiente, bastou consultar dois links:

Partindo de um dongle BT4.0 funcional em hci0 (ver o meu artigo anterior, LEGO B3AC0N)

Carregar no botão de discovery do SensorTag e fazer um scan BLE:

root@ev3dev:~# hcitool -i hci0 lescan
BC:6A:29:AB:83:4D SensorTag
BC:6A:29:AB:83:4D (unknown)
BC:6A:29:AB:83:4D SensorTag
BC:6A:29:AB:83:4D (unknown)
BC:6A:29:AB:83:4D SensorTag
...
^C

 

root@ev3dev:~# gatttool -b BC:6A:29:AB:83:4D -I
[BC:6A:29:AB:83:4D][LE]> connect
Attempting to connect to BC:6A:29:AB:83:4D
Connection successful
[BC:6A:29:AB:83:4D][LE]> char-read-hnd 0x25
Characteristic value/descriptor: 00 00 00 00
[BC:6A:29:AB:83:4D][LE]> char-write-cmd 0x29 01
[BC:6A:29:AB:83:4D][LE]> char-read-hnd 0x25
Characteristic value/descriptor: ae fe 74 0c
[BC:6A:29:AB:83:4D][LE]>

 

Os quatro bytes do valor medido (ae fe 74 0c) correspondem a duas temperaturas: a do objecto em frente e a ambiente, no formato

ObjLSB ObjMSB AmbLSB AmbMSB

Assim:

Temp Amb = 0c74 = 3188

O valor real é obtido dividindo por 120:

3188/128 = 24.90625 = 25ºC

 (Valor confirmado pela aplicação Android BLE SensorTag da TI)

Nota: ocasionalmente a conexão BLE falha. Nessas situações pode-se reiniciar o dongle com os comandos:

hciconfig hci0 down
hciconfig hci0 up

(em casos extremos será necessário remover fisicamente o dongle e voltar a inseri-lo ou na sua impossibilidade o velho reboot resolve tudo)

 

 

 

LEGO B3AC0N

Com os updates recentes ao kernel do ev3dev e a inclusão do módulo btusb [a meu pedido, modéstia à parte] já é possível utilizar dongles Bluetooth 4.0 com o LEGO EV3.

Enquanto não me chegam às mãos beacons e outros dispositivos BT 4.0 para testar, o único aparelho que tenho é um telemóvel Android que infelizmente apenas “consome” serviços. Bem, demos-lhe qualquer coisa para consumir: um beacon – não posso dizer iBeacon porque é trademark da Apple… mas é a mesma coisa: um «farol» que pode servir para marcar presença, orientação, medição de distâncias, fornecimento personalizado de informação…

A implementação é muito fácil, está quase tudo no post “piBeacon – DIY Beacon with a Raspberry Pi” da Adafruit.

Claro que primeiro é necessário um dongle Bluetooth 4.0 como este (qualquer um que funcione num Raspberry Pi a correr Raspbian deverá funcionar também com o EV3 a correr ev3dev).

É necessário também uma versão recente do ev3dev (baseado em Debian jessie, com kernel  3.16.1-1 ou superior, neste momento estou a usar a 3.16.1-2). Quem já tiver instalado a versão pre-release (ev3dev-jessie-2014-07-12) que à data é a mais recente disponível para download, basta

apt-get update
apt-get dist-upgrade
apt-get install linux-image-3.16.1-2

Para confirmar que temos o dongle a funcionar usamos o comando hciconfig:

root@ev3dev:~# hciconfig -a
hci1:    Type: BR/EDR  Bus: UART
    BD Address: 00:17:EC:02:91:B7  ACL MTU: 1021:4  SCO MTU: 180:4
    UP RUNNING PSCAN
    RX bytes:563 acl:0 sco:0 events:27 errors:0
    TX bytes:879 acl:0 sco:0 commands:27 errors:0
    Features: 0xff 0xff 0x2d 0xfe 0x9b 0xff 0x79 0x83
    Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
    Link policy: RSWITCH HOLD SNIFF PARK
    Link mode: SLAVE ACCEPT
    Name: 'ev3dev-0'
    Class: 0x000000
    Service Classes: Unspecified
    Device Class: Miscellaneous,
    HCI Version: 2.1 (0x4)  Revision: 0x0
    LMP Version: 2.1 (0x4)  Subversion: 0x191f
    Manufacturer: Texas Instruments Inc. (13)

hci0:    Type: BR/EDR  Bus: USB
    BD Address: 00:1A:7D:DA:71:13  ACL MTU: 310:10  SCO MTU: 64:8
    UP RUNNING PSCAN
    RX bytes:25008 acl:0 sco:0 events:1215 errors:0
    TX bytes:2486 acl:0 sco:0 commands:159 errors:0
    Features: 0xff 0xff 0x8f 0xfe 0xdb 0xff 0x5b 0x87
    Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
    Link policy: RSWITCH HOLD SNIFF PARK
    Link mode: SLAVE ACCEPT
    Name: 'ev3dev #1'
    Class: 0x000000
    Service Classes: Unspecified
    Device Class: Miscellaneous,
    HCI Version: 4.0 (0x6)  Revision: 0x22bb
    LMP Version: 4.0 (0x6)  Subversion: 0x22bb
    Manufacturer: Cambridge Silicon Radio (10)

Temos 2 dispositivos Bluetooth: hci1 (UART) que é o nativo (interno) do EV3, ainda 2.0 e que por isso de pouco serve; e hci0 (USB) que é o nosso.

Podemos ver na informação acima que hci0 já está em estado “UP RUNNING” apesar de na maioria dos artigos que encontrei referirem sempre ser necessário activá-lo

hciconfig hci0 up

Inicialmente hci0 não está a anunciar-se em modo Low Energy pelo que é necessário ativar esse modo. Além disso o artigo em que me inspirei refer ser conveniente desactivar a procura de outros dispositivos por poder interferir e apesar de achar isso estranho para este exemplo mal não faz por isso

hciconfig hci0 leadv
hciconfig hci0 noscan

Resta apenas configurar hci0 para funcionar se anunciar como beacon e fingir ser da Apple:

root@ev3dev:~# hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C8 00
< HCI Command: ogf 0x08, ocf 0x0008, plen 32
  1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F
  17 D1 AD 07 A9 61 00 00 00 00 C8 00
> HCI Event: 0x0e plen 4
  01 08 20 00

Se no nosso Android instalarmos uma aplicação como a “Bluetooth LE Scan” (e o telemóvel/tablet tiver BT 4.0 e Android 4.3 ou superior) após um SCAN encontramos o nosso Beacon:

ev3dev #1
MAC: 00:1A:7D:DA:71:13
Updated: 2014-09-12 17:46:50
RSSI: -60.0db / -60.0 dB
UUID: ....
Major:   0     Minor:  0
Tx Power: -56  Distance: 1.64m
Descriptor: NEAR

A precisão deixa um pouco a desejar: vários SCAN consecutivos deram valores entre 1m [NEAR] e 7m [FAR] quando o telemóvel estava a cerca de 1m40 do dongle.

Podemos ajustar um pouco mudando um valor de referência Tx Power enviado pelo Beacon no penúltimo byte do comando hcitool (“C8” acima) que corresponde ao valor RSSI medido em laboratória a distâncias de um metro, escrito em complemento para 2 (C8 = -56 portanto corresponde a uma medição de RSSI de -56 dB a um metro)

Seguem algures valores próximos de -56 dB:

-54 dB = CA
-55 dB = C9
-56 dB = C8
-57 dB = C7
-58 dB = C6
-59 dB = C5

Portanto se colocarmos o dispositivo Android a exactamente um metro do dongle USB, o valor RSSI medido será o nosso valor ideal de Tx Power.

Infelizmente obtive valores entre -55 e -63 dB (com o EV3 alimentado por carregador para garantir uma alimentação constante) por isso fiquei na mesma. Após algumas experiências com algumas medições a várias distâncias fiquei-me por -57 dB = C7, pareceu-me o menos flutuante.

hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C7 00

Quando tiver um Beacon a sério vou poder calibrar melhor este meu EV3 B3AC0N (e até pode ser que a flutuação seja do software, vou procurar outras aplicações semelhantes).

Falta agora o inverso: utilizar hci0 para encontrar outros Beacons. Ou talvez outros B3AC0Ns.

Nota: podemos não fingir ser Apple, substituindo 4C 00 pelo Company ID de outro fabricante (infelizmente a LEGO não aparece) como por exemplo ​FE 00 (reserved)… só que deixa de ser reconhecido como um Beacon.