Avançar para o conteúdo principal

i-Racer

Neste post vamos criar um programa que permite controlar um i-Racer com um dispositivo Android através do Bluetooth.
Para quem ainda não conhece este pequeno carro fica aqui um vídeo.




O i-Racer responde a um conjunto limitado de comandos em hexadecimal cujo significado pode ser encontrado aqui.

O objetivo é demonstrar o funcionamento do envio dos comandos através do Bluetooth, para isso vamos implementar quatro comandos: andar para a frente, andar para trás, virar rodas para direita e virar rodas para a esquerda. Os restantes comandos são igualmente fáceis de implementar basta alterar os códigos enviados ao i-Racer.

Para começar a interface.
O design:
Em execução:
Resumindo:
- oito botões;
- duas listviews, uma onde estão os botões a outra para apresentar os dispositivos Bluetooth emparelhados e detetados.

O xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="false"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:layout_marginTop="4dp"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:onClick="onLigarClick"
            android:text="@string/ligar" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onProcurarClick"
            android:text="@string/procurar" />
        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onAbrirClick"
            android:text="@string/abrir" />
        </LinearLayout>
    <LinearLayout
        android:id="@+id/linearLayout4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="false"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:layout_marginTop="58dp"
        android:orientation="horizontal" >
    
    <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onFrenteClick"
            android:text="@string/frente" />
       <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onPararClick"
            android:text="@string/parar" />
        
       <Button
            android:id="@+id/button6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onEsquerdaClick"
            android:text="@string/esquerda" />
       <Button
            android:id="@+id/button7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onDireitaClick"
            android:text="@string/direita" />
<Button
            android:id="@+id/button8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onSairClick"
            android:text="@string/sair" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/linearLayout1"
        android:layout_marginLeft="0dp"
        android:layout_marginTop="82dp"
        android:orientation="vertical" >

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

    </ListView>
</LinearLayout>
</RelativeLayout>


Agora uma classe para controlar as comunicações. Os tutoriais da google sobre o Bluetooth do Android são muito completos mas também algo complexos, ainda que isso seja normal quando estamos a criar programas de comunicação entre dispositivos.

Para este pequeno dispositivo nós só temos de enviar os comandos não temos de receber, assim a classe criada permite abrir um canal de comunicação, estabelecer uma ligação, enviar dados, devolver o estado da ligação e terminar a ligação.

O código é algo complexo mas está comentado e envia mensagens para o Log de modo que a que seja mais fácil fazer o debug da aplicação.


package edu.pjcferreira.btremote;

import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

public class ConnectThread extends Thread{
private BluetoothAdapter mBluetoothAdapter;//dispositivo local
private BluetoothSocket mmSocket;
private BluetoothDevice mmDevice; //dispositivo remoto
    private static final UUID MY_UUID =
            UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //para ligar a uma board serial tem que ser com este UUID
    private int estado;
    private OutputStream mmOutStream=null;
    
    //recebe o dispositivo remoto ao qual ligar
public ConnectThread(BluetoothDevice device){
BluetoothSocket tmp=null;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mmDevice=device;
estado=-1;
try{
Log.d("BT","Abre o canal de ligação");
tmp=mmDevice.createRfcommSocketToServiceRecord(MY_UUID);
mmSocket=tmp;
estado=0;
Log.d("BT","Canal aberto!");
}catch(IOException e){
estado=-1;
}
}
//método para enviar os comandos
    public void enviar(byte x){
    if (mmOutStream==null){
    try{
    mmOutStream=mmSocket.getOutputStream();
    }catch(IOException e){
    return;
    }
    }
    try{
    mmOutStream.write(x);
    }catch(IOException ee){
    return;
    }
    Log.d("BT","ENVIADO COM SUCESSO!");
    }
    //função que devolve o estado da ligação
    public String getEstado(){
    return Integer.toString(estado);
    }
//estabelece a ligação
public void run(){
mBluetoothAdapter.cancelDiscovery();

try{
mmSocket.connect();
Log.d("BT","LIGADO");
estado=1;
}catch(IOException connectException){
try{
mmSocket.close();
Log.d("BT","ERRO A LIGAR");
estado=-1;
}catch(IOException closeException){}
}
return;
}
//termina a ligação
public void cancel(){
try{
mmSocket.close();
estado=-1;
}catch(IOException e){}
}
}

Agora com esta classe podemos iniciar a comunicação mas primeiro precisamos de verificar se o dispositivo tem Bluetooth, isso é feito na função onCreate que se apresenta a seguir.
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //verifica se existe bt neste dispositivo
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter==null){
        showMessage("Não tem suporte para bluetooth!");
        }else{
        showMessage("Tem suporte para bluetooth!");
        }
        //lista de dispositivos
        mArrayAdapter = new ArrayAdapter<String>(this,R.layout.simplerow,0);
        //registar a função que vai receber os dispositivos encontrados
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        //tambem recebe a mensagem que terminou a pesquisa
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        this.registerReceiver(mReceiver, filter);
        //lista de dispositivos
        listView = (ListView) findViewById(R.id.listView1);
    }

