これは、ディップ Advent Calendar 2023の記事になります。
経緯
私は趣味で筋トレやボディメイクに取り組んでいます。その一環として毎朝、タニタの体重計で体重を測っています。
タニタの体重計は、Bluetoothを介して、測定したデータを専用アプリ「Health Planet」に送信してくれる機能があります。この機能を使って、毎日の測定結果をデジタルデータとして記録しています。
人間の体重は、食事量や水分量、睡眠時間などで常に変動しているため、ボディメイクでは、1日ごとの体重の変化よりも、週単位、月単位での中長期的な平均の推移を見てあげることが重要です。しかし、毎日の体重を記録し、その平均を手計算するのはとても面倒くさい作業です。
そこで、タニタの「Health Planet API」の存在を知りました。このAPIを利用して、自動でデータの取得し、平均値や増減の算出を行い、毎日必ず見るLINEに通知を送れば、自身のボディメイクにかなり役に立つと思い、実装してみようと考えました。
Health Planet API とは?
「Health Planet API」は、日時や取得項目などを指定して、測定日時や体重、体脂肪率などを取得できるAPIです。
詳しくは、Health Planet API 仕様書をご覧ください。
今回作ったもの
直近7日間の平均体重と先週分(8~14日前)の平均体重との増減を、毎朝8時にLINE通知するシンプルなものです。
実装
今回は、大まかに
- Health Planet APIから測定データ取得
- 取得した測定データから平均値や増減の算出
- LINE Notifyを使って通知
- GitHub Actionsのscheduleイベントから毎朝8時に通知処理を実行
という流れで実装しました。
実装のコードは掻い摘んで載せるので、全貌はGitHubのリポジトリをご覧ください。
1. Health Planet API から体重データを取得
その前に)Health Planet APIのユーザー認証
Health Planet APIは、誰でもすぐ叩けるというわけではなく、ユーザー認証を行う必要があります。
ユーザー認証、データ取得までの過程は以下の記事でまとめられていたので、こちらをご覧ください。
直近2週間分の体重データを取得
次にデータを取得します。
言語はTypeScriptを使いました。(言語はなんでもいいと思います)
今回、実行環境はブラウザではなくNode.jsを想定しているため、ブラウザのみでサポートされているJavaScript標準のfetch
メソッドではなく、axios で、データフェッチを行いました。
また、日時の処理は「date-fns」を使用しています。
/** 体重データを取得 */
export const fetchWeight = async () => {
/** Health Planet API URL */
const url = "https://www.healthplanet.jp/status/innerscan.json";
/** 現在のJST時間を取得 */
const jstNow = utcToZonedTime(new Date(), "Asia/Tokyo");
/** 14日前(YYYYMMDD000000) */
const date14DaysAgo = format(subDays(jstNow, 14), "yyyyMMdd") + "000000";
/** 1日前(YYYYMMDD235959) */
const date1DayAgo = format(subDays(jstNow, 1), "yyyyMMdd") + "235959";
const params = {
access_token: process.env.HEALTH_PLANET_ACCESS_TOKEN,
date: "1",
from: date14DaysAgo,
to: date1DayAgo,
tag: "6021",
};
try {
const response = await axios.get(url, { params });
if (response.status !== 200) {
throw new Error("Network response was not ok");
}
return response.data;
} catch (error) {
console.error("Error fetching data from Health Planet API", error);
throw error;
}
};
すると、以下のようなデータが返ってきます。(平気で晒す)
{
birth_date: '19991027',
data: [
{
date: '202312090750', // 2023年12月9日7時50分
keydata: '74.10', // 74.10kg
model: '01000099', // 測定機器名(01000099は、RD-903)
tag: '6021' // 体重
},
{
date: '202312080728',
keydata: '73.90',
model: '01000099',
tag: '6021'
},
// 略
{
date: '202311270816', // 2週間前まで取得
keydata: '74.00',
model: '01000099',
tag: '6021'
}
],
height: '173',
sex: 'male'
}
こちらのデータをもとに、平均値や増減の算出を行っていきます。
2. 取得した測定データから平均値と増減の算出
date-fnsのisWithinInterval
メソッドを使い、取得した2週間分のデータから、今週分の体重(1~7日前の体重)と先週分の体重(8~14日前の体重)で分けました。
そこから、今週分の体重の平均と先週分との体重の増減を算出しました。
calcWeightAverageDiff
関数では、以下のような返り値が返ってきます。
/** 今週分(1~7日前)の体重の平均と先週分との体重の増減 */
const { currentWeekAverageWeight, weeklyWeightDiff } = calcWeightAverageDiff(fetchData);
console.log(currentWeekAverageWeight, weeklyWeightDiff);
>> 73.63000000000001 -0.15
これは直近1週間の平均体重が73.63kg
、先週の体重平均と比較して-0.15kg
だったことを表しています。
ここで算出した値を、LINE通知させていきます。
3. LINE Notifyでの通知
「LINE Notify」は、文字通りLINEでメッセージを通知するためのLINE公式サービスです。
LINE Notify トークンの発行
LINE Notifyでも、アクセストークンを発行する必要があります。
トークンの発行方法は以下の記事にまとめられていましたので、こちらをご覧ください。
LINE通知する関数を作成
まず、今週の体重 XX.Xkg (±〇〇.〇kg)
の形になるように通知するメッセージを整形します。
/** 通知メッセージの整形 */
export const formatMessage = (currentWeekAverageWeight: number, weeklyWeightDiff: string) => {
/** 通知メッセージ */
const message = `\n今週の体重 ${currentWeekAverageWeight.toFixed(2)}kg(${weeklyWeightDiff}kg)`;
return message;
};
整形した通知メッセージを引数に渡して、以下のようにsendLineNotification
関数でLINE通知を行います。
export const sendLineNotification = async (message: string) => {
const url = "https://notify-api.line.me/api/notify";
const token = process.env.LINE_NOTIFY_TOKEN;
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${token}`,
};
try {
const response = await axios.post(url, `message=${encodeURIComponent(message)}`, { headers });
if (response.status !== 200) {
throw new Error("Network response was not ok");
}
} catch (error) {
console.log("Error sending LINE notification", error);
throw error;
}
};
次のステップで、体重取得、計算処理、LINE通知の一連の処理を定期実行させていきます。
4. GitHub Actionsで毎朝8時に定期実行させる
当初、cronジョブを設定したり、Macの「Automator」を使用して、定期実行させていました。ただこれらの方法だと、JavaScriptを実行するコンピューターがスリープ状態のときは、実行できないという懸念点がありました。
そこで、GitHub Actionsのschedule
イベントで毎朝8時に定期実行させようと考えました。
アクセストークンをSecretsに追加
コードでは、Health Planet API と LINE Notify のトークンを環境変数にしているので、GitHub Actionsでも環境変数を使用するために、リポジトリのセキュリティ設定に値を追加する必要があります。
以下の手順で追加
- タブ「Settings」を選択
- サイドバー「Secrets and variables」から「Actions」を選択
- 「New repository secret」で追加
ワークフローの作成
協定世界時(UTC)で23時(日本標準時で翌朝の8時)に定期実行させるようにワークフローを作成して、Pushします。
name: Daily Weight Notification
on:
schedule:
- cron: "0 23 * * *" # UTCで毎日23時(JSTで翌日の8時)
workflow_dispatch:
jobs:
build-and-run:
runs-on: ubuntu-latest
env: # 環境変数を設定
LINE_NOTIFY_TOKEN: ${{ secrets.LINE_NOTIFY_TOKEN }}
HEALTH_PLANET_ACCESS_TOKEN: ${{ secrets.HEALTH_PLANET_ACCESS_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install
- name: Compile TypeScript
run: npm run build # tscを実行するように、事前にbuildコマンドを用意
- name: Run script
run: node dist/index.js
そうすると、リポジトリのActionsタブから定期実行されていることが確認できました。
感想
感想としては、「便利すぎる」という点に尽きます。
実際に今年の夏、人生で初めて本格的な減量を行いましたが、12~3キロほど、計画的に減量させることができました。もちろん減量期だけではなく、増量期にも使っています。
今回の開発で、日常の「こういうことできたらいいけど、現状ちょっと足りないな...」というかゆい機能を自分でプラスできるのは、開発してて楽しいなと感じました。
あと、ドヤ顔で晒せるぐらいムキムキになりたい.....。
補足
使用している体重計
innerscanDUAL RD-903