http://www.thisisant.com/pages/products/fit-sdk
The Flexible and Interoperable Data Transfer (FIT) protocol is designed specifically for the storing and sharing of data that originates from sport, fitness and health devices. The FIT protocol defines a set of data storage templates (FIT messages) that can be used to store information such as user profiles and activity data in files. It is specifically designed to be compact, interoperable and extensible.
namespace eval ::fit {}
array set ::fit::global_msg {
0 file_id
1 capabilities
2 device_settings
3 user_profile
4 hrm_profile
5 sdm_profile
6 bike_profile
7 zones_target
8 hr_zone
9 power_zone
10 met_zone
12 sport
15 goal
18 session
19 lap
20 record
21 event
23 device_info
26 workout
27 workout_step
30 weight_scale
31 course
32 course_point
33 totals
34 activity
35 software
37 file_capabilities
38 mesg_capabilities
39 field_capabilities
49 file_creator
51 blood_pressure
}
array set ::fit::msg_activity {
253 timestamp
0 total_timer_time
1 num_sessions
2 type
3 event
4 event_type
5 local_timestamp
6 event_group
}
array set ::fit::msg_session {
254 message_index
253 timestamp
0 event
1 event_type
2 start_time
3 start_position_lat
4 start_position_long
5 sport
6 sub_sport
7 total_elapsed_time
8 total_timer_time
9 total_distance
10 total_cycles
11 total_calories
13 total_fat_calories
14 avg_speed
15 max_speed
16 avg_heart_rate
17 max_heart_rate
18 avg_cadence
19 max_cadence
20 avg_power
21 max_power
22 total_ascent
23 total_descent
24 total_training_effect
25 first_lap_index
26 num_laps
27 event_group
28 trigger
29 nec_lat
30 nec_long
31 swc_lat
32 swc_long
}
array set ::fit::msg_record {
253 timestamp
0 position_lat
1 position_long
2 altitude
3 heart_rate
4 cadence
5 distance
6 speed
7 power
8 compressed_speed_distance
9 grade
10 resistance
11 time_from_course
12 cycle_length
13 temperature
}
array set ::fit::msg_file_id {
0 type
1 manufacturer
2 product
3 serial_number
4 time_created
5 number
}
array set ::fit::msg_file_creator {
0 software_version
1 hardware_version
}
array set ::fit::msg_event {
253 timestamp
0 event
1 event_type
2 data16
3 data
4 event_group
}
array set ::fit::msg_device_info {
253 timestamp
0 device_index
1 device_type
2 manufacturer
3 serial_number
4 product
5 software_version
6 hardware_version
7 cum_operating_time
10 battery_voltage
11 battery_status
}
array set ::fit::msg_lap {
254 message_index
253 timestamp
0 event
1 event_type
2 start_time
3 start_position_lat
4 stat_position_long
5 end_position_lat
6 end_position_long
7 total_elapsed_time
8 total_timer_time
9 total_distance
10 total_cycles
11 total_calories
12 total_fat_calories
13 avg_speed
14 max_speed
15 avg_heart_rate
16 max_heart_rate
17 avg_cadence
18 max_cadence
19 avg_power
20 max_power
21 total_ascent
22 total_descent
23 intensity
24 lap_trigger
25 sport
26 event_group
}
proc ::fit::read_header {fh} {
binary scan [read $fh 12] ccsia4 hlen proto profile size magic
if {$magic != ".FIT"} {
return -code error "not a fit file"
}
seek $fh $hlen start
return [dict create \
header_len $hlen \
data_size $size \
proto $proto \
profile $profile \
]
}
proc ::fit::decode_definition {fh} {
variable global_msg
set len 0
set fields [list]
binary scan [read $fh 5] ccsuc res arch msg cnt
if {[info exists global_msg($msg)]} { set msg $global_msg($msg) }
for {set i 0} {$i < $cnt} {incr i} {
binary scan [read $fh 3] cuccu field size base
incr len $size
lappend fields $field $size $base
}
return [dict create \
global_msg $msg \
count $cnt \
len $len \
fields $fields \
]
}
proc ::fit::read_record {fh} {
variable msg_defs
variable global_msg
binary scan [read $fh 1] c header
set ret {}
if {$header >> 7 > 0} {
# compressed timestamp header
dict set ret record data
set type [expr {($header & 0b01111111) >> 5}]
if {![info exists msg_defs($type)]} {
return -code error "undefined message type $type"
}
dict set ret type [dict get $msg_defs($type) global_msg]
set raw [read $fh [dict get $msg_defs($type) len]]
dict set ret data [decode_data_fields [dict get $msg_defs($type) global_msg] $raw [dict get $msg_defs($type) fields]]
dict set ret data relative_timestamp [expr {$header & 0b00011111}]
} else {
# normal header
set type [expr {$header & 0b00001111}]
if {$header & 0b01000000} {
set def [decode_definition $fh]
set msg_defs($type) $def
dict set ret record definition
dict set ret local_type $type
dict set ret definition $def
} else {
dict set ret record data
if {![info exists msg_defs($type)]} {
return -code error "undefined message type $type"
}
dict set ret type [dict get $msg_defs($type) global_msg]
set raw [read $fh [dict get $msg_defs($type) len]]
dict set ret data [decode_data_fields [dict get $msg_defs($type) global_msg] $raw [dict get $msg_defs($type) fields]]
}
}
return $ret
}
proc ::fit::decode_data_fields {msg data fields} {
set ret {}
variable msg_$msg
upvar 0 msg_$msg msgs
set offset 0
foreach {name len datatype} $fields {
if {![info exists msgs($name)]} {
dict set ret $name {}
continue
}
switch -- $datatype {
0 { binary scan $data x${offset}a val }
1 { binary scan $data x${offset}c val }
2 { binary scan $data x${offset}cu val }
131 { binary scan $data x${offset}s val }
132 { binary scan $data x${offset}su val }
133 { binary scan $data x${offset}i val }
134 { binary scan $data x${offset}iu val }
7 { binary scan $data x${offset}a$len val }
135 { binary scan $data x${offset}f val }
137 { binary scan $data x${offset}cu val }
10 { binary scan $data x${offset}cu val }
139 { binary scan $data x${offset}su val }
140 { binary scan $data x${offset}iu val }
14 { binary scan $data x${offset}a$len val }
default { return -code error "unknown data type $datatype" }
}
dict set ret $msgs($name) $val
incr offset $len
}
return $ret
}
array set ::fit::cook_imperial {
timestamp {expr {$val + 631065600}}
position_lat {expr {$val / (2**31 / 180.00)}}
position_long {expr {$val / (2**31 / 180.00)}}
speed {expr {($val / 1609344.00) * 3600}}
altitude {expr {(($val / 5.00) - 500) * 3.2808399}}
}
proc ::fit::format_values {rec {sys imperial}} {
if {$sys != "imperial" && $sys != "metric"} { return -code error "measurement system must be either metric or imperial" }
variable cook_$sys
upvar 0 cook_$sys cook
if {[dict get $rec record] != "data"} { return $rec }
dict for {key val} [dict get $rec data] {
if {[info exists cook([dict get $rec type]_$key)]} {
dict set rec data $key [eval $cook([dict get $rec type]_$key)]
} elseif {[info exists cook($key)]} {
dict set rec data $key [eval $cook($key)]
}
}
return $rec
}
proc ::fit::fit_data {file} {
variable msg_defs
unset -nocomplain msg_defs
set fh [open $file r]
fconfigure $fh -translation binary
set header [read_header $fh]
set out [list]
set data_end [expr [dict get $header header_len] + [dict get $header data_size]]
while {[tell $fh] < $data_end} {
set rec [read_record $fh]
if {[dict get $rec record] == "data"} {
lappend out [format_values $rec]
}
}
return $out
}