How to Use New Beacon APIs

Overview

ESF implements a new set of APIs based on TinyB for managing Beacon devices. The new APIs replace the existing Bluetooth APIs, but the old ones are still available.

The purpose of the new BLE Beacon APIs is to simplify the development of applications that interact with Bluetooth LE Beacon devices, offering clear and easy-to-use methods for advertising and scanning. ESF offers out-of-the-box the implementation of the Beacon APIs for iBeacon™ and Eddystone™ technologies. Moreover, the new APIs allow to easily plug new beacon implementations in ESF.

🚧

The support for this set of APIs is available in the ReliaGATE 20-25 since Eurotech Linux Version 22.0.0.
The support is also available for the BoltGATE 20-25.

How to Use the New iBeacon™ APIs

This section briefly presents how to use the iBeacon™ implementation of the ESF Beacon APIs, providing several code snippets that explain how to perform common operations on iBeacons.
For a complete example on iBeacon advertising and scanning, please refer to the new iBeacon™ advertiser and iBeacon™ scanner examples.

For more information about iBeacon™ please refer to the official page.

An application that wants to use the iBeacon™ implementation of ESF Beacon APIs should bind the BluetoothLeService and BluetoothLeIBeaconService OSGi services, as shown in the following Java snippet:

public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {
    this.bluetoothLeService = bluetoothLeService;
}

public void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {
    this.bluetoothLeService = null;
}

public void setBluetoothLeIBeaconService(BluetoothLeIBeaconService bluetoothLeIBeaconService) {
    this.bluetoothLeIBeaconService = bluetoothLeIBeaconService;
}

public void unsetBluetoothLeIBeaconService(BluetoothLeIBeaconService bluetoothLeIBeaconService) {
    this.bluetoothLeIBeaconService = null;
}

and in the component definition:

<reference bind="setBluetoothLeService" 
            cardinality="1..1" 
            interface="org.eclipse.kura.bluetooth.le.BluetoothLeService" 
            name="BluetoothLeService" 
            policy="static" 
            unbind="unsetBluetoothLeService"/>

<reference bind="setBluetoothLeIBeaconService" 
            cardinality="1..1" 
            interface="org.eclipse.kura.ble.ibeacon.BluetoothLeIBeaconService" 
            name="BluetoothLeIBeaconService" 
            policy="static" 
            unbind="unsetBluetoothLeIBeaconService"/>

The BluetoothLeService is used to get the BluetoothLeAdapter to be used with the BluetoothLeIBeaconScanner and BluetoothLeIBeaconAdvertiser. The adapter can be retrieved and powered on as follows:

this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);
if (this.bluetoothLeAdapter != null) {
    if (!this.bluetoothLeAdapter.isPowered()) {
        this.bluetoothLeAdapter.setPowered(true);
    }
}

where adapterName is the name of the adapter, i.e. hci0.

Create an iBeacon™ Advertiser

In order to properly configure an iBeacon™ advertiser, a BluetoothLeIBeaconService is needed to create a new BluetoothLeIBeaconAdvertiser instance that is bound to a specific Bluetooth adapter:

try {
    BluetoothLeBeaconAdvertiser<BluetoothLeIBeacon> advertiser = this.bluetoothLeIBeaconService.newBeaconAdvertiser(this.bluetoothLeAdapter);
} catch (KuraBluetoothBeaconAdvertiserNotAvailable e) {
    logger.error("Beacon Advertiser not available on {}", this.bluetoothLeAdapter.getInterfaceName(),e);
}

Then a BluetoothLeIBeacon object should be created, containing all the information to be broadcasted.

In the following snippet, the BluetoothLeIBeacon object is instantiated and added to the advertiser. Then the broadcast time interval is set and the beacon advertising is started.

