Sensors

Devices running BlackBerry 10 have many sensors that collect data about the device's external environment. Some are low-level, real-time sensors such as the accelerometer, and others are higher-level, event-driven sensors, like the holster sensor. In Cascades, you can access the data from these sensors using the Sensors API, a component of the Qt Mobility Project.

For a complete list of all the sensors that are currently supported on BlackBerry 10 devices, see Supported sensors.

Accessing sensor data

Regardless of the type of sensor, or whether you're using C++ or QML, how you access the data is nearly the same.

Requests from the application are delivered through  QSensor to QSensorBackend which communicates with platform services and sensor hardware. Data is returned through the  QSensorReading class. The diagram to the right shows how these components interact with each other.

A diagram that shows how an application communicates with the device sensors.

Setting up your app

To access sensor readings in your application, there are a few steps that you'll need to take to set up your app.

First, you must make some modifications to the application's project file. In your Cascades project, add the following two lines to the .pro file:

CONFIG += mobility
MOBILITY += sensors

In a C++ application, you must qualify the QtSensors namespace and the class name, as well as add a using directive for the QtMobility namespace.

#include <QtSensors/QCompass>

using namespace QtMobility

In a QML application, a similar import statement is also required.

// Import statement for sensors available in API levels
// 1.1 and earlier.
import QtMobility.sensors 1.2

// Import statement for sensors made available in API
// levels 1.2 and later.
import QtMobility.sensors 1.3

Sensors in C++

To handle sensor data in C++, the first thing you do is create an instance of QSensor or one of its subclasses on the stack or heap.

#include <QtSensors/QAccelerometer>
#include <QtSensors/QOrientationSensor>
using namespace QtMobility

// Create the sensor on the heap (deleted when object is deleted).
QAccelerometer *sensor = new QAccelerometer(this);

// Create the sensor on the stack (deleted when current scope ends).
QOrientationSensor orient_sensor;

Next, you start the sensor.

sensor->start();

Then you receive the sensor reading using one of the QSensorReading classes and extract the data.

QAccelerometerReading *reading = sensor->reading();
qreal x = reading->x();
qreal y = reading->y();
qreal z = reading->z();

// For debugging purposes
qDebug() << "x acceleration: " << x;
qDebug() << "y acceleration: " << y;
qDebug() << "z acceleration: " << z;

If your application needs to be able to listen to changes in sensor readings, you can connect a custom slot to the QSensor::readingChanged() signal and handle the sensor data within the custom slot.

// SensorsApp.cpp
#include "SensorsApp.h"

#include <bb/cascades/Application>
#include <QtSensors/QCompass>
using namespace QtMobility;

SensorsApp::SensorsApp(bb::cascades::Application *app)
    : QObject(app)
{
    // Define the UI and perform any other required operations

    // Create the compass sensor.
    m_CompassSensor = new QCompass(this);

    // Set the orientation mode to fixed so that sensor readings
    // aren't affected by device orientation.
    m_CompassSensor->setAxesOrientationMode
                        (QCompass::FixedOrientation);

    // If any Q_ASSERT statement(s) indicate that the slot failed
    // to connect to the signal, make sure you know exactly why this
    // has happened. This is not normal, and will cause your app
    // to stop working
    bool res = connect(m_CompassSensor,
                       SIGNAL(readingChanged()),
                       this,
                       SLOT(compassReadingChanged()));
    Q_ASSERT(res);

    // Since the variable is not used in the app, this is
    // added to avoid a compiler warning.
    Q_UNUSED(res);

    m_CompassSensor->start();
}

void SensorsApp::compassReadingChanged()
{
    QCompassReading *reading = m_CompassSensor->reading();

    qreal azimuth = reading->azimuth();
    qDebug() << "The azimuth is " << azimuth << " degrees.";
}

After you're finished with the sensor, all that's left to do is stop the sensor.

sensor->stop();
// SensorsApp.h

#ifndef SENSORSAPP_H_
#define SENSORSAPP_H_

#include <QObject>
#include <QtSensors/QCompass>

QTM_USE_NAMESPACE

namespace bb { namespace cascades { class Application; }}

using namespace bb::cascades;

class SensorsApp : public QObject
{
    Q_OBJECT

public:

    SensorsApp(bb::cascades::Application *app);
        virtual ~SensorsApp() {}

public slots:

void compassReadingChanged();

private:

