Skip to content

Commit 87eb762

Browse files
Decode cadence from zwo files
1 parent 352c863 commit 87eb762

File tree

5 files changed

+50
-34
lines changed

5 files changed

+50
-34
lines changed

Diff for: Sources/FitWorkoutDecoder/FitWorkoutDecoder.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@ public final class FitWorkoutDecoder: WorkoutDecoding {
6161
switch stepsDistance {
6262
case 0:
6363
let workoutStep = workoutSteps[repeatStep.range.lowerBound]
64-
return WorkoutPart.steady(duration: workoutStep.duration, power: workoutStep.power(for: userFtp))
64+
return WorkoutPart.steady(duration: workoutStep.duration, power: workoutStep.power(for: userFtp),
65+
cadence: nil)
6566
case 1:
6667
let firstWorkoutStep = workoutSteps[repeatStep.range.lowerBound]
6768
let secondWorkoutStep = workoutSteps[repeatStep.range.upperBound]
6869
return WorkoutPart.intervals(repeat: repeatStep.repeatCount,
6970
onDuration: firstWorkoutStep.duration, onPower: firstWorkoutStep.power(for: userFtp),
70-
offDuration: secondWorkoutStep.duration, offPower: secondWorkoutStep.power(for: userFtp))
71+
offDuration: secondWorkoutStep.duration, offPower: secondWorkoutStep.power(for: userFtp),
72+
cadence: nil)
7173
default:
7274
throw FitDecodeError.notSupportedRange
7375
}

Diff for: Sources/WorkoutDecoderBase/WorkoutDecoding.swift

+16-13
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,32 @@ public protocol WorkoutDecoding {
1313
}
1414

1515
public enum WorkoutPart {
16-
case steady(duration: Int, power: Double)
17-
case intervals(repeat: Int, onDuration: Int, onPower: Double, offDuration: Int, offPower: Double)
18-
case ramp(duration: Int, powerLow: Double, powerHigh: Double)
19-
case freeRide(duration: Int)
16+
case steady(duration: Int, power: Double, cadence: String?)
17+
case intervals(repeat: Int, onDuration: Int, onPower: Double, offDuration: Int, offPower: Double, cadence: String?)
18+
case ramp(duration: Int, powerLow: Double, powerHigh: Double, cadence: String?)
19+
case freeRide(duration: Int, cadence: String?)
2020

2121
public func toSegments(startIndex: Int) -> [WorkoutSegment] {
2222
switch self {
23-
case .steady(let duration, let power):
24-
return [WorkoutSegment(duration: duration, index: startIndex, intervalIndex: nil, powerStart: power, powerEnd: nil)]
25-
case .intervals(let repeats, let onDuration, let onPower, let offDuration, let offPower):
23+
case .steady(let duration, let power, let cadence):
24+
return [WorkoutSegment(duration: duration, index: startIndex, intervalIndex: nil,
25+
powerStart: power, powerEnd: nil, cadence: cadence)]
26+
case .intervals(let repeats, let onDuration, let onPower, let offDuration, let offPower, let cadence):
2627
return (0..<repeats).map { index -> [WorkoutSegment] in
2728
[
2829
WorkoutSegment(duration: onDuration, index: startIndex + index * 2,
29-
intervalIndex: index, powerStart: onPower, powerEnd: nil),
30+
intervalIndex: index, powerStart: onPower, powerEnd: nil, cadence: cadence),
3031
WorkoutSegment(duration: offDuration, index: startIndex + index * 2 + 1,
31-
intervalIndex: index, powerStart: offPower, powerEnd: nil),
32+
intervalIndex: index, powerStart: offPower, powerEnd: nil, cadence: cadence)
3233
]
3334
}.flatMap { $0 }
3435

35-
case .ramp(let duration, let powerLow, let powerHigh):
36+
case .ramp(let duration, let powerLow, let powerHigh, let cadence):
3637
return [WorkoutSegment(duration: duration, index: startIndex,
37-
intervalIndex: nil, powerStart: powerLow, powerEnd: powerHigh)]
38-
case .freeRide(let duration):
39-
return [WorkoutSegment(duration: duration, index: startIndex, intervalIndex: nil, powerStart: -1.0, powerEnd: nil)]
38+
intervalIndex: nil, powerStart: powerLow, powerEnd: powerHigh, cadence: cadence)]
39+
case .freeRide(let duration, let cadence):
40+
return [WorkoutSegment(duration: duration, index: startIndex, intervalIndex: nil,
41+
powerStart: -1.0, powerEnd: nil, cadence: cadence)]
4042
}
4143
}
4244
}
@@ -47,6 +49,7 @@ public struct WorkoutSegment: Codable, Equatable {
4749
public let intervalIndex: Int?
4850
public let powerStart: Double // negative power means free ride
4951
public let powerEnd: Double?
52+
public let cadence: String?
5053

5154
public func powerAt(second: Int, for ftp: Double) -> Int {
5255
let power: Double

Diff for: Sources/ZwoWorkoutDecoder/ZwoWorkoutDecoder.swift

+17-6
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@ public final class ZwoWorkoutDecoder: WorkoutDecoding {
2020
let name: String = workout["name"].text ?? "no_name"
2121
let parts = try workout["workout"].element?.childElements.map { part -> WorkoutPart in
2222
let attributes = part.attributes
23+
let cadence = attributes["Cadence"]
2324
switch part.name {
2425
case "SteadyState":
2526
return WorkoutPart.steady(duration: attributes.int("Duration"),
26-
power: Double(attributes["Power"]!)!)
27+
power: attributes.double("Power"),
28+
cadence: cadence)
2729
case "IntervalsT":
2830
return WorkoutPart.intervals(repeat: attributes.int("Repeat"),
2931
onDuration: attributes.int("OnDuration"),
30-
onPower: Double(attributes["OnPower"]!)!,
32+
onPower: attributes.double("OnPower"),
3133
offDuration: attributes.int("OffDuration"),
32-
offPower: Double(attributes["OffPower"]!)!)
34+
offPower: attributes.double("OffPower"),
35+
cadence: cadence)
3336
case "Warmup", "Cooldown", "Ramp":
3437
return WorkoutPart.ramp(duration: attributes.int("Duration"),
35-
powerLow: Double(attributes["PowerLow"]!)!,
36-
powerHigh: Double(attributes["PowerHigh"]!)!)
38+
powerLow: attributes.double("PowerLow"),
39+
powerHigh: attributes.double("PowerHigh"),
40+
cadence: cadence)
3741
case "FreeRide":
38-
return WorkoutPart.freeRide(duration: attributes.int("Duration"))
42+
return WorkoutPart.freeRide(duration: attributes.int("Duration"),
43+
cadence: cadence)
3944
default:
4045
throw WorkoutDecodeError.unknownElement(name: part.name)
4146
}
@@ -87,4 +92,10 @@ private extension Dictionary where Value == String {
8792
Int($0)
8893
} ?? fallback
8994
}
95+
96+
func double(_ key: Key, fallback: Double = 0.0) -> Double {
97+
return self[key].flatMap {
98+
Double($0)
99+
} ?? fallback
100+
}
90101
}

