harryのブログ

ロードバイクとか模型とかゲームについて何か書いてあるかもしれません

FIT SDKを再発明してFITファイルのPowerデータを補正するツールを作った話

FIT SDKを再発明して、ペダリング開始時のパワースパイクを検出して0に補正するツールをJavaで作りました。

github.com

SDKに相当する部分はfitrepair-utilsプロジェクトです。

  • 今はActivityファイルに関連した機能しか移植(実装)していません
  • 将来的にSDKに該当するプロジェクトを別の新しいリポジトリに移し、汎用的に使えるようにしたい

元々の課題

f:id:harry0000:20151009133008p:plain

  • この異常値を検出して0に補正したい。

    • 2,000Wのようなデータが1つでもあると、STRAVAで他のデータが地面を這うようなグラフになってしまい困る

検討

Garmin online fit repair toolROTOR User Software 1.6で一定値以上のパワーを補正することはできますが、今回のようなデータだけを補正することができません。

Fit File Repair Tool もありましたが、Windows 8(x64)で動作しませんでした。

FIT SDK の使用を検討し調査しましたが、コードがクソだった。*3

harry0000.hatenablog.com

Qiitaの記事には書かなかったこと

  • SDK(fit.jar)はJava 6 compatibleでコンパイルされている

  • Qiitaの記事投稿後*4、FIT SDKに実装をパクられた?

    • サンプルコードでデータ型をkeyとしたmapに型ごとのinvalid valueを保持していましたが、SDKにもそのmapが追加されました
      ただしSDKのmapはunmodifiableMapではないので、valueが外部から変更できてしまうというクソっぷり

    • サンプルコードはMITライセンスで公開してたので、仮にパクられていたとしても問題はありません

結果

冒頭のようにFIT SDKを再発明して、それを使用してパワーデータ補正ツールを作成しました。

パワーデータ補正ツール (fitrepair-main)

f:id:harry0000:20151009144030p:plain

0Wから上昇して直後に50%より大きく減少*5しているパワーを検出して、0Wに補正します。

この補正によりmax powerとaverage powerが変わるので更新します。 またデータ上accumulated power(累計値)があるので、これも更新します。

TrainingPeaks独自のNormalized Power (NP) もありますが、計算式が不明のため、補正していません。

SDK (fitrepair-utils)

  • Java 8前提の作り
  • デコード処理の流れはC#版のFIT SDKとほぼ同等

最も基本的な使用例

final Reader reader = new Reader();
reader.getDispatcher().setHeaderLintener(
    (header) -> {
        // do something.
    });

reader.getDispatcher().setDefinitionMsgListener(
    (defMsg) -> {
        // do something.
    });

reader.getDispatcher().setDataMsgListener(
    (defMsg, msg) -> {
        // do something.
    });

final boolean result;
try (final InputStream in = new FileInputStream(fitFile)) {
    result = reader.read(in);
} catch (final IOException e) {
    logger.error("Error while reading fit file.", e);
    return;
} finally {
    reader.getDispatcher().removeListeners();
}

DataMessageListenerではデコードしたデータのRawレベルなオブジェクトDataMessageが返ります。(相当使いづらいと思います)

Record、Lap、Session等の走行データを処理したい場合は、それぞれ対応したListenerを登録すればWrapされたオブジェクトが返りますので、簡単に処理することができます。

例:RecordListener

reader.getDispatcher().setRecordListener(
    (DefinitionMessage defMsg, Record msg) -> {
        // do something.
        // e.g. msg.getPower();
        //      msg.getCadence();
    });

*1:100~40,000Wなど

*2:停止後、ペダルの位置調整でクランクを逆回転しているのが原因?

*3:一応、月1ペースで更新され続けてはいます

*4:GitHubにサンプルコードをアップロード後

*5:ただし0Wに減少したものを除く