How to Use Bluetooth 2.1

This document describes how to configure an ESF bundle to use Bluetooth 2.1. This bundle provides the ability to perform a scan for near devices and setup a SP serial connection in server or client mode.

🚧

The support for Bluetooth 2.1 is not available in the ReliaGATE 20-25 since Eurotech Linux Version 22.0.0.
The support is also not available for the BoltGATE 20-25.

Prerequisites

  • Development Environment Setup
  • Hardware
    • Use an embedded device running ESF with Bluetooth 2.1 capabilities
    • Use a device with Bluetooth 2.1 capabilities for test the serial connection

This tutorial uses a Raspberry Pi Type B with a LMTechnologies LM506 Bluetooth 4.0 http://lm-technologies.com/wireless-adapters/lm506-class-1-bluetooth-4-0-usb-adapter/ dongle.

Prepare the Embedded Device

Follow the instructions located here to setup the device for Bluetooth communication.

Once the bluez tools are installed on both devices, check discovery and serial connection via shell.

To discover near devices, enter the following command in a shell:

hcitool -i hci0 scan

To setup an SP serial connection between two devices, start an SP server on the first device using the following command:

sudo rfcomm listen hci0 1

With this command, an SP serial server will listen for incoming connections on channel 1 using the hci0 interface.

To connect to the server from the second device, enter the following command:

sudo rfcomm connect /dev/rfcomm0 <ADDRESS> <CHANNEL>

where

is the BT server address and is the channel used for the serial connection (1 in this example). If the connection is successful, communication will be enabled between the two devices using minicom or the cat/echo commands on /dev/rfcomm0.

Develop the Bluetooth 2.1 Bundle

Once the required packages and libraries are installed on the embedded device, you may start to develop the Bluetooth 2.1 bundle that allows the user to perform discovery and setup serial communication in server or client mode.

Start developing the bundle as follows:

📘

For more detailed information about bundle development (i.e., the plug-in project, classes, and MANIFEST file configuration), please refer to the Hello World Example.

  • Create a Plug-in Project named com.eurotech.example.bluetooth.

  • Create a class named BluetoothExample in the com.eurotech.example.bluetooth project.

  • Include the following bundles in the MANIFEST.MF:

    • javax.bluetooth - 2.1.2
    • javax.microedition.io
    • org.eclipse.kura.bluetooth
    • org.eclipse.kura.configuration
    • org.osgi.service.component
    • org.slf4j

The following files need to be implemented in order to write the source code:

OSGI-INF/metatype/com.eurotech.example.bluetooth.BluetoothExample.xml File

The OSGI-INF/metatype/com.eurotech.example.bluetooth.BluetoothExample.xml file describes the parameters for this bundle including the following:

  • bluetooth.test.enabled - starts the BT interface.

  • bluetooth.mode - specifies the BT modality.

  • btspp.client.url - defines the URL of the BT SPP server if the BT modality is set to client.

com.eurotech.example.bluetooth.BluetoothExample.java File

The com.eurotech.example.bluetooth.BluetoothExample.java file contains the activate and deactivate methods for this bundle. The activate method configures the BT adapter according to the bluetooth.mode setting.

In discovery mode, the following code is executed:

private void doDiscovery() {
    
    LocalDevice localDevice = null;
    try {
        
        if (m_remoteDevices == null) {
            m_remoteDevices = new Vector<RemoteDevice>();
        }
        
        localDevice = LocalDevice.getLocalDevice();
        localDevice.setDiscoverable(DiscoveryAgent.GIAC);

        //find devices
        m_discoveryAgent = localDevice.getDiscoveryAgent();
        s_logger.info("Starting device inquiry...");
        m_discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
        
        try {
            synchronized(lock){
                lock.wait();
            }
        }
            catch (InterruptedException e) {
            e.printStackTrace();
        }

        s_logger.info("Device Inquiry Completed.");

        //print all devices in vecDevices
        int deviceCount = m_remoteDevices.size();
        if(deviceCount <= 0) {
            s_logger.info("No Devices Found .");
        }
        else{
            //print bluetooth device addresses and names in the format [ No. address (name) ]
            s_logger.info("Bluetooth Devices: ");
            for (int i = 0; i < deviceCount; i++) {
                RemoteDevice remoteDevice = (RemoteDevice) m_remoteDevices.elementAt(i);
                String friendlyName = remoteDevice.getFriendlyName(false);
                if (friendlyName != null)
                    s_logger.info(remoteDevice.getBluetoothAddress() + " " + remoteDevice.getFriendlyName(false));
                else
                    s_logger.info(remoteDevice.getBluetoothAddress() + " n/a");
            }
        }

    } catch (BluetoothStateException e) {
        s_logger.error("BluetoothStateException: " + e.getLocalizedMessage(), e);
    } catch (IOException e) {
        e.printStackTrace();
    } 

}

