sábado, 14 de fevereiro de 2009

J2ME e Bluetooth

Olá povo,

Uma das coisas mais legais de se desenvolver aplicações e jogos para celulares é a mobilidade que eles nos proporcionam. Uma tecnologia que está cada vez mais presente nos celulares mais populares no Brasil é o Bluetooth. Por isso, hoje vou falar um pouquinho sobre como desenvolver aplicações com essa tecnologia em Java ME.

Bluetooth é uma tecnologia utilizada para criar pequenas redes sem fio, também chamadas de WPANs (Wireless Personal Area Network). O alcance das redes Bluetooth normalmente é de 10 metros. Não vou filosofar muito sobre a pilha de protocolos, potências, etc. Informações mais detalhadas acesse o site oficial da tecnologia Bluetooth.

Antes de qualquer coisa precisaremos saber se o dispositivo para qual iremos desenvolver suporta a JSR-082. É ela que define o uso da tecnologia Bluetooth com Java ME. Consulte o site do fabricante para obter essa informação. Caso seu telefone não implemente essa JSR, você poderá testar esse exemplo no WTK.

O esquema abaixo demonstra como é estabelecida uma conexão entre dispositivos Bluetooth.



Note que primeiramente devemos estabelecer quem é o servidor. Ele ficará visível para que outros dispositivos o encontrem, aguardará por clientes e os proverá um serviço. O cliente por sua vez terá mais trabalho. Ele primeiro procurará por dispositivos (inquiry) disponíveis em sua área de alcance. Esses podem ser mouses, teclados, antenas ou qualquer outro dispositivo Bluetooth. Após obter essa lista de dispositivos, escolheremos um e verificaremos se ele está disponibilizando o serviço que nos interessa. Se estiver, a conexão entre os dois será estabelecida.

Agora, mãos à obra! Vamos listar agora as classes mais importantes do pacote javax.microedition.bluetooth.*:



LocalDevice - Representa o dispositivo Bluetooth local. É um singleton que pode ser obtido pelo método getLocalDevice(). Com ele podemos, entre outras coisas, dizer se o nosso aparelho será visível para os outros dispositivos, obter o endereço ou o nome do seu dispositivo Bluetooth entre outras.

LocalDevice myPhone = LocalDevice.getLocalDevice();
System.out.println(myPhone.getFriendlyName());

DiscoveryListener - Interface que é notificada quando os eventos de busca de dispositivos e serviços acontecem. Ela tem quatro métodos:

/*
Método chamado quando um dispositivo é econtrado.
Devemos armazenar (em um Vector por exemplo) o
remoteDevice para podermos buscar pelo serviço.
*/
void deviceDiscovered(RemoteDevice remoteDevice,
DeviceClass deviceClass);

/*
Método que é chamado quando a busca de dispositivos
termina.
discType - indica o resultado da busca:
INQUIRY_COMPLETED - Busca terminada com sucesso.
INQUIRY_TERMINATED - Busca cancelada.
INQUIRY_ERROR - Erro na busca.
*/
void inquiryCompleted(int discType);

/*
Método chamado quando a busca do serviço termina.
transID - Id da transação de busca pelo serviço
respCode - indica o resultado da busca:
SERVICE_SEARCH_TERMINATED - Busca cancelada.
SERVICE_SEARCH_ERROR - Erro na busca.
SERVICE_SEARCH_NO_RECORD - Dispositivo não tem o
serviço solicitado.
SERVICE_SEARCH_DEVICE_NOT_REACHABLE - Dispositivo
remoto está fora do alcance.
SERVICE_SEARCH_COMPLETED - Serviço encontrado com
sucesso.
*/
void serviceSearchCompleted(int transID, int respCode);

/*
Método chamando quando o serviço procurado é encontrado.
transID - Id da transação de busca pelo serviço
serviceRecord - informações do serviço encontrado.
*/
void servicesDiscovered(int transID, ServiceRecord[] serviceRecord);

DiscoveryAgent - Obtido através da instância do LocalDevice é o responsável por realizar a busca de dispositivos remotos na área de alcance do dispositivo local e de localizar o serviço no dispositivo remoto.

DiscoveryAgent agent = myPhone.getDiscoveryAgent();
agent.startInquiry(DiscoveryAgent.GIAC,
classeQueImplementeDiscoveryListener);

Com a chamada do startInquiry, cada vez que um dispositivo for encontrado a classe que implementa DiscoveryListener será notificada através do método deviceDiscovered. E quando o inquiry terminar o método inquiryCompleted será chamado.

RemoteDevices - representa um dispositivo Bluetooth remoto. Objetos dessa classe são retornados quando se realiza um inquiry (busca por dispositivos). Então devemos armazena-los em memória para que possamos fazer a busca de serviços com esse objeto através do objeto discoveryAgent.

