0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Fitbit × MySQL × Grafana で睡眠データを可視化する(個人学習メモ)

0
Posted at

始めようと思ったキッカケ

上記動画にて、

  • 「WHOOP(フィットネストラッカー)のデータを自身のインフラ基盤に取り込む」
  • 「サービス側が終了しても、自分のデータは自分で管理したい」

という話が面白く感じた。

今回の目的

外部データをDBに保存し、Grafanaで可視化する流れを理解
AIは補助として使用、公式仕様やトライ&エラーで検証を進める

参考にした公式ドキュメント

Grafana × MySQL

Grafana公式
https://grafana.com/docs/grafana/latest/datasources/mysql/

→ GrafanaがMySQLをデータソースとして扱えることを確認。


Fitbit OAuth2

Fitbit公式
https://dev.fitbit.com/build/reference/web-api/authorization/

→ Fitbit APIがOAuth2ベースで認証されることを確認。
※具体的な項目は以下
・day:その日のデータ
・fitbit_user_id:ユーザ識別
・log_id:Fitbit側のログを特定するため
・is_main_sleep:1日1行としてログ保存するため(true判定)
・合計:deep_minutes, deep_count, light_minutes, light_count, rem_minutes, rem_count, wake_minutes, wake_count (可視化用)
・ステージ:deep/light/rem/wakeのminutesとcount(可視化用)
・更新検知:updated_at,ingested_at(取り込み時刻)

※MySQLのUNIQUE制約で重複防止を図る
・UNIQUE(fitbit_user_id, day) ← 1日1行を守る
・UNIQUE(fitbit_user_id, log_id) ← 同ログ重複防止
・INDEX(fitbit_user_id, day) ← 期間クエリ高速化(Grafana向け)


Fitbit Sleep API

Fitbit公式
https://dev.fitbit.com/build/reference/web-api/sleep/get-sleep-log-by-date/

→ 必要なJSONを確認


事前準備

1. Docker Desktop インストール

Docker Desktop をインストールして起動しておく。


2. Fitbit Developer アカウント作成


3. Grafana アカウント作成


作業フォルダ作成

今回は Cドライブ直下に work フォルダを作成。

mkdir C:\work
cd C:\work
mkdir fitbit-mysql-grafana
cd fitbit-mysql-grafana

Step1 MySQLをDockerで起動してみる

まずは単体で MySQL コンテナを起動し、
Docker上で動作することを確認する。

docker run --name fitbit-mysql `
  -e MYSQL_ROOT_PASSWORD=ルート用パスワードを入力 `
  -e MYSQL_DATABASE=fitbit `
  -e MYSQL_USER=app `
  -e MYSQL_PASSWORD=App用パスワードを入力 `
  -p 3306:3306 `
  -d mysql:8.0

Dockerコマンドの内容

オプション 内容
docker run コンテナ起動コマンド
--name コンテナ名を付与
-e 環境変数
MYSQL_ROOT_PASSWORD 必須環境変数
MYSQL_DATABASE スキーマを自動作成
MYSQL_USER アプリ用ユーザを作成
MYSQL_PASSWORD appユーザPW
-p 3306:3306 ポート紐づけ(MySQL標準)
-d バックグラウンド実行

コンテナ確認

docker ps

NAMESに「fitbit-mysql」が表示されればOK。


Step2 docker compose化

・ファイルを固定してまとめて起動する仕組み
・MySQLとGrafanaを「この設定で動かす」と.YAMLに書いておく
 →台本として用意しておく意味

フォルダ構成作成、確認

mkdir db
mkdir db\init
fitbit-mysql-grafana/
│
├─ compose.yaml
├─ .env
│
└─ db/
    └─ init/
        └─ 001_schema.sql

・compose.yaml → 構成定義
・.env → 秘密情報
・db/init → MySQL公式仕様で自動実行されるフォルダ


.env 作成

・docker compose up実行時に読み込まれる。
・パスワード直書きを避ける

cd C:\work\fitbit-mysql-grafana
New-Item .env

メモ帳で開いて、以下入力して保存する。

