July 16, 2023

Garmin FIT files - The Garmin SDK

It seems that Garmin is more interested in having you download their SDK and look at code in various languages that taking pains to document the FIT protocol in detail.

So I took the plunge on 7-16-2023 and downloaded version 21.115 of their SDK, release date June 20, 2023.

-rw-r--r-- 1 tom tom    9964330 Jul 16 17:42  FitSDKRelease_21.115.00.zip
Not that big really. They say it includes code for C, C++, C#, Java, JavaScript, Objective-C, and Python.
The also say it includes example projects. It does include a bunch of example FIT files with accompanying CSV files. Interestingly a search for "latitud" turns up nothing in these and the files seem oriented towards fitness (swimming, sports, steps, weights).

The "C" subdirectory has an "examples directory with encode.c and decode.c. The code is provided along with Visual Studio "accessory files". It could be interesting (as an experiment) to copy the decode.c and the various support files into a directory and try to work up a Makefile to build it under linux and see how that develops.

It turned out to be a 5 minute job with no surprises:

# 7-16-2023
# Tom Trebisky

# Makefile for the Garmin SDK decode.c example under linux

OBJS = decode.o fit_convert.o fit_crc.o fit.o fit_example.o

decode:	$(OBJS)
	cc -o decode $(OBJS)
Interestingly, two files were not required:
fit_product.c
fit_ram.c
Running it, I get:
 ./decode
usage: decode.exe 
./decode /home/tom/c.fit
Testing file conversion using /home/tom/c.fit file...
File is not FIT.
This message is funny, as this file came from my Garmin66i and every other open source FIT utility I have run on it works just fine.

Digging a bit deeper, I see that two calls are made to FitConvert_Read
They return:

0 -- FIT_CONVERT_CONTINUE
5 -- FIT_CONVERT_DATA_TYPE_NOT_SUPPORTED
How far do I want to go with buggy code release by Garmin?

What about the Python code?

I played with this a little. There is no standalone decoder or anything like that. There a bunch of things in the "tests" directory that are not intended to just be run on their own. The python code actually looks clean and straightforward.

The file "decoder.py" has an entry point "read()" in the Decoder class.
Anyway, let's just try this before spending any time with possible buggy code:
I create the file "tom" in the "py" directory as follows:

#!/bin/python

from garmin_fit_sdk import Decoder, Stream

#path = "Activity.fit"
path = "/home/tom/c.fit"

stream = Stream.from_file(path)
decoder = Decoder(stream)
messages, errors = decoder.read()

print(errors)
print(messages)
It works! The output is by no means neat and organized, but I can see valid data from my FIT file in the blob of ascii that it spews out.

Looking at decoder.py, this is nice clean code, well written, and a pleasure to look through.
I add to the above demo program:

!/bin/python

from garmin_fit_sdk import Decoder, Stream

path = "/home/tom/c.fit"

stream = Stream.from_file(path)
decoder = Decoder(stream)
messages, errors = decoder.read()

# list
ne = len(errors)
# hash
nm = len(messages)

print ( f"{ne} errors" )
print ( f"{nm} messages" )

print ( messages.keys())
# dict_keys(['file_id_mesgs', 'file_creator_mesgs', 'device_info_mesgs', 'record_mesgs', 'event_mesgs', 'lap_mesgs', 'session_mesgs', 'activity_mesgs'])

for k in messages.keys() :
    print ( f"{k} -- {len(messages[k])}" )
# file_id_mesgs -- 1
# file_creator_mesgs -- 1
# device_info_mesgs -- 1
# record_mesgs -- 1175
# event_mesgs -- 2
# lap_mesgs -- 1
# session_mesgs -- 1
# activity_mesgs -- 1

for m in messages["event_mesgs"] :
    print ( m )

rm = messages["record_mesgs"]
m = rm[0]
print ( m )
I get output like this:
{'timestamp': datetime.datetime(2023, 7, 12, 20, 40, 30, tzinfo=datetime.timezone.utc), 'data': 0, 'event': 'timer', 'event_type': 'start', 'event_group': 0, 'timer_trigger': 'manual'}
{'timestamp': datetime.datetime(2023, 7, 13, 10, 25, 25, tzinfo=datetime.timezone.utc), 'data': 0, 'event': 'timer', 'event_type': 'stop_all', 'event_group': 0, 'timer_trigger': 'manual'}
{'timestamp': datetime.datetime(2023, 7, 12, 20, 40, 30, tzinfo=datetime.timezone.utc), 'position_lat': 378104392, 'position_long': -1322736078, 'enhanced_speed': 0.12, 'enhanced_altitude': 2211.2, 'altitude': 12607.0, 'speed': 65.535, 'temperature': 27}

Have any comments? Questions? Drop me a line!

Tom's backpacking pages / tom@mmto.org