Skip to main content
zerologs
Back to Learn Hub
Telemetry export

Telemetry Export for Video Overlays

How to turn your Zero's diagnostic logs into a CSV or GPX you can drag into Telemetry Overlay, RaceRender, Dashware, Insta360 Studio, Garmin VIRB Edit, and friends.

Last updated: July 30, 2026

TL;DR

No GPS recording? Use CSV (no GPS) and sync it to your video by aligning t_seconds = 0 to a known frame. Have GPS from your phone or Garmin? Use GPX merge, which attaches Zero metrics as <extensions> to your trackpoints.

Densification (resampling to 1 Hz) is optional and off by default. Read the densification section below before turning it on.

Which format should I use?

Quick decision tree based on what you have and what you want.

CSV: fast, no GPS needed

Pure telemetry: timestamp + speed + SOC + voltage + temperatures + everything else, one row per sample.

Use CSV when:
  • You don't have a separate GPS recording
  • You only need data gauges (speed, kW, temps), not a map
  • Your editor is Telemetry Overlay, RaceRender, or Dashware
  • You're syncing manually to a video (drag to position in timeline)
How sync works:
  1. Identify a known moment in your video (engine start, throttle wide-open, etc.)
  2. Find the same moment in the CSV (e.g. speed_kmh jumps from 0 to non-zero)
  3. Note the t_seconds at that moment
  4. In your video editor, align the data clip so that the moment lands on the same video frame
One sample a minute, so align on the clock

Zero logs telemetry once every 60 seconds (measured here), so “the frame where speed leaves zero” is only ever known to within about half a minute. Use the absolute timestamp_iso against your camera's clock for the offset, and treat the data-feature method above as a sanity check rather than the sync itself.

GPX: your GPS + Zero metrics

Your GPS track from a phone app or Garmin, enriched per-trackpoint with Zero telemetry as XML extensions.

Use GPX merge when:
  • You recorded GPS with a phone app (Strava, OsmAnd, Wahoo, Gaia GPS, etc.)
  • You have a Garmin Edge / Forerunner / VIRB recording the ride
  • You want a map overlay in the video (not just gauges)
  • Your editor is Insta360 Studio, Garmin VIRB Edit, or Telemetry Overlay
Why we don't generate GPX without your GPS

Zero motorcycles don't record GPS coordinates in their diagnostic logs. A GPX without lat/lon is invalid. Some tools fake coordinates from speed integration. We don't, because the result lies about where you were.

Compatibility with video tools

Which exports work with which editors. = works, ~ = partial / manual mapping needed, — = not supported.

ToolCSVGPXNeeds GPSNotes
Telemetry OverlaynoMost flexible. Reads CSV without GPS, GPX with extensions, gauges + graphs + maps.
RaceRendernoDrag CSV in, map columns to gauges. GPS optional.
Dashware~noCSV-first; you map columns manually to gauges. GPS not required.
GoPro Quik (native)——yesReads only GPMF embedded by GoPro hardware. Use Telemetry Overlay alongside it.
DJI native overlay——yesLocked to DJI SRT/DAT. Use Telemetry Overlay for Zero data on DJI footage.
Insta360 Studio—yesGPX with GPS for path overlay. Use the GPX merge mode.
Garmin VIRB Edit—yesGPX or FIT. Reads Garmin TrackPointExtension (atemp emitted by us).
RaceChronoyesLap timer + overlay. Needs GPS to be useful.
Harry's LapTimeryesLap analysis app. CSV import for non-GPS data + paired GPX.
TrackAddictyesLap timer; CSV import works but GPS-centric.

For best results across most editors we recommend Telemetry Overlay . It accepts both CSV and GPX, supports the Zero zero: namespace via custom field mappings, and ships per-frame interpolation.

Gen2 vs Gen3: why Gen3 may need two files

How telemetry coverage differs by generation, and what we do about it.

Three different things are called a “generation” here

This page is about the log format: which binary layout the file uses, and therefore which channels the export can possibly contain. That is not the same as the motorcycle's product generation, and neither is the same as the number our parser stores.

  • Product generation: what Zero built, per the owner manuals. FX (2014–2025) and FXS (2016–2021) are generation 1 on the XMX platform. The FXE (2022–2025) is generation 2 on XMX · Cypher II, as are the S, SR, DS and DSR through 2023. Generation 3 (Cypher III) is the SR/F and SR/S from 2020, the DSR/X from 2023, the S, SR, DS and DSR from 2024, and the 2026 LS1, XE and XB.
  • Log format: the layout on disk. This is what the cards and tables below describe.
  • Routing generation: the log_generation our parser stores, which is a statement about which decoder ran, not about the machine.

