IceBT

9 min read

9 min read

9 min read

9 min read

9 min read

9 min read

9 min read

9 min read

9 min read

IceBT is a transport plug-in that allows clients and servers to communicate over Bluetooth RFCOMM connections. Ice provides IceBT for C++ on Linux and for Java on Android. On Linux, Python, Ruby, PHP and MATLAB applications can also load the IceBT shared library.

IceBT is an Ice plug-in that must be installed in the clients and servers that need to communicate over Bluetooth. This section reviews some concepts that will help you as you learn more about IceBT.

The Bluetooth specification defines a standard mechanism for discovering services called the Service Discovery Protocol (SDP). It's a flexible but complex specification that accommodates a wide range of Bluetooth device functionality and requirements. Fortunately, Ice users only need a passing familiarity with SDP.

The operating system's Bluetooth stack implements an SDP service that provides two basic functions: query and registration. IceBT considers each object adapter endpoint in an Ice server to be a service and adds a corresponding entry for it in the local SDP registry. This entry associates a UUID with a human-friendly name and an RFCOMM channel. For example, an entry might contain:

Name: My Bluetooth Service
UUID: 1c6a142a-aae6-4d58-bef8-33196f531da7
RFCOMM: Channel #8

The SDP entry automatically expires when its service terminates.

An Ice client requires two values to connect to a server:

  1. The server's Bluetooth device address (such as 01:23:45:67:89:AB)
  2. The UUID of the desired service

To establish a connection, the client first queries the SDP service on the server device for an entry matching the target UUID. If a match is found, the SDP service returns the server's current RFCOMM channel, and the client opens a connection to that channel.

IceBT takes care of all of this for you during server initialization and connection establishment.

When developing a client application, you'll normally hard-code the UUIDs of the remote services that your client requires because those UUIDs must match the ones advertised by your servers. However, in addition to a UUID, a client also needs to know the device address on which a service is running. Typically the client will use the system's Bluetooth API to initiate device discovery and present the results to the user. We discuss this further in the "Using IceBT" section below.

The IceBT plug-in must be installed in every client and server that needs to communicate via Bluetooth.

You should install IceBT in your communicator using the pluginFactories field of InitializationData:

C++
#include <IceBT/IceBT.h>
Ice::InitializationData initData;
initData.properties = Ice::createProperties(argc, argv);
initData.pluginFactories = {IceBT::btPluginFactory()};
Ice::CommunicatorPtr communicator = Ice::initialize(initData);

Alternatively, you can install the IceBT plug-in at runtime using configuration:

Properties
# Linux only
Ice.Plugin.IceBT=IceBT:createIceBT

You should install IceBT in your communicator using the pluginFactories field of InitializationData:

Java
// Android only
InitializationData initData = new InitializationData();
initData.properties = new com.zeroc.Ice.Properties(args);
initData.pluginFactories = java.util.List.of(
new com.zeroc.IceBT.PluginFactory());
try (Communicator communicator = new Communicator(initData)) {
// Use the communicator.
}

Alternatively, you can install the IceBT plug-in at runtime using configuration:

Properties
# Android only
Ice.Plugin.IceBT=com.zeroc.IceBT.PluginFactory

On Linux, install the IceBT shared library and load it with:

Properties
Ice.Plugin.IceBT=IceBT:createIceBT

The IceBT plug-in provides a number of configuration properties, including settings to modify the size of the send and receive buffers for a connection. The default settings should be sufficient for most applications.

Developers should also be aware of some core Ice properties that can affect Bluetooth connections:

  • Default device address – If you omit a device address from an object adapter endpoint or proxy endpoint, the plug-in defaults to the address specified by the property Ice.Default.Host.
  • Connect timeout – Establishing a Bluetooth connection can take several seconds to complete. Ice's default timeout settings give plenty of time for a connection to succeed, but an application could experience problems if it configures custom timeouts that are too small for Bluetooth connections.

This section describes how to incorporate IceBT into your Ice applications.

A Bluetooth "service" corresponds to an Ice endpoint, and each endpoint requires its own UUID.

For example, using the syntax for Bluetooth endpoints, you can configure an object adapter named GreeterAdapter as follows:

Properties
GreeterAdapter.Endpoints=bt -u 4f140cef-d75e-4c93-b4e4-20ac111d36d1 --name "Greeter Service"