try {
    BluetoothLeIBeacon iBeacon = new BluetoothLeIBeacon(uuid, major, minor, txPower);
    advertiser.updateBeaconAdvertisingData(iBeacon);
    advertiser.updateBeaconAdvertisingInterval(minInterval, maxInterval;

    advertiser.startBeaconAdvertising();
} catch (KuraBluetoothCommandException e) {
    logger.error("IBeacon configuration failed", e);
}

The BluetoothLeIBeacon represents the beacon packet that will be broadcasted by the advertiser and it should be configured will the following parameters:

  • uuid a unique number that identifies the beacon.
  • major a number that identifies a subset of beacons within a large group.
  • minor a number that identifies a specific beacon.
  • txPower the transmitter power level indicating the signal strength one meter from the device.

🚧

Only one advertising packet can be broadcasted at a time on a specific Bluetooth adapter.

Finally, in the following snippet, the advertiser is stopped and removed from the BluetoothLeIBeaconService:

try {
    advertiser.stopBeaconAdvertising();
    this.bluetoothLeIBeaconService.deleteBeaconAdvertiser(advertiser);
} catch (KuraBluetoothCommandException e) {
    logger.error("Stop iBeacon advertising failed", e);
}

Create an iBeacon™ Scanner

As done for the advertiser, a BluetoothLeIBeaconService is needed to create a new BluetoothLeIBeaconScanner instance bound to a specific Bluetooth adapter:

bluetoothLeBeaconScanner<BluetoothLeIBeacon> scanner = this.bluetoothLeIBeaconService.newBeaconScanner(this.bluetoothLeAdapter);

A BluetoothLeIBeaconScanner needs a listener to collect the iBeacon packets that the Bluetooth adapter detects. In the following snippet a simple listener, that prints the iBeacon packet configuration, is added to the scanner object:

private class iBeaconListener implements BluetoothLeBeaconListener<BluetoothLeIBeacon> {

    @Override
    public void onBeaconsReceived(BluetoothLeIBeacon beacon) {
        logger.info("iBeacon received from {}", beacon.getAddress());
        logger.info("UUID : {}", beacon.getUuid());
        logger.info("Major : {}", beacon.getMajor());
        logger.info("Minor : {}", beacon.getMinor());
        logger.info("TxPower : {}", beacon.getTxPower());
        logger.info("RSSI : {}", beacon.getRssi());   
    }
    
}
scanner.addBeaconListener(listener);

The scanner is started for a specific time interval (in this case 10 seconds):

scanner.startBeaconScan(10);

Finally, the scanner should be stopped and the resources should be released:

if (scanner.isScanning()) {
    scanner.stopBeaconScan();
}
scanner.removeBeaconListener(listener);
this.bluetoothLeIBeaconService.deleteBeaconScanner(scanner);

 How to Use the New Eddystone™ APIs

Eddystone™ is a protocol specification that defines a BLE message format for proximity beacon messages. It describes several different frame types that may be used individually or in combinations to create beacons that can be used for a variety of applications. For more information please see the Google developer reference for Beacons and the Google developer reference for Eddystone.

In this section, the Eddystone™ implementation of the ESF Beacon APIs is presented, providing several code snippets that explain how to perform common operations on them. For a complete example on Eddystone™ advertising and scanning, please refer to the new Eddystone™ advertiser and Eddystone™ scanner examples.

🚧

Only Eddystone UID and URL frame types are currently supported.

As done with the iBeacon™ implementation, an application has to bind the BluetoothLeService and BluetoothLeEddystoneService OSGi services, as shown in the following Java snippet:

public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {
    this.bluetoothLeService = bluetoothLeService;
}

public void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {
    this.bluetoothLeService = null;
}

public void setBluetoothLeEddystoneService(BluetoothLeEddystoneService bluetoothLeEddystoneService) {
    this.bluetoothLeEddystoneService = bluetoothLeEddystoneService;
}

public void unsetBluetoothLeEddystoneService(BluetoothLeEddystoneService bluetoothLeEddystoneService) {
    this.bluetoothLeEddystoneService = null;
}

and in the component definition:

<reference bind="setBluetoothLeService" 
            cardinality="1..1" 
            interface="org.eclipse.kura.bluetooth.le.BluetoothLeService" 
            name="BluetoothLeService" 
            policy="static" 
            unbind="unsetBluetoothLeService"/>

<reference bind="setBluetoothLeEddystoneService" 
            cardinality="1..1" 
            interface="org.eclipse.kura.ble.eddystone.BluetoothLeEddystoneService" 
            name="BluetoothLeEddystoneService" 
            policy="static" 
            unbind="unsetBluetoothLeEddystoneService"/>

The BluetoothLeService is used to get the BluetoothLeAdapter to be used with the BluetoothLeEddystoneScanner and BluetoothLeEddystoneAdvertiser. The adapter can be retrieved and powered shown in the following code snippet:

this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);
if (this.bluetoothLeAdapter != null) {
    if (!this.bluetoothLeAdapter.isPowered()) {
        this.bluetoothLeAdapter.setPowered(true);
    }
}