The FXE is where all three collide, and all three are true at once: it is a Cypher II product, our router flags it Gen3, and its files decode through the 538XX path, a Gen2-lineage record format. Gen2 vs Gen3: Log Differences works through the routing rule in full.

There are two known gaps. A 2024-or-later S, SR, DS or DSR is a generation-3 FST motorcycle by the manuals, but the parser's Gen2-only veto list is keyed on the bare model name with no year term, so such a bike is routed down the Gen2 path on its name alone. The 2026 LS1, XE and XB have no route at all. We hold no log from any of those bikes, so we do not know what their files contain. Do not read the tables below as covering them.

Gen1 / Gen2 format

S, SR, DS and DSR through 2023; FX 2014–2025; FXS 2016–2021. The FX and FXS are product generation 1, but they write the same MBB\0 format as the Gen2 street bikes, which is why one card covers both.

One log file is enough for motor and pack data: the MBB log carries both, because the BMS publishes onto the bike's CAN bus and the MBB logs what it hears. Per-cell voltages are the exception: those live only in the BMS dump.

Sample rate: one telemetry row per 60 seconds. Measured over every 0x2c “Riding” record in our 352-file corpus: 152,427 intervals, of which 138,084 are exactly 60 s, and not one is 2 s or shorter. That is about 0.017 Hz. See what a one-minute cadence means for a video overlay before you plan one.

Gen3: two flavours

Our router sends two quite different families down the Gen3 path, and they behave nothing alike:

  • FXE: a Cypher II motorcycle on the XMX platform whose MBB firmware writes the 538XX length-prefixed variant. We route it as Gen3 and decode it with the 538XX decoder, which reads a Gen2-lineage record. Its MBB log is rich: RPM, pack voltage, pack current, SOC, odometer, and pack/motor/controller/ambient temperatures. Road speed is the one channel it does not carry. The speed we used to publish for these bikes was derived from RPM through a fitted constant, and that fit has been withdrawn. Cell voltages and the power limits are not in this record either.
  • SR/F · SR/S · DSR/X (FST, Cypher III): the native Gen3 ring buffer. The MBB log carries bike state, its temperatures and, on a small fraction of records, the discharge/charge power limits; motor RPM and motor current only while the high-voltage bus is live; and road speed and the odometer only in its rarer 0x52/0x53 records, the odometer only on newer firmware. (The field once decoded as an odometer in the common record turned out to be a 12 V accessory rail.) The BMS log carries voltage, current, SOC and cell data.

If you upload both MBB and BMS for an FST bike, we automatically merge them by timestamp (within 30 seconds). The Export dialog shows "Gen3 MBB+BMS merged" and the CSV filename includes _merged.

Hardware quirk for FST bikes

