Converting MyWhoosh FIT file to Garmin Connect
This will be a combined post about two of my great passions: cycling and programming.

Illustration generated by ChatGpt
I've been using the MyWhoosh platform for training in the winter for the second year. It has some good features and some bad ones, but overall it's a great free solution. It offers good graphics, a relatively realistic feeling of cycling and a rich selection of tracks. However, it has one major drawback - it does not automatically synchronize with the Garmin Connect platform.
In the last couple of years, Garmin Connect has become a very serious tool for assessing fitness and physical readiness. Garmin tracks more and more parameters, including sleep quality, nutrition, not to mention physical activity. However, the Garmin Connect platform works exclusively for those activities that were created through licensed platforms or tracked using a Garmin device. You guessed it - MyWhoosh is not one of the supported options.
Until recently, the solution was to export the FIT file from MyWhoosh and import it into Garmin Connect. After a few minutes, Garmin would recalculate and your activity would affect all parameters of your fitness - acute load, fatigue, race readiness... Unfortunately, recently MyWhoosh changed its FIT file so that it is no longer possible to simply load it into Garmin. The main problem with the FIT file is the field 'device', which is recently changed to 'mywhoosh'.
For a while, I used the capabilities of a free online tool at https://www.fitfiletools.com to change the device the FIT file was created with. Quite simply, the original FIT file is selected, the manufacturer and device are selected and after a few seconds you have a new FIT file that seems to have been created by the chosen device. A few clicks, a bit more unnecessary files on the disk, but it works... Or rather - it worked.

Printscreen: https://www.fitfiletools.com
I guess a lot of people started using this tool and at the end of the day it either uses up its quota of space on the server, or experiences some other problem. Mostly - the tool rarely works.

Illustration generated by ChatGpt
And that's where my programming spirit comes into the light of day. I once made a small program for a similar reason that changed device in GPX files, so why not do the same with FIT file? So - today I sat down, did a good Google job and read a ton of texts and specifications about the FIT standard. I can't say I know even 5% of what there is to know about the subject, but towards the end of the day I knew enough to use the code to change a couple of file parameters related to the device that created it.
The tool is dotnet console app, since there are readily available libraries for it, and I'm currently working a lot in C#, so it was a logical choice.
To get started, I created a new console application and provided the Garmin FIT C# SDK as a NuGet:
dotnet new console -n FitDeviceSetter
cd FitDeviceSetter
dotnet add package Garmin.FIT.Sdk
Then with a bit of fiddling and experimentation, I created a simple code that reads an input file as a stream, changes a few properties and writes a new output file. You will see that I nailed the 3570 - Edge 1030 Plus as a device, because, to be honest, I hated digging around in the SDK looking for a device that I could use, and the original file I had as an example was made with that device, so I stole the device code and its serial number 🤷♂
using System;
using System.Collections.Generic;
using System.IO;
using Dynastream.Fit;
using IOFile = System.IO.File;
using FitFile = Dynastream.Fit.File;
class Program
{
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: FitDeviceSetter <input.fit> <output.fit>");
return;
}
var inputPath = args[0];
var outputPath = args[1];
if (!IOFile.Exists(inputPath))
{
Console.WriteLine("Input file not found.");
return;
}
var messages = new List<Mesg>();
// 1) Decode + integrity
using (var inputStream = new FileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var decode = new Decode();
if (!decode.CheckIntegrity(inputStream))
{
Console.WriteLine("Invalid FIT file (integrity check failed).");
return;
}
inputStream.Position = 0;
decode.MesgEvent += (s, e) => messages.Add(e.mesg);
if (!decode.Read(inputStream))
{
Console.WriteLine("Failed to decode FIT file.");
return;
}
}
// 2) SANITIZE: exactly one FILE_ID (by message number)
ushort edge1030PlusProductId = 3570; // hardcoded
ushort fileIdNum = new FileIdMesg().Num;
var sanitized = new List<Mesg>(messages.Count);
bool fileIdKept = false;
foreach (var m in messages)
{
if (m.Num == fileIdNum)
{
if (fileIdKept)
continue; // skip duplicate FILE_ID
var fileId = new FileIdMesg(m);
fileId.SetManufacturer(Manufacturer.Garmin);
fileId.SetProduct(edge1030PlusProductId);
fileId.SetSerialNumber(3313379353u);
sanitized.Add(fileId);
fileIdKept = true;
}
else
{
sanitized.Add(m);
}
}
if (!fileIdKept)
{
var fileId = new FileIdMesg();
fileId.SetType(FitFile.Activity);
fileId.SetManufacturer(Manufacturer.Garmin);
fileId.SetProduct(edge1030PlusProductId);
fileId.SetSerialNumber(3313379353u);
fileId.SetTimeCreated(new Dynastream.Fit.DateTime(System.DateTime.UtcNow));
sanitized.Insert(0, fileId);
}
// 3) Encode output
using (var outStream = new FileStream(outputPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
var encode = new Encode(ProtocolVersion.V20);
try
{
encode.Open(outStream);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to open encoder output stream: {ex.Message}");
return;
}
foreach (var m in sanitized)
encode.Write(m);
try
{
encode.Close(); // writes CRC
outStream.Flush(true);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to finalize FIT output: {ex.Message}");
return;
}
}
Console.WriteLine($"Done. Wrote: {outputPath}");
Console.WriteLine($"Product set to Edge 1030 Plus (id={edge1030PlusProductId}).");
}
}
Here is the complete code. Surely something can be better, so don't be shy to give some advice. If it's not clear to you why I wrote some things the way I did - ask, I'll be happy to clarify any problems I ran into. Of course, feel free to use the code in your own projects, no need to ask ;)
🚴🚵🚴🚵🚴🚵🚴🚵🚴🚵🚴🚵🚴🚵🚴🚵🚴🚵🚴🚵🚴
I just had an example of use and confirmation of correctness. I did today's workout and converted the MyWhoosh generated FIT file to a Garmin device made FIT which I successfully imported into Garmin Connect. Mission successful!

Printscreen: checking output FIT at https://www.fitfileviewer.com/

Printscreen: imported FIT in Garmin Connect
#cycling #bicycle #mywhoosh #garmin #wahoo #develop #dotnet
#srbija #PeakD #Curie #OCD



