GoogleFitのデータをC#で取得してみた！

# 概要
最近、弊社では**「健康経営」**を意識して過ごそう！という話がありました。
自分の健康を考えて行動しようということなんですが、今回は「運動」の観点から、
自分の歩いた歩数を管理します！

たまたま「GoogleFit」というアプリを入れていたのですが、歩数だけでなく運動の強度から
設定基準までの達成度を教えてくれます！
しかもデータはGoogleアカウントごとにクラウドに保存されており、別デバイスからも確認できるようです！

これを使ってなんかできそう！と思った僕はとりあえずC#のコンソールアプリから自分のGoogleFitのデータを
とってみようと思いました！

# 仕様
GoogleFitにOAuth認証で接続し、自分のGoogleアカウントのGoogleFit歩数データを取得する。

# 開発環境
VisualStudio 2017

# 必要なもの
Googleアカウント
GoogleFitアプリ（iOS端末, Android端末）

# 認証方法について
認証方法は3つあるみたいです！
下記のエントリを参考にさせて頂きました！
[Google APIの認証の種類](https://qiita.com/masaha03/items/fab8c8411a020ff2bd42)

今回はOAuth認証でAPIに接続しようと思います。

# APIの有効化

まずはGoogleAPIのコンソールでAPIを有効化します。
下記サイトにアクセスし、自分のGoogleアカウントでログインしてください。
https://console.developers.google.com/

左のサイドメニューから「ライブラリ」を選択します。
![2019-08-15_14h21_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147118/366d0308-a24f-b0e4-0294-0dfcef086336.png)

検索窓で「fitness」と入力するとでてきます。
![2019-08-15_14h22_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147118/844a30f3-8203-0eb3-3ec6-5aa4d5afd2cb.png)

「Fitness API」を選択し、APIを有効化してください。

続いて、認証情報を作成します。
左のサイドメニューから「認証情報」を選択します。

「認証情報を作成」＞「OAuthクライアントID」を選択します。
![2019-08-15_14h26_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147118/2feda4b6-e5c4-8a58-85a3-df75079119f6.png)

アプリケーションの種類は、「その他」を選択してください。
![2019-08-15_14h28_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147118/25371bcd-3890-9e68-6741-d3d43448576b.png)

これで認証情報が作成されました！
ダウンロードのマークからjsonをダウンロードして保管してください。（後々使います）
![2019-08-15_14h35_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147118/7a621336-e308-bf5f-7ceb-ec8d7c8d3acb.png)

# アプリ作成
やっていきまっしょう！！
API操作部分のコードは下記のサイトを参考にしています！
[Getting your weight from Google Fit with C#](https://keestalkstech.com/2016/07/getting-your-weight-from-google-fit-with-c/)

## NuGetパッケージのインストール
パッケージマネージャーで「Google.Apis.Fitness.v1」を検索し、インストールします。

## 認証情報jsonの配置
先ほどダウンロードした認証情報のjsonをプロジェクトのルートに配置します。

## FitnessQueryクラス
まず、GoogleFitへの問い合わせを行うベースクラスを作成します。

```c#:FitnessQuery.cs
using Google.Apis.Fitness.v1;
using Google.Apis.Fitness.v1.Data;
using System;

namespace GoogleFitClient.Import
{
  class FitnessQuery
  {
    private FitnessService _service;
    private string _dataSourceId;
    private string _dataType;

    public FitnessQuery(FitnessService service, string dataSourceId, string dataType)
    {
      _service = service;
      _dataSourceId = dataSourceId;
      _dataType = dataType;
    }
    /// <summary>
    /// リクエストを作成します。
    /// </summary>
    protected virtual AggregateRequest CreateRequest(DateTime start, DateTime end, TimeSpan? bucketDuration = null)
    {
      var bucketTimeSpan = bucketDuration.GetValueOrDefault(TimeSpan.FromDays(1));
      return new AggregateRequest
      {
        AggregateBy = new AggregateBy[] {
          new AggregateBy
          {
            DataSourceId = _dataSourceId,
            DataTypeName = _dataType
          }
        },
        BucketByTime = new BucketByTime
        {
          DurationMillis = (long)bucketTimeSpan.TotalMilliseconds
        },
        StartTimeMillis = GoogleTime.FromDateTime(start).TotalMilliseconds,
        EndTimeMillis = GoogleTime.FromDateTime(end).TotalMilliseconds
      };
    }
    /// <summary>
    /// リクエストを実行します。
    /// </summary>
    protected virtual AggregateResponse ExecuteRequest(AggregateRequest request, string userId = "me")
    {
      var agg = _service.Users.Dataset.Aggregate(request, userId);
      return agg.Execute();
    }
  }
}
```
## GoogleTimeクラス
次に、GoogleAPIでは時間をミリ秒で扱いますので、DateTimeとの変換を行うヘルパークラスを作成します。

```c#:GoogleTime.cs
using System;

namespace GoogleFitClient.Import
{
  class GoogleTime
  {
    private static readonly DateTime zero = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    /// <summary>
    /// ミリ秒のトータル
    /// </summary>
    public long TotalMilliseconds { get; private set; }

    private GoogleTime(){ }

    /// <summary>
    /// DateTimeを与える。
    /// </summary>
    public static GoogleTime FromDateTime(DateTime dt)
    {
      if (dt < zero)
      {
        throw new Exception("Invalid Google datetime.");
      }
      return new GoogleTime
      {
        TotalMilliseconds = (long)(dt - zero).TotalMilliseconds,
      };
    }

    /// <summary>
    /// ミリ秒を与える。
    /// </summary>
    public static GoogleTime FromNanoseconds(long? nanoseconds)
    {
      if (nanoseconds < 0)
      {
        throw new ArgumentOutOfRangeException(nameof(nanoseconds), "Must be greater than 0.");
      }
      return new GoogleTime
      {
        TotalMilliseconds = (long)(nanoseconds.GetValueOrDefault(0) / 1000000)
      };
    }
    /// <summary>
    /// 現在の時間を取得する。
    /// </summary>
    public static GoogleTime Now
    {
      get { return FromDateTime(DateTime.Now); }
    }

    /// <summary>
    /// 時間を追加する。
    /// </summary>
    public GoogleTime Add(TimeSpan timeSpan)
    {
      return new GoogleTime
      {
        TotalMilliseconds = this.TotalMilliseconds + (long)timeSpan.TotalMilliseconds
      };
    }

    /// <summary>
    /// インスタンスをDateTimeに変換する。
    /// </summary>
    public DateTime ToDateTime()
    {
      return zero.AddMilliseconds(this.TotalMilliseconds);
    }
  }
}
```

## StepDataPointクラス
APIから返ってきたデータを受け取るデータクラスを用意します。

```c#:StepDataPoint.cs
using System;

namespace GoogleFit.Import
{
  class StepDataPoint
  {
    /// <summary>
    /// 歩数データ
    /// </summary>
    public int? Step { get; set; }
        
    /// <summary>
    /// タイムスタンプ
    /// </summary>
    public DateTime Stamp { get; set; }
  }
}
```

## StepQueryクラス
そして、歩数データのクエリを発行するクラスを作成します。

```c#:StepQuery.cs
using Google.Apis.Fitness.v1;
using System;
using System.Collections.Generic;
using System.Linq;

namespace GoogleFitClient.Import
{
  /// <summary>
  /// GoogleFit歩数クエリクラス
  /// </summary>
  class StepQuery : FitnessQuery
  {
    private const string DataSource = "derived:com.google.step_count.delta:com.google.android.gms:merge_step_deltas";
    private const string DataType = "com.google.step_count.delta";

    public StepQuery(FitnessService service) :
      base(service, DataSource, DataType)
    {
  }

  /// <summary>
  /// 歩数データのクエリ
  /// </summary>
  public IList<StepDataPoint> QueryStep(DateTime start, DateTime end)
  {
    var request = CreateRequest(start, end);
    var response = ExecuteRequest(request);

    return response
      .Bucket
      .SelectMany(b => b.Dataset)
      .Where(d => d.Point != null)
      .SelectMany(d => d.Point)
      .Where(p => p.Value != null)
      .SelectMany(p =>
      {
        return p.Value.Select(v =>
          new StepDataPoint
          {
            Step = v.IntVal.GetValueOrDefault(),
            Stamp = GoogleTime.FromNanoseconds(p.StartTimeNanos).ToDateTime()
          });
        })
        .ToList();
      }
  }
}
```

## エントリポイント
では、最後にエントリポイントとなるクラスを作成します！

```c#:Program.cs
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ALK.Import.google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Fitness.v1;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace ALK.Import
{
  public class Program
  {
    static void Main(string[] args)
    {
      new Program().Run().Wait();
    }

    public  async Task Run()
    {
      var scopes = new[] {
        FitnessService.Scope.FitnessActivityRead
      };

      ICredential credential = await GetUserCredential(scopes);
      GetSteps(credential);
    }

    /// <summary>
    /// OAuth認証を用いてCredentialを取得する。
    /// </summary>
    private Task<UserCredential> GetUserCredential(string[] scopes)
    {
      // ファイル名は先ほど取得した認証情報のjson
      using (var stream = new FileStream(@"C:\\secret.json", FileMode.Open, FileAccess.Read))
      {
        string credPath = "token.json";
        return GoogleWebAuthorizationBroker.AuthorizeAsync(
          GoogleClientSecrets.Load(stream).Secrets,
          scopes,
          "user", CancellationToken.None, new FileDataStore(credPath, true));
      }
    }

    /// <summary>
    /// 歩数を取得する。
    /// </summary>
    private void GetSteps(ICredential credential)
    {
      var service = new FitnessService(new BaseClientService.Initializer()
      {
        HttpClientInitializer = credential,
        ApplicationName = "Get Fitness Step"
      });

      var step = new StepQuery(service);

      // 1日前～現在までのデータを取得
      var results = step.QueryStep(DateTime.Now.AddDays(-1), DateTime.Now);
      foreach (var result in results)
      {
        Console.WriteLine(result.Step);
      }
      Console.ReadKey();
    }
  }
}
```

コーディングはこれだけです！
実行してみると、GoogleのOAuth認証窓が出てきて、ログインし、コンソールに歩数がプリントされたら成功です！

# まとめ
GoogleFitのデータをC#コンソールアプリで取得することができました！

参考にできる日本語ドキュメントが少なかったので苦戦しました･･･
取りたいデータごとにデータソースとデータタイプ、スコープなどを設定する必要があるので、それは公式のREST APIのドキュメントを参考にしました～

WebアプリからAPIに接続するとなるとドメイン許可設定やリダイレクト先の設定等必要になるみたいで、時間があればやって追記します！

#参考文献
[Getting your weight from Google Fit with C#](https://keestalkstech.com/2016/07/getting-your-weight-from-google-fit-with-c/)
[Fitness Client Library for .NET](https://developers.google.com/api-client-library/dotnet/apis/fitness/v1)
[Fitness Data Types](https://developers.google.com/fit/android/data-types)