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).
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:
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.
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’:
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.
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()
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()
Um controlo remoto simples em Snap! usando as 4 teclas cursoras:
A device extension que criei implementa 3 motores: A (canal #1), B (canal #2) e um pseudo-motor P (ambos os canais). Para movimentar o rover para trás e para a frente é usado o motor P, para rodá-lo para a esquerda ou para a direita são usados os motores A e B com direcções opostas.
Depois de uma décima de segunda de rotação dos motores é dado um comando de rotação aos motores com duty cycle de zero (COAST) mas também podia ser usado o comando stop (BREAK).
A linguagem Snap! (antes designada BYOB) é uma linguagem de programação visual baseada na experiência “drag-and-drop”, uma reimplementação extendida da linguagem Scratch que tem ganho alguma notoriedade por ter sido incluída no ambiente gráfico dos Raspberry Pi.
Eis um exemplo de um programa muito simples, que faz um rover andar às voltas:
De há um par de anos para cá que alimento a ideia de utilizar Snap! para ensinar à miudagem [e não só] conceitos básicos de robótica. A forma como o Snap! está a ser desenvolvido permite criar «device extensions» para interagir com dispositivos físicos (descobri o Snap! justamente por ter uma extensão para Mindstorms NXT, a Snap-NXT by Technoboy10) e hoje criei a minha primeira extensão, para o Sbrick.
Basicamente editei a extensão para Mindstorms NXT, deitei fora a parte específica do NXT e acrescentei os meus comandos que invocam o comando gatttool do BlueZ (do que resulta a minha extensão apenas funcionar em Linux, lamento).
O primeiro é um programa em Python que corre um servidor http muito básico que aceita comandos http. O segundo é uma definição XML das funcionalidades implementadas (dois comandos apenas: ‘move motor’ e ‘stop motor’, sendo possível controlar 3 «motores»: A = channel#1, B=channel#2 e P = channel#1+channel#2). Estando o programa ‘snap-sbrick.py’ a correr, lançamos o Snap! com a device extension quando acedemos por browser ao endereço:
Ou seja o Snap! em si é carregado da Universidade de Berkeley e apontado para o nosso PC de onde carrega as definições das funcionalidades adicionais. É possível carregar o Snap! a partir do nosso próprio PC para trabalhar em modo offline mas isso já é outra história.
A definição dos comandos pode também ser feita graficamente, sendo depois gerado um ficheiro XML semelhante ao acima referido:
comando ‘move motor’
comando ‘stop motor’
Podemos ver que a definição de ‘move motor’ está bem melhor que a de ‘stop motor’ – é feita uma validação dos parâmetros, se speed não pertencer ao intervalo [-100,100] ou se motor não for A/B/P é gerada uma excepção.
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: