Para este post vamos desenvolver uma aplicação, ou como se dizia antigamente um programa, para calcular notas finais de uma disciplina do ensino secundário português.
Como a interface é relativamente grande precisamos de permitir deslocar o conteúdo verticalmente.
Para validar os dados nas textboxes vamos criar uma função que implementa a função isNumeric da versão desktop do Visual Basic.
Public Function isNumeric(ByVal str As String) As Boolean
Dim t As Decimal = 0
Return Decimal.TryParse(str, t)
End Function
Se a string poder ser convertida num número devolve true se não devolve false.
O primeiro botão serve para calcular a chamada CIF (Classificação Interna Final) que corresponde à média das notas do 10º, do 11º e do 12º ano.
Para isso temos de verificar se cada textbox tem um valor numérico caso contrário não consideramos o seu conteúdo.
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim n10, n11, n12, media, exame, conta As Integer
Dim notamin As Double
If isnumeric(Me.tb_nota10.Text) = True Then
n10 = Me.tb_nota10.Text
conta = 1
End If
If isnumeric(Me.tb_nota11.Text) = True Then
n11 = Me.tb_nota11.Text
conta = conta + 1
End If
If isnumeric(Me.tb_nota12.Text) = True Then
n12 = Me.tb_nota12.Text
conta = conta + 1
End If
If conta = 0 Then
MessageBox.Show("Tem de introduzir pelo menos uma nota!")
Exit Sub
End If
media = (n10 + n11 + n12) / conta
Me.lb_media.Text = media
notamin = media * 0.7
For exame = 1 To 20
If notamin + exame * 0.3 >= 9.5 Then
Exit For
End If
Next
Me.tb_exame.Text = exame
End Sub
O segundo botão calcula a CFD (classificação Final da Disciplina).
Private Sub Button2_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button2.Click
Dim cif, exame, cfd As Integer
If isnumeric(lb_media.Text) = False Then
MessageBox.Show("Calcule primeiro a CIF")
Exit Sub
End If
If isnumeric(tb_nota_exame.Text) = False Then
MessageBox.Show("Indique a nota do exame!")
Exit Sub
End If
cif = lb_media.Text
exame = tb_nota_exame.Text
cfd = cif * 0.7 + exame * 0.3
Me.tb_cfd.Text = cfd
End Sub
O terceiro botão guarda a CFD na listbox.
Private Sub Button3_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button3.Click
If isnumeric(Me.tb_cfd.Text) = False Then
MessageBox.Show("Calcule primeiro a CFD!")
Exit Sub
End If
Me.ListBox1.Items.Add(Me.tb_cfd.Text)
End Sub
Por fim temos uma hiperligação que permite abrir uma página da net.
Private Sub HyperlinkButton1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles HyperlinkButton1.Click
Dim pagina As WebBrowserTask = New WebBrowserTask()
pagina.Uri = New Uri("http://alunosnet.pt.vu", UriKind.Absolute)
pagina.Show()
End Sub
Projeto para download
quinta-feira, 28 de fevereiro de 2013
sexta-feira, 8 de fevereiro de 2013
RFID Tags
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.
O projeto pode ser encontrado aqui.
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.
O projeto pode ser encontrado aqui.
sexta-feira, 25 de janeiro de 2013
Vamos acelerar
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.
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this,mSensor,SensorManager.SENSOR_DELAY_NORMAL);
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.
Por fim o layout da atividade que fica assim:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#FFFFFF">
<edu.pjcferreira.labirinto.ecran
android:id="@+id/ecran"
android:layout_height="fill_parent"
android:layout_width="fill_parent"/>
</LinearLayout>
É 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.
O projeto e a aplicação.
sexta-feira, 4 de janeiro de 2013
i-Racer
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.
O xml:
<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" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="false"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:layout_marginTop="4dp"
android:orientation="horizontal" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onLigarClick"
android:text="@string/ligar" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onProcurarClick"
android:text="@string/procurar" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onAbrirClick"
android:text="@string/abrir" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="false"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:layout_marginTop="58dp"
android:orientation="horizontal" >
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onFrenteClick"
android:text="@string/frente" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onPararClick"
android:text="@string/parar" />
<Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onEsquerdaClick"
android:text="@string/esquerda" />
<Button
android:id="@+id/button7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onDireitaClick"
android:text="@string/direita" />
<Button
android:id="@+id/button8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onSairClick"
android:text="@string/sair" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/linearLayout1"
android:layout_marginLeft="0dp"
android:layout_marginTop="82dp"
android:orientation="vertical" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
</RelativeLayout>
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){}
}
}
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.
O xml:
<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" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="false"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:layout_marginTop="4dp"
android:orientation="horizontal" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onLigarClick"
android:text="@string/ligar" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onProcurarClick"
android:text="@string/procurar" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onAbrirClick"
android:text="@string/abrir" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="false"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:layout_marginTop="58dp"
android:orientation="horizontal" >
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onFrenteClick"
android:text="@string/frente" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onPararClick"
android:text="@string/parar" />
<Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onEsquerdaClick"
android:text="@string/esquerda" />
<Button
android:id="@+id/button7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onDireitaClick"
android:text="@string/direita" />
<Button
android:id="@+id/button8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onSairClick"
android:text="@string/sair" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/linearLayout1"
android:layout_marginLeft="0dp"
android:layout_marginTop="82dp"
android:orientation="vertical" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
</RelativeLayout>
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.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//verifica se existe bt neste dispositivo
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter==null){
showMessage("Não tem suporte para bluetooth!");
}else{
showMessage("Tem suporte para bluetooth!");
}
//lista de dispositivos
mArrayAdapter = new ArrayAdapter<String>(this,R.layout.simplerow,0);
//registar a função que vai receber os dispositivos encontrados
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
//tambem recebe a mensagem que terminou a pesquisa
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
//lista de dispositivos
listView = (ListView) findViewById(R.id.listView1);
}
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){
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
showMessage(device.getName());
}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
// showMessage("Terminado! Encontrados " + mArrayAdapter.getCount() + " dispositivos!");
listView.setAdapter(mArrayAdapter);
listView.setClickable(true);
//função a chamar quando clicar num item da lista
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
Object o = listView.getItemAtPosition(position);
showMessage("Selecionou " + o.toString());
//tenta ligar ao dispositivo
dispositivo=position;
String[] endereco=o.toString().split("\n");
/* showMessage(endereco[0]);
showMessage(endereco[1]);*/
remoteBluetoothDevice= mBluetoothAdapter.getRemoteDevice(endereco[1]);
}
});
}
}
};
De seguida precisamos de uma função para pesquisar dispositivos Bluetooth, quer os que estão emparelhados quer outros:
public void onProcurarClick(View botton) {
//primeiro vamos procurar dispositivos emparelhados
pairedDevices=mBluetoothAdapter.getBondedDevices();
//se existem
if(pairedDevices.size()>0){
for(BluetoothDevice device : pairedDevices){
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
showMessage(device.getName());
}
}
//procurar novos dispositivos
mBluetoothAdapter.startDiscovery();
}
Posto isto podemos abrir uma ligação com o dispositivo selecionado:
public void onAbrirClick(View botton) {
if(remoteBluetoothDevice==null){
showMessage("Primeiro deve selecionar o dispositivo!");
return;
}
ligacao=new ConnectThread(remoteBluetoothDevice);
ligacao.run();
showMessage(ligacao.getEstado());
}
Agora podemos enviar os comandos, para isso temos uma função para cada comando:
public void onFrenteClick(View botton) {
byte x;
x=0x1B;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
ligacao.enviar(x);
}
public void onEsquerdaClick(View botton) {
byte x;
x=0x36;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
ligacao.enviar(x);
}
public void onDireitaClick(View botton) {
byte x;
x=0x46;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
ligacao.enviar(x);
}
public void onPararClick(View botton){
byte x;
x=0x00;
if(ligacao==null){
showMessage("Primeiro tem de abrir uma ligação.");
return;
}
ligacao.enviar(x);
}
Antes de terminarmos precisamos de limpar a casa, o botão sair e ainda a função onDestroy:
public void onSairClick(View botton) {
byte x;
x=0x00;
if(ligacao!=null){
ligacao.enviar(x);
ligacao.cancel();
}
finish();
}
protected void onDestroy() {
byte x;
x=0x00;
if(ligacao!=null){
ligacao.enviar(x);
ligacao.cancel();
}
this.unregisterReceiver(mReceiver);
super.onDestroy();
}
Quase me esquecia as permissões, neste projeto precisamos de duas:
- Permissão para utilizar Bluetooth:
<uses-permission android:name="android.permission.BLUETOOTH"/>
- Permissão para pesquisar dispositivos com o Bluetooth:
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
sexta-feira, 28 de dezembro de 2012
Time's Up com ficheiros
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("");
}
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());
}
}
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:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
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ê.
Time's Up
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.
A interface
Vamos começar pela interface. O código é:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<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="wrap_content" >
<Button
android:id="@+id/bt_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="bt_guardar_click"
android:text="@string/guardar" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/bt_start"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/bt_iniciar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/bt_start"
android:onClick="bt_iniciar_click"
android:text="@string/inicio" />
<Button
android:id="@+id/bt_continuar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/bt_iniciar"
android:onClick="bt_continuar_click"
android:text="@string/reiniciar" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView2"
android:layout_alignBottom="@+id/textView2"
android:layout_toRightOf="@+id/bt_start"
android:text="@string/intermedios"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
</ScrollView>
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
h=(int)cal.getTimeInMillis()/(1000*60*60);
Por fim criei um menu com duas opções:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/item1" android:title="Sobre"></item>
<item android:id="@+id/item2" android:title="Sair"></item>
</menu>
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.
A interface
Vamos começar pela interface. O código é:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<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="wrap_content" >
<Button
android:id="@+id/bt_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="bt_guardar_click"
android:text="@string/guardar" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/bt_start"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/bt_iniciar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/bt_start"
android:onClick="bt_iniciar_click"
android:text="@string/inicio" />
<Button
android:id="@+id/bt_continuar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/bt_iniciar"
android:onClick="bt_continuar_click"
android:text="@string/reiniciar" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView2"
android:layout_alignBottom="@+id/textView2"
android:layout_toRightOf="@+id/bt_start"
android:text="@string/intermedios"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
</ScrollView>
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
h=(int)cal.getTimeInMillis()/(1000*60*60);
Por fim criei um menu com duas opções:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/item1" android:title="Sobre"></item>
<item android:id="@+id/item2" android:title="Sair"></item>
</menu>
O código que cria o menu e responde às opções é apresentado a seguir:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_times_up, menu);
return true;
}
//opções do menu
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getTitle().toString().equals("Sobre"))
showMessage("Paulo Ferreira");
if(item.getTitle().toString().equals("Sair")){
showMessage("That's all folks!");
finish();
}
return true;
}
A primeira função é criada pelo Eclipse automaticamente enquanto que a segunda é a que reage ao clique nas opções dos menus.
A função showMessage é foi apresentada noutros projetos e somente cria uma Toast com o texto que lhe foi passado:
/* mostra o texto passado numa pequena mensagem */
private void showMessage(CharSequence text) {
Context context = getApplicationContext();
int duration = Toast.LENGTH_SHORT;
//faz aparecer uma mensagem pequena durante um certo tempo
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
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:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
Basta adicionar no inicio da função onCreate.
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.
Subscrever:
Mensagens (Atom)
















