As etiquetas RFID são, hoje em dia, muito comuns em diversas atividades, desde cartões de identificação a etiquetas para identificar produtos.
RFID significa Radio Frequency Identification (Identificação por rádio frequência).
O principio é simples, existe um leitor e uma etiqueta que comunicam, mas ao contrário dos rádios a etiqueta não tem de ser alimentada (apesar de existirem algumas que são) por baterias, esta é alimentada pelo leitor por um período breve de tempo durante o qual é enviado para o leitor o número de identificação da etiqueta.
Para este pequeno projeto vou utilizar uma placa RFID USB da SparkFun para comunicar com um leitor RFID.
O leitor encaixa na placa que por sua vez liga-se ao computador via USB. Nada mais fácil.
Agora precisamos dos drivers da placa para isso basta seguir este link.
Depois do dispositivo estar instalado temos de saber qual o número da porta que lhe foi atribuído para isso basta ir ao gestor de dispositivos e procurar na secção portas.
No meu caso é a porta COM31.
Agora vamos até ao Visual Basic fazer um pequeno programa para ler as etiquetas.
No formulário vamos utilizar o componente SerialPort para receber do leitor RFID os dados das etiquetas.
Agora o código.
Primeiro o botão que atualiza a porta de comunicação e abre a porta.
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
If Me.SerialPort1.IsOpen = True Then Me.SerialPort1.Close() Try Me.SerialPort1.PortName = Me.TextBox1.Text If Me.SerialPort1.IsOpen = False Then Me.SerialPort1.Open() Catch ex As Exception MessageBox.Show(ex.Message) End Try
End Sub
Agora o componente SerialPort, por defeito este já está configurado para um baud rate de 9600 que é a mesma velocidade da placa da SparkFun, como já deve ter percebido esta porta apesar de ser usb utiliza um protocolo de comunicação série tal como o arduino.
Para ler os dados da SerialPort adicionamos um timer que quando detetar dados lê-os e adiciona-os à listbox, o código do timer:
Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick Dim letra As Char Static texto As String If Me.SerialPort1.IsOpen = False Then Exit Sub letra = ChrW(Me.SerialPort1.ReadChar()) If Char.IsLetterOrDigit(letra) Then texto = texto & letra Else If texto = "" Then Exit Sub If texto <> "" Then Me.ListBox1.Items.Add(texto) texto = "" End If End If End Sub
Com este código cada letra da tag é lida individualmente e adicionada à string quando a letra lida não é válida verificamos se terminamos de ler para adicionar à listbox.
Como é do conhecimento geral os telemóveis Android têm um sensor designado de acelerómetro. Este sensor permite avaliar os movimentos a que o dispositivo está sujeito num sistema de três eixos. O eixo dos x permite avaliar a inclinação do dispositivo para a esquerda ou para a direita. O eixo dos y é controlado pela inclinação do dispositivo para a frente ou para trás. O eixo dos z reflete o movimento para cima e para baixo.
Para este pequeno projeto vamos fazer um labirinto cujo objetivo é levar uma esfera até ao buraco inclinando o dispositivo tal como se se trata-se um jogo físico.
Primeiro vamos criar um projeto novo.
Na atividade principal (MainActivity) vamos implementar uma função que fica associada ao sensor, é o que se designa por SensorEventListener.
Assim alteramos a declaração da class para
public class MainActivity extends Activity implements SensorEventListener{
De seguida na função onCreate temos de "capturar" o sensor, para isso criamos um objeto SensorManager e outro objeto Sensor.
Posto isto temos de implementar a função onSensorChanged, que vai receber os valores do sensor sempre que este sofrer alterações.
public void onSensorChanged(SensorEvent event) { //recolher os três valores para as variáveis x=event.values[0]; y=event.values[1]; z=event.values[2]; }
Com estas funções implementadas vamos passar à fase gráfica, para implementar a interface vamos utilizar uma ImageView que depois associamos ao xml do layout do programa.
Vamos, então, adicionar uma class nova que vai implementar a lógica e os gráficos.
public class ecran extends ImageView{
Nesta class vamos implementar a função onSizeChanged que vai permitir saber o tamanho do ecrã, para, em função desse tamanho, implementarmos as paredes do labirinto.
Precisamos, ainda, da função onDraw, que além de desenhar tudo no ecrã vai calcular a posição da esfera em função dos valores do sensor. Como o sensor é controlado na MainActivity é necessário implementar três métodos públicos na MainActivity para podermos aceder aos valores do sensor. Primeiro um método que nos dá a instancia atual.
public static MainActivity getInstance(){ return instance; }
Este método vai permitir à class ecran, que é a que desenha no ecrã do dispositivo, ter acesso aos outros dois métodos, um que devolve o valor x e outro para o valor y do sensor.
public float getX() { return x; } public float getY() { return y; } No projeto também está implementado o método para o z mas não é utilizado.
Para as paredes do labirinto criei um vetor de retângulos. As paredes são definidas por código e desenhadas com a cor vermelha.
A função principal é a onDraw que trata de: 1º desenhar o buraco de saída do labirinto. 2º desenhar as paredes. 3º receber os valores da inclinação do dispositivo. 4º calcular a nova posição da esfera calculado se esta bate em alguma parede. 5º se a esfera está suficientemente perto do buraco para declarar vitória.
É um layout linear que associa a class ecran ao ecrã.
Em resumo: este projeto demonstra como desenhar no ecrã utilizando as funções gráficas do sistema operativo Android, desde imagens a figuras geométricas bem como escrever texto e ainda a utilização do sensor acelerómetro.
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.
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.
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){
Aparentemente não demorou muito tempo, tal como tinha dito no post anterior seria interessante que os tempos guardados pudessem ser recuperados para sessões posteriores, assim adicionei dois botões à interface.
O botão Gravar vai gravar num ficheiro de texto os dados relativos aos tempos intermédios. O botão Limpar, tal como o nome indica, apaga os tempos da textview não do ficheiro.
O código
A função associada ao botão Limpar: //função para limpar a textview 2 public void bt_limpar_click(View v){ tv2.setText(""); }
Simples, sem comentários.
Agora a função associada ao botão Gravar:
//gravar no ficheiro os tempos public void bt_gravar_click(View v){ try{ File myfile = new File(ficheiro); myfile.createNewFile(); FileOutputStream fOut = new FileOutputStream(myfile); OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut); myOutWriter.append(tv2.getText()); myOutWriter.close(); fOut.close(); showMessage("Gravado com sucesso!"); }catch (Exception ex){ showMessage(ex.getMessage()); } }
Nesta função criamos um ficheiro e associamos um Output Stream a esse ficheiro que depois utilizamos para gravar o conteúdo do textView2.
Por fim a função que abre e lê o ficheiro sempre que a aplicação é iniciada:
//função para ler o ficheiro e adicionar à textview
public void ler_ficheiro(){
try{
File myfile = new File(ficheiro);
FileInputStream fIn = new FileInputStream(myfile);
BufferedReader myReader = new BufferedReader(new InputStreamReader(fIn));
String aDataRow="";
String aBuffer="";
while((aDataRow=myReader.readLine())!=null){
aBuffer +=aDataRow + "\n";
}
tv2.setText(aBuffer);
myReader.close();
showMessage("Tempos carregados!");
}catch (Exception ex){
}
}
Esta função é chamada na função onCreate.
Não nos podemos esquecer de adicionar ao manifesto da aplicação a permissão com a seguinte linha:
E assim temos um projeto completo, o próximo passo é visual, tenho de começar a ter mais cuidado com a interface, até agora tem sido só atirar botões e textviews para o ecrã e depois se vê.
De volta à plataforma Android desta vez para fazer um pequeno programa que vai permitir cronometrar e guardar tempos.
A interface é simples:
O botão Guardar vai permitir adicionar o tempo atual aos tempos intermédios. O botão Iniciar vair intercalar a interface entre a hora atual e o cronometro. O botão Recomeçar vai recolocar o temporizador a zero.
Sempre que carregar no botão Guardar o tempo do cronometro é adicionado aos tempos intermédios o que implica ter uma área que se pode deslizar para cima e para baixo uma vez que não existe limite no número de tempos que podemos adicionar.
Os desafios deste pequeno programa são dois: - primeiro a atualização da interface implica criar uma thread separada, de outro modo o sistema não atualiza a informação mostrada ao utilizador. - segundo temos os cálculos com tempo, neste aspeto encontrei um problema inesperado! O programa apresenta resultados diferentes quando executado no emulador de quando é executado no dispositivo real. Ainda não percebi porquê mas testei com duas versões diferentes do emulador e os resultados foram os mesmos, mas quando executei num Huawei com a versão 4.0.3 do Android os resultados não coincidiam com os apresentados pelo emulador.
Resumindo temos: - 1 scrollview para podermos deslizar o conteúdo - 3 botões - 3 Textviews: 1 para mostrar a hora ou o tempo cronometrado, 1 para o texto "Intermédios" e por fim para os tempos intermédios guardados.
O código
Em relação ao código vamos apresentar a função on create
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_times_up); tv1=(TextView) findViewById(R.id.textView1); tv2=(TextView) findViewById(R.id.textView2); iniciado=false; //thread que vai atualizar a UI Thread th = new Thread(){ @Override public void run(){ while(true){ try { Thread.sleep(250); } catch (InterruptedException e) { e.printStackTrace(); } runOnUiThread(new Runnable(){ public void run(){ String t; if(!iniciado){ cal = Calendar.getInstance(); t=String.format("%02d:%02d:%02d",cal.get(Calendar.HOUR_OF_DAY),cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND)); tv1.setText(t); }else{ int m=0,h=0,s=0; cal = Calendar.getInstance(); cal.setTimeInMillis(cal.getTimeInMillis()-inicio.getTimeInMillis()); //h=cal.get(Calendar.HOUR_OF_DAY); h=(int)cal.getTimeInMillis()/(1000*60*60); m=cal.get(Calendar.MINUTE); s=cal.get(Calendar.SECOND); t=String.format("%02d:%02d:%02d",h,m,s); tv1.setText(t);// + "-" + Long.toString(mil)); } //if else }//run });//runOnUiThread }//whiler }//run };//thread th.start(); }//oncreate
Este código cria um ciclo infinito que vai atualizar e apresentar a hora ou o tempo cronometrado de acordo com a variável "iniciado" que indica se o cronometro foi ou não iniciado.
A linha de código que está comentada é a que apresenta um valor diferente quando executada no emulador de quando executada no dispositivo real. Esta linha devia calcular quantas horas correspondem ao valor de milissegundos calculados como diferença entre o tempo inicial do cronometro e o atual. No emulador o valor é calculado corretamente como sendo zero inicialmente enquanto que no dispositivo começa em um!! Vai-se lá saber porquê. Esta é a imagem do programa a correr no dispositivo real.
E esta é a imagem do programa a correr no emulador.
Para resolver este problema decidi calcular a hora de outro modo utilizando a linha
Por fim temos as funções que implementam a lógica dos três botões:
//botão guardar
public void bt_guardar_click(View v){
String t;
t=(String) tv2.getText();
t = t + "\n" + tv1.getText();
tv2.setText(t);
}
//botão iniciar
public void bt_iniciar_click(View v){
if(inicio==null)
inicio = Calendar.getInstance();
iniciado=!iniciado;
}
//botão recomeçar
public void bt_continuar_click(View v){
inicio = Calendar.getInstance();
}
Um dos problemas que ainda temos para resolver é o que acontece quando mudamos a orientação do dispositivo, uma vez que o programa é reiniciado o tempo do temporizador perdido. Assim podemos fixar a orientação em vertical (portait) com a seguinte linha:
Uma funcionalidade que fica para depois, espero que não muito depois, é adicionar a possibilidade de guardar os tempos para mais tarde poder recarregar e comparar.
Como sidekick temos uma caraterística interessante que permite que o programa mesmo não estando aberto em primeiro plano continua a calcular o tempo no cronometro graças ao modo como o Android executa os programas, assim podemos ter o programa aberto a executar em modo cronometro e atender chamadas ou executar outro programa que quando voltamos o tempo continua.
Depois da versão Android fica aqui a versão Windows Phone.
Para este pequeno projeto vamos utilizar o Visual Studio 2010 Express for Windows Phone, a linguagem utilizada é o Visual Basic.
Começamos por criar o projeto novo.
Depois criamos a interface com dois radio buttons, uma textbox, um botão e um textblock.
Ainda antes de começar a codificar vamos precisar de adicionar os dois ficheiros txt ao projeto para podermos aceder a eles quando for necessário verificar a palavra introduzida. Para isso basta arrastar os ficheiros para o Solution Explorer.
Depois temos de alterar as propriedades dos dois ficheiros para que sejam compilados como recursos, assim com o botão direito do rato abrimos as propriedades dos ficheiros e alteramos a propriedade Build Action para Resource.
As hipóteses são:
Agora o código no botão verificar, basta fazer duplo clique para abrir o editor do evento click no botão e inserir o seguinte código:
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click Dim palavra As String Dim linha As String Dim encontra As Boolean Dim streamreaderinfo As System.Windows.Resources.StreamResourceInfo encontra = False palavra = Me.TextBox1.Text If palavra.Length = 0 Then MessageBox.Show("Preencha com uma palavra por favor!") Exit Sub End If palavra = palavra.ToLower() If Me.RadioButton2.IsChecked() = True Then 'português streamreaderinfo = Application.GetResourceStream(New Uri("BidicionarioWP;component/portugues.txt", UriKind.Relative)) Else streamreaderinfo = Application.GetResourceStream(New Uri("BidicionarioWP;component/ingles.txt", UriKind.Relative)) End If Me.TextBlock1.Text = "A pesquisar..." Try Using f As New IO.StreamReader(streamreaderinfo.Stream) Do While f.EndOfStream = False linha = f.ReadLine() linha = linha.ToLower() If linha = palavra Then encontra = True Exit Do End If Loop End Using Catch ex As Exception MessageBox.Show("Erro no acesso ao ficheiro: " & ex.Message) Exit Sub End Try If encontra = False Then Me.TextBlock1.Text = "Palavra não encontrada!" MessageBox.Show("Palavra não encontrada!") Else Me.TextBlock1.Text = "Palavra encontrada!" MessageBox.Show("Palavra encontrada!") End If End Sub
Este código é muito mais simples do que a versão Android e a execução no emulador também é muito mais rápida, a Microsoft já aprendeu há muito tempo que as ferramentas de programação são fundamentais.
Quem já programou em VB.NET vai achar o código muito parecido com o que tem utilizado, incluindo a função que permite apresentar pequenas mensagens (messagebox).
A principal diferença está no modo como referenciamos os ficheiros de texto porque neste caso eles estão inseridos como recursos no projeto e não como ficheiro isolados no sistema de ficheiros do dispositivo.
Uma última palavra sobre o emulador do Windows Phone que é muito rápido e estável comparativamente com o emulador Android que é bastante pesado e lento.