概要
あすけんに入力した各種栄養素の情報を集計し、グラフ化しました!
私はあすけんプレミアムサービスを使用しているため、基本サービス(無料プラン)だと使えない可能性があります。
また、AndroidユーザーなのでAndroidでしかこの手法は使えません。
動機
私は普段あすけんで食事管理をしているのですがPFCバランスの推移がどうだったのかをグラフで見たいと思いました。
だがしかし、あすけんの公式機能ではその機能が提供されていない・・・。(接種カロリーの推移のグラフはあります)
そこであすけんで入力されたPFCの値をGoogleスプレッドシートで集計し、自分でグラフ化しようと思い立ちました。
だがしかし、あすけんの公式機能ではデータのエクスポート機能が提供されていない・・・。(なんで・・・)
検索してみるとiPhoneの「ヘルスケア」アプリになら送信されているという情報は見つかるものの、Androidユーザーなのでかなわず・・・。
途方に暮れていましたがどうもGoogleFitにカロリーデータが届いているっぽい?ということに気付きそこからデータを取得するようにしてみました。
やりかたの候補
やりかたはいくつかあると思います。
- 手入力
- 最終手段に・・・。結局不要でした。
- iPhoneの「ヘルスケア」アプリを使用する
- 調べたら出てくる情報ですが、iPhoneを持っていないので諦め。
- Androidの「ヘルスコネクト」アプリに送信されたデータを使用する
やりかた
ヘルスコネクトからデータを取り出す
前述のデータがあることから、GoogleFitまたはヘルスコネクトからデータがエクスポートできるのでは?と調査してみました。
まずはGoogleFit。しかしAPIはObsorete・・・。
https://developers.google.com/fit/scenarios/read-daily-nutrition?hl=ja
ヘルスコネクトを使えとのことなのでそちらへ。
詳細は省きますが、端末側のAPIらしいので専用のアプリを入れないとだめっぽい?
アプリを作ってみてもいいのですがとりあえずそれ以外の方法を模索することに。
更にヘルスコネクトについて調べてみると定期的にバックアップしてくれる機能があるらしい。
https://support.google.com/android/answer/15323271?hl=ja-JP
zipファイルがアップロードされることを確認したのでバックアップされるのを待ちます。
その後アップロードされたzipファイルを解凍したところ health_connect_export.db
というファイルが取り出せました。
雑にVSCodeに投げてみたところSQLiteファイルで nutrition_record_table
というテーブルに情報が入っていそうということが判明。
↓のクエリで各種値が取り出せそうということもわかったのでこれをコピペすればデータの取り出しはできました。
SELECT DATE('1970-01-01', '+' || local_date || ' days') AS date, protein, total_fat as fat, total_carbohydrate as carbohydrate FROM nutrition_record_table;
データ取り出しの自動化
解凍して、クエリを投げて、という手順を毎回やるのは結構面倒なので自動化してみます。手作業でいいよという方はスキップしてください。
私の場合C#(LinqPad)を使うのが一番手軽なのでそれを使用していますが、やってる処理は単純ですし他の手段でもできそうな気はしますね。
プログラム全文はこちら
前述のバックアップファイルはGoogleドライブに出力されるため、ドライブがマウントされている前提で進めています。
ダウンロード等が必要な場合は別途じっこうしてください
テンポラリディレクトリの作成
zipの展開先ディレクトリを作成し、finallyで削除するようにしておきます。
var destinationPath = Directory.CreateTempSubdirectory().FullName;
try
{
// TODO: Work
}
finally
{
try
{
// Delete Temporary Directory
if (Directory.Exists(destinationPath))
{
Directory.Delete(destinationPath, true);
}
}
catch (Exception e)
{
e.Dump();
}
}
展開
ZipFile.ExtractToDirectory を呼ぶだけです。
ZipFile.ExtractToDirectory(sourcePath, destinationPath);
データの読み込み
SQLiteを扱うにはSystem.Data.SQLite.Coreパッケージが必要です。
特に難しいことはないですね。IAsyncEnumerable
を使っているのは単に趣味です。全部一度に読み込んでも問題ないと思います。
record class Entry(DateTime date, double protein, double fat, double carbohydrate);
async IAsyncEnumerable<Entry> ReadDataAsync(string destinationPath, [EnumeratorCancellation] CancellationToken ct)
{
const string dataName = "health_connect_export.db";
const string query = "SELECT DATE('1970-01-01', '+' || local_date || ' days') AS date, protein, total_fat as fat, total_carbohydrate as carbohydrate FROM nutrition_record_table;";
string connectionString = $"Data Source={Path.Combine(destinationPath, dataName)};Pooling=false;";
await using var connection = new SQLiteConnection(connectionString);
await connection.OpenAsync(ct);
try
{
await using var command = new SQLiteCommand(query, connection);
var reader = await command.ExecuteReaderAsync(ct);
while (await reader.ReadAsync(ct))
{
yield return toEntry(reader);
}
}
finally
{
await connection.CloseAsync();
SQLiteConnection.ClearAllPools();
}
static Entry toEntry(System.Data.Common.DbDataReader reader)
=> new(
date: DateTime.Parse(reader["date"].ToString()),
protein: reader["protein"] is var p && p is DBNull ? 0.0 : Convert.ToDouble(p),
fat: reader["fat"] is var f && f is DBNull ? 0.0 : Convert.ToDouble(f),
carbohydrate: reader["carbohydrate"] is var c && c is DBNull ? 0.0 : Convert.ToDouble(c)
);
}
ただ、SQLiteがなかなかファイルハンドルを放してくれないようでテンポラリディレクトリの削除時に IOException
が発生してしまうようです。
以下の点を対応すると回避できたのですが正しいかどうかはわからないので自己責任でお願いします。
-
connectionString
にPooling=false
を追加 - finallyで
connection.CloseAsync
とSQLiteConnection.ClearAllPools
を呼ぶ - テンポラリディレクトリの削除前に
Task.Delay
とGC.Collect
、GC.WaitForPendingFinalizers
を置いておく- 手元で試す感じだと
GC.Collect
だけで良さそうではあるのですが詳細不明なので全部置いておくことにしました
- 手元で試す感じだと
ファイルの書き出し
特にひねりもなく書き出します。
async Task WriteAsync(IAsyncEnumerable<Entry> data, CancellationToken ct)
{
using var s = new StreamWriter(DestinationFilePath);
await foreach (var d in data)
{
await s.WriteLineAsync(d.ToString());
}
}
フォーマットはどこに書いてもいいのですが読みやすさ重視で Entry
の ToString
に書いてあります。
タブ区切りにすることでペーストしやすいようにしてあります。
public override string ToString() => $"{date:yyyy/MM/dd}\t{protein}\t{fat}\t{carbohydrate}";
出力内容のチェック
出力内容に問題ないかチェックします。
データの入力
データは入手できたのでスプレッドシートへ入力し、グラフ化してみます。
用意したのは以下のカラム。青い部分にコピペして、それより右は自動計算させています。
データの計算
具体的な計算式はこんな感じです。
グラム換算
入力しなかった日は前日と翌日の中間値を入力してグラフが0に凹まないよう対応しました。
=IF($A2<>"", IF(B2>0, B2, (B1+B3)/2), )
カロリー換算
別のシートに名前付きで1gあたりのカロリーを定数として入力しておき、計算します。
Pは4kcal/g、Fは9kcal/g、Cは4kcal/g(3.7kcalが正しい説もあるらしいのでお好みで)で計算しています。
=IF($A2<>"", E2*Pcpg, )
PFCバランス計算
カロリーの合算をして、割るだけです。
=IF($A2<>"", H2/$K2, )
グラフの作成
グラフの作り方は別途調べてください。(手抜き)
折れ線グラフや積み上げグラフなどお好みのもので。
データの入力
スプレッドシートへ前述のC#から書き込むのは大変すぎるので流石にコピペします。
SQLiteからのコピペの場合は,
区切りになっていることがあるのでご注意ください。(単純置換すればいいはずです)
完成
グラフ化することができました!
よいダイエットライフを!