Getting started guide

This guide aims to cover all the main features of the API and how to use them in an Linux application.

All the needed interactions are explained in detail with snippets of code.

All public API classes are documented using doxygen. See it here.

The library is supplied as an Linux shared library and needs to be installed on the system.

Build

To build the library, first we need to install some build tools:

sudo apt install build-essential autotools-dev autoconf libtool pkg-config cmake

Next, we need to install some libraries:

sudo apt install glib-2.0 libglib2.0-dev

Next, we need to install a third party library, tinyb:

git clone https://github.com/intel-iot-devkit/tinyb.git
cd tinyb
mkdir build
cd build
cmake ..
make
sudo make install

Finally, we build and install the Linux API:

autoreconf -f -i
./configure TARGET=linux
make
sudo make install

And we are done!

Usage

The following guide provides a step-by-step on how to interact with the API. Some code is simplified and omitted for brevity.

Starting the system

KallistoCore is the core class for interacting with the system. It has 3 methods that return singletons that are used to interact with different parts of the library.

SystemManager is used for configuring the Bluetooth adapter and scanner. It provides useful listeners that can be used to react to different system events. These will be detailed later. Acquire an instance using KallistoCore::getSystemManagerInstance().

KallistoManager is used for interacting with a Kallisto device. This class provides methods for getting the devices and perform operations like updating the firmware and encrypting the connection. It allows to register a listener for device events like link loss. Acquire an instance using KallistoCore::getKallistoManagerInstance().

SensorManager is used for interacting with a Sensor. This class provides methods for getting the sensors and register listeners to get sensor data. Acquire an instance using KallistoCore::getSensorManagerInstance().

To start, initialize the library core using KallistoCore::init(). This will start the library by searching for an available Bluetooth Low Energy adapter and, if found, enable it. The library will try to handle all the interactions with the adapter automagically.

After this, implement a class that inherits from BluetoothAdapterEventListener. This class define callbacks for Bluetooth adapter events. Register it using KallistoCore::getSystemManagerInstance()->registerBluetoothAdapterEventListener().

Do not forget to call KallistoCore::getSystemManagerInstance()->unregisterBluetoothAdapterEventListener() before exiting your application or the system will not terminate correctly.

int main(int argc, char* argv[]){
    (...)

    kallisto::error::KallistoDetailedResult err_code = kallisto::core::KallistoCore::init();

    if(err_code.getResult() != kallisto::error::SUCCESS){
        std::cout << "Error while stating Kallisto Core: " << err_code.getMessage();
        exit(-1);
    }

    //Start our tester
    Tester myTester();

    //Wait for exit condition (signal for example). Out-of-scope of this guide.
    (...)

    return 0;
}

//We are inlining everything for simplicity. Do not forget to add the namespaces!
class Tester : public BluetoothAdapterEventListener {

    Tester() {
        KallistoCore::getSystemManagerInstance()->registerBluetoothAdapterEventListener(*this);
    }

    ~Tester() {
        KallistoCore::getSystemManagerInstance()->unregisterBluetoothAdapterEventListener(*this);
    }

    //BluetoothAdapterEventListener callbacks

    /**
     * Called when the Bluetooth adapter is turned off.
     * Following this event, one of onBluetoothAdapterEnabled() or onBluetoothAdapterIrrecuperable() will be triggered.
     */
    void onBluetoothAdapterDisabled(std::chrono::nanoseconds timestamp){
        //When the Bluetooth adapter is turned, all devices lose connection
        //Wait for recovery for example
    }

    /**
     * Called when the Bluetooth adapter is turned back on following a onBluetoothAdapterDisabled() event.
     */
    void onBluetoothAdapterEnabled(std::chrono::nanoseconds timestamp){
        //Resume normal behaviour
    }

    /**
     * Called when the Bluetooth adapter fails to turn back on following a onBluetoothAdapterDisabled() event.
     * This can happen if the adapter was removed from the system of if it is in error and fails to be enabled.
     * The system will wait for a new adapter.
     */
    void onBluetoothAdapterIrrecuperable(std::chrono::nanoseconds timestamp){
        //Show an error to the user, for example
    }
}

Scanning for devices

