しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms で Micorosft Band のジャイロセンサー値を取得する

今回は Xamarin.Forms で回転の速度が取れるジャイロセンサー値を取得してみます

ソースコードの一式は下記にあります!

細かい実装などはこちらを参照ください

github.com

※ 順次改修していく予定なので、この記事の内容が現時点のソースより古い可能性があります

まずはデータのいれもの

/// <summary>
///  iOS 用角速度データ
/// </summary>
class NativeBandGyroscopeReading : IBandGyroscopeReading
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="data">センサーデータ</param>
    public NativeBandGyroscopeReading(Native.Sensors.BandSensorGyroscopeData data)
    {
        this.Timestamp = DateTime.Now;
        this.AngularVelocityX = data.X;
        this.AngularVelocityY = data.Y;
        this.AngularVelocityZ = data.Z;
    }

    /// <summary>
    /// 検出日時
    /// </summary>
    public DateTimeOffset Timestamp { get; private set; }

    /// <summary>
    /// X 軸角速度
    /// </summary>
    public double AngularVelocityX { get; private set; }

    /// <summary>
    /// Y 軸角速度
    /// </summary>
    public double AngularVelocityY { get; private set; }

    /// <summary>
    /// Z 軸角速度
    /// </summary>
    public double AngularVelocityZ { get; private set; }

    /// <summary>
    /// X 軸角加速度
    /// </summary>
    [Obsolete("Acceleration by Gyroscope is not supported for iOS.")]
    public double AccelerationX { get; private set; }

    /// <summary>
    /// Y 軸角加速度
    /// </summary>
    [Obsolete("Acceleration by Gyroscope is not supported for iOS.")]
    public double AccelerationY { get; private set; }

    /// <summary>
    /// Z 軸角加速度
    /// </summary>
    [Obsolete("Acceleration by Gyroscope is not supported for iOS.")]
    public double AccelerationZ { get; private set; }
}

Android は大丈夫なのに iOS の場合は角加速度が取得できないようです・・・仕方がないので Obsolete 属性で非推奨にしました

次にセンサークラ

/// <summary>
/// iOS 用ジャイロセンサー
/// </summary>
public class NativeBandGyroscope : NativeBandSensorBase<IBandGyroscopeReading>
{
    /// <summary>
    /// ジャイロセンサー
    /// </summary>
    private Native.Sensors.GyroscopeSensor sensor = null;

    /// <summary>
    /// センサー値変更イベント
    /// </summary>
    public override event EventHandler<BandSensorReadingEventArgs<IBandGyroscopeReading>> ReadingChanged;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="manager">Band センサー管理クラス</param>
    public NativeBandGyroscope(Native.Sensors.IBandSensorManager manager) : base(manager)
    {
        this.sensor = Native.Sensors.BandSensorManagerExtensions.CreateGyroscopeSensor(manager);
        this.sensor.ReadingChanged += this.OnReadingChanged;
    }

    /// <summary>
    /// センサー値変更イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    protected void OnReadingChanged(object sender, Native.Sensors.BandSensorDataEventArgs<Native.Sensors.BandSensorGyroscopeData> e)
    {
        if (this.ReadingChanged == null)
        {
            return;
        }
        this.ReadingChanged.Invoke(
            this, new BandSensorReadingEventArgs<IBandGyroscopeReading>(new NativeBandGyroscopeReading(e.SensorReading)));
    }

    /// <summary>
    /// センサー検知を開始する
    /// </summary>
    /// <returns>Task</returns>
    public override Task StartReadingsAsync()
    {
        return Task.Run(() => this.sensor.StartReadings());
    }

    /// <summary>
    /// センサー検知を停止する
    /// </summary>
    /// <returns>Task</returns>
    public override Task StopReadingsAsync()
    {
        return Task.Run(() => this.sensor.StopReadings());
    }
}

NativeBandGyroscope は最低限の記述で済ませることができました

あとは画面側も対応していきます

/// <summary>
/// センサー情報 ViewModel
/// </summary>
public class SensorReadingViewModel : BindableBase
{
    ~ 中略 ~

