Por que eu devo ler este artigo:Este artigo é útil para programadores que não possuem conhecimentos técnicos sobre a utilização das classes e métodos da API do Android responsáveis por acessar os sensores de um smartphone ou tablet, e aos que desejam aprender sobre o protoloco de comunicação bluetooth e o protocolo específico utilizado pelo LEGO Mindstorms para comunicação com outros dispositivos.

Os celulares modernos, mais conhecidos como smartphones, já vêm equipados com inúmeros sensores, entre acelerômetros, giroscópios, bússola, sensores de proximidade e luz, entre outros. O uso de sensores associados a plataformas mobile em código aberto, como a plataforma Android, têm proporcionado aos desenvolvedores construírem aplicativos e tecnologias que integrem hardware e software, proporcionando inúmeros avanços no uso da computação e robótica.

Uma prova da grande revolução da robótica nos dias de hoje está nos brinquedos “robotizados”, sendo um deles o LEGO Mindstorms, uma plataforma voltada para a educação tecnológica na qual é possível montar robôs sem grandes conhecimentos sobre eletrônica. Entretanto, há casos em que os robôs são incapazes de desenvolver determinadas tarefas de forma independente. Uma alternativa para resolver esse tipo de limitação é utilizar um controle remoto.

A comunicação entre smartphones e dispositivos remotos aumenta consideravelmente as possibilidades de aplicações para sistemas móveis, em especial quando se necessita recursos alheios aos smartphones como utilização de impressoras, leitores de códigos de barras ou o controle remoto de dispositivos.

Assim, o foco deste artigo é o desenvolvimento de um aplicativo para a plataforma Android que controle remotamente um LEGO Mindstorms. Para a comunicação foi optada pela tecnologia bluetooth, já para o controle será utilizado o sensor de movimento presente nos smartphones modernos, assim, de acordo com a movimentação do smartphone é esperada uma determinada ação do robô.

Bluetooth é uma tecnologia sem fio usada para conectar e transmitir dados entre dispositivos em rede pessoais (chamadas de Personal Area Networks - PANs). Com velocidades de transmissão que podem se aproximar de 24 Mb/s, o bluetooth é um o padrão em comunicação de curta distância.

Celulares, smartphones, câmeras digitais, teclados, mouses e outros dispositivos adotaram a tecnologia por ser robusta, economizar energia e ter uma fácil implementação. Um dispositivo operando bluetooth pode se conectar com até oito outros dispositivos, que o torna uma ótima opção para uma rede de dispositivos móveis. O bluetooth surgiu como resposta para a necessidade de conectar dispositivos sem a utilização de cabos, visando a econômica de energia, a fácil operação e a comodidade. São utilizadas ondas de rádio de baixa potência, operando em frequências que vão de 2.4Ghz a 2.5 Ghz, na faixa de frequência conhecida como ISM (Industrial, Scientific, Medical). O uso da baixa potência aumenta a economia de energia das baterias e limita o alcance a um máximo de 100m.

Android e Bluetooth

Android é um sistema operacional com código aberto baseado no núcleo do Linux para dispositivos móveis. A primeira versão do Android com suporte a bluetooth foi anunciada em 2009 pela Google, este possuindo nome Eclair. A versão é a 2.0 e conta com suporte ao bluetooth 2.1.

É possível utilizar as classes nativas no Android e suas funções para criar aplicativos que utilizem o bluetooth. Aplicações Android que tem suporte ao bluetooth podem descobrir e se conectar a dispositivos, estabelecer conexões ponto-a-ponto e ponto-multiponto e utilizar conexões RFCOMM para transferir dados.

Através de APIs, os aplicativos desenvolvidos para Android podem configurar conexões, buscar, conectar e transferir dados entre os dispositivos.

