Quantcast
Viewing all articles
Browse latest Browse all 24

Comunicação Bluetooth entre Arduino e Android

Depois de quase um século, estamos aqui voltando a falar sobre comunicação Bluetooth. Agora, entre Arduino e Android. A realização desse post foi fortemente influenciada pelos comentários no post sobre programação Bluetooth entre dois dispositivos Android. Prontos? Vamos!

Para mostrar como se faz a conexão e comunicação entre um aparelho Android e um Arduino, vamos desenvolver um contador. Algo bem simples. O app Android terá apenas uma tela, nesta tela um TextView para exibir o valor do contador e outro para exibir o status da conexão. O valor do contador exibido será calculado pelo Arduino e transmitido ao Android, que é apenas responsável por exibir o número (assim demonstramos transmissões Arduino -> Android). Se o usuário clicar na tela, o Android envia um comando para que o contador seja reiniciado (assim demonstramos transmissões Android -> Arduino). Que acham? Bem simples e faz sentido, não?

Módulo Bluetooth

A maioria dos Arduinos não possui um módulo Bluetooth embutido (com exceção de alguns como ArduinoBT). Logo, é necessário um módulo de comunicação Bluetooth externo. Alguns exemplos são os módulos HC-05, HC-06, HC-07 e HC-08. Não vamos explorar muito as diferenças entre esses módulos. Mas algumas informações são úteis. Uma das principais diferenças entre o HC-05 e o HC-06 é que o HC-05 pode atuar como master ou slave, enquanto o HC-06 atua apenas como slave. Isso implica que o HC-06 não é capaz de iniciar uma conexão com outro dispositivo, ele apenas pode aceitar ou negar quando outro dispositivo tenta iniciar uma conexão. Mas após a conexão ser estabelecida, a comunicação de dados ocorre em duas vias, tanto do módulo HC-06 para o outro aparelho quanto do aparelho para o HC-06. Neste tutorial, vamos usar o módulo HC-06 para realizar a comunicação.

O módulo HC-06 possui quatro pinos: VCC e GND para alimentação do módulo. O pino VCC pode ser conectado diretamente à saída de 5V do Arduino, já que suporta tensões entre 3.3V e 6V. Os outros dois, TXD e RXD são os pinos de escrita e leitura do módulo. Estes serão utilizados para comunicação serial entre o HC-06 e o Arduino.

 

Image may be NSFW.
Clik here to view.
DSCN4196
HC-06 Visão frontal
Image may be NSFW.
Clik here to view.
DSCN4198
HC-06 Visão Traseira

Você deve estar se perguntando como o módulo Bluetooth se integra ao Arduino e como os dados vão sair do Arduino e chegar no Android e vice-versa. É o que veremos.

 

Arduino

A integração entre o Arduino e o HC-06 é bem simples. Todas as informações que o Arduino precisar transmitir ao Android deverão passar antes pelo módulo Bluetooth HC-06 e vice versa. A comunicação entre Arduino e HC-06 ocorre via serial. Então, tecnicamente, bastaria conectar os pinos seriais (RX, TX) do Arduino aos pinos seriais (TXD, RXD) do HC-06 e usar as funções da biblioteca Serial para ler e escrever caracteres. Os caracteres que chegassem à porta serial RX do Arduino correspondem aos dados que o Android está enviando. Os caracteres escritos através da porta serial TX do Arduino seriam enviados ao Android pelo módulo Bluetooth. Mas encontrei problemas ao tentar utilizar os pinos RX, TX padrões do Arduino. Apesar de obter sucesso realizando a conexão, as informações simplesmente não fluíam entre os lados da conexão.

Para resolver esse problema, usei a biblioteca SoftwareSerial. O que essa biblioteca faz é simular as portas seriais RX, TX utilizando os pinos digitais. Isso é maravilhoso! Então, em vez de conectar (RXD,TXD) do módulo HC-06 a (TX,RX) do Arduino, podemos escolher dois pinos digitais para simular as porta seriais e fazer a mesma conexão. Aqui, escolhi de forma arbitrária os pinos 8 e 9 para RX e TX, respectivamente. Um detalhe que pode vir a ser bem importante: o módulo HC-06 trabalha com níveis lógicos de tensão de 3.3V. Isso significa que os pinos RXD do HC-06 só estão preparados para trabalhar em torno de 3.3V, enquanto o Arduino estará oferecendo 5V na saída do pino 9 (que estamos usando como TX). Para diminuir a chance de danificar o módulo Bluetooth, é bem recomendado que você diminua a tensão de 5V para 3.3V. Façamos isso com um divisor de tensão, que tal? Dessa maneira, a saída de 5V do pino digital do Arduino chega ao pino RXD do módulo Bluetooth como 3.3V e não corremos risco de fritar o senhor HC-06. Mostramos esse divisor de tensão no esquemático lá embaixo, relaxa.

