Hello! This article is the second part of a large article on the use of sensor devices running Sailfish OS. The first part is available
here .
Tilt angle sensor
The tilt angle sensor, as the name implies, determines the tilt angle of the device. The situation with tilt measurement components is similar to light sensors: there is one more “smart” component - the
RotationSensor , and its less advanced companion - the
OrientationSensor .
RotationReading provides information about the exact angle of inclination relative to three coordinate axes: property x contains information about how far the longitudinal axis of the smartphone deviates from the horizontal position, property y contains the same information, but for the transverse axis, and z - about the rotation relative to the screen center . More clearly it can be traced in the image:
You can watch these values in the improvised table on the device screen:
')
RotationSensor { id: rotationSensor active: true } Column { Label { text: "x: " + rotationSensor.reading.x } Label { text: "y: " + rotationSensor.reading.y } Label { text: "z: " + rotationSensor.reading.z } }
The definition of the z values is not always available. To determine the availability of this value, you need to check the
hasZ property of the
RotationReading component.
OrientationSensor , as a less functional component, allows you to answer the important question: "What is the edge of a smartphone pointing up?". Accordingly, it can take six values, each of which corresponds to a face. Plus one additional value indicating the inability to determine the position. These values can be found in the following list:
- OrientationReading.Undefined — Cannot determine the location of the device,
- OrientationReading.TopUp - the top face is pointing up,
- OrientationReading.TopDown - bottom edge pointing up (upside down)
- OrientationReading.LeftUp - the left side faces up,
- OrientationReading.RightUp - right edge pointing up,
- OrientationReading.FaceUp - the device is located with the screen up,
- OrientationReading.FaceDown - the device is located with the screen down.
OrientationSensor can be used, for example, in order to maintain the orientation of the image when the position of the device itself changes:
OrientationSensor { id: orientationSensor active: true } Image { anchors.centerIn: parent source: "http://social.fruct.org/content/img/fruct.png" transform: Rotation { angle: rotationAngle() } } function rotationAngle() { switch (orientationSensor.reading.orientation) { case OrientationReading.TopUp: return 0; case OrientationReading.RightUp: return 90; case OrientationReading.TopDown: return 180; case OrientationReading.LeftUp: return 270; default: return 0; } }
In the example above, the logo will be drawn to the top of the face of the smartphone:
Magnetometer
The magnetometer is represented by a QML-type
Magnetometer , it provides information about the magnetic field strength around the device in X, Y, and Z axes. Values are provided in tesla.
The magnetometer can react both to the intensity of any magnetic fields around the device, and to the intensity of only the geomagnetic field. This behavior is governed by the
returnGeoValues property, which can accept either
true or
false . Initially, it is
false and responds to any magnetic fields nearby. The difference between these two modes is that when
returnGeoValues is on, the sensor will try to eliminate the influence of extraneous magnetic fields coming from sources of the magnetic field, such as conductors, magnets and electronic devices, and focus on the Earth’s natural magnetic field. Of course, completely eliminate the influence of extraneous magnetic fields will not succeed, so that the sensor readings will still change when new sources appear.
The
MagnetometerReading object has, in addition to the already familiar
x ,
y , and
z properties, which contain axial strength, also the
calibrationLevel property — the level of accuracy in measuring the geomagnetic field intensity. This parameter takes values from 0 to 1 in accordance with the sensor calibration level and changes its value only when
returnGeoValues contains
true . The property reflects how strongly close magnetic fields affect the measurement of the geomagnetic field. With a large impact, the value will decrease to zero. To return the accuracy of the measurements, it is necessary to calibrate the sensor. Usually this is done by rotating the device in an arc of the number 8 or simply swinging it in different directions in space. With a low accuracy rate, the sensor values will be very different from the actual ones. This is especially true when using a magnetometer as a compass.
Compass
The
Compass component acts as an electronic compass. His reading property contains a
CompassReading object, which provides information about the azimuth of the device — the angle between the north magnetic pole and the device axis X in the direction of the clockwise direction. This information, like the readings of a magnetometer, is based on data from the geomagnetic field. In this regard, the compass is also susceptible to various interferences and also requires calibration.
CompassReading has two properties:
- azimuth : qreal is the horizontal angle between the north magnetic pole of the Earth and the direction to which the top edge of the smartphone is pointing,
- calibrationLevel : qreal - the accuracy of the sensor readings, is completely analogous to the calibrationLevel property of the MagnetometerReading type.
Filtering sensor readings
The readings of some sensors may change many times per second. Using the standard Qt notification mechanism using signals and slots significantly slows down the process of obtaining values and reduces the accuracy of measurements in general. An alternative to using signals to obtain sensor values is the
QSensorFilter class. This class is an interface that implements a callback function, which is called when new readings are received by a signed sensor class. A remarkable feature of this function is the ability to change the sensor measurements coming into it, thereby transforming the final value of the
reading property. And together with the ability to hang several filters on one sensor, all conversions over the values can be carried out by means of filters with the output of the desired results.
Similar to other parent classes from Qt Sensors API,
QSensorFilter has only a universal set of methods:
- public bool filter (QSensorReading * reading) is the same callback function that is called when receiving measurements from the sensor. It can be pre-processed values. If this function returns true , then the readings are transmitted either to the next filter, if it exists, or a signal is emitted to change the value of the reading property. When false is returned, processing of the current sensor reading is terminated.
- protected void setSensor (QSensor * sensor) - allows you to specify the sensor class to which the filter is attached, from the filter itself.
Methods that are specific to a particular type of sensor are already implemented in heir classes. As with objects of type
Reading , each sensor has its own filter. For example, an accelerometer, with which the
QAccelerometer class
corresponds , has a
QAccelerometerFilter filter, and its
filter method takes the following form:
bool filter(QAccelerometerReading *reading)
Unlike the method of its parent, as a parameter it receives an object with accelerometer measurements. The same applies to other types of filters.
Filters are managed using class-sensor methods. To do this,
QSensors has a number of functions that allow them to manage a set of handlers:
- void addFilter (QSensorFilter * filter) - adds the specified filter to the end of the list. In this case, one filter may be included in the list several times.
- void removeFilter (QSensorFilter * filter) - removes from the list the first found entry of the specified filter.
- QList <QSensorFilter *> filters () const - a list of pointers to all filters the sensor is subscribed to. The call to handler functions occurs in ascending order of their position in the list.
Unlike other Qt Sensors API classes, filters initially do not have an interface for working with them from QML code. In addition, methods for controlling the filter bank of a particular sensor also cannot be used directly from QML.
As an example of using a filter, you can consider the implementation of the accelerometer modes
QAccelerometer :: User and
QAccelerometer :: Gravity . This filter can be useful on those devices that do not support accelerometer modes other than
Combined . For this, you also need to attract the rotation sensor - the
RotationSensor .
Before proceeding with the implementation, you need to insert a couple of clarifying words about the accelerometer operation modes: acceleration caused by gravity and acceleration caused by the device’s movement by the user the sensor cannot physically distinguish, most devices use systems from several sensors and information available on acceleration of free fall to calculate the effects of gravity only. And after this information is used to calculate the acceleration caused by the user. We will use this approach to implement
Gravity and
User modes.
First of all, you need to have a class that performs filtering, which will be inherited from
QAccelerometerFilter . In it we will immediately set the value of G - the acceleration of gravity.
#define G 9.80665 #include <QtSensors> #include <math.h> class AccelerationModeFilter : public QAccelerometerFilter { public: AccelerationModeFilter(); bool filter(QAccelerometerReading *reading); void setAccelerationMode(QAccelerometer::AccelerationMode accelerationMode); QAccelerometer::AccelerationMode getAccelerationMode(); private: QRotationSensor rotationSensor; QAccelerometer::AccelerationMode accelerationMode; };
In the class, you need to have two types of properties:
QRotationSensor - rotation sensor, we will use it to determine the position of the device in space;
QAccelerometer :: AccelerationMode - measurement mode of the accelerometers value, in accordance with this value the required method of calculating the values will be selected.
When calculating the projections of the acceleration on the coordinate axes, it will be necessary to often resort to trigonometric operations like taking sine and cosine. For this, the standard library C
math.h and its methods
sin () and
cos () will be used. These methods take angle values in radians, while the sensor provides values in degrees. Therefore, we get a small method that will convert the angle values from degrees to radians:
qreal toRadian(qreal degree) { return degree * M_PI / 180; }
The mode of calculating the acceleration will be set in the class constructor, in the same place we initialize and activate the tilt sensor:
AccelerationModeFilter::AccelerationModeFilter() : rotationSensor() { rotationSensor.setActive(true); }
The most important part is to override the
filter (QAccelerometerReading * reading) method, in which the calculations will be performed:
bool AccelerationModeFilter::filter(QAccelerometerReading* reading) { switch (accelerationMode) { case QAccelerometer::Gravity: reading->setX(G * sin(toRadian(rotationSensor.reading()->y()))); reading->setY(- G * sin(toRadian(rotationSensor.reading()->x()))); reading->setZ(- zAxisAcceleration(rotationSensor.reading()->x(), rotationSensor.reading()->y())); break; case QAccelerometer::User: reading->setX(reading->x() - G * sin(toRadian(rotationSensor.reading()->y()))); reading->setY(reading->y() + G * sin(toRadian(rotationSensor.reading()->x()))); reading->setZ(reading->z() + zAxisAcceleration(rotationSensor.reading()->x(), rotationSensor.reading()->y())); break; } return true; } void AccelerationModeFilter::setAccelerationMode(QAccelerometer::AccelerationMode accelerationMode) { this->accelerationMode = accelerationMode; } QAccelerometer::AccelerationMode AccelerationModeFilter::getAccelerationMode() { return accelerationMode; }
In the
switch statement, we select blocks, each of which will be responsible for its mode of measuring acceleration; there are two such modes:
Gravity and
User ;
Combined can be skipped, since the values initially go in this mode. In the
switch statement blocks, the acceleration values for each of the axes are recalculated - the values are written to the properties of the passed argument
reading . To calculate the acceleration along the X and Y axes, it is enough to simply multiply the gravitational acceleration by the sine of the angle between the smartphone and the corresponding axis of the coordinate plane, and to calculate the acceleration along the Z axis, you will need to apply a transformation matrix. We implement this in a separate method:
qreal zAxisAcceleration(qreal xDegree, qreal yDegree) { qreal xRadian = toRadian(xDegree); qreal yRadian = toRadian(yDegree); double zCoordinates[3] = {0, 0, G}; double rotationValues[9] = { -cos(xRadian), 0, sin(xRadian), sin(yRadian) * sin(xRadian), cos(yRadian), -sin(yRadian) * cos(yRadian), -cos(yRadian) * sin(xRadian), sin(yRadian), cos(yRadian) * cos(xRadian) }; QGenericMatrix<1, 3, double> zVector(zCoordinates); QGenericMatrix<3, 3, double> rotationMatrix(rotationValues); return (rotationMatrix * zVector)(2, 0); }
Since we do not have a geometry lesson, I think there is no need to explain why the matrix looks that way, but those interested can always turn to
Wikipedia .
For the
Gravity mode, the calculated acceleration values along the axes are simply written into the corresponding properties of the
reading object, since in this case the acceleration values depend only on the position of the device. To determine the values in
User mode, the absolute values of the obtained calculations must be subtracted from the accelerometer readings - this way we exclude the effect of gravity on the sensor measurements.
On this implementation of the filter is over, connect it to the desired accelerometer and use two new modes:
#include <QtSensors> #include "accelerometerfilterexample.h" class AccelerometerWithFilter : public QAccelerometer { Q_OBJECT public: AccelerometerWithFilter() : filter() { setActive(true); addFilter(&filter); connect(this, &AccelerometerWithFilter::accelerationModeChanged, [=](AccelerationMode accelerationMode) {this->filter.setAccelerationMode(accelerationMode);}); } private: AccelerationModeFilter filter; };
Conclusion
This and the
previous article review the basic sensors available on the Sailfish OS operating system. And although these examples are very basic, they should acquaint the reader with the capabilities of the Qt Sensors API and show the potential in using the plugin when developing applications.
Technical issues can also be discussed on
the Sailfish OS Russian-speaking community channel in a Telegram or
VKontakte group .
Posted by: Maxim Kosterin