As principais classes usadas para configurar conexões bluetooth no Android são:

  • BluetoothAdapter: Representa o adaptador bluetooth local, o hardware de bluetooth. Utilizado para instanciar dispositivos bluetooth usando macs conhecidos e criar soketsbluetooth para receber conexões de outros dispositivos;
  • BluetoothDevice: Usado para requisitar informações e conexões a dispositivos remotos;
  • BluetoothSocket: Representa a interface da conexão;
  • BluetoothServeSocket: Habilita o servidor a receber pedidos de conexão;
  • BluetoothClass: Propriedades de somente leitura que definem as características e os serviços do dispositivo bluetooth;
  • BluetoothProfile: Representa um perfil bluetooth. Existem vários perfis pré-definidos de configuração que podem ser escolhidos para se trabalhar como headset profile e o hands-free profile;
  • BluetoothHeadset: Suporte para headsets utilizando a tecnologia bluetooth;
  • BluetoothA2p: Define a qualidade do áudio que pode ser transmitido em conexões entre dispositivos;
  • BluetoothProfile.ServiceListener: Avisa clientes bluetooth quando são desconectados ou conectados.

Agora que conhecemos algumas definições importantes sobre o bluetooth, vamos entender um pouco mais sobre o uso de sensores no Android.

Sensores

Os smartphones e tablets de última geração possuem uma vasta rede de sensores para controlar algumas funções do dispositivo.

Quando uma ligação está em curso, por exemplo, e o usuário aproxima o smartphone do ouvido, a tela é desligada e os sensores de toque desativados. Isso ocorre devido ao uso do sensor de proximidade. Quando um smartphone ou tablet com Android é rotacionado, a orientação da tela também muda. Isso se deve ao uso do acelerômetro ou giroscópio. Um acelerômetro é um sensor capaz de medir a aceleração a partir da força aplicada sobre ele. Assim, é possível saber qual foi o movimento efetuado pelo usuário, em graus, durante o giro do equipamento. Acelerômetros são os sensores mais comuns dos dispositivos Android e estão presentes na maioria deles.

Basicamente, um acelerômetro é um sensor de movimento, sendo responsável por medir as posições relativas aos eixos X,Y e Z de um objeto, em outras palavras, o valor retornado por este sensor corresponde ao valor de deslocamento correspondente ao movimento sofrido pelo objeto. Portanto, este será o sensor utilizado no estudo de caso desenvolvido e apresentado neste artigo.

Existem basicamente três classes necessárias para interação com os sensores presentes em um dispositivo Android e que são disponibilizadas nas APIs para desenvolvimento:

  • SensorManager: permite o acesso aos sensores do dispositivos;
  • Sensor: representa um dos sensores propriamente dito;
  • SensorEvent: encapsula as informações de um evento ocasionado por um sensor. Por exemplo, no sensor de luz, quando a leitura indica uma alteração na luminosidade do ambiente, o sensor acusará um evento, cabe então ao programa trabalhar com os novos valores.

Iremos agora apresentar o LEGO Mindstorms. Esta será a plataforma que utilizaremos para o desenvolvimento de nosso estudo de caso.

LEGO Mindstorms

O LEGO Mindstorms é uma linha do brinquedo LEGO lançada comercialmente em 1998 voltada para a educação tecnológica e para o ensino básico de robótica (ver Figura 1).

Este ambiente é resultado de uma parceria entre o Media Lab do Massachusetts Institute of Technology (MIT) e o LEGO Group. O LEGO Mindstorms é constituído por um conjunto de peças da linha tradicional (peças de encaixe) e da linha LEGO Technic (motores, eixos, engrenagens, polias e correntes), acrescido de sensores de toque, de intensidade luminosa, de temperatura, entre outros. Todos os sensores são controlados por um processador programável, o módulo RCX (Robotic Command Explorer), sendo este o cérebro do robô.

LEGO Mindstroms NXT
Figura 1. LEGO Mindstroms NXT

Em 2006, o modelo RCX foi substituído com o lançamento do novo modelo NXT, o qual tem as seguintes características:

  • Processador Atmel32-bit ARM de 48MHz;
  • Três portas de saída digital;
  • Quatro portas de entrada (uma IEC 61158, tipo 4);
  • Display tipo matriz de 100x64 pixels;
  • Alto-falante;
  • Alimentado por pilhas de lítio, padrão “AA”;
  • Bluetooth;
  • Porta de Comunicação USB2.0;
  • Conjunto de três servo-motores interativos (com encoder acoplado);
  • Sensores de ultrassom, som, luz, cor, contato, entre outros;
  • Programa de computador intuitivo com uma versão LEGO do LabVIEW;
  • Compatível com PCs tradicionais com sistema operacional Windows e com MAC OS.