MYSQL_ROOT_PASSWORD=ルート用パスワードを入力
MYSQL_DATABASE=fitbit
MYSQL_USER=app
MYSQL_PASSWORD=App用パスワードを入力

compose.yaml 作成

New-Item compose.yaml

メモ帳で開いて、以下入力して保存する。

services:
  mysql:
    image: mysql:8.0
    container_name: fitbit-mysql
    restart: unless-stopped

    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}

    ports:
      - "3306:3306"

    volumes:
      - mysql_data:/var/lib/mysql
      - ./db/init:/docker-entrypoint-initdb.d

  grafana:
    image: grafana/grafana:latest
    container_name: fitbit-grafana
    restart: unless-stopped

    ports:
      - "3000:3000"

    depends_on:
      - mysql

volumes:
  mysql_data:

・services→起動するコンテナ一覧を書く場所
・environment→先ほど用意した.envを参照
・volumes→データ永続化に推奨
・/docker-entrypoint-initdb.d→配下の .sql を初回起動時に自動実行する仕様となっている。


Step3 sleep_daily テーブル作成

cd work\fitbit-mysql-grafana\db\init
New-Item 001_schema.sql

メモ帳で開いて、以下を入力する。

CREATE TABLE IF NOT EXISTS sleep_daily (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  day DATE NOT NULL,
  fitbit_user_id VARCHAR(64) NOT NULL,
  log_id BIGINT UNSIGNED NOT NULL,
  is_main_sleep BOOLEAN NOT NULL,

  deep_minutes INT NOT NULL,
  deep_count INT NOT NULL,
  light_minutes INT NOT NULL,
  light_count INT NOT NULL,
  rem_minutes INT NOT NULL,
  rem_count INT NOT NULL,
  wake_minutes INT NOT NULL,
  wake_count INT NOT NULL,

  updated_at DATETIME NULL,
  ingested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

  PRIMARY KEY (id),
  UNIQUE KEY uq_user_day (fitbit_user_id, day),
  UNIQUE KEY uq_user_log (fitbit_user_id, log_id),
  KEY idx_user_day (fitbit_user_id, day)
);

・CREATE TABLE IF NOT EXISTS→sleep_dailyというテーブルを作成します。IF =重複してなければ作成させる。
・AUTO_INCREMENT →値が連番になる
・log_id →Fitbit側のログ識別
・is_main_sleep →1日1行にするためのtrue検知
・INT →Grafanaで扱いやすい型
・NOT NULL →空では登録させない


Step4 docker compose 起動

docker compose up -d

ここで、ContainerがErrorになります。
原因は、Step1で行った「docker run」で、既に作成したからです。
なので、以下のコマンドで消して、再度コマンドを打てば成功します。

docker rm -f fitbit-mysql

起動確認

docker ps

以下2つが表示されればOK。

  • fitbit-mysql
  • fitbit-grafana

Step5 MySQLログイン確認

docker exec -it fitbit-mysql mysql -uapp -p fitbit

・docker exec →起動中のコンテナ内で実行
・-it →コンソールに結果を出力
・-uapp →appユーザでログイン
・-p →PW入力宣言

パスワード入力後、

SHOW TABLES;

sleep_daily が表示されれば成功。


Step6 Grafana接続

http://localhost:3000

1.ブラウザで上記にアクセスして、
・username →admin
・passwaord →admin
2.Data sources →MySQLを選択
3.Host URL →mysql:3306
4.Database name →fitbit
5.Username →app
6.Password →.envのもの
7.test&saveをクリック → connection OK と表示される。

【Grafanaの設定】

・Explore→時間範囲をLast 30 daysに変更。
・format:tabelをtime seriesに変更。
・code画面に切り替えて、以下を入力、グラフが出るか確認、
 無事グラフ化出来たら、Dashboardへ保存する。

