やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

C# と Google Fitness API を使って体重の取得と登録を行う

今年、健康診断で再検査して、LDL コレステロール値と尿酸値が上がってしまう悲惨な結果を受けました😭仕方ないので、数値の改善を目的としてフィットネスバイクとスマートバンドを購入し、日々の体重記録を行うことにしました。せっかくなので Google Fit を使って継続的にデータを記録したいな~と思い、過去に記録した体重データも登録する手段を探しました。

ここでは、Google Fitness API + C# を使用して Google Fit に体重データを記録するまでの流れをまとめています。

概要

Google Fit を使って過去に別の手段で記録した体重データを登録します。私の場合、体重データを Google Spread Sheet で記録していたので、それを CSV ファイルで出力して Google Fit に記録したいと考えました。

Google Fit を選んだ理由は「使用しているスマホAndroid である」ことと「購入したスマートバンドの連携先に Google Fit があった」ことです。スマホは Pixel6 Pro を使用して、スマートバンドは Xiaomi の Smart Band 8 です。スマートバンドは体重データを記録するものではないのですが、毎日体重を記録するならサービスは統一したほうがいいかなと思い Google Fit を使うことにしました。

やりたいこと

前述した通り、過去に記録した CSV 形式の体重データがあるので、それを Google Fit に登録します。体重データは次のように日付体重の2つの情報が記録されています。

日付 体重
2023/08/16 67.75
2023/08/17 68.3
2023/08/19 67.75

先に結果だけお見せしておきます。このように Google Fit を確認すると、過去の体重データが記録されるようになります。

開発環境

このエントリでは次の開発環境、及び言語、ライブラリを使用するものとします。

Google Fit に体重データを登録するための前提知識

まず、Google Fitl に登録するためには、次の情報について理解しておく必要があります。ざっくり説明すると、Google Fit アプリに体重データを記録するには、Google Cloud API に含まれる Google Fitness API を使用する必要があります。今回は、C# / .NET でこの Google Fitness API を使用します。

Google Fit について。これは Android もしくは iPhone のアプリとして提供されていて、PC から操作・閲覧することはできません。
www.android.com

Google Cloud API について。Google が提供しているサービスの各種 Web API です。
cloud.google.com

Google Fitness API について。Google Cloud API にある、Google Fit のデータにアクセスするための Web API です。今回はこちらを使用して体重データの取得と登録を行います。
developers.google.com

Google Fitness API を使うには?

Google Cloud API の中にある Google Fitness API を使うためには、Google Cloud API クライアントライブラリが必要です。Google Cloud API は、複数言語向けに提供されています。
developers.google.com

.NET 向けにも NuGet Gallery | Google.Apis.Fitness.v1 1.68.0.3232 が提供されています。こちらは記事作成の2024年4月時点で .NET8 に対応しています。

Google Cloud API の認証・認可について

Google Cloud API を使用するためには認証・認可について知っておく必要があります。基本的には以下のページを読んでおけばよいです。 cloud.google.com cloud.google.com

今回は Google Cloud API の OAuth2.0 を使用します。というか、Google Fitness API は個人情報を扱うため、OAuth2.0 しか使えません。.NET で使用する場合は以下のページを読んでおけば問題ありません。
developers.google.com

Google Fitness API を使うための事前準備

Google Finess API を使うためにはいくつかの事前準備が必要です。ここでは、Google Fitness API を使用するために必要な手順について説明します。

  1. プロジェクトを作成する
  2. API を有効化する
  3. OAuth 同意画面を設定する
  4. 認証情報を追加する

プロジェクトを作成する

ここで言うプロジェクトとは、Google Cloud API の権限を管理したり API の使用者や共同管理者の追加と削除などを行う単位を表します。Google Cloud API では、プロジェクト単位で特定の API を有効化できます。

プロジェクトの作成手順は次のページを参照してください。
cloud.google.com

API を有効化する

Google Fitness API を有効化するには Google Cloud コンソールからFitness APIを探して[有効にする]をクリックします。 有効化できたら API とサービスの詳細画面が表示されます。
console.cloud.google.com

OAuth 同意画面を設定する

同意画面というのは Google Cloud API を使用する際、ユーザーに情報を参照することを通知して同意するかどうかを選択する画面のことです。今回のケースでは、同意するのは自分自身なので、他人が使用することは考慮しないものとします。[認証情報]メニューの[+ 認証情報を作成]から、[OAuth クライアント ID]を選択します。同意画面の設定が必要になるので、[同意画面を設定]を選択します。

