Aegidius
 Plüss   Aplulogo
     
 www.aplu.ch      Print Text 
© 2021, V10.4
 
  Bluetooth Library
 
 

 

Bluetooth Package for J2SE/J2ME

Bluetooth provides a way to exchange information between devices such as mobile phones, telephones, laptops, personal computers, printers, digital cameras, video game consoles, etc. using the 2.4 GHz short-range radio frequency band. The Bluetooth specifications are developed and licensed by the Bluetooth Special Interest Group (SIG). JSR-82 (Java Specification Request) standardizes a set of Java APIs to allow Java-enabled devices to integrate into a Bluetooth environment. The ch.aplu.bluetooth package simplifies the use of JSR-82 and let you write client-server applications using Bluetooth with only basic knowledge of Java.

In a typical client-server scenario one device called the server waits in a blocked state where it "listens" for an incoming request from another device called a client, to establish a connection. For Bluetooth the server exposes his service on a channel with an integer channel number attributed dynamically by the operating system. The service has a specific service name and service properties called UUID. Bluetooth uses those UUIDs to uniquely identify a service among those exposed by a given device. The most common UUIDs are those for the virtual serial port (RFCOMM) and the OBEX file transfer service.

Every Bluetooth device has a unique Bluetooth address, which is 48 bit long (normally expressed in 12 hex digits) and a Bluetooth "friendly name", a string of printing ASCII characters.

A client must engage a connection by "searching" the server device. To speed up this process, an operating system utility called BCC (Bluetooth Control Center) can perform the device (and service) search and store the devices and their services in an internal database. Stored devices are called "preknown". For security reasons the "pairing" process of two Bluetooth devices requests a security code that must be confirmed on the remote device.

After the device and service search, the client engages the communication by calling the open() method of the Connector class. If the link is established, an Input- and OutputStream reference is available both on the client and server side that is used to transfer data.

The whole process involves good knowledge of the JSR-82 Bluetooth library. The package ch.aplu.bluetooth sits on top of the JSR-82 library in order to hide the internal mechanism as much as possible. With the package it becomes straightforward to write a Bluetooth client-server application.

The package is available for J2SE using the Bluecove JSR-82 library and for J2ME using the JSR-82 implementation available in Sun's WTK (Wireless Toolkit). Therefore applications can be ported from desktop PCs to mobile phones and vice versa with little effort.

 

A simple client-server demonstration

In a typical client-server application a Bluetooth server offers the service to do some lengthy calculations for a remote client. For the purpose of the demonstration the server just inverts a integer number and sends it back to the client. The server and the client runs on two different desktop PCs. There is no need to "pair" the PCs, because an extensive Bluetooth search is performed.

The server must be started before the client, so we show the server code first:

// BtServer.java
// Data exchange with BtClient.java

import
 java.io.*;
import
 javax.bluetooth.*;
import
 ch.aplu.bluetooth.*;
import
 javax.swing.JOptionPane;

public
 class BtServer implements BtListener
{
  
private final String serviceName = "BtServer";
  
private BluetoothServer bs;

  
public BtServer()
  
{
    bs 
= new BluetoothServer(serviceName, thisfalse);
    
System.out.println("Waiting for a client exposing service '" + 
                        serviceName 
+ "'");
    
int rc;
    
do
    
{
      rc 
= JOptionPane.
        
showConfirmDialog(null"Stopping the server?"
                          
"BtServer"JOptionPane.YES_NO_OPTION);
    
}
    
while (rc != JOptionPane.YES_OPTION);
    bs.
close();  // Announce termination to client
    bs.
cancel(); // No more client to wait for
    
System.exit(0);
  
}

  
// Callback when a connection is established
  
public void notifyConnection(RemoteDevice rd, 
                               
InputStream is, OutputStream os)
  
{
    
// Retrieve streams
    
DataInputStream dis = new DataInputStream(is);
    
DataOutputStream dos = new DataOutputStream(os);

    
try
    
{
      
while (true)
      
{
        
// Wait for data
        
int n = dis.readInt();
        
System.out.println("Got: " + n);

        
// Reply inverse
        dos.
writeInt(-n);
        dos.
flush();  // Don't forget to flush
      
}
    
}
    
catch (IOException ex)
    
{
      bs.
close();
    
}
    
System.out.println("Transfer finished.\n" +
                       
"Waiting for the next client");
  
}
  
  
public static void main(String[] args)
  
{
    
new BtServer();
  
}
}


Execute BtServerC using WebStart
(Widcomm compatible Bluetooth device needed, System.out is redirected to a console window, stopping the server by closing the window)