Estes conjuntos são utilizados com função didática em instituições de ensino tecnológico, abordando a teoria e a prática de conteúdos direcionados para a introdução à robótica, permitindo o desenvolvimento de projetos de pequeno e médio porte, estimulando a criatividade e a solução de problemas do quotidiano por parte dos alunos.

Para a comunicação bluetooth entre um dispositivo e o Mindstorms, deve-se respeitar um protocolo específico. Toda mensagem precisa iniciar por dois bytes, que indicam o tamanho da mensagem. Em seguida, vem a mensagem propriamente dita.

Neste artigo a mensagem usada para controlar os motores é indicada como SETOUTPUTSTATE e tem alguns bytes característicos, mostrados na lista a seguir:

  • Byte 0: Tipo de comando, geralmente usado com 0x80, para comando mestre;
  • Byte 1: Indica qual a mensagem, nesse caso 0x04 para SETOUTPUTSTATE ;
  • Byte 2: Indica a porta para a qual se destina o comando. Os motores estão geralmente nas portas 1 e 2;
  • Byte 3: Aqui será definida a potência aplicada nesta saída (-100 a 100);
  • Byte 4: Indica em que modo estará o motor (0x01 para ligado);
  • Byte 5: Tipo de regulação. Regulação deve ser ativada no Mode Byte, usando 0x04;
  • Byte 6: Controle interno do LEGO Mindstorms;
  • Byte 7: Usado para indicar se o motor deve continuar rodando.
  • Bytes 8 – 12: O tempo em que o motor deve rodar. 0x00 indica tempo ilimitado.

Partiremos agora para o desenvolvimento de nossa aplicação.

Sistema proposto

Para apresentar na prática a utilização de sensores na plataforma Android será utilizado um estudo de caso no qual o smartphone ou tablet terá a função de controlar remotamente o LEGO Mindstorms NXT, e conforme o usuário movimenta o “controle”, é enviado via Bluetooth ao robô um comando relacionado aos valores recebidos dos sensores.

A partir da inicialização do aplicativo, o usuário irá conectar o smartphone ou tablet ao NXT via comunicação bluetooth.

Estabelecida a comunicação, o aplicativo apresentará uma única tela que possui apenas uma barra vertical deslizante e uma imagem em gradiente, que percorre a tela conforme o usuário movimenta o aparelho. A Figura 2 apresenta a interface da aplicação.

O usuário poderá controlar a potência dos motores, deslizando a barra para cima, indicando que o robô deve ir em frente, ou para baixo, movimentando o robô na direção contrária. Assim, a barra vertical é o acelerador do NXT.

Tela de
controle do aplicativo
Figura 2. Tela de controle do aplicativo

Ao girar o dispositivo Android para a direita ou para a esquerda, a imagem em gradiente apresentada na tela (ponto em vermelho) irá acompanhar o movimento indicando que o robô será controlado para girar para o mesmo lado do movimento. Tudo na proporcionalidade de giro ou de potência aplicada pelo usuário na interface.

Na Figura 2 é possível ver que a barra de potência inicia posicionada no meio da tela, indicando que a potência aplicada inicialmente é 0, ou seja, o robô está em repouso. Assim, ao deslizar a barra para cima, a potência será modificada, e poderá alcançar no máximo 100. O círculo em gradiente, no meio da tela, deverá acompanhar a barra. Deslizando para baixo, o usuário estará indicando ao robô que ele deve ir para trás, com a mesma potência (no máximo -100).

No que diz respeito a interface gráfica, parte de seu desenvolvimento é demonstrado na Listagem 1.

<FrameLayout
android:id="@+id/frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_toLeftOf="@+id/seekbar"
android:background="#ff000000"
>
<ImageView
    android:id="@+id/image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:src="@drawable/posimg2"/>
</FrameLayout>
Listagem 1. Frame que contém a imagem em gradiente

O FrameLayout permite bloqueia uma parte da tela para exibir um único componente. Assim, é possível organizar os elementos da tela para o desenvolvimento do layout proposto pelo artigo. Nesse caso, a imagem é posicionada dentro do FrameLayout, e o atributo layout_toLeftOf, na linha 5 deixa o frame posicionado ao lado da seekbar, dando a impressão de que a imagem pertence a uma segunda tela ao lado da barra vertical.

