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

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

Muitas vagas, otimismo e sensação de reaquecimento da economia

2010 começou cedo, contrariando a expectativa que muitos Brasileiros tem de "O ano começa mesmo depois do carnaval".  Embora o inicio do ano tenha sido "repleto" de catástrofes naturais e muita chuva aqui no Brasil, no campo profissional o otimismo está evidente, as pessoas estão cheias de energia e boas perspectivas em relação as oportunidades de JavaEE 6 e muitas outras tecnologias. Mas, parece que não são somente as pessoas que estão otimistas, os anúncios de vagas voltaram a crescer, empresas anunciando boas vagas, ou grandes quantidades de vagas, refletindo o que podemos chamar de "Otimismo corporativo" . Veja algumas empresas que anunciaram vagas: IBM SpringSource   Yahoo!  Elumini   Cyberlynxx   Google Kaizen SPC Brasil Se em 2009, um ano de crise fizemos tudo isto , imagine em 2010. E você está preparado ?  Está otimista ? []s Yara http://twitter.com/yarasenger http://twitter.com/globalcode

Melhorando Performance de JPA com Spring Web Flow

No TDC2009 realizado pela Globalcode em São Paulo foi apresentado um Lightning Talk sobre um problema específico de performance em aplicações Web com JPA e uma possível solução usando o Spring Web Flow . Num período de 15 minutos, os slides a seguir foram apresentados e seguidos de alguns vídeos de demonstração de uma aplicação Web em execução. Melhorando performance do JPA com Spring Web Flow View more presentations from Dr. Spock . Nesta apresentação foi dito que temos encontrado problemas de performance em aplicações Web que utilizam as tecnologias JSF + JPA + Ajax quando precisamos gerenciar um contexto de persistência (EntityManager). Estes problemas se manifestam quando aplicamos uma resposta errada para a pergunta: Como gerenciar o contexto de persistência numa aplicação Web? Se as aplicações não usam Ajax e limitam-se ao modelo orientado a requisições, a solução mais comum é o uso do design pattern chamado "Open Session In View Filter". Através deste design

Dica rápida: Apagando registros duplicados no MySQL

Ola pessoal, Sei que vocês estão acostumados a ver posts meus sobre tecnologia móvel ou algo relacionado, mas hoje vou falar sobre um pequeno "truque" que usei esse final de semana com o MySQL. Eu estava desenvolvendo o lado servidor de uma nova aplicação mobile (ahh, então "tem a ver" com mobile hehe), e quando fui fazer alguns testes percebi que tinha quase 7 mil registros duplicados (!!!) na minha base de dados! Bom, o meu primeiro reflexo como programador foi pensar em fazer um "programinha" Java para buscar e deletar todos esses registros duplicados. Mas ai, resolvi tirar as teias de aranha dos neurônios e usar os vários anos de experiência que passei com SQL e criar uma query que fizesse esse trabalho todo de uma vez!! E a query ficou assim: delete from TABLE_NAME USING  TABLE_NAME, TABLE_NAME  AS  auxtable WHERE   ( NOT  TABLE_NAME.id  =  auxtable.id ) AND   ( TABLE_NAME.name  =  auxtable.name ) Explicação direta: TABLE_NAME

JavaMail: Enviando mensagem HTML com anexos

Introdução Depois do post "JavaMail: Enviando e-mail com Java" , que apresentava como enviar um e-mail com Java, resolvi complementar a assunto apresentando como enviar uma mensagem formatada, em HTML , e também como realizar o envio de anexos. Bibliotecas Além da biblioteca JavaMail, veja mais no post anterior , é necessário incluir o JavaBeans Activation Framework (JAF), apenas se a versão utilizada for anterior ao JSE 6.0 , que já tem o JAF incluso. O JAF está disponível em http://www.oracle.com/technetwork/java/javase/downloads/index-135046.html , e neste download encontramos, alguns exemplos na pasta demo , documentação, incluindo javadocs, na pasta docs e a biblioteca activation.jar , que deve ser acrescentada no classpath da aplicação para versões anteriores ao JSE 6.0. Exemplo Primeiramente devemos realizar a configuração da javax.mail.Session e da javax.mail.internet.MimeMessage , estes passos podem ser vistos no post anterior . Agora vamos montar um

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