Open5

2024.11 Fitbit アプリの開発日誌 (進捗報告用)

ピン留めされたアイテム
Taichi AsakuraTaichi Asakura

固定用コメント

プログラムへのリンク

https://github.com/tatsuyasusukida/heartrate-switch/
https://github.com/t-asa-nagaoka/heartrate-switch/

ロードマップ

※毎週金曜日は研究室ゼミがあり,あまり作業できないため進捗更新はなし(作業のみ)としています.

  • 2024.11.18 (月) 〜 22 (金) Fitbit 開発に慣れる,heartrate-switchの分析
    • app部の調査
    • companion部の調査
  • 2024.11.25 (月) 〜 29 (金) heartrate-switch に機能追加
    • リラックス傾向算出時の心拍サンプル数を一定 (保持時間の秒数と同一) にする
    • トリガー発動時の擬似通知 (バイブレーション・画面が消灯していたら点灯)
    • 主観トリガー機能の実装
  • 2024.12.2 (月) 〜 6 (金) Fitbit アプリ・Webシステム間の連携機能の開発開始
    • Webシステム側に新たに設ける外部デバイス用のAPI (仮称 : External API) の仕様策定

疑問点・気になること

  • Fitbit Web APIで取得する心拍サンプルは5〜15秒間隔だが,アプリ内で直接心拍センサーを読み取るのであればもっと細かく(1秒単位)で取得できるのではないか.→2024.11.20 (水) 解決
Taichi AsakuraTaichi Asakura

2024.11.18 (月)

できたこと

  • Fitbit アプリの開発環境を整える (Win&Mac)
  • Fitbit SDKのサンプルアプリを実機(versa 3)で動かす
  • heartrate-switch のソースコードちょっと読む

Fitbit アプリの開発環境を整える

https://dev.fitbit.com/getting-started/
ここを参考に開発環境を整えました.

つまづいたのが,最初に Fitbit App Gallaly で自分のFitbitアカウントを「開発者」に昇格させないといけないというところでした.Fitbit CLIでOS Simulatorが認識されずアプリのインストールができなかったので,公式フォーラムで調べたらこれが原因だということがわかりました.

一応公式のドキュメントにも書いてあったのですが,見落としていたみたいです.

In order to gain access to the developer functionality, you must first login to Gallery Admin, and accept the Fitbit Platform Terms of Service.

Fitbit SDKのサンプルアプリを実機で動かす

ビルドしたアプリケーションを実際のFitbit デバイスで動かすには,当該端末がWi-Fiに接続されていないといけないようです.今日は出先(大学内)での作業でしたが,スマートフォンのテザリング機能を使うことでインストールすることができました.

ソースコード分析 : 設定周り (Companion API/Settings API)

https://dev.fitbit.com/build/reference/settings-api/

Settings APIで jsx 形式で設定ページを実装する場合,各要素の settingsKey 属性にキーを入れることで,設定の読み書きの実装を省くことができるということがわかりました.

ただし,注意点としては settingsKey を使うと settingsStorage.getItem(key) の返り値がJsonになってしまうため,そこから値を取り出さないといけないことです.(自力で実装した場合,直接値を読み書きできるようにすることができます.)

こんな感じのJsonが返ってくる
{
    name: "ここにテキストボックスの値が入る",
}

Fitbitの公式ドキュメントではこの辺りの説明が不十分なので,薄田さんのソースコードを見なかったら気づけなかったと思います.

Taichi AsakuraTaichi Asakura

2024.11.19 (火)

できたこと

  • companion部の分析

今日はそれ以外は heartrate (Webシステム) の方の作業をしていました.

ソースコード分析 : appとcompanionの共通点

ソースコードの構成

appもcompanionも単一のソースファイルで構成されており,大まかな流れは共通している.

上から

  1. 定数
  2. ソースコード全体で使用する変数,オブジェクト
  3. 起動時に実行する処理 setup()
  • イベントハンドラへの登録 registerHandlers()
  • 個別の処理の関数
  1. イベント発生時に実行する処理
  • イベントハンドラ別の関数 on〇〇()
  • 個別の処理の関数

appとcompanionでやり取りするメッセージのフォーマット

const data = {
  type,
  // ここにデータが入る (`type` の値がそのままキーとなる)
};

type の種類は以下の通り

  • request (app => companionのみ)
  • setting (companion => appのみ)

送信されるHTTPリクエストのフォーマット

app/index.js
/** 送信される HTTP リクエストボディの内容です。 */
const request = {
  /** 送信日時です。 */
  date: new Date().toISOString(),
  /** 現在のリラックス傾向です。 */
  relax: state.currentRelax,
  /** 低リラックス状態のしきい値です。 */
  threshold: state.settings.thresholdLow,
  /** HTTP リクエストの再送信が行われたかを示すフラグです。 */
  retry: false,
};