Feitas essas conexões, o resto é alimentar o módulo Bluetooth com 5V e pronto. Veja o esquemático, é tranquilo. Leia o código e os comentários, é favorável.

#include <SoftwareSerial.h>

/* Definição de um objeto SoftwareSerial.
 * Usaremos os pinos 8 e 9, como RX e TX, respectivamente.
 */
SoftwareSerial serial(8, 9);

/* A String data será utilizada para armazenar dados vindos
 *  do Android. O inteiro counter será incrementado a cada
 *  execução do loop principal e transmitido ao Android.
 *  O led conectado ao pino 2 é mais para debug. É útil.
 */
String data = "";
int counter = 0;
int led = 2;

/* Nosso setup inclui a inicialização do objeto SoftwareSerial,
 *  com uma baud rate de 9600 bps. A definição do pino do led
 *  como saída e um delay de 2 segundos, só para garantir que
 *  o módulo HC-06 iniciou direitinho.
 */
void setup() {
  serial.begin(9600);
  pinMode(led, OUTPUT);
  delay(2000);
}

/* Vamos pelo loop passo a passo.
 */
void loop() {
  /* No início de cada loop, verificamos se há algo no buffer
   *  da nossa serial. Se houver bytes disponíveis, significa 
   *  que o Android enviou algo, então faremos a leitura do 
   *  novo caractere e adicionamos ao final da string data.
   */
  while(serial.available() > 0) {
    data += char(serial.read());
  }

  /* Se o Arduino receber a string "restart" seguida de uma
   *  quebra de linha, reinicializamos o contador e ligamos
   *  o led por um segundo. Esse comando indicará que a 
   *  comunicação no sentido Android -> Arduino está sendo 
   *  realizada corretamente.
   */
  if(data == "restart\n") {
    digitalWrite(led, HIGH);
    counter = 0;
    delay(1000);
    digitalWrite(led, LOW);
  }

  /* Ao fim de cada loop, o Arduino transmite uma string
   *  representando o valor armazenado no contador, seguido
   *  de uma quebra de linha. Essa string será enviada para o
   *  módulo HC-06 e daí para o Android.
   */
  serial.print(String(counter));
  serial.print('\n');

  /* Finalmente, incrementamos o contador e limpamos data.
   */
  counter = counter + 1;
  data = "";

  /* Um pequeno delay para evitar bugs estranhos.
   */
  delay(10);
}

Image may be NSFW.
Clik here to view.
Esquemático só para ficar bonito. Note o resistor de 330Ω conectado ao LED. Perceba também o divisor de tensão (feito com resistores de 1KΩ) conectado ao pino RXD do módulo HC-06.

Image may be NSFW.
Clik here to view.
arduino+hc06
Usei Arduino Pro Micro, mas a ideia é a mesma para outros Arduinos, sério. Só lembre de acertar o modelo na hora de programar.

 

Android

Permissões

Precisamos obter permissão para utilizar o dispositivo Bluetooth do Android. Fazemos isso adicionando essas linhas ao Manifest.xml, dentro da tag <manifest>. Faça isso.

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

Design

Já comentamos como seria nossa interface gráfica no começo do post, lembra? Uma tela, um TextView para o contador, um TextView para mensagem de status da conexão. Abaixo estão o arquivo XML que usei e um print de como deve ficar a tela do app. Modifique o visual como preferir. Sei lá, coloque no plano de fundo uma selfie sua.

<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"
    android:paddingLeft="4dp"
    android:paddingRight="4dp"
    android:paddingTop="4dp"
    android:paddingBottom="4dp"
    tools:context=".MainActivity"
    android:background="#ff6eb6ff"
    android:onClick="restartCounter">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="DRAGÃO"
        android:id="@+id/counterMessage"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:textSize="@dimen/abc_text_size_display_3_material"
        android:textColor="#ffffffff"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello there!"
        android:id="@+id/statusMessage"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:textColor="#ffffffff"/>
