Pular para o conteúdo principal

Criando aplicação de Código Morse com Arduino + ADK + Android

Bom dia.

Há alguns dias, publiquei o post "Como conectar uma placa ADK e um aplicativo Android de forma indolor", onde falei sobre os passos básicos para fazer a conexão entre smartphone e arduino mega com suporte a ADK. Fiquei de escrever novos conteúdos. Sendo assim, como prometido, desta vez vou falar sobre a troca de dados entre os dois elementos.

* OBS: Caso não saiba o que é Arduino, ADK ou Android, leia o post que mencionei anteriormente.

Outro ponto importante. Não vou colocar todo o código fonte produzido aqui, porque prejudicaria a leitura e ficaria um post muito grande. Mas, não se preocupem, joguei tudo no GitHub. O link para download de todo fonte está aqui.

Vamos começar pela parte Arduino. Perceba que na descrição do AndroidAccessory colocamos dados mais reais desta vez. Veja como ficou:

AndroidAccessory acc("RicardoOgliari", "MorseTHT", "Código Morse como você nunca viu", "1.0.0", "URI", "Serial");

A seguir podem ser vistas três funções: ponto, traco e espera. Todas mudam a cor do LED RGB e, sem seguida, chamam a função delay passando um parâmetro em milissegundos. Desta forma, a troca de cores e a mensagem Morse será perceptível a olho nú. Caso contrário, o processo seria tão rápido que daria a impressão de apenas uma cor existente no LED.

Dentro do loop, setamos a cor para preto inicialmente. Lembrando que o segundo, terceiro e quatro parâmetros da função setColorRGB são RED, GREEN e BLUE, respectivamente. O isConnected já foi falado no outro artigo. Ele retorna true se já existe um equipamento conversando com a placa ADK. Sendo assim, se entrar neste if a brincadeira começa.

O segredo começa a ser desvendado na criação da variável len, que recebe o retorno da função read de acc, objeto de AndroidAccessory. Esta função recebe por parâmetro um vetor de bytes, seu tamanho e por fim, um valor inicial caso a transmissão não seja reconhecida. E logo abaixo, temos mais um teste lógico. Se a variável len for maior que zero, então isso nos diz que teve a recepção de dados no ADK.


