Sensor efficiency

Some sensors generate a lot of data (for example, the accelerometer). Processing all of these readings can be costly for battery life and app performance. When developing an app, you should keep the following best practices in mind.

Best practices

Check for the existence of sensors. The sensors that are available to your app may vary depending on which device your app is running on. You should verify that a sensor exists before trying to use it in your app.

The following code sample demonstrates how to verify whether the accelerometer is supported:

You can't check for availability of a sensor in QML directly. You can find out if a sensor is available by calling QSensor::sensorTypes() in your C++ code and then exposing a context property to QML as follows:

  
bool hasSensor = QSensor::sensorTypes().contains("QOrientationSensor");
qml->setContextProperty("hasSensor", hasSensor);

You can check which sensors are available as follows:

#include <QtSensors/QSensor>
using namespace QtMobility;

foreach (QByteArray sensor, QSensor::sensorTypes())
    qDebug() << sensor;

You can check for a specific sensor as follows:

bool hasSensor=QSensor::sensorTypes().contains("QRotationSensor");
qDebug() << hasSensor;

In the following code sample, if sensor_is_supported() returns true, then you can use the accelerometer in your app.

if (!sensor_is_supported(SENSOR_TYPE_ACCELEROMETER)) {    
    printf("Accelerometer not supported by device! Exiting program.");
    bps_shutdown();
    return EXIT_FAILURE;
}

Simulate sensors. If you are running your app on the BlackBerry 10 Device Simulator, you can simulate the following sensor events and data by using the controller app:

  • Device orientation
  • Device tilt
  • Device rotation
  • Proximity sensor values
  • Ambient light sensor values

For more information on using the controller app with the simulator, see Using the simulator.

Avoid overuse of expensive sensor functions. As discussed in Influences on sensors, the Earth's magnetic field has an impact on the readings taken by various sensors. The function wmm_get_geomagnetic_field() returns the geomagnetic field for a specified location and time, but it is an expensive call that should be called only when the location of the device has changed. Your app might handle a location change in a BPS event handling loop by calling a function that updates a global variable containing the geomagnetic field value returned by wmm_get_geomagnetic_field(). Using a globally accessible instance of the geomagnetic field value in your app avoids the need to call wmm_get_geomagnetic_field() in multiple places.

Filter sensor readings. Device sensors can generate a lot of data. There are two primary ways to tune how your app uses sensor data:

  1. Skipping duplicate sensor data. Removing duplicate data means less data needs to be processed, which improves performance.
  2. Setting the sensor's refresh rate. This setting is directly tied to how often sensor data is reported. The higher the rate, the more processing that is required.

If you are developing a Cascades app, 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 app that requires a user's physical input, the accelerometer can send out numerous reading updates, even for small changes. Using a filter, you can specify the frequency or number of new readings that the sensor delivers to your app, rather than have every new reading delivered.

Not only can the filter help make your app more efficient, it can also be used to decrease noise in the sensor readings. In some apps, drastic changes to accelerometer readings can have a negative effect on the overall experience. For example, in a racing game where the user "steers" the device, you probably don't want the car to be as responsive as the driver. Quick, jerky movements by the user 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.

If you are developing an app using C, you can set a sensor's rate by calling sensor_set_rate() specifying the appropriate sensor_type_t and how often (in microseconds) the sensor should try to provide updates. A sensor's refresh rate is the rate at which the sensor provides updates. The device tries to achieve the sensor rate you specify, but this rate is not guaranteed. Updates may occur more or less frequently because of different internal hardware sensor rates, or other apps using a different rate. For example, if you set a sensor's rate to 50Hz, and the closest supported rate is 48Hz, the rate is adjusted automatically to 48Hz. Call the function sensor_info() to retrieve information about the sensor, including the minimum, maximum, and default sensor rates (also referred to as delays).

Not applicable

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_ */

When you've defined the filter, create the QAccelerometer in your app 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
	// that is used for handling the readings

	bool success = connect(m_Sensor,
						   SIGNAL(readingChanged()),
						   this,
						   SLOT(accelReadingChanged()));
    
    Q_ASSERT(success);
	
	Q_UNUSED(success);

	m_Sensor->start();
}

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

The following code sample demonstrates setting the refresh rate of the accelerometer to 250,000 microseconds (or four times a second):

if (sensor_set_rate(SENSOR_TYPE_ACCELEROMETER, 250000) == BPS_SUCCESS)
{
  // Rate set successfully
} else {
  // Rate was not set
}

Skip duplicate values. By default, a sensor reports a new value for any reading, regardless of whether the value has changed. Your app might receive irrelevant readings. You can reduce the number of readings updates that your app receives by using the QSensor::skipDuplicates property. When this property is set to true, successive readings with the same or similar values are omitted and skipped.

Not applicable

Not applicable

You can call sensor_set_skip_duplicates() to reduce the number of sensor updates. When this function is called with enable_skipdup set to true, successive readings with the same or similar values are omitted and skipped. Ignoring similar values is necessary because some sensors (for example, the accelerometer) can report small changes in their readings even if the device is stationary.

The following code sample requests that the device should try to skip duplicate events from the accelerometer:

if (sensor_set_skip_duplicates(SENSOR_TYPE_ACCELEROMETER, true) == BPS_SUCCESS)
{
  // Rate set successfully
} else {
  // Rate was not set
}                

Ignoring similar values saves a lot of power when using sensors that report on small changes, like the accelerometer. Because the accelerometer is so sensitive, it can report changes even while the device is stationary. Another example is the light sensor, which generally changes 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 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 adjusts itself accordingly so that the sensor only reports on much more significant changes.

Receive sensor readings in the background. By default, an app stops receiving sensor readings when 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 this property is set to true, the sensor continues to send updates to your app, even if your app is inactive. In order to use this property, the run_when_backgrounded permission must be enabled in your app's bar-descriptor.xml file.

You should request updates in the background only if it is essential to the functionality of your app. Receiving updates in the background can consume a lot of battery power. Apps that abuse this permission might be rejected when they're submitted for approval to BlackBerry World.

Here's an example of a device motion app that can run in the background and still conserve battery life. When the app is started and the device is set down, the app monitors movements of the device, and turns on an alarm if the device moves. The app 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.xml)
            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"
        }
    }
}

Not applicable

Not applicable

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus