harryのブログ

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

FITファイルの概要とFIT SDKのサンプルコード


はじめに

Garminのフィットネスデバイスなどで使用されているFIT *1 ファイルのデータフォーマット概要とFIT SDKを使用してエンコード/デコードを行うサンプルコードに関する記事です。

例えば、Garmin Edgeで記録した自転車の走行データを表示・解析・編集(補正)するようなアプリ開発に興味がある方向けの記事です。 *2

目次

  • FITファイルフォーマット
  • FIT SDK & サンプルコード

FITファイルフォーマット

FITファイルのデータフォーマット概要です。 ほぼSDKのドキュメントから引用したものですので、より詳細な仕様はFIT SDKに同梱されているドキュメントを参照してください。

概要図

f:id:harry0000:20200419155353p:plain D00001275 Flexible & Interoperable Data Transfer (FIT) Protocol Rev 1.7.pdf (P.11)

Header

Byte Parameter Description
0 Header Size Headerのサイズで、通常14(0x0E)が入ります。レガシーなデータは12(0x0C)で、HeaderのCRCが存在しないデータになります。
1 Protocol Version Protocol version
2 Profile Version LSB Profile Version
3 Profile Version MSB
4 Data Size LSB Headerとファイル終端のCRCを除いたデータのサイズ。
5 Data Size
6 Data Size
7 Data Size MSB
8 Data Type Byte[0] ASCII文字コード".FIT"を格納する。
つまりData Type Byte[0]から順に{0x2E, 0x46, 0x49, 0x54}を格納します。
9 Data Type Byte[1]
10 Data Type Byte[2]
11 Data Type Byte[3]
12 CRC LSB HeaderのCRCで、0~11Byteのデータから算出して格納する。このCRCはOptionalで、0x0000が設定されていることがあります。 *3
13 CRC MSB

Data Records

1ByteのRecord Headerとそれに続くRecord Contentで構成されます。

Record Header

Record Headerは2種類あり、最上位bitでどちらかが特定できます。

1. Normal Header

Headerの最上位bitが 0

以下の仕様を見れば分かりますが、バイナリエディタで見た時に0x4Xで始まってるのがDefinition Messageで、0x0Xで始まってるのがData Messageと、ある程度見当をつけることはできます。 *4

bit Value Description
7 0 Normal Header
6 0 or 1 Message Type
1: Definition Message
0: Data Message
5 0 Reserved
4 0 Reserved
0 - 3 0 - 15 Local Message Type

2. Compressed Timestamp Header

Headerの最上位bitが 1。 今回は説明を省略します。

Definition Message

f:id:harry0000:20200419155330p:plain D00001275 Flexible & Interoperable Data Transfer (FIT) Protocol Rev 1.7.pdf (P.19)

以降に続く実際のデータであるData Messageの種類やデータサイズの定義です。 この定義とData Messageの紐付けはLocal Message Typeで行なわれます。

1つのFITファイルで同じLocal Message TypeDefinition Messageが複数回出現することがありますが、これは定義の更新(再定義)です。定義の更新はData Messageのフィールド数増減などで発生し、以降のData Messageは更新後の定義をもとにエンコードする必要があります。

Byte Parameter Description
0 Normal Header 0x400x4Fのいずれかの値が格納されます。
下位bitの0FLocal Message Typeです。
1 Reserved
2 Architecture Data Messageに格納されているデータのエンディアン(バイトオーダ)です。
0: Little Endian
1: Big Endian
3 - 4 Global Message Number Data Messageの種別。
例えば、Recodeの場合は20(0x14)が格納されています。
エンディアンArchitecture依存です。
5 Fields Data Messageに含まれるフィールド数。
6 - END Field Definition Data Messageに含まれる各フィールドの定義。
フィールド1つにつき3Byteで、それがフィールド数分あります。

Field Definition

Data Messageに含まれている各フィールドに対する定義です。

Byte Parameter Description
0 Field Definition Number データの種別。例えばRecodeのMessageであれば、スピード、ケイデンス、パワー等を表す数値が格納されています。
1 Size フィールドのデータサイズ。単位はByte。
2 Base Type フィールドのデータ型。byteenumstringsint8など14種あります。

Data Message

f:id:harry0000:20200419155250p:plain D00001275 Flexible & Interoperable Data Transfer (FIT) Protocol Rev 1.7.pdf (P.21)

実際のデータに相当する部分で、フィールドごとのデータが格納されています。 Normal HeaderLocal Message Typeから対応するDefinition Messageを特定してデータをデコードします。

Byte Parameter Description
0 Normal Header 0x000x0Fのいずれかの値が格納されます。
下位bitの0FLocal Message Typeです。
1 - END Data Fields Definition Messageで定義されたフィールド数分データが続きます。
各フィールドのデータサイズはField Definitionで定義されている通りです。