以降はアプリの情報を登録することになります。登録する情報は次の通りです。前述した通り、同意画面を表示するのは自分だけなので最低限の情報のみ設定します。設定できたら[保存して次へ]を選択します。

  • User Type: 外部
  • アプリ名: 任意の名称(同意画面に表示されます)
  • ユーザー サポートメール: 自分のアカウント

Google Cloud API では、使用する API によっては権限が必要です。Google Fitness API は個人情報へアクセスするため、「制限付きのスコープ」に該当する権限が必要です。以下の[スコープを追加または削除]を選択します。

フィルタでFitness APIと入力して、以下のスコープを選択します。選択できれば[更新]を選択します。

  • https://www.googleapis.com/auth/fitness.body.read
  • https://www.googleapis.com/auth/fitness.body.write

developers.google.com

スコープが設定できたら[保存して次へ]を選択します。

テストユーザーは、データの取得と登録さえできればよいので自分のアカウントを設定します。設定できれば[保存して次へ]を選択します。これで同意画面の設定は完了です。

認証情報を追加する

OAuth2.0 の認証を追加します。[認証情報]メニューの[+ 認証情報を作成]から、[OAuth クライアント ID]を選択します。今回はコンソールアプリを使うため、デスクトップ アプリを選択します。名前は管理画面でしか出てこないので、任意の値を設定してください。[作成]を選択すると OAuth クライアントが作成されます。

以下のようなダイアログが出てくるので[JSON をダウンロード]で認証情報をダウンロードします。このファイルは後で実装する際に使用します。

これで実装に必要な Google Cloud API の準備が整いました。

Google Fitness API を使って体重データを取得する

体重データを登録する前に、まずは Google Fitness API を使ってどういったデータ構造になっているのかを確認します。

実装

先に実装した結果だけ記載します。

これを実行すると次のような結果が得られます。事前に体重データを登録しています。

======== Start ========
2024-04-20 - 61.80kg
2024-04-22 - 62.20kg
2024-04-23 - 61.70kg
2024-04-24 - 61.60kg
2024-04-25 - 61.00kg
2024-04-26 - 61.40kg
2024-04-27 - 61.50kg
2024-04-28 - 62.10kg
======== Finish ========

解説

実装内容について解説します。まずは OAuth 認証部分です。この実装サンプルは以下ページにも記載されています。
developers.google.com

Google Cloud API .NET 向けライブラリでは、GoogleWebAuthorizationBroker.AuthorizeAsyncで認証を行います。第1引数には事前準備しておいた JSON ファイルを使用します。第2引数には、使用する API のスコープを設定します。第3引数はuser固定です。この認証情報をFitnessServiceに渡すことでクライアントが利用可能になります。

var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                              GoogleClientSecrets.FromFile("GoogleAPI.Authorize.json").Secrets
                            , [ FitnessService.Scope.FitnessBodyRead ]
                            , "user"
                            , CancellationToken.None);
var fitnessクライアント = new FitnessService(new BaseClientService.Initializer
{
    HttpClientInitializer = credential,
});

このクライアントを生成すると、ブラウザが起動して同意画面が表示されます。個人情報へのアクセスに同意が求められるのでチェックを付けて同意してください。同意しないと Google Fit に登録したデータを取得する権限が付与されません。

次に体重データを取得するための条件を指定します。

var 体重データリスト = fitnessクライアント.Users
                            .Dataset
                            .Aggregate(body: new AggregateRequest
                                             {
                                                 StartTimeMillis = DateTime.Today.AddDays(-10).ToGoogleTime()
                                               , EndTimeMillis   = DateTime.Today.ToGoogleTime()
                                               , AggregateBy = new List<AggregateBy>
                                                               {
                                                                   new AggregateBy
                                                                   {
                                                                       DataTypeName = "com.google.step_count.delta"
                                                                     , DataSourceId = "derived:com.google.weight:com.google.android.gms:merge_weight"
                                                                   }
                                                               }
                                             }
                                   , userId: "me")
                            .Execute()
                            .Bucket
                            .SelectMany(x => x.Dataset[0].Point)
                            .ToArray();

使用しているのは以下の API です。条件の指定に必要なプロパティの解説も記載されているので、一度確認しておいてください。
developers.google.com

上記のコードは、特定のデータソースで登録した体重データを集約しています。データソースは、データを登録したデバイスやアプリケーションのことで、Google Cloud API ではDataSourceIdによって一意に識別します。上記のコードではderived:com.google.weight:com.google.android.gms:merge_weight(これは Androidスマホから入力)のデータソースから体重データを取得しています。

データタイプDataTypeNameは、Google Fit で扱うデータの型名です。アクティビティや身体データなど、ある瞬間や一定期間内に集約したデータであることを示します。com.google.step_count.deltaは、1日単位の体重データの合計を集計することができます。