SELECT UNIX_TIMESTAMP(day) AS time, deep_minutes AS value, 'deep' AS metric
FROM sleep_daily
UNION ALL
SELECT UNIX_TIMESTAMP(day) AS time, light_minutes AS value, 'light' AS metric
FROM sleep_daily
UNION ALL
SELECT UNIX_TIMESTAMP(day) AS time, rem_minutes AS value, 'rem' AS metric
FROM sleep_daily
UNION ALL
SELECT UNIX_TIMESTAMP(day) AS time, wake_minutes AS value, 'wake' AS metric
FROM sleep_daily
ORDER BY time;

※コードの内容
 sleep_dailyテーブルから日付を「Grafana用の時刻形式」に変換し、deep sleep(深い睡眠時間)を取得して、日付順に並べる

・SELECT →データの指定
・UNIX_TIMESTAMP(day) →UNIX時間に変更=GrafanaがUnix時間採用してる
・As time →列の名前をtimeにする。(Unix時間にあたる)
・deep_minutes AS value →deep_minutes列をvalueで取得
 (GrafanaのX軸=time、Y軸=value)
・FROM sleep_daily →自分で作ったテーブル名から取得
・UNION ALL →結果を縦に結合する
・ORDER BY time →日付ソート実施

Step7 Fitbit Developer設定

Fitbit Developer Portal にログイン。

アプリ作成を行う。

重要設定

OAuth 2.0 Application Type

Personal

を選択。

設定が適切でない場合、OAuth認証エラーとなるケースがある。


Redirect URL

http://localhost:8080/callback

を設定。


Scope

最低限以下を許可。
※今回は睡眠データだけほしいので、その他の許可をしない最小権限の考え

sleep

Step8 アクセストークン取得

今回は以下記事を参考。


OAuth2について

Fitbit API は OAuth2 認証方式。

アクセストークンを利用して APIへアクセスする。


Step9 Postmanで睡眠ログを取得

1.POSTMANでアカウントを作成(Githubアカウントでログイン可)
2.new→https→URL欄に以下のFitbit公式APIの Get Sleep Log を入力
https://api.fitbit.com/1.2/user/-/sleep/date/{date}.json

3.Method:GET、Authorization:Bearer Token、Token:アクセストークンを入力
4.sendをクリックすると、JSONでsleepログが返ってくる
※メモ:Fitbit APIはOAuth2.0採用=OAuth 2.0標準形式=Bearer


確認した主なJSON

項目 用途
dateOfSleep 日付
logId ログ識別
isMainSleep メイン睡眠判定
levels.summary.deep.minutes deep睡眠
levels.summary.rem.minutes REM睡眠

Step10 MySQLにデータを入れてみる

1.キーとのマッピング情報
 day:.dateOfSleep =日付単位集計
 log_id:.logId =ログの識別
 is_main_sleep:.isMainSleep =1日1行判定
 fitbit_user_id:user_id =トークン取得時のもの

2.MySQLにINSERTする

INSERT INTO sleep_daily (
  day,
  fitbit_user_id,
  log_id,
  is_main_sleep,
  deep_minutes,
  deep_count,
  light_minutes,
  light_count,
  rem_minutes,
  rem_count,
  wake_minutes,
  wake_count,
  updated_at
)
VALUES (
  '2026-02-15',
  'ユーザーID',
  ログID(日毎に変わる),
  TRUE,
  69,
  12,
  273,
  31,
  133,
  17,
  44,
  23,
  NOW()
)
ON DUPLICATE KEY UPDATE
  log_id = VALUES(log_id),
  deep_minutes = VALUES(deep_minutes),
  deep_count = VALUES(deep_count),
  light_minutes = VALUES(light_minutes),
  light_count = VALUES(light_count),
  rem_minutes = VALUES(rem_minutes),
  rem_count = VALUES(rem_count),
  wake_minutes = VALUES(wake_minutes),
  wake_count = VALUES(wake_count),
  updated_at = NOW();

※メモ
 INSERT INTOでテーブルに値を読み込み、
VALUEでカラム順と完全一致で数値を入力する
 ON DUPLICATE KEY UPDATE :同じ日付を再読み込みした際には更新とする

Step11 Grafanaで可視化

スクリーンショット 2026-05-03 112454.png


今後、試したい事
・API取得の自動化
・CI/CD
・IaC化

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?