Chegando à última etapa do projeto do robô, desenvolveremos uma aplicação de controle. Até agora, vimos todos os exemplos sendo executados em um terminal, porém, agora criaremos uma interface gráfica que tornará a experiência de controle um pouco mais agradável.
Quem já desenvolveu aplicações Android, sabe que a interface da aplicação é formada por um arquivo XML, onde são definidos os Widgets que compõem a interface. Para a construção da interface do robô, utilizaremos o mesmo princípio, porém, usando HTML. O WebView fornecerá todos os recursos a partir da aplicação Python para montarmos a interface HTML.
A aplicação será dividida em três camadas, sendo a camada principal em Python, a interface em HTML e uma camada intermediária, em JavaScript. O gráfico abaixo, ajuda a explicar melhor como tudo funciona:
A ideia de misturar duas linguagens de programação e uma linguagem de marcação voltada para WEB, pode parecer confuso, como analogia, podemos pensar em um botão usado em eletrônicos: o HTML seria o acabamento plástico que o botão recebe, o JavaScript seria o circuito do botão, já o Python, é todo o resto do circuito em que o botão está inserido e que define o que o botão faz.
HTML
O uso de HTML para criação da interface gráfica, abre um grande leque de recursos gráficos e fácil manipulação. Não entrarei em detalhes sobre HTML, pois é um conhecimento muito difundido e simples de ser utilizado.
Além do JavaScript, também integramos CSS à nossa aplicação. Usando a mesma analogia do botão, o CSS seria a pintura do botão e sua posição na carcaça do objeto; o CSS, na verdade, é o conjunto de regras de estilo que a aplicação deve seguir.
Abaixo, segue o código HTML já com CSS:
<!DOCTYPE HTML><br/>
<html><br/>
<head><br/>
<script type="text/javascript" src="/sdcard/sl4a/scripts/Script.js"></script><br/>
<!--Referencia o arquivo que contém o código JavaScript--><br/>
<br/>
<meta name="viewport" id="viewport" content="width=device-width, target-densitydpi=device-dpi,initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/><br/>
<!--Cuida para que nenhum elemento seja redmensionado--><br/>
<style><br/>
<br/>
body {<br/>
background-color: #eeeeec;<br/>
margin:0;<br/>
<br/>
}<br/>
<br/>
<span class="comentario">#pc4r</span>{<br/>
position:relative;<br/>
float:left;<br/>
height:30px;<br/>
width:300px;<br/>
font-weight:bold;<br/>
font-size:35px;<br/>
text-align:left;<br/>
padding-left:10px;<br/>
}<br/>
<br/>
<span class="comentario">#sub</span>{<br/>
position:relative;<br/>
float:left;<br/>
font-size:16px;<br/>
height:30px;<br/>
width:300px;<br/>
padding-left:10px;<br/>
padding-top:10px;<br/>
}<br/>
<span class="comentario">#fechar</span>{<br/>
text-align:center;<br/>
padding-bottom:17px;<br/>
float:right;<br/>
font-weight:bold;<br/>
font-size:35px;<br/>
height:70px;<br/>
width:100px;<br/>
}<br/>
<br/>
<span class="comentario">#cabeca</span>{<br/>
-webkit-box-shadow:inset 0px 0px 2px 2px #204a87;<br/>
background-color:#007dc1;<br/>
font-family:arial;<br/>
color:#f3f3f3;<br/>
height:70px;<br/>
width:500px;<br/>
margin-bottom:10px;<br/>
}<br/>
<br/>
<span class="comentario">#display</span> {<br/>
-webkit-border-radius:10px;<br/>
padding-top:10px;<br/>
padding-left:10px;<br/>
padding-right:10px;<br/>
margin-left:40px;<br/>
font-size:20px;<br/>
margin-bottom:20px;<br/>
width:385px;<br/>
height:240px;<br/>
background:white;<br/>
}<br/>
<br/>
button {<br/>
<br/>
-webkit-box-shadow:inset 0px 1px 0px 0px #54a3f7;<br/>
background-color:#007dc1;<br/>
-webkit-border-radius:3px;<br/>
border:1px solid #124d77;<br/>
display:inline-block;<br/>
color:#ffffff;<br/>
font-family:arial;<br/>
font-size:13px;<br/>
padding:6px 24px;<br/>
text-decoration:none;<br/>
text-shadow:0px 1px 0px #154682;<br/>
margin-bottom:5px;<br/>
}<br/>
button:hover {background-color:#0061a7;}<br/>
button:active {position:relative;top:1px;}<br/>
<br/>
<span class="comentario">#botoes1</span>{margin-top:10px;margin-left:45px;<br/>
}<br/>
<span class="comentario">#botoes2</span>{margin-top:5px;margin-left:10px;margin-right:10px;}<br/>
<br/>
button{width:120px;height:60px;}<br/>
<br/>
</style><br/>
<!--CSS--><br/>
</head><br/>
<br/>
<body><br/>
<div id="cabeca"><br/>
<div id="fechar"><a onclick="sair('')">X</a></div><br/>
<div id="pc4r">PC4R</div><br/>
<div id="sub">Python Controller For Robots</div><br/>
</div><br/>
<div id="display"><br/>
<p>Python Controller for Robots</p><br/>
<p>Use o teclado para movimentar o robô e os sinais de '<' e '>' para movimentar o sensor de distância.</p><br/>
<p>Code by:vikitor566</b><br/>
</div><!--Exibe informações--><br/>
<br/>
<br/>
<!--Botoes--><br/>
<!--Os botoes usam o teclado de computador como referencia para definir direções--><br/>
<!--Cada botão armazena uma coordenada que define uma direção--><br/>
<!--A função onclick passa a coordenada do botão ao JavaScript--><br/>
<br/>
<div id= 'botoes1'><br/>
<button id="q" type="button" onclick="botoes('x-1,y1')">Q</button><br/>
<button id="w" type="button" onclick="botoes('x0,y1')">W</button><br/>
<button id="e" type="button" onclick="botoes('x1,y1')">E</button><br><br/>
<button id="a" type="button" onclick="botoes('x-1,y0')">A</button><br/>
<button id="s" type="button" onclick="botoes('x0,y0')">S</button><br/>
<button id="d" type="button" onclick="botoes('x1,y0')">D</button><br><br/>
<button id="z" type="button" onclick="botoes('x-1,y-1')">Z</button><br/>
<button id="x" type="button" onclick="botoes('x0,y-1')">X</button><br/>
<button id="c" type="button" onclick="botoes('x1,y-1')">C</button><br><br/>
</div><br/>
<br/>
<div id = botoes2 align="center""><br/>
<!--Botões de comando do Sensor--><br/>
<button id="sensor_esquerda" type="button" onclick="visao('esquerda')"><</button><br/>
<!--Faz com que o Sensor se vire para direita em 30º --><br/>
<button id="sensor_direita" type="button" onclick="visao('direita')">></button><br><br/>
<!--Faz com que o Sensor se vire para direita em 30º --><br/>
<button id="acelerometro" type="button" onclick="acelerometro('')"> Acelerometro</button><br><br/>
<!--Inicia o controle por acelerometro --><br/>
</div><br/>
<br/>
</body><br/>
</html>
Ao pressionarmos o botão, a função é invocada passando um parâmetro, se necessário. No caso, passamos o parâmetro diretamente na função, mas, no caso de uma área de texto, por exemplo, usaríamos a sintaxe
onkeyup="Texto(this.value)". A função seria invocada, assim que pressionássemos o
Enter, passando o valor do campo de texto como parâmetro.
Após digitarmos o código HTML, devemos salvá-lo e copiá-lo para o Android. No caso, temos um código pronto, mas, no caso do desenvolvimento, precisamos copiar o código diversas vezes, então, o uso de um
alias, que faça isso de forma prática, ajudará muito. Vamos salvar o arquivo como
interface.html.
JavaScript
A segunda camada da aplicação será responsável por integrar a interface com a aplicação em si, para isso, utilizaremos JavaScript. Todo o código JavaScript é baseado em funções que recebem dados da interface e encaminha para aplicação, ou o processo inverso de levar dados da aplicação para interface.
O código abaixo, apresenta pequenos conceitos, que serão vitais para entendermos como a integração entre as camadas funciona. Cada função precisa enviar um tipo de dado à aplicação principal, algumas apenas enviam, outras, além de enviarem, recolhem os dados recebidos e os exibem na interface.
Para enviar um dado, utilizamos um evento, como no exemplo abaixo:
var droid = new Android()
function Funcao_JavaScript(Parametro){
droid.eventPost("Nome_do_evento",Parametro)
}
Para enviarmos uma informação da interface para a aplicação, usamos a função
droid.eventPost("Nome_do_evento",Parametro), ao definirmos um nome para o evento, fica mais fácil tratar a forma da aplicação receber os dados. O código abaixo, mostra a camada de JavaScript funcionando:
var droid = new Android()
// Cria o objeto droid que fornece os recuros SDK que vao permitir a comunicacao com a aplicacao Python
function botoes(bt_value){
// Funcao dos botoes de direcao
droid.eventPost('botao', bt_value)
// Cria o evento botao que envia os valores das coordenadas para a aplicacao Python
}
function acelerometro(){
//Funcao de controle por acelerometro
droid.eventPost('acelerometro','')
//Ativa o controle via acelerometro
}
function visao(dir){
// Funcao do sensor ultrassonico
droid.eventPost('visao', dir)
// Envia a direcao em que o sensor deve virar
droid.registerCallback('distancia', function(d) {
// Recebe a distancia ate o obstaculo e cria uma funcao que
document.getElementById('display').innerHTML='Distância: ' + d.data + 'cm';
// Exibe a distancia no display
});
//Aguarda 2s para limpar a tela
setTimeout(function(){limpar();},2000);
}
function limpar(){
var art = "
Python Controller for Robots
Use o teclado para movimentar o robô e os sinais de '<' e '>' para movimentar o sensor de distância.
";
document.getElementById('display').innerHTML=art;
// Limpa a tela
}
function sair(){
// Sair da aplicacao
droid.eventPost('sair','')
// Apenas cria um evento que nao envia dados
}
A função
visao, além de disparar os movimentos do sensor, deve exibir a distância na tela, para isso, editamos o conteúdo da div
display:
function visao(dir){ // Funcao do sensor ultrassonico
droid.eventPost('visao', dir) // Envia a direcao em que o sensor deve virar
droid.registerCallback('distancia', function(d) { // Recebe a distancia ate o obstaculo e cria uma funcao que
document.getElementById('display').innerHTML=d.data;// Exibe a distancia no display
});
O comando
droid.registerCallback('distancia', function(d) aguarda a aplicação Python disparar o evento distancia, e já chama a função
function passando os dados recebidos do Python, nesse caso, a distância até o obstáculo. A função, por sua vez, usa um seletor que indica para buscar a div display na interface e editá-lo com o conteúdo da variável que foi passada,
document.getElementById('display').innerHTML=d.data, exibindo assim os valores no display.
Salvamos o arquivo como
Script.js e copiamos para o dispositivo.
Depois das duas primeiras camadas, chegamos à parte mais importante de todo o projeto, a aplicação Python que fará a integração da interface com o Android que, por sua vez, está integrado com o Arduino.
O interessante, é o nível de abstração de todo projeto, por alguns momentos, poderíamos até esquecer que destina-se ao Arduino, tudo isso graças à simplicidade de cada elemento. A aplicação deve ler os dados do usuário, tratar de forma a gerar as coordenadas que controlam o robô, enviá-los e, caso houverem dados a serem recebidos, os exibir na tela.
Vamos ao código:
#!/usr/bin/python
#Author vikitor566
import android
import time
droid = android.Android()
def conexao():
droid.toggleBluetoothState(True)
#Ativa o Bluetooth
droid.bluetoothConnect('00001101-0000-1000-8000-00805F9B34FB')
#Abre conexo Serial
def leia(): ## Le dados
entrada = droid.bluetoothReadLine().result
return entrada
def escreva(linha): ## Envia dados
droid.bluetoothWrite(linha)
time.sleep(100/1000)
droid.eventClearBuffer()
def visao(ang): #Definie a angulacao do sensor
escreva('z'+str(ang)+'\n') # Envia a posicao
distancia = leia() # Recebe a distancia ate o obtaculo
return distancia
def acelerometro(tempo): #Controle por acelerometro
while(tempo > 0): #Enquanto tempo for maior que zero
droid.startSensingTimed(2, 100) ##Incia a leitura
x = droid.sensorsReadAccelerometer().result[0]#Coordenada de x
y = droid.sensorsReadAccelerometer().result[1]#Coordenada de y
if(x and y): # Se houver valores
x,y= int(x),int(y) #Transforma em inteiro
x = (x*-1) #Inverte x
if(y >-2 and y < 2 ): # Diminui a sensibilidade para que o robô permaneca parado
y = 0
if(x >-2 and x < 2 ):
x = 0
linha = ('x' + str(x)) + (' ') +('y' + str(y)) + ('\n') #Formata as coordenadas
droid.bluetoothWrite(linha) #Envia as coordenas
time.sleep(100/1000) #Aguarda um 100 ms
tempo=(tempo - 100) #Diminui o tem tempo em 10ms
linha = ('x0,y0') #Para o robo
droid.bluetoothWrite(linha)
droid.eventClearBuffer()
droid.stopSensing() #Para o sensor
conexao() ## Inicia a conexao
angulo = 90; #Define a posicao inicial do sensor.
droid.webViewShow('file:///sdcard/sl4a/scripts/teste.html')
#Cria a interface grafica apartir do arquivo HTML
grafico = True # Variavel de parada exibe a interface enquanto Verdadeira
while(grafico): # Inicio do loop
event = droid.eventWait().result #Aguardando evento
# Trata os eventos dos botoes de direcao(Q,W,E,A,S,D,F,Z,X,C)
# Cada botao armazena uma coordenada
if event['name'] == 'botao':
droid.eventClearBuffer()
cord = event['data'] # Armazena coordenada
escreva(cord) ## Escreve a coordenada na Serial/Bluetooth
# Trata os enventos dos botoes '<' e '>' que fazem com que o sensor vire para esquerda ou direita
# A posicao minina do Sensor e 0 e a maxima 180
elif event['name'] == 'visao':
dir = event['data'] ## Armazena a direcao
if(dir == 'direita' and angulo >= 30): ##Se posicao do sensor e maior ou igual a 30
angulo = (angulo-30) ## Posicao recebe posicao menos 30
elif(dir == 'esquerda' and angulo <= 150 ): ##Se posicao do sensor e menor ou igual a 150
angulo = (angulo+30) ## Posicao recebe posicao mais 30
## Armazena em cm a distancia ate o obstaculo na posicao angulo
cm = visao(angulo)
## Armazena em d a String a ser exibida na interface
d = str(cm)
#Envia a interface
droid.eventPost('distancia', d)
if(cm < 20):
droid.ttsSpeak("Obstaculo a frente")
droid.eventClearBuffer()
elif event['name'] == 'acelerometro':
acelerometro(10000)
elif event['name'] == 'sair': ## Sair da aplicacao
escreva('x0,y0,z90') ##Para totalmente o robo
droid.toggleBluetoothState(False) ##Desliga o Bluetooth
droid.eventClearBuffer()
grafico = False
O código acima, trabalha com a mesma ideia de funções e eventos. As três primeiras funções são responsáveis pela comunicação Bluetooth, logo em seguida, temos a função que recebe os dados do sensor ultrassônico e a função do controle por acelerômetro.
Depois da definição das funções, iniciamos a conexão Bluetooth e abrimos o loop da interface gráfica, atribuindo verdadeiro a variável booleana grafico. E iniciamos a interface com o comando
droid.webViewShow('file:///sdcard/sl4a/scripts/interface.html'), enquanto a variável gráfico for verdadeira, a interface gráfica será exibida.
Como definimos nomes a cada evento, fica fácil definir ações, com o comando
event = droid.eventWait().result, o loop fica em espera, esperando por um evento da interface. A partir da interação com a interface, é disparada uma função do JavaScript que faz com que o evento seja postado. A partir do nome do evento, o programa executa a ação necessária.
Evento = droid.eventWait().result
if Evento['name'] == 'Exemplo':
print "Teste"
No exemplo anterior, apenas usamos um evento que dispara uma ação, mas, em alguns casos, será necessário recolher dados como com os botões de direção, cada botão armazena uma coordenada que define a direção, e/ou o sentido do robô.
if event['name'] == 'botao':
cord = event['data'] ## Armazena coordenada
escreva(cord) ## Escreve a coordenada na Serial/Bluetooth
Para ler os valores enviados com o evento, usamos o comando
cord = event['data'], e logo após enviamos via Bluetooth.
A função
visão, faz com que o sensor se vire sempre de 30° em 30°, adicionando ou subtraindo 30 da posição do servo; para manter tal controle no começo da aplicação, definimos a posição do sensor em 90°. Após mover o sensor, lemos a distância e a exibimos na tela.
elif event['name'] == 'visao':
dir = event['data'] ## Armazena a direcao
if(dir == 'direita' and angulo >= 30): ##Se posicao do sensor e maior ou igual a 30
angulo = (angulo-30) ## Posicao recebe posicao menos 30
elif(dir == 'esquerda' and angulo <= 150 ): ##Se posicao do sensor e menor ou igual a 150
angulo = (angulo+30) ## Posicao recebe posicao mais 30
## Armazena em cm a distancia ate o obstaculo na posicao angulo
cm = visao(angulo)
## Armazena em d a String a ser exibida na interface
d = "Distancia" + cm
#Envia a interface
droid.eventPost('distancia', d)
O comando
droid.eventPost('distancia', d), envia a distância para que possa ser exibida na tela, através da camada JavaScript, usando a mesma ideia de um evento, com nome seguido dos dados. É importante observar que a variável
d é a mesma usada na camada JavaScript do código anterior.
Para controlar o robô por acelerômetro, é necessário ler os dados do sensor e enviá-los como coordenadas, para diminuir um pouco da sensibilidade o robô apenas se move com as coordenadas maiores que 2.
A leitura dos sensores será feita em intervalos de 200 milissegundos, tempo necessário para que os dados sejam enviados ao robô. A cada leitura, a variável tempo decresce 200 unidades até chegar a zero, é necessário esse controle por tempo, caso contrário, a aplicação entraria em loop infinito. Após o tempo chegar a zero, enviamos as coordenadas zeradas para que o robô pare.
def acelerometro(tempo): #Controle por acelerometro
while(tempo > 0): #Enquanto tempo for maior que zero
droid.startSensingTimed(2, 100) ##Incia a leitura
x = droid.sensorsReadAccelerometer().result[0]#Coordenada de x
y = droid.sensorsReadAccelerometer().result[1]#Coordenada de y
if(x and y): # Se houver valores
x,y= int(x),int(y) #Transforma em inteiro
x = (x*-1) #Inverte x
if(y >-2 and y < 2 ): # Diminui a sensibilidade para que o robô permaneca parado
y = 0
if(x >-2 and x < 2 ):
x = 0
linha = ('x' + str(x)) + (' ') +('y' + str(y)) + ('\n') #Formata as coordenadas
droid.bluetoothWrite(linha) #Envia as coordenas
time.sleep(100/1000) #Aguarda um 100 ms
tempo=(tempo - 100) #Diminui o tem tempo em 10ms
linha = ('x0,y0') #Para o robo
droid.bluetoothWrite(linha)
droid.eventClearBuffer()
droid.stopSensing() #Para o sensor
No final, o pequeno Steve toma vida:
Referências