これは、Xiaomi Smart Band 7 のデータを Gadgetbridge 経由で取り出し、InfluxDB に保存して Grafana で可視化するまでの個人的な作業メモです。
作業自体は、デバッグモードにしたスマホをUSBでつなぎ、Codex GPT-5.5 xhighに自由にadbコマンドをたたいてもらって95%くらい自律的にやってもらいました。
もし役立てばということで記録します。
やりたかったこと
Xiaomi Smart Band 7 は小さくて軽く、電池持ちもよく、日常的に着けっぱなしにしやすいスマートバンドです。
ただ、歩数、心拍、睡眠、SpO2 などのデータは基本的に Zepp Life や Mi Fitness のアプリ内で見る前提になっています。自分の用途では、これを外へ出して、InfluxDB に蓄積し、Grafana で長期的に見たいという目的がありました。
今回の方針はこうです。
- Xiaomi Smart Band 7 を Zepp Life から Gadgetbridge 運用へ移す
- Gadgetbridge でアクティビティデータを同期する
- Gadgetbridge の SQLite DB を Android スマホ内に自動エクスポートする
- Syncthing-Fork でその DB をサーバーへ同期する
- サーバー側で SQLite を読んで InfluxDB に投入する
- Grafana でダッシュボード化する
全体像はこうなりました。
flowchart LR
band["Xiaomi Smart Band 7"] -->|Bluetooth| phone["Galaxy S24 Ultra<br/>Gadgetbridge"]
phone -->|Auto fetch activity data| gbdb["Gadgetbridge internal DB"]
gbdb -->|Auto export database| exportdb["/sdcard/Documents/Gadgetbridge/Gadgetbridge.db"]
exportdb -->|Syncthing-Fork| server["Orion<br/>Syncthing receive-only folder"]
server --> importer["gb2influx<br/>Docker container"]
importer --> influx["InfluxDB"]
influx --> grafana["Grafana dashboard"]スマホは Bluetooth 同期と DB エクスポートだけを担当し、InfluxDB token を持たせない構成にしました。Python で DB を読んで InfluxDB に書き込む処理は、Android ではなく Linux サーバー側で動かします。
環境
今回確認した環境です。
Phone: Galaxy S24 Ultra
Android: 16
Model: SM-S928Q
Band: Xiaomi Smart Band 7
Gadgetbridge: nodomain.freeyourgadget.gadgetbridge 0.91.1
Zepp Life: com.xiaomi.hm.health 6.16.0
ADB: Windows platform-tools from WSL
ADB は WSL から Windows 側の platform-tools を呼び出しました。
/mnt/c/Software/platform-tools/adb.exe
重要な注意点
Smart Band 7 を Gadgetbridge で使うには、Huami/Xiaomi の auth key が必要です。
この auth key を取得する前に Zepp Life アプリ側でバンドを解除すると、キーが無効になり、やり直しになります。Android の Bluetooth 設定からペアリング情報を消すことと、Zepp Life アプリ内でデバイス解除することは別物です。
今回やったこと、やらなかったことはこうです。
OK:
- Zepp Life を強制停止する
- Zepp Life を disable-user で無効化する
- Gadgetbridge 接続成功後に Zepp Life を止める
- Android OS 側の Bluetooth ペアリング情報を必要に応じて整理する
NG:
- auth key 取得前に Zepp Life アプリ内でバンドを解除する
- バンド本体を初期化する
Auth key 自体はこの記事には載せません。
Zepp Life を止める
auth key を取得し、Gadgetbridge 側へ移行する準備ができたので、Zepp Life を強制停止して無効化しました。
/mnt/c/Software/platform-tools/adb.exe shell am force-stop com.xiaomi.hm.health
/mnt/c/Software/platform-tools/adb.exe shell pm disable-user --user 0 com.xiaomi.hm.health
無効化されていることを確認します。
/mnt/c/Software/platform-tools/adb.exe shell cmd package list packages -d
実行結果です。
package:com.xiaomi.hm.health
戻す場合は以下です。
/mnt/c/Software/platform-tools/adb.exe shell pm enable com.xiaomi.hm.health
Gadgetbridge にペアリングする
Gadgetbridge 側では、次の流れで Smart Band 7 を追加しました。
- Gadgetbridge を開く
- Device discovery を開く
- Start discovery を押す
- Smart Band 7 が見つかる
- デバイス行を長押しする
- Authentication settings を開く
- auth key を入力する
- Submit する
- デバイス行を選択する
- Android の Bluetooth ペアリング確認を許可する
- Companion device permission を許可する
- バンド側に確認が出たら承認する
接続後、Gadgetbridge のメイン画面に Smart Band 7 が表示され、歩数、距離、睡眠などの値が出るようになりました。
この時点で、Zepp Life は「アプリ内で解除した」のではなく、Android 側で止めただけです。
Gadgetbridge の自動同期設定
Gadgetbridge では、まずアクティビティデータの自動取得を有効化しました。
Settings -> Automations -> Auto fetch activity data
これは、バンドからスマホ上の Gadgetbridge へデータを取り込む設定です。サーバーへの送信ではありません。
次に DB の自動エクスポートを有効化しました。
Settings -> Automations -> Auto export database
エクスポート先は以下にしました。
/sdcard/Documents/Gadgetbridge/Gadgetbridge.db
Android のパスとしては、こちらと同じ場所です。
/storage/emulated/0/Documents/Gadgetbridge/Gadgetbridge.db
ここで重要なのは、Auto export database はあくまで「スマホ内の通常ストレージへ SQLite DB を書き出す」だけということです。サーバーへ自動で送るには、Syncthing などの別アプリが必要です。
エクスポート間隔は最終的に 3 時間にしました。健康データの可視化であればリアルタイム性はそこまで必要ないので、まずはこのくらいで十分と判断しました。反映遅延は、ざっくり「Gadgetbridge のエクスポート間隔 + Syncthing 同期 + importer の実行間隔」です。
スマホ内の DB を確認する
ADB で、スマホ内に DB が出ているか確認しました。
/mnt/c/Software/platform-tools/adb.exe shell '
for d in /sdcard/Documents/Gadgetbridge /storage/emulated/0/Documents/Gadgetbridge; do
echo DIR:$d
ls -la "$d" 2>&1
done
' | tr -d '\r'
実行結果です。
DIR:/sdcard/Documents/Gadgetbridge
total 2740
-rw-rw---- 1 u0_a293 media_rw 2805760 2026-06-13 18:40 Gadgetbridge.db
DIR:/storage/emulated/0/Documents/Gadgetbridge
total 2740
-rw-rw---- 1 u0_a293 media_rw 2805760 2026-06-13 18:40 Gadgetbridge.db
DB ファイルを探す場合は、こうしました。
/mnt/c/Software/platform-tools/adb.exe shell '
find /sdcard/Documents -maxdepth 4 \( -iname "*gadget*" -o -iname "*.zip" -o -iname "*.db" \) -print 2>/dev/null
' | tr -d '\r'
実行結果です。
/sdcard/Documents/Gadgetbridge
/sdcard/Documents/Gadgetbridge/Gadgetbridge.db
SQLite DB を WSL にコピーして中身を見る
初回確認では、Syncthing ではなく ADB で直接 DB を取り出しました。
adb pull で WSL
側のパスにうまく書けないことがあったので、今回は
exec-out cat を使いました。
rm -rf /tmp/gadgetbridge-check
mkdir -p /tmp/gadgetbridge-check
/mnt/c/Software/platform-tools/adb.exe exec-out \
cat /sdcard/Documents/Gadgetbridge/Gadgetbridge.db \
> /tmp/gadgetbridge-check/Gadgetbridge.db
ls -lh /tmp/gadgetbridge-check/Gadgetbridge.db
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db 'PRAGMA integrity_check;'
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db '.tables'
実行結果です。
-rw-r--r-- 1 ctrluser ctrluser 2.7M Jun 13 18:41 /tmp/gadgetbridge-check/Gadgetbridge.db
ok
実際に入っていたテーブル
Smart Band 7 のデータは、今回の DB では XIAOMI_*
系ではなく、主に HUAMI_* 系のテーブルに入っていました。
件数はこうでした。
65172 HUAMI_EXTENDED_ACTIVITY_SAMPLE
1599 HUAMI_SPO2_SAMPLE
993 HUAMI_STRESS_SAMPLE
40 HUAMI_PAI_SAMPLE
26 BATTERY_LEVEL
6 HUAMI_HEART_RATE_RESTING_SAMPLE
一方で、以下のテーブルは空でした。
XIAOMI_ACTIVITY_SAMPLE
XIAOMI_SLEEP_STAGE_SAMPLE
XIAOMI_SLEEP_TIME_SAMPLE
XIAOMI_DAILY_SUMMARY_SAMPLE
XIAOMI_MANUAL_SAMPLE
MI_BAND_ACTIVITY_SAMPLE
ここは Gadgetbridge
のバージョン、デバイス、移行経路によって変わる可能性があるので、最初に
.tables と PRAGMA table_info(...)
を見るのが安全です。
歩数・心拍のテーブル
メインの活動データは HUAMI_EXTENDED_ACTIVITY_SAMPLE
に入っていました。
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db \
'PRAGMA table_info(HUAMI_EXTENDED_ACTIVITY_SAMPLE);'
カラムは以下です。
TIMESTAMP
DEVICE_ID
USER_ID
RAW_INTENSITY
STEPS
RAW_KIND
HEART_RATE
UNKNOWN1
SLEEP
DEEP_SLEEP
REM_SLEEP
データ範囲と件数を確認しました。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(MIN(TIMESTAMP),'unixepoch','localtime') AS first_time,
datetime(MAX(TIMESTAMP),'unixepoch','localtime') AS last_time,
COUNT(*) AS rows,
SUM(STEPS) AS total_steps,
SUM(CASE WHEN HEART_RATE > 0 AND HEART_RATE != 255 THEN 1 ELSE 0 END) AS valid_hr_rows
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE;
"
実行結果です。
first_time last_time rows total_steps valid_hr_rows
2026-04-29 11:58:00 2026-06-13 18:10:00 65172 98252 14867
日別に見るとこうです。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
date(TIMESTAMP,'unixepoch','localtime') AS day,
SUM(STEPS) AS steps,
COUNT(*) AS rows,
SUM(CASE WHEN HEART_RATE > 0 AND HEART_RATE != 255 THEN 1 ELSE 0 END) AS valid_hr_rows,
MIN(NULLIF(HEART_RATE,255)) AS min_hr,
MAX(CASE WHEN HEART_RATE != 255 THEN HEART_RATE END) AS max_hr
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE
GROUP BY day
ORDER BY day DESC
LIMIT 10;
"
実行結果の一部です。
day steps rows valid_hr_rows min_hr max_hr
2026-06-13 2283 1091 609 55 141
2026-06-12 9 1440 348 56 111
2026-06-11 9 1440 324 56 113
2026-06-10 30 1440 341 54 102
2026-06-09 25 1440 388 53 106
2026-06-08 3459 1440 607 51 134
直近の有効な心拍行も見ました。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP,'unixepoch','localtime') AS local_time,
STEPS,
HEART_RATE,
RAW_KIND,
RAW_INTENSITY
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE
WHERE HEART_RATE > 0 AND HEART_RATE != 255
ORDER BY TIMESTAMP DESC
LIMIT 20;
"
実行結果の一部です。
local_time STEPS HEART_RATE RAW_KIND RAW_INTENSITY
2026-06-13 17:44:00 0 141 67 52
2026-06-13 17:43:00 31 140 64 63
2026-06-13 17:42:00 102 125 64 94
HEART_RATE = 255 は心拍なしとして扱い、InfluxDB
へは入れないようにしました。
SpO2・ストレス・安静時心拍・バッテリー
SpO2 は HUAMI_SPO2_SAMPLE に入っていました。
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db \
'PRAGMA table_info(HUAMI_SPO2_SAMPLE);'
TIMESTAMP
DEVICE_ID
USER_ID
TYPE_NUM
SPO2
ここで注意が必要なのは、TIMESTAMP
が13桁のミリ秒タイムスタンプだったことです。Unix 秒にするには
/1000 します。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP/1000,'unixepoch','localtime') AS local_time,
TYPE_NUM,
SPO2
FROM HUAMI_SPO2_SAMPLE
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
実行結果の一部です。
local_time TYPE_NUM SPO2
2026-06-13 12:55:30 0 97
2026-06-13 12:50:30 0 98
2026-06-13 12:45:30 0 97
2026-06-13 12:40:30 0 94
ストレスは HUAMI_STRESS_SAMPLE
です。これもミリ秒タイムスタンプでした。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP/1000,'unixepoch','localtime') AS local_time,
TYPE_NUM,
STRESS
FROM HUAMI_STRESS_SAMPLE
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
local_time TYPE_NUM STRESS
2026-06-13 13:06:00 1 10
2026-06-13 12:56:00 1 29
2026-06-13 12:51:00 1 20
安静時心拍は HUAMI_HEART_RATE_RESTING_SAMPLE です。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP/1000,'unixepoch','localtime') AS local_time,
UTC_OFFSET,
HEART_RATE
FROM HUAMI_HEART_RATE_RESTING_SAMPLE
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
local_time UTC_OFFSET HEART_RATE
2026-06-13 12:56:00 32400000 61
2026-06-12 23:59:00 32400000 61
2026-06-11 23:59:00 32400000 60
バッテリーは BATTERY_LEVEL に入っていました。こちらは Unix
秒でした。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP,'unixepoch','localtime') AS local_time,
LEVEL,
BATTERY_INDEX
FROM BATTERY_LEVEL
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
local_time LEVEL BATTERY_INDEX
2026-06-13 18:38:42 84 0
2026-06-13 18:37:42 83 0
2026-06-13 18:36:42 81 0
Syncthing-Fork でスマホからサーバーへ送る
Gadgetbridge の DB エクスポートはスマホ内で完結します。そこで、Android 側に Syncthing-Fork を入れ、サーバー側の Syncthing と同期させました。
Android 側は以下です。
package: com.github.catfriend1.syncthingfork
version: 2.1.1.0
このときは F-Droid の APK を ADB で入れました。
wget -O /tmp/syncthingfork.apk \
https://f-droid.org/repo/com.github.catfriend1.syncthingfork_2010100.apk
/mnt/c/Software/platform-tools/adb.exe install "$(wslpath -w /tmp/syncthingfork.apk)"
通知権限やバックグラウンド動作まわりも ADB から設定しました。
/mnt/c/Software/platform-tools/adb.exe shell pm grant \
com.github.catfriend1.syncthingfork android.permission.POST_NOTIFICATIONS
/mnt/c/Software/platform-tools/adb.exe shell pm grant \
com.github.catfriend1.syncthingfork android.permission.ACCESS_FINE_LOCATION
/mnt/c/Software/platform-tools/adb.exe shell appops set \
com.github.catfriend1.syncthingfork MANAGE_EXTERNAL_STORAGE allow
/mnt/c/Software/platform-tools/adb.exe shell dumpsys deviceidle whitelist \
+com.github.catfriend1.syncthingfork
Android 側の Syncthing-Fork では、次のフォルダを共有しました。
label: Gadgetbridge
id: gadgetbridge
directory: /storage/emulated/0/Documents/Gadgetbridge
サーバー側は、Syncthing の gadgetbridge フォルダを
receive-only にしました。
id: gadgetbridge
label: Gadgetbridge
path inside container: /var/syncthing/Gadgetbridge
host path: /home/ctrluser/docker/gb2influx/data
type: receiveonly
receive-only にした理由は、Android/Gadgetbridge 側を正とし、サーバー側の古い DB をスマホへ押し戻さないためです。
同期後、サーバー側には以下のように DB が届きました。
ssh orion '
ls -la ~/docker/gb2influx/data
find ~/docker/gb2influx/data -maxdepth 1 -type f \
-printf "%f %s bytes %TY-%Tm-%Td %TH:%TM:%TS\n"
'
実行結果です。
Gadgetbridge.db 2805760 bytes 2026-06-13 09:40:15...
Linux 側の find では UTC 表示なので、これは Android 側の
2026-06-13 18:40 JST のエクスポートに対応します。
Syncthing のフォルダ状態も正常でした。
state: idle
globalFiles: 1
globalBytes: 2805760
localFiles: 1
localBytes: 2805760
needFiles: 0
needBytes: 0
InfluxDB に入れるデータ構造
InfluxDB 側は、最初から細かくやりすぎず、測定値ごとに measurement を分けました。
wearable_activity
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
fields:
steps
heart_rate_bpm
raw_intensity
raw_kind
sleep
deep_sleep
rem_sleep
wearable_spo2
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
type_num=<TYPE_NUM>
fields:
spo2_percent
wearable_stress
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
type_num=<TYPE_NUM>
fields:
stress
wearable_resting_heart_rate
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
fields:
heart_rate_bpm
wearable_battery
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
battery_index=<BATTERY_INDEX>
fields:
level_percent
タイムスタンプの扱いは以下です。
HUAMI_EXTENDED_ACTIVITY_SAMPLE.TIMESTAMP: Unix seconds
BATTERY_LEVEL.TIMESTAMP: Unix seconds
HUAMI_SPO2_SAMPLE.TIMESTAMP: Unix milliseconds
HUAMI_STRESS_SAMPLE.TIMESTAMP: Unix milliseconds
HUAMI_HEART_RATE_RESTING_SAMPLE.TIMESTAMP: Unix milliseconds
Importer では、毎回直近 72 時間を再処理するようにしました。Gadgetbridge 側の同期遅延や睡眠データの後補正を拾うためです。
importer の配置
importer はスマホではなく、サーバー側で Docker コンテナとして動かしました。
配置はこうです。
Galaxy S24 Ultra
/sdcard/Documents/Gadgetbridge/Gadgetbridge.db
-> Syncthing-Fork
Orion
/home/ctrluser/docker/gb2influx/data/Gadgetbridge.db
/home/ctrluser/docker/gb2influx/app/gb2influx.py
/home/ctrluser/docker/gb2influx/state/state.json
-> writes to InfluxDB
InfluxDB / Grafana
wearable_* measurements
手動実行は以下です。
ssh orion 'cd ~/docker/gb2influx && docker compose run --rm -e RUN_ONCE=1 gb2influx'
常駐実行は以下です。
ssh orion 'cd ~/docker/gb2influx && docker compose up -d'
初回の historical import では、以下の結果になりました。
written_points=67796 db=/data/Gadgetbridge.db
Syncthing で DB が届いたあとの再実行では、以下です。
written_points=4685 db=/data/Gadgetbridge.db
2回目の件数が少ないのは、state file を持っていて、初回の全量投入後は直近ウィンドウだけを再処理しているためです。
InfluxDB 側では、以下の measurement が確認できました。
wearable_activity
wearable_battery
wearable_resting_heart_rate
wearable_spo2
wearable_stress
件数確認では、以下のようになりました。
wearable_activity 405899 field values
wearable_battery 26 field values
wearable_resting_heart_rate 6 field values
wearable_spo2 1599 field values
wearable_stress 993 field values
wearable_activity が SQLite
の行数より多く見えるのは、InfluxDB 側では field value
単位で数えているためです。1行の SQLite レコードから複数 field
が入るので、元の行数とは一致しません。
Grafana ダッシュボード
Grafana では、最初のダッシュボードとして以下を作りました。
uid: wearable-health-smart-band-7
title: Wearable Health - Smart Band 7
folder: Studio-managed
panels: 11
最初に見るパネルは、次のあたりで十分だと思います。
- 日次歩数
- 心拍の時系列
- SpO2 の時系列
- ストレスの時系列
- バッテリー残量
- 各 measurement の最新 timestamp
睡眠については、HUAMI_EXTENDED_ACTIVITY_SAMPLE に
SLEEP, DEEP_SLEEP, REM_SLEEP
のカラムがあります。ただ、これらの値の意味は Gadgetbridge の UI
表示と照合してから使った方がよさそうです。少なくとも最初のダッシュボードでは、睡眠ステージをきれいに解釈するより、就寝中の心拍、心拍の低下、起床前の上昇、データ欠損を見る方が安全だと思います。
現時点の状態
2026-06-13 時点で、状態はこうです。
Gadgetbridge pairing: OK
Zepp Life: disabled, not unpaired
Auto fetch activity data: enabled
Auto export database: enabled
Exported DB: /sdcard/Documents/Gadgetbridge/Gadgetbridge.db
DB integrity_check: ok
Syncthing-Fork: Android -> Orion sync OK
Orion Syncthing folder: idle / in-sync
Importer: Docker container running
InfluxDB measurements: wearable_activity, wearable_battery, wearable_resting_heart_rate, wearable_spo2, wearable_stress
Grafana dashboard: deployed
残っている作業は、数日から1週間ほど運用して、同期抜け、エクスポート間隔、バッテリー消費、睡眠データの解釈を確認することです。
感想
今回の構成で分かったのは、Xiaomi Smart Band 7 は Gadgetbridge との相性がかなりよく、買い替え前に試す価値が高いということです。
一方で、Gadgetbridge へつながっただけで自動的に InfluxDB まで流れるわけではありません。実際のワークフローは、バンド、Android、Gadgetbridge、DB export、Syncthing、importer、InfluxDB、Grafana の複数段に分かれます。
ただ、一度流れを作ってしまえば、各段階はかなり素直です。
特に重要なのは、最初に以下を切り分けて考えることでした。
Band -> Gadgetbridge:
Bluetooth pairing, auth key, activity sync
Gadgetbridge -> Android storage:
Auto export database
Android storage -> Server:
Syncthing-Fork
Server -> InfluxDB:
SQLite importer
InfluxDB -> Grafana:
Dashboard
ここを混ぜて考えると、「Gadgetbridge に入れたのに Grafana に出ない」のように見えますが、実際には途中にいくつも確認ポイントがあります。
Smart Band 7 をすでに持っていて、Zepp Life のアプリ内だけでデータが閉じるのが不満な場合は、まずこの Gadgetbridge ルートを試すのがよさそうです。


コメント