void loop() {
  leds.setColorRGB(0, 0, 0, 0); 
  if (acc.isConnected()) {
      int len = acc.read(rcvmsg, sizeof(rcvmsg), 1);
      if (len > 0) {
        if (rcvmsg[0] == COMANDO_MORSE) {
          byte qtdLetras = rcvmsg[1];
          ... 


Se o último if retornou true, isso também significa que o vetor de bytes foi valorizado. Existem alguns padrões de uso deste vetor de bytes. No exemplo mostrado aqui, no primeiro byte enviado pelo aplicativo informamos qual a ação que queremos efetuar. Logicamente só temos uma ação e isso é supérfluo. Mas, decidi deixar assim para o leitor ver como pode tratar diferentes tarefas oriundas do smartphone. Sendo assim, caso o segundo if for verdadeiro, o ADK não só recebeu dados, como o primeiro byte recebido indica que a ação desejada é um comando morse.

Na sequência recuperamos o segundo byte recebido, armazenado o mesmo na variável qtdLetras. Isso serve para sabermos quantos caracteres que foram passados na caixa de texto da aplicação Android. A variável indiceCor, presente na sequência, trata a mudança de cor utiliza no led de uma letra para outra. Assim fica mais fácil identificar o código morse.


OBS: Para saber tudo sobre o Código Morse, visite a Wikipedia.


Depois encontramos um laço for que percorrerá todas as letras recebidas, começando pelo índdice dois do vetor de bytes. Isso porque já lemos e utilizamos os índices 0 e 1. Dentro do for é feito um switch-case para saber qual o valor do byte recebido. Sabendo o código ASCII e, que o mesmo representa os números decimais de 97 até 122 para a sequência de letras de 'a' até 'z', utilizamo-nos nos respectivos cases. Veja:


          for (int i = 2; i < qtdLetras+2; i++){
             switch (rcvmsg[i]){
               case 97:
                 ponto();
                 espera();
                 traco();
                 espera();
                 break;
               case 98:
                 traco();
                 espera();
                 ponto();
                 espera();
                 ponto();
                 espera();
                 ponto();
                 espera();
                 break;

Outro ponto importante na comunicação Android - ADK e, essencial neste projeto, é avisar o aplicativo de que o código morse já foi apresentado e o arduino está pronto para receber um novo texto e refazer a tradução. Para isso vamos usar a função write de AndroidAccessory. Os parâmetros enviados são um vetor de bytes e seu tamanho. Veja:

byte sntmsg[1] = {0xe}; //está no início do código, na criação das variáveis
acc.write(sntmsg, 1);//na parte final do código

E no Android?
Bem, no lado Android o processo não é tão simples assim.

OBS: Antes de começarmos a ver os códigos, indico que pegue os mesmos no GitHub, ficará mais fácil de acompanhar.

No método onCreate da única Activity presente no projeto é criado um BroadcastReceiver para interceptar o momento em que a união entre smartphone e ADK for feita.

mUsbManager = UsbManager.getInstance(this);
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(mUsbReceiver, filter);


Perceba que ao registrar o receiver o primeiro parâmetro é um mUsbReceiver. Este é um objeto criado na própria classe. Perceba que ao sobrescrever o onReceive é tratado os dois possíveis eventos: o acessório foi plugado ou desplugado. E, também, preste atenção e veja que temos dois métodos auxiliares: openAccessory e closeAccessory.


private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
     String action = intent.getAction();
     if (ACTION_USB_PERMISSION.equals(action)) {
       synchronized (this) {
         UsbAccessory accessory = UsbManager.getAccessory(intent);
         if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
             openAccessory(accessory);
         } else {}
         mPermissionRequestPending = false;
       }
     } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
       UsbAccessory accessory = UsbManager.getAccessory(intent);
       if (accessory != null && accessory.equals(mAccessory)) {
         closeAccessory();
       }
     }
   }
};


Vamos estudar primeiro o openAccessory. Neste método a linha mais importante é justamente a primeira. Onde o método openAccessory de UsbManager é chamado. O retorno é uma instância da classe FileDescriptor que, por sua vez, serve de parâmetro para criarmos instância de FileInputStream e FileOutputStream. Estas classes stream serão o caminho de ida e volta dos dados. A estrada da informação entre arduino e smartphone.

private void openAccessory(UsbAccessory accessory) {
        mFileDescriptor = mUsbManager.openAccessory(accessory);
        if (mFileDescriptor != null) {
            mAccessory = accessory;
            FileDescriptor fd = mFileDescriptor.getFileDescriptor();
            mInputStream = new FileInputStream(fd);
            mOutputStream = new FileOutputStream(fd);
            Thread thread = new Thread(null, commRunnable, TAG);
            thread.start();
        } else {}
    }


No método descrito anteriormente é possível perceber a criação de uma Thread, sendo que um dos parâmetros passados é uma instância de Runnable criada na própria classe, chamada de commRunnable. Esta classe tem um papel importante. Ela fica chegando se tem algo novo oriundo do input stream. Caso afirmativo, significa que o ADK mandou dados para a aplicação. Isso só acontece quando a placa já mostrou o código morse e está aguardando um novo texto. Sendo assim, o campo de texto e o botão voltam a ter seu estado enabled como true.

Runnable commRunnable = new Runnable() {
        @Override
        public void run() {
            int ret = 0;
            byte[] buffer = new byte[255];
            while (ret >= 0) {
                try {
                    ret = mInputStream.read(buffer);
                } catch (IOException e) {
                    break;
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvEdt.setEnabled(true);
                        btn.setEnabled(true);
                    }
                });
            }
        }
    }; 

