public static void ExportSchedulesFITFile(Stream exportStream, MemoryStream dataStream)
        {
            MemoryStream tempDataStream = new MemoryStream();

            // Reserve size for header
            tempDataStream.Write(new Byte[12], 0, 12);

            // File id message
            FITMessage fileIdMessage = new FITMessage(FITGlobalMessageIds.FileId);
            FITMessageField fileType = new FITMessageField((Byte)FITFileIdFieldsIds.FileType);
            FITMessageField manufacturerId = new FITMessageField((Byte)FITFileIdFieldsIds.ManufacturerId);
            FITMessageField productId = new FITMessageField((Byte)FITFileIdFieldsIds.ProductId);
            FITMessageField serialNumber = new FITMessageField((Byte)FITFileIdFieldsIds.SerialNumber);
            FITMessageField exportDate = new FITMessageField((Byte)FITFileIdFieldsIds.ExportDate);
            FITMessageField number = new FITMessageField((Byte)FITFileIdFieldsIds.Number);

            manufacturerId.SetUInt16(1);
            fileType.SetEnum((Byte)FITFileTypes.Schedules);

            // Invalid fields
            productId.SetUInt16(0xFFFF);
            serialNumber.SetUInt32z(0);
            exportDate.SetUInt32(0xFFFFFFFF);
            number.SetUInt16(0xFFFF);

            fileIdMessage.AddField(serialNumber);
            fileIdMessage.AddField(exportDate);
            fileIdMessage.AddField(manufacturerId);
            fileIdMessage.AddField(productId);
            fileIdMessage.AddField(number);
            fileIdMessage.AddField(fileType);

            fileIdMessage.Serialize(tempDataStream);

            // Write all passed in data to output stream
            tempDataStream.Write(dataStream.GetBuffer(), 0, (int)dataStream.Length);

            // Write FIT header at the start of the stream
            GarminFitnessByteRange headerSize = new GarminFitnessByteRange(12);
            GarminFitnessByteRange protocolVersion = new GarminFitnessByteRange((Byte)((FITConstants.FITProtocolMajorVersion << 4) | FITConstants.FITProtocolMinorVersion));
            GarminFitnessUInt16Range profileVersion = new GarminFitnessUInt16Range((UInt16)((FITConstants.FITProfileMajorVersion * FITConstants.FITProfileMajorVersionMultiplier) + FITConstants.FITProfileMinorVersion));
            GarminFitnessInt32Range dataSize = new GarminFitnessInt32Range(0);

            tempDataStream.Seek(0, SeekOrigin.Begin);
            dataSize.Value = (int)tempDataStream.Length - 12;

            headerSize.Serialize(tempDataStream);
            protocolVersion.Serialize(tempDataStream);
            profileVersion.Serialize(tempDataStream);
            dataSize.Serialize(tempDataStream);
            tempDataStream.Write(Encoding.UTF8.GetBytes(FITConstants.FITFileDescriptor), 0, 4);

            // Write CRC
            GarminFitnessUInt16Range crc = new GarminFitnessUInt16Range(FITUtils.ComputeStreamCRC(tempDataStream));
            tempDataStream.Seek(0, SeekOrigin.End);
            crc.Serialize(tempDataStream);

            // Write all data to output stream
            exportStream.Write(tempDataStream.GetBuffer(), 0, (int)tempDataStream.Length);
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField durationType = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationType);
            FITMessageField durationValue = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationValue);

            durationType.SetEnum((Byte)FITWorkoutStepDurationTypes.Calories);
            durationValue.SetUInt32((UInt32)CaloriesToSpend);
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField durationType = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationType);
            FITMessageField repeatCount = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetValue);

            durationType.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatCount);
            repeatCount.SetUInt32((UInt32)RepetitionCount);
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField powerZone = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetValue);
            FITMessageField minPower = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            FITMessageField maxPower = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);

            powerZone.SetUInt32(0);

            if (IsPercentFTP)
            {
                minPower.SetUInt32((UInt32)MinPower);
                maxPower.SetUInt32((UInt32)MaxPower);
            }
            else
            {
                minPower.SetUInt32((UInt32)MinPower + 1000);
                maxPower.SetUInt32((UInt32)MaxPower + 1000);
            }
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField HRZone = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetValue);
            FITMessageField minHR = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            FITMessageField maxHR = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            bool exportAsPercentMax = Options.Instance.ExportSportTracksHeartRateAsPercentMax;
            float lastMaxHR = GarminProfileManager.Instance.UserProfile.GetProfileForActivity(Options.Instance.GetGarminCategory(BaseTarget.ParentStep.ParentWorkout.Category)).MaximumHeartRate;

            HRZone.SetUInt32(0);

            if (float.IsNaN(lastMaxHR))
            {
                exportAsPercentMax = false;
            }

            if (exportAsPercentMax)
            {
                float baseMultiplier = Constants.MaxHRInPercentMax / lastMaxHR;

                minHR.SetUInt32((UInt32)Math.Max(Constants.MinHRInPercentMax, Math.Round(Zone.Low * baseMultiplier, 0, MidpointRounding.AwayFromZero)));
                maxHR.SetUInt32((UInt32)Math.Min(Constants.MaxHRInPercentMax, Math.Round(Zone.High * baseMultiplier, 0, MidpointRounding.AwayFromZero)));
            }
            else
            {
                minHR.SetUInt32((UInt32)Utils.Clamp(Zone.Low, Constants.MinHRInBPM, Constants.MaxHRInBPM) + 100);
                maxHR.SetUInt32((UInt32)Utils.Clamp(Zone.High, Constants.MinHRInBPM, Constants.MaxHRInBPM) + 100);
            }
        }
Beispiel #6
0
        public void FillFITMessage(FITMessage workoutMessage)
        {
            FITMessageField capabilities = new FITMessageField((Byte)FITWorkoutFieldIds.Capabilities);
            FITMessageField sportType = new FITMessageField((Byte)FITWorkoutFieldIds.SportType);
            FITMessageField numValidSteps = new FITMessageField((Byte)FITWorkoutFieldIds.NumSteps);
            FITMessageField unknown = new FITMessageField((Byte)FITWorkoutFieldIds.Unknown);
            FITMessageField workoutName = new FITMessageField((Byte)FITWorkoutFieldIds.WorkoutName);

            sportType.SetEnum((Byte)Options.Instance.GetFITSport(Category));
            capabilities.SetUInt32z(32);
            numValidSteps.SetUInt16(StepCount);
            unknown.SetUInt16(0xFFFF);

            workoutMessage.AddField(capabilities);
            if (!String.IsNullOrEmpty(Name))
            {
                workoutName.SetString(Name, (Byte)(Constants.MaxNameLength + 1));
                workoutMessage.AddField(workoutName);
            }
            workoutMessage.AddField(numValidSteps);
            workoutMessage.AddField(unknown);
            workoutMessage.AddField(sportType);
        }