Esta função também regista uma função callback para que quando os dispositivos Bluetooth são encontrados poderem ser adicionados à listview. O código dessa função é este:
    private final BroadcastReceiver mReceiver = new BroadcastReceiver(){
    public void onReceive(Context context, Intent intent){
    String action = intent.getAction();
    if (BluetoothDevice.ACTION_FOUND.equals(action)){
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    showMessage(device.getName());
    }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                // showMessage("Terminado! Encontrados " + mArrayAdapter.getCount() + " dispositivos!");
                listView.setAdapter(mArrayAdapter);
                listView.setClickable(true);
                //função a chamar quando clicar num item da lista
                listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                 public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
                   Object o = listView.getItemAtPosition(position);
                   showMessage("Selecionou " + o.toString());
                   //tenta ligar ao dispositivo
                   dispositivo=position;
                   String[] endereco=o.toString().split("\n");
                  /* showMessage(endereco[0]);
                   showMessage(endereco[1]);*/
                   remoteBluetoothDevice=  mBluetoothAdapter.getRemoteDevice(endereco[1]);
                 }
                });
            }
    }
    };

De seguida precisamos de uma função para pesquisar dispositivos Bluetooth, quer os que estão emparelhados quer outros:
    public void onProcurarClick(View botton) {
    //primeiro vamos procurar dispositivos emparelhados
    pairedDevices=mBluetoothAdapter.getBondedDevices();
    //se existem
    if(pairedDevices.size()>0){
    for(BluetoothDevice device : pairedDevices){
    mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    showMessage(device.getName());
    }
    }
    //procurar novos dispositivos
      mBluetoothAdapter.startDiscovery();
    }

Posto isto podemos abrir uma ligação com o dispositivo selecionado:
    public void onAbrirClick(View botton) {
    if(remoteBluetoothDevice==null){
    showMessage("Primeiro deve selecionar o dispositivo!");
    return;
    }
    ligacao=new ConnectThread(remoteBluetoothDevice);
   ligacao.run();
   showMessage(ligacao.getEstado());
    
    }

Agora podemos enviar os comandos, para isso temos uma função para cada comando:
    public void onFrenteClick(View botton) {
    byte x;
    x=0x1B;
    if(ligacao==null){
    showMessage("Primeiro tem de abrir uma ligação.");
    return;
    }
    ligacao.enviar(x);
    }
    public void onEsquerdaClick(View botton) {
    byte x;
    x=0x36;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
    ligacao.enviar(x);
    }
    public void onDireitaClick(View botton) {
    byte x;
    x=0x46;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
    ligacao.enviar(x);
    }
    public void onPararClick(View botton){
    byte x;
    x=0x00;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
    ligacao.enviar(x);
    }

Antes de terminarmos precisamos de limpar a casa, o botão sair e ainda a função onDestroy:
    public void onSairClick(View botton) {
    byte x;
    x=0x00;
if(ligacao!=null){
ligacao.enviar(x);
ligacao.cancel();
}
    finish();
    }
    protected void onDestroy() {
    byte x;

    x=0x00;
    if(ligacao!=null){
    ligacao.enviar(x);
    ligacao.cancel();
    }
       this.unregisterReceiver(mReceiver);
    super.onDestroy();
    }

Quase me esquecia as permissões, neste projeto precisamos de duas:
    - Permissão para utilizar Bluetooth:
<uses-permission android:name="android.permission.BLUETOOTH"/>
    - Permissão para pesquisar dispositivos com o Bluetooth:
<uses-permission  android:name="android.permission.BLUETOOTH_ADMIN"/>

E pronto temos um programa que permite controlar o pequeno i-Racer. Como sempre o projeto e aplicação    estão disponíveis para download.

Comentários

Mensagens populares deste blogue

Upgrade do Windows Home para Pro sem formatar

 Há algum tempo que tentava fazer o upgrade do meu Windows 10 da versão Home para a versão Pro, mas chegava sempre a um ponto em que me era solicitado para formatar o sistema e não estava para isso. Finalmente conseguinte seguindo estes passos: - seguinte estes passos  utilizei uma das chaves genéricas para o Windows 10 Pro e fui a Settings > Update & Security > Activation > Change the product key; - após inserir uma das chaves o Windows instala as funcionalidades Pro e pede para reiniciar; - agora tem o Windows Pro mas não está ativado, assim fui ao site urcdkeys  onde comprei uma chave para o Windows Pro por menos de €20; - com essa chave voltei a funcionalidade Change the product key e ativei o Windows; - e pronto, Windows Pro ativado sem formatar ou reinstalar. Importante : eu não tenho nada a ver com o site urcdkeys por isso a vossa experiência pode correr de forma diferente da minha.

PONG em Flash AS3.0

Mais um pequeno jogo para demonstrar algumas das funcionalidades do AS3.0. Para este exemplo vamos implementar uma versão do Pong. Para este Pong vamos criar a possibilidade de acelerar a bola com a raqueta e, para tornar o jogo mais difícil, quando se atingir uma determinada pontuação fazemos aparecer uma parede no meio do campo de jogo. O código é muito parecido com o jogo do post anterior, mas um pouco mais complicado. Para controlar a nossa raqueta utilizamos a seguinte função: function teclado(e:KeyboardEvent):void{ dir_j1=0; if (e.keyCode == Keyboard.UP){ if(jogador1.y>0) jogador1.y -=5; dir_j1=-5; } if (e.keyCode == Keyboard.DOWN){ if(jogador1.y<370) jogador1.y +=5; dir_j1=5; } } Agora está mais simples pois só percorremos as linhas, ou seja, a coluna nunca muda. A raqueta que é controlada pelo computador depende do seguinte código: function movepc():void { if (bola.y>jogador2.y) jogador2.y = jogador2.y + velocidade_y; if (bola.y<jogador2.y) joga...

Vamos fazer um carro com o Unity 3D

Neste artigo vamos fazer um carro, simples, com o Unity 3D. A ideia é utilizar o motor de física do Unity 3D para simular o comportamento do carro. Os passos a seguir são: [1] - Criar um projeto novo