public ICollection <LapMesg> GetLapsBasedOnSegments(WorkoutSamples workoutSamples, Dynastream.Fit.DateTime startTime, Sport sport, SubSport subSport)
        {
            using var tracing = Tracing.Trace($"{nameof(FitConverter)}.{nameof(GetLapsBasedOnSegments)}")
                                .WithTag(TagKey.Format, FileFormat.Fit.ToString());

            var stepsAndLaps = new List <LapMesg>();

            if (workoutSamples is null)
            {
                return(stepsAndLaps);
            }

            ushort stepIndex    = 0;
            var    speedMetrics = GetSpeedSummary(workoutSamples);

            if (workoutSamples.Segment_List.Any())
            {
                var totalElapsedTime = 0;
                foreach (var segment in workoutSamples.Segment_List)
                {
                    var lapStartTime = new Dynastream.Fit.DateTime(startTime);
                    lapStartTime.Add(segment.Start_Time_Offset);

                    totalElapsedTime += segment.Length;

                    var lapMesg = new LapMesg();
                    lapMesg.SetStartTime(lapStartTime);
                    lapMesg.SetMessageIndex(stepIndex);
                    lapMesg.SetEvent(Event.Lap);
                    lapMesg.SetLapTrigger(LapTrigger.Time);
                    lapMesg.SetSport(sport);
                    lapMesg.SetSubSport(subSport);

                    lapMesg.SetTotalElapsedTime(segment.Length);
                    lapMesg.SetTotalTimerTime(segment.Length);

                    var startIndex          = segment.Start_Time_Offset;
                    var endIndex            = segment.Start_Time_Offset + segment.Length;
                    var lapDistanceInMeters = 0f;
                    for (int i = startIndex; i < endIndex; i++)
                    {
                        if (speedMetrics is object && i < speedMetrics.Values.Length)
                        {
                            var currentSpeedInMPS = ConvertToMetersPerSecond(speedMetrics.GetValue(i), workoutSamples);
                            lapDistanceInMeters += 1 * currentSpeedInMPS;
                        }
                    }

                    lapMesg.SetTotalDistance(lapDistanceInMeters);
                    stepsAndLaps.Add(lapMesg);

                    stepIndex++;
                }
            }

            return(stepsAndLaps);
        }