CRC

HeaderData Recordsのデータから算出したCRCを2Byte(Little Endian)で記録します。 実際の算出処理などはSDKのソース・ドキュメント・サンプルコードを参照してください。

FIT SDK

ANTのサイトでFITデータを利用したアプリケーション開発の為のSDKが配布されています。 最新版は16.00です。 *5

SDKの主な内容物は以下の通りです。

SDKAPIドキュメントについては、XMLドキュメントコメント(C#)やJavadoc(Java)が存在する程度なので、APIの詳細や使用方法についてはソースコードやサンプルコードを読むことになるかと思います。

サンプルコード

SDKに同梱されてるサンプルコードはFITファイルをデコードし、データをコンソールやCSVファイルに出力するものです。

個人的にFITファイルをデコードし、FITファイルとして出力(エンコード)するサンプルが欲しかったので・・・自分で作りました(C#版とJava版)。

https://github.com/harry0000/FitDataDuplicator

デコードした結果をファイルに出力してバイナリレベルで同一のファイルを新規作成するだけのサンプルですが、FITファイルのデータを書き換えて新たなFITファイルを作成するアプリ開発などの参考になるかと思います。

ハマリどころ

最後に、元のFITファイルとバイナリレベルで全く同じファイルを作成(エンコード)しようとした時のハマリどころを紹介します。

C# / Java版共通

  • デコードで、元データに存在しないFieldMesgクラスに追加されていることがある
    • 詳細
      デコード処理において、情報が圧縮して格納されているようなFieldがあった場合、Mesg.ExpandComponents()により情報が展開され、別のFieldとして追加されます。
      例えば、RecodeメッセージにCompressedSpeedDistanceフィールドがあった場合、SpeedDistanceの2つのフィールドに展開し、それぞれFieldへ追加します。SpeedDistanceのフィールドが既に存在する場合は、それらのフィールドの値を上書きします。 *6
      展開して追加されたフィールドは元のFITデータには存在しないため、このままエンコードすると余計なデータが追加されたFITファイルができあがってしまいます。
    • 対処方法
      • C#Java共にFieldは順序つきリストなので、MesgDefで定義されたフィールド数より多い要素(追加された要素)を削除してエンコードすれば元データと同じバイナリを出力できます。
      • より丁寧に処理する場合、Field DefinitionField Definition Numberに存在しないフィールドを削除するのがよいと思います。
         
  • SDKEncode(FileEncoder)クラスを使用してエンコードすると、HeaderのCRCが更新されてしまう
    • 詳細
      HeaderCRCには0x0000が入っている場合がありますが、Encodeクラスを使用してエンコードを行うとHeader.UpdateCRC()FileEncoder.writeFileHeader()が実行され、CRCが更新されてしまいます。
      • Encodeクラスは特に使う理由がなかったので、今回のサンプルでは使いませんでした
         
  • SDKに同梱されているActivity.fitMonitoringFile.fitを正常にデコードできない
    • 詳細
      SDKの仕様(バグ)かFITデータの不備が原因です。
      例えばActivity.fit内のEventメッセージで、Dataフィールドのサイズが1Byteと定義されているデータが含まれています。ですがSDKEventメッセージのDataフィールドをUINT32(4Byte)でデコードしようとするため、正しくデコードできません。
    • 対処方法
      • 同梱されているFITファイルは正確なデータではない可能性があるので、これを動作確認に使用しない
      • 例えば、FIT SDKを避ける

C#版

Java

  • MesgDefクラスとMesgクラスのFieldデータが常にBig Endianエンコードされる
    • 詳細
      MesgDefクラスにLittle Endianが指定されたデータを渡しても、SDKがBig Endianに指定し直してデータをエンコードします。そしてFieldクラスにはBig Endianエンコードする処理しか存在しません。 *7
    • 対処方法
      • Little Endianが指定されている場合のエンコード処理を追加する
      • FITデータがBig Endianに変換されてしまう問題に目をつぶってJavaSDKを使用する
      • 例えば、FIT SDKを避ける
         
  • ハマリどころではないですが、SDKHeaderクラスが存在しない
    • C#版のSDKをもとに追加しました

*1:Flexible and Interoperable Data Transfer

*2:つまり俺の俺による俺のための記事

*3:少なくともGarmin EdgeのActivityデータは0x0000がセットされています

*4:FITファイルをバイナリエディタで開いて見当をつけろとは言っていない

*5:2015/08/07 現在

*6:上書きが発生するような状況はないと思いますが…

*7:元データと意味的に等価で正常なFITデータになりますが、バイナリが異なってしまいます