where adapterName is the name of the adapter, i.e. hci0.

Create an Eddystone™ Advertiser

In order to properly configure an Eddystone™ advertiser, a BluetoothLeEddystoneService is needed to create a new BluetoothLeEddystoneAdvertiser instance bound to a specific Bluetooth adapter:

try {
    BluetoothLeBeaconAdvertiser<BluetoothLeEddystone> advertiser = this.advertising = this.bluetoothLeEddystoneService.newBeaconAdvertiser(this.bluetoothLeAdapter);
} catch (KuraBluetoothBeaconAdvertiserNotAvailable e) {
    logger.error("Beacon Advertiser not available on {}", this.bluetoothLeAdapter.getInterfaceName(),e);
}

The advertisement has to be configured with a BluetoothLeEddystone object that contains all the information to be broadcasted. Currently, only the UID and the URL frame types are supported. A UID frame can be created as follows:

BluetoothLeEddystone eddystone = new BluetoothLeEddystone();
eddystone.configureEddystoneUIDFrame(namespace, instance, txPower);

where namespace and instance are respectively 10-bit and 6-bit long bytes that compose a unique 16-byte Beacon ID. The txPower is the calibrated transmission power at 0 m.

A URL frame is created as follows:

BluetoothLeEddystone eddystone = new BluetoothLeEddystone();
eddystone.configureEddystoneURLFrame(url, txPower);

where url is the URL to be broadcasted and the txPower is the calibrated transmission power at 0 m.

After the BluetoothLeEddystone creation, the packet is added to the advertiser and the broadcast time interval is set. Then the advertiser is started:

try {
    advertiser.updateBeaconAdvertisingData(eddystone);
    advertiser.updateBeaconAdvertisingInterval(this.options.getMinInterval(), this.options.getMaxInterval());
    advertiserstartBeaconAdvertising();
} catch (KuraBluetoothCommandException e) {
    logger.error("Eddystone configuration failed", e);
}

Finally, in the following code snippet the advertiser is stopped and removed from the BluetoothLeEddystoneService:

try {
    advertiser.stopBeaconAdvertising();
    this.bluetoothLeEddystoneService.deleteBeaconAdvertiser(advertiser);
} catch (KuraBluetoothCommandException e) {
    logger.error("Stop Advertiser advertising failed", e);
}

Create an Eddystone™ Scanner

As done for the advertiser, a BluetoothLeEddystoneService is needed to create a new BluetoothLeEddystoneScanner instance bound to a specific Bluetooth adapter:

bluetoothLeBeaconScanner<BluetoothLeEddystone> scanner = this.bluetoothLeEddystoneService.newBeaconScanner(this.bluetoothLeAdapter);

A BluetoothLeEddystoneScanner needs a listener to collect the Eddystone packets that the Bluetooth adapter detects.

In the following snippet, a simple listener that detects the frame type and prints the packet content is added to the scanner object:

private class EddystoneListener implements BluetoothLeBeaconListener<BluetoothLeEddystone> {