以下のような流れで送信されます.
app => (ソケット通信) => companion => (HTTP通信) => 指定したサーバー

ソケット通信,HTTP通信それぞれにキューが設置されており,前者は一度送信して失敗した場合に追加,後者は未送信のまま直接追加されます. (このためサーバーが受け取る時には retry は必ず true になります)

ソースコード分析 : companion/index.js

1. 定数

  • RESEND_INTERVAL : HTTPリクエストを送信する間隔 (ms)

2. ソースコード全体で使用する変数,オブジェクト

  • state : アプリケーションの状態を格納するオブジェクト
    • requests : 送信予定のHTTPリクエストを格納するキュー (配列)

3. 起動時に実行する処理 setup()

起動時に実行するのは「イベントハンドラへの登録」のみ

イベントハンドラへの登録

コメントを追記する形式で書きます.

companion/index.js
function registerHandlers() {
  // settingsStorageに変更が生じたとき
  settingsStorage.addEventListener("change", () => {
    sendSettings();
  });

  // settingsStorageの変更によってcompanionが起動したとき
  if (companion.launchReasons.settingsChanged) {
    sendSettings();
  }

  // app (Fitbit側) とのソケット通信が開始したとき
  messaging.peerSocket.addEventListener("open", () => {
    sendSettings();
  });

  // app からメッセージを受け取ったとき
  messaging.peerSocket.addEventListener("message", onMessage);

  // 一定間隔で繰り返す処理の登録(を少し遅らせて実行)
  setTimeout(() => {
    // 一定間隔でキューに格納されたHTTPリクエストを確認・送信
    setInterval(onTimeout, RESEND_INTERVAL);
  }, RESEND_INTERVAL / 2);
}

4. イベント発生時に実行する処理

sessionStorageが変更されたとき
appとのソケット通信が開始されたとき

companion/index.js
function sendSettings() {
  const type = "settings";
  const settings = loadSettings();
  const data = { type, settings };

  if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
    messaging.peerSocket.send(data);
  }
}

ソケット通信が OPEN の状態 (appがメッセージを待ち受けている状態) であれば,設定をオブジェクトとして app に送信する.

appからメッセージを受け取ったとき

companion/index.js
async function onMessage(event) {
  if (!event || !event.data) {
    return;
  }

  const { type } = event.data;

  if (type === "request") {
    const url = loadSendUrl();

    if (url) {
      const { request } = event.data;
      // const sent = sendRequest(url, request);

      // if (!sent) {
        request.retry = true;
        state.requests.push(request);
      // }
    }
  } else {
    console.warn(`Unknown event.data.type: ${type}`);
  }
}

app からメッセージ(オブジェクト)を受け取ると,それに基づいたHTTPリクエストを指定したURLに送信する(キューに追加する).

HTTPリクエストの送信キュー (一定間隔で実行)

companion/index.js
function onTimeout() {
  const url = loadSendUrl();

  if (!url) {
    return;
  }

  while (state.requests.length >= 1) {
    const [request] = state.requests;
    const sent = sendRequest(url, request);

    if (!sent) {
      return;
    }

    state.requests.shift();
  }
}

一定間隔おきに,その時にキューに格納されているHTTPリクエストを古いものから順に送信する.
HTTPリクエストの送信キューは app 側でも実装されていますが(厳密にはcompanionへのソケット送信用のキュー),一度送信を試して失敗したらキューに追加する app とは異なり,companion では未送信のまま直接キューに追加しているという違いがあります.

Taichi AsakuraTaichi Asakura

2024.11.20 (水)

やったこと

  • リラックス傾向算出に用いる,心拍間隔の単位を s から ms に変更する.
  • Fitbit 心拍センサー 周波数の検証 (Versa 3)

心拍間隔の単位の変更

https://github.com/t-asa-nagaoka/heartrate-switch/pull/2

Fitbit 心拍センサー 周波数の検証

Fitbit上に現在の心拍サンプル数が表示されるようにして,プログラム上で指定した周波数でちゃんと取得されているのか調べてみました.

何故か一番上のリラックス傾向の小数第1位が「十分なスペースがない」という理由で表示されていません.(下のより長い文字列がちゃんと表示されているのでバグ?)

App: Error 84 Not enough space, string truncated to '[21] 929.'

周波数 1Hz (1秒に1回, ソースコードそのまま) の場合

実機で保持時間 20 秒で実験したところ,1秒ずつちゃんと心拍サンプル数が増えていき20秒経過後は心拍サンプル数は 20 をキープしていました.

Fitbitを腕から外すと画面の更新が止まり,もう一度付けると心拍サンプル数が減少した状態から画面が更新され始めました.

Fitbit Web APIで取れる心拍記録とは異なり,アプリ内であれば1秒に1回は確実に心拍サンプルを取得できるようです.ただ,ソースコード上で周波数を早く/遅くしても,何故か1Hz (1秒に1回) という実際の周波数は変わりませんでした.