For scanning for devices, implement a class that inherits from BluetoothScanEventListener. This class define callbacks for Bluetooth scanner events. Register it using KallistoCore::getSystemManagerInstance()->registerBluetoothScanEventListener().

Do not forget to call KallistoCore::getSystemManagerInstance()->unregisterBluetoothScanEventListener() before exiting your application or the system will not terminate correctly.

Before starting a scan, the scanner needs to be configured. For that, a BluetoothScannerConfiguration needs to be created and passed to the scanner using KallistoCore::getSystemManagerInstance()->setScannerConfiguration().

Scanning is an intensive operation, so it is recommended to filter for the desired devices and, when found, stop it. To filter devices use BluetoothScannerFilter and pass it using KallistoCore::getSystemManagerInstance()->setScannerFilter().

class Tester : public BluetoothAdapterEventListener,
               public BluetoothScanEventListener {

    Tester() {
        KallistoCore::getSystemManagerInstance()->registerBluetoothAdapterEventListener(*this);
        KallistoCore::getSystemManagerInstance()->registerBluetoothScanEventListener(*this);

        startScan();
    }

    ~Tester() {
        stopScan();

        KallistoCore::getSystemManagerInstance()->unregisterBluetoothScanEventListener(*this);
        KallistoCore::getSystemManagerInstance()->unregisterBluetoothAdapterEventListener(*this);
    }

    void startScan(){
        //Create a new scanner filter. Will only search for device with MAC FA:5B:60:DA:51:05
        //Can be omitted if all Kallisto devices are needed
        //Beware that, if not added, all LE devices will be treated as Kallistos
        auto scanner_filter = BluetoothScannerFilter::Builder()
            .includeDeviceWithMac("FA:5B:60:DA:51:05")

        //Pass it to the scanner
        KallistoCore::getSystemManagerInstance()->setScannerFilter(scanner_filter);

        //Create a new scanner configuration. This configuration will cycle between scanning and not scanning
        //continuously.
        auto scanner_configuration = BluetoothScannerConfiguration::Builder()
            .withMediumDuration()
            .withBalancedCycle()
            .build();

        //Pass it to the scanner
        KallistoCore::getSystemManagerInstance()->setScannerConfiguration(scanner_configuration);
    }

    void stopScan(){
        //This configuration stops the scanner
        auto scanner_configuration = BluetoothScannerConfiguration::Builder()
            .stop()
            .build();

        //Pass it to the scanner
        KallistoCore::getSystemManagerInstance()->setScannerConfiguration(scanner_configuration);
    }

    //BluetoothAdapterEventListener callbacks

    /**
     * Called when the Bluetooth adapter is turned off.
     * Following this event, one of onBluetoothAdapterEnabled() or onBluetoothAdapterIrrecuperable() will be triggered.
     */
    void onBluetoothAdapterDisabled(std::chrono::nanoseconds timestamp){
        //When the Bluetooth adapter is turned, all devices lose connection
        //Wait for recovery for example
    }

    /**
     * Called when the Bluetooth adapter is turned back on following a onBluetoothAdapterDisabled() event.
     */
    void onBluetoothAdapterEnabled(std::chrono::nanoseconds timestamp){
        //Resume normal behaviour
    }

    /**
     * Called when the Bluetooth adapter fails to turn back on following a onBluetoothAdapterDisabled() event.
     * This can happen if the adapter was removed from the system of if it is in error and fails to be enabled.
     * The system will wait for a new adapter.
     */
    void onBluetoothAdapterIrrecuperable(std::chrono::nanoseconds timestamp){
        //Show an error to the user, for example
    }

    //BluetoothScanEventListener callbacks

    /**
     * Called when a new device is found.
     */
    void onDeviceFound(std::chrono::nanoseconds timestamp, hardware::Kallisto& device) {
        //When the device is found, it is reported here
        //Since we configured a single device on the filter, if this is called we know that it is our Kallisto
    }

    /**
     * Called when a device disappears.
     */
    void onDeviceLost(std::chrono::nanoseconds timestamp, hardware::Kallisto& device) {
        //Optional. When a device disappears during scanning, it is reported here
        //Can be used to manage a device list for example
    }

    /**
     * Called when a new advertising packet from the device is received.
     */
    void onDeviceScanReport(std::chrono::nanoseconds timestamp, hardware::Kallisto& device, int rssi) {
        //Optional. Can be used to show the RSSI of the devices
    }
}

