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 }