Na linha 8, o ImageView é o componente que possibilita exibir uma imagem. A imagem em gradiente foi desenhada utilizando o software Corel DRAW Graphics Suite X6, conhecido por ser uma das mais poderosas ferramentas para criação de elementos gráficos. Para que essa imagem possa ser utilizada no aplicativo, ela deve ser colocada nas pastas /res/drawable no diretório do projeto. Assim, ela é referenciada pela linha 13.

A classe SeekBar é uma extensão da classe ProgressBar, a qual possibilita arrastar a barra para modificar o seu progresso. Ela é, por padrão, apresentada na tela horizontalmente e não possui nativamente um atributo que possibilite modificar.

O fragmento de código da Listagem 2 é adicionada ao arquivo XML da interface, juntamente com a Listagem 1.

  
<br.edu.utfpr.nxtcontrolv2.VerticalSeekBar
  android:id="@+id/seekbar"
  android:layout_width="wrap_content"
  android:layout_height="fill_parent"
  android:layout_alignParentRight="true"
/>
Listagem 2. Definindo o uso da barra vertical na interface

A Listagem 2 apresenta a declaração do uso da VerticalSeekBar, com atributos que são comuns também à classe SeekBar. É possível notar que os atributos usados (id, layout_width e layout_height) também estão presentes na classe original SeekBar. A linha 5 define que a barra deverá estar alinhada à direta da tela.

Criando um componente personalizado em Android

Como apresentado, não é possível posicionar a barra de progressos verticalmente. Para isso algumas adaptações foram necessárias. Inicialmente, é necessária a criação de uma nova classe, conforme código abaixo:

publicclass VerticalSeekBar extends SeekBar {}

Desse modo, a classe VerticalSeekBar será uma extensão da classe SeekBar. A partir dela, alguns métodos devem ser alterados para realizar os ajustes necessários para a exibição da barra com orientação vertical. Outros métodos, porém, continuarão sendo iguais ao da classe pai. A codificação da classe é apresentada na Listagem 3.

  
public VerticalSeekBar(Context context) {
  super(context);
}

public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
}

public VerticalSeekBar(Context context, AttributeSet attrs) {
  super(context, attrs);
}

publicvoid onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(h, w, oldh, oldw);
}
Listagem 3. Métodos da classe VerticalSeekBar que não possuem alteração

Entre os métodos que receberão modificações estão a onDraw, que é executado no momento em que o componente é adicionado à tela, e a onTouchEvent, executada quando o usuário clicar sobre o componente. Estes dois métodos, apresentados na Listagem 4, sobrescreverão o método da classe pai.

protectedvoid onDraw(Canvas c) {
  c.rotate(270);
  c.translate(-getHeight(), 0);
  
  super.onDraw(c);
}

@Override
publicboolean onTouchEvent(MotionEvent event) {
  if (!isEnabled()) {
  returnfalse;
}

switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
  case MotionEvent.ACTION_MOVE:
  case MotionEvent.ACTION_UP:
  setProgress(200 - (int) (getMax() * event.getY() / getHeight()));
  onSizeChanged(getWidth(), getHeight(), 0, 0);
  break;

  case MotionEvent.ACTION_CANCEL:
  break;
}
returntrue;
//return super.onTouchEvent(event);
}
Listagem 4. Métodos onDraw e onTouchEvent da classe VerticalSeekBar

No método onDraw está o principal motivo para a criação dessa nova classe. No momento em que o componente é instanciado e apresentado na tela, o método onDraw é executado. Nele utilizou-se dois métodos da classe Canvas: orotate, que altera a matriz de rotações com o valor em graus, nesse caso 270 graus, e o translate, que altera a matriz de translação com as posições indicadas por parâmetro.

Nesse ponto é necessário analisar as duas alteração. O eixo de rotação padrão do componente é o extremo esquerdo. Ao aplicar a rotação, em 270 graus, o componente deixará o seu espaço de visualização (Canvas), como mostra a Figura 3, e “desaparecerá” da tela. Por isso, é necessário também alterar a translação do componente. Como mostra a linha 3 da Listagem 4, a translação irá modificar somente a sua posição com relação à altura. O parâmetro –getHeight irá levar o componente para cima, deixando-o exatamente no local em que o canvas é apresentado.