Since scanning is an expensive operation in terms of power consumption, it is not recommended to keep the default behavior. Full control is given to the developer to start and stop scans as desired.

Receive sensor data

After getting an instance of the desired Kallisto device, sensor data can now be requested. For that, an instance of a Sensor must be acquired. For that, SensorManager has methods to get it like SensorManager.getSensor(SensorType type).

The sensors of a given device can be obtained using SensorManager.getSensor(SensorType type, std::string address).

Implement a class that inherits from SensorEventListener and use SensorManager.registerListener(SensorEventListener& listener, Sensor& sensor, const std::chrono::microseconds delay) (continuous sensors) or SensorManager.registerListener(SensorEventListener& listener, Sensor& sensor) (on-change sensors) to register to receive sensor data. See the sensor reporting mode using Sensor.getReportingMode().

Do not forget to call SensorManager.unregisterListener() when done.

Since to receive sensor data a connection is established, it can disconnect at any time.

In order to be notified of that, implement a class that inherits from KallistoEventListener. Register it using KallistoCore::getKallistoManagerInstance()->registerListener(). This class defines callbacks that are called when a link is loss, recovered or removed.

During a link loss, a quick scan is started in order to recover the connection to the device. The scanner returns to its previous state when the connection is recovered or a timeout passed.