Discussion: The constructor of the class BluetoothServer takes the arbitrary name of the service, a reference to a BtListener that implements notifyConnection() and a boolean flag that may be set to true if verbose output to System.out or a log file is desired. When it returns, the server is up and running. We block the main thread in a simple modal dialog, but we must be careful to shutdown the server by closing the streams and cancel the listener when the application terminates.

All important work for the data exchange is done in the callback method notifyConnection() that is called whenever a client successfully connects. Using the passed Input- and OutputStream references we convert them for convenience to DataInput- and DataOutputStreams respectively in order to use readInt() and writeInt() to read a single integer from the client and to send back the result.

It is important to note that readInt() is a blocking method that waits until a integer is received or an end of stream is detected. The latter occurs when the client connection is lost because the client terminates. The endless while-loop is abandoned because of the IOException and the server waits for the next client to connect.

The client application first asks for the server's Bluetooth name. It then constructs a BluetoothClient instance with the given Bluetooth and the hard-wired service name. The connection trial is engaged by calling connect() that returns false, if the connection fails. If successful, the Input- and OutputStream references are fetched, converted for convenience to DataInput- and DataOutputStreams respectively and used to send and receive the integer values. Like for the server, readInt() is blocking. If the server quits before the end of the data transfer, an end of stream is detected that throws an IOException. This takes us out of the blocking readInt().

// BtClient.java
// Data exchange with BtServer.java

import
 java.io.*;
import
 ch.aplu.bluetooth.*;
import
 javax.swing.JOptionPane;

public
 class BtClient
{
  
private final String serviceName = "BtServer";
  
private BluetoothClient bc;
  
private DataInputStream dis;
  
private DataOutputStream dos;

  
public BtClient()
  
{
    
String prompt = "Enter Server's Bluetooth Name";
    
String serverName;
    
do
    
{
      serverName 
= JOptionPane.showInputDialog(null, prompt);
      
if (serverName == null)
        
System.exit(0);
    
}
    
while (serverName.trim().length() == 0);

    bc 
= new BluetoothClient(serverName, serviceName);
    
System.out.println("Trying to connect to '" + serverName +
      
"' using service '" + serviceName + "'");
    
if (!bc.connect())
    
{
      
System.out.println("Connection failed");
      
return;
    
}

    
System.out.println("Connection established");
    dis 
= new DataInputStream(bc.getInputStream());
    dos 
= new DataOutputStream(bc.getOutputStream());

    
try
    
{
      
for (int n = 1; n <= 100; n++)
      
{
        
System.out.println("Send value: " + n);
        dos.
writeInt(n);
        dos.
flush();

        
// Blocking, throws IOException when server closes
        
// connection or BluetoothClient.disconnect() is called
        
int k = dis.readInt();
        
System.out.println("Reply value: " + k);
      
}
    
}
    
catch (IOException ex)
    
{
      
System.out.println("readInt() unblocked");
    
}
    bc.
disconnect();
    
System.exit(0);
  
}

  
public static void main(String[] args)
  
{
    
new BtClient();
  
}
}

Execute BtClient using WebStart.
(Widcomm compatible Bluetooth device needed, System.out is redirected to a console window, stopping the client by closing the window)

Discussion: If the server goes away before the end of the data transfer without announcing it by closing his output stream, the client hangs in the blocking readInt(). To be able to react to this condition it is recommended to perform the data transfer in a special transfer thread. The main thread can check for a timeout or terminate the application on a user request. The source for this improved version of BtClient is included in the package distribution.

In most cases there are better choices than Bluetooth to link desktop computers. Because the ch.aplu.bluetooth classes and methods are the same for J2SE and J2ME, it is simple to migrate from the PC to mobile phones applications, especially when the Gidlet framework is used. Of course the display handling in J2SE and J2ME is quite different, so most work has to be spent to adapt the user interface. But as you see in the following MIDlet versions of BtServer and BtClient the general ideas remain the same.

// BtServer.java
// Data exchange with BtClient.java

import
 java.io.*;
import
 javax.bluetooth.*;
import
 ch.aplu.bluetooth.*;
import
 ch.aplu.gidlet.*;