Modo de rotação da barra
Figura 3. Modo de rotação da barra

O método onTouchEvent, iniciado na linha 9 da Listagem 4, fará a alteração no progresso da barra. Nesse caso, sobrescrever o método é necessário já que agora ele está apresentado verticalmente. Portanto, o atributo necessário para o cálculo do progresso atual é o event.getY().

Feitas as modificações, tem-se agora uma adaptação do SeekBar para apresentá-lo verticalmente. Nesse momento ele já pode ser instanciado pela classe MainActivity e utilizado.

Utilizando sensores

Entre as três classes de sensores apresentadas no início deste artigo, a primeira a ser usada será a SensorManager. Logo, o primeiro passo no trabalho foi criar uma instância dela na classe MainActivity.

Uma Activity é um simples objeto com foco, onde o usuário pode fazer algo. Quase todas as atividades (Activity) representam uma tela, podendo ser apresentadas de diversas maneiras: tela cheia, janela flutuante ou incorporadas dentro de outra Activity. A classe MainActivity é a representação da tela principal deste trabalho.

A partir do método getSystemService, a classe Activity permite acessar um serviço disponível no sistema operacional. Assim, fornecendo a flag SENSOR_SERVICE pode-se instanciar um novo SensorManager. Esse comando é dado pela seguinte linha:

mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

Depois, fez-se necessário definir o tipo de sensor usados no trabalho. Apesar da vasta quantidade de sensores disponíveis na classe Sensor, é preciso saber quais os sensores disponíveis no dispositivo onde o aplicativo será executado.

Como discutido na seção Sensores, o acelerômetro é o sensor indicado para este estudo de caso. Nesse caso, para termos acesso ao acelerômetro, basta incluir a linha a seguir no método onCreate da classe principal:

mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

A SensorManager possui um método chamado getDefaultSensor que retorna uma instância de Sensor. O método requer um parâmetro do tipo int, sendo que a classe já nos fornece um conjunto de constantes que define os vários tipos de sensores que são passíveis de tratamento com a Android API (ver Tabela 1).

Sensores disponíveis
Tabela 1. Sensores disponíveis

O próximo passo é declarar um listener para acompanhar as alterações no sensor. Para isso foi adicionada a seguinte linha:

mSensorManager.registerListener(this, mSensor,
  SensorManager.SENSOR_DELAY_NORMAL);

O registerListener requer três parâmetros:

  • Uma classe que implemente SensorEventListener;
  • Uma instância da classe Sensor;
  • Um inteiro que define a taxa de leitura de dados do sensor.

No terceiro parâmetro, em específico, é possível definir a taxa de atualização da leitura dos sensores. A classe SensorManager também possui algumas constantes para facilitar a definição da taxa ideal para cada aplicação. Estas constantes são:

  • SENSOR_DELAY_FASTEST: retorna os dados o mais rápido possível;
  • SENSOR_DELAY_GAME: utiliza uma taxa adequada para jogos;
  • SENSOR_DELAY_NORMAL: adequada para mudanças na orientação da tela;
  • SENSOR_DELAY_UI: taxa adequada para a interface de usuário.

Usou-se nesse artigo a flag SENSOR_DELAY_NORMAL já que, ao movimentar o smartphone ou tablet, as informações do sensor influenciarão na direção do robô. Essas informações vão passar por um tratamento e serão enviadas por bluetooth. Assim, o sensor não deve ser tão veloz a ponto de ser atualizado antes que a transmissão do evento anterior seja concluída.

Definida a velocidade de atualização, é necessário saber ainda que ao movimentar o tablet ou smartphone para a direita, o sensor será atualizado e seu valor terá uma resposta positiva para o eixo x. Para a esquerda: resposta negativa no eixo x. Para frente e para trás, as respostas devem ser alteradas no eixo y. Para cima ou para baixo o eixo z é que terá respostas diferentes. No entanto, neste artigo apenas o eixo x será considerado.

Tratando as mudanças de sensor e da barra de progressos