No método closeAccessory só acontece a limpeza das variáveis, para não ocuparmos recursos da memória desnecessariamente.

E o envio dos dados? Isso acontece no método showMorse. Ele foi passado para a propriedade onClick do Button. Neste trecho de código é construído o vetor de bytes, com a tarefa desejada no primeiro índice, que, deve ser o mesmo byte configurado no teste if no código presente na placa, feito em C++. O vetor de bytes é passado como parâmetro no método write de output stream. Desta forma os dados chegaram no ADK. E, tanto o campo de texto como o botão tem seu estado enabled configurado para false.


public void showMorse(View v) {
        String texto = tvEdt.getText().toString();
        Toast.makeText(this, texto, 8000).show();
        byte[] buffer = new byte[2 + texto.length()];
        buffer[0] = 0xf;
        buffer[1] = (byte)texto.length();
        for (int pos = 2, i = 0; i < texto.length(); i++, pos++){
            buffer[pos] = (byte)texto.charAt(i);
        
        }
        try {
            mOutputStream.write(buffer);
            tvEdt.setEnabled(false);
            btn.setEnabled(false);
        } catch (IOException e) {}
    }


O código do aplicativo também a parte de controle de pausa da aplicação e de sua retomada. No onResume é chamado o método getAccessoryList da classe UsbManager. Caso exista um acessório plugado, o método openAccessory é chamado novamente.

Pronto, isso é tudo.

Veja a solução em funcionamento:

Conclusão

O segredo é o input stream e output stream. Ambos são usados extensamente em vários tipos de conexão, como HTTP por exemplo. O vetor de bytes e sua correta separação é a chave para uma boa conversa entre as duas estrelas deste artigo, o aplicativo Android e o ADK.

Agora é colocar a cabeça para funcionar e criar nossos próprios projetos. Afinal, a onda agora é MAKE YOURSELF.

Comentários

Postagens mais visitadas deste blog

Devo fazer um curso ou ler um livro?

Acredito que todos os instrutores ou professores, independentemente da área, escola ou centro de treinamento, já devam ter recebido essa pergunta alguma vez na vida: devo fazer um curso ou ler um livro? Para responder a essa pergunta, precisamos avaliar os prós e contras de cada opção. Trabalho com treinamento há algum tempo e, hoje, recebi essa pergunta de um aluno. Não adianta responder a ou b sem argumentar, demonstrando as opções conforme a situação do aluno. O conteúdo, a forma de transmissão e a capacidade de assimilação do indivíduo são chaves para haver benefício maior de aprendizado. Tanto em um bom curso quanto em um bom livro, o conteúdo é a premissa básica . Por conteúdo entendemos: se está organizado; se respeita pré-requisitos; se promove o aprendizado guiado e incremental; se aborda de forma satisfatória os principais pontos; se tem bom balanço entre teoria, exemplos e prática (favorecendo exemplos e prática); se tem como premissa a acessibilidade possível (e cabível) pa...

10 reasons why we love JSF

1. One-slide technology: it's so simple that I can explain basic JSF with one slide. 2. Easy to extend: components, listeners, render kit, Events, Controller, etc. 3. Real-world adoption: JBoss, Exadel, Oracle, IBM, ... 4. Architecture model: you can choose between more than 100 different architecture. 5. Open-mind community: using JSF you are going to meet very interesting people. 6. We are using JSF the last 5 years and we found very good market for JSF in Brazil 7. Progress: look to JSf 1.1 to JSF 1.2, JSF 1.2 to JSF 2.0. People are working really hard! 8. Many professionals now available 9. It's a standard. It's JCP. Before complain, report and help! 10. Ed Burns, spec leader, is an old Globalcode community friend! EXTRA: My wife is specialist in JSF. She's my F1 for JSF :) Nice job JSF community! -Vinicius Senger

O que é Lógica de programação?