Beispiel #7
0
        public virtual void SerializeToFIT(Stream stream)
        {
            Debug.Assert(GetSplitPartsCount() == 1);

            FITMessage workoutMessage = new FITMessage(FITGlobalMessageIds.Workout);

            FillFITMessage(workoutMessage);
            workoutMessage.Serialize(stream);

            bool serializeDefinition = true;
            foreach (IStep step in Steps)
            {
                step.SerializeToFIT(stream, serializeDefinition);
                serializeDefinition = false;
            }
        }
 public override void DeserializeFromFIT(FITMessage stepMessage)
 {
     Debug.Assert(false);
 }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField powerZone = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetValue);
            FITMessageField minPower = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            FITMessageField maxPower = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            bool exportAsPercentFTP = Options.Instance.ExportSportTracksPowerAsPercentFTP;
            GarminCategories category = Options.Instance.GetGarminCategory(BaseTarget.ParentStep.ParentWorkout.Category);
            float lastFTP = 0;

            if (category == GarminCategories.Biking)
            {
                GarminBikingActivityProfile profile = (GarminBikingActivityProfile)GarminProfileManager.Instance.UserProfile.GetProfileForActivity(category);

                lastFTP = profile.FTP;
            }
            else
            {
                exportAsPercentFTP = false;
            }

            powerZone.SetUInt32(0);

            if (exportAsPercentFTP)
            {
                float baseMultiplier = 100.0f / lastFTP;

                minPower.SetUInt32((UInt32)Utils.Clamp(Math.Round(Zone.Low * baseMultiplier, 0, MidpointRounding.AwayFromZero), Constants.MinPowerInPercentFTP, Constants.MaxPowerInPercentFTP));
                maxPower.SetUInt32((UInt32)Utils.Clamp(Math.Round(Zone.High * baseMultiplier, 0, MidpointRounding.AwayFromZero), Constants.MinPowerInPercentFTP, Constants.MaxPowerInPercentFTP));
            }
            else
            {
                minPower.SetUInt32((UInt32)Utils.Clamp(Zone.Low, Constants.MinPowerInWatts, Constants.MaxPowerWorkoutInWatts) + 1000);
                maxPower.SetUInt32((UInt32)Utils.Clamp(Zone.High, Constants.MinPowerInWatts, Constants.MaxPowerWorkoutInWatts) + 1000);
            }
        }
        public void TestFITScheduleDeserialization()
        {
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.WorkoutSchedules);
            FITMessageField dateField = new FITMessageField((Byte)FITScheduleFieldIds.ScheduledDate);
            DateTime referenceDate = new DateTime(1989, 12, 31);
            DateTime resultDate;

            serializedMessage.AddField(dateField);

            DateTime currentDate = DateTime.Today;
            dateField.SetUInt32((UInt32)(currentDate - referenceDate).TotalSeconds);
            resultDate = WorkoutImporter.ImportWorkoutScheduleMessage(serializedMessage);
            Assert.AreEqual(currentDate, resultDate, "Deserialized date invalid for FIT format");

            currentDate = currentDate.AddDays(1);
            dateField.SetUInt32((UInt32)(currentDate - referenceDate).TotalSeconds);
            resultDate = WorkoutImporter.ImportWorkoutScheduleMessage(serializedMessage);
            Assert.AreEqual(currentDate, resultDate, "Deserialized date invalid for FIT format");

            dateField.SetUInt32(315576000);
            resultDate = WorkoutImporter.ImportWorkoutScheduleMessage(serializedMessage);
            Assert.AreEqual(new DateTime(1999, 12, 31, 12, 0, 0), resultDate, "Deserialized date invalid for FIT format");

            // New Zealand time (UST+12H) is a case where we had problems because the offset changes the day
            String currentZoneName = Time.CurrentTimeZone.standardName;
            bool res = Time.SetTimeZone("New Zealand Standard Time");
            dateField.SetUInt32(315576000);
            resultDate = WorkoutImporter.ImportWorkoutScheduleMessage(serializedMessage);
            res = Time.SetTimeZone(currentZoneName);
            Assert.AreEqual(new DateTime(1999, 12, 31, 12, 0, 0), resultDate, "Deserialized date invalid for FIT format");
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField targetType = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetType);

            targetType.SetEnum((Byte)FITWorkoutStepTargetTypes.Cadence);

            ConcreteTarget.FillFITStepMessage(message);
        }
        public void TestFITSerialization()
        {
            Workout placeholderWorkout = new Workout("Test", PluginMain.GetApplication().Logbook.ActivityCategories[0]);
            RegularStep placeholderStep = new RegularStep(placeholderWorkout);
            ILogbook logbook = PluginMain.GetApplication().Logbook;
            bool exportHRAsMax = Options.Instance.ExportSportTracksHeartRateAsPercentMax;
            bool exportPowerAsFTP = Options.Instance.ExportSportTracksPowerAsPercentFTP;
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.WorkoutStep);
            FITMessageField messageField;

            // This is required to determine the step's id in the workout during serialization
            placeholderWorkout.Steps.AddStepToRoot(placeholderStep);

            // No target
            NullTarget noTarget = new NullTarget(placeholderStep);
            noTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid no target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.NoTarget, messageField.GetEnum(), "Invalid target type in field for target");
            serializedMessage.Clear();

            // Cadence targets
            BaseCadenceTarget cadenceTarget = new BaseCadenceTarget(placeholderStep);

            // Cadence range
            cadenceTarget.ConcreteTarget = new CadenceRangeTarget(80, 90, cadenceTarget);
            cadenceTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Cadence, messageField.GetEnum(), "Invalid target type in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(80, messageField.GetUInt32(), "Invalid lower value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(90, messageField.GetUInt32(), "Invalid upper value in field for CadenceRange target");
            serializedMessage.Clear();

            cadenceTarget.ConcreteTarget = new CadenceRangeTarget(60, 120, cadenceTarget);
            cadenceTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Cadence, messageField.GetEnum(), "Invalid target type in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(60, messageField.GetUInt32(), "Invalid lower value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(120, messageField.GetUInt32(), "Invalid upper value in field for CadenceRange target");
            serializedMessage.Clear();

            // Cadence ST zone
            cadenceTarget.ConcreteTarget = new CadenceZoneSTTarget(logbook.CadenceZones[0].Zones[2], cadenceTarget);
            cadenceTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid CadenceST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Cadence, messageField.GetEnum(), "Invalid target type in field for CadenceST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(70, messageField.GetUInt32(), "Invalid lower value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(85, messageField.GetUInt32(), "Invalid upper value in field for CadenceRange target");
            serializedMessage.Clear();

            cadenceTarget.ConcreteTarget = new CadenceZoneSTTarget(logbook.CadenceZones[0].Zones[4], cadenceTarget);
            cadenceTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid CadenceST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Cadence, messageField.GetEnum(), "Invalid target type in field for CadenceST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(100, messageField.GetUInt32(), "Invalid lower value in field for CadenceRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid CadenceRange target FIT serialization");
            Assert.AreEqual(120, messageField.GetUInt32(), "Invalid upper value in field for CadenceRange target");
            serializedMessage.Clear();

            // Speed targets
            BaseSpeedTarget speedTarget = new BaseSpeedTarget(placeholderStep);

            // Speed range
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[3];
            speedTarget.ConcreteTarget = new SpeedRangeTarget(20, 30, Length.Units.Kilometer, Speed.Units.Speed, speedTarget);
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(5555, messageField.GetUInt32(), "Invalid lower value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(8333, messageField.GetUInt32(), "Invalid upper value in field for SpeedRange target");
            serializedMessage.Clear();

            speedTarget.ConcreteTarget = new SpeedRangeTarget(20, 30, Length.Units.Mile, Speed.Units.Speed, speedTarget);
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(8940, messageField.GetUInt32(), "Invalid lower value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(13411, messageField.GetUInt32(), "Invalid upper value in field for SpeedRange target");
            serializedMessage.Clear();

            // Pace shouldn't change the values saved
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[2];
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for SpeedRange pace target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual(8940, messageField.GetUInt32(), "Invalid lower value in field for SpeedRange pace target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual(13411, messageField.GetUInt32(), "Invalid upper value in field for SpeedRange pace target");
            serializedMessage.Clear();

            // Speed Garmin zone
            speedTarget.ConcreteTarget = new SpeedZoneGTCTarget(1, speedTarget);
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedGTC target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for SpeedGTC target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedGTC target FIT serialization");
            Assert.AreEqual(1, messageField.GetUInt32(), "Invalid zone value in field for SpeedGTC target");
            serializedMessage.Clear();

            speedTarget.ConcreteTarget = new SpeedZoneGTCTarget(3, speedTarget);
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedGTC target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for SpeedGTC target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedGTC target FIT serialization");
            Assert.AreEqual(3, messageField.GetUInt32(), "Invalid zone value in field for SpeedGTC target");
            serializedMessage.Clear();

            // Speed ST zone
            Options.Instance.ExportSportTracksHeartRateAsPercentMax = false;
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[3];
            speedTarget.ConcreteTarget = new SpeedZoneSTTarget(logbook.SpeedZones[0].Zones[1], speedTarget);
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(2777, messageField.GetUInt32(), "Invalid lower value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(5555, messageField.GetUInt32(), "Invalid upper value in field for SpeedRange target");
            serializedMessage.Clear();

            speedTarget.ConcreteTarget = new SpeedZoneSTTarget(logbook.SpeedZones[0].Zones[2], speedTarget);
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(5555, messageField.GetUInt32(), "Invalid lower value in field for SpeedRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid SpeedRange target FIT serialization");
            Assert.AreEqual(8333, messageField.GetUInt32(), "Invalid upper value in field for SpeedRange target");
            serializedMessage.Clear();

            // Pace shouldn't change the values saved
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[2];
            speedTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid SpeedST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Speed, messageField.GetEnum(), "Invalid target type in field for target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for SpeedRange pace target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual(5555, messageField.GetUInt32(), "Invalid lower value in field for SpeedRange pace target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid SpeedRange pace target FIT serialization");
            Assert.AreEqual(8333, messageField.GetUInt32(), "Invalid upper value in field for SpeedRange pace target");
            serializedMessage.Clear();

            // Heart rate targets
            BaseHeartRateTarget hrTarget = new BaseHeartRateTarget(placeholderStep);

            // HR range
            hrTarget.ConcreteTarget = new HeartRateRangeTarget(130, 170, false, hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for HRRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual(230, messageField.GetUInt32(), "Invalid lower value in field for HRRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual(270, messageField.GetUInt32(), "Invalid upper value in field for HRRange target");
            serializedMessage.Clear();

            hrTarget.ConcreteTarget = new HeartRateRangeTarget(100, 190, false, hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for HRRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual(200, messageField.GetUInt32(), "Invalid lower value in field for HRRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid HRRange target FIT serialization");
            Assert.AreEqual(290, messageField.GetUInt32(), "Invalid upper value in field for HRRange target");
            serializedMessage.Clear();

            hrTarget.ConcreteTarget = new HeartRateRangeTarget(50, 70, true, hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRRange %Max target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRRange %Max target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRRange %Max target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for HRRange %Max target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid HRRange %Max target FIT serialization");
            Assert.AreEqual(50, messageField.GetUInt32(), "Invalid lower value in field for HRRange %Max target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid HRRange %Max target FIT serialization");
            Assert.AreEqual(70, messageField.GetUInt32(), "Invalid upper value in field for HRRange %Max target");
            serializedMessage.Clear();

            // HR Garmin zone
            hrTarget.ConcreteTarget = new HeartRateZoneGTCTarget(1, hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRGTC target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRGTC target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRGTC target FIT serialization");
            Assert.AreEqual(1, messageField.GetUInt32(), "Invalid zone value in field for HRGTC target");
            serializedMessage.Clear();

            hrTarget.ConcreteTarget = new HeartRateZoneGTCTarget(3, hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRGTC target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRGTC target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRGTC target FIT serialization");
            Assert.AreEqual(3, messageField.GetUInt32(), "Invalid zone value in field for HRGTC target");
            serializedMessage.Clear();

            // HR ST zone
            Options.Instance.ExportSportTracksHeartRateAsPercentMax = false;
            hrTarget.ConcreteTarget = new HeartRateZoneSTTarget(logbook.HeartRateZones[0].Zones[2], hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for HRST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual(240, messageField.GetUInt32(), "Invalid lower value in field for HRST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual(260, messageField.GetUInt32(), "Invalid upper value in field for HRST target");
            serializedMessage.Clear();

            hrTarget.ConcreteTarget = new HeartRateZoneSTTarget(logbook.HeartRateZones[0].Zones[4], hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for HRST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual(280, messageField.GetUInt32(), "Invalid lower value in field for HRST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid HRST target FIT serialization");
            Assert.AreEqual(340, messageField.GetUInt32(), "Invalid upper value in field for HRST target");
            serializedMessage.Clear();

            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[1];
            Options.Instance.ExportSportTracksHeartRateAsPercentMax = true;
            hrTarget.ConcreteTarget = new HeartRateZoneSTTarget(logbook.HeartRateZones[1].Zones[2], hrTarget);
            hrTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid HRST %Max target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.HeartRate, messageField.GetEnum(), "Invalid target type in field for HRST %Max target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid HRST %Max target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for HRST %Max target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid HRST %Max target FIT serialization");
            Assert.AreEqual(68, messageField.GetUInt32(), "Invalid lower value in field for HRST %Max target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid HRST %Max target FIT serialization");
            Assert.AreEqual(82, messageField.GetUInt32(), "Invalid upper value in field for HRST %Max target");
            serializedMessage.Clear();

            // Power targets
            BasePowerTarget powerTarget = new BasePowerTarget(placeholderStep);

            // Power range
            powerTarget.ConcreteTarget = new PowerRangeTarget(150, 200, false, powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for PowerRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for PowerRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual(1150, messageField.GetUInt32(), "Invalid lower value in field for PowerRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual(1200, messageField.GetUInt32(), "Invalid upper value in field for PowerRange target");
            serializedMessage.Clear();

            powerTarget.ConcreteTarget = new PowerRangeTarget(300, 400, false, powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for PowerRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual(1300, messageField.GetUInt32(), "Invalid lower value in field for PowerRange target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid PowerRange target FIT serialization");
            Assert.AreEqual(1400, messageField.GetUInt32(), "Invalid upper value in field for PowerRange target");
            serializedMessage.Clear();

            powerTarget.ConcreteTarget = new PowerRangeTarget(67, 80, true, powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerRange %FTP target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for PowerRange %FTP target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerRange %FTP target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for PowerRange %FTP target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid PowerRange %FTP target FIT serialization");
            Assert.AreEqual(67, messageField.GetUInt32(), "Invalid lower value in field for PowerRange %FTP target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid PowerRange %FTP target FIT serialization");
            Assert.AreEqual(80, messageField.GetUInt32(), "Invalid upper value in field for PowerRange %FTP target");
            serializedMessage.Clear();

            // Power Garmin zone
            powerTarget.ConcreteTarget = new PowerZoneGTCTarget(1, powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerGTC target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for PowerGTC target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerGTC target FIT serialization");
            Assert.AreEqual(1, messageField.GetUInt32(), "Invalid zone value in field for PowerGTC target");
            serializedMessage.Clear();

            powerTarget.ConcreteTarget = new PowerZoneGTCTarget(3, powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerGTC target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerGTC target FIT serialization");
            Assert.AreEqual(3, messageField.GetUInt32(), "Invalid zone value in field for PowerGTC target");
            serializedMessage.Clear();

            // Power ST zone
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[4];
            Options.Instance.ExportSportTracksPowerAsPercentFTP = false;
            powerTarget.ConcreteTarget = new PowerZoneSTTarget(logbook.PowerZones[0].Zones[1], powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for PowerST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerST target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for PowerST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid PowerST target FIT serialization");
            Assert.AreEqual(1150, messageField.GetUInt32(), "Invalid lower value in field for PowerST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid PowerSTFTP target FIT serialization");
            Assert.AreEqual(1200, messageField.GetUInt32(), "Invalid upper value in field for PowerST target");
            serializedMessage.Clear();

            powerTarget.ConcreteTarget = new PowerZoneSTTarget(logbook.PowerZones[0].Zones[3], powerTarget);
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerST target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for PowerST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerST target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for PowerST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid PowerST target FIT serialization");
            Assert.AreEqual(1300, messageField.GetUInt32(), "Invalid lower value in field for PowerST target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid PowerSTFTP target FIT serialization");
            Assert.AreEqual(1400, messageField.GetUInt32(), "Invalid upper value in field for PowerST target");
            serializedMessage.Clear();

            Options.Instance.ExportSportTracksPowerAsPercentFTP = true;
            powerTarget.FillFITStepMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetType);
            Assert.IsNotNull(messageField, "Invalid PowerST %FTP target FIT serialization");
            Assert.AreEqual((Byte)FITWorkoutStepTargetTypes.Power, messageField.GetEnum(), "Invalid target type in field for PowerST %FTP target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetValue);
            Assert.IsNotNull(messageField, "Invalid PowerST %FTP target FIT serialization");
            Assert.AreEqual(0, messageField.GetUInt32(), "Invalid zone value in field for PowerST %FTP target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            Assert.IsNotNull(messageField, "Invalid PowerST %FTP target FIT serialization");
            Assert.AreEqual(120, messageField.GetUInt32(), "Invalid lower value in field for PowerST %FTP target");
            messageField = serializedMessage.GetField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            Assert.IsNotNull(messageField, "Invalid PowerST %FTP target FIT serialization");
            Assert.AreEqual(160, messageField.GetUInt32(), "Invalid upper value in field for PowerST %FTP target");
            serializedMessage.Clear();

            // Make sure to reset options to previous values
            Options.Instance.ExportSportTracksHeartRateAsPercentMax = exportHRAsMax;
            Options.Instance.ExportSportTracksPowerAsPercentFTP = exportPowerAsFTP;
        }
        public void TestFITDeserialization()
        {
            ILogbook logbook = PluginMain.GetApplication().Logbook;
            Workout placeholderWorkout = null;
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.Workout);
            FITMessageField nameField = new FITMessageField((Byte)FITWorkoutFieldIds.WorkoutName);
            FITMessageField numStepsField = new FITMessageField((Byte)FITWorkoutFieldIds.NumSteps);
            FITMessageField categoryField = new FITMessageField((Byte)FITWorkoutFieldIds.SportType);

            try
            {
                placeholderWorkout = WorkoutImporter.ImportWorkoutFromMessage(serializedMessage, logbook.ActivityCategories[0]);
                Assert.Fail("Workout without name was FIT deserialized");
            }
            catch (FITParserException)
            {
            }

            nameField.SetString(String.Empty);
            serializedMessage.AddField(nameField);

            try
            {
                placeholderWorkout = WorkoutImporter.ImportWorkoutFromMessage(serializedMessage, logbook.ActivityCategories[0]);
                Assert.Fail("Workout without step count was FIT deserialized");
            }
            catch (FITParserException)
            {
            }

            numStepsField.SetUInt16(0);
            serializedMessage.AddField(numStepsField);

            // Name
            nameField.SetString("TestName");
            placeholderWorkout = WorkoutImporter.ImportWorkoutFromMessage(serializedMessage, logbook.ActivityCategories[0]);
            Assert.AreEqual("TestName", placeholderWorkout.Name, "Workout name not properly FIT deserialized");
            GarminWorkoutManager.Instance.RemoveWorkout(placeholderWorkout);

            nameField.SetString("Workout2");
            placeholderWorkout = WorkoutImporter.ImportWorkoutFromMessage(serializedMessage, logbook.ActivityCategories[0]);
            Assert.AreEqual("Workout2", placeholderWorkout.Name, "Workout name not properly FIT deserialized");
            GarminWorkoutManager.Instance.RemoveWorkout(placeholderWorkout);

            // No need to test category, we ignore them in the application at this time (TODO)

            // Number of steps is tested in the steps list test since it requires multiple messages

            // Do not test steps deserialization here, it has it's own test
        }
        public void TestFITDeserialization()
        {
            Workout placeholderWorkout = new Workout("Test", PluginMain.GetApplication().Logbook.ActivityCategories[0]);
            RegularStep placeholderStep = new RegularStep(placeholderWorkout);
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.WorkoutStep);
            FITMessageField typeField = new FITMessageField((Byte)FITWorkoutStepFieldIds.TargetType);
            FITMessageField valueField = new FITMessageField((Byte)FITWorkoutStepFieldIds.TargetValue);
            FITMessageField lowRangeField = new FITMessageField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            FITMessageField highRangeField = new FITMessageField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);
            ITarget loadedTarget = null;

            serializedMessage.AddField(typeField);

            // No target
            typeField.SetEnum((Byte)FITWorkoutStepTargetTypes.NoTarget);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(loadedTarget is NullTarget, "No target not properly FIT deserialized without value fields");

            serializedMessage.AddField(valueField);
            serializedMessage.AddField(lowRangeField);
            serializedMessage.AddField(highRangeField);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(loadedTarget is NullTarget, "No target not properly FIT deserialized with unset value fields");

            valueField.SetUInt32(1);
            lowRangeField.SetUInt32(1);
            highRangeField.SetUInt32(1);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(loadedTarget is NullTarget, "No target not properly FIT deserialized with value fields");

            // Cadence targets
            BaseCadenceTarget cadenceTarget;

            // Cadence range
            CadenceRangeTarget cadenceRangeTarget;

            typeField.SetEnum((Byte)FITWorkoutStepTargetTypes.Cadence);
            valueField.SetUInt32(0);
            lowRangeField.SetUInt32(80);
            highRangeField.SetUInt32(90);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "CadenceRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseCadenceTarget, "CadenceRange target wasn't deserialized as proper type");
            cadenceTarget = loadedTarget as BaseCadenceTarget;
            Assert.IsTrue(cadenceTarget.ConcreteTarget is CadenceRangeTarget, "CadenceRange target wasn't deserialized as proper concrete type");
            cadenceRangeTarget = cadenceTarget.ConcreteTarget as CadenceRangeTarget;
            Assert.AreEqual(80, cadenceRangeTarget.MinCadence, "CadenceRange min value wasn't properly deserialized");
            Assert.AreEqual(90, cadenceRangeTarget.MaxCadence, "CadenceRange max value wasn't properly deserialized");

            lowRangeField.SetUInt32(60);
            highRangeField.SetUInt32(120);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "CadenceRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseCadenceTarget, "CadenceRange target wasn't deserialized as proper type");
            cadenceTarget = loadedTarget as BaseCadenceTarget;
            Assert.IsTrue(cadenceTarget.ConcreteTarget is CadenceRangeTarget, "CadenceRange target wasn't deserialized as proper concrete type");
            cadenceRangeTarget = cadenceTarget.ConcreteTarget as CadenceRangeTarget;
            Assert.AreEqual(60, cadenceRangeTarget.MinCadence, "CadenceRange min value wasn't properly deserialized");
            Assert.AreEqual(120, cadenceRangeTarget.MaxCadence, "CadenceRange max value wasn't properly deserialized");

            // Speed targets
            BaseSpeedTarget speedTarget;

            // Speed range
            SpeedRangeTarget speedRangeTarget;
            double speed;

            typeField.SetEnum((Byte)FITWorkoutStepTargetTypes.Speed);
            valueField.SetUInt32(0);
            lowRangeField.SetUInt32(5555);
            highRangeField.SetUInt32(8333);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "SpeedRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseSpeedTarget, "SpeedRange target wasn't deserialized as proper type");
            speedTarget = loadedTarget as BaseSpeedTarget;
            Assert.IsTrue(speedTarget.ConcreteTarget is SpeedRangeTarget, "SpeedRange target wasn't deserialized as proper concrete type");
            speedRangeTarget = speedTarget.ConcreteTarget as SpeedRangeTarget;
            speed = Length.Convert(speedRangeTarget.GetMinSpeedInBaseUnitsPerHour(), speedRangeTarget.BaseUnit, Length.Units.Kilometer);
            Assert.AreEqual(20, speed, 0.01, "SpeedRange min value wasn't properly deserialized");
            speed = Length.Convert(speedRangeTarget.GetMaxSpeedInBaseUnitsPerHour(), speedRangeTarget.BaseUnit, Length.Units.Kilometer);
            Assert.AreEqual(30, speed, 0.01, "SpeedRange max value wasn't properly deserialized");

            lowRangeField.SetUInt32(1788);
            highRangeField.SetUInt32(3576);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "SpeedRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseSpeedTarget, "SpeedRange target wasn't deserialized as proper type");
            speedTarget = loadedTarget as BaseSpeedTarget;
            Assert.IsTrue(speedTarget.ConcreteTarget is SpeedRangeTarget, "SpeedRange target wasn't deserialized as proper concrete type");
            speedRangeTarget = speedTarget.ConcreteTarget as SpeedRangeTarget;
            speed = Length.Convert(speedRangeTarget.GetMinSpeedInBaseUnitsPerHour(), speedRangeTarget.BaseUnit, Length.Units.Mile);
            Assert.AreEqual(4, speed, 0.01, "SpeedRange min value wasn't properly deserialized");
            speed = Length.Convert(speedRangeTarget.GetMaxSpeedInBaseUnitsPerHour(), speedRangeTarget.BaseUnit, Length.Units.Mile);
            Assert.AreEqual(8, speed, 0.01, "SpeedRange max value wasn't properly deserialized");

            // Speed Garmin zone
            SpeedZoneGTCTarget speedGTCTarget;

            valueField.SetUInt32(1);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "SpeedGTC target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseSpeedTarget, "SpeedGTC target wasn't deserialized as proper type");
            speedTarget = loadedTarget as BaseSpeedTarget;
            Assert.IsTrue(speedTarget.ConcreteTarget is SpeedZoneGTCTarget, "SpeedGTC target wasn't deserialized as proper concrete type");
            speedGTCTarget = speedTarget.ConcreteTarget as SpeedZoneGTCTarget;
            Assert.AreEqual(1, speedGTCTarget.Zone, "SpeedGTC zone value wasn't properly deserialized");

            valueField.SetUInt32(3);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "SpeedGTC target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseSpeedTarget, "SpeedGTC target wasn't deserialized as proper type");
            speedTarget = loadedTarget as BaseSpeedTarget;
            Assert.IsTrue(speedTarget.ConcreteTarget is SpeedZoneGTCTarget, "SpeedGTC target wasn't deserialized as proper concrete type");
            speedGTCTarget = speedTarget.ConcreteTarget as SpeedZoneGTCTarget;
            Assert.AreEqual(3, speedGTCTarget.Zone, "SpeedGTC zone value wasn't properly deserialized");

            // Heart rate targets
            BaseHeartRateTarget hrTarget;

            // Heart rate range
            HeartRateRangeTarget hrRangeTarget;

            typeField.SetEnum((Byte)FITWorkoutStepTargetTypes.HeartRate);
            valueField.SetUInt32(0);
            lowRangeField.SetUInt32(230);
            highRangeField.SetUInt32(270);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "HRRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseHeartRateTarget, "HRRange target wasn't deserialized as proper type");
            hrTarget = loadedTarget as BaseHeartRateTarget;
            Assert.IsTrue(hrTarget.ConcreteTarget is HeartRateRangeTarget, "HRRange target wasn't deserialized as proper concrete type");
            hrRangeTarget = hrTarget.ConcreteTarget as HeartRateRangeTarget;
            Assert.AreEqual(false, hrRangeTarget.IsPercentMaxHeartRate, "HRRange %Max wasn't properly deserialized");
            Assert.AreEqual(130, hrRangeTarget.MinHeartRate, "HRRange min value wasn't properly deserialized");
            Assert.AreEqual(170, hrRangeTarget.MaxHeartRate, "HRRange max value wasn't properly deserialized");

            lowRangeField.SetUInt32(50);
            highRangeField.SetUInt32(70);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "HRRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseHeartRateTarget, "HRRange target wasn't deserialized as proper type");
            hrTarget = loadedTarget as BaseHeartRateTarget;
            Assert.IsTrue(hrTarget.ConcreteTarget is HeartRateRangeTarget, "HRRange target wasn't deserialized as proper concrete type");
            hrRangeTarget = hrTarget.ConcreteTarget as HeartRateRangeTarget;
            Assert.AreEqual(true, hrRangeTarget.IsPercentMaxHeartRate, "HRRange %Max wasn't properly deserialized");
            Assert.AreEqual(50, hrRangeTarget.MinHeartRate, "HRRange min value wasn't properly deserialized");
            Assert.AreEqual(70, hrRangeTarget.MaxHeartRate, "HRRange max value wasn't properly deserialized");

            // Heart rate Garmin zone
            HeartRateZoneGTCTarget hrGTCTarget;

            valueField.SetUInt32(2);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "HRGTC target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseHeartRateTarget, "HRGTC target wasn't deserialized as proper type");
            hrTarget = loadedTarget as BaseHeartRateTarget;
            Assert.IsTrue(hrTarget.ConcreteTarget is HeartRateZoneGTCTarget, "HRGTC target wasn't deserialized as proper concrete type");
            hrGTCTarget = hrTarget.ConcreteTarget as HeartRateZoneGTCTarget;
            Assert.AreEqual(2, hrGTCTarget.Zone, "HRGTC zone value wasn't properly deserialized");

            valueField.SetUInt32(4);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "HRGTC target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BaseHeartRateTarget, "HRGTC target wasn't deserialized as proper type");
            hrTarget = loadedTarget as BaseHeartRateTarget;
            Assert.IsTrue(hrTarget.ConcreteTarget is HeartRateZoneGTCTarget, "HRGTC target wasn't deserialized as proper concrete type");
            hrGTCTarget = hrTarget.ConcreteTarget as HeartRateZoneGTCTarget;
            Assert.AreEqual(4, hrGTCTarget.Zone, "HRGTC zone value wasn't properly deserialized");

            // Power targets
            BasePowerTarget powerTarget;

            // Power range
            PowerRangeTarget powerRangeTarget;

            typeField.SetEnum((Byte)FITWorkoutStepTargetTypes.Power);
            valueField.SetUInt32(0);
            lowRangeField.SetUInt32(1150);
            highRangeField.SetUInt32(1200);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "PowerRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BasePowerTarget, "PowerRange target wasn't deserialized as proper type");
            powerTarget = loadedTarget as BasePowerTarget;
            Assert.IsTrue(powerTarget.ConcreteTarget is PowerRangeTarget, "PowerRange target wasn't deserialized as proper concrete type");
            powerRangeTarget = powerTarget.ConcreteTarget as PowerRangeTarget;
            Assert.AreEqual(false, powerRangeTarget.IsPercentFTP, "PowerRange %FTP wasn't properly deserialized");
            Assert.AreEqual(150, powerRangeTarget.MinPower, "PowerRange min value wasn't properly deserialized");
            Assert.AreEqual(200, powerRangeTarget.MaxPower, "PowerRange max value wasn't properly deserialized");

            lowRangeField.SetUInt32(67);
            highRangeField.SetUInt32(200);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "PowerRange target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BasePowerTarget, "PowerRange target wasn't deserialized as proper type");
            powerTarget = loadedTarget as BasePowerTarget;
            Assert.IsTrue(powerTarget.ConcreteTarget is PowerRangeTarget, "PowerRange target wasn't deserialized as proper concrete type");
            powerRangeTarget = powerTarget.ConcreteTarget as PowerRangeTarget;
            Assert.AreEqual(true, powerRangeTarget.IsPercentFTP, "PowerRange %FTP wasn't properly deserialized");
            Assert.AreEqual(67, powerRangeTarget.MinPower, "PowerRange min value wasn't properly deserialized");
            Assert.AreEqual(200, powerRangeTarget.MaxPower, "PowerRange max value wasn't properly deserialized");

            // Power Garmin zone
            PowerZoneGTCTarget powerGTCTarget;

            valueField.SetUInt32(1);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "PowerGTC target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BasePowerTarget, "PowerGTC target wasn't deserialized as proper type");
            powerTarget = loadedTarget as BasePowerTarget;
            Assert.IsTrue(powerTarget.ConcreteTarget is PowerZoneGTCTarget, "PowerGTC target wasn't deserialized as proper concrete type");
            powerGTCTarget = powerTarget.ConcreteTarget as PowerZoneGTCTarget;
            Assert.AreEqual(1, powerGTCTarget.Zone, "PowerGTC zone value wasn't properly deserialized");

            valueField.SetUInt32(7);
            loadedTarget = TargetFactory.Create(serializedMessage, placeholderStep);
            Assert.IsNotNull(loadedTarget, "PowerGTC target wasn't properly deserialized");
            Assert.IsTrue(loadedTarget is BasePowerTarget, "PowerGTC target wasn't deserialized as proper type");
            powerTarget = loadedTarget as BasePowerTarget;
            Assert.IsTrue(powerTarget.ConcreteTarget is PowerZoneGTCTarget, "PowerGTC target wasn't deserialized as proper concrete type");
            powerGTCTarget = powerTarget.ConcreteTarget as PowerZoneGTCTarget;
            Assert.AreEqual(7, powerGTCTarget.Zone, "PowerGTC zone value wasn't properly deserialized");
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField speedZone = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetValue);
            FITMessageField minSpeed = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueLow);
            FITMessageField maxSpeed = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetCustomValueHigh);

            speedZone.SetUInt32(0);
            minSpeed.SetUInt32((UInt32)(MinMetersPerSecond * 1000));
            maxSpeed.SetUInt32((UInt32)(MaxMetersPerSecond * 1000));
        }
 public override void FillFITStepMessage(FITMessage message)
 {
     Debug.Assert(false);
 }
        public static void ExportWorkoutToFIT(IWorkout workout, Stream exportStream, bool updateExportDate)
        {
            MemoryStream dataStream = new MemoryStream();

            // Reserve size for header
            dataStream.Write(new Byte[12], 0, 12);

            // File id message
            FITMessage fileIdMessage = new FITMessage(FITGlobalMessageIds.FileId);
            FITMessageField fileType = new FITMessageField((Byte)FITFileIdFieldsIds.FileType);
            FITMessageField manufacturerId = new FITMessageField((Byte)FITFileIdFieldsIds.ManufacturerId);
            FITMessageField productId = new FITMessageField((Byte)FITFileIdFieldsIds.ProductId);
            FITMessageField serialNumber = new FITMessageField((Byte)FITFileIdFieldsIds.SerialNumber);
            FITMessageField exportDate = new FITMessageField((Byte)FITFileIdFieldsIds.ExportDate);
            FITMessageField number = new FITMessageField((Byte)FITFileIdFieldsIds.Number);

            fileType.SetEnum((Byte)FITFileTypes.Workout);
            manufacturerId.SetUInt16(1);
            productId.SetUInt16(20119);
            serialNumber.SetUInt32z(0);
            exportDate.SetUInt32(workout.CreationTimestamp);
            number.SetUInt16(0xFFFF);

            fileIdMessage.AddField(serialNumber);
            fileIdMessage.AddField(exportDate);
            fileIdMessage.AddField(manufacturerId);
            fileIdMessage.AddField(productId);
            fileIdMessage.AddField(number);
            fileIdMessage.AddField(fileType);
            fileIdMessage.Serialize(dataStream);

            // File creator message
            FITMessage fileCreatorMessage = new FITMessage(FITGlobalMessageIds.FileCreator);
            FITMessageField software = new FITMessageField((Byte)FITFileCreatorFieldsIds.SoftwareVersion);
            FITMessageField hardware = new FITMessageField((Byte)FITFileCreatorFieldsIds.HardwareVersion);

            software.SetUInt16(3605);
            hardware.SetUInt8(0);

            fileCreatorMessage.AddField(software);
            fileCreatorMessage.AddField(hardware);
            fileCreatorMessage.Serialize(dataStream);

            // Write workout
            workout.SerializeToFIT(dataStream);

            // Write FIT header at the start of the stream
            GarminFitnessByteRange headerSize = new GarminFitnessByteRange(12);
            GarminFitnessByteRange protocolVersion = new GarminFitnessByteRange((Byte)((FITConstants.FITProtocolMajorVersion << 4) | FITConstants.FITProtocolMinorVersion));
            GarminFitnessUInt16Range profileVersion = new GarminFitnessUInt16Range((UInt16)((FITConstants.FITProfileMajorVersion * FITConstants.FITProfileMajorVersionMultiplier) + FITConstants.FITProfileMinorVersion));
            GarminFitnessInt32Range dataSize = new GarminFitnessInt32Range(0);

            dataStream.Seek(0, SeekOrigin.Begin);
            dataSize.Value = (int)dataStream.Length - 12;

            headerSize.Serialize(dataStream);
            protocolVersion.Serialize(dataStream);
            profileVersion.Serialize(dataStream);
            dataSize.Serialize(dataStream);
            dataStream.Write(Encoding.UTF8.GetBytes(FITConstants.FITFileDescriptor), 0, 4);

            // Write CRC
            GarminFitnessUInt16Range crc = new GarminFitnessUInt16Range(FITUtils.ComputeStreamCRC(dataStream));
            dataStream.Seek(0, SeekOrigin.End);
            crc.Serialize(dataStream);

            // Write all data to output stream
            exportStream.Write(dataStream.GetBuffer(), 0, (int)dataStream.Length);

            if (updateExportDate)
            {
                workout.LastExportDate = DateTime.Now;
            }
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField durationType = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationType);
            FITMessageField durationValue = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationValue);

            durationType.SetEnum((Byte)FITWorkoutStepDurationTypes.Distance);
            durationValue.SetUInt32((UInt32)GetDistanceInUnits(Length.Units.Centimeter));
        }
        public void TestFITScheduleSerialization()
        {
            Workout placeholderWorkout = new Workout("Test", PluginMain.GetApplication().Logbook.ActivityCategories[0]);
            RegularStep placeholderStep = new RegularStep(placeholderWorkout);
            ILogbook logbook = PluginMain.GetApplication().Logbook;
            bool exportHRAsMax = Options.Instance.ExportSportTracksHeartRateAsPercentMax;
            bool exportPowerAsFTP = Options.Instance.ExportSportTracksPowerAsPercentFTP;
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.Workout);
            FITMessageField messageField;
            DateTime serializedDate;
            DateTime referenceDate = new DateTime(1989, 12, 31);

            // Scheduled dates
            placeholderWorkout.Name = "WorkoutTest5";
            serializedDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 12, 0, 0);
            placeholderWorkout.FillFITMessageForScheduledDate(serializedMessage, serializedDate);
            messageField = serializedMessage.GetField((Byte)FITScheduleFieldIds.ScheduledDate);
            Assert.IsNotNull(messageField, "Schedule date field not serialized for step");
            Assert.AreEqual((UInt32)(serializedDate - referenceDate).TotalSeconds,
                            messageField.GetUInt32(),
                            "Invalid Schedule date FIT serialization");
            serializedMessage.Clear();

            placeholderWorkout.Name = "WorkoutTest6";
            serializedDate.AddDays(1);
            placeholderWorkout.FillFITMessageForScheduledDate(serializedMessage, serializedDate);
            messageField = serializedMessage.GetField((Byte)FITScheduleFieldIds.ScheduledDate);
            Assert.IsNotNull(messageField, "Schedule date field not serialized for step");
            Assert.AreEqual((UInt32)(serializedDate - referenceDate).TotalSeconds,
                            messageField.GetUInt32(),
                            "Invalid Schedule date FIT serialization");
            serializedMessage.Clear();

            // Test specific date with hardcoded result
            placeholderWorkout.Name = "WorkoutTest6b";
            serializedDate = new DateTime(1999, 12, 31, 12, 0, 0);
            placeholderWorkout.FillFITMessageForScheduledDate(serializedMessage, serializedDate);
            messageField = serializedMessage.GetField((Byte)FITScheduleFieldIds.ScheduledDate);
            Assert.IsNotNull(messageField, "Schedule date field not serialized for step");
            Assert.AreEqual(315576000, messageField.GetUInt32(), "Invalid Schedule date FIT serialization");
            serializedMessage.Clear();

            // New Zealand time (UST+12H) is a case where we had problems because the offset changes the day
            String currentZoneName = Time.CurrentTimeZone.standardName;
            placeholderWorkout.Name = "WorkoutTest7";
            bool res = Time.SetTimeZone("New Zealand Standard Time");
            serializedDate = new DateTime(1999, 12, 31, 0, 0, 0);
            placeholderWorkout.FillFITMessageForScheduledDate(serializedMessage, serializedDate);
            res = Time.SetTimeZone(currentZoneName);
            messageField = serializedMessage.GetField((Byte)FITScheduleFieldIds.ScheduledDate);
            Assert.IsNotNull(messageField, "Schedule date field not serialized for step");
            Assert.AreEqual(315576000, messageField.GetUInt32(), "Invalid Schedule date FIT serialization");
            serializedMessage.Clear();
        }
        public void TestFITDeserialization()
        {
            Workout placeholderWorkout = new Workout("Test", PluginMain.GetApplication().Logbook.ActivityCategories[0]);
            RepeatStep placeholderStep = new RepeatStep(placeholderWorkout);
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.WorkoutStep);
            FITMessageField typeField = new FITMessageField((Byte)FITWorkoutStepFieldIds.DurationType);
            FITMessageField valueField = new FITMessageField((Byte)FITWorkoutStepFieldIds.TargetValue);
            IRepeatDuration deserializedDuration;

            serializedMessage.AddField(typeField);
            serializedMessage.AddField(valueField);

            // Repeat count
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatCount);
            valueField.SetUInt32(6);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatCountDuration, "Repeat count duration not properly FIT deserialized");
            RepeatCountDuration repeatDuration = deserializedDuration as RepeatCountDuration;
            Assert.AreEqual(6, repeatDuration.RepetitionCount, "Invalid value deserialized for FIT repeat count duration");

            valueField.SetUInt32(2);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatCountDuration, "Repeat count duration not properly FIT deserialized");
            repeatDuration = deserializedDuration as RepeatCountDuration;
            Assert.AreEqual(2, repeatDuration.RepetitionCount, "Invalid value deserialized for FIT repeat count duration");

            // Repeat until calories
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilCalories);
            valueField.SetUInt32(666);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilCaloriesDuration, "Repeat until calories duration not properly FIT deserialized");
            RepeatUntilCaloriesDuration caloriesDuration = deserializedDuration as RepeatUntilCaloriesDuration;
            Assert.AreEqual(666, caloriesDuration.CaloriesToSpend, "Invalid value deserialized for FIT repeat until calories duration");

            valueField.SetUInt32(150);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilCaloriesDuration, "Repeat until calories duration not properly FIT deserialized");
            caloriesDuration = deserializedDuration as RepeatUntilCaloriesDuration;
            Assert.AreEqual(150, caloriesDuration.CaloriesToSpend, "Invalid value deserialized for FIT repeat until calories duration");

            // Repeat until distance
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilDistance);
            valueField.SetUInt32(100000);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilDistanceDuration, "Repeat until distance duration not properly FIT deserialized");
            RepeatUntilDistanceDuration distanceDuration = deserializedDuration as RepeatUntilDistanceDuration;
            Assert.AreEqual(1,
                            Length.Convert(distanceDuration.GetDistanceInBaseUnit(), distanceDuration.BaseUnit, Length.Units.Kilometer),
                            STCommon.Data.Constants.Delta,
                            "Invalid value deserialized for FIT repeat until distance duration");

            valueField.SetUInt32(160934);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilDistanceDuration, "Repeat until distance duration not properly FIT deserialized");
            distanceDuration = deserializedDuration as RepeatUntilDistanceDuration;
            Assert.AreEqual(1,
                            Length.Convert(distanceDuration.GetDistanceInBaseUnit(), distanceDuration.BaseUnit, Length.Units.Mile),
                            STCommon.Data.Constants.Delta,
                            "Invalid value deserialized for FIT repeat until distance duration");

            // Repeat until HR Above
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilHeartRateGreaterThan);
            valueField.SetUInt32(220);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilHeartRateAboveDuration, "Repeat until HRAbove duration not properly FIT deserialized");
            RepeatUntilHeartRateAboveDuration hrAboveDuration = deserializedDuration as RepeatUntilHeartRateAboveDuration;
            Assert.IsFalse(hrAboveDuration.IsPercentageMaxHeartRate, "Invalid value deserialized for FIT repeat until HRAbove duration");
            Assert.AreEqual(120, hrAboveDuration.MaxHeartRate, "Invalid value deserialized for FIT repeat until HRAbove duration");

            valueField.SetUInt32(80);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilHeartRateAboveDuration, "Repeat until HRAbove duration not properly FIT deserialized");
            hrAboveDuration = deserializedDuration as RepeatUntilHeartRateAboveDuration;
            Assert.IsTrue(hrAboveDuration.IsPercentageMaxHeartRate, "Invalid value deserialized for FIT repeat until HRAbove duration");
            Assert.AreEqual(80, hrAboveDuration.MaxHeartRate, "Invalid value deserialized for FIT repeat until HRAbove duration");

            // Repeat until HR Below
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilHeartRateLessThan);
            valueField.SetUInt32(230);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilHeartRateBelowDuration, "Repeat until HRBelow duration not properly FIT deserialized");
            RepeatUntilHeartRateBelowDuration hrBelowDuration = deserializedDuration as RepeatUntilHeartRateBelowDuration;
            Assert.IsFalse(hrBelowDuration.IsPercentageMaxHeartRate, "Invalid value deserialized for FIT repeat until HRBelow duration");
            Assert.AreEqual(130, hrBelowDuration.MinHeartRate, "Invalid value deserialized for FIT repeat until HRBelow duration");

            valueField.SetUInt32(65);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilHeartRateBelowDuration, "Repeat until HRBelow duration not properly FIT deserialized");
            hrBelowDuration = deserializedDuration as RepeatUntilHeartRateBelowDuration;
            Assert.IsTrue(hrBelowDuration.IsPercentageMaxHeartRate, "Invalid value deserialized for FIT repeat until HRBelow duration");
            Assert.AreEqual(65, hrBelowDuration.MinHeartRate, "Invalid value deserialized for FIT repeat until HRBelow duration");

            // Repeat until Power Above
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilPowerGreaterThan);
            valueField.SetUInt32(1220);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilPowerAboveDuration, "Repeat until PowerAbove duration not properly FIT deserialized");
            RepeatUntilPowerAboveDuration powerAboveDuration = deserializedDuration as RepeatUntilPowerAboveDuration;
            Assert.IsFalse(powerAboveDuration.IsPercentFTP, "Invalid value deserialized for FIT repeat until PowerAbove duration");
            Assert.AreEqual(220, powerAboveDuration.MaxPower, "Invalid value deserialized for FIT repeat until PowerAbove duration");

            valueField.SetUInt32(80);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilPowerAboveDuration, "Repeat until PowerAbove duration not properly FIT deserialized");
            powerAboveDuration = deserializedDuration as RepeatUntilPowerAboveDuration;
            Assert.IsTrue(powerAboveDuration.IsPercentFTP, "Invalid value deserialized for FIT repeat until PowerAbove duration");
            Assert.AreEqual(80, powerAboveDuration.MaxPower, "Invalid value deserialized for FIT repeat until PowerAbove duration");

            // Repeat until Power Below
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilPowerLessThan);
            valueField.SetUInt32(1400);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilPowerBelowDuration, "Repeat until PowerBelow duration not properly FIT deserialized");
            RepeatUntilPowerBelowDuration powerBelowDuration = deserializedDuration as RepeatUntilPowerBelowDuration;
            Assert.IsFalse(powerBelowDuration.IsPercentFTP, "Invalid value deserialized for FIT repeat until PowerBelow duration");
            Assert.AreEqual(400, powerBelowDuration.MinPower, "Invalid value deserialized for FIT repeat until PowerBelow duration");

            valueField.SetUInt32(125);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilPowerBelowDuration, "Repeat until PowerBelow duration not properly FIT deserialized");
            powerBelowDuration = deserializedDuration as RepeatUntilPowerBelowDuration;
            Assert.IsTrue(powerBelowDuration.IsPercentFTP, "Invalid value deserialized for FIT repeat until PowerBelow duration");
            Assert.AreEqual(125, powerBelowDuration.MinPower, "Invalid value deserialized for FIT repeat until PowerBelow duration");

            // Repeat until time
            typeField.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilTime);
            valueField.SetUInt32(60000);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilTimeDuration, "Repeat until time duration not properly FIT deserialized");
            RepeatUntilTimeDuration timeDuration = deserializedDuration as RepeatUntilTimeDuration;
            Assert.AreEqual(60, timeDuration.TimeInSeconds, "Invalid value deserialized for FIT repeat until time duration");

            valueField.SetUInt32(600000);
            deserializedDuration = DurationFactory.Create(serializedMessage, placeholderStep);
            Assert.IsTrue(deserializedDuration is RepeatUntilTimeDuration, "Repeat until time duration not properly FIT deserialized");
            timeDuration = deserializedDuration as RepeatUntilTimeDuration;
            Assert.AreEqual(600, timeDuration.TimeInSeconds, "Invalid value deserialized for FIT repeat until time duration");
        }
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField durationType = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationType);
            FITMessageField repeatPower = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.TargetValue);

            durationType.SetEnum((Byte)FITWorkoutStepDurationTypes.RepeatUntilHeartRateGreaterThan);

            if (IsPercentageMaxHeartRate)
            {
                repeatPower.SetUInt32((UInt32)MaxHeartRate);
            }
            else
            {
                repeatPower.SetUInt32((UInt32)MaxHeartRate + 100);
            }
        }
        public void TestFITSerialization()
        {
            Workout placeholderWorkout = new Workout("Test", PluginMain.GetApplication().Logbook.ActivityCategories[0]);
            RegularStep placeholderStep = new RegularStep(placeholderWorkout);
            ILogbook logbook = PluginMain.GetApplication().Logbook;
            bool exportHRAsMax = Options.Instance.ExportSportTracksHeartRateAsPercentMax;
            bool exportPowerAsFTP = Options.Instance.ExportSportTracksPowerAsPercentFTP;
            FITMessage serializedMessage = new FITMessage(FITGlobalMessageIds.Workout);
            FITMessageField messageField;

            // Name
            placeholderWorkout.Name = "WorkoutTest1";
            placeholderWorkout.FillFITMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutFieldIds.WorkoutName);
            Assert.IsNotNull(messageField, "Workout name field not serialized for step");
            Assert.AreEqual("WorkoutTest1", messageField.GetString(), "Invalid workout name FIT serialization");
            serializedMessage.Clear();

            placeholderWorkout.Name = "WorkoutTest2";
            placeholderWorkout.FillFITMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutFieldIds.WorkoutName);
            Assert.IsNotNull(messageField, "Workout name field not serialized for step");
            Assert.AreEqual("WorkoutTest2", messageField.GetString(), "Invalid workout name FIT serialization");
            serializedMessage.Clear();

            // Category/Sport
            placeholderWorkout.Name = "WorkoutTest3";
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[5];
            placeholderWorkout.FillFITMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutFieldIds.SportType);
            Assert.IsNotNull(messageField, "Workout sport field not serialized for step");
            Assert.AreEqual(FITSports.Cycling, (FITSports)messageField.GetEnum(), "Invalid workout sport FIT serialization");
            serializedMessage.Clear();

            placeholderWorkout.Name = "WorkoutTest4";
            placeholderWorkout.Category = logbook.ActivityCategories[0].SubCategories[6];
            placeholderWorkout.FillFITMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutFieldIds.SportType);
            Assert.IsNotNull(messageField, "Workout sport field not serialized for step");
            Assert.AreEqual(FITSports.Running, (FITSports)messageField.GetEnum(), "Invalid workout sport FIT serialization");
            serializedMessage.Clear();

            // Number of steps
            placeholderWorkout.Name = "WorkoutTest5";
            placeholderWorkout.FillFITMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutFieldIds.NumSteps);
            Assert.IsNotNull(messageField, "Workout sport field not serialized for step");
            Assert.AreEqual(1, messageField.GetUInt16(), "Invalid workout step count FIT serialization");
            serializedMessage.Clear();

            placeholderWorkout.Name = "WorkoutTest6";
            placeholderWorkout.Steps.AddStepToRoot(new RepeatStep(placeholderWorkout));
            placeholderWorkout.FillFITMessage(serializedMessage);
            messageField = serializedMessage.GetField((Byte)FITWorkoutFieldIds.NumSteps);
            Assert.IsNotNull(messageField, "Workout sport field not serialized for step");
            Assert.AreEqual(3, messageField.GetUInt16(), "Invalid workout step count FIT serialization");
            serializedMessage.Clear();

            // Do not test steps serialization here, it has it's own test
        }