Example #2
0
        private Dictionary <int, Tuple <WorkoutStepMesg, LapMesg> > GetWorkoutStepsAndLaps(WorkoutSamples workoutSamples, Dynastream.Fit.DateTime startTime, Sport sport, SubSport subSport)
        {
            var stepsAndLaps = new Dictionary <int, Tuple <WorkoutStepMesg, LapMesg> >();

            if (workoutSamples is null)
            {
                return(stepsAndLaps);
            }

            var cadenceTargets = workoutSamples.Target_Performance_Metrics?.Target_Graph_Metrics?.FirstOrDefault(w => w.Type == "cadence")?.Graph_Data;

            if (cadenceTargets is null)
            {
                return(stepsAndLaps);
            }

            uint            previousCadenceLower = 0;
            uint            previousCadenceUpper = 0;
            ushort          stepIndex            = 0;
            var             duration             = 0;
            float           lapDistanceInMeters  = 0;
            WorkoutStepMesg workoutStep          = null;
            LapMesg         lapMesg      = null;
            var             speedMetrics = GetSpeedSummary(workoutSamples);

            foreach (var secondSinceStart in workoutSamples.Seconds_Since_Pedaling_Start)
            {
                var index = secondSinceStart <= 0 ? 0 : secondSinceStart - 1;
                duration++;

                if (speedMetrics is object && index < speedMetrics.Values.Length)
                {
                    var currentSpeedInMPS = ConvertToMetersPerSecond(speedMetrics.GetValue(index), workoutSamples);
                    lapDistanceInMeters += 1 * currentSpeedInMPS;
                }

                var currentCadenceLower = index < cadenceTargets.Lower.Length ? (uint)cadenceTargets.Lower[index] : 0;
                var currentCadenceUpper = index < cadenceTargets.Upper.Length ? (uint)cadenceTargets.Upper[index] : 0;

                if (currentCadenceLower != previousCadenceLower ||
                    currentCadenceUpper != previousCadenceUpper)
                {
                    if (workoutStep != null && lapMesg != null)
                    {
                        workoutStep.SetDurationValue((uint)duration * 1000);                         // milliseconds

                        var lapEndTime = new Dynastream.Fit.DateTime(startTime);
                        lapEndTime.Add(secondSinceStart);
                        lapMesg.SetTotalElapsedTime(duration);
                        lapMesg.SetTotalTimerTime(duration);
                        lapMesg.SetTimestamp(lapEndTime);
                        lapMesg.SetEventType(EventType.Stop);
                        lapMesg.SetTotalDistance(lapDistanceInMeters);

                        stepsAndLaps.Add(stepIndex, new Tuple <WorkoutStepMesg, LapMesg>(workoutStep, lapMesg));
                        stepIndex++;
                        duration            = 0;
                        lapDistanceInMeters = 0;
                    }

                    workoutStep = new WorkoutStepMesg();
                    workoutStep.SetDurationType(WktStepDuration.Time);
                    workoutStep.SetMessageIndex(stepIndex);
                    workoutStep.SetTargetType(WktStepTarget.Cadence);
                    workoutStep.SetCustomTargetValueHigh(currentCadenceUpper);
                    workoutStep.SetCustomTargetValueLow(currentCadenceLower);
                    workoutStep.SetIntensity(currentCadenceUpper > 60 ? Intensity.Active : Intensity.Rest);

                    lapMesg = new LapMesg();
                    var lapStartTime = new Dynastream.Fit.DateTime(startTime);
                    lapStartTime.Add(secondSinceStart);
                    lapMesg.SetStartTime(lapStartTime);
                    lapMesg.SetWktStepIndex(stepIndex);
                    lapMesg.SetMessageIndex(stepIndex);
                    lapMesg.SetEvent(Event.Lap);
                    lapMesg.SetLapTrigger(LapTrigger.Time);
                    lapMesg.SetSport(sport);
                    lapMesg.SetSubSport(subSport);

                    previousCadenceLower = currentCadenceLower;
                    previousCadenceUpper = currentCadenceUpper;
                }
            }

            return(stepsAndLaps);
        }
Example #3
0
        private Dynastream.Fit.DateTime AddMetrics(ICollection <Mesg> messages, WorkoutSamples workoutSamples, Dynastream.Fit.DateTime startTime)
        {
            var allMetrics        = workoutSamples.Metrics;
            var hrMetrics         = allMetrics.FirstOrDefault(m => m.Slug == "heart_rate");
            var outputMetrics     = allMetrics.FirstOrDefault(m => m.Slug == "output");
            var cadenceMetrics    = allMetrics.FirstOrDefault(m => m.Slug == "cadence");
            var speedMetrics      = GetSpeedSummary(workoutSamples);
            var resistanceMetrics = allMetrics.FirstOrDefault(m => m.Slug == "resistance");
            var inclineMetrics    = GetGradeSummary(workoutSamples);
            var locationMetrics   = workoutSamples.Location_Data?.SelectMany(x => x.Coordinates).ToArray();
            var altitudeMetrics   = allMetrics.FirstOrDefault(m => m.Slug == "altitude");

            var recordsTimeStamp = new Dynastream.Fit.DateTime(startTime);

            if (workoutSamples.Seconds_Since_Pedaling_Start is object)
            {
                for (var i = 0; i < workoutSamples.Seconds_Since_Pedaling_Start.Count; i++)
                {
                    var record = new RecordMesg();
                    record.SetTimestamp(recordsTimeStamp);

                    if (speedMetrics is object && i < speedMetrics.Values.Length)
                    {
                        record.SetSpeed(ConvertToMetersPerSecond(speedMetrics.GetValue(i), workoutSamples));
                    }

                    if (hrMetrics is object && i < hrMetrics.Values.Length)
                    {
                        record.SetHeartRate((byte)hrMetrics.Values[i]);
                    }

                    if (cadenceMetrics is object && i < cadenceMetrics.Values.Length)
                    {
                        record.SetCadence((byte)cadenceMetrics.Values[i]);
                    }

                    if (outputMetrics is object && i < outputMetrics.Values.Length)
                    {
                        record.SetPower((ushort)outputMetrics.Values[i]);
                    }

                    if (resistanceMetrics is object && i < resistanceMetrics.Values.Length)
                    {
                        var resistancePercent = resistanceMetrics.Values[i] / 1;
                        record.SetResistance((byte)(254 * resistancePercent));
                    }

                    if (altitudeMetrics is object && i < altitudeMetrics.Values.Length)
                    {
                        var altitude = ConvertDistanceToMeters(altitudeMetrics.GetValue(i), altitudeMetrics.Display_Unit);
                        record.SetAltitude(altitude);
                    }

                    if (inclineMetrics is object && i < inclineMetrics.Values.Length)
                    {
                        record.SetGrade((float)inclineMetrics.GetValue(i));
                    }

                    if (locationMetrics is object && i < locationMetrics.Length)
                    {
                        // unit is semicircles
                        record.SetPositionLat(ConvertDegreesToSemicircles(locationMetrics[i].Latitude));
                        record.SetPositionLong(ConvertDegreesToSemicircles(locationMetrics[i].Longitude));
                    }

                    messages.Add(record);
                    recordsTimeStamp.Add(1);
                }
            }

            return(recordsTimeStamp);
        }
