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.
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 ccom.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:
-
META-INF/MANIFEST.MF - OSGI manifest that describes the bundle and its dependencies.
-
OSGI-INF/bluetoothExample.xml - declarative services definition that describes the services exposed and consumed by this bundle.
-
OSGI-INF/metatype/com.eurotech.example.bluetooth.BluetoothExample.xml - configuration description of the bundle and its parameters, types, and defaults.
-
com.eurotech.example.bluetooth.BluetoothExample.java - main implementation class.
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.
Updated less than a minute ago