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

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

Vem vindo novidade por ai... Minicurso de desenvolvimento iOS

Ola pessoal, Depois de uma conversa com a Yara, resolvemos colocar em prática uma idéia que já tinhamos a um tempo, que é aumentar as opções de mobilidade no portifólio da Globalcode e também da iniciativa Open4Education. E nesse caso estamos falando de um minicurso sobre desenvolvimento para iPhone e iPad. O objetivo desse minicurso vai ser mostrar que desenvolver para iOS não é tão complicado como se pode pensar e que não é preciso ter medo do Objective-C ! Serão algumas horas onde os alunos poderão entender os fundamentos da plataforma, como funciona o programa para desenvolvedores da Apple, preparação do ambiente e dos devices de teste e também um overview dos layers de desenvolvimento do iOS. Além disso, o objetivo também vai ser passar um pouco da minha experiência na "migração" que fiz de Java (seja SE, ME ou Android) para o novo mundo do Objective-C e Cocoa Touch. O material está no começo e ainda não temos uma data certa (só sabemos que a primeira edição desse minicu...

JavaMail: Enviando e-mail com Java

Introdução Além da necessidade de envio de e-mail ser comum a várias aplicações, foi a pergunta de um aluno da Academia Java ,  “Como enviar um e-mail com Java?”, que me motivou a escrever este post sobre JavaMail. JavaMail Para realizar o envio de e-mail por meio de uma aplicação Java, precisamos da biblioteca JavaMail, pois ela não é incluída no Java SE. A biblioteca está disponível em http://www.oracle.com/technetwork/java/index-138643.html . Neste download, além da biblioteca mail.jar que inclui a implementação completa da API e providers, também é disponibilizada a documentação da biblioteca ( javadoc ) na pasta docs , alguns exemplos na pasta demo e partes da implementação em lib . A forma mais simples de utilizar a JavaMail e incluir o mail.jar , porém para uma aplicação que só envia e-mail como o nosso exemplo, necessitamos apenas dos arquivos mailapi.jar e smtp.jar , economizando 177KB. Como a economia é pouca e as aplicações evoluem, vamos adicionar o mail.j...

Facelets uma forma mais ágil para construção de telas – Parte I

A construção de telas ou camada de apresentação em um sistema MVC seja web ou desktop é uma tarefa complexa e de extrema importância. Nesse post vou comentar e mostrar algum exemplo do Facelets como solução para os desafios existentes nessa etapa especificamente para web. Com a web cada vez mais presente em nosso dia-a-dia, um fato é que com isso nossos usuários tornam-se mais exigentes em relação a usabilidade, agilidade, performance ou de uma forma bem resumida “o usuário espera uma navegação simples e agradável aonde uma determinado tarefa possa ser concluída em poucos passos e em um curto espaço de tempo”. Atender as expectativas em relação ao que o usuário espera com o que realmente ele precisa, definir uma estrutura flexível a mudanças sem engessar o desenvolvimento, acessibilidade, portabilidade em múltiplos navegadores, tudo isso e muito mais, num prazo que quase sempre é apertado. Um outro ponto fundamental é manter o time motivado e produtivo em um ambiente que favoreça a...

JavaOne Brasil, dicas para submissão de palestras

Não quero parecer pretensiosa dando dicas para submissão de palestras para o JavaOne Brasil, mas sim repassar os tantos conselhos e sugestões recebidas pelos vetaranos do JavaOne: Bruno Souza e Leonardo Galvão que revisaram dezenas de submissões para o JavaOne e ajudaram a aprovar tantas palestras, e também misturar um pouco da minha experiência na seleção de palestras nos eventos realizados pela Globalcode e SouJava . 10 anos de JavaOne: http://www.globalcode.com.br/noticias/Globalcode10AnosNoJavaOne Os palestrantes ganham a entrada! A submissão pode ser feita em português! O passo mais importante para ser aprovado como palestrante no JavaOne é sem dúvida nenhuma submeter pelo menos uma palestra. Então, independente de qualquer coisa, participe, arrisque, divulgue.  Mas, se quiser aumentar as suas chances...   1) Leve a sério: peça para amigos fazerem uma leitura crítica do texto, e claro uma boa revisão ortográfica. 2) Submissão de várias palestras ou variações do ...

Facelets ainda mais divertido! Parte II

De volta ao Facelets , na primeira parte mantive o foco na utilização de templates e técnicas de reutilização visando maior agilidade para desenvolver telas com JSF , mas o Facelets vai bem além disso! Nesse post vou comentar e mostrar um pouco sobre a criação de componentes UI (User Interface) usando xht ml - na minha opinião esse é o grande diferencial da tecnologia. Com esse recurso é possível customizar / padronizar componentes usando xhtml + tags JSF + JavaScript + Css, sem código Java. A ideia é bem próxima ao Tag File em uma rápida comparação com JSP (JavaServer Pages), mas no caso do Facelets feito de uma forma ainda mais simples e com aderência a (infra)estrutura do JSF. Vou descrever o mesmo cenário da primeira parte, um sistema composto por vários cadastros ( C reate R ead U pdate D elete). Pensando especificamente em cada formulário, usando como exemplo um rascunho ou protótipo para o cadastro de Fornecedores, podemos assumir o seguinte formato: campos para preenchi...