Instanciados os componentes da interface gráfica e de sensor, o próximo passo é trabalhar com as suas respectivas mudanças. Lembrando que a barra se refere à potência aplicada aos motores e o sensor de aceleração define a direção que o LEGO Mindstorms NXT deve seguir. A Listagem 5 mostra a implementação do método onProgressChanged da classe SeekBar.

 
publicvoid onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  // TODO Auto-generated method stub
  altura = frame.getHeight();
  MarginLayoutParams mlp = (MarginLayoutParams) image.getLayoutParams();
  left=mlp.leftMargin;
  right=mlp.rightMargin;
  top=-1*(int)(seekBar.getProgress()-100)*altura/200;
  bottom=mlp.bottomMargin;
  mlp.setMargins(left, top, right, bottom);
  image.setLayoutParams(mlp);
    
  if(lastevent> 10){
    lpower = (int)(seekbar.getProgress()-100);
    rpower = (int)(-(lastevent/100)*(seekbar.getProgress()-100)+(seekbar.getProgress()-100));
  }elseif(lastevent< -10){
    lastevent*=-1;
    lpower = (int)(-(lastevent/100)*(seekbar.getProgress()-100)+(seekbar.getProgress()-100));
    rpower = (int)(seekbar.getProgress()-100);
  }else{
    lpower = (int)(seekbar.getProgress()-100);
    rpower = (int)(seekbar.getProgress()-100);
  }

  motors(rpower, lpower);
}
Listagem 5. Implementação do método onProgressChanged

Sabendo que a barra controla a potência, a imagem deve acompanha-la. Então quando há uma modificação no valor da barra de progresso, a imagem deve mudar a sua altura, conforme o valor da barra. Para isso, na linha 4 da Listagem 5 o valor da altura do FrameLayout é atribuído à variável altura. Ela é usada para definir a altura em que a imagem deverá ser posicionada. A linha 8 indica a relação usada para posicionar corretamente a imagem sem que ela ultrapasse o tamanho da tela.

A classe MarginLayoutParams é uma classe especial para gerenciar os parâmetros de layout de um componente. Usando apenas seus métodos foi possível adequar a imagem na tela.

Entre as linhas 13e 26 tem-se os tratamentos para o comando dos motores. Esse fragmento de código também estará presente na Listagem 6 e só então será discutido.

publicvoidonSensorChanged(SensorEvent event) {
  float eixorotacao = (float)event.values[0];
  eixorotacao = (float)Math.toDegrees(eixorotacao);
  if(eixorotacao>100)eixorotacao=100;
  elseif(eixorotacao<-100)eixorotacao=-100;
    
  if(Math.abs(Math.abs(lastevent) - Math.abs(eixorotacao)) > 5){
    lastevent = eixorotacao;
    largura = frame.getWidth();
    MarginLayoutParams mlp = (MarginLayoutParams) image.getLayoutParams();
    left=-1*(int)(eixorotacao*largura/200);
    right=mlp.rightMargin;
    top=mlp.topMargin;
    bottom=mlp.bottomMargin;
    mlp.setMargins(left, top, right, bottom);
    image.setLayoutParams(mlp);
      if(lastevent> 10){
        lpower = (int)(seekbar.getProgress()-100);
        rpower = (int)(-(lastevent/100)*(seekbar.getProgress()-100)+(seekbar.getProgress()-100));
      }elseif(lastevent< -10){
        lastevent*=-1;
        lpower = (int)(-(lastevent/100)*(seekbar.getProgress()-100)+(seekbar.getProgress()-100));
        rpower = (int)(seekbar.getProgress()-100);
      }else{
        lpower = (int)(seekbar.getProgress()-100);
        rpower = (int)(seekbar.getProgress()-100);
      }
    motors(rpower, lpower);
  }
}
Listagem 6. Implementação do método onSensorChanged

Na linha 2 da Listagem 6 o valor atualizado da aceleração do eixo z é atribuído à variável eixorotacao. Em seguida, a variável largura recebe a largura total do Frame que contém a imagem. Sabendo que quando o dispositivo é movimentado o atributo direção deve mudar, a imagem em gradiente deve variar sua posição em relação à largura da tela. Na linha 12 está, portanto, a relação matemática utilizada para posicionar a imagem corretamente, sendo proporcional à largura da tela.

