今回は Xamarin.Forms で心拍数センサー値を取得してみます
ソースコードの一式は下記にあります!
細かい実装などはこちらを参照ください
※ 順次改修していく予定なので、この記事の内容が現時点のソースより古い可能性があります
ほぼこれまでの実装の仕方と同じなのでこなれてきました
心拍数データのいれものは iOS と Android でほぼ変わりません
/// <summary> /// iOS 用心拍数データ /// </summary> public class NativeBandHeartRateReading : IBandHeartRateReading { /// <summary> /// コンストラクタ /// </summary> /// <param name="data">センサーデータ</param> public NativeBandHeartRateReading(Native.Sensors.BandSensorHeartRateData data) { this.Timestamp = DateTime.Now; this.HeartRate = (int)data.HeartRate; if (data.Quality == Native.Sensors.HeartRateQuality.Locked) { this.Quality = HeartRateQuality.Locked; } if (data.Quality == Native.Sensors.HeartRateQuality.Acquiring) { this.Quality = HeartRateQuality.Acquiring; } } /// <summary> /// 検出日時 /// </summary> public DateTimeOffset Timestamp { get; private set; } /// <summary> /// 心拍数 /// </summary> public int HeartRate { get; private set; } /// <summary> /// 計測状況 /// </summary> public HeartRateQuality Quality { get; private set; } }
iOS が心拍数が nuint という特殊な型で来るので、int にキャストしています
次にセンサー制御側
/// <summary> /// iOS 用心拍数センサー /// </summary> public class NativeBandHeartRateSensor : NativeBandSensorBase<IBandHeartRateReading> { /// <summary> /// 心拍数センサー /// </summary> private Native.Sensors.HeartRateSensor sensor = null; /// <summary> /// センサー値変更イベント /// </summary> public override event EventHandler<BandSensorReadingEventArgs<IBandHeartRateReading>> ReadingChanged; /// <summary> /// コンストラクタ /// </summary> /// <param name="manager">Band センサー管理クラス</param> public NativeBandHeartRateSensor(Native.Sensors.IBandSensorManager manager) : base(manager) { this.sensor = Native.Sensors.BandSensorManagerExtensions.CreateHeartRateSensor(manager); this.sensor.ReadingChanged += this.OnReadingChanged; } /// <summary> /// センサー値変更イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> private void OnReadingChanged(object sender, Native.Sensors.BandSensorDataEventArgs<Native.Sensors.BandSensorHeartRateData> e) { if (this.ReadingChanged == null) { return; } this.ReadingChanged.Invoke( this, new BandSensorReadingEventArgs<IBandHeartRateReading>(new NativeBandHeartRateReading(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()); } }
iOS のセンサー制御クラスはこんな感じで
/// <summary> /// Android 用心拍数センサー /// </summary> public class NativeBandHeartRateSensor : NativeBandSensorBase<IBandHeartRateReading> { /// <summary> /// 心拍数センサー /// </summary> private Native.Sensors.HeartRateSensor sensor = null; /// <summary> /// センサー値変更イベント /// </summary> public override event EventHandler<BandSensorReadingEventArgs<IBandHeartRateReading>> ReadingChanged; /// <summary> /// コンストラクタ /// </summary> /// <param name="manager">Band センサー管理クラス</param> public NativeBandHeartRateSensor(Native.Sensors.IBandSensorManager manager) : base(manager) { this.sensor = Native.Sensors.BandSensorManagerExtensions.CreateHeartRateSensor(manager); this.sensor.ReadingChanged += this.OnReadingChanged; } /// <summary> /// センサー値変更イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> private void OnReadingChanged(object sender, Native.Sensors.IBandSensorEventEventArgs<Native.Sensors.IBandHeartRateEvent> e) { if (this.ReadingChanged == null) { return; } this.ReadingChanged.Invoke( this, new BandSensorReadingEventArgs<IBandHeartRateReading>(new NativeBandHeartRateReading(e.SensorReading))); } /// <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(); } }
Android はこうです・・・心拍数はサンプリングレートを設定しないようですね
あとは ViewModel を改修して
/// <summary> /// センサー情報 ViewModel /// </summary> public class SensorReadingViewModel : BindableBase { ~ 中略 ~ #region HeartRateSensor /// <summary> /// 心拍数 /// </summary> private int heartRate = 0; /// <summary> /// 心拍数 /// </summary> public int HeartRate { get { return this.heartRate; } set { this.SetProperty<int>(ref this.heartRate, value); } } /// <summary> /// 心拍計測状況 /// </summary> private HeartRateQuality heartRateQuality = HeartRateQuality.Acquiring; /// <summary> /// 心拍計測状況 /// </summary> public HeartRateQuality HeartRateQuality { get { return this.heartRateQuality; } set { this.SetProperty<HeartRateQuality>(ref this.heartRateQuality, value); } } #endregion //HeartRateSensor ~ 中略 ~ /// <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; } } 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; } } } ~ 中略 ~ /// <summary> /// 心拍数変更イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> private void OnHeartRateReadingChanged(object sender, BandSensorReadingEventArgs<IBandHeartRateReading> e) { if (e == null) { return; } this.HeartRate = e.SensorReading.HeartRate; this.HeartRateQuality = e.SensorReading.Quality; } }
画面の表示領域を用意
<?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="*"/> </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> <StackLayout Orientation="Horizontal" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" Spacing="5"> <Label Text="Heart Rate:" FontSize="Medium"/> <StackLayout Orientation="Vertical" Padding="10,0,0,0" Spacing="10"> <Label Text="{Binding HeartRate, StringFormat='rate={0}'}" FontSize="Small"/> <Label Text="{Binding HeartRateQuality, StringFormat='status={0}'}" FontSize="Small"/> </StackLayout> </StackLayout> </Grid> </ScrollView> </Grid> </ContentPage>
いつものようにおためし実行
status のところも直接列挙型をバインドできてかんたんにできました