Beispiel #23
0
 public abstract void FillFITStepMessage(FITMessage message);
        public override void FillFITStepMessage(FITMessage message)
        {
            FITMessageField durationType = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationType);
            FITMessageField durationValue = message.GetExistingOrAddField((Byte)FITWorkoutStepFieldIds.DurationValue);

            durationType.SetEnum((Byte)FITWorkoutStepDurationTypes.PowerLessThan);

            if (IsPercentFTP)
            {
                durationValue.SetUInt32((UInt32)MinPower);
            }
            else
            {
                durationValue.SetUInt32((UInt32)MinPower + 1000);
            }
        }
Beispiel #25
0
 public abstract void DeserializeFromFIT(FITMessage workoutMessage);
 public static Workout ImportWorkoutFromMessage(FITMessage workoutMessage)
 {
     return ImportWorkoutFromMessage(workoutMessage, null);
 }
Beispiel #27
0
        public virtual void FillFITMessageForScheduledDate(FITMessage scheduleMessage, DateTime scheduledDate)
        {
            FITMessageField workoutManufacturer = new FITMessageField((Byte)FITScheduleFieldIds.WorkoutManufacturer);
            FITMessageField workoutProduct = new FITMessageField((Byte)FITScheduleFieldIds.WorkoutProduct);
            FITMessageField workoutSN = new FITMessageField((Byte)FITScheduleFieldIds.WorkoutSN);
            FITMessageField scheduleType = new FITMessageField((Byte)FITScheduleFieldIds.ScheduleType);
            FITMessageField workoutCompleted = new FITMessageField((Byte)FITScheduleFieldIds.WorkoutCompleted);
            FITMessageField workoutId = new FITMessageField((Byte)FITScheduleFieldIds.WorkoutId);
            FITMessageField scheduledField = new FITMessageField((Byte)FITScheduleFieldIds.ScheduledDate);
            DateTime midDaySchedule = new DateTime(scheduledDate.Date.Year, scheduledDate.Date.Month, scheduledDate.Day, 12, 0, 0);
            TimeSpan timeSinceReference = midDaySchedule - new DateTime(1989, 12, 31);

            // Hardcoded fields from the schedule file
            workoutManufacturer.SetUInt16(1);           // Always 1
            workoutProduct.SetUInt16(20119);            // Always 20119
            workoutSN.SetUInt32z(0);                    // Invalid
            workoutCompleted.SetEnum((Byte)0xFF);       // Invalid/Not completed

            // Real data
            scheduleType.SetEnum((Byte)FITScheduleType.Workout);
            workoutId.SetUInt32(CreationTimestamp);

            scheduleMessage.AddField(workoutSN);
            scheduleMessage.AddField(workoutId);
            scheduleMessage.AddField(scheduledField);
            scheduleMessage.AddField(workoutManufacturer);
            scheduleMessage.AddField(workoutProduct);
            scheduleMessage.AddField(workoutCompleted);
            scheduleMessage.AddField(scheduleType);

            scheduledField.SetUInt32((UInt32)timeSinceReference.TotalSeconds);
        }
        public static Workout ImportWorkoutFromMessage(FITMessage workoutMessage, IActivityCategory category)
        {
            // Peek name
            FITMessageField nameField = workoutMessage.GetField((Byte)FITWorkoutFieldIds.WorkoutName);

            if (nameField != null)
            {
                GarminFitnessView pluginView = PluginMain.GetApplication().ActiveView as GarminFitnessView;
                String workoutName = nameField.GetString();

                if (category == null && pluginView != null)
                {
                    GarminWorkoutControl workoutControl = pluginView.GetCurrentView() as GarminWorkoutControl;

                    if (workoutControl != null)
                    {
                        workoutControl.GetNewWorkoutNameAndCategory(ref workoutName, ref category);
                    }
                }

                return GarminWorkoutManager.Instance.CreateWorkout(workoutName, workoutMessage, category);
            }
            else
            {
                throw new FITParserException("No name for workout");
            }
        }
Beispiel #29
0
        public virtual void SerializeToFITSchedule(Stream stream, bool serializeDefiniton)
        {
            Debug.Assert(GetSplitPartsCount() == 1);

            FITMessage scheduleMessage = new FITMessage(FITGlobalMessageIds.WorkoutSchedules);

            foreach (DateTime scheduledDate in ScheduledDates)
            {
                FillFITMessageForScheduledDate(scheduleMessage, scheduledDate);

                scheduleMessage.Serialize(stream, serializeDefiniton);
                serializeDefiniton = false;
            }
        }
        public static DateTime ImportWorkoutScheduleMessage(FITMessage scheduleMessage)
        {
            // Make sure we have a workout file
            FITMessageField scheduledDate = scheduleMessage.GetField((Byte)FITScheduleFieldIds.ScheduledDate);
            UInt32 secondsSinceReference = scheduledDate.GetUInt32();

            return new DateTime(1989, 12, 31) + new TimeSpan(0, 0, (int)secondsSinceReference);
        }