Essa relação será novamente um dos parâmetros usados no método que altera as posições da imagem assim como a altura foi modificada no método onProgressChanged da Listagem 5.

Tendo apresentado os métodos onProgressChanged e onSensorChanged, tem-se todos os dados necessários para entender o que as linhas 18 a 31 da Listagem 6 (iguais às linhas 13 a 26 da Listagem 5) implementam. Quando a variável lastevent for positiva, o LEGO Mindstorms NXT deve se movimentar para a direita, sempre proporcional ao progresso da barra. Nesse caso, o motor da esquerda deve ter mais potência que o motor da direita. A porcentagem fornecida pela angulação é diminuída da potência total e atribuída ao motor direito. Ainda, quando a angulação for negativa, a direção deve ir à esquerda. Desse modo, a mesma proporção da potência é diminuída do motor esquerdo. Se o ângulo estiver centralizado, o LEGO Mindstorms NXT deve andar em frente, isso indica que os dois motores devem ter a mesma potência.

O comando if da linha 7 aplica uma técnica conhecida por histerese. Isso faz com que os motores somente sejam atualizados com uma variação significante em relação à atual. Desse modo, o robô não ficará num estado de “ida e volta” e terá uma movimentação mais suave e precisa.

Definidas as potências de cada motor, elas são enviadas ao método motors, mostrado na Listagem 7.


publicvoid motors(int rmotor, int lmotor) {
  byte[] data = { 0x0c, 0x00, (byte) 0x80, 0x04, 0x02, 0x32, 0x07, 
    0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, (byte) 0x80, 
    0x04, 0x01, 0x32, 0x07, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00 };
  data[5] = (byte)lmotor;
  data[19] = (byte)rmotor;

  write(data);
}
Listagem 7. O método motors

No método da Listagem 7, as potências dos motores são adequadas ao tipo de mensagem que deve ser enviado ao robô via bluetooth. Esse protocolo já foi apresentado anteriormente na seção LEGO Mindstorms no início do artigo. Nas linhas 2 e 3 tem-se a fração do comando que representa o motor esquerdo. Por isso na linha 6, o byte 5 recebe a potência do motor esquerdo. As linhas 4 e 5 são a fração do comando referente ao motor direito. Na linha 9, o método write é chamado.

Enviando os dados para o robô via bluetooth

O método write implementa o envio de dados por bluetooth. Para isso, primeiramente é necessário declarar algumas instâncias para o uso do bluetooth. As declarações são mostradas pela Listagem 8.


privatestaticfinalintRECUPERA_DISPOSITIVO = 0;
publicstaticfinal UUID BTUUID = UUID.fromString("00001101-0000-1000-8000-
  00805F9B34FB");
private String nomeDispositivo;
private String enderecoDispositivo;

private BluetoothAdapter btAdapter;
private BluetoothDevice mmDevice;
private BluetoothSocket servidor;
Listagem 8. Declarações para o uso do Bluetooth

As linhas 7, 8 e 9 da Listagem 8 mostram a declaração dos objetos das classes BluetoothAdapter, BluetoothDevice, e BluetoothSocket. A primeira representa a interface com um dispositivo bluetooth. O BluetoothDevice contém os dados do dispositivo remoto, neste caso, o LEGO Mindstorms. E o terceiro representa o objeto que gerencia o envio e a recepção de dados.

Em seguida, é preciso recuperar o dispositivo bluetooth e habilitá-lo, caso ainda não esteja. A Listagem 9 demonstra os comandos necessários para essa execução.

btAdapter = BluetoothFactory.getBluetootAdapter();

if ( ! btAdapter.isEnabled() ) {
    Intent intent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE );
    startActivityForResult( intent, 2 );
}

Listagem 9. Habilitando o bluetooth

O próximo passo é conectar ao NXT. O BTUUID, declarado na linha 2 da Listagem 8, é importante nesta etapa, pois ele identifica o serviço presente nos LEGO Mindstorms para seu controle remoto via bluetooth. O método connect irá chamar outra Activity de lista, onde será feita a escolha do dispositivo (o robô LEGO que será controlado). Após isso, no retorno do Activity de lista (no método onActivityResult) o método conectarDispositivo tentará fazer a conexão. As implementações desses métodos são mostradas na Listagem 10.