	QCompass *m_CompassSensor;

};
#endif /* SENSORSAPP_H_ */

Sensors in QML

Accessing sensor data in QML is very similar to C++. The sensor is started by setting the  active property to true. The component receives a signal when the reading changes, which it captures using the onReadingChanged() signal handler. Within the signal handler you can access the reading  property of the element to extract the current sensor data so that your application can respond accordingly. Sensor objects must be attached to an existing VisualNode using the attachedObjects property.

import bb.cascades 1.0
import QtMobility.sensors 1.2

Page {
    Container {
        layout: DockLayout {}
        Label {
            id: compassHeading
            text: "Sensors!"
        }
        attachedObjects: [
            Compass {
                id: compass
                active: true
         
                onReadingChanged: {
                    compassHeading.text = reading.azimuth;
                }
            }
        ]
    }
}

Device orientation and sensor readings

There are several sensors that can have their sensor readings modified to reflect the orientation of the device (for example, QRotationSensor, QCompass, and QMagnetometer). The AxesOrientationMode enum contains the different settings that describe how reading values are affected by the device orientation. When using FixedOrientation, sensor values are displayed as normal, regardless of orientation. With AutomaticOrientation, the reading values are automatically rotated based on the device orientation. When using UserOrientation, the reading values are rotated based on the angle of the userOrientation property, which is an integer that reflects the screen orientation. The only valid values for the userOrientation property are 0, 90, 180 and 270.

If sensor readings are not modified at all to reflect the orientation of the device, the following conventions apply:

Unless otherwise specified, sensors use the right-hand Cartesian coordinate system.

A graphic that shows the right hand cartesian coordinate system as it relates to the device.

To allow for measurements in all six directions, negative values are used.

A graphic that shows how the coordinate system works in negative directions.

Where rotation around an axis is used, the rotation shall be expressed as a right-hand rotation.

A graphic that shows how device rotations work.

In general, sensor data is oriented to the top of the device.

A graphic that shows where the top and bottom of the device are.

If values are to be displayed on the screen, the values may need to be transformed so that they match the user interface orientation.

A sensor may define its data as being oriented to the UI. This will be noted in the documentation for the sensor.

Sensor efficiency

In some applications, you may have a sensor that is accessed very frequently (for example, the accelerometer). Receiving all of these readings can be costly for battery life and app performance. When developing an app, you should keep the following considerations in mind in order to save power:

Skipping duplicate values

By default, a sensor reports a new value for any change, regardless of the size of the change. This can result in your application receiving a lot of irrelevant readings. You can reduce the number of readings updates that your application receives by using the QSensor::skipDuplicates property. When set to true, successive readings with the same or very similar values are omitted and skipped.

Ignoring similar values will save a lot of power when using sensors that report on very small changes, like the accelerometer. Because the accelerometer is so sensitive, it can actually report changes even while the device is stationary. Another example is the light sensor, which generally changes very slowly. If you're only interested in relevant lighting changes, skipDuplicates can save a lot of power.

While the default behavior is to omit the same or very similar readings, this behavior can change depending on how much power the device has. When the device is in a low-power state, the system will adjust itself accordingly so that the sensor only reports on much more significant changes.

Running in the background

By default, an application stops receiving sensor readings once it is running in the background or if the screen is locked. However, it's possible to override this behavior by using the QSensor::alwaysOn property. When set to true, the sensor will continue to send updates to your application, even if your application is inactive. In order to use this property, the run_when_backgrounded setting must be enabled in your application's bar-descriptor.xml file.

While it's possible to receive sensor updates in the background, you should only do this if it's essential to the functionality of your app. Receiving updates in the background can be very costly for power consumption. Applications that abuse this permission might be rejected when they're submitted for approval to BlackBerry World.

Here's an example of a device motion alarm application that is able to run in the background and still conserve battery life. Once the app is started and the device is set down, the application monitors movements of the device, and turns on an alarm if the device moves. The application sets skipDuplicates to true so that the accelerometer doesn't report on changes even while it's stationary.

import bb.cascades 1.0
import bb.multimedia 1.0
import QtMobility.sensors 1.2