On SR/F · SR/S · DSR/X no MBB record carries a throttle field, and the common telemetry record has no road speed: speed, and on newer firmware the odometer, arrive only on the rarer 0x52/0x53 records, and RPM only while the high-voltage bus is live. So speed and RPM reach the CSV sparsely, and throttle never, whatever you upload. (This box said until October 2026 that there was no speed or RPM field at all; the decoder has read both since 2026-09-07. The record's pack-voltage and pack-current words are decoded too, but they go to zero with the bus, so the BMS log stays our source for those channels.) FXE bikes are unaffected.

Gen3 ring buffer caveat

Gen3 logs use a ring buffer that a parked bike fills in about 3 days: the MBB wakes once an hour and writes ~31 entries per cycle, so roughly 78 hibernation cycles fill it. If you're trying to export a ride from two weeks ago and the data isn't there, the buffer overwrote it. See Why Your Gen3 Log Shows No Rides for the full story.

What you'll get per generation, in practice

FieldGen1 / Gen2FXE (538XX)SR/F · SR/S · DSR/X
speed_kmhderivedno~
motor_rpm✓✓~
throttle_pctnonono
pack_voltage_v✓✓✓ BMS
pack_current_a✓✓✓ BMS
power_kw✓✓✓ BMS
soc_pct✓✓✓ BMS
battery_temp_c✓✓~
motor_temp_c✓✓~
controller_temp_c✓✓~
odometer_km✓✓~
cell_voltage_min/max/deltaBMS fileBMS file✓ BMS
bike_statederived✓✓
discharge_limit_kw / charge_limit_kw—no~

The FXE is routed as Gen3, but it is a Cypher II motorcycle on the older XMX platform, so its MBB log keeps the rich Gen2-style motor telemetry, road speed excepted. On the FST bikes (SR/F, SR/S, DSR/X) the MBB writes RPM only while the high-voltage bus is live, road speed only in its rarer 0x52/0x53 records, and no cell voltages at all, while the BMS writes no motor channel. Always upload both MBB and BMS for FST bikes to get the full picture.

“sparse” means present on a fraction of samples. On the FST bikes that applies to speed, RPM and the odometer, because they come from the MBB's rarer records or only while the bus is live; densification helps fill gaps inside an active ride. Until October 2026 those three were marked “no (hw)” here, as if the bytes were absent from the binary, which they are not. The two FST power limits are the sparsest of all: they are written as text lines, not on every telemetry record. On a 2021 SR/F dump 190 of 2,238 records carry them; on our DSR/X dump, 94 of 2,535.

“derived” means we compute it rather than read it. speed_kmh on a Gen1/Gen2 bike is motor_rpm × a per-model constant, measured from riders' odometers and checked against Zero's gearing (see riding data). Until 25 October 2026 those constants were about 30% high on the FX family and the S/SR, so a CSV exported before then reads that much too fast. No Gen1 or Gen2 log contains a road-speed field (a Gen3 main board writes one only in its rarer 0x52/0x53 records). Treat it as an estimate, not a measurement. bike_state on Gen1/Gen2 is inferred from the event text, so it only ever takes the values RUN, CHRG, STOP and WAKE; the eight-state vocabulary in the column reference below belongs to Gen3.

“BMS file” means the channel is not in the MBB record at all. Per-cell voltages come from the bike's BMS dump, which is a separate file and a separate report. The automatic MBB+BMS merge only runs on reports our parser classified generation 3, so on a Gen1/Gen2 bike you export the BMS report on its own to get cell data.

throttle_pct ships as a CSV column and a GPX extension, but nothing in our code ever writes it: no parser path, in any generation, assigns a throttle value. The column is always empty. It is listed here only so you do not go hunting for it.

Densification: when (and why not) to use it

Native sample rates vs resampling to a regular grid.

A Zero writes one telemetry record per minute while riding. We measured exactly 60 s on 138,084 of the 152,427 intervals in our corpus, and no interval anywhere was shorter than 10 s. A 30-minute ride is therefore about 30 telemetry rows, not thousands. (On a real 24-minute Gen2 ride: 25 telemetry rows, inside 40 CSV rows total; the extra rows are event lines whose telemetry columns are empty.)

Native (default): we hand you exactly what your bike recorded. Timestamps are irregular (whatever the firmware decided to log), but every value is real.

Resampled to 1 Hz: we lay down a regular one-sample-per-second grid and fill it with linear interpolation between your real samples for numeric values (speed, voltage, temps) and forward-fill for state values (RUN/STOP/CHRG, charging on/off).

We never interpolate across gaps larger than 60 seconds. If your bike sat in HIB (deep sleep) for 10 minutes, the CSV will show that gap as a gap, not as fabricated data.

Put those two facts together before you turn densification on: at a 60-second cadence, resampling to 1 Hz manufactures 59 interpolated rows for every real one. That is fine for a smooth gauge needle and useless as evidence. It also sits right on the interpolation limit: a sample pair 61 s apart is left as a gap, so a densified export is not guaranteed to be continuous.

Turn densification ON when

  • You want smooth needle/gauge animations in the overlay
  • Your editor expects regular-rate input (some legacy tools)
  • You're building a chart that doesn't tolerate timestamp gaps
  • You're using RaceRender's generic data import (prefers regular cadence)

Leave densification OFF when

  • You want forensic accuracy (incident analysis, warranty disputes)
  • Your tool natively interpolates (Telemetry Overlay does)
  • You're feeding the CSV to other software for analysis (pandas, R)
  • You'd rather see real gaps than synthetic smoothness

Step-by-step: CSV in Telemetry Overlay

A worked example with the most common workflow.

  1. 1
    Open your report

    Pick the log file on your reports page. If it's a Gen3 ride, make sure both the MBB and BMS files for that day are uploaded. They'll auto-merge.

  2. 2
    (Optional) Click into the specific ride session

    If you only want a fragment, click the ride session card. The Export dialog will pre-fill that ride's start/end as a custom range, so you don't have to type it.

  3. 3
    Open Export → Telemetry (CSV / GPX)

    Top right of the report page. Stay on the CSV tab. Leave "Resample to 1 Hz" off unless your editor needs it. Click Download CSV.

  4. 4
    Drop the CSV into Telemetry Overlay

    Drag the file onto the Telemetry Overlay window. It auto-detects ISO timestamps. Pick the columns you want as gauges: soc_pct, pack_voltage_v and power_kw are good defaults on any bike. Add speed_kmh on a Gen1/Gen2 machine. On an FXE that column is empty, and on an FST bike it is sparse.

  5. 5
    Sync to your video

    Line the two clocks up: read timestamp_iso on the first row and compare it with your camera's creation time, then set the data offset from the difference. Because the bike samples only once a minute, an identifiable moment in the footage (first hard pull; arriving home) is worth a sanity check but will not get you closer than about half a minute on its own. Render.

CSV column reference

All columns in the exported CSV. Empty cell = the field wasn't recorded for that timestamp.

ColumnUnitSourceWhat it tells you
timestamp_isoISO 8601bothAbsolute time of the sample (with timezone)
t_secondssderivedSeconds from the first row, used as your sync handle
speed_kmhkm/hderived (Gen1 / Gen2); FST MBBNot a recorded channel on Gen1 / Gen2: there it is motor_rpm × a per-model constant. Empty on the FXE. On the FST bikes it is the road speed the MBB writes in its rarer 0x52/0x53 records, so it is sparse
throttle_pct%—Always empty. The column exists, but no parser path in any generation ever writes it
motor_rpmRPMMBBMotor revolutions per minute. On the FST bikes, only while the high-voltage bus is live
torque_nmNmderived; FST MBBCalculated as power × 9549 / RPM; on the FST bikes read from the MBB's motor record (shaft torque) instead
pack_voltage_vVBMS / Gen1-Gen2 MBB / FXE MBBTotal pack voltage
pack_current_aABMS / Gen1-Gen2 MBB / FXE MBBPositive when discharging, negative when charging. Regen therefore reads negative too, which is how you spot it. (Measured over eight real logs from five bikes: of 2,404 charging samples, 2,397 are negative; of 6,149 samples taken above 500 motor RPM, 4,726 are positive and the rest are regen)
power_kwkWderivedV × A / 1000, so it follows the current sign and reads negative on regen. One exception: the native Gen3 BMS decoder emits it as a magnitude, always positive
soc_pct%BMS / Gen2 MBBState of charge
motor_temp_c°CMBBMotor temperature
battery_temp_c°CGen2 BMS / MBBBattery pack temperature. On the FST bikes it is the MBB's highest cell temperature; the Gen3 BMS record carries none
controller_temp_c°CMBBMotor controller temperature
ambient_temp_c°CbothAmbient temperature (where available)
odometer_kmkmMBBLifetime odometer reading. On the FST bikes (SR/F, SR/S, DSR/X) only from the MBB's rarer 0x52/0x53 records and only on newer firmware, so it is sparse or empty
discharge_limit_kwkWFST MBB onlyBMS-imposed power ceiling (low SOC, hot pack, etc.). Read from text lines, so present on only a few percent of records, and never on an FXE, whose decoder does not produce it
charge_limit_kwkWFST MBB onlyMaximum allowed charge rate at this moment. Same sparsity and the same FXE caveat as above
cell_voltage_min_vVBMSLowest individual cell voltage. BMS log only: no MBB log of any generation carries per-cell data
cell_voltage_max_vVBMSHighest individual cell voltage
cell_voltage_delta_vVBMSSpread between min and max, a health indicator
cell_voltage_ocv_min_vVBMSOpen-circuit (unloaded) voltage of the lowest cell
bike_state—Gen3 MBB; derived on Gen1 / Gen2Gen3 records RUN / STOP / CHRG / HIB / WAIT / WAKE / STRT / PWSU. On Gen1/Gen2 we infer it from the event text and it only ever reads RUN, CHRG, STOP or WAKE
is_charging0/1BMS (any gen) / Gen3 MBBCharging session active. Empty on a Gen1/Gen2 MBB-only export. That branch never writes the flag, so use the BMS log if you need it
is_under_load0/1Gen3 BMSBMS sees significant current draw

GPX extension reference

The enriched GPX is a valid GPX 1.1 file (your editor will load it). Extra Zero metrics live in two namespaces inside <extensions> blocks:

xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"

Garmin standard. We emit gpxtpx:atemp (ambient temperature) when present. Read by Garmin VIRB Edit, Connect, and most GPS analytics tools.

xmlns:zero="https://zerologs.bike/gpx/1"

Custom Zero namespace for everything specific to electric motorcycles:

<zero:speed_kmh>     <zero:throttle_pct>
<zero:motor_rpm>     <zero:torque_nm>
<zero:voltage_v>     <zero:current_a>
<zero:power_kw>      <zero:soc_pct>
<zero:motor_temp_c>  <zero:battery_temp_c>
<zero:controller_temp_c>
<zero:odometer_km>   <zero:state>

Editors that don't understand the namespace will silently ignore it (per XML rules), so it does no harm.

Two of these carry the caveats from the column reference: zero:throttle_pct is emitted for schema completeness and is always empty, and zero:speed_kmh is our RPM-derived estimate on Gen1/Gen2, sparse on the FST bikes and empty on the FXE.

FAQ

My CSV opens but speeds are stuck at 0 (or the column is empty)

Check which bike it is before you go looking for a second file. On an SR/F, SR/S or DSR/X the speed column is sparse by nature: the MBB writes road speed only in its rarer 0x52/0x53 records, roughly every four minutes on a running bike, and the BMS carries battery data only, so no upload fills it densely. On an FXE its record has RPM but no speed, and the RPM-to-speed fit we used to publish has been withdrawn. On Gen1/Gen2 bikes the speed column is motor_rpm times a per-model constant rather than a recorded value. What a missing MBB log costs you on an FST bike is bike_state, the power limits, the motor channels and that sparse speed.

My GPX export says "no telemetry samples in the time range"

Your GPS track and the Zero log don't overlap in time. Check both clocks. Phone GPS is usually correct; if your bike clock drifted, use the "Time offset (seconds)" field in the GPX merge dialog.

Why is the CSV smaller than I expected?

Because the bike logs far less often than people assume. The telemetry record is written once every 60 seconds on Gen1/Gen2 and on the FXE. Across our whole 352-file corpus, 138,084 of 152,427 intervals are exactly 60 s and none is shorter than 10 s. Native Gen3 is event-driven on top of that (sparse in HIB, denser in RUN). So a 30-minute ride is roughly 30 telemetry rows, inside maybe 30–70 CSV rows in total; the extra rows are event lines whose telemetry columns are blank. Turn on densification if your editor wants a regular 1 Hz grid, but understand that it is inventing 59 rows for every real one.

Can I export FIT for Strava / Garmin Connect?

Yes, use the FIT tab. On its own the file is positionless (Zero logs have no GPS), so Strava shows it like an indoor ride with speed/power/temperature. Attach a GPS track (GPX) in the same tab to get a mapped activity. Regen shows as 0 W because FIT power can't be negative.

Does my GPX file ever leave my browser?

It goes to our backend for processing (we need server-side resources to query telemetry and align by timestamp), but we don't store it. The merged result is streamed back and not persisted.

What does "Match tolerance" do in the GPX merge?

For each GPS trackpoint we look for the nearest Zero telemetry sample. If the closest one is more than this many seconds away, we leave that trackpoint without telemetry rather than attaching wrong data. The field defaults to 5 seconds, which is too tight for a bike that logs once a minute: at a 60-second cadence a 5-second window reaches under a fifth of your trackpoints and the rest come out bare. Raise it to 30, the smallest value that guarantees every trackpoint finds a sample when samples are 60 s apart. The dialog accepts up to 60.

Why do you not draw a fake GPS path from speed?

It would be wrong. Heading isn't logged, so any reconstruction is a straight line, and a straight line lies about your route. We'd rather you bring real GPS.

Why is densification off by default?

Real samples show the viewer what the bike recorded, including gaps, while interpolation invents data that wasn't there. Most overlay tools do their own interpolation at render time anyway.

Ready to overlay your Zero ride?

Open one of your reports and click Export → Telemetry. If you don't have any logs uploaded yet, start there.