Содержание
Показания датчиков устройства Android представлены различными типами данных — векторами типа Vector3, простыми числами типа double и так далее. В этой части мы допишем наше приложение по работе с датчиками и научимся считывать их показания.
Расположение осей координат относительно устройства
Датчики, возвращающие данные по трем осям, например, акселерометр или гироскоп используют систему координат показанную на рисунке ниже:
то есть, ось X направлена вправо, ось Y — вверх, а ось Z — вперед (на нас). Координаты по оси Z за экраном устройства имеют отрицательное значение.
Событие ReadingChanged
Все рассматриваемые нами классы датчиков предоставляют событие ReadingChanged, которое генерируется каждый раз при чтении данных. При этом, каждый датчик устройства возвращает свои показания в аргументах события, а именно в свойстве Reading аргументов события. Ниже в таблице показаны типы данных, возвращаемых в свойстве Reading каждого типа датчика:
| Интерфейс датчика | Тип данных датчика | Описание |
IAccelerometer |
AccelerometerData |
Ускорение по осям X, Y, Z. |
IBarometer |
BarometerData |
Текущее давление в гектопаскалях |
ICompass |
CompassData |
Магнитный северный заголовок устройства. Магнитный северный заголовок устройства — это направление, в котором указывает устройство, ориентированное на магнитный север. |
IGyroscope |
GyroscopeData |
Угловая скорость по осям X,Y,Z. Единицы измерения — радианы в секунду. |
IMagnetometer |
MagnetometerData |
Вектор магнитного поля. Единицы измерения по осям — микротесла |
IOrientationSensor |
OrientationSensorData |
Показания IOrientationSensor возвращаются в виде Quaternion — описания ориентации устройства в двух трехмерных системах координат:
Первая система координат — система координат устройства (см. рисунок выше) Вторая система координат — трехмерная система координат относительно Земли имеет следующие оси:
|
Показания датчиков устройства
Подписка на событие ReadingChanged
Чтобы получить показания датчика, нам необходимо подписаться на событие ReadingChanged. Так как для каждого датчика используются свои аргументы, а сам процесс подписки/отписки будет идентичным, то продемонстрируем этот процесс на примере одного вида датчика, например, акселерометра. Оставшиеся типы датчиков и получение их данных будет показано в конце этой части.
Итак, откроем код модели представления SensorsViewModel и добавим в неё обработчик события ReadingChanged для акселерометра:
public AccelerometerData AccelerometerData { get; private set; }
public class SensorsViewModel : INotifyPropertyChanged
{
...
private void Accelerometer_ReadingChanged(object? sender, AccelerometerChangedEventArgs e)
{
if (AccelerometerData != e.Reading)
{
AccelerometerData = e.Reading;
OnPropertyChanged("AccelerometerData");
}
}
...
}
здесь AccelerometerData — свойство в котором мы храним данные, полученные от датчика. Теперь добавим два метода первый из которых будет подписываться на событие конкретного датчика, а второй, наоборот — отписываться от события:
private void SubscribeToEvent(object sensor)
{
switch (sensor)
{
case IAccelerometer:
{
(sensor as IAccelerometer).ReadingChanged += Accelerometer_ReadingChanged;
break;
}
//тут добавим другие типы датчиков
}
}
private void UnsubscribeFromEvent(object sensor)
{
switch (sensor)
{
case IAccelerometer:
{
(sensor as IAccelerometer).ReadingChanged -= Accelerometer_ReadingChanged;
break;
}
//тут добавим другие типы датчиков
}
}
Так как все типы датчиков у нас хранятся в виде object, то мы проверяем тип объекта датчика и выбираем какой обработчик выбрать для подписки/отписки. Теперь применим эти методы в команде StartCommand следующим образом:
public SensorsViewModel()
{
StartCommand = new Command<SensorInfo>((sensorInfo) =>
{
if (sensorInfo.CanStart)
{
if (sensorInfo.IsStarted == false)
{
SubscribeToEvent(sensorInfo.Sensor);
var method = sensorInfo.Sensor.GetType().GetMethod(name: "Start", genericParameterCount: 0, [typeof(SensorSpeed)]);
var result = method?.Invoke(sensorInfo.Sensor, [SensorSpeed.Default]);
}
else
{
UnsubscribeFromEvent(sensorInfo.Sensor);
var method = sensorInfo.Sensor.GetType().GetMethod(name: "Stop");
var result = method?.Invoke(sensorInfo.Sensor, null);
}
}
sensorInfo.IsStarted = (bool)sensorInfo.Sensor.GetType().GetProperty("IsMonitoring").GetValue(sensorInfo.Sensor);
});
Если датчик есть на устройстве (CanStart == true) и он не запущен (sensorInfo.IsStarted == false), то мы подписываемся на событие и запускаем датчик, иначе — отписываемся от события и останавливаем работу датчика.
Остается только применить новый код в приложении. Изменим код XAML страницы MainPage следующим образом:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiSensors.MainPage"
xmlns:local="clr-namespace:MauiSensors.ViewModels"
xmlns:converters="clr-namespace:MauiSensors.Converters"
xmlns:models="clr-namespace:MauiSensors.Models"
x:DataType="local:SensorsViewModel">
....
<ScrollView>
<VerticalStackLayout
Padding="30,0"
Spacing="25">
....
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1.5*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="Акселерометр" Grid.Column="0" Grid.Row="0"/>
<Label Text="{Binding AccelerometerData}" Grid.Column="1" Grid.Row="0" TextColor="Blue"/>
</Grid>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Все объекты, содержащие данные датчика имеют переопределенный метод ToString() поэтому мы просто привязываем значение Text меток Label к этим объектам. Точно таким же образом мы можем организовать получение данных с других датчиков устройства. Вот как будет выглядеть приложение после запуска акселерометра:
Код модели представления для приложения «Показания датчиков устройства»
Весь код модели представления представлен ниже:
using MauiSensors.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MauiSensors.ViewModels
{
public class SensorsViewModel : INotifyPropertyChanged
{
private List<object> _sensors =[Accelerometer.Default, Compass.Default, Magnetometer.Default, Barometer.Default, Gyroscope.Default, OrientationSensor.Default];
public ObservableCollection<SensorInfo> Sensors { get; set; } =
[
new SensorInfo( Accelerometer.Default, false),
new SensorInfo( Compass.Default, false),
new SensorInfo( Magnetometer.Default, false),
new SensorInfo( Barometer.Default, false),
new SensorInfo( Gyroscope.Default, false),
new SensorInfo( OrientationSensor.Default, false)
];
public ICommand CheckCommand { get; set; }
public ICommand StartCommand { get; set; }
public AccelerometerData AccelerometerData { get; private set; }
public BarometerData BarometerData { get; private set; }
public CompassData CompassData { get; private set; }
public MagnetometerData MagnetometerData { get; private set; }
public GyroscopeData GyroscopeData { get; private set; }
public OrientationSensorData OrientationSensorData { get; private set; }
public SensorsViewModel()
{
StartCommand = new Command<SensorInfo>((sensorInfo) =>
{
if (sensorInfo.CanStart)
{
if (sensorInfo.IsStarted == false)
{
SubscribeToEvent(sensorInfo.Sensor);
var method = sensorInfo.Sensor.GetType().GetMethod(name: "Start", genericParameterCount: 0, [typeof(SensorSpeed)]);
var result = method?.Invoke(sensorInfo.Sensor, [SensorSpeed.Default]);
}
else
{
UnsubscribeFromEvent(sensorInfo.Sensor);
var method = sensorInfo.Sensor.GetType().GetMethod(name: "Stop");
var result = method?.Invoke(sensorInfo.Sensor, null);
}
}
sensorInfo.IsStarted = (bool)sensorInfo.Sensor.GetType().GetProperty("IsMonitoring").GetValue(sensorInfo.Sensor);
});
CheckCommand = new Command(() =>
{
Sensors.Clear();
foreach (var sensor in _sensors)
{
Sensors.Add(new SensorInfo(sensor, (bool)(sensor.GetType().GetProperty("IsSupported").GetValue(sensor))));
}
});
}
private void Accelerometer_ReadingChanged(object? sender, AccelerometerChangedEventArgs e)
{
if (AccelerometerData != e.Reading)
{
AccelerometerData = e.Reading;
OnPropertyChanged("AccelerometerData");
}
}
private void Barometer_ReadingChanged(object? sender, BarometerChangedEventArgs e)
{
if (BarometerData != e.Reading)
{
BarometerData = e.Reading;
OnPropertyChanged("BarometerData");
}
}
private void Compass_ReadingChanged(object? sender, CompassChangedEventArgs e)
{
if (CompassData != e.Reading)
{
CompassData = e.Reading;
OnPropertyChanged("CompassData");
}
}
private void Magnetometer_ReadingChanged(object? sender, MagnetometerChangedEventArgs e)
{
if (MagnetometerData != e.Reading)
{
MagnetometerData = e.Reading;
OnPropertyChanged("MagnetometerData");
}
}
private void Gyroscope_ReadingChanged(object? sender, GyroscopeChangedEventArgs e)
{
if (GyroscopeData != e.Reading)
{
GyroscopeData = e.Reading;
OnPropertyChanged("GyroscopeData");
}
}
private void Orientation_ReadingChanged(object? sender, OrientationSensorChangedEventArgs e)
{
if (OrientationSensorData != e.Reading)
{
OrientationSensorData = e.Reading;
OnPropertyChanged("OrientationSensorData");
}
}
private void SubscribeToEvent(object sensor)
{
switch (sensor)
{
case IAccelerometer:
{
(sensor as IAccelerometer).ReadingChanged += Accelerometer_ReadingChanged;
break;
}
case IBarometer:
{
(sensor as IBarometer).ReadingChanged += Barometer_ReadingChanged;
break;
}
case ICompass:
{
(sensor as ICompass).ReadingChanged += Compass_ReadingChanged;
break;
}
case IMagnetometer:
{
(sensor as IMagnetometer).ReadingChanged += Magnetometer_ReadingChanged;
break;
}
case IGyroscope:
{
(sensor as IGyroscope).ReadingChanged += Gyroscope_ReadingChanged;
break;
}
case IOrientationSensor:
{
(sensor as IOrientationSensor).ReadingChanged += Orientation_ReadingChanged;
break;
}
}
}
private void UnsubscribeFromEvent(object sensor)
{
switch (sensor)
{
case IAccelerometer:
{
(sensor as IAccelerometer).ReadingChanged -= Accelerometer_ReadingChanged;
break;
}
case IBarometer:
{
(sensor as IBarometer).ReadingChanged -= Barometer_ReadingChanged;
break;
}
case ICompass:
{
(sensor as ICompass).ReadingChanged -= Compass_ReadingChanged;
break;
}
case IMagnetometer:
{
(sensor as IMagnetometer).ReadingChanged -= Magnetometer_ReadingChanged;
break;
}
case IGyroscope:
{
(sensor as IGyroscope).ReadingChanged -= Gyroscope_ReadingChanged;
break;
}
case IOrientationSensor:
{
(sensor as IOrientationSensor).ReadingChanged -= Orientation_ReadingChanged;
break;
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
}
Итого
Чтобы получать данные с датчиков устройства, необходимо подписаться на событие ReadingChanged. Каждый тип датчика возвращает свои данные, поэтому нам необходимо знать точный типа датчика, чтобы подписаться на его событие и получать данные.
