I. L'article original▲
Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez les trouver en version originale.
Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.
Cet article est la traduction de l'article Sensor Bonanza de Geir Vattekar paru dans la Qt Quarterly Issue 35.
Cet article est une traduction de l'un des tutoriels en anglais écrits par Nokia Corporation and/or its subsidiary(-ies), inclus dans la documentation de Qt. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.
II. Introduction▲
Ces dernières années, la quantité de capteurs disponibles au développeur a sans cesse augmenté. Par exemple, un des plus récents ajouts est le capteur de proximité, qui détecte si un objet est proche ou non de l'écran.
Cet article montre comment utiliser ces capteurs dans des applications Qt C++/QML. On va voir comment lire des données de ces capteurs et comment minimiser l'utilisation de la batterie - en effet, de nombreux capteurs sont assez gourmands en énergie et peuvent vider la batterie en quelques heures sur certains périphériques.
On a implémenté trois petites applications pour cet article ; chacune utilise un capteur différent :
- Accelerometer QML App montre les données de l'accéléromètre ;
- RotationSensor QML App affiche un ring de hockey avec un palet qui peut être bougé en inclinant la patinoire ;
- Compass C++ App affiche une boussole.
Les capteurs utilisent le système de coordonnées ci-dessous. Quand le périphérique est tenu de cette manière, le plan xz sera aligné avec le sol et le plan xy lui sera parallèle.
Les capteurs vont donner des données relatives au système de coordonnées. Un accéléromètre va notifier des accélérations selon les trois axes. Un capteur de rotation va donner des angles entre le périphérique et les axes.
III. Présentation des applications▲
On va utiliser le code des applications proposées en téléchargement tout au long de l'article. Voici une courte présentation de chacune. On ne donnera que quelques bouts de code de ces exemples.
III-A. La boussole▲
L'application affiche une boussole à l'écran et utilise un capteur pour trouver le nord magnétique. On devrait mentionner que les boussoles digitales sont affectées par d'autres champs magnétiques et doivent être calibrées avant de donner des valeurs correctes. Les données du capteur sont l'azimut, qui donne le nord magnétique en degrés par rapport à la partie supérieure de l'interface, soit le haut de l'écran, ainsi que le niveau actuel de calibration.
III-B. L'accéléromètre▲
Un accéléromètre mesure des accélérations propres relatives à une chute libre. Il fournit trois valeurs : l'accélération selon chaque axe. Quand le périphérique est au repos, seule la gravité s'applique sur lui et on peut calculer l'angle entre le périphérique est les axes du système de coordonnées. Comme on peut le voir sur l'image, l'application montre les valeurs données par l'accéléromètre en tant que texte sur l'écran. On utilise ces valeurs pour faire tourner le texte, pour qu'il s'affiche toujours en face de l'utilisateur quand on fait tourner le périphérique dans le plan xy (le texte sera toujours aligné avec l'axe y).
III-C. Le capteur de rotation▲
Il donne les angles entre le périphérique et les axes du système de coordonnées. On utilise ces angles pour calculer la vitesse du palet sur la patinoire. L'image ci-dessous illustre comment les angles sont donnés relativement à la manière de tenir le périphérique.
IV. Lire les données des capteurs▲
Pour montrer comment lire les données des capteurs, on se tourne vers l'accéléromètre et la boussole.
IV-A. En C++▲
L'API Qt pour les capteurs est disponible dans le projet Qt Mobility. Les API sont livrées avec le Qt SDK, mais il est toujours possible de les compiler depuis les sources (voir la documentation à ce sujet).
On peut accéder aux capteurs depuis le C++ ainsi que QML. On va commencer en utilisant les classes C++. Chaque capteur dans Qt est un QSensor. Ses sous-classes implémentent une interface pour un capteur spécifique. QCompass, par exemple, lit les données d'une boussole digitale. En construisant un QCompass avec son constructeur par défaut, la boussole par défaut (probablement la seule) du système sera utilisée.
QCompass
*
compass =
new
QCompass
;
Les valeurs courantes pour la boussole sont disponibles par la fonction QCompass::reading(), qui retourne un objet QCompassReading. Cette classe contient les données de la boussole, elles peuvent être récupérées par azimuth() et calibrationLevel().
Pour commencer à écouter les données de la boussole, il faut la démarrer. On peut ensuite connecter le signal QSensor::readingChanged(). De même, on pourrait utiliser la classe QCompassFilter, qui fournit un rappel quand de nouvelles données sont disponibles. Ceci se passe le mieux avec le système de signaux et de slots de Qt.
class
CompassFilter : public
QCompassFilter
{
public
:
...
bool
filter(QCompassReading
*
reading) {
view->
setAzimuth(reading->
azimuth());
return
false
;
}
...
}
;
En retournant false, on évite que le signal QSensor::readingChanged() soit émis. Il faut noter que les valeurs ne seront pas stockées dans QCompass, c'est-à-dire qu'on ne peut pas les récupérer par la fonction QCompass::reading().
Pour installer le filtre sur un QCompass, il faut utiliser la fonction QSensor::addFilter().
Retourner false arrêtera également la lecture dans les autres filtres. Dans le cas où plusieurs filtres sont installés, seul le dernier devrait agir de la sorte.
IV-B. En QML▲
En QML, il y a un élément pour chaque type de capteur. Pour les accéléromètres, il s'agit d'Accelerometer. Les autres éléments sont nommés d'une manière très similaire aux classes C++, comme RotationSensor et ProximitySensor.
Accelerometer
{
id
:
sensor
onReadingChanged
: {
xText.
text
=
"X: "
+
reading.
x.toFixed
(
2
)
yText.
text
=
"Y: "
+
reading.
y.toFixed
(
2
)
zText.
text
=
"Z: "
+
reading.
z.toFixed
(
2
)
var subFromAngle =
screen.
width >
screen.
height ?
Math.
PI
:
Math.
PI/
2
var angle =
Math.atan2
(
reading.
y,
-
reading.
x)
theColumn.
rotation = (
angle-
subFromAngle) * (
180
/
Math.
PI)
...
}
}
Pour lire les données, on utilise le gestionnaire de signal onReadingChanged, appelé quand QSensor émet QSensor::readingChanged(). Les données sont disponibles dans la propriété Accelerometer::reading. theColumn est une Column qui contient trois éléments Text, qui affichent les données de l'accéléromètre.
On ne peut pas utiliser les filtres en QML.
IV-C. En utilisant directement QSensor▲
Les sous-classes de QSensor ne sont que des classes de commodité qui aident à lire des données de capteurs connus. On peut cependant aussi lire des données de la classe QSensor directement : son constructeur utilise le nom du backend de capteur à utiliser. On peut créer un QSensor pour lire les données de l'accéléromètre comme ceci :
QSensor
accelerometer("QAccelerometer"
);
Le nom du capteur est déterminé par le backend. Qt utilise le nom de la classe de commodité pour cela. Le QSensor émettra également le signal QSensor::readingChanged() quand de nouvelles données seront disponibles. Les données pour les valeurs actuelles du capteur sont disponibles dans QSensor::reading() :
QSensorReading
*
reading =
accelerometer.reading();
Les données sont lues avec la fonction QSensorReading::reading(). Elle prend un index si le capteur fournit plusieurs valeurs. Si on veut accéder aux données avec un QSensorFilter, on peut agir comme suit :
bool
Filter::
filter(QSensorReading
*
reading)
{
int
x =
reading.value(0
).toInt();
int
y =
reading.value(1
).toInt();
int
z =
reading.value(2
).toInt();
// S'occuper des valeurs
return
false
;
}
La classe QAccelerometerReading est simplement un emballage qui a des propriétés Qt pour x, y et z. Comme mentionné, on ne devra probablement pas utiliser l'API des capteurs directement. Pour implémenter son propre backend de capteur, il est recommandé de prendre le temps d'implémenter des classes de commodité.
V. Vérifier le support des capteurs▲
On peut demander au système quels capteurs sont disponibles avec la fonction QSensor::sensorTypes().
foreach
(QByteArray
sensor, QSensor
::
sensorTypes())
qDebug
() <<
sensor;
Le tableau d'octets contiendra des chaînes pour chaque capteur supporté. Les chaînes correspondent au nom des classes de commodité des capteurs, comme QAccelerometer ou QRotationSensor.
On peut vérifier si un capteur spécifique existe :
bool
hasSensor =
QSensor
::
sensorTypes().contains("QRotationSensor"
);
QML ne permet pas de manière directe de vérifier la disponibilité de capteurs. Ainsi, si on veut effectuer de telles vérifications en QML, on doit exposer hasSensor :
QDeclarativeView
view;
bool
hasSensor =
QSensor
::
sensorTypes().contains("QOrientationSensor"
);
view.rootContext()->
setContextProperty("hasSensor"
, hasSensor);
Le booléen est maintenant exposé comme propriété de l'objet global de QDeclarativeEngine, on peut donc y faire référence n'importe où dans le code QML.
Text
{
anchors.centerIn
:
parent
opacity
:
hasSensor ? 0.0 :
1.0
font.pointSize
:
12
text
:
"Rotation sensor not available"
}
Ce simple élément est visible quand un capteur de rotation n'est pas disponible et affiche un message approprié à l'utilisateur. Il faut noter que l'on définit la propriété Item::z pour qu'il soit affiché par dessus les autres éléments.
VI. Gestion de la batterie▲
La plupart des capteurs sont des entités très gourmandes en énergie. Un accéléromètre, par exemple, peut vider la batterie en quelques heures sur bon nombre de périphériques. Voici quelques astuces pour minimiser l'utilisation de la batterie :
- désactiver les capteurs quand l'application passe en arrière-plan ;
- désactiver les capteurs quand l'utilisateur est inactif ;
- ne garder les capteurs actifs que quand leurs données sont requises.
La définition de l'inactivité dépendra de l'application. Lors de l'utilisation d'un accéléromètre ou d'un capteur de rotation, on peut enregistrer quand un périphérique est au repos ou déposé sur une table, par exemple. Dans la plupart des applications, il peut être raisonnable de simplement enregistrer depuis combien de temps l'utilisateur n'a plus interagi avec l'interface. Pour les deux approches, on peut utiliser un QTimer ou un élément QML Timer. Voici comment on peut enregistrer l'inactivité de l'utilisateur avec un accéléromètre.
function
stop
()
{
if
(sensor.active) {
sensor.stop
(
)
}
}
Timer
{
id
:
inactiveTimer
interval
:
2000
onTriggered
:
screen.stop()
}
Accelerometer
{
id
:
sensor
onReadingChanged
: {
...
if (
Math.abs
(
reading.
x) <
2
.
0
&&
Math.abs
(
reading.
y) <
2
.
0
) {
inactiveTimer.start
(
)
}
else {
inactiveTimer.stop
(
)
}
}
}
On décide ici de l'inactivité quand le téléphone est posé sur la table (ou tenu de cette manière). On se permet un peu de bruit dans les lectures (il y en a toujours). Évidemment, si l'utilisateur est en mouvement (dans une voiture), cette technique va échouer. Le capteur de rotation n'est pas affecté par cette accélération, on peut l'utiliser en lieu et place.
Désactiver un capteur quand l'application passe en arrière-plan est un peu plus engagé. On doit écouter les événements du widget de premier niveau. Si on sous-classe QDeclarativeView, on peut réimplémenter QWidget::event().
bool
View::
event(QEvent
*
event)
{
switch
(event->
type()) {
;
case
QEvent
::
Leave:
case
QEvent
::
WindowDeactivate:
// Stop the sensor
break
;
}
return
QDeclarativeView
::
event(event);
}
MeeGo envoie l'événement QEvent::Leave quand l'application part en arrière-plan, tandis que Symbian envoie QEvent::WindowDeactivate. Les deux utilisent QEvent::Activate quand l'application est de retour à l'avant-plan.
Avec QML (dans l'exemple de l'accéléromètre, par exemple), on peut utiliser la classe QDeclarativeExpression, qui permet d'exécuter du code JavaScript.
QDeclarativeExpression
expr(rootContext(), rootObject(), "stop()"
);
expr.evaluate();
if
(expr.hasError()) {
qDebug
() <<
"Failed to stop accelerometer."
;
}
Pour que ceci fonctionne, l'élément Accelerometer doit aussi être membre de l'objet racine, c'est-à-dire l'objet défini dans QDeclarativeView::setSource(). On peut aussi définir un signal dans la sous-classe et exposer la vue elle-même au moteur QML. On peut connecter des signaux C++ en utilisant la syntaxe suivante :
myExposedQObject.
signalName.connect
(
myElement.
slotFunction)
On utilise généralement le gestionnaire Component.onCompleted pour installer les connexions.
VII. Emballer le tout▲
On n'a regardé ici que trois capteurs disponibles, alors que Qt fournit des QSensor pour bien d'autres capteurs que l'on peut trouver sur des mobiles. On a, notamment, le capteur de lumière ambiante, qui permet de déterminer si l'ambiance est plutôt lumineuse ou sombre. Peu importe quels capteurs sont requis pour vos applications, nous espérons que cet article vous aura donné quelques astuces pour les utiliser de la bonne manière.
VIII. Divers▲
Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisés à traduire cet article !
Merci à ram-0000 pour sa relecture !