Page {
    attachedObjects: [
        SystemSound {
            id: sound
            sound: SystemSound.InputKeypress
        },
        Accelerometer {
            id: alarm
            // Create a variable to hold movement state.
            property bool movement: false
            // Turn on the sensor.
            active: true
            // Don't change sensor axis on screen rotation.
            axesOrientationMode: Accelerometer.FixedOrientation
            // Remove gravity, detect only user movement.
            accelerationMode: Accelerometer.User
            // Keep the sensor active when the app isn't visible
            // or the screen is off (requires app permission in
            // bar-descriptor).
            alwaysOn: true
            // If the device isn't moving (x&y&z==0), don't send
            // updates (saves power).
            skipDuplicates: true

            // Called when a new accel reading is available.
            onReadingChanged: { 
                movement = Math.sqrt(reading.x * reading.x 
                    + reading.y * reading.y + reading.z 
                    * reading.z) > .2;
                if (movement) {
                    // Movement is detected, play a sound.
                    sound.play();
                }
            }
        }
    ]
    Container {
        Label {
            text: alarm.movement ? "ALARM: Movement Detected!" :
                    "No Movement Detected"
        }
    }
}

Filtering sensor readings

If you need more control over how you filter sensor readings, you can use the QSensorFilter interface or one of its subclasses to provide a more efficient way for the sensor to notify your class that the sensor has changed. For example, in an application that requires a user's physical input, the accelerometer can send out numerous reading updates, even for very small changes. Using a filter, you can specify the frequency or number of new readings that the sensor delivers to your application, rather than have every single new reading delivered.

Not only can the filter help make your application more efficient, it can also be used to decrease noise in the sensor readings. In some applications, drastic changes to accelerometer readings can have a negative effect on the overall experience. For example, in a racing game that is controlled by the user "steering" the device, you're probably not going to want the car to be as responsive as the driver. Quick, jerky movements by the user will result in cars crashing against barriers. In these cases, you could implement a low-pass filter that reduces the effect of drastic changes in the readings, "smoothing out" the values.

Here's an example of how to create a filter by subclassing QAccelerometerFilter. The logic for filtering sensor values is located in filter(), which is called for every new sensor reading. When you return false, the sensor reading is suppressed. When you return true, the readingChanged() signal is emitted, indicating that the sensor reading is valid.

// AccelerometerFilter.h

#ifndef ACCELEROMETERFILTER_H_
#define ACCELEROMETERFILTER_H_

#include <QtSensors/QAccelerometerFilter>
#include <QVariant>

using namespace QtMobility;

class AccelerometerFilter : public QObject, public QAccelerometerFilter
{
	Q_OBJECT

public:
    // Constructor
	explicit AccelerometerFilter(QObject *parent = 0);

public slots:

	/* Overrides QAccelerometerFilter::filter().
     * This function would contain the logic used to filter out
     * sensor readings. Return false in order to suppress a
     * sensor reading. Return true and the readingChanged()
     * signal is emitted.
     */
    bool filter(QAccelerometerReading *reading);
};

#endif /* ACCELEROMETERFILTER_H_ */

Once you've defined the filter, create the QAccelerometer in your application class, set the filter using addFilter(), and connect the readingChanged() signal to the slot that handles readings changes.

// SensorFilterApp.cpp

#include "app.hpp"
#include "AccelerometerFilter.h"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
using namespace bb::cascades;

#include <QtSensors/QAccelerometer>
using namespace QtMobility;


App::App()
{
	// ...Define the UI and perform any other app setup tasks...

    // Create the sensor and add the filter.
    m_Sensor = new QAccelerometer(this);
    m_Sensor->addFilter(new AccelerometerFilter());

	// Connect the readingChanged() signal to the custom slot used
	// for handling the readings.
	// If any Q_ASSERT statement(s) indicate that the slot failed to connect to 
	// the signal, make sure you know exactly why this has happened. This is not
	// normal, and will cause your app to stop working!!
	bool success = connect(m_Sensor,
						   SIGNAL(readingChanged()),
						   this,
						   SLOT(accelReadingChanged()));
    
    // This is only available in Debug builds.
    Q_ASSERT(success);
	
	// Since the variable is not used in the app, this is added to avoid a 
	// compiler warning.
	Q_UNUSED(success);

	m_Sensor->start();
}

void App::accelReadingChanged()
{
	// ...Handle new accelerometer readings...
}

Related resources

Web-based training

 

Last modified: 2013-12-21

comments powered by Disqus