Данная тема достаточно обширна и у нас получилось очень много материала, поэтому мы решили разделить ее на две части для удобства чтения. Вторая часть доступна тут.
Предисловие
Встроенные датчики – это та начинка смартфона, которая делает его умным. Каждый смартфон оснащен какими-либо датчиками. Более того, чем крупнее набор сенсоров смартфона, тем шире его возможности. Естественно, смартфоны, работающие под управлением операционной системы Sailfish OS, не являются исключением.
Для большинства пользователей смартфонов сенсоры лишь привносят удобство в повседневное использование, будь то поворот экрана при наклоне устройства или понижения яркости при снижении освещенности помещения. Однако, датчики позволяют реализовать более серьезные идеи, вплоть до создания дополненной и виртуальной реальности. Использование сенсоров в таких масштабах мы рассматривать, конечно же, не будем, но довольно полное представление об их возможностях дать можем.
Обзор Qt Sensors API
Инструментарий для работы с датчиками на Sailfish OS представлен в виде Qt Sensors API, который предоставляет как нативную библиотеку C++, так и интерфейс для работы напрямую из QML-кода.
Для того, чтобы использовать QML-типы из Qt Sensors API, необходимо в проект приложения под Sailfish OS добавить зависимость от плагина qt5-qtdeclarative-import-sensors. Производится это добавлением наименования плагина в раздел Requires YAML-файла проекта:
...
# Runtime dependencies which are not automatically detected
Requires:
- sailfishsilica-qt5 >= 0.10.9
- qt5-qtdeclarative-import-sensors
...
После подключению плагина необходимо в QML-код импортировать модуль QtSensors версии 5 и выше. На момент написания статьи, наиболее актуальной версией, доступной на Sailfish OS, является версия под номером 5.2. Она и будет использоваться в примерах:
import QtSensors 5.2
Если же вы планируете использовать Sensors API напрямую в коде C++, то добавлять зависимость от плагина не нужно. Вместо этого потребуется в .pro-файле проекта указать на компоновку с использованием библиотеки QtSensors. Производится это добавлением в него строки:
QT += sensors
После чего все классы API станут доступны для включения в проект, и можно будет импортировать либо всю библиотеку сразу:
#include <QtSensors>
Либо только необходимые классы, например, акселерометр:
#include <QtSensors/QAccelerometer>
Набор доступных датчиков в первую очередь зависит от начинки устройства. Например, в распоряжении смартфона INOI R7 находятся датчики ориентации, освещение, вращения, угла наклона, близости, напряженности магнитного поля, а также акселерометр.
Для Sailfish OS работу с датчиками помогает выстроить Qt Sensors API. Набор сенсоров, охватываемый этим API, гораздо обширнее того, что рассматривается в этой статье.
Каждый сенсор представлен в Qt Sensors двумя интерфейсами: классом C++ и типом QML, оборачивающим соответствующий класс C++. Оба интерфейса предоставляют полноценный доступ к функциям датчика, поэтому то, какой из них будет использоваться, зависит только от желания и возможности разработчика.
Сначала стоит получить список всех доступных на устройстве датчиков. Для этого есть элемент SensorGlobal и три его метода:
- string defaultSensorForType(type);
- list<string> sensorTypes();
- list<string> sensorsForType(type).
sensorTypes возвращает список всех типов сенсоров, присутствующих на устройстве, в то время как sensorsForType возвращает зарегистрированные идентификаторы для указанного типа сенсоров. defaultSensorForType, как это следует из названия, возвращает идентификатор стандартного сенсора для указанного типа, то есть тот, который будет использоваться QML компонентом, если явно не указать идентификатор.
Аналоги описанных выше методов присутствуют и в C++. В качестве них выступают статические функции класса QSensors:
- static QByteArray defaultSensorForType(const QByteArray &type);
- static QList<QByteArray> sensorTypes();
- static QList<QByteArray> sensorsForType(const QByteArray &type).
Их назначение полностью соответствует назначению QML-методов с соответствующими названиями.
Создадим небольшое приложение, которое выведет на экран список всех сенсоров устройства:
Page {
SilicaListView {
header: PageHeader { title: "Available sensor types" }
anchors.fill: parent
delegate: ListItem {
Label {
text: modelData
anchors.verticalCenter: parent.verticalCenter
x: Theme.horizontalPageMargin
}
}
// Заполняем модель списком доступных типов сенсоров
model: QmlSensors.sensorTypes()
}
}
На смартфоне Jolla C метод QmlSensors.sensorTypes() возвращает следующий список датчиков:
Это говорит о том, что на устройстве доступен следующий набор сенсоров: датчик освещенности, датчик приближения, датчик угла наклона и акселерометр.
Все классы, осуществляющие работу с датчиками, унаследованы от одного общего родительского класса Sensor. Этот класс представляет из себя абстрактный сенсор, свойства и методы которого применимы ко всем его дочерним классам:
- active — свойство, определяющее активность сенсора в данный момент. Присвоение ему значения true аналогично вызову метода start() — начинает считывание значений с датчика, если это возможно. Присвоение false наоборот останавливает считывание.
- alwaysOn — свойство, определяющее активность сенсора при выключенном экране.
- axesOrientationMode — определяет, как положение устройства влияете на оси измерения значений. По-умолчанию оси располагаются так, как показано на изображении:
Доступно три режима:
- FixedOrientation — оси всегда сохраняют свое положение по отношению к устройству. Это значение является стандартным и будет использоваться, если устройство не поддерживает смену режима ориентации осей.
- AutomaticOrientation — оси сохраняют свое положение в пространстве, меняя положение относительно устройство. Другими словами положение устройства не сказывается на значения датчиков.
- UserOrientation — положение осей задается пользователем.
- busy — свойство, содержащее состояние занятости сенсора. Если он занят другим процессом, то использовать его невозможно.
- dataRate — содержит частоту обмена данными с датчиком, выражается в количестве операций в секунду.
- error — код последней произошедшей ошибки. Значение свойства устанавливается автоматически при возникновении ошибки и не может быть изменено вручную.
- reading — свойство, содержащее SensorReading — компонент, осуществляющий считывание значений с датчиков. Именно с его использованием происходит получение информации с сенсоров. Значение данного свойства обновляется в соответствии с установленной частотой обмена, оно не может быть записано вручную.
- type — строковое название датчика. Задается в момент определения объекта, не может быть изменено.
- description — содержит строку, описывающую сенсор. Как и type, задается в момент определения объекта, не может быть изменено.
- start() — начинает считывание значений с датчика, равносильно присвоению значения true свойству active.
- stop() — прекращает считывание значений с датчика, равносильно присвоению значения false свойству active.
Так как Sensor является лишь абстрактным типом, то он не может быть объявлен непосредственно. Вместо этого должны быть созданы наследуемые от него компоненты, соответствующие определенному типу датчиков.
Объект типа SensorReading, содержащийся в свойстве reading, заслуживает отдельного внимания, потому как он служит базовым типом для всех компонентов, производящих считывание значений с датчиков. Значения свойства reading изменяется в соответствии с частотой обновления, указанной в свойстве dataRates, но только лишь в те моменты, пока датчик находится в активном состоянии. Если датчик не активен, в reading содержатся последние полученные измерения, а на устройствах без поддержки соответствующего датчика — null.
Сам по себе SensorReading имеет только лишь свойство timestamp, содержащее время последнего получения информации с датчика, однако его подклассы реализуют свойства, непосредственно предоставляющие значения с датчиков устройства. SensorReading, так же как и его наследники, не могут быть объявлены напрямую. Единственный способ их получения — обращение к свойству reading объекта, представляющего соответствующий датчик.
Акселерометр
Начать рассмотрение сенсоров Sailfish OS можно на примере работы с, наверное, самым распространенным датчиком — акселерометром. Акселерометр, иногда называемый G-сенсором, встречается почти во всех современных моделях мобильных устройств. Типичное применения для акселерометра — регистрация изменения ускорения при изменении положения смартфона, что используется для смены ориентации изображения на экране смартфона.
Итак, для получения измерений с акселерометра первым делом стоит объявить на желаемой странице и перевести в активное состояние компонент Accelerometer, отличительной чертой которого от Sensor является наличие в свойстве type значения QAccelerometer.
Accelerometer {
id: accelerometer
active: true
}
Все, компонент создан и уже производит считывание показаний с акселерометра устройства. Но как теперь разработчик может получить эти самые измерения? Здесь нужно вспомнить о свойстве reading, упоминавшемся выше. В этот раз оно содержит QML-тип AccelerometerReading — наследник SensorReading, который кроме всего прочего обзавелся новыми свойствами: x, y и z. Они содержат линейное ускорение устройства по осям X, Y и Z соответственно. Так как мы не меняли значение axesOrientationMode, то положение осей останется стандартным, а именно тем, что приведено на изображении ниже.
Свяжем значения ускорения по осям X и Y с горизонтальным и вертикальным положением изображения на экране. Для этого применим к изображению трансформацию, сдвигающую его горизонтально на значение ускорения по оси X и вертикально на значение ускорения по оси Y. Также добавим ограничение на допустимое положение изображения таким образом, чтобы оно не выходило за границы экрана.
Image {
id: fruct
source: "http://social.fruct.org/content/img/fruct.png"
transform: Translate {
property double horizontalPosition: x - accelerometer.reading.x
property double verticalPosititon: y + accelerometer.reading.y
x: if (horizontalPosition < 0) {
0
} else if (horizontalPosition > page.width - fruct.width) {
page.width - fruct.width
} else {
horizontalPosition
}
y: if (verticalPosititon < 0) {
0
} else if (verticalPosititon > page.height - fruct.height) {
page.height - fruct.height
} else {
verticalPosititon
}
}
}
В результате логотип FRUCT будет перемещается по экрану при наклоне смартфона:
Другой особенностью компонента Accelerometer, отличающей его, от Sensor является наличие свойства accelerationMode, которое контролирует то, как датчик ускорения рассчитывает свои значения. Всего присутствует три режима расчета ускорения:
- Accelerometer.Combined — совместно учитываются значения ускорения, вызываемого как гравитацией, так и движением устройства пользователем.
- Accelerometer.Gravity — учитывается только значение ускорения, вызванного гравитацией. Движение устройства пользователем не оказывает влияния на значения, возвращаемые датчиком, однако учитывается угол наклона устройства.
- Acceleration.User — учитывается только ускорение, вызванное перемещением устройства пользователем, значения ускорения, вызванного гравитацией, не учитываются. В этом режиме при неподвижном положении девайса значение датчика будут близки к 0.
Стоит отметить, что не все устройства и датчики поддерживают возможность изменения режима расчета. Для устройств, не имеющих такой возможности будет применяться комбинированный режим, а изменение значения свойства accelerationMode не будет иметь эффекта на измерение.
Датчик приближения
Датчик приближения реагирует на находящиеся в непосредственной близости объекты без физического контакта с ними. Например, датчик приближения, установленный на мобильном телефоне, позволяет отключать сенсор касаний экрана при приближении телефона к уху пользователя во время разговора или переводить устройство в спящий режим при перемещении его в карман. Из этого следует, что основная задача датчика — блокировать экран устройства, тем самым не давая пользователю случайно нажать на кнопку или произвести незапланированное действие. Обычно такой датчик располагается на лицевой панели устройства сверху. Свиду он немного напоминает отверстие под фронтальную камеру.
Датчик приближения в QML представлен компонентом ProximitySensor. О нем не так много можно написать, ведь единственное его отличие от родительского компонента — это наличие свойства near у соответствующего ему ProximityReading QML-типа. Это свойство принимает значение true, если какой-либо объект расположен достаточно близко к датчику.
Определив требуемый компонент, можно добавить обработчик сигнала об изменении значения свойства near, а в нем уже выполнять требуемые действия. К примеру, при приближении объекта к датчику возможно сворачивать приложения или делать что-нибудь действительно полезное — это уже зависит от фантазии разработчика:
ProximitySensor {
id: proximitySensor
active: true
}
Connections {
target: proximitySensor.reading
onNearChanged: proximitySensor.reading.near ? appWindow.deactivate() : appWindow.activate()
}
Датчик освещенности
Датчик освещенности — один из самых старых датчиков в мире мобильных устройств. Его задача заключается в измерении уровня освещенности обстановки, в которой используется смартфон. Эти измерения используются программным обеспечением смартфона для регулировки яркости экрана: при слабом освещении яркость уменьшается, тем самым снижая нагрузку на глаза пользователя и сохраняя заряд батареи, а в ясный день наоборот выкручивается на максимум для того, чтобы текст на экране можно было хоть как-то различить.
QML-тип LightSensor предоставляет API для получения уровня освещённости в люксах, что может быть использовано для регулировки параметров приложения, в том числе яркости экрана. И хотя Sailfish OS уже располагает встроенным ПО для автоматической регулировки уровня подсветки экрана, это не мешает разработчикам подстраивать ее под свое приложения.
Компонент для получения показаний датчика все также можно получить из свойства reading. В этот раз оно содержит объект LightReading, обладающий свойством illuminance — именно в нем содержится значения освещенности. Используя сенсор освещенности в совокупности с плагином nemo-qml-plugin-systemsettings можно добиться автоматически регулируемого уровня яркости, подходящего именно под ваше приложение. Просто свяжите показания LightSensor со свойством brightness компонента DisplaySettings, установите ограничения на принимаемые значения, и уровень яркости будет адаптироваться в желаемых вами пределах. Также предварительно стоит присвоить свойству autoBrightnessEnabled значение false для отключения автоматической регулировки уровня яркости операционной системой.
import org.nemomobile.systemsettings 1.0
Page {
LightSensor {
id: lightSensor
active: true
}
DisplaySettings {
id: display
autoBrightnessEnabled: false
brightness: if (lightSensor.reading.illuminance < 50 ) {
50
} else if (lightSensor.reading.illuminance > 100) {
100
} else {
lightSensor.reading.illuminance
}
}
}
Кроме этого, LightSensor предоставляет информация об угле обзора датчика. Получить ее можно обратившись к свойству fieldOfView. Доступен этот функционал правда не на всех устройствах, и если fieldOfView содержит 0, то, увы, используемый сенсор такую функцию не поддерживает.
В Sailfish OS доступен и другой компонент для измерения уровня освещенности — AmbientLightSensor. Его функционал почти полностью аналогичен предыдущему датчику, но вместо значения освещенности в люксах он возвращает заранее обозначенные константы из следующего списка:
- AmbientLightReading.Undefined — уровень освещенности не удается распознать,
- AmbientLightReading.Dark — темно,
- AmbientLightReading.Twilight — умеренно темно,
- AmbientLightReading.Light — в освещенном помещении,
- AmbientLightReading.Bright — в тени ясным днем,
- AmbientLightReading.Sunny — под прямыми солнечными лучами.
Таким образом, этот компонент целесообразно использовать, когда точные значения уровня освещенности являются лишними, и приблизительного будет достаточно. К примеру, можно создать читалку, которая переходит в “ночной” режим при слабом освещении.
Первым делом как обычно определяется компонент датчика:
AmbientLightSensor {
id: ambientLightSensor
active: true
}
Создается фон и текст с изначальными параметрами цвета:
Rectangle {
id: background
anchors.fill: parent
color: "white"
Label {
id: text
text: "Съешь ещё этих мягких французских булок, да выпей чаю"
anchors.fill: parent
wrapMode: Text.WordWrap
color: "black"
}
}
Ну и в конце задается набор состояний и переходов, которые будут применяться к объектам на экране. Условием для смены состояний как раз и будет низкое значение датчика освещения:
states: State {
name: "dark"
PropertyChanges {
target: text
color: "white"
}
PropertyChanges {
target: background
color: "black"
}
when: ambientLightSensor.reading.lightLevel === AmbientLightReading.Dark
}
transitions: Transition {
to: "dark"
reversible: true
ColorAnimation {
duration: 200
}
}
При слабом освещении экран слева плавно перейдет в «ночную» версию справа:
Заключение
Данная статья является лишь первой частью, во второй части мы продолжаем рассматривать основные датчики, доступные на операционной системе Sailfish OS.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Автор: Максим Костерин
Комментарии (12)
amarao
30.11.2017 13:42Простите, что с наивными вопросами, но:
1) Sailfish — это наследник maemo или meego? Пакеты dpkg ставятся или нет?
2) X-сервер или что-то другое?
3) Ядро апстримовое, своё или андроидовое?broken
30.11.2017 15:251. Наследник meego, а meego — наследник от двух родителей — maemo и moblin. Пакеты не dpkg (debian), а rpm.
3. Андроидовое.amarao
30.11.2017 16:141. Увы, жаль. Maemo было куда круче. С позиции гика, разумеется, хотя и потребительские свойства были офигенны.
2. Аналогично, жаль. Хочет свободного апстримового.chuvilin
30.11.2017 16:34По п.1.
Мобильной MeeGo, в общем-то, не было.
Был Harmattan, брендированный как MeeGo, но, по сути, являющийся Maemo 6.
Sailfish OS — наследница именно Harmattan, если говорить о UX и функционалу.
Делать какие-то выводы о функционале, основываясь на используемой системе пакетов, по-моему, не корректно.
chuvilin
В этом коде
стоит заменить "
anchors.fill: parent
" на