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.

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

Tem troco

Para hoje um pequeno programa que dá troco, bem dar não dá mas calcula o troco a dar em função das moedas disponíveis. Neste projeto vamos utilizar o novo Visual Studio 2012. Como era de se esperar vamos iniciar um projeto novo: Agora adicionamos os seguintes elementos:  - um botão para calcular as moedas a dar de troco  - um botão para repor o número de moedas iniciais disponíveis  - uma textbox para introduzir o valor a pagar  - uma textbox para introduzir o valor entregue  - umas labels para informar o utilizador do que deve introduzir e outra para mostrar o troco  - por fim uma grelha para mostrar os valores das moedas e as quantidades disponíveis de cada uma. A janela principal do programa fica assim: Agora o código, primeiro o evento load do formulário, neste vamos definir os valores das moedas e as respetivas quantidades Para guardar estes valores vamos necessitar de uma variável definida ao nível do formulário, logo abaixo da definição da class: Public Class Form1     Public mo