/// <summary> /// Gets a summary of the data for all the laps. /// </summary> /// <param name="lap">List of laps to get the summary for.</param> /// <returns>The new summary.</returns> private static Summary GetSessionSummary(LapsList laps) { Summary summary = new Summary(); foreach (Lap lap in laps) { foreach (Record record in lap.Records) { summary.AveSpeed += record.Speed; summary.AvePower += record.Power; summary.AveHeartRate += record.HeartRate; summary.AveCadence += record.Cadence; if (record.Speed > summary.MaxSpeed) { summary.MaxSpeed = record.Speed; } if (record.Power > summary.MaxPower) { summary.MaxPower = record.Power; } if (record.HeartRate > summary.MaxHeartRate) { summary.MaxHeartRate = record.HeartRate; } if (record.Cadence > summary.MaxCadence) { summary.MaxCadence = record.Cadence; } } summary.NumRecords += lap.Records.Count; } if (summary.NumRecords != 0) { summary.AveSpeed = summary.AveSpeed / summary.NumRecords; summary.AvePower = summary.AvePower / summary.NumRecords; summary.AveHeartRate = summary.AveHeartRate / summary.NumRecords; summary.AveCadence = summary.AveCadence / summary.NumRecords; } return(summary); }
/// <summary> /// Opens a FIT file. /// </summary> private void OpenFileButton_Click(object sender, RoutedEventArgs e) { // set the initial directory from Settings.OpenFilePath string path = (string)Properties.Settings.Default["OpenFilePath"]; if (path.Length == 0) { path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } openFileDialog.InitialDirectory = path; // prompt the user for a file name bool?result = openFileDialog.ShowDialog(this); if (result == true) { // update Settings.OpenFilePath from the file name path = System.IO.Path.GetDirectoryName(openFileDialog.FileName); Properties.Settings.Default["OpenFilePath"] = path; Properties.Settings.Default.Save(); string fileName = openFileDialog.FileName; // get the CSV and FIT file names string csvFileName = openFileDialog.FileName; string fitFileName = Path.ChangeExtension(csvFileName, "fit"); FileNameTextBox.Text = csvFileName; // get the start time string[] parts = Path.GetFileNameWithoutExtension(csvFileName).Split(new char[] { '-' }); System.DateTime start = System.IO.File.GetCreationTime(csvFileName); if (parts.Length == 5) { try { start = new System.DateTime(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), int.Parse(parts[3]), int.Parse(parts[4]), 0, DateTimeKind.Local); } catch { } } // read the lines from the CSV file laps = new LapsList(); Lap lap = new Lap(); laps.Add(lap); string[] textLines = System.IO.File.ReadAllLines(csvFileName); // convert the CSV lines into FIT records and laps int skipped = 0; int removed = 0; bool gotTotals = false; int rideCalories = 0; uint rideWork = 0; for (int i = 0; i < textLines.Length; i++) { string line = textLines[i]; if (!Char.IsDigit(line[0])) { if (line.StartsWith("Stage_")) { lap = new Lap(); laps.Add(lap); } else if (line.StartsWith("Ride_Totals")) { gotTotals = true; } else if (line.StartsWith("KCal,")) { if (int.TryParse(line.Substring(5), out int calories)) { if (gotTotals) { rideCalories = calories; } else if (laps.Count > 1) { laps[laps.Count - 2].Calories = calories; } } } else if (line.StartsWith("KJ,")) { if (uint.TryParse(line.Substring(3), out uint work)) { if (gotTotals) { rideWork = work; } else if (laps.Count > 1) { laps[laps.Count - 2].Work = work; } } } continue; } Record record = new Record(line, start); if (record.Speed != 0 || record.Power != 0 || record.HeartRate != 0 || record.Cadence != 0) { lap.Records.Add(record); } else { skipped++; } } // remove the last lap if it's empty Lap lastLap = laps[laps.Count - 1]; if (lastLap.Records.Count == 0 && laps.Count > 1) { laps.Remove(lastLap); lastLap = laps[laps.Count - 1]; } // fix up the calorie values int sumCalories = 0; uint sumWork = 0; foreach (Lap l in laps) { sumCalories += l.Calories; sumWork += l.Work; } if (rideCalories == 0) { rideCalories = sumCalories; } if (lastLap.Calories == 0) { lastLap.Calories = rideCalories - sumCalories; } if (rideWork == 0) { rideWork = sumWork; } if (lastLap.Work == 0) { lastLap.Work = rideWork - sumWork; } // remove trailing rundown records from the last lap Record lastRecord = lastLap.Records[lastLap.Records.Count - 1]; Summary lapSummary = GetLapSummary(lastLap); double speedThreshold = lapSummary.AveSpeed * 0.6; int j = lastLap.Records.Count - 1; while (j >= 0 && lastLap.Records[j].Speed < speedThreshold) { j--; } if (j != lastLap.Records.Count - 1) { int count = lastLap.Records.Count - j - 1; lastLap.Records.RemoveRange(j + 1, count); removed += count; } // display a summary of the file Record firstRecord = laps[0].Records[0]; Summary sessionSummary = GetSessionSummary(laps); NumRecordsLabel.Content = sessionSummary.NumRecords.ToString(); NumSkippedLabel.Content = skipped.ToString(); NumRemovedLabel.Content = removed.ToString(); NumLapsLabel.Content = laps.Count.ToString(); System.DateTime startTime = firstRecord.Time.ToLocalTime(); StartDateLabel.Content = startTime.ToString("yyyy/MM/dd"); StartTimeLabel.Content = startTime.ToString("HH:mm:ss"); ElapsedTimeLabel.Content = (lastRecord.Time - firstRecord.Time).ToString(); AveCadenceLabel.Content = sessionSummary.AveCadence.ToString("0"); MaxCadenceLabel.Content = sessionSummary.MaxCadence.ToString(); AvePowerLabel.Content = sessionSummary.AvePower.ToString("0"); MaxPowerLabel.Content = sessionSummary.MaxPower.ToString(); AveHeartRateLabel.Content = sessionSummary.AveHeartRate.ToString("0"); MaxHeartRateLabel.Content = sessionSummary.MaxHeartRate.ToString(); AveSpeedLabel.Content = sessionSummary.AveSpeed.ToString("0.##"); MaxSpeedLabel.Content = sessionSummary.MaxSpeed.ToString("0.##"); // write the data to a FIT file WriteFitFile(fitFileName, start, laps, rideCalories, rideWork); } }
/// <summary> /// Writes the data to a FIT file. /// </summary> /// <param name="fileName">Name of the FIT file to write to.</param> /// <param name="start">Start date/time of the activity.</param> /// <param name="laps">Lap and record data to be written.</param> /// <param name="calories">Calories used for the activity.</param> /// <param name="work">Work done for the activity.</param> static void WriteFitFile(string fileName, System.DateTime start, LapsList laps, int calories, uint work) { // open the encoder and stream Encode encoder = new Encode(ProtocolVersion.V20); FileStream fitStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); encoder.Open(fitStream); // write the file ID message FileIdMesg fileIdMsg = new FileIdMesg(); fileIdMsg.SetType(Dynastream.Fit.File.Activity); fileIdMsg.SetManufacturer(Manufacturer.StagesCycling); fileIdMsg.SetProduct(3); fileIdMsg.SetSerialNumber(1); fileIdMsg.SetTimeCreated(new Dynastream.Fit.DateTime(start)); encoder.Write(fileIdMsg); // write the record and lap messages foreach (Lap lap in laps) { // write the record messages foreach (Record record in lap.Records) { RecordMesg recordMsg = new RecordMesg(); recordMsg.SetTimestamp(new Dynastream.Fit.DateTime(record.Time.ToUniversalTime())); recordMsg.SetHeartRate((byte)record.HeartRate); recordMsg.SetCadence((byte)record.Cadence); recordMsg.SetDistance((float)record.Distance * 1000); recordMsg.SetSpeed((float)(record.Speed / 3.6)); recordMsg.SetPower((ushort)record.Power); encoder.Write(recordMsg); } // write the lap message Record first = lap.Records[0]; Record last = lap.Records[lap.Records.Count - 1]; TimeSpan time = last.Time - first.Time; LapMesg lapMsg = new LapMesg(); lapMsg.SetTimestamp(new Dynastream.Fit.DateTime(last.Time.ToUniversalTime())); lapMsg.SetStartTime(new Dynastream.Fit.DateTime(first.Time.ToUniversalTime())); lapMsg.SetTotalElapsedTime((int)time.TotalSeconds); lapMsg.SetTotalTimerTime((int)time.TotalSeconds); lapMsg.SetTotalDistance((float)(last.Distance - first.Distance) * 1000); lapMsg.SetTotalCalories((ushort)lap.Calories); lapMsg.SetTotalWork(lap.Work * 1000); lapMsg.SetEvent(Event.Lap); lapMsg.SetEventType(EventType.Stop); lapMsg.SetIntensity(Intensity.Active); lapMsg.SetLapTrigger(LapTrigger.Manual); lapMsg.SetSport(Sport.Cycling); Summary lapSummary = GetLapSummary(lap); lapMsg.SetAvgCadence((byte)lapSummary.AveCadence); lapMsg.SetMaxCadence((byte)lapSummary.MaxCadence); lapMsg.SetAvgHeartRate((byte)lapSummary.AveHeartRate); lapMsg.SetMaxHeartRate((byte)lapSummary.MaxHeartRate); lapMsg.SetAvgPower((ushort)lapSummary.AvePower); lapMsg.SetMaxPower((ushort)lapSummary.MaxPower); lapMsg.SetAvgSpeed((float)lapSummary.AveSpeed / 3.6f); lapMsg.SetMaxSpeed((float)lapSummary.MaxSpeed / 3.6f); encoder.Write(lapMsg); } // get the first and last records Record firstRecord = laps[0].Records[0]; Lap lastLap = laps[laps.Count - 1]; Record lastRecord = lastLap.Records[lastLap.Records.Count - 1]; TimeSpan totalTime = lastRecord.Time - firstRecord.Time; // write the session message SessionMesg sessionMsg = new SessionMesg(); sessionMsg.SetTimestamp(new Dynastream.Fit.DateTime(lastRecord.Time.ToUniversalTime())); sessionMsg.SetStartTime(new Dynastream.Fit.DateTime(firstRecord.Time.ToUniversalTime())); sessionMsg.SetTotalElapsedTime((int)totalTime.TotalSeconds); sessionMsg.SetTotalTimerTime((int)totalTime.TotalSeconds); sessionMsg.SetTotalDistance((float)(lastRecord.Distance - firstRecord.Distance) * 1000); sessionMsg.SetTotalCalories((ushort)calories); sessionMsg.SetTotalWork(work * 1000); sessionMsg.SetFirstLapIndex(0); sessionMsg.SetNumLaps((ushort)laps.Count); sessionMsg.SetEvent(Event.Session); sessionMsg.SetEventType(EventType.Stop); sessionMsg.SetSport(Sport.Cycling); sessionMsg.SetSubSport(SubSport.Spin); Summary sessionSummary = GetSessionSummary(laps); sessionMsg.SetAvgCadence((byte)sessionSummary.AveCadence); sessionMsg.SetMaxCadence((byte)sessionSummary.MaxCadence); sessionMsg.SetAvgHeartRate((byte)sessionSummary.AveHeartRate); sessionMsg.SetMaxHeartRate((byte)sessionSummary.MaxHeartRate); sessionMsg.SetAvgPower((ushort)sessionSummary.AvePower); sessionMsg.SetMaxPower((ushort)sessionSummary.MaxPower); sessionMsg.SetAvgSpeed((float)sessionSummary.AveSpeed / 3.6f); sessionMsg.SetMaxSpeed((float)sessionSummary.MaxSpeed / 3.6f); encoder.Write(sessionMsg); // write the activity message ActivityMesg activityMsg = new ActivityMesg(); activityMsg.SetTimestamp(new Dynastream.Fit.DateTime(lastRecord.Time.ToUniversalTime())); activityMsg.SetTotalTimerTime((int)totalTime.TotalSeconds); activityMsg.SetNumSessions(1); activityMsg.SetType(Activity.Manual); activityMsg.SetEvent(Event.Activity); activityMsg.SetEventType(EventType.Stop); encoder.Write(activityMsg); // close the encoder and stream encoder.Close(); fitStream.Close(); }