public
 class BtServer extends Gidlet implements BtListener
{
  
private BluetoothServer bs;
  
private int nbConnections = 0;
  
private MConsole c = new MConsole();
  
private boolean isQuitting = false;

  
public void main()
  
{
    bs 
= new BluetoothServer("BtServer"thisfalse);
    c.
println("OK to stop server");
    c.
println("Wait for connection");
    
waitOk();  // Block until OK is clicked
    isQuitting 
= true;
    bs.
close();  // Announce termination to client
    bs.
cancel(); // No more client to wait for
    c.
println("Server dead");
  
}

  
public void notifyConnection(RemoteDevice rd, 
                               
InputStream is, OutputStream os)
  
{
    nbConnections
++;
    c.
println("# calls: " + nbConnections);
    
try
    
{
      c.
println("Connection established.");
      c.
println("Name: " + rd.getFriendlyName(false));
      c.
println("Address " + rd.getBluetoothAddress());
    
}
    
catch (IOException ex)
    
{
      c.
println("Got exception\nwhile retrieving client info");
      
return;
    
}

    
// Retrieve streams
    
DataInputStream dis = new DataInputStream(is);
    
DataOutputStream dos = new DataOutputStream(os);

    
try
    
{
      
while (true)
      
{
        
// Wait for data
        
int n = dis.readInt();
        c.
println("Got: " + n);
        
delay(1000);  // Sleep a while to simulate hard work

        
// Reply inverse
        dos.
writeInt(-n);
        dos.
flush();  // Don't forget to flush
      
}
    
}
    
catch (IOException ex)
    
{
      bs.
close();
    
}
      c.
println("Transfer finished");
      
if (!isQuitting)
        c.
println("Waiting next connection");
  
}
}

Download JAR/JAD files for installation on mobile phone. (You can install it directly on your mobile phone from http://www.aplu.ch/g.)

Discussion: For demonstration and testing is convenient to use MConsole that behaves much like a usual System.out console (println-methods, vertical and horizontal scrolling). Because of the compatibility between the J2SE and J2ME Bluetooth package, the program works perfectly with a BtClient running on a PC. But BTClient is also easily ported to J2ME. To improve response behaviour we use a special transfer thread for the data exchange.

// BtClient.java
// Data exchange with BtServer.java

import
 java.io.*;
import
 javax.microedition.lcdui.*;
import
 ch.aplu.gidlet.*;
import
 ch.aplu.bluetooth.*;

public
 class BtClient extends Gidlet
{
  
// ------------- Inner class TransferThread -----------
  
private class TransferThread extends Thread
  
{
    
public void run()
    
{
      
try
      
{
        
for (int n = 1; n <= 100; n++)
        
{
          dos.
writeInt(n);
          dos.
flush();

          
// Blocking, throws IOException when server closes
          
// connection or BluetoothClient.disconnect() is called
          
int k = dis.readInt();
          it.
setText("Sent: " + n + "\nReply: " + k);
        
}
      
}
      
catch (IOException ex)
      
{
        it.
setText("readInt() unblocked");
        
delay(2000);
      
}
      bc.
disconnect();
      it.
setText("Transfer thread terminated");
    
}
  
}
  
// ------------- End of inner class -------------------

  
private final boolean isVerbose = false;

  
private String[] names = {""""};
  
private BluetoothClient bc;
  
private DataInputStream dis;
  
private DataOutputStream dos;
  
private TextField tf1;
  
private TextField tf2;
  
private StringItem it;
  
private volatile boolean ok = false;
  
private MForm form = new MForm("BtClient");

  
public void main()
  
{
    
if (isVerbose)
      VerboseWriter.
init("e:/debug/BtClient.txt");
    
// Get old values from StringStore to avoid to much fingertips
    StringStore store 
= new StringStore("BtClientStore");
    
String content = store.read();
    
if (content != null)
      names 
= split(content, ";");

    tf1 
= form.addInputField("Server name:", names[0]);
    tf2 
= form.addInputField("Service name:", names[1]);
    it 
= form.addOutputField();
    form.
show();
    
while (!ok)
      
waitOk();

    store.
write(names[0] + ";" + names[1]);
    it.
setText("Trying to connect to\n" + names[0] +
                
"\nwith service\n" + names[1]);

    bc 
= new BluetoothClient(names[0], names[1]);
    
if (isVerbose)
      bc.
setVerbose(true);
    
if (!bc.connect())
    
{
      it.
setText("Connection failed");
      VerboseWriter.
close();
      
return;
    
}

    dis 
= new DataInputStream(bc.getInputStream());
    dos 
= new DataOutputStream(bc.getOutputStream());

    it.
setText("Connection established");
    
delay(2000);
    
new TransferThread().start();
  
}

  
public void doOk()
  
{
    names
[0] = form.getText(tf1);
    names
[1] = form.getText(tf2);
    
if (names[0].equals(""|| names[1].equals(""))
      it.
setText("Illegal entry");
    
else
      ok 
= true;
  
}

  
public void doExit()
  
{
    
if (bc != null)
      bc.
disconnect();
    
if (isVerbose)
      VerboseWriter.
close();
    
super.doExit();
  
}
}

Download JAR/JAD files for installation on mobile phone. (You can install it directly on your mobile phone from http://www.aplu.ch/g.)

Discussion: For convenience we store server and service names from one invocation to the another in the mobile phone's RMS (Record Managing System) by using Gidlet's StringStore.