Diff for: Tests/WorkoutDecodersTests/Resources/workout1.zwo

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<Warmup Duration="10" PowerLow="0.5" PowerHigh="0.55">
99
<textevent timeoffset="0" message="Starting message"/>
1010
</Warmup>
11-
<SteadyState Duration="5" Power="0.4"/>
12-
<FreeRide Duration="100"/>
11+
<SteadyState Duration="5" Power="0.4" Cadence="100"/>
12+
<FreeRide Duration="100" Cadence="90"/>
1313
<SteadyState Duration="10" Power="0.5">
1414
<textevent timeoffset="5" message="Should be at offset 120"/>
1515
</SteadyState>

Diff for: Tests/WorkoutDecodersTests/WorkoutDecodersTests.swift

+11-11
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ final class WorkoutDecodersTests: XCTestCase {
1717
let workout = decodedWorkout()
1818

1919
XCTAssertEqual(workout.segments, [
20-
WorkoutSegment(duration: 10, index: 0, intervalIndex: nil, powerStart: 0.5, powerEnd: 0.55),
21-
WorkoutSegment(duration: 5, index: 1, intervalIndex: nil, powerStart: 0.4, powerEnd: nil),
22-
WorkoutSegment(duration: 100, index: 2, intervalIndex: nil, powerStart: -1, powerEnd: nil), // free ride
23-
WorkoutSegment(duration: 10, index: 3, intervalIndex: nil, powerStart: 0.5, powerEnd: nil),
20+
WorkoutSegment(duration: 10, index: 0, intervalIndex: nil, powerStart: 0.5, powerEnd: 0.55, cadence: nil),
21+
WorkoutSegment(duration: 5, index: 1, intervalIndex: nil, powerStart: 0.4, powerEnd: nil, cadence: "100"),
22+
WorkoutSegment(duration: 100, index: 2, intervalIndex: nil, powerStart: -1, powerEnd: nil, cadence: "90"), // free ride
23+
WorkoutSegment(duration: 10, index: 3, intervalIndex: nil, powerStart: 0.5, powerEnd: nil, cadence: nil),
2424
// interval start, repeat 3
25-
WorkoutSegment(duration: 15, index: 4, intervalIndex: 0, powerStart: 1.2, powerEnd: nil),
26-
WorkoutSegment(duration: 5, index: 5, intervalIndex: 0, powerStart: 0.4, powerEnd: nil),
27-
WorkoutSegment(duration: 15, index: 6, intervalIndex: 1, powerStart: 1.2, powerEnd: nil),
28-
WorkoutSegment(duration: 5, index: 7, intervalIndex: 1, powerStart: 0.4, powerEnd: nil),
29-
WorkoutSegment(duration: 15, index: 8, intervalIndex: 2, powerStart: 1.2, powerEnd: nil),
30-
WorkoutSegment(duration: 5, index: 9, intervalIndex: 2, powerStart: 0.4, powerEnd: nil),
25+
WorkoutSegment(duration: 15, index: 4, intervalIndex: 0, powerStart: 1.2, powerEnd: nil, cadence: nil),
26+
WorkoutSegment(duration: 5, index: 5, intervalIndex: 0, powerStart: 0.4, powerEnd: nil, cadence: nil),
27+
WorkoutSegment(duration: 15, index: 6, intervalIndex: 1, powerStart: 1.2, powerEnd: nil, cadence: nil),
28+
WorkoutSegment(duration: 5, index: 7, intervalIndex: 1, powerStart: 0.4, powerEnd: nil, cadence: nil),
29+
WorkoutSegment(duration: 15, index: 8, intervalIndex: 2, powerStart: 1.2, powerEnd: nil, cadence: nil),
30+
WorkoutSegment(duration: 5, index: 9, intervalIndex: 2, powerStart: 0.4, powerEnd: nil, cadence: nil),
3131
// interval end
32-
WorkoutSegment(duration: 5, index: 10, intervalIndex: nil, powerStart: 0.6, powerEnd: nil)
32+
WorkoutSegment(duration: 5, index: 10, intervalIndex: nil, powerStart: 0.6, powerEnd: nil, cadence: nil)
3333
])
3434
}
3535

0 commit comments

Comments
 (0)