We're associating the UUID 4f140cef-d75e-4c93-b4e4-20ac111d36d1 with our service. At runtime, this service will be advertised in the Service Discovery Protocol (SDP) registry along with the descriptive name Greeter Service. We omitted a device address and assume Ice.Default.Host is unset, so the plug-in uses the host's default Bluetooth adapter. We also did not specify a particular RFCOMM channel (using the -c option) and therefore the plug-in will automatically select an available channel.

If you omit the -u UUID option from the object adapter's endpoint, the plug-in will automatically generate a random UUID for use in the SDP registry. Note however that your clients will still need some way of discovering this UUID. Generally speaking, you should generate and use your own well-known UUIDs instead.

A Bluetooth endpoint in a proxy must include a UUID and a device address:

greeter:bt -u 4f140cef-d75e-4c93-b4e4-20ac111d36d1 -a "01:23:45:67:89:AB"
Java
var greeter = GreeterPrx.createProxy(
communicator,
"greeter:bt -u 4f140cef-d75e-4c93-b4e4-20ac111d36d1 -a \"01:23:45:67:89:AB\"");

The UUID specified with the -u option must match the one you assigned to your object adapter endpoint.

Notice that the device address given by the -a option is enclosed in quotes; this is necessary because colon (:) characters are used as separators in stringified proxies.

Refer to Proxy and Endpoint Syntax for complete details on the format of a Bluetooth endpoint.

Applications are responsible for determining the Bluetooth address of the device hosting the target service, as described in the next section.

Device discovery is a platform-specific activity that applications are responsible for implementing. On Linux, the Bluetooth service must know the target device before IceBT can connect to it: discover or pair the device first.

On Linux, IceBT::Plugin provides startDiscovery, stopDiscovery, and getDevices. Obtain this interface from the communicator's plug-in manager:

C++
auto plugin = std::dynamic_pointer_cast<IceBT::Plugin>(
communicator->getPluginManager()->getPlugin("IceBT"));
std::string adapterAddress = "01:23:45:67:89:AB";
plugin->startDiscovery(
adapterAddress,
[](const std::string& address, const IceBT::PropertyMap& properties)
{
// Record or display the discovered device.
});

Replace adapterAddress with the address of a local Bluetooth adapter. Both startDiscovery and stopDiscovery require that address.

The callback receives the remote device's Bluetooth address and an IceBT::PropertyMap, a string-to-string map of metadata. The plug-in can report the same device more than once. Discovery continues until you stop it with plugin->stopDiscovery(adapterAddress) or the Bluetooth service stops it. Stopping discovery removes the callbacks registered for that adapter.

plugin->getDevices() returns a snapshot of all known remote devices as an IceBT::DeviceMap, keyed by Bluetooth address. The plug-in reads the initial devices from the Bluetooth service at startup and updates its map as devices are added or removed.

On Android, an app can use the APIs in android.bluetooth to initiate discovery and receive intent notifications about nearby devices. IceBT cancels Android device discovery before opening an outgoing connection.

The app must obtain the Bluetooth permissions required by its Android version and target SDK. See Android Bluetooth permissions.

The IceBT discovery API is available in C++ only. For a client, make the server's device known to the Linux Bluetooth service before the first connection: discover or pair the device with bluetoothctl, then use the device's address in the proxy endpoint. A server has nothing to discover; clients need the address of the server's Bluetooth adapter, which bluetoothctl show prints.

Be aware of the following limitation when using IceBT:

  • An application cannot open multiple Bluetooth connections to the same remote endpoint. This is not a limitation in Ice but rather in the Bluetooth stack. Normally this limitation won't impact your application because Ice's default behavior is to reuse an existing connection to an endpoint whenever possible in preference to opening a new connection. However, some application designs may attach additional semantics to a connection, and use Ice APIs to override the default behavior and force the establishment of new connections to the same endpoint. This strategy will not work when using Bluetooth.

The operating system manages Bluetooth pairing. IceBT uses the platform's Bluetooth connection APIs; applications manage pairing through the operating system's facilities.

On Android, IceBT uses the secure RFCOMM APIs. Android can prompt the user to complete pairing while establishing a connection. See Android's Bluetooth connection guide.

For TLS over Bluetooth, use bts endpoints and the SSL configuration for your language mapping. IceBT installs both the bt and bts endpoint types.