Getting started guide

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

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

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

Integration

The library is supplied as an aar archive. To import it to your project, create a folder named aarlibs inside your app module folder.

Add the following snippet to your project base build.gradle file:

repositories {
    ...

    flatDir {
        dirs 'aarlibs'
    }
}

Add the following snippet to your app build.gradle file replacing version for the correct value:

dependencies {
    ...
    implementation 'com.elvishew:xlog:1.3.0'
    implementation 'no.nordicsemi.android:dfu:1.3.1'
    implementation (name:'kallistoapi-release-[version]', ext:'aar')
}

Usage

The following guide provides a step-by-step on how to interact with the API in order to build a full app. Some code is omitted for brevity.

Starting the system

Get an instance of a KallistoManager. Since only a KallistoSensorManager is implemented, it is used.

When an instance of a manager is requested for the first time, the remote service is created if is not running.

A broadcast with a SENSOR_SERVICE_CONNECTED or SERVICE_FAILED is received detailing the service status.

The service will keep the Bluetooth adapter on at all times and will turn it on automatically if turned off.

When destroying the app, do not forget to call KallistoManager.disconnect() or the system will leak resources.

public class YourClass extends Activity {

private ServiceBroadcastReceiver mBroadcastReceiver = new ServiceBroadcastReceiver();
private KallistoManager mKallistoManager;

...
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Register a broadcast receiver.
    IntentFilter i = new IntentFilter();
    i.addAction(KallistoManager.SENSOR_SERVICE_CONNECTED);
    i.addAction(KallistoManager.SERVICE_DISCONNECTED);
    i.addAction(KallistoManager.SERVICE_FAILED);

    registerReceiver(mBroadcastReceiver, i);

    mKallistoManager = KallistoSensorManager.getInstance(this)
}

@Override
protected void onDestroy() {
    super.onDestroy();

    //Destroy the system
    mKallistoManager.disconnect();
}

//Simple broadcast receiver
private class ServiceBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (KallistoManager.SENSOR_SERVICE_CONNECTED.equals(action)) {
            //From now on, the service is up and running!
            //We can start requesting operations using the KallistoManager.

        } else if (KallistoManager.SERVICE_DISCONNECTED.equals(action)) {
            //From now on, the service is dead

        } else if (KallistoManager.SERVICE_FAILED.equals(action)) {
            //Service failed. Impossible to continue.

        }
    }
}

The static method KallistoSensorManager.getInstance(Context context) is used to get the instance with default configurations. Several parameters can be configured using the full builder method.

Scanning for devices

If the scanning method is not configured explicitly while starting the system, it will default to a cyclic behavior where it scans for a few seconds and then stops. No explicit configuration is needed.

To receive information from the system a callback architecture is used. The app must register a SystemEventListener to receive SystemEvents. Should unregister when done.

For scanning, the most interesting events are SCAN_STARTED, SCAN_STOPPED, SCAN_FOUND_DEVICE, SCAN_LOST_DEVICE.

public class KallistoActivity extends Activity implements SystemEventListener {

(...)

@Override
protected void onDestroy() {
    super.onDestroy();

    //Unregister the listener
    mKallistoManager.unregisterSystemListener(this);
}

@Override
public void onSystemChanged(SystemEvent event) {
    if (event.type.equals(SystemEventType.SCAN_FOUND_DEVICE)) {
        //A new device was found. Add it to a list or something

    }

    if (event.type.equals(SystemEventType.SCAN_LOST_DEVICE)) {
        //A device stopped advertising. Remove it from a list or something

    }
}

private class ServiceBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (KallistoManager.SENSOR_SERVICE_CONNECTED.equals(action)) {
            //Register listener for System events.
            mKallistoManager.registerSystemListener(Homescreen.this, SystemEventType.TYPE_INFO);
        }
        (...)
    }
}

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.

Use KallistoManager.updateScannerConfiguration() to update the scanner state.

See ScannerConfiguration.Builder for on how to build a scanner configuration.

private void startScan(){
    mKallistoManager.updateScannerConfiguration(new ScannerConfiguration.Builder()
        .withResponsiveCycle()
        .build());
}

