📜 ⬆️ ⬇️

Development for Sailfish OS: Using Sensors (Part 2)

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:


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:


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:


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:


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

Source: https://habr.com/ru/post/343530/


All Articles