</RelativeLayout>

Aproveite, procure o arquivo styles.xml, que provavelmente estará no caminho app/src/main/res/values/styles.xml e deixe-o assim:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
    </style>

</resources>

Isso serve para remover aquela barra superior com o nome do aplicativo. Faz com que o design fique mais limpo, mais bonito.

 

Image may be NSFW.
Clik here to view.
Screenshot_20160409-224157
Algo similar a isso deverá aparecer na tela principal. Quando o Arduino começar a transmitir dados, o valor do contador aparecerá no lugar de “DRAGÃO”.

Programação

Classe ConnectionThread

Se você leu Programação Bluetooth no Android, sabe que criamos uma classe, subclasse de Thread, para gerenciar a conexão Bluetooth. Faremos o mesmo aqui. Não precisamos reescrever essa classe, pois já fizemos isso. É claro, mesmo que já tenhamos a classe ConnectionThread, isso não nos impede de fazer alguns aprimoramentos. Então, o código abaixo é uma versão levemente aprimorada da ConnectionThread que fizemos no primeiro post sobre Bluetooth.

package br.com.dragaosemchama.supercounter;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Message;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.UUID;

public class ConnectionThread extends Thread{

    BluetoothSocket btSocket = null;
    BluetoothServerSocket btServerSocket = null;
    InputStream input = null;
    OutputStream output = null;
    String btDevAddress = null;
    String myUUID = "00001101-0000-1000-8000-00805F9B34FB";
    boolean server;
    boolean running = false;
    boolean isConnected = false;

    /*  Este construtor prepara o dispositivo para atuar como servidor.
     */
    public ConnectionThread() {

        this.server = true;
    }

    /*  Este construtor prepara o dispositivo para atuar como cliente.
        Tem como argumento uma string contendo o endereço MAC do dispositivo
    Bluetooth para o qual deve ser solicitada uma conexão.
     */
    public ConnectionThread(String btDevAddress) {

        this.server = false;
        this.btDevAddress = btDevAddress;
    }

    /*  O método run() contem as instruções que serão efetivamente realizadas
    em uma nova thread.
     */
    public void run() {

        /*  Anuncia que a thread está sendo executada.
            Pega uma referência para o adaptador Bluetooth padrão.
         */
        this.running = true;
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();

        /*  Determina que ações executar dependendo se a thread está configurada
        para atuar como servidor ou cliente.
         */
        if(this.server) {

            /*  Servidor.
             */
            try {

                /*  Cria um socket de servidor Bluetooth.
                    O socket servidor será usado apenas para iniciar a conexão.
                    Permanece em estado de espera até que algum cliente
                estabeleça uma conexão.
                 */
                btServerSocket = btAdapter.listenUsingRfcommWithServiceRecord("Super Counter", UUID.fromString(myUUID));
                btSocket = btServerSocket.accept();

                /*  Se a conexão foi estabelecida corretamente, o socket
                servidor pode ser liberado.
                 */
                if(btSocket != null) {

                    btServerSocket.close();
                }

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }


        } else {

            /*  Cliente.
             */
            try {

                /*  Obtem uma representação do dispositivo Bluetooth com
                endereço btDevAddress.
                    Cria um socket Bluetooth.
                 */
                BluetoothDevice btDevice = btAdapter.getRemoteDevice(btDevAddress);
                btSocket = btDevice.createRfcommSocketToServiceRecord(UUID.fromString(myUUID));

                /*  Envia ao sistema um comando para cancelar qualquer processo
                de descoberta em execução.
                 */
                btAdapter.cancelDiscovery();

                /*  Solicita uma conexão ao dispositivo cujo endereço é
                btDevAddress.
                    Permanece em estado de espera até que a conexão seja
                estabelecida.
                 */
                if (btSocket != null) {
                    btSocket.connect();
                }

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
            }

        }

        /*  Pronto, estamos conectados! Agora, só precisamos gerenciar a conexão.
            ...
         */

        if(btSocket != null) {

            /*  Envia um código para a Activity principal informando que a
            a conexão ocorreu com sucesso.
             */
            this.isConnected = true;
            toMainActivity("---S".getBytes());

            try {

                /*  Obtem referências para os fluxos de entrada e saída do
                socket Bluetooth.
                 */
                input = btSocket.getInputStream();
                output = btSocket.getOutputStream();

                /*  Permanece em estado de espera até que uma mensagem seja
                recebida.
                    Armazena a mensagem recebida no buffer.
                    Envia a mensagem recebida para a Activity principal, do
                primeiro ao último byte lido.
                    Esta thread permanecerá em estado de escuta até que
                a variável running assuma o valor false.
                 */
                while(running) {

                    /*  Cria um byte array para armazenar temporariamente uma
                    mensagem recebida.
                        O inteiro bytes representará o número de bytes lidos na
                    última transmissão recebida.
                        O inteiro bytesRead representa o número total de bytes
                    lidos antes de uma quebra de linha. A quebra de linha
                    representa o fim da mensagem.
                     */
                    byte[] buffer = new byte[1024];
                    int bytes;
                    int bytesRead = -1;

                    /*  Lê os bytes recebidos e os armazena no buffer até que
                    uma quebra de linha seja identificada. Nesse ponto, assumimos
                    que a mensagem foi transmitida por completo.
                     */
                    do {
                        bytes = input.read(buffer, bytesRead+1, 1);
                        bytesRead+=bytes;
                    } while(buffer[bytesRead] != '\n');

                    /*  A mensagem recebida é enviada para a Activity principal.
                     */
                    toMainActivity(Arrays.copyOfRange(buffer, 0, bytesRead-1));

                }

            } catch (IOException e) {

                /*  Caso ocorra alguma exceção, exibe o stack trace para debug.
                    Envia um código para a Activity principal, informando que
                a conexão falhou.
                 */
                e.printStackTrace();
                toMainActivity("---N".getBytes());
                this.isConnected = false;
            }
        }

    }