class Tester : public BluetoothAdapterEventListener,
               public BluetoothScanEventListener,
               public KallistoEventListener,
               public SensorEventListener {

    Tester() {
        KallistoCore::getSystemManagerInstance()->registerBluetoothAdapterEventListener(*this);
        KallistoCore::getSystemManagerInstance()->registerBluetoothScanEventListener(*this);

        startScan();
    }

    ~Tester() {
        stopScan();

        KallistoCore::getSensorManagerInstance()->unregisterListener(*this);
        KallistoCore::getKallistoManagerInstance()->unregisterListener(*this);
        KallistoCore::getSystemManagerInstance()->unregisterBluetoothScanEventListener(*this);
        KallistoCore::getSystemManagerInstance()->unregisterBluetoothAdapterEventListener(*this);
    }

    void startScan(){
        //Create a new scanner filter. Will only search for device with MAC FA:5B:60:DA:51:05
        //Can be omitted if all Kallisto devices are needed
        //Beware that, if not added, all LE devices will be treated as Kallistos
        auto scanner_filter = BluetoothScannerFilter::Builder()
            .includeDeviceWithMac("FA:5B:60:DA:51:05")

        //Pass it to the scanner
        KallistoCore::getSystemManagerInstance()->setScannerFilter(scanner_filter);

        //Create a new scanner configuration. This configuration will cycle between scanning and not scanning
        //continuously.
        auto scanner_configuration = BluetoothScannerConfiguration::Builder()
            .withMediumDuration()
            .withBalancedCycle()
            .build();

        //Pass it to the scanner
        KallistoCore::getSystemManagerInstance()->setScannerConfiguration(scanner_configuration);
    }

    void stopScan(){
        //This configuration stops the scanner
        auto scanner_configuration = BluetoothScannerConfiguration::Builder()
            .stop()
            .build();

        //Pass it to the scanner
        KallistoCore::getSystemManagerInstance()->setScannerConfiguration(scanner_configuration);
    }

    //BluetoothAdapterEventListener callbacks

    /**
     * Called when the Bluetooth adapter is turned off.
     * Following this event, one of onBluetoothAdapterEnabled() or onBluetoothAdapterIrrecuperable() will be triggered.
     */
    void onBluetoothAdapterDisabled(std::chrono::nanoseconds timestamp){
        //When the Bluetooth adapter is turned, all devices are cleared
        //Wait for the adapter to turn on
    }

    /**
     * Called when the Bluetooth adapter is turned back on following a onBluetoothAdapterDisabled() event.
     */
    void onBluetoothAdapterEnabled(std::chrono::nanoseconds timestamp){
        //Restart the scan since all devices where destroyed
        startScan();
    }

    /**
     * Called when the Bluetooth adapter fails to turn back on following a onBluetoothAdapterDisabled() event.
     * This can happen if the adapter was removed from the system of if it is in error and fails to be enabled.
     * The system will wait for a new adapter.
     */
    void onBluetoothAdapterIrrecuperable(std::chrono::nanoseconds timestamp){
        //Show an error to the user, for example
    }

    //BluetoothScanEventListener callbacks

    /**
     * Called when a new device is found.
     */
    void onDeviceFound(std::chrono::nanoseconds timestamp, hardware::Kallisto& device) {
        //When the device is found, it is reported here
        //Since we configured a single device on the filter, if this is called we know that it is our Kallisto

        //Register Kallisto listener
        KallistoCore::getKallistoManagerInstance()->registerListener(*this, device);

        //Try to get an accelerometer
        auto accelerometer = KallistoCore::getSensorManagerInstance()->getSensor(SensorType::ACCELEROMETER, device.getAddress());

        if(accelerometer != NULL) {

            //Try to register the accelerometer to send data at 100 Hz (10 ms period)
            auto err_code = KallistoCore::getSensorManagerInstance()->registerListener(*this, accelerometer, std::chrono::milliseconds(10));

            //If we success, we don't need to scan anymore
            if(err_code.getResult() == error::SUCCESS) {
                stopScan();
            }
            else {
                //Show error maybe?
            }
        }
    }

    /**
     * Called when a device disappears.
     */
    void onDeviceLost(std::chrono::nanoseconds timestamp, hardware::Kallisto& device) {
        //Optional. When a device disappears during scanning, it is reported here
        //Can be used to manage a device list for example
    }

    /**
     * Called when a new advertising packet from the device is received.
     */
    void onDeviceScanReport(std::chrono::nanoseconds timestamp, hardware::Kallisto& device, int rssi) {
        //Optional. Can be used to show the RSSI of the devices
    }

    //Kallisto callbacks

    /**
     * Called when the connection to a device under use is lost.
     */
    void onLinkLoss(std::chrono::nanoseconds timestamp, Kallisto& device) {
        //Optional. Show an warning? The system will try to recover the connection automagically
    }

    /**
     * Called when the connection to a device under use is recovered.
     */
    void onLinkRecovered(std::chrono::nanoseconds timestamp, Kallisto& device) {
        //Optional. Clear warning?
    }

    /**
     * Called when there is a timeout while waiting for connection recovery.
     */
    void onLinkRemoved(std::chrono::nanoseconds timestamp, Kallisto& device) {
        //Link was lost and was impossible to recover. Start scanning again?
        startScan();
    }

    //SensorEventListener callbacks

    /**
     * Called when there is a new sensor event.
     */
    void onSensorChanged(Sensor& sensor, SensorEvent event) {
        //Will be called when there is new sensor data
        std::cout << "Accelerometer: ";

        for(int i = 0; i < event.values.size(); i++) {
            std::cout << event.values[i] << "m/s ";
        }

        std::cout << std::endl;
    }
}

Other features

Beyond reporting sensor data, several other features are supported by the library.

Battery

A Kallisto device may support the reporting of battery data.

Check if the device has a SensorType::BATTERY_SOC for battery state of charge and SensorType::BATTERY_CHARGING_STATUS.

Register like other sensors.

Device Firmware Update

A Kallisto firmware can be updated over-the-air.

At this time, this is not supported on this API.

Final Remarks

Some key aspects of the library must be taken into account while integrating it in an application:

  • The library is synchronous in all calls that may trigger a connection to a device. An asynchronous interface will be added in the future;

  • The connections are opportunistic. A connection is only established when it is needed and is dropped when the device is not in use.

    • The function KallistoManager.stayConnected() can be used to override this behavior. Set it to true and a connection operation is executed (if device is not already connected) and will be kept.

    • The device metadata is only downloaded when the device connects for the first time or when request before any connection is made.

  • The Bluetooth adapter will be fully controlled by the system. If it is turned off it will be forced on again.

  • Depending of the system configuration, sudo might be needed to access Bluetooth devices. Refer to your distro documentation.