Node.js で Google Fit に体重データを登録する

先日 Node.js で Google Fit から歩数や体重データを取得するのをやりました。

blog.kondoumh.com

体重データはスマホアプリから登録できるけど Fit を使う前の過去データは手入力やってられてられないので、API による登録方法を調べました。

公式ドキュメントには記述が見つけられなくて API のヘッダーコメントのサンプルを頼りに試行錯誤が必要でした。

https://github.com/googleapis/google-api-nodejs-client/blob/master/src/apis/fitness/v1.ts

Python 実装ですがこのリポジトリが参考になりました。

github.com

GCP のプロジェクトで Fitness API を有効にしたり OAuth 2.0 クライアント ID を作成したり googleapis の NPM パッケージを使用したりするのは前回と同様です。

www.npmjs.com

まず体重データを登録するための DataSource を登録する必要があります。これには、 fitness.users.dataSources.create メソッドを使用します。dataType の定義が重要で、name 属性に com.google.weight を指定し、field 属性の配列にに体重データを格納するための name 属性 weightformat 属性 floatPoint を指定します。 devicemodelmanifacturer などはダミーデータで OK です。*1

async function createDataSource() {
  const res = await fitness.users.dataSources.create({
    userId: "me",
    requestBody: {
      "application": {
        name: "patch_weight",
        detailsUrl: 'https://example.com',
        version: "1"
      },
      "dataType": {
        name: "com.google.weight",
        field: [
          {
            name: "weight",
            format: "floatPoint"
          }
        ]
      },
      "dataStreamName": "patch_weight",
      "type": "raw",
      "device": {
        manufacturer: "mh",
        model: "hoge",
        type: "scale",
        uid: "pw-01",
        version: "1.0"
      }
    }
  });
  console.log(res.data);
}

この関数を、認証関数に続けて実行します。

authenticate(scopes)
  .then(client => createDataSource())
  .catch(console.error);

次のようなレスポンスが得られます。PROJECT_NO には Fitness API を有効化している GCP のプロジェクト番号が入ります。

{
  dataStreamId: 'raw:com.google.weight:PROJECT_NO:mh:hoge:pw-01:patch_weight',
  dataStreamName: 'patch_weight',
  type: 'raw',
  dataType: { name: 'com.google.weight', field: [ [Object] ] },
  device: {
    uid: 'pw-01',
    type: 'scale',
    version: '1.0',
    model: 'hoge',
    manufacturer: 'mh'
  },
  application: {
    version: '1',
    detailsUrl: 'https://example.com',
    name: 'patch_weight'
  },
  dataQualityStandard: []
}

この dataStreamId を指定して、体重データを登録していくことになります。

fitness.users.dataSources.datasets.patch メソッドを使用して体重データを登録する関数を定義します。

async function patch(dataSourceId, datasetId, start, end, time, val) {
  const res = await fitness.users.dataSources.datasets.patch({
    datasetId: datasetId,
    dataSourceId: dataSourceId,
    userId: "me",
    requestBody: {
      "dataSourceId": dataSourceId,
      "minStartTimeNs": start,
      "maxEndTimeNs": end,
      "point": [
        {
          dataTypeName: "com.google.weight",
          startTimeNanos: time,
          endTimeNanos: time,
          value: [
            {
              fpVal: val
            }
          ]
        }
      ]
    },
  });
  console.log(res.data);
}

時刻についてはなぜかナノ秒単位での指定が必要なので変換関数を用意し、特定日付で dataset を刻んで1件登録。測定時刻は午前7時としました。

function getNano(day) {
  const dt = new Date(day);
  return dt.getTime() * 1000000;
}

const dataSourceId = "raw:com.google.weight:PROJECT_NO:mh:hoge:pw-01:patch_weight";
const start = getNano("2020-12-31T00:00:00");
const end = getNano("2021-12-31T23:59:59");
const time = getNano("2020-12-31T07:00:00");
const datasetId = `${start}-${end}`;
const val = 69.3;

authenticate(scopes)
  .then(client => patch(dataSourceId, datasetId, start, end, time, val))
  .catch(console.error);

実行結果。エラーにはなりませんでした。

{
  minStartTimeNs: '1609340400000000000',
  maxEndTimeNs: '1640962799000000000',
  dataSourceId: 'raw:com.google.weight:PROJECT_NO:mh:hoge:pw-01:patch_weight',
  point: [
    {
      startTimeNanos: '1609365600000000000',
      endTimeNanos: '1609365600000000000',
      dataTypeName: 'com.google.weight',
      originDataSourceId: '',
      value: [Array]
    }
  ]
}

ちゃんとデータが入ったか確認するため、前回の歩数と体重データ取得関数で日付を指定して取得してみます。

const from = new Date("2020-12-31T00:00:00");
const to = new Date("2020-12-31T23:59:59");

authenticate(scopes)
  .then(client => aggregate(client, from, to))
  .catch(console.error);

実行結果。歩数は Pixel から、体重は API で登録した DataSource から取得され、他のデバイスと同等に集計されているようです。

raw:com.google.step_count.cumulative:Google:Pixel 3 XL:caa195e4620531ba:Step Counter
2020/12/31 15:05:19
2020/12/31 16:39:24
com.google.step_count.delta
3648

raw:com.google.weight:PROJECT_NO:mh:hoge:pw-01:patch_weight
2020/12/31 7:00:00
2020/12/31 7:00:00
com.google.weight.summary
69.3

ちなみに、Fit アプリで登録したデータはこのようにアプリでユーザの手入力とわかるようになっています。*2

raw:com.google.weight:com.google.android.apps.fitness:user_input
2021/7/23 8:58:26
2021/7/23 8:58:26
com.google.weight.summary
67.80000305175781

Pixel の Fit アプリでも反映まで少し時間がかかりましたが、API で登録したデータが確認できました。

f:id:kondoumh:20210726104907p:plain:w400

ということで、過去データも Fit に登録できるようになりました。

*1:電話や時計を持っているとそのデバイス専用の DataSource が登録されますが、更新はそのデバイスにしか許可されておらず、REST API で更新しようとすると認証エラーになります。

*2:Google Fit と連携可能な体重計の場合はここにデバイス名が入るはず。