    /*  Utiliza um handler para enviar um byte array à Activity principal.
        O byte array é encapsulado em um Bundle e posteriormente em uma Message
    antes de ser enviado.
     */
    private void toMainActivity(byte[] data) {

        Message message = new Message();
        Bundle bundle = new Bundle();
        bundle.putByteArray("data", data);
        message.setData(bundle);
        MainActivity.handler.sendMessage(message);
    }

    /*  Método utilizado pela Activity principal para transmitir uma mensagem ao
     outro lado da conexão.
        A mensagem deve ser representada por um byte array.
     */
    public void write(byte[] data) {

        if(output != null) {
            try {

                /*  Transmite a mensagem.
                 */
                output.write(data);

            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {

            /*  Envia à Activity principal um código de erro durante a conexão.
             */
            toMainActivity("---N".getBytes());
        }
    }

    /*  Método utilizado pela Activity principal para encerrar a conexão
     */
    public void cancel() {

        try {

            running = false;
            this.isConnected = false;
            btServerSocket.close();
            btSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
        running = false;
        this.isConnected = false;
    }

    public boolean isConnected() {
        return this.isConnected;
    }
}

As alterações feitas são (1) adição da variável booleana isConnected, que indica exatamente o que o nome diz: a thread Bluetooth está conectada ou não? e (2) para garantir que as mensagens transmitidas do Arduino para o Android não cheguem quebradas, leremos byte a byte o que for recebido no Android e armazenaremos em um buffer. Quando, enfim, recebermos um caractere ‘\n’, uma quebra de linha, então assumimos que a mensagem completa foi recebida e trabalhamos com ela. Você pode ver essa alteração no loop while localizado entre as linhas 159 e 186 da classe ConnectionThread.

Ótimo. Adicione essa classe ao projeto e siga em frente.

Activity Principal

Já que nosso único objetivo é conectar o Android a um módulo Bluetooth que provavelmente já conhecemos o endereço, não haverá aquela complicação toda de realizar busca por dispositivos, etc. Já sabemos o endereço do nosso módulo HC-06. Você já sabe? É bom saber, pois vamos precisar dele.

Então, de forma sucinta, o que faremos na Activity principal é ativar o hardware Bluetooth, iniciar uma conexão com um dispositivo pré-definido e atualizar os TextView relativos ao contador e ao status da conexão.

Veja abaixo o código que utilizei para a Activity Principal. Eu poderia explicá-lo novamente, mas como adicionei comentários em praticamente todos os trechos do código, acredito que será tranquilo acompanhá-lo e entendê-lo. Você consegue! Que a força esteja com você.

package br.com.dragaosemchama.supercounter;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {

    /* Definição dos objetos que serão usados na Activity Principal
        statusMessage mostrará mensagens de status sobre a conexão
        counterMessage mostrará o valor do contador como recebido do Arduino
        connect é a thread de gerenciamento da conexão Bluetooth
     */
    static TextView statusMessage;
    static TextView counterMessage;
    ConnectionThread connect;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /* Link entre os elementos da interface gráfica e suas
            representações em Java.
         */
        statusMessage = (TextView) findViewById(R.id.statusMessage);
        counterMessage = (TextView) findViewById(R.id.counterMessage);

        /* Teste rápido. O hardware Bluetooth do dispositivo Android
            está funcionando ou está bugado de forma misteriosa?
            Será que existe, pelo menos? Provavelmente existe.
         */
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        if (btAdapter == null) {
            statusMessage.setText("Que pena! Hardware Bluetooth não está funcionando :(");
        } else {
            statusMessage.setText("Ótimo! Hardware Bluetooth está funcionando :)");
        }

        /* A chamada do seguinte método liga o Bluetooth no dispositivo Android
            sem pedido de autorização do usuário. É altamente não recomendado no
            Android Developers, mas, para simplificar este app, que é um demo,
            faremos isso. Na prática, em um app que vai ser usado por outras
            pessoas, não faça isso.
         */
        btAdapter.enable();

        /* Definição da thread de conexão como cliente.
            Aqui, você deve incluir o endereço MAC do seu módulo Bluetooth.
            O app iniciará e vai automaticamente buscar por esse endereço.
            Caso não encontre, dirá que houve um erro de conexão.
         */
        connect = new ConnectionThread("00:14:03:18:43:45");
        connect.start();

        /* Um descanso rápido, para evitar bugs esquisitos.
         */
        try {
            Thread.sleep(1000);
        } catch (Exception E) {
            E.printStackTrace();
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public static Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {

            /* Esse método é invocado na Activity principal
                sempre que a thread de conexão Bluetooth recebe
                uma mensagem.
             */
            Bundle bundle = msg.getData();
            byte[] data = bundle.getByteArray("data");
            String dataString= new String(data);

            /* Aqui ocorre a decisão de ação, baseada na string
                recebida. Caso a string corresponda à uma das
                mensagens de status de conexão (iniciadas com --),
                atualizamos o status da conexão conforme o código.
             */
            if(dataString.equals("---N"))
                statusMessage.setText("Ocorreu um erro durante a conexão D:");
            else if(dataString.equals("---S"))
                statusMessage.setText("Conectado :D");
            else {

                /* Se a mensagem não for um código de status,
                    então ela deve ser tratada pelo aplicativo
                    como uma mensagem vinda diretamente do outro
                    lado da conexão. Nesse caso, simplesmente
                    atualizamos o valor contido no TextView do
                    contador.
                 */
                counterMessage.setText(dataString);
            }

        }
    };

    /* Esse método é invocado sempre que o usuário clicar na TextView
        que contem o contador. O app Android transmite a string "restart",
        seguido de uma quebra de linha, que é o indicador de fim de mensagem.
     */
    public void restartCounter(View view) {
        connect.write("restart\n".getBytes());
    }
}

Pronto, depois de modificar o código de sua Activity principal e deixá-lo aproximadamente como mencionado acima, você está pronto para testar o app. Acredito fortemente nisso. Se faltar algo, me avisem, obrigado!

Notas Breves

Provavelmente, na primeira conexão entre o seu dispositivo Android e o módulo Bluetooth, você deverá entrar com uma senha para realizar o pareamento. Para o meu dispositivo, um módulo HC-06, a senha correta era 1234. Caso essa não funcione, tente 0000. Se essa não funcionar, então não sei. É uma boa ideia pesquisar na Internet sobre o assunto, pois seu módulo Bluetooth é estranho.

The post Comunicação Bluetooth entre Arduino e Android appeared first on Dragão sem Chama.


Viewing all articles
Browse latest Browse all 24