Example #4
0
        static public void CreateTimeBasedActivity()
        {
            const double TwoPI = Math.PI * 2.0;
            const double SemicirclesPerMeter = 107.173;
            const string FileName            = "ActivityEncodeRecipe.fit";

            var messages = new List <Mesg>();

            // The starting timestamp for the activity
            var startTime = new Dynastream.Fit.DateTime(System.DateTime.UtcNow);

            // Timer Events are a BEST PRACTICE for FIT ACTIVITY files
            var eventMesgStart = new EventMesg();

            eventMesgStart.SetTimestamp(startTime);
            eventMesgStart.SetEvent(Event.Timer);
            eventMesgStart.SetEventType(EventType.Start);
            messages.Add(eventMesgStart);

            // Create the Developer Id message for the developer data fields.
            var developerIdMesg = new DeveloperDataIdMesg();

            // It is a BEST PRACTICE to reuse the same Guid for all FIT files created by your platform
            byte[] appId = new Guid("00010203-0405-0607-0809-0A0B0C0D0E0F").ToByteArray();
            for (int i = 0; i < appId.Length; i++)
            {
                developerIdMesg.SetApplicationId(i, appId[i]);
            }
            developerIdMesg.SetDeveloperDataIndex(0);
            developerIdMesg.SetApplicationVersion(110);
            messages.Add(developerIdMesg);

            // Create the Developer Data Field Descriptions
            var doughnutsFieldDescMesg = new FieldDescriptionMesg();

            doughnutsFieldDescMesg.SetDeveloperDataIndex(0);
            doughnutsFieldDescMesg.SetFieldDefinitionNumber(0);
            doughnutsFieldDescMesg.SetFitBaseTypeId(FitBaseType.Float32);
            doughnutsFieldDescMesg.SetFieldName(0, "Doughnuts Earned");
            doughnutsFieldDescMesg.SetUnits(0, "doughnuts");
            doughnutsFieldDescMesg.SetNativeMesgNum(MesgNum.Session);
            messages.Add(doughnutsFieldDescMesg);

            FieldDescriptionMesg hrFieldDescMesg = new FieldDescriptionMesg();

            hrFieldDescMesg.SetDeveloperDataIndex(0);
            hrFieldDescMesg.SetFieldDefinitionNumber(1);
            hrFieldDescMesg.SetFitBaseTypeId(FitBaseType.Uint8);
            hrFieldDescMesg.SetFieldName(0, "Heart Rate");
            hrFieldDescMesg.SetUnits(0, "bpm");
            hrFieldDescMesg.SetNativeFieldNum(RecordMesg.FieldDefNum.HeartRate);
            hrFieldDescMesg.SetNativeMesgNum(MesgNum.Record);
            messages.Add(hrFieldDescMesg);

            // Every FIT ACTIVITY file MUST contain Record messages
            var timestamp = new Dynastream.Fit.DateTime(startTime);

            // Create one hour (3600 seconds) of Record data
            for (uint i = 0; i <= 3600; i++)
            {
                // Create a new Record message and set the timestamp
                var recordMesg = new RecordMesg();
                recordMesg.SetTimestamp(timestamp);

                // Fake Record Data of Various Signal Patterns
                recordMesg.SetDistance(i);                                                          // Ramp
                recordMesg.SetSpeed(1);                                                             // Flatline
                recordMesg.SetHeartRate((byte)((Math.Sin(TwoPI * (0.01 * i + 10)) + 1.0) * 127.0)); // Sine
                recordMesg.SetCadence((byte)(i % 255));                                             // Sawtooth
                recordMesg.SetPower((ushort)((i % 255) < 127 ? 150 : 250));                         // Square
                recordMesg.SetAltitude((float)Math.Abs(((double)i % 255.0) - 127.0));               // Triangle
                recordMesg.SetPositionLat(0);
                recordMesg.SetPositionLong((int)Math.Round(i * SemicirclesPerMeter));

                // Add a Developer Field to the Record Message
                var hrDevField = new DeveloperField(hrFieldDescMesg, developerIdMesg);
                recordMesg.SetDeveloperField(hrDevField);
                hrDevField.SetValue((byte)((Math.Sin(TwoPI * (0.01 * i + 10)) + 1.0) * 127.0)); // Sine

                // Write the Rercord message to the output stream
                messages.Add(recordMesg);

                // Increment the timestamp by one second
                timestamp.Add(1);
            }

            // Timer Events are a BEST PRACTICE for FIT ACTIVITY files
            var eventMesgStop = new EventMesg();

            eventMesgStop.SetTimestamp(timestamp);
            eventMesgStop.SetEvent(Event.Timer);
            eventMesgStop.SetEventType(EventType.StopAll);
            messages.Add(eventMesgStop);

            // Every FIT ACTIVITY file MUST contain at least one Lap message
            var lapMesg = new LapMesg();

            lapMesg.SetMessageIndex(0);
            lapMesg.SetTimestamp(timestamp);
            lapMesg.SetStartTime(startTime);
            lapMesg.SetTotalElapsedTime(timestamp.GetTimeStamp() - startTime.GetTimeStamp());
            lapMesg.SetTotalTimerTime(timestamp.GetTimeStamp() - startTime.GetTimeStamp());
            messages.Add(lapMesg);

            // Every FIT ACTIVITY file MUST contain at least one Session message
            var sessionMesg = new SessionMesg();

            sessionMesg.SetMessageIndex(0);
            sessionMesg.SetTimestamp(timestamp);
            sessionMesg.SetStartTime(startTime);
            sessionMesg.SetTotalElapsedTime(timestamp.GetTimeStamp() - startTime.GetTimeStamp());
            sessionMesg.SetTotalTimerTime(timestamp.GetTimeStamp() - startTime.GetTimeStamp());
            sessionMesg.SetSport(Sport.StandUpPaddleboarding);
            sessionMesg.SetSubSport(SubSport.Generic);
            sessionMesg.SetFirstLapIndex(0);
            sessionMesg.SetNumLaps(1);

            // Add a Developer Field to the Session message
            var doughnutsEarnedDevField = new DeveloperField(doughnutsFieldDescMesg, developerIdMesg);

            doughnutsEarnedDevField.SetValue(sessionMesg.GetTotalElapsedTime() / 1200.0f);
            sessionMesg.SetDeveloperField(doughnutsEarnedDevField);
            messages.Add(sessionMesg);

            // Every FIT ACTIVITY file MUST contain EXACTLY one Activity message
            var activityMesg = new ActivityMesg();

            activityMesg.SetTimestamp(timestamp);
            activityMesg.SetNumSessions(1);
            var timezoneOffset = (int)TimeZoneInfo.Local.BaseUtcOffset.TotalSeconds;

            activityMesg.SetLocalTimestamp((uint)((int)timestamp.GetTimeStamp() + timezoneOffset));
            activityMesg.SetTotalTimerTime(timestamp.GetTimeStamp() - startTime.GetTimeStamp());
            messages.Add(activityMesg);

            CreateActivityFile(messages, FileName, startTime);
        }
