Разработка под Android в .NET MAUI. Показания датчиков устройства

Показания датчиков устройства 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 — описания ориентации устройства в двух трехмерных системах координат:

Первая система координат — система координат устройства (см. рисунок выше)

Вторая система координат — трехмерная система координат относительно Земли имеет следующие оси:

  • Положительная ось X касается поверхности Земли и указывает на восток.
  • Положительная ось Y также касается поверхности Земли и указывает на север.
  • Положительная ось Z является перпендикулярной к поверхности Земли и указывает вверх.

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. Каждый тип датчика возвращает свои данные, поэтому нам необходимо знать точный типа датчика, чтобы подписаться на его событие и получать данные.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии