Robótica com Android e Arduino

Nesse artigo, pretendo descrever o processo de criação de um robô baseado na plataforma Arduino e integrado ao Android, através da camada de script SL4A.

[ Hits: 84.556 ]

Por: João Victor em 16/04/2014


Aplicação de controle



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:
Linux: Robótica com Android e Arduino

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


Página anterior     Próxima página

Páginas do artigo
   1. Introdução
   2. Montagem do robô
   3. Programando
   4. Bluetooth
   5. Integrando recursos
   6. Aplicação de controle
   7. Conclusão
Outros artigos deste autor

Python com SL4A - A camada de script do Android

Leitura recomendada

Monitorando produtos no ML com Python 3 via BeautifulSoup

Python - Enviando Backup para Servidor Secundário

rwd - Restart When Down

Splash Screen para Inkscape

Interagindo com servidores HTTP com Python

  
Comentários
[1] Comentário enviado por albfneto em 16/04/2014 - 19:46h

Muito bom isso! favoritado

[2] Comentário enviado por vikitor566 em 16/04/2014 - 23:02h

Muito obrigado albfneto

[3] Comentário enviado por Lisandro em 16/04/2014 - 23:18h

Excelente! Parabéns pelo artigo e pela paciência. Por várias vezes pensei em fazer um artigo semelhante e sei bem o trabalho que dá.
Já está nos meus favoritos.

[4] Comentário enviado por mcnd2 em 17/04/2014 - 13:19h

Muito interessante.

Artigo muito bem explicado e de fácil entendimento.

10!

[5] Comentário enviado por vikitor566 em 17/04/2014 - 14:03h

Muito obrigado a todos, o trabalho é recompensado quando o artigo consegue compartilhar a idéia pelo qual foi feito.

[6] Comentário enviado por Sandro1 em 17/04/2014 - 21:24h

Excelente artigo e como já disseram está muito bem explicado, qualquer pessoa vai conseguir fazer esse robô!

[7] Comentário enviado por k_cesar em 20/04/2014 - 13:52h

Parabéns pelo artigo.
Excelente!

[8] Comentário enviado por matiasalexsandro em 22/04/2014 - 23:56h

excelente artigo favoritado pra projetos futuros

[9] Comentário enviado por vikitor566 em 23/04/2014 - 09:59h

Muito obrigado a todos

[10] Comentário enviado por JFurio em 19/05/2014 - 09:14h

Realmente, nota 10 mesmo ! Cheguei até a estudar um orçamento e ligar em umas lojas para ver uns orçamentos, e realmente não é tão caro ! Vou tentar montar um dia, e independente do resultado, posto aqui ! Vlw !


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts