しっぽを追いかけて

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

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

Xamarin.Forms で Micorosft Band の着用状態を取得する

センサー系の実装は最後、Xamarin.Forms で着用状態を取得してみます

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

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

github.com

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

いつものようにまずはデータのいれものを作ります

/// <summary>
/// iOS 用着用状態センサー
/// </summary>
public class NativeBandContactReading : IBandContactReading
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="data">センサーデータ</param>
    public NativeBandContactReading(Native.Sensors.BandSensorContactData data)
    {
        this.Timestamp = DateTime.Now;
        this.State = BandContactState.NotWorn;
        if (data.WornState == Native.Sensors.BandContactStatus.Worn)
        {
            this.State = BandContactState.Worn;
        }
    }

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

    /// <summary>
    /// 着用状態
    /// </summary>
    public BandContactState State { get; private set; }
}

iOSAndroid でほぼ同じです

次にセンサー制御クラス

/// <summary>
/// iOS 用着用状態
/// </summary>
public class NativeBandContactSensor : NativeBandSensorBase<IBandContactReading>, IBandContactSensor
{
    /// <summary>
    /// 着用状態センサー
    /// </summary>
    private Native.Sensors.ContactSensor sensor = null;

    /// <summary>
    /// 現在の着用状態
    /// </summary>
    private IBandContactReading contactReading = null;

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

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="manager">Band センサー管理クラス</param>
    public NativeBandContactSensor(Native.Sensors.IBandSensorManager manager)
        : base(manager)
    {
        this.sensor = Native.Sensors.BandSensorManagerExtensions.CreateContactSensor(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.BandSensorContactData> e)
    {
        if (this.ReadingChanged == null)
        {
            return;
        }
        var args = new BandSensorReadingEventArgs<IBandContactReading>(new NativeBandContactReading(e.SensorReading));
        this.contactReading = args.SensorReading;
        this.ReadingChanged.Invoke(
            this, args);
    }

    /// <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());
    }

    /// <summary>
    /// 現在の状態を取得する
    /// </summary>
    /// <returns>現在の状態</returns>
    public Task<IBandContactReading> GetCurrentStateAsync()
    {
        return Task.FromResult<IBandContactReading>(this.contactReading);
    }
}

こっちが iOS ・・・今までと同じと思いきや、IBandContactSensor というインタフェースを追加で実装していて

    /// <summary>
    /// 現在の状態を取得する
    /// </summary>
    /// <returns>現在の状態</returns>
    public Task<IBandContactReading> GetCurrentStateAsync()
    {
        return Task.FromResult<IBandContactReading>(this.contactReading);
    }

変更通知イベント以外に現在の状態の取得メソッドが増えています

なんで着用状態だけこのインタフェースがあるのかは謎

/// <summary>
/// Android 用着用状態センサー
/// </summary>
public class NativeBandContactSensor : NativeBandSensorBase<IBandContactReading>, IBandContactSensor
{
    /// <summary>
    /// 着用状態センサー
    /// </summary>
    private Native.Sensors.ContactSensor sensor = null;

    /// <summary>
    /// 現在の着用状態
    /// </summary>
    private IBandContactReading contactReading = null;

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

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

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

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

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

    /// <summary>
    /// 現在の状態を取得する
    /// </summary>
    /// <returns>現在の状態</returns>
    public Task<IBandContactReading> GetCurrentStateAsync()
    {
        return Task.FromResult<IBandContactReading>(this.contactReading);
    }
}

こっちが Android

さらに ViewModel も改修

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

    #region ContactSensor

    /// <summary>
    /// 着用状態
    /// </summary>
    private BandContactState contactState = 0;

    /// <summary>
    /// 着用状態
    /// </summary>
    public BandContactState ContactState
    {
        get { return this.contactState; }
        set { this.SetProperty<BandContactState>(ref this.contactState, value); }
    }

    #endregion //ContactSensor

    ~ 中略 ~

    /// <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;
            }
            // 心拍数の検知開始
            if (this.client.SensorManager.HeartRate.IsSupported)
            {
                await this.client.SensorManager.HeartRate.StartReadingsAsync();
                this.client.SensorManager.HeartRate.ReadingChanged += this.OnHeartRateReadingChanged;
            }
            // 歩数の検知開始
            if (this.client.SensorManager.Pedometer.IsSupported)
            {
                await this.client.SensorManager.Pedometer.StartReadingsAsync();
                this.client.SensorManager.Pedometer.ReadingChanged += this.OnPedometerReadingChanged;
            }
            // 移動距離の検知開始
            if (this.client.SensorManager.Distance.IsSupported)
            {
                await this.client.SensorManager.Distance.StartReadingsAsync();
                this.client.SensorManager.Distance.ReadingChanged += this.OnDistanceReadingChanged;
            }
            // 肌温度の検知開始
            if (this.client.SensorManager.SkinTemperature.IsSupported)
            {
                await this.client.SensorManager.SkinTemperature.StartReadingsAsync();
                this.client.SensorManager.SkinTemperature.ReadingChanged += this.OnSkinTemperatureReadingChanged;
            }
            // 紫外線レベルの検知開始
            if (this.client.SensorManager.Ultraviolet.IsSupported)
            {
                await this.client.SensorManager.Ultraviolet.StartReadingsAsync();
                this.client.SensorManager.Ultraviolet.ReadingChanged += this.OnUltravioletReadingChanged;
            }
            // 着用状態の検知開始
            if (this.client.SensorManager.Contact.IsSupported)
            {
                await this.client.SensorManager.Contact.StartReadingsAsync();
                this.client.SensorManager.Contact.ReadingChanged += this.OnContactReadingChanged;
            }
        }
        else
        {
            // 加速度センサーの検知終了
            if (this.client.SensorManager.Accelerometer.IsSupported)
            {
                await this.client.SensorManager.Accelerometer.StopReadingsAsync();
                this.client.SensorManager.Accelerometer.ReadingChanged -= this.OnAccelerometerReadingChanged;
                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;
            }
            // 心拍数の検知終了
            if (this.client.SensorManager.HeartRate.IsSupported)
            {
                await this.client.SensorManager.HeartRate.StopReadingsAsync();
                this.client.SensorManager.HeartRate.ReadingChanged -= this.OnHeartRateReadingChanged;
                this.HeartRate = 0;
                this.HeartRateQuality = HeartRateQuality.Acquiring;
            }
            // 歩数の検知終了
            if (this.client.SensorManager.Pedometer.IsSupported)
            {
                await this.client.SensorManager.Pedometer.StopReadingsAsync();
                this.client.SensorManager.Pedometer.ReadingChanged -= this.OnPedometerReadingChanged;
                this.TotalSteps = 0L;
            }
            // 移動距離の検知終了
            if (this.client.SensorManager.Distance.IsSupported)
            {
                await this.client.SensorManager.Distance.StopReadingsAsync();
                this.client.SensorManager.Distance.ReadingChanged -= this.OnDistanceReadingChanged;
                this.CurrentMotion = MotionType.Unknown;
                this.Pace = 0d;
                this.Speed = 0d;
                this.TotalDistance = 0L;
            }
            // 肌温度の検知開始
            if (this.client.SensorManager.SkinTemperature.IsSupported)
            {
                await this.client.SensorManager.SkinTemperature.StopReadingsAsync();
                this.client.SensorManager.SkinTemperature.ReadingChanged -= this.OnSkinTemperatureReadingChanged;
                this.SkinTemperature = 0d;
            }
            // 紫外線レベルの検知終了
            if (this.client.SensorManager.Ultraviolet.IsSupported)
            {
                await this.client.SensorManager.Ultraviolet.StopReadingsAsync();
                this.client.SensorManager.Ultraviolet.ReadingChanged -= this.OnUltravioletReadingChanged;
                this.ExposureLevel = UltravioletExposureLevel.None;
            }
            // 着用状態の検知終了
            if (this.client.SensorManager.Contact.IsSupported)
            {
                await this.client.SensorManager.Contact.StopReadingsAsync();
                this.client.SensorManager.Contact.ReadingChanged -= this.OnContactReadingChanged;
                this.ContactState = BandContactState.NotWorn;
            }
        }
    }

    ~ 中略 ~

    /// <summary>
    /// 着用状態変更イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnContactReadingChanged(object sender, BandSensorReadingEventArgs<IBandContactReading> e)
    {
        if (e == null)
        {
            return;
        }
        this.ContactState = e.SensorReading.State;
    }
}

最後に 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="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="100"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

    ~ 中略 ~

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

          <Label Text="Contact State:"
                 FontSize="Medium"/>
          <StackLayout Orientation="Vertical"
                       Padding="10,0,0,0"
                       Spacing="10">
            <Label Text="{Binding ContactState, StringFormat='state={0}'}"
                   FontSize="Small"/>
          </StackLayout>

        </StackLayout>

      </Grid>
    </ScrollView>
  </Grid>

</ContentPage>

表示領域を追加してお試し実行

f:id:matatabi_ux:20150426084714p:plain

ひと通りセンサー値が取得できるようになりました!