publicvoid connect(){
  Intent i = new Intent(MainActivity.this, EscolhaDispositivo.class);
  startActivityForResult(i, RECUPERA_DISPOSITIVO);
}

publicvoid onActivityResult(int requestCod, int resultCode, Intent data){
    if ( requestCod == RECUPERA_DISPOSITIVO ){
      nomeDispositivo = data.getExtras().getString("nome");
      enderecoDispositivo = data.getExtras().getString("endereco");   
    }
  
    if (conectarDispositivo(enderecoDispositivo)){
          Toast.makeText( this, "Conectado com "+nomeDispositivo+"!", 
          Toast.LENGTH_SHORT ).show();            
    }
    else{
      Toast.makeText( this, "Não foi possível estabelecer conexão!", 
      Toast.LENGTH_SHORT ).show();
      finish();
    }

}

privateboolean conectarDispositivo(String enderecoDispositivo) {
  mmDevice = btAdapter.getRemoteDevice(enderecoDispositivo);
  BluetoothSocket tmp;
  
  try{
    tmp = mmDevice.createRfcommSocketToServiceRecord(BTUUID);
  }catch(Exception e){
    returnfalse;
  }
  
  servidor = tmp;       
  btAdapter.cancelDiscovery();
  
  try{
    servidor.connect();
  }catch(IOException connectExcption){
    try{
      servidor.close();
  }catch(IOException closException){
      returnfalse;
  }
}
  
returntrue;
}
Listagem 10. Conectando com o robô

A escolha do dispositivo, feito em outra Activity, é retornada ao método onActivityResult, da linha 6 da Listagem 10. A partir deste ponto, é chamado o método conectarDispositivo que, na linha 25, recupera o dispositivo remoto. Na linha 29, o método tenta criar uma conexão RFCOMM entre os dois dispositivos utilizando o UUID declarado inicialmente. Depois, o método tenta efetuar a conexão. Quando a conexão for estabelecida com sucesso, o servidor está pronto para enviar e receber os dados.

Agora teremos o método write, que é usado para enviar os dados ao robô pode ser apresentado. A Listagem 11 demonstra a implementação do método.


publicvoid write(byte[] dado){
  try {
    Thread.sleep(10);
  } catch (InterruptedException e1) {
    Log.i("SLEEP", "Erro no sleep");
  }

  OutputStream saida;
  try{
    saida = servidor.getOutputStream();
    saida.write(dado);
    saida.flush();          
  }catch(Exception e){
    Log.i("WRITE", "Erro no write");
    try {
      servidor.close();
    } catch (IOException e1) {
      // TODO Auto-generated catch block
      e1.printStackTrace();
    }
    finish();
  }
}
Listagem 11. O método write

Na linha 8 é criado o objeto saída da classe OutputStream. Esse objeto servirá como um canal de saída de bytes para o bluetooth, como mostra a linha 10, onde é recuperado o canal de saída bluetooth do servidor. Aí então o comando é enviado.

Conclusão

Este artigo apresentou uma das inúmeras aplicações relacionadas ao uso de sensores em aplicativos Android e da comunicação com dispositivos remotos utilizando bluetooth. A solução apresentada ainda se torna divertida, já que permite controlar um robô usando o dispositivo Android como se fosse um controle remoto. O uso do LEGO Mindstorms é essencial no ensino didático da robótica em muitos países, sendo hoje muito utilizado nas universidades brasileiras.

Apesar da complexidade do aplicativo desenvolvido, o presente artigo apresentou os conceitos de forma didática, e abordou inúmeros recursos, como a criação de um componente customizável, a personalização em tempo de execução de desenhos na tela do dispositivo, a utilização de sensores, a comunicação bluetooth, além do uso do protocolo fornecido pelo LEGO Mindstorms para o controle dos robôs via bluetooth.

Da maneira em que foi codificado o aplicativo Android, este conecta-se diretamente na plataforma Mindstorms por meio do protocolo fornecido pela LEGO. Entretanto, uma maneira alternativa de enviar dados é desenvolver um programa servidor para o LEGO Mindstorms. Este utilizaria a linguagem LeJOS e enviaríamos para ele os dados via bluetooth.

Links:

Confira também