The code receives the LocalDevice instance and the DiscoveryAgent and uses the latter to start the inquiry. The execution of the code is stopped until the lock object is notified. The bundle must implement the javax.bluetooth.DiscoveryListener interface in order to manage the results from the discovery process.

The DiscoveryListener API implements the following methods:

@Override
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
        ...
}

@Override
public void inquiryCompleted(int arg0) {
    synchronized(lock) {
        s_logger.info("Device inquiry complete.");
        lock.notify();
    }
}

@Override
public void serviceSearchCompleted(int arg0, int arg1) {
        ...
}

@Override
public void servicesDiscovered(int arg0, ServiceRecord[] serviceRecords) {
        ...
}

Note that the inquiryCompleted method unlocks the lock object and ends the discovery.

When bluetooth.mode is set to server mode, the following code is executed:

private void doServer() {
    s_logger.info("Starting server...");
    LocalDevice localDevice;
    StreamConnectionNotifier notifier;

    try {
        // Get local device and make discoverable
        localDevice = LocalDevice.getLocalDevice();
        localDevice.setDiscoverable(DiscoveryAgent.GIAC);
        
        // Wait for incoming connections
        notifier = (StreamConnectionNotifier) m_bluetoothConnector.open(SERVER_URL);
        
        s_logger.info("Waiting for incoming connection...");
        m_streamConnection = notifier.acceptAndOpen();
        
        // Client connected, echo responses
        s_logger.info("Client Connected...");
        m_dataInputStream = new DataInputStream (m_streamConnection.openInputStream());
        while(m_inputStreamPoll){
            String cmd = "";
            int b;
            while ((b = m_dataInputStream.read()) > 0) {
                if ((b != '\n') && (b != '\r')) {
                    s_logger.debug("Received char : " + (char) b);
                    cmd = cmd + (char) b;
                    b = 0;
                } else {
                    if (cmd != "")
                        s_logger.info("Received " + cmd);
                }
            }
            // If DataInputStream is closed, then close also the connection.
            deactivate();
            notifier.close();
            
        }
        
        // Connection closed
        s_logger.info("Connection closed.");
        
    } catch (Exception e) {
        s_logger.error("Error: " + e.getLocalizedMessage(), e);
    }
}

Note that the code is blocked until a client is connected, and then the code listenes for messages from the client. When the connection is closed from client or server side, the deactivate method is executed and the streams and notifier are closed.

When operating in client mode, the bundle uses the btspp.client.url setting to connect.

private void doClient() {
    try {
        s_logger.info("Starting client...");
        String url = (String) m_properties.get(BTSPP_CLIENT_URL);
        if ("btspp://".equals(url) || url.isEmpty() || url == null) {
            throw new Exception();
        }
        
        // Open connection to remove device
        s_logger.info("Opening connection with: " + url);
        m_streamConnection = (StreamConnection) m_bluetoothConnector.open(url);
        
        if (m_streamConnection != null) {
            s_logger.info("Connected!");
            // Open streams
            m_dataOutputStream = (DataOutputStream) m_streamConnection.openDataOutputStream();
            m_dataInputStream = (DataInputStream) m_streamConnection.openDataInputStream();
            
            // Send message to server
            if (m_dataOutputStream != null) {
                s_logger.info("Have output stream, will write...");
                String data = "Hello Server!\r";
                m_dataOutputStream.write(data.getBytes("UTF-8"));
                m_dataOutputStream.flush();
                s_logger.info("Wrote");
            }
            else {
                s_logger.info("DataOutputStream is null");
            }
            
            // Wait for messages from server
            while(m_inputStreamPoll){
                String cmd = "";
                int b;
                while ((b = m_dataInputStream.read()) > 0) {
                    if ((b != '\n') && (b != '\r')) {
                        s_logger.debug("Received char : " + (char) b);
                        cmd = cmd + (char) b;
                        b = 0;
                    } else {
                        if (cmd != "") {
                            s_logger.info("Received " + cmd);
                            cmd = "";
                        }
                        
                    }
                }
                // If DataInputStream is closed, then close also the connection.
                deactivate();
                
            }
            
            // Connection closed
            s_logger.info("Connection closed.");
        }
        else {
            s_logger.info("StreamConnection is null.");
        }

    } catch (Exception e) {
        s_logger.error("Error: " + e.getLocalizedMessage(), e);
    }
}

When in client mode, the bundle sends a string to the server and waits for the reply. Also in this case, when the connection is closed, the deactivate method is executed and the streams are closed.

Deploy and Validate the Bundle

To validate the bundle, deploy it on ESF using a deployment package or mToolkit. You can follow the mToolkit instructions for installing a single bundle to the remote target device located here.

Then, configure the bundle through the Gateway Administration Console and check the kura.log file to view the results. For the serial communication, use a device as described in How to Use Bluetooth 2.1.