Another python BLE library – not the most maintained, certainly not the most trendy but at the moment the only one I managed to install and use on EV3 (running ev3dev of course): bluepy.
Short instructions:
start from a totally fresh ev3dev installation
enable Wi-Fi and Bluetooth (assuming you have a USB hub with a compatible Wi-Fi dongle and a compatible BLE dongle)
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()
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:
Para terminar a apresentação do AD4M4ST0R faltava apenas o código.
O programa principal é um script escrito em python ‘joy.py’ que lida com o gamepad e os motores. O controlo da webcam e do buzzer é delegado em dois bash scripts (‘saycheese.sh’ e ‘beep.sh’).
O programa principal, “joy.py’:
import sys, traceback, os
os.environ['SDL_VIDEODRIVER'] = 'dummy'
# from math import sqrt
from math import log10
from subprocess import call
from time import sleep
from pygame import joystick, event, display
### botoes ###
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();
### arranca em modo Joystick ###
control_by_JOY=True;
num_axes=js.get_numaxes();
num_buttons=js.get_numbuttons();
num_hats=js.get_numhats();
### sabemos a priori que tem 4 axes, 13 buttons e 1 hat
### inicializa motores com Duty Cycle=0
call("echo 0 > /sys/class/tacho-motor/tacho-motor0/duty_cycle_sp", shell=True);
call("echo 0 > /sys/class/tacho-motor/tacho-motor1/duty_cycle_sp", shell=True);
call("echo 1 > /sys/class/tacho-motor/tacho-motor0/run", shell=True);
call("echo 1 > /sys/class/tacho-motor/tacho-motor1/run", shell=True);
flag=False;
while True:
x=y=motor_direito=motor_esquerdo=0.0;
event.pump();
button_mode=js.get_button(B_SELECT);
button_shot=js.get_button(B_SQUARE);
if button_mode ==1:
call("./beep.sh" , shell=True);
if control_by_JOY==True:
control_by_JOY=False;
print 'Control Mode=HAT';
else:
control_by_JOY=True;
print 'Control Mode=JOYSTICK';
if button_shot==1:
print 'Cheese!!!';
call("/root/saycheese.sh", shell=True);
### eixo dos joysticks entre -1 e +1
### x=axis2 , y=-axis3
### ignorar valores abaixo de 0.1 (dead zone)
### aplicar log10(100x) aos restantes (para dar enfase aos mais baixos)
### o resultado e' 0 ou 1 a 2 por isso
### multiplicar por 25 da' valores Duty Cycle nulos ou entre 25 e 50
if control_by_JOY==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.1:
x=0;
else:
x=log10(axis2*100);
elif axis2<0:
if axis2>-0.1:
x=0;
else:
x=-log10(-axis2*100);
if axis3>0:
if axis3<0.1:
y=0;
else:
y=-log10(axis3*100);
elif axis3<0:
if axis3>-0.1:
y=0;
else:
y=log10(-axis3*100);
else:
y=0;
if y<>0:
if x<0:
motor_direito=50*y/2;
# rodar para a esquerda => reduzir motor esquerdo
motor_esquerdo=motor_direito*(1+x/3);
else:
motor_esquerdo=50*y/2;
# rodar para a direita => reduzir motor direito
motor_direito=motor_esquerdo*(1-x/3);
elif x<>0:
# y=0, apenas roda
motor_esquerdo=25*x;
motor_direito=-motor_esquerdo;
else:
# Control by HAT keys
hat=js.get_hat(0);
if hat==(0,1):
### anda em frente ###
motor_direito=50;
motor_esquerdo=50;
elif hat==(1,0):
### roda para a direita ###
motor_esquerdo=50;
motor_direito=-50;
elif hat==(0,-1):
### recua ###
motor_direito=-50;
motor_esquerdo=-50;
elif hat==(-1,0):
### roda para a esquerda ###
motor_esquerdo=-50;
motor_direito=50;
elif hat==(1,1):
### frente e roda para a direita ###
motor_esquerdo=50;
motor_direito=25;
elif hat==(-1,1):
### frente e roda para a esquerda ###
motor_esquerdo=25;
motor_direito=50;
elif hat==(-1,-1):
### recua e roda para a esquerda ###
motor_esquerdo=-50;
motor_direito=-25;
elif hat==(1,-1):
### recua e roda para a direita ###
motor_esquerdo=-25;
motor_direito=-50;
comando_motordir = "echo " + repr(motor_direito) + " > /sys/class/tacho-motor/tacho-motor0/duty_cycle_sp";
comando_motoresq = "echo " + repr(motor_esquerdo) + " > /sys/class/tacho-motor/tacho-motor1/duty_cycle_sp";
call(comando_motordir, shell=True);
call(comando_motoresq, shell=True);
# end while
except (KeyboardInterrupt, SystemExit):
print "Exiting...";
except Exception:
traceback.print_exc(file=sys.stdout);
call("echo 0 > /sys/class/tacho-motor/tacho-motor0/duty_cycle_sp", shell=True);
call("echo 0 > /sys/class/tacho-motor/tacho-motor1/duty_cycle_sp", shell=True);
call("echo 0 > /sys/class/tacho-motor/tacho-motor0/run", shell=True);
call("echo 0 > /sys/class/tacho-motor/tacho-motor1/run", shell=True);
js.quit();
joystick.quit();
display.quit();
sys.exit(0);
if __name__ == "__main__":
main()
Com a webcam a funcionar e com tanta coisa em comum com a comunidade Raspberry Pi ocorreu-me experimentar o OpenCV e testar as capacidades do EV3 no campo da Computer Vision.
Em vez de instalar o opencv todo, instalei apenas o módulo para python:
root@ev3dev:~# apt-get install python-opencv
Depois foi só criar o script ‘teste-opencv.py’:
import numpy as np
import cv2
imagePath = "/root/teste-face.jpg"
cascPath = "/root/haarcascade_frontalface_default.xml"
resultPath ="/root/resultado.jpg"
faceCascade = cv2.CascadeClassifier(cascPath)
image = cv2.imread(imagePath)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags = cv2.cv.CV_HAAR_SCALE_IMAGE
)
print "Found {0} faces!".format(len(faces))
# Draw a rectangle around the faces
for (x, y, w, h) in faces:
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imwrite(resultPath,image)
Como não instalei o opencv não tenho os ficheiros xml com regras de classificação. Para este exemplo o autor do artigo referido disponibiliza também o ficheiro ‘haarcascade_frontalface_default.xml‘.
Para esta foto ‘teste-face.jpg’:
O resultado do script, ao fim de 3 a 5 minutos de CPU a 80% foi este:
Para a primeira aplicação prática montei o Roaring Lion do conjunto LEGO WeDo (as instruções de montagem deste e outros modelos simples estão disponiveis no site LEGO Education).
O leão é muito simples: fazendo rodar o motor num sentido ele levanta-se nas patas dianteiras, no outro sentido volta a deitar-se (como não há sensor de posição é necessário afinar os tempos à mão)
Numa primeira fase testei comandar o leão remotamente a partir de uma sessão SSH, criando para isso um script (lion.py) em python:
#!/usr/bin/python3
import sys
from time import sleep
import pifacedigitalio
DELAY_UP = 0.17 # o leão custa um pouco mais a subir
DELAY_DOWN = 0.14 # do que a descer
pifacedigital = pifacedigitalio.PiFaceDigital()
led7=pifacedigital.leds[7]
led6=pifacedigital.leds[6]
led7.turn_off()
led6.turn_off()
comando='.'
while(comando!="s"):
comando=input("Comando: ")
if (comando=="c"):
print("Para cima!")
led7.turn_on()
sleep(DELAY_UP)
led7.turn_off()
elif (comando=="b"):
print("Para baixo!")
led6.turn_on()
sleep(DELAY_DOWN)
led6.turn_off()
elif (comando=="s"):
print("Tchau!")
else:
print("?") # comando desconhecido
É necessário dar permissões de execução ao script:
sudo chmod u+x lion.py
Para correr, basta invocar directamente o script:
./lion.py
Os comandos são simples:
c – para cima
b – para baixo
s – para sair
Funciona bem mas seria bem melhor se o leão estivesse sempre à espera de um comando e pudessemos usar o rato em vez do teclado, por exemplo a partir de um browser.
Para isso precisamos de um servidor (daemon) http – poderia ter ido para o apache mas preferi ir à procura de um servidor http mais leve e encontrei o Monkey (no passado tive boas experiências com o lighttpd e existe também o nginx):
Começamos por adicionar o repositório para usar a versão directamente mais recente em vez da que vem do mundo Debian:
sudo nano /etc/apt/sources.list
e adicionar a linha
deb http://packages.monkey-project.com/primates_pi primates_pi main
(embora só seja, mesmo necessários o monkey e os módulos liana e cgi )
Para testar basta aceder por browser na porta 2001:
http://192.168.1.123:2001/
(o conteúdo desta página está ficamente em /usr/share/monkey/)
Para poder invocar scripts de python a partir de páginas html é necessário recorrer a CGI (penso que seria possível fastcgi mas esta não é de todo a minha especialidade).
Criei uma pasta só para armazenar os scripts que vão ser acedidos por browser:
sudo mkdir /usr/share/monkey/cgi-bin
É necessário configurar o monkey para carregar o módulo CGI:
sudo nano /etc/monkey/plugins.load
basta descomentar a linha (mentendo os espaços em branco):
Load /usr/lib/monkey/monkey-cgi.so
além disso é necessário mudar a conta com que o monkey corre para poder utilizar as libraries do PiFace:
sudo nano /etc/monkey/monkey.conf
(basta mudar de www-data para root)
E finalmente reiniciar o monkey:
sudo service monkey restart
Para testar, colocar na pasta /usr/share/monkey/cgi-bin o ficheiro hello.cgi:
#!/usr/bin/python3
print("Content-Type: text/html") # HTML is following
print() # blank line, end of headers
print("<TITLE>CGI script output</TITLE>")
print("<H1>This is my first CGI script</H1>")
print("Hello, world!")
(eventualmente dar-lhe permissões de execução – sudo chmod u+x)
E aceder por browser: http://192.168.1.123:2001/cgi-bin/hello.cgi
Confirmado o funcionamento, criar 2 scripts para controlo do leão:
É pena que o CGI obrigue a que cada vez que é dado um comando seja reinicializado o python e recarregados os módulos – cada comando demora por isso demora 2 ou 3 segundos. Os melhoramentos ficam para outra altura.