Este é o segundo de uma série de posts voltados aos leitores do blog que estão dando início à carreira de desenvolvimento de software. O assunto de hoje é a lógica de programação. Para ler antes: Entendendo como funciona a programação de computadores: linguagens de programação, lógica, banco de dados A lógica de programação é um pré-requisito para quem quer se tornar um desenvolvedor de software, independente da linguagem de programação que se pretende utilizar. Mas o que é de fato a Lógica de Programação e como saber se eu tenho esse pré-requisito? A lógica de programação nada mais é do que a organização coerente das instruções do programa para que seu objetivo seja alcançado. Para criar essa organização, instruções simples do programa, como mudar o valor de uma variável ou desenhar uma imagem na tela do computador, são interconectadas a estruturas lógicas que guiam o fluxo da execução do programa. Isso é muito próximo ao que usamos em nosso cotidiano para realizar atividad...

Parceria Globalcode no projeto Samsung Ocean

Já faz algum tempo que a Globalcode e a Samsung tem uma parceria no projeto "Samsung Ocean". Esse é um projeto muito interessante com o objetivo de divulgar e difundir o uso de tecnologia, principalmente associado a dispositivos móveis como celulares e relógios inteligentes (smart watches). No projeto são oferecidos diversos treinamentos e workshops gratuitos . Alguns dos treinamentos oferecidos são: Desenvolvimento de aplicações Android Desenvolvimento de aplicações para wearable Tópicos em desenvolvimento ágil Introdução aos jogos digitais Para a maioria dos cursos, o material e instrutores são fornecidos pela Globalcode. Atualmente nosso grupo de instrutores do projeto conta com excelentes profissionais como: Thiago Moreira Heider Lopes Luis Palma Taynã Bonaldo Thais Andrade O centro de treinamentos está localizado na Escola Politécnica da USP e atualmente estão abertas as inscrições para um dos programas mais bacanas do projeto. É o programa de pré-a...

Sistema interativo de TV Digital com Ginga-J

No início de 2009, os estudantes de Sistema de Informação do Centro Universitário de Votuporanga ( UNIFEV ), Caio César Pereira de Souza e Rodrigo Gonçalves Constantino me apresentaram uma proposta para que eu fosse co-orientador junto ao professor orientador Djalma Domingos da Silva , em seu Trabalho de conclusão de curso (TCC) com tema TV Digital. A base que motivou o assunto, foi a palestra apresentada por Maurício Leal na I Conferência Java Noroeste sobre o tema TV Digital, realizada em 2006 em Votuporanga-SP. Ficamos muito entusiasmados com a possibilidade de interatividade na TV Digital, e a grande quantidade de possibilidades de desenvolvimento de aplicativos nesta área. Acompanhamos de perto as notícias na imprensa e todo o esforço e iniciativas realizadas pelo Fórum do Sistema Brasileiro de TV Digital Terrestre (SBTVD) , que organizou e produziu especificações ABNT, normatizando o sistema de TV Digital Terrestre. O foco do TCC foi realizar o desenvolvimento de uma pequena ...

EJB 3: Uma evolução sob os conceitos do Hibernate e Spring

Definitivamente o modelo de componentização definido no Java EE 5 e 6 evoluiu e melhorou muito. Mas, sem dúvida muita dessa evolução se deve às pressões do Hibernate e Spring Framework. Estes dois últimos frameworks nasceram baseados no conceito de POJO, que nada mais é do que a concepção de um modelo de componentização baseado em classes Java sem as regras impostas pelo EJB (curioso, sem o EJB não existiria o Hibernate ou o Spring). A morte dos Entity Beans O Hibernate nasceu da idéia de promover um modelo de persistência mais simples que o proposto pelos EJBs do tipo Entity Beans definido na especificação EJB 2.x. Este foi o primeiro tipo de EJB a sofrer com a evasão de desenvolvedores com o surgimento deste framework e a conscientização sobre os problemas nos Entity Beans. A partir de um modelo baseado em JavaBeans e o uso do JDBC, o Hibernate usa a Reflection API para gerar os SQLs necessários para persistir o estado de beans em diversos banco de dados relacionais, além de defini...