Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Laps based on target metrics #452

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Common/Dto/P2GWorkout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class P2GWorkout
public UserData UserData { get; set; }
public Workout Workout { get; set; }
public WorkoutSamples WorkoutSamples { get; set; }
public RideDetails RideDetails { get; set; }
public ICollection<P2GExercise> Exercises { get; set; }

public dynamic Raw { get; set; }
Expand Down Expand Up @@ -47,7 +48,7 @@ public static WorkoutType GetWorkoutType(this Workout workout)
};
}

public static ICollection<P2GExercise> GetClassPlanExercises(Workout workout, RideSegments rideSegments)
public static ICollection<P2GExercise> GetClassPlanExercises(Workout workout, Segments rideSegments)
{
var movements = new List<P2GExercise>();

Expand Down Expand Up @@ -76,7 +77,7 @@ public static ICollection<P2GExercise> GetClassPlanExercises(Workout workout, Ri
return movements;
}

var segments = rideSegments?.Segments?.Segment_List;
var segments = rideSegments?.Segment_List;
if (segments is null || segments.Count <= 0) return movements;

foreach (var segment in segments)
Expand Down
40 changes: 40 additions & 0 deletions src/Common/Dto/Peloton/RideDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;

namespace Common.Dto.Peloton;

public record RideDetails
{
public Ride Ride { get; init; }
public TargetMetricsData Target_Metrics_Data { get; init; }
public Segments Segments { get; init; }
}

public record Segments
{
public ICollection<Segment> Segment_List { get; init; }
}

public record TargetMetricsData
{
public ICollection<TargetMetric> Target_Metrics { get; init; }
}

public record TargetMetric
{
public Offsets Offsets { get; init; }
public string Segment_Type { get; init; }
public ICollection<MetricData> Metrics { get; init; }
}

public record Offsets
{
public int Start { get; init; }
public int End { get; init; }
}

public record MetricData
{
public string Name { get; init; }
public float Upper { get; init; }
public float Lower { get; init; }
}
13 changes: 0 additions & 13 deletions src/Common/Dto/Peloton/WorkoutSegments.cs

This file was deleted.

85 changes: 58 additions & 27 deletions src/Conversion/FitConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ protected override async Task<Tuple<string, ICollection<Mesg>>> ConvertInternalA

var workout = workoutData.Workout;
var workoutSamples = workoutData.WorkoutSamples;
var rideDetails = workoutData.RideDetails;
var userData = workoutData.UserData;

// MESSAGE ORDER MATTERS
Expand Down Expand Up @@ -155,12 +156,16 @@ protected override async Task<Tuple<string, ICollection<Mesg>>> ConvertInternalA
if (sport == Sport.Rowing)
preferredLapType = settings.Format.Rowing.PreferredLapType;

if ((preferredLapType == PreferredLapType.Class_Targets || preferredLapType == PreferredLapType.Default)
&& workoutSamples.Target_Performance_Metrics?.Target_Graph_Metrics?.FirstOrDefault(w => w.Type == "cadence")?.Graph_Data is object)
if (preferredLapType == PreferredLapType.Class_Targets || preferredLapType == PreferredLapType.Default)
{
var stepsAndLaps = GetWorkoutStepsAndLaps(workoutSamples, startTime, sport, subSport);
workoutSteps = stepsAndLaps.Values.Select(v => v.Item1).ToList();
laps = stepsAndLaps.Values.Select(v => v.Item2).ToList();
var stepsAndLaps = GetWorkoutStepsAndLaps(workout, workoutSamples, rideDetails, startTime, sport, subSport);
if (stepsAndLaps.Count > 0)
{
workoutSteps = stepsAndLaps.Values.Select(v => v.Item1).ToList();
laps = stepsAndLaps.Values.Select(v => v.Item2).ToList();
}
else
laps = GetLaps(preferredLapType, workoutSamples, startTime, sport, subSport).ToList();
}
else
{
Expand Down Expand Up @@ -427,7 +432,7 @@ private SessionMesg GetSessionMesg(Workout workout, WorkoutSamples workoutSample
return sessionMesg;
}

private Dictionary<int, Tuple<WorkoutStepMesg, LapMesg>> GetWorkoutStepsAndLaps(WorkoutSamples workoutSamples, Dynastream.Fit.DateTime startTime, Sport sport, SubSport subSport)
private Dictionary<int, Tuple<WorkoutStepMesg, LapMesg>> GetWorkoutStepsAndLaps(Workout workout, WorkoutSamples workoutSamples, RideDetails rideDetails, Dynastream.Fit.DateTime startTime, Sport sport, SubSport subSport)
{
using var tracing = Tracing.Trace($"{nameof(FitConverter)}.{nameof(GetWorkoutStepsAndLaps)}")
.WithTag(TagKey.Format, FileFormat.Fit.ToString());
Expand All @@ -437,36 +442,50 @@ private Dictionary<int, Tuple<WorkoutStepMesg, LapMesg>> GetWorkoutStepsAndLaps(
if (workoutSamples is null)
return stepsAndLaps;

var cadenceTargets = GetCadenceTargets(workoutSamples);
var targetMetrics = GetRideTargets(rideDetails, workoutSamples).ToList();
targetMetrics.AddRange(GetWorkoutTargets(workoutSamples));

var targets = targetMetrics.Select(target => target.Get1sTargets()).ToList();
var powerZones = CalculatePowerZones(workout);

if (cadenceTargets is null)
if (targets.Count == 0)
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);
var speedMetricsEnumerator = speedMetrics?.Values.GetEnumerator();
int? prevSecondSinceStart = null;

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

if (speedMetrics is object && index < speedMetrics.Values.Length)
speedMetricsEnumerator?.MoveNext();
var speed = (double?)speedMetricsEnumerator?.Current;
if (speed is not null)
{
var currentSpeedInMPS = ConvertToMetersPerSecond(speedMetrics.GetValue(index), speedMetrics.Display_Unit);
lapDistanceInMeters += 1 * currentSpeedInMPS;
var currentSpeedInMPS = ConvertToMetersPerSecond(speed, speedMetrics.Display_Unit);
lapDistanceInMeters += secondsSincePrevIter * 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)
var targetsChanged = targets.Aggregate(false, (changed, target) =>
{
Target prev = target.Current;
var moved = target.MoveNext();
if (prev is null ^ target.Current is null)
return true;
else if (prev is not null && target.Current is not null)
return changed || !target.Current.Equals(prev);
else
return changed;
});

if (targetsChanged)
{
if (workoutStep != null && lapMesg != null)
{
Expand All @@ -489,10 +508,9 @@ private Dictionary<int, Tuple<WorkoutStepMesg, LapMesg>> GetWorkoutStepsAndLaps(
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);
foreach (var target in targets)
// Convert to range targets because Garmin doesn't visualize zone targets
target.Current?.ToRange(powerZones).ApplyToWorkoutStep(workoutStep);

lapMesg = new LapMesg();
var lapStartTime = new Dynastream.Fit.DateTime(startTime);
Expand All @@ -504,10 +522,23 @@ private Dictionary<int, Tuple<WorkoutStepMesg, LapMesg>> GetWorkoutStepsAndLaps(
lapMesg.SetLapTrigger(LapTrigger.Time);
lapMesg.SetSport(sport);
lapMesg.SetSubSport(subSport);

previousCadenceLower = currentCadenceLower;
previousCadenceUpper = currentCadenceUpper;
}
duration += secondsSincePrevIter;
}

if (workoutStep != null && lapMesg != null)
{
workoutStep.SetDurationValue((uint)duration * 1000); // milliseconds

var lapEndTime = new Dynastream.Fit.DateTime(startTime);
lapEndTime.Add(workoutSamples.Seconds_Since_Pedaling_Start.Count);
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));
}

return stepsAndLaps;
Expand Down
Loading