agent.searchServices(null, uuidSet, remoteDevice,
classeQueImplementeDiscoveryListene);

Com a chamada desse método, quando o serviço procurado for encontrado a classe que implementa DiscoveryListener será notificada através do método servicesDiscovered, e quando a busca por serviços terminar o método serviceSearchCompleted será chamado.

Abrindo uma conexão (Servidor)
Lembrando que toda conexão utilizando GCF (Generic Connection Framework) deve ser feita em uma Thread separada, segue abaixo um trecho de código que abre uma conexão servidora e fica aguardando por conexões de clientes.

localDevice = LocalDevice.getLocalDevice();
int initialDiscoveredMode =
localDevice.getDiscoverable();
localDevice.setDiscoverable(DiscoveryAgent.GIAC);

StreamConnectionNotifier notifier =
(StreamConnectionNotifier) Connector.open(
"btspp://localhost:0000000000000000000BE3125CDAE");
connection = notifier.acceptAndOpen();
execute(connection);

Antes de abrir a conexão, o dispositivo é colocado em modo de descoberta para que outros possam encontra-lo. Depois é aberta a conexão servidora (localhost) utilizando "btspp" que é o protocolo de comunicação (BlueTooth Serial Port Profile). O número gigante :) após os ":" é o identificador do serviço UUID. O método execute trata os dados vindos do outro dispositivo. Segue abaixo o seu código:

protected void execute(StreamConnection conn){
try {
os = connection.openDataOutputStream();
is = connection.openDataInputStream();
String message;
while (true){
message = is.readUTF();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//closeStreams();
}
}

"os" e "is" são atributos da classe e devem ser um DataOutputStream e um DataInputStream.
Por último, o método que envia as mensagens:

public void send(String message){
try {
os.writeUTF(message);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

Bem pessoal, esse é só um aperitivo. Quem quiser saber mais, postem suas dúvidas.

[Editado]
Atendendo aos comentários, um exemplo bem simples pode ser baixado aqui.

[Editado 2]
E neste link vocês podem baixar um exemplo mais simplificado e comentado (mas precisando de algumas melhorias) do exemplo anterior.

4br4ç05,
nglauber

7 comentários:

Anônimo disse...

Muito bom ^^

Anônimo disse...

Vc pode me mandar esse código zipado amigo???

guto067@gmail.com

obrigado... abraços...

Renato Huard disse...

No seu codigo o executa() recebe como parametro uma StreamConnection "conn" e insere em os um "connection.openDataoutputStream();" deveria ser conn.open.. certo? estou tendo um pouco de dificuldades na parte do client, vc teria um exemplo.
poderia me ajudar? renato.jhs@gmail.com Valew, muito sucesso pra voce!

Anônimo disse...

E como seria o codigo para jogos multplayer.

Nelson Glauber disse...

Oi Anônimo...

No caso de jogos, você terá que fazer a lógica de sincronia entre os participantes do jogo. Esse trabalho é bem complicado em jogos "em tempo real" (como corrida e luta), mas em jogos "de turno" (como jogo da velha, damas, batalha naval) é bem mais simples. Em ambos os casos, isso é questão de lógica mesmo. Um envia, e outro recebe, depois isso e inverte e assim sucessivamente. É só estabelecer um protocolo de troca de mensagens entre as aplicações e realizar a sincronia.

Espero ter ajudado :)

Marcos Peres disse...

interessante seu artigo!
Estava pensando em um aplicativo para transferir arquivos entre um computador e um celular com a comunicação feita nós 2 sentidos. veja se estou certo na sequência de coisas que teria que fazer:
1- ativar o computador para deixa-lo visível.
2- procurar por dispositivos bluetooth e já verificar se o serviço solicitado está disponível (existe uma tabela dos códigos de serviço bluetooth disponíveis?).
3- enviar um arquivo para o(s) dispositivo(s) encontrado(s).
Daí o meu celular também deverá ter um software instalado para encontrar o servidor e enviá-lo outro arquivo. Certo?
Os 2 dispositivos serão cliente e servidor, é isso?
Valeu pela ajuda! Artigos como esse é que dão real utilidade à internet!
Grande abraço!

Nelson Glauber disse...

Oi Marcos,

Primeiro obrigado pelos elogios :)
No exemplo que mostrei com esse post, utilizei o BTSPP (Bluetooth Serial Port Profile) no entanto pra troca de arquivos o ideal seria utilizar OBEX (OBject EXchange) porém não conheço nenhum aparelho que implemente esse protocolo em Java ME :(
Mas dá pra fazer o que você quer com o BTSPP, desde que o aparelho suporte a JSR-75 (File API) para você gravar o arquivo no telefone. A comunicação é um pouco lenta por ser serial, mas funciona sim. Na parte do PC, dá uma olhada no bluecove, vai te ajudar.