Bike-only Zwift .zwo generator with human-readable preview + confirmation loop. - scripts/zwo_builder.py: spec<->.zwo build/validate/parse/render/TSS estimate - scripts/zwo.py: CLI (build, from-template, validate, parse, render, templates) - references/zwo-format.md: condensed offline format reference - assets/example-vo2max.zwo + tests/test_zwo.py (9 tests) - Publishes via hosting skill; files result into Knowledge Wiki Format ground truth: h4l/zwift-workout-file-reference
3.1 KiB
3.1 KiB
Zwift .zwo Format Reference (condensed)
Offline copy of the essentials from
https://github.com/h4l/zwift-workout-file-reference. A .zwo file is XML; there
is no official Zwift schema. This is the subset Zwift reliably accepts.
Document structure
<?xml version="1.0" encoding="UTF-8"?>
<workout_file>
<author>IT Guy</author>
<name>VO2 Max 5x3</name>
<description>5 × 3 min @ 115 % FTP</description>
<sportType>bike</sportType>
<tags><tag name="vo2max"/></tags>
<workout>
<Warmup Duration="600" PowerLow="0.50" PowerHigh="0.75"/>
<IntervalsT Repeat="5" OnDuration="180" OffDuration="180"
OnPower="1.15" OffPower="0.55"/>
<Cooldown Duration="300" PowerLow="0.75" PowerHigh="0.40"/>
</workout>
</workout_file>
Conventions
- Power is a fraction of FTP (
0.50= 50 %,1.15= 115 %).units="power"is the implicit default. - Duration is always in seconds.
<sportType>values seen:bike(61 %),run(37 %),swim,ride. We usebike.- Text cues are children of a step:
<textevent timeoffset="30" message="Push!"/>.
Step elements (child of <workout>)
| Element | Attributes (relevant) | Meaning |
|---|---|---|
Warmup |
Duration, PowerLow, PowerHigh, Cadence |
linear ramp Low→High |
Cooldown |
Duration, PowerLow, PowerHigh, Cadence |
linear ramp High→Low |
SteadyState |
Duration, Power, (PowerLow,PowerHigh), Cadence |
constant target |
SolidState |
Duration, Power |
constant power, no ERG variation |
Ramp |
Duration, PowerLow, PowerHigh |
explicit ramp |
IntervalsT |
Repeat, OnDuration, OffDuration, OnPower, OffPower, Cadence, CadenceResting, OverUnder |
N repeated on/off pairs |
FreeRide |
Duration, FlatRoad |
ERG off, rider's choice |
MaxEffort |
Duration |
all-out, ERG off |
RestDay |
— | placeholder |
textevent |
timeoffset, message, duration |
on-screen cue |
Top-level metadata elements (child of <workout_file>)
activitySaveName, author, author_alias, authorIcon, category,
categoryIndex, description, durationType, entid, ftpOverride,
ftpMaleOverride, ftpFemaleOverride, name, nameImperial, nameMetric,
painIndex, setFtpAtPercentage, ShowCP20, Skippable, sportType,
subcategory, tags, test_details, visibleAfterTime, visibleOutsidePlan,
workout, workoutLength, WorkoutPlan.
Minimal viable set we emit: author, name, description, sportType,
tags, workout.
Importing a .zwo into Zwift
- Copy the file into
~/Documents/Zwift/Workouts/<userID>/(folder name is your numeric Zwift ID), or - Use the Custom Workouts import on zwift.com, or
- Drop it into the Zwift folder sync (Dropbox/OneDrive) if configured.
Then restart Zwift; the workout appears under Custom Workouts.
Gotchas
- Malformed XML or unescaped
&/quotes → Zwift silently ignores the file. - Power outside ~0.20–1.50 FTP is almost always a unit mistake.
- Duplicate/unknown attributes are tolerated but ignored; keep it minimal.