    @Override
    public void onBeaconsReceived(BluetoothLeEddystone beacon) {
        logger.info("Eddystone {} received from {}", eddystone.getFrameType(), eddystone.getAddress());
        if ("UID".equals(eddystone.getFrameType())) {
            logger.info("Namespace : {}", bytesArrayToHexString(eddystone.getNamespace()));
            logger.info("Instance : {}", bytesArrayToHexString(eddystone.getInstance()));
        } else if ("URL".equals(eddystone.getFrameType())) {
            logger.info("URL : {}", eddystone.getUrlScheme() + eddystone.getUrl());
        }
        logger.info("TxPower : {}", eddystone.getTxPower());
        logger.info("RSSI : {}", eddystone.getRssi());  
    }
    
}
scanner.addBeaconListener(listener);

The scanner is started for a specific time interval (in this case 10 seconds):

scanner.startBeaconScan(10);

Finally, the scanner should be stopped and the resources are released:

if (scanner.isScanning()) {
    scanner.stopBeaconScan();
}
scanner.removeBeaconListener(listener);
this.bluetoothLeEddystoneService.deleteBeaconScanner(scanner);

Add the New Beacon APIs Implementation

Eclipse Kura offers the implementation for iBeacon™ and Eddystone™ protocols, but it is possible to add implementations of different kind of beacon protocols.

The org.eclipse.kura.bluetooth.le.beacon package contains the interfaces used by the beacon implementations:

  • BluetoothLeBeaconService is the entry point for applications that want to use the Beacon APIs.
  • BluetoothLeBeaconManager is used by the BluetoothLeBeaconService and provides methods to create and delete Beacon advertisers and scanners.
  • BluetoothLeBeaconAdvertiser allows to configure advertisement packets and manage the advertisement.
  • BluetoothLeBeaconScanner is used to search for specific Beacon packets.
  • BluetoothLeBeaconEncoder implements methods for encoding a Beacon object to a stream of bytes.
  • BluetoothLeBeaconDecoder implements methods for decoding a stream of bytes into a Beacon object.
  • BluetoothLeBeacon represents a generic Beacon packet.

The BluetoothLeBeaconManager, BluetoothLeBeaconScanner and BluetoothLeBeaconAdvertiser interfaces handle generic BluetoothLeBeacon objects and their implementations are provided by the org.eclipse.kura.ble.provider. The others interfaces, instead, are Beacon specific and their implementations depend on the specific protocol that is used. As a consequence, anyone that wants to support a new Beacon protocol should provide the implementation of the BluetoothLeBeaconService, BluetoothLeBeaconEncoder, and BluetoothLeBeaconDecoder interfaces and extend the BluetoothLeBeacon class.

As an example, the org.eclipse.kura.ble.ibeacon.provider provides the implementation of the above APIs for the iBeacon™ protocol. In this case, the org.eclipse.kura.ble.ibeacon package contains the following:

  • BluetoothLeIBeacon implements the BluetoothLeBeacon interface for the iBeacon™ packet.
  • BluetoothLeIBeaconEncoder is a marker interface that extends BluetoothLeBeaconEncoder.
  • BluetoothLeIBeaconDecoder is a marker interface that extends BluetoothLeBeaconDecoder.
  • BluetoothLeIBeaconService is a marker interface that extends BluetoothLeBeaconService and is the entry point for applications that want to use an iBeacon™.

The org.eclipse.kura.internal.ble.ibeacon provides the implementations for the above interfaces:

  • BluetoothLeIBeaconEncoderImpl implements BluetoothLeIBeaconEncoder offering a method to encode the BluetoothLeIBeacon into a byte stream.
  • BluetoothLeIBeaconDecoderImpl implements BluetoothLeIBeaconDecoder offering a method to decode a stream of bytes into a BluetoothLeIBeacon object.
  • BluetoothLeIBeaconServiceImpl is the implementation of BluetoothLeIBeaconService and uses the generic BluetoothLeBeaconManager service to create scanners and advertisers.

The following image shows the UML diagram.