developers.google.com

集計期間には次のプロパティを使用します。いずれもミリ秒単位なので、拡張メソッド.ToGoogleTime()を使ってDateTime型からlong型へ変換しています。

  • StartTimeMillis: 収集開始日時
  • EndTimeMillis: 収集終了日時

あとは集約したデータを順番に出力しているだけです。Google Cloud API のレスポンスの日時はミリ秒、もしくはナノ秒long型です。これらもDateTime型に変換できるように.FromGoogleTime()という拡張メソッドを作って呼び出しています。

foreach (var 体重データ in 体重データリスト)
{
    Console.WriteLine($"{体重データ.StartTimeNanos.Value.FromGoogleTime().Date:yyyy-MM-dd} - {体重データ.Value[0].FpVal:F2}kg");
}

レスポンスのスキーマは以下のページを参照してください。 developers.google.com

developers.google.com

構造が複雑でどこになんのデータがあるのか分かりづらいですが、体重データについて言えば基本的には次のプロパティを理解していればいいです。

  • Bucket(IList<AggregateBucket>)
    • Dataset(IList<Dataset>)
      • Point(IList<DataPoint>)
        • StartTimeNanos(long?): 体重を記録開始した日時
        • Value(IList<Value>)
          • FpVal(double): これが体重の値

Google Fitness API を使って体重データを登録する

体重データを取得できたので、次は登録する方法について見ていきます。

実装

これを実行した結果はやりたいことに記載した通りです。CSV から読み込んだ体重データが無事に記録されています。

解説

OAuth 認証部分については省略します。CSV を読み込む部分も CsvHelper を使っているだけなので省略します。

最初にfitnessクライアント.Users.DataSources.Createの部分の説明からします。これはUsers.dataSources: createAPI を実行してデータソースを新規作成している部分です。

DataSource newDataset = fitnessクライアント.Users.DataSources.Create(body: new DataSource
    {
        Application = new Application
        {
              Name = "Upload from local"
        }
        , DataType = new DataType
        {
            Name = "com.google.weight"
          , Field = new List<DataTypeField>
                                 {
                                     new DataTypeField
                                     {
                                           Name = "weight"
                                         , Format = "floatPoint"
                                     }
                                 }
        }
        , Device = new Device
        {
              Manufacturer = "Original"
            , Model = "My PC"
            , Type = "unknown"
            , Uid = "<一意の値>"
            , Version = "1.0"
        }
        , Type = "raw"
    }
    , userId: "me")
.Execute();

developers.google.com

Google Fitness API では、体重などの身体情報を登録する際に特定のデータソースから登録したことを示す必要があります。これから登録しようとする環境=デバイスは存在しないため、新規に作成する必要があります。既存のデータソースを使用することはできません。今回の要件では登録は1回きりなので、適当な情報でデータソースの情報を埋めました。特にdevicemanufacturer, model, uid, versionプロパティは適当な値にしました。なお、このデータソースの作成は1回実行すれば良いので、2回目以降は実行されないようにしてください。

あとは、CSV から読み込んだ日時と体重データから登録したい Dataset を作ります。ちょっと面倒なのが日時がナノ秒であることです。体重データを取得する際はAggregateRequestでミリ秒だったのにここではナノ秒です。引っかかりやすそうなので注意してください。

Dataset[] 体重のDatasetリスト = 登録する体重情報.Select(x => new Dataset
{
    DataSourceId = newDataset.DataStreamId
  , MinStartTimeNs = x.補正した時間GoogleAPI向け
  , MaxEndTimeNs = x.補正した時間GoogleAPI向け
  , Point = [
                 new DataPoint
                 {
                      DataTypeName = "com.google.weight"
                    , StartTimeNanos = x.補正した時間GoogleAPI向け
                    , EndTimeNanos = x.補正した時間GoogleAPI向け
                    , Value = [new Value { FpVal = (double?)x.体重 }]     // これが実際に登録する体重
                 }
            ]
}).ToArray();

作成した Dataset を登録すれば完了です。

    fitnessクライアント.Users
                    .DataSources
                    .Datasets
                    .Patch(body: 体重のDataset
                         , userId: "me"
                         , dataSourceId: newDataset.DataStreamId
                         , datasetId: $"{体重のDataset.MinStartTimeNs}-{体重のDataset.MaxEndTimeNs}")
                    .Execute();

developers.google.com


Google Fitness API は実際に触ってみるとデータ構造と仕組みが複雑ですが、体重を取得・登録することはできます。 Google のことなので、Fit 自体ももしかしたらサービス終了する可能性もありますが、そうなった場合でもまとめて取得してしまえば移行も簡単ですね。