private void stopScan(){
    mKallistoManager.updateScannerConfiguration(new ScannerConfiguration.Builder()
            .stop()
            .build());
}

To be reported to the app, the device must pass a scanning filter that is fully configurable. By default, no filter is used and all devices are reported.

Use KallistoManager.updateScannerFilter() to update the scanner filter.

See ScannerFilter.Builder for on how to build a scanner filter.

Receive sensor data

After getting an instance of the desired Kallisto device, sensor data can now be requested. For that, an instance of a KallistoSensor must be acquired. For that, KallistoSensorManager has methods to get it like KallistoSensorManager.getSensorList(int type).

The sensors of a given device can be obtained using Kallisto.getSensors().

Use KallistoSensorManager.registerListener(SensorEventListener listener, KallistoSensor sensor, final int rateUs) to register to receive sensor data.

Should unregister when done.

Beware that the sensor registering is synchronous so it must not be performed in the main thread.

Since to receive sensor data a connection is established, it can disconnect at any time. In order to be notified of that, the system events LINK_LOSS, LINK_RECOVERED and LINK_REMOVED are supplied and should be used to present information to the user.

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.

public class SensorActivity extends Activity implements SensorEventListener, SystemEventListener {

    @Override
    protected void onResume() {
        super.onResume();

        //Start sensor register task
        new RegisterSensorsTask().execute();

        //Register system listener
        mKallistoManager.registerSystemListener(this, SystemEventType.TYPE_INFO);
    }

    @Override
    protected void onPause() {
        super.onDestroy();

        //Unregister the listeners
        mKallistoManager.unregisterListener(this);
        mKallistoManager.unregisterSystemListener(this);
    }

    @Override
    public void onAccuracyChanged(KallistoSensor sensor, int i) {
        //Not used
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        //Do something with the event
    }

    @Override
    public void onSystemChanged(SystemEvent event) {
        if (event.Kallisto == null || !event.Kallisto.equals(mKallisto)) {
            //Ignore events that aren't for our device
            return;
        }

        if (event.type.equals(SystemEventType.LINK_LOSS)) {
            //Connection dropped, system is trying to recover

        }

        if (event.type.equals(SystemEventType.LINK_RECOVERED)) {
            //Connection recovered

        }

        if (event.type.equals(SystemEventType.LINK_REMOVED)) {
            //Timeout passed, device was lost!

        }

    }

    private class RegisterSensorsTask extends AsyncTask<Void, Void, Boolean> {

        protected Boolean doInBackground(Void... notUsed) {
            //Register to all sensors in the device
            for (KallistoSensor p : mKallisto.getSensors()) {
                if (!mKallistoManager.registerListener(Device.this, p, KallistoSensorManager.SENSOR_DELAY_NORMAL)) {
                    //Failed to register to the sensor
                    return false;
                }
            }
        }
        (...)
    }
}

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 supports it by using Kallisto.supportsBatteryChanges().

Register to receive battery events using KallistoManager.registerBatteryListener() and implement the BatteryEventListener.

Unregister using KallistoManager.unregisterBatteryListener().

Calibration

A KallistoSensor may support calibration.

Check if the device supports it by using KallistoSensor.supportsCalibration().

Start a calibration using KallistoSensorManager.startCalibration() and implement the CalibrationEventListener.

A calibration can be stopped using KallistoSensorManager.stopCalibration().

Device Firmware Update

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

Start an update using KallistoManager.startDfu() and implement the DfuEventListener.

An update can be stopped using KallistoManager.stopDfu().

Final Remarks

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

  • The library is synchronous in all calls that may trigger a connection to a device. This choice was made to supply an API as close as possible to the Android Sensor Manager. 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.

    • A bonded device, once connected, will stay connected.

    • 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.

    • Sometimes, the Bluetooth adapter can enter into an abnormal state where it can’t be turned on without a reboot. A system event STACK_IRREPARABLE is triggered when this happens to allow an app to warn an user.

Advanced Features

Custom Sensors

To be added.