Example #5
0
        static public void CreateLapSwimActivity()
        {
            // Example Swim Data representing a 500 yard pool swim using different strokes and drills.
            var swimData = new List <Dictionary <string, object> >()
            {
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 20U }, { "stroke", "Freestyle" }, { "strokes", 30U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 25U }, { "stroke", "Freestyle" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 30U }, { "stroke", "Freestyle" }, { "strokes", 10U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 35U }, { "stroke", "Freestyle" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Idle" }, { "duration", 60U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 20U }, { "stroke", "Backstroke" }, { "strokes", 30U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 25U }, { "stroke", "Backstroke" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 30U }, { "stroke", "Backstroke" }, { "strokes", 10U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 35U }, { "stroke", "Backstroke" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Idle" }, { "duration", 60U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 20U }, { "stroke", "Breaststroke" }, { "strokes", 30U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 25U }, { "stroke", "Breaststroke" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 30U }, { "stroke", "Breaststroke" }, { "strokes", 10U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 35U }, { "stroke", "Breaststroke" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Idle" }, { "duration", 60U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 20U }, { "stroke", "Butterfly" }, { "strokes", 30U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 25U }, { "stroke", "Butterfly" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 30U }, { "stroke", "Butterfly" }, { "strokes", 10U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 35U }, { "stroke", "Butterfly" }, { "strokes", 20U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Idle" }, { "duration", 60U }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 40U }, { "stroke", "Drill" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 40U }, { "stroke", "Drill" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 40U }, { "stroke", "Drill" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Active" }, { "duration", 40U }, { "stroke", "Drill" }
                },
                new Dictionary <string, object>()
                {
                    { "type", "Lap" }
                },
            };

            const string FileName = "ActivityEncodeRecipeLapSwim.fit";
            var          messages = new List <Mesg>();

            // The starting timestamp for the activity
            var startTime = new Dynastream.Fit.DateTime(System.DateTime.UtcNow);


            // Timer Events are a BEST PRACTICE for FIT ACTIVITY files
            var eventMesgStart = new EventMesg();

            eventMesgStart.SetTimestamp(startTime);
            eventMesgStart.SetEvent(Event.Timer);
            eventMesgStart.SetEventType(EventType.Start);
            messages.Add(eventMesgStart);

            //
            // Create a Length or Lap message for each item in the sample swim data. Calculate
            // distance, duration, and stroke count for each lap and the overall session.
            //

            // Session Accumulators
            uint   sessionTotalElapsedTime = 0;
            float  sessionDistance         = 0;
            ushort sessionNumLengths       = 0;
            ushort sessionNumActiveLengths = 0;
            ushort sessionTotalStrokes     = 0;
            ushort sessionNumLaps          = 0;

            // Lap accumulators
            uint   lapTotalElapsedTime = 0;
            float  lapDistance         = 0;
            ushort lapNumActiveLengths = 0;
            ushort lapNumLengths       = 0;
            ushort lapFirstLengthIndex = 0;
            ushort lapTotalStrokes     = 0;
            var    lapStartTime        = new Dynastream.Fit.DateTime(startTime);

            var    poolLength     = 22.86f;
            var    poolLengthUnit = DisplayMeasure.Statute;
            var    timestamp      = new Dynastream.Fit.DateTime(startTime);
            ushort messageIndex   = 0;

            foreach (var swimLength in swimData)
            {
                string type = (string)swimLength["type"];

                if (type.Equals("Lap"))
                {
                    // Create a Lap message, set its fields, and write it to the file
                    var lapMesg = new LapMesg();
                    lapMesg.SetMessageIndex(sessionNumLaps);
                    lapMesg.SetTimestamp(timestamp);
                    lapMesg.SetStartTime(lapStartTime);
                    lapMesg.SetTotalElapsedTime(lapTotalElapsedTime);
                    lapMesg.SetTotalTimerTime(lapTotalElapsedTime);
                    lapMesg.SetTotalDistance(lapDistance);
                    lapMesg.SetFirstLengthIndex(lapFirstLengthIndex);
                    lapMesg.SetNumActiveLengths(lapNumActiveLengths);
                    lapMesg.SetNumLengths(lapNumLengths);
                    lapMesg.SetTotalStrokes(lapTotalStrokes);
                    lapMesg.SetAvgStrokeDistance(lapDistance / lapTotalStrokes);
                    lapMesg.SetSport(Sport.Swimming);
                    lapMesg.SetSubSport(SubSport.LapSwimming);
                    messages.Add(lapMesg);

                    sessionNumLaps++;

                    // Reset the Lap accumulators
                    lapFirstLengthIndex = messageIndex;
                    lapNumActiveLengths = 0;
                    lapNumLengths       = 0;
                    lapTotalElapsedTime = 0;
                    lapDistance         = 0;
                    lapTotalStrokes     = 0;
                    lapStartTime        = new Dynastream.Fit.DateTime(timestamp);
                }
                else
                {
                    uint duration   = (uint)swimLength["duration"];
                    var  lengthType = (LengthType)Enum.Parse(typeof(LengthType), type);

                    // Create a Length message and its fields
                    var lengthMesg = new LengthMesg();
                    lengthMesg.SetMessageIndex(messageIndex++);
                    lengthMesg.SetStartTime(timestamp);
                    lengthMesg.SetTotalElapsedTime(duration);
                    lengthMesg.SetTotalTimerTime(duration);
                    lengthMesg.SetLengthType(lengthType);

                    timestamp.Add(duration);
                    lengthMesg.SetTimestamp(timestamp);

                    // Create the Record message that pairs with the Length Message
                    var recordMesg = new RecordMesg();
                    recordMesg.SetTimestamp(timestamp);
                    recordMesg.SetDistance(sessionDistance + poolLength);

                    // Is this an Active Length?
                    if (lengthType == LengthType.Active)
                    {
                        // Get the Active data from the model
                        string     stroke     = swimLength.ContainsKey("stroke") ? (String)swimLength["stroke"] : "Freestyle";
                        uint       strokes    = swimLength.ContainsKey("strokes") ? (uint)swimLength["strokes"] : 0;
                        SwimStroke swimStroke = (SwimStroke)Enum.Parse(typeof(SwimStroke), stroke);

                        // Set the Active data on the Length Message
                        lengthMesg.SetAvgSpeed(poolLength / (float)duration);
                        lengthMesg.SetSwimStroke(swimStroke);

                        if (strokes > 0)
                        {
                            lengthMesg.SetTotalStrokes((ushort)strokes);
                            lengthMesg.SetAvgSwimmingCadence((byte)(strokes * 60U / duration));
                        }

                        // Set the Active data on the Record Message
                        recordMesg.SetSpeed(poolLength / (float)duration);
                        if (strokes > 0)
                        {
                            recordMesg.SetCadence((byte)((strokes * 60U) / duration));
                        }

                        // Increment the "Active" accumulators
                        sessionNumActiveLengths++;
                        lapNumActiveLengths++;
                        sessionDistance     += poolLength;
                        lapDistance         += poolLength;
                        sessionTotalStrokes += (ushort)strokes;
                        lapTotalStrokes     += (ushort)strokes;
                    }

                    // Write the messages to the file
                    messages.Add(recordMesg);
                    messages.Add(lengthMesg);

                    // Increment the "Total" accumulators
                    sessionTotalElapsedTime += duration;
                    lapTotalElapsedTime     += duration;
                    sessionNumLengths++;
                    lapNumLengths++;
                }
            }

            // Timer Events are a BEST PRACTICE for FIT ACTIVITY files
            var eventMesgStop = new EventMesg();

            eventMesgStop.SetTimestamp(timestamp);
            eventMesgStop.SetEvent(Event.Timer);
            eventMesgStop.SetEventType(EventType.StopAll);
            messages.Add(eventMesgStop);

            // Every FIT ACTIVITY file MUST contain at least one Session message
            var sessionMesg = new SessionMesg();

            sessionMesg.SetMessageIndex(0);
            sessionMesg.SetTimestamp(timestamp);
            sessionMesg.SetStartTime(startTime);
            sessionMesg.SetTotalElapsedTime(sessionTotalElapsedTime);
            sessionMesg.SetTotalTimerTime(sessionTotalElapsedTime);
            sessionMesg.SetTotalDistance(sessionDistance);
            sessionMesg.SetSport(Sport.Swimming);
            sessionMesg.SetSubSport(SubSport.LapSwimming);
            sessionMesg.SetFirstLapIndex(0);
            sessionMesg.SetNumLaps(sessionNumLaps);
            sessionMesg.SetPoolLength(poolLength);
            sessionMesg.SetPoolLengthUnit(poolLengthUnit);
            sessionMesg.SetNumLengths(sessionNumLengths);
            sessionMesg.SetNumActiveLengths(sessionNumActiveLengths);
            sessionMesg.SetTotalStrokes(sessionTotalStrokes);
            sessionMesg.SetAvgStrokeDistance(sessionDistance / sessionTotalStrokes);
            messages.Add(sessionMesg);

            // Every FIT ACTIVITY file MUST contain EXACTLY one Activity message
            var activityMesg = new ActivityMesg();

            activityMesg.SetTimestamp(timestamp);
            activityMesg.SetNumSessions(1);
            var timezoneOffset = (int)TimeZoneInfo.Local.BaseUtcOffset.TotalSeconds;

            activityMesg.SetLocalTimestamp((uint)((int)timestamp.GetTimeStamp() + timezoneOffset));
            activityMesg.SetTotalTimerTime(sessionTotalElapsedTime);
            messages.Add(activityMesg);

            CreateActivityFile(messages, FileName, startTime);
        }
        public ICollection <LapMesg> GetLapsBasedOnDistance(WorkoutSamples workoutSamples, Dynastream.Fit.DateTime startTime, Sport sport, SubSport subSport)
        {
            using var tracing = Tracing.Trace($"{nameof(FitConverter)}.{nameof(GetLapsBasedOnDistance)}")
                                .WithTag(TagKey.Format, FileFormat.Fit.ToString());

            var stepsAndLaps = new List <LapMesg>();

            if (workoutSamples is null)
            {
                return(stepsAndLaps);
            }

            var speedMetrics = GetSpeedSummary(workoutSamples);

            if (speedMetrics is null)
            {
                return(stepsAndLaps);
            }

            var speedUnit = GetDistanceUnit(speedMetrics?.Display_Unit);
            var lapMeters = 0;

            switch (speedUnit)
            {
            case DistanceUnit.Kilometers: lapMeters = 1000; break;

            default: lapMeters = 1600; break;
            }

            LapMesg lap                 = null;
            ushort  stepIndex           = 0;
            var     lapDistanceInMeters = 0f;
            float   lapLengthSeconds    = 0;

            for (var secondsSinceStart = 0; secondsSinceStart < speedMetrics.Values.Length; secondsSinceStart++)
            {
                if (lap is null || lap.GetTotalElapsedTime() is not null)
                {
                    // Start new Lap
                    var lapStartTime = new Dynastream.Fit.DateTime(startTime);
                    lapStartTime.Add(secondsSinceStart);

                    lap = new LapMesg();
                    lap.SetStartTime(lapStartTime);
                    lap.SetMessageIndex(stepIndex);
                    lap.SetEvent(Event.Lap);
                    lap.SetLapTrigger(LapTrigger.Time);
                    lap.SetSport(sport);
                    lap.SetSubSport(subSport);

                    lapLengthSeconds    = 0;
                    lapDistanceInMeters = 0f;
                }

                var currentSpeedInMPS = ConvertToMetersPerSecond(speedMetrics.GetValue(secondsSinceStart), workoutSamples);
                lapDistanceInMeters += 1 * currentSpeedInMPS;
                lapLengthSeconds++;

                if (lapDistanceInMeters >= lapMeters || secondsSinceStart == speedMetrics.Values.Length - 1)
                {
                    lap.SetTotalElapsedTime(lapLengthSeconds);
                    lap.SetTotalTimerTime(lapLengthSeconds);
                    lap.SetTotalDistance(lapDistanceInMeters);
                    stepsAndLaps.Add(lap);
                    stepIndex++;
                }
            }

            return(stepsAndLaps);
        }