    /// <summary>
    /// センサー監視切替
    /// </summary>
    /// <param name="detecting">センサー監視フラグ</param>
    /// <returns>Task</returns>
    private async Task ChangeDetectSensors(bool detecting)
    {
        if (detecting)
        {
            // 加速度センサーの検知開始
            if (this.client.SensorManager.Accelerometer.IsSupported)
            {
                await this.client.SensorManager.Accelerometer.StartReadingsAsync();
                this.client.SensorManager.Accelerometer.ReadingChanged += this.OnAccelerometerReadingChanged;
            }
            // ジャイロセンサーの検知開始
            if (this.client.SensorManager.Gyroscope.IsSupported)
            {
                await this.client.SensorManager.Gyroscope.StartReadingsAsync();
                this.client.SensorManager.Gyroscope.ReadingChanged += this.OnGyroscopeReadingChanged;
            }
        }
        else
        {
            // 加速度センサーの検知終了
            if (this.client.SensorManager.Accelerometer.IsSupported)
            {
                await this.client.SensorManager.Accelerometer.StopReadingsAsync();
                this.AccelerationX = 0d;
                this.AccelerationY = 0d;
                this.AccelerationZ = 0d;
            }
            // ジャイロセンサーの検知終了
            if (this.client.SensorManager.Gyroscope.IsSupported)
            {
                await this.client.SensorManager.Gyroscope.StopReadingsAsync();
                this.client.SensorManager.Gyroscope.ReadingChanged -= this.OnGyroscopeReadingChanged;
                this.AngularVelocityX = 0d;
                this.AngularVelocityY = 0d;
                this.AngularVelocityZ = 0d;
                this.GyroAccelerationX = 0d;
                this.GyroAccelerationY = 0d;
                this.GyroAccelerationZ = 0d;
            }
        }
    }

    ~ 中略 ~

    /// <summary>
    /// 角速度変更イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnGyroscopeReadingChanged(object sender, BandSensorReadingEventArgs<IBandGyroscopeReading> e)
    {
        if (e == null)
        {
            return;
        }
        this.AngularVelocityX = e.SensorReading.AngularVelocityX;
        this.AngularVelocityY = e.SensorReading.AngularVelocityY;
        this.AngularVelocityZ = e.SensorReading.AngularVelocityZ;
        this.GyroAccelerationX = e.SensorReading.AccelerationX;
        this.GyroAccelerationY = e.SensorReading.AccelerationY;
        this.GyroAccelerationZ = e.SensorReading.AccelerationZ;
    }
}

ViewModel ではセンサー監視切替の処理を入れて、イベントハンドラ追加するだけで OK

最後に XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:cv="clr-namespace:XamarinBandSample.Converters;assembly=XamarinBandSample"
             xmlns:t="clr-namespace:XamarinBandSample.Triggers;assembly=XamarinBandSample"
             xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=XamarinBandSample"
             prismmvvm:ViewModelLocator.AutoWireViewModel="true"
             x:Class="XamarinBandSample.Views.TopPage">

    ~ 中略 ~

    <!-- Sensor Info Pain-->
    <ScrollView IsVisible="{Binding ShowSensors}"
                Orientation="Vertical">
      <Grid Padding="10"
            RowSpacing="10"
            ColumnSpacing="10"
            BindingContext="{Binding SensorReading}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="100"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Label Text="Detecting"
               Grid.Column="0"
               Grid.Row="0"
               VerticalOptions="Center"
               FontSize="Medium"/>
        <Switch IsToggled="{Binding IsSensorDetecting}"
                Grid.Column="1"
                Grid.Row="0">
          <Switch.Triggers>
            <EventTrigger Event="Toggled">
              <t:InvokeCommandAction Command="{Binding ChangeDetectSensorsCommand}" 
                                     CommandParameter="{Binding IsSensorDetecting}"/>
            </EventTrigger>
          </Switch.Triggers>
        </Switch>

        <StackLayout Orientation="Horizontal"
                     Grid.Column="0"
                     Grid.ColumnSpan="2"
                     Grid.Row="1"
                     Spacing="5">

          <Label Text="Accelometer:"
                 FontSize="Medium"/>
          <StackLayout Orientation="Vertical"
                       Padding="10,0,0,0"
                       Spacing="10">
            <Label Text="{Binding AccelerationX, StringFormat='x={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding AccelerationY, StringFormat='y={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding AccelerationZ, StringFormat='z={0}'}"
                   FontSize="Small"/>
          </StackLayout>

        </StackLayout>

        <StackLayout Orientation="Horizontal"
                     Grid.Column="0"
                     Grid.ColumnSpan="2"
                     Grid.Row="2"
                     Spacing="5">

          <Label Text="Gyroscope:"
                 FontSize="Medium"/>
          <StackLayout Orientation="Vertical"
                       Padding="10,0,0,0"
                       Spacing="10">
            <Label Text="{Binding AngularVelocityX, StringFormat='x={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding AngularVelocityY, StringFormat='y={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding AngularVelocityZ, StringFormat='z={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding GyroAccelerationX, StringFormat='dx={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding GyroAccelerationY, StringFormat='dy={0}'}"
                   FontSize="Small"/>
            <Label Text="{Binding GyroAccelerationZ, StringFormat='dz={0}'}"
                   FontSize="Small"/>
          </StackLayout>

        </StackLayout>

      </Grid>
    </ScrollView>

    ~ 中略 ~

</ContentPage>

ジャイロセンサーの値を表示領域を追加し、表示領域の縦幅が長くなってきたので、画面スクロールできるように ContentView から ScrollView に置き換えました

いつものおためし

f:id:matatabi_ux:20150424212007p:plain

やはり iOS だと角加速度は取れませんが角速度は取れるみたいですね