Skip to main content
zerologszerologs
Back to Learn Hub
Real extracts, annotated

Anatomy of a Zero log, line by line

Everything on this page is a real line out of a real rider’s Zero: 406 of them, arranged as 19 windows on 10 motorcycles across all 5 decode routes the parser really has. Each window is one thing the motorcycle was doing — being switched on, ridden, charged, parked, going wrong, being switched off — and each line carries the board that wrote it and a badge saying whether the words are the motorcycle’s own (21 lines) or our reading of the bytes (385). That second promise is the one worth reading twice, and it does not fall where the platforms suggest it should: the only lines a motorcycle here stored as words are on the NEWER platform, all 21 of them written by a Gen3 pack, while every one of the 169 Gen2 lines is our reading of a binary record. The honest version of “here is the raw log” is “here is what the bytes said, and here is who is telling you so”.

Last updated: September 4, 2026
~26 min read19 windows · 406 real lines · 5 decode routes · 10 motorcycles

TL;DR

  • Two boards write two files and neither one tells the whole story. The main board sees the motor, the odometer and the switches; the battery management sees the cells, the pack current and the contactor. In the shutdown window below they log the same contactor opening in the same second under two different names, and measure the same voltage independently: 107.997 V from the pack against 107.998 V from the main board, 1 mV apart. Two instruments, not a disagreement.
  • On Gen3 those two boards do not share a clock, and that single fact overturned three conclusions before this page was written. The two Gen3 pairs here run 605 s and 2,697 s apart. Merged on their raw timestamps the boards look as though they never write together — one and three shared seconds. Merged on a reconciled clock they share 68 and 72, and the contactor closes on both boards in the same second.
  • The sharp line between the platforms is riding, and only riding. Across the Gen3 main-board files measured here, motor speed, road speed, odometer and pack current are present in 0 of 9,472 entries — that is why the two platforms are shown as two separate blocks and never as two columns. Everything a Gen3 ride knows about itself is in the battery file, one sample every 60 seconds.
  • A parked Gen3 never stops writing. Its main board declares 3,600 s of sleep and keeps to it to the second; add the 91 s it spends awake and the true period is 3,691 s — 23 entries a day, in a garage, with the key in your pocket. A parked Gen2 wakes its battery management about once a day (86,401 s between blocks here) and its main board writes nothing at all.
  • Sign is the thing everyone gets wrong, so it is stated once and shown: positive is out of the pack. I=-15864mA is a motorcycle charging, not discharging. And the channel alone will not settle it — the Gen3 ride window contains two negative samples the pack itself labels mode: Charge, in the middle of a journey, because regenerative braking and a wall socket are the same current in the same direction.
  • Some of the timestamps in a Gen2 log are reconstructed, and this page shows one window where they are, on purpose. 11 main-board entries sit exactly 18,150 s apart — an interval divided by a count — while the battery lane in the very same window keeps its real beat. The same burst of events, in the switch-on window, takes 7 s.

What a Zero log is

A Zero log is a binary file the motorcycle keeps about itself. It is not a report and it is not written for you: it is a ring buffer of fixed records that the firmware appends to and eventually writes over, and a download hands you the buffer as it stands. There is no header explaining the fields, no units, and no guarantee that the oldest record in the file is the oldest thing that ever happened.

A download produces at least two files, because a Zero has at least two computers that keep logs — the main board that runs the motorcycle and the battery management that runs the pack. Neither file is a copy of the other. They are two witnesses to the same ride, and the section after this one is about what each of them can and cannot see.

Two platform names run through this page. Gen2 means the pre-Cypher motorcycles — the ones measured here are DS, DSR, FXS, SR — and Gen3 means the Cypher III platform, here DSR/X, FXE, SR/F. The model year does not decide which one you have, and the section on decode routes is about why not.

Everything printed on this page came out of that corpus: 363 log files were available to the extractor and 24 vehicle identification numbers were checked against its output. What is published is 19 windows — 406 lines in total, 231 from main boards and 175 from battery management — chosen because each one shows a thing that prose cannot show.

Every line is rendered in six cells, in the same shape the app’s own log viewer uses, so that a reader who later uploads a file recognises what they are looking at.

  • BoardMBB for the Main Bike Board, BMS for the Battery Management System, the pack’s own computer. In a two-lane window this is also the lane, and it is the first question to ask about any line.
  • Sequence — where the entry sits in its own file, and it is not the same number on every decoder. On both Gen2 boards and on the Gen3 pack it is source_no, the firmware’s own entry number. On the Gen3 main board the firmware leaves that field empty, so the cell holds the position in the decoder’s chronological order instead — deterministic, but not a number the bike wrote. And the Gen3 pack’s own counter is not a key either: BMS 675 appears twice in the parked window below, 5 min apart, on two different records. Never sort or deduplicate on this cell.
  • Clock — the time of day on that board’s clock. Dates are shifted by a whole number of days, so every interval inside a window is exact and no absolute date survives; a window that crosses midnight prints the shifted date as a rule between the rows, because two clock readings alone would make a day look like a second.
  • Leveldebug, info, warning, error or critical, exactly as the app colours it. A recurring error is often a start-up ritual rather than a fault, and the switch-on window below has 2 of them.
  • Event — the name of the thing that happened.
  • Conditions — the key: value half of the line, which is where the numbers live.
Three lines, three decoders: a Gen2 main board, a Gen2 pack, a Gen3 pack
MBB   4209  03:38:33  INFO    Module 00 Registered        module_number: 0, event_type: Registered, event_code: 2, module_voltage_volts: 92.555, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: NoneBMS   5844  16:29:43  INFO    Discharge level             amp_hours: 36, state_of_charge_percent: 65, current_amps: 0, voltage_low_cell_volts: 3.754, voltage_unloaded_cell_volts: 3.753, voltage_high_cell_volts: 3.759, voltage_balance_mv: 5, pack_temp_celsius: 15, bms_temp_celsius: 15, pack_voltage_volts: 105.202, pack_voltage_mv: 105202, mode: IdleBMS   1199  10:01:06  INFO    BMS telemetry (Gen3)        SOC=79% CvLow=3965mV CvHigh=3971mV I=31930mA

The third line is the one that matters for how this page is written. Its stored raw text is literally B2 entry #1199 [52B] — a record header, a length, and nothing a human wrote — so the words beside it are ours. That is true of all 158 Gen3 main-board lines here and of all 169 Gen2 lines on either board: a Gen2 record is a message id plus arguments, and the English you read next to it is the parser’s table, not the motorcycle’s. The exception runs the other way from what the platforms suggest. Of the 406 lines published here, 21 lines are text a motorcycle really did store as a string — every one of them Gen3, and every one written by the pack, out of the 79 Gen3 pack lines on this page.

What is not in it

Across all 5 decode routes the parser normalises 52 distinct channels. Not one of them is a brake, a belt, a wheel bearing, a tyre pressure, a suspension position, a dash element or a coordinate. A Zero log is an excellent witness to the pack, the charger, the controller and the interlocks, and completely blind to the rest of the motorcycle. Half of a good answer is knowing which half you are in.

Position is the absence people are most surprised by. A Zero writes no latitude and no longitude, on any generation. A map on a ride page in this app comes from a phone recording matched to the ride afterwards, never from the motorcycle.

Road speed is the subtler one, because it is published and it is not measured. The glossary entry is blunt about it: “Motor rpm times the model's gearing. Computed by us — the bike does not write a speed.” The motorcycle writes motor revolutions — 7,077 of the 27,533 Gen2 main-board entries counted here carry them — and a speed is what we compute from those and the gearing. That is why it is badged derived in the glossary and why a cruise control set in the Gen3 riding window below is set in rpm rather than in km/h.

Distance is the third. The odometer appears on 5,626 of 27,533 Gen2 main-board entries and on 0 of 9,472 Gen3 ones. Not “rarely”: never.

The one promise this page makes

Not “here is the raw log of every generation”, because on no generation here is the readable line the one the motorcycle stored: a Gen2 record is a message id plus arguments and a Gen3 main-board record is a length-prefixed blob, and both are rendered into English by us. The promise is narrower and more useful: for every line you are told which it is, and the 21 lines that really are the motorcycle’s own words say so. Understating that is recoverable. Overstating it would make every other claim on the page worth less.

The file before the first line

Open a Zero log with the tool most owners run — zero-log-parser, the community decoder this application also leans on for the older platforms — and the entries are not the first thing on the screen. Above them sits a short block about the file itself: which board wrote it, which motorcycle it came off, when that board was first switched on, and how many entries follow. It is the most recognisable artefact in a Zero log, and it is what an owner reads before they read anything else.

Every one of the 5 decode routes behind this page prints a different block. Between them the 5 cards below print 31 lines, and 11 of those have been touched. Redaction takes 8: 6 name a specific board, a specific pack or a specific motorcycle, and 2 — the firmware and the board revision — go by rule rather than by risk, because a revision printed beside a model and a year narrows a motorcycle to a small population. The other 3 lines carry a real date, moved by the same whole-day shift as every other date here. Everything else is exactly what the tool printed — including the lines where it printed nothing at all, which are three separate states and not one.

Three shapes carry the whole story and they follow one another below. The main board’s block is the rich one. The pack’s block, off the same motorcycle in the same download, is three lines and it names no motorcycle. The Gen3 block has the same labels as the first and answers Unknown to all but one of them — and the one it answers did not come from the file.

The Gen2 main board — 6 lines about the file, all 6 of them read out of it
Zero MBB logSerial number      [redacted]VIN                [redacted]Firmware rev.      [redacted]Board rev.         [redacted]Model              DSInitial date       Jan 10 2018 15:26:09Timezone           UTC+0.0Printing 4392 of 4392 log entries..

Redacted field by field rather than line by line, and that is not a detail. The redaction module every log line on this page goes through rewrites the LABEL of a column block as well as its value: fed this block whole it turns Firmware rev. 44 into Firmware: [redacted], and the column layout — the thing a reader would recognise — is gone. The dates move by the same whole-day shift as the rest of the page, so this stays exact: the board first came on 1,734 days, about 4.7 years, before the switch-on window published further down.

The pack on the same motorcycle, in the same download — 3 lines, no VIN, no model
Zero BMS logInitial date       Mar  6 2019 16:56:03BMS serial number  [redacted]Pack serial number [redacted]Timezone           UTC+0.0Printing 3152 of 3152 log entries..

A pack does not know which motorcycle it is bolted to, and its header says so by omission on every generation: of the 3 battery-management routes here, not one carries a VIN line or a model line. There are two serials instead and they mean different things — one is the controller board, one is the pack. The second survives the pack being moved to another motorcycle, which is what makes it the longest-lived identifier in the whole file.

The Gen3 main board — the same 6 labels, 0 of them read out of the file
Zero MBB logVIN                [redacted]Serial number      UnknownInitial date       UnknownModel              UnknownFirmware rev.      UnknownBoard rev.         UnknownTimezone           UTC+0.0Printing 2383 of 2383 log entries..

The one line with a value is the one that did not come from the file. Copy the same bytes to a neutral name, re-parse them, and the VIN becomes Unknown as well — measured that way rather than assumed, and the Gen2 block above is unchanged by the same test. On Gen3 the tool is quoting your file name back at you.

One line in that block is not from the file at all

Timezone is the time zone of whatever machine ran the parser. The same file read with TZ=Europe/Rome prints UTC+2.0 and moves every entry timestamp by two hours, while Initial date, which is a raw string, does not move by a second. The extraction behind this page forces zero, which is the only reason these blocks are reproducible. If you are comparing two downloads, check this line before you conclude the motorcycle’s clock drifted.

The same five blocks, one line at a time

The card below carries all 5 blocks, one route at a time — the buttons choose which — and every line of the block on screen answers a click: what the line claims, whether the file actually supports the claim, and what was done to the value before it reached this page. The numbers that come with each block are the file behind it — its size, the entries the tool declares against the entries we decode, and the two date windows, which are not always the same window.

Zero MBB log · the tool reads this file as REV0 · quality partial

What the tool prints above the entries — click a line

Zero MBB log

The date above is real, moved by the same whole-day shift as every other date on this page. 4 values were redacted field by field, which is what keeps the columns intact.

Pick a line

Every line in that block is a claim about the file, and several of them are claims the file does not actually support. Click one to see what it is, why the tool prints it, and what it is worth.

The file behind that block

Entries declared
4,392
Entries we decode
4,392
File size
131,072 B
Days, the tool
27
Days, ours
27
Impossible dates
0 / 0

VIN survives a renamewe read a VIN in the bytesboard found by magic

Editor’s note on this card (Italian)

L intestazione che un proprietario vede per prima aprendo il proprio log con lo strumento della comunita, ed e la sola cosa di questa pagina che riconoscerebbe a colpo d occhio. E la forma piu ricca delle cinque: nomina la scheda, la moto, il firmware, la revisione hardware, il modello e la data del primo avvio. Quattro righe su sei qui sono redatte, e quali e perche e il contenuto.

  • Il numero di serie e della SCHEDA, il VIN e della MOTO: due identificatori con due vite diverse, perche una scheda si sostituisce e un telaio no.
  • Initial date non e l inizio di questo log: e il primo avvio che la scheda ricorda. La distanza fra quella data e la finestra pubblicata piu sotto resta esatta anche qui, perche tutto e traslato dello stesso numero intero di giorni.
  • Confronta i campi che upstream legge con quelli che il nostro LogHeader riempie: su questa rotta upstream ne legge sei e noi ne teniamo uno. Non e una mancanza — e che il resto dell applicazione non ha bisogno degli altri cinque.

What each line is

Across the 5 cards the tool prints 10 distinct labels. Only 3 of them appear on every route — Initial date, Timezone, Printing — and which of the other 7 a file carries is already a measurement of what that file is.

  • Serial number — on the main board, 2 routes of 5. The BOARD’s serial, not the vehicle’s: year, role, batch, sequence. It names a piece of hardware that outlives a change of owner and can be moved into another motorcycle, which is why it is redacted here.
  • VIN — on the main board, 2 routes of 5. The frame number. It is not a credential — it is stamped on the frame and it appears in every log file — but it is the key everything else joins to a person, so it is not published. Read it beside the rename test below: on Gen2 it is read out of the bytes, on Gen3 it is the file name quoted back.
  • Firmware rev. — on the main board, 2 routes of 5. Which firmware the board was running. Redacted by rule rather than by risk assessment: the redaction module this page runs every line through lists firmware and build strings among the things it removes, because a revision plus a model and a year narrows a motorcycle to a small population. A page that publishes what its own redaction claims to strip cannot be checked.
  • Board rev. — on the main board, 2 routes of 5. The board’s hardware revision. Same rule and same reason as the firmware line, and the two together are more identifying than either alone.
  • Model — on the main board, 2 routes of 5. The model as the board knows it. A family code and not an individual, so it is published as written.
  • Initial date — on all five routes. The first switch-on this board remembers — not the beginning of this log. It is the oldest date the file contains and it usually sits years upstream of the window you downloaded. Shifted by the same whole number of days as every other date on this page, so the distance between it and the entries is exact while the absolute date is gone.
  • Timezone — on all five routes. Not a property of the file at all. It is the time zone of whatever machine ran the parser: the same file read with TZ=Europe/Rome prints UTC+2.0 and moves every entry timestamp by two hours, while Initial date, which is a raw string, does not move by a second. The extraction behind this page forces zero, which is the reason these blocks are reproducible.
  • Printing — on all five routes. How many entries the tool says it printed, always in the form N of N. It is a count of its own work — not the file’s capacity, and not what it skipped.
  • BMS serial number — on the battery management, 3 routes of 5. The serial of the pack’s controller board. Watch the state on this one: absent is not empty. On a Gen2 pack fitted to a Gen3 motorcycle the tool takes a different branch and never writes the line at all.
  • Pack serial number — on the battery management, 3 routes of 5. The serial of the pack itself, and the longest-lived identifier in the file: it survives being moved to another motorcycle, so it names hardware rather than a moment. Two real packs in the corpus answer here with raw control bytes, which is a state and not a failed read.
The same block, across all five decode routes
Header lineGen2 MBB · MBBEntryGen2 BMS · BMSEntryGen3 MBB · Gen3MBBEntryGen3 BMS · Gen3BMSEntryGen3 BMS · BMSEntry
Serial numberredactedUnknown
VINredactedredacted
Firmware rev.redactedUnknown
Board rev.redactedUnknown
ModelpublishedUnknown
Initial datedate, shifteddate, shiftedUnknownlabel, no valuedate, shifted
Timezoneparser’s hostparser’s hostparser’s hostparser’s hostparser’s host
Printingpublishedpublishedpublishedpublishedpublished
BMS serial numberredactedlabel, no valueline not printed
Pack serial numberredactedlabel, no valueredacted

A dash means the board has no such line at all. line not printed is a different claim: the label belongs to that board and this route’s branch does not write it — which is what happens to the controller serial on a Gen2 pack fitted to a Gen3 motorcycle. label, no value is a third state again, the label printed with nothing after it. Collapsing those three into “missing” is how a header stops being evidence.

On Gen3 there is nothing to read

Both Gen3 routes come out at 0 header lines read from the file, against 6 on the Gen2 main board and 3 on the Gen2 pack. On the Gen3 main board the block is a column of Unknown. On the Gen3 pack it is worse, and more interesting.

The Gen3 pack’s block is three labels with nothing after them. The tool did not report a missing header — it printed an empty one. It read a format byte it does not recognise, fell back to the same branch it uses for the Gen2 pack above (both cards say REV0), and then looked for Gen2 header fields at Gen2 offsets inside a Gen3 file. Three empty strings is what that returns.

Who reads what out of the same file
MeasuredGen2 MBB · MBBEntryGen2 BMS · BMSEntryGen3 MBB · Gen3MBBEntryGen3 BMS · Gen3BMSEntryGen3 BMS · BMSEntry
Identity lines the tool prints63633
of those, read from the file63002
the VIN survives a renameyesno VIN lineno — it is the file nameno VIN lineno VIN line
our decoder finds a VIN in the bytesyesnoyesnono
how our decoder recognised the boardmagicmagicgen3-headergen3-headermagic

Read the last two rows against each other before taking the third column as a boast. On the Gen3 main board our decoder does recover the VIN from the bytes where the tool falls back to the file name. On every pack, on both generations, it recovers nothing — correctly, because a pack file contains no VIN — and the VIN this application shows for those files came from the file name, which is the same crutch. Naming that is cheaper than defending it.

The Gen3 pack: an empty header, not an absent one
Zero BMS logInitial dateBMS serial numberPack serial numberTimezone           UTC+0.0Printing 781 of 781 log entries..

The count underneath is real — the tool goes on to print 781 entries. It is the identity block that is empty. Our own decoder reads 1,373 entries out of the same bytes, which is the subject of the next part.

The bytes are not blank — we simply do not print them

A Gen3 file does carry identity in clear in its first bytes: a model code, part numbers and a platform marker, and beside them a lot number, two board serials and a build id. The first group is shared by every motorcycle of a type; the second names one machine. Neither our redaction module nor the leak check that grades it recognises the second group — each of those tokens was passed through both and came back untouched — so nothing on this page prints a hex dump or a strings of that area. What it publishes instead is the measurement: what the tool read, what we read, and the difference.

The file as an object: size, window, and what the count means

The header answers what the file is. These numbers answer how much of your motorcycle’s life is in it, and the answer is usually less than people expect.

A Zero log is a fixed allocation, not a diary that grows: 4 of the 5 files here sit within a kilobyte of 128 KiB, and the largest is 256 KiB. Between them they hold 18,726 entries. When the buffer fills, the firmware writes over the oldest record, so what you download is a window that slides — and Initial date in the block above, which is years older than any entry in the same file, is the proof that the oldest record in a Zero log is not the oldest thing that happened.

Printing N of N is the tool counting its own output, and it never says N of more. On 2 routes its count and ours already disagree — Gen3 main board (ring buffer), 2,383 against 2,382; Gen3 battery management (ring buffer), 781 against 1,373 — so that number is a claim about one decoder’s run, not a property of the file.

The five files, as objects
MeasuredGen2 MBB · MBBEntryGen2 BMS · BMSEntryGen3 MBB · Gen3MBBEntryGen3 BMS · Gen3BMSEntryGen3 BMS · BMSEntry
File size, bytes131,072131,072131,200131,328262,144
Entries the tool declares4,3923,1522,3837817,427
Entries we decode4,3923,1522,3821,3737,427
Days covered, by the tool272226420,58711,298
Days covered, by us272226485867
Rows dated outside 2010–2040, the tool000131
Rows dated outside 2010–2040, us00000
Rows that are control characters only005182100

The two “days covered” rows are the pair to read twice. Both are computed the same way, from the first and last dated entry, and on the Gen3 pack they differ by 19,729 days because 13 rows in that one file decode to a date no motorcycle can have had. One row landing in the 1960s moves the window by half a century, and a window is what every “how long have I got data for” question is answered from. Across the five files that is 14 rows for the tool and 0 for us, plus 728 rows the tool prints with nothing after the level column but control characters.

Downloads are cumulative — never add two of them together

Each download hands you the buffer as it stands, so an event that happened once is in that download and in every download taken afterwards, until it is written over. Counting occurrences across a pile of files therefore counts downloads and not events. The busiest line in the message census on the “Reading your logs” page is “Sevcon controller event”, at 42,319 occurrences across 127 files, an average of 333 per file — a statement about how often that text was downloaded, not about how often it happened. This application de-duplicates across overlapping files before it counts anything, which is why its ride and charge totals do not double when you upload the same motorcycle twice.

The tool’s line and ours, side by side

Every window on this page is rendered by us. That is said plainly in the section above and it is easy to nod at, so here are the same entries — same file, same entry numbers, same seconds — printed by the community tool and printed by this page. 3 pairings, 15 entries in all, redacted, odometer-rounded and day-shifted identically on both sides — and Serial Number removed from both, because on these rows it holds a single character and a control byte and is not a serial at all. What is left of the difference is the renderer.

What the community tool prints — Switching on, main board, 6 entries
 Entry    Time of Log            Level     Event                      Conditions 04202     2022-10-10 03:38:28  STATE      Power On                   Power On: True, Power State: On, Power Source Code: 1, Power Source: Key Switch, Is Key Switch: True, Is External Charger: False, Is Onboard Charger: False, Is Known Source: True 04203     2022-10-10 03:38:29  INFO       Key On                     Key On: True, Key State: On, Is Key On: True, Is Key Off: False 04204     2022-10-10 03:38:29  DEBUG      Module scheme changed from None mode to Stopped mode 04205     2022-10-10 03:38:30  INFO       Module 00 Opening Contractor  Module Number: 0, Event Type: Opening Contractor, Event Code: 0, Module Voltage Volts: 0.0V, System Max Voltage Volts: 0.0mA, System Min Voltage Volts: 0.0V, Voltage Difference Volts: 0.0V, Capacitor Voltage Volts: 4294967.196V, Battery Current Amps: 0A, Precharge Percent: None% 04206     2022-10-10 03:38:31  INFO       Module 01 Opening Contractor  Module Number: 1, Event Type: Opening Contractor, Event Code: 0, Module Voltage Volts: 0.0V, System Max Voltage Volts: 0.0mA, System Min Voltage Volts: 0.0V, Voltage Difference Volts: 0.0V, Capacitor Voltage Volts: 4294967.196V, Battery Current Amps: 0A, Precharge Percent: None% 04207     2022-10-10 03:38:31  ERROR      Disabling CIB for non-timeout reason

Three things to look at. The entry number is the same number, printed 04202 there and 4202 here — no thousands separator, because it is an identifier and not a quantity. The level column carries a value ours has no room for — STATE — which reaches us as info. And two of these entries carry a field printed there as Capacitor Voltage Volts: 4294967.196V and absent here: 0xFFFFFFFF microvolts is the firmware’s way of saying “not measured”, and rendered as a number it is a capacitor at four million volts. That one field is the whole argument for rendering rather than pasting.

The same entries, in this page’s own rendering
MBB   4202  03:38:28  INFO    Power On                    power_on: True, power_state: On, power_source_code: 1, power_source: Key Switch, is_key_switch: True, is_external_charger: False, is_onboard_charger: False, is_known_source: TrueMBB   4203  03:38:29  INFO    Key On                      key_on: True, key_state: On, is_key_on: True, is_key_off: FalseMBB   4204  03:38:29  DEBUG   Module scheme changed from None mode to Stopped modeMBB   4205  03:38:30  INFO    Module 00 Opening Contractor  module_number: 0, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: NoneMBB   4206  03:38:31  INFO    Module 01 Opening Contractor  module_number: 1, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: NoneMBB   4207  03:38:31  ERROR   Disabling CIB for non-timeout reason

Same file, same entry numbers, same seconds. Provenance upstream-text and never device text: the block above is a third rendering of the same bytes, not the bytes.

What the community tool prints — Riding, main board, 4 entries
 Entry    Time of Log            Level     Event                      Conditions 00929     2022-06-08 15:22:15  STATE      Riding                     Pack Temp High Celsius: 34°C, Pack Temp Low Celsius: 32°C, State Of Charge Percent: 75%, Pack Voltage Volts: 108.28V, Motor Temp Celsius: 44°C, Controller Temp Celsius: 32°C, Motor Rpm: 1349, Battery Current Amps: 4A, Motor Current Amps: 5A, Ambient Temp Celsius: 28°C, Odometer Km: 30800, Mods: 11, Mods Raw: 3 00930     2022-06-08 15:23:15  STATE      Riding                     Pack Temp High Celsius: 34°C, Pack Temp Low Celsius: 32°C, State Of Charge Percent: 74%, Pack Voltage Volts: 106.924V, Motor Temp Celsius: 46°C, Controller Temp Celsius: 32°C, Motor Rpm: 1744, Battery Current Amps: 23A, Motor Current Amps: 48A, Ambient Temp Celsius: 28°C, Odometer Km: 30800, Mods: 11, Mods Raw: 3 00931     2022-06-08 15:24:15  STATE      Riding                     Pack Temp High Celsius: 34°C, Pack Temp Low Celsius: 32°C, State Of Charge Percent: 72%, Pack Voltage Volts: 106.419V, Motor Temp Celsius: 48°C, Controller Temp Celsius: 32°C, Motor Rpm: 2303, Battery Current Amps: 23A, Motor Current Amps: 36A, Ambient Temp Celsius: 27°C, Odometer Km: 30800, Mods: 11, Mods Raw: 3 00932     2022-06-08 15:25:15  STATE      Riding                     Pack Temp High Celsius: 34°C, Pack Temp Low Celsius: 32°C, State Of Charge Percent: 71%, Pack Voltage Volts: 108.3V, Motor Temp Celsius: 49°C, Controller Temp Celsius: 31°C, Motor Rpm: 2028, Battery Current Amps: -15A, Motor Current Amps: -37A, Ambient Temp Celsius: 27°C, Odometer Km: 30800, Mods: 11, Mods Raw: 3

Eleven values on one line, each of them named twice. Pack Temp High Celsius: 34°C there, pack_temp_high_celsius: 34 here: the spaced-out words are the tool’s formatter, and the snake_case is what the decoder hands the database and what you get in a CSV export. The degree sign is the sharpest part of it — the motorcycle stored a bare number, and the unit is something a formatter adds. This is also the only pairing where the odometer shows on both sides, rounded to 100 km on both: Odometer Km: 30800 and odometer_km: 30800.

The same entries, in this page’s own rendering
MBB    929  15:22:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 75, pack_voltage_volts: 108.28, motor_temp_celsius: 44, controller_temp_celsius: 32, motor_rpm: 1349, battery_current_amps: 4, motor_current_amps: 5, ambient_temp_celsius: 28, odometer_km: 30800, mods: 11, mods_raw: 3MBB    930  15:23:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 74, pack_voltage_volts: 106.924, motor_temp_celsius: 46, controller_temp_celsius: 32, motor_rpm: 1744, battery_current_amps: 23, motor_current_amps: 48, ambient_temp_celsius: 28, odometer_km: 30800, mods: 11, mods_raw: 3MBB    931  15:24:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 72, pack_voltage_volts: 106.419, motor_temp_celsius: 48, controller_temp_celsius: 32, motor_rpm: 2303, battery_current_amps: 23, motor_current_amps: 36, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3MBB    932  15:25:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 71, pack_voltage_volts: 108.3, motor_temp_celsius: 49, controller_temp_celsius: 31, motor_rpm: 2028, battery_current_amps: -15, motor_current_amps: -37, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3

Same file, same entry numbers, same seconds. Provenance upstream-text and never device text: the block above is a third rendering of the same bytes, not the bytes.

What the community tool prints — Switching on, battery management, 5 entries
 Entry    Time of Log            Level     Event                      Conditions 03027     2022-10-10 03:38:28  INFO       System Turned Off 03028     2022-10-10 03:38:28  DEBUG      CAN NOT Receiving Syncs 03029     2022-10-10 03:38:28  DEBUG      CAN Link Is Down 03030     2022-10-10 03:38:28  DEBUG      Entered Idle State 03031     2022-10-10 03:38:32  INFO       System Turned On

The negative control. When a line carries no conditions the two renderings differ only in column width — which is the point: the difference between the two tools is not spread evenly over the file, it is concentrated entirely in the decoded fields.

The same entries, in this page’s own rendering
BMS   3027  03:38:28  INFO    System Turned OffBMS   3028  03:38:28  DEBUG   CAN NOT Receiving SyncsBMS   3029  03:38:28  DEBUG   CAN Link Is DownBMS   3030  03:38:28  DEBUG   Entered Idle StateBMS   3031  03:38:32  INFO    System Turned On

Same file, same entry numbers, same seconds. Provenance upstream-text and never device text: the block above is a third rendering of the same bytes, not the bytes.

So which of the two is “the log”?

Neither. The log is the binary, and both blocks are renderings of it. The tool’s degree signs are added by its own formatter, which is why 4 rows here contain a character the motorcycle cannot write, and why these blocks are tagged upstream-text and never device text. The tool’s rendering is the one another owner will recognise in a forum post. Ours is the one that suppresses a field the firmware marked as not measured and names its own provenance on every line. Use the first to be understood, and the second to be right.

An entry is not a line — but in this dump that is a hazard rather than a fact. A field value can hold a raw control byte, and the pack serial on an Opening Contractor row is the single character x followed by a vertical tab. Split the file with a routine that treats a vertical tab as a line break, as Python’s own splitlines() does, and hundreds of entries appear to wrap; split on newlines alone and the body comes out at exactly the 4,392 rows the file’s own header declares, with 0 continuation lines. Every apparent wrapped entry in these pairings was the splitter, not the log — and the tail it swallowed was published as a redacted serial. Slice a dump by entry number, never by counting lines.

Two boards: the motorcycle and the pack

The main bike board runs the vehicle: the key, the kickstand, the immobiliser, the motor, the controller, the odometer, the chargers, the 12 V network and the vehicle state machine. The battery management runs the pack: the cells one by one, balancing, pack current, pack and board temperatures, isolation, the contactor and the coulomb counter.

The consequence is not symmetrical, and it is worth stating as a rule before any extract: the motor is visible only to the main board, the cells only to the pack. Neither file is a subset of the other, and no single file describes a complete ride.

There is one more asymmetry with an immediate practical consequence. The vehicle identification number is written into the main-board file only, on both generations. A battery-management file uploaded on its own genuinely does not know which motorcycle it came from, which is why this app asks. And the pack carries an identity of its own that survives being transplanted into another motorcycle — which is why a battery is a separate thing from a bike in this app’s data model, and why the section on privacy at the end of this page exists at all.

The same instant, twiceFXS 2016 switching off08:17:218 lines · 6 bike, 2 packMBBPower OffMBBSevcon Turned OffMBBModule 00 Opening Contractor107.998 VMBBModule 01 Opening Contractor107.971 VMBBDisabling External Chg 0 Charger 2MBBDisabling External Chg 1 Charger 3BMSContactor was Opened107.997 VBMSSystem Turned OffOne contactor, two vocabulariesThe two highlighted rows are the same piece ofhardware opening, named differently.1 mV apart — two voltmetersbattery management107.997 Vmain board · module 00107.998 Vmain board · module 01107.971 VTwo modules in parallel: one pack voltage,read three times. And 740 seconds in this pair offiles carry lines from both boards.
The same contactor, the same second, two names and two independent voltmeters 1 mV apart. If you look at one drawing on this page, look at this one: it is the whole argument for why a Zero hands you two files. Neither file says the two events are the same thing — you have to put them on one axis — and this second is ordinary rather than rare: 740 seconds in this pair carry lines from both boards.

The same instant, written twice

At 08:17:21 on a 2016 FXS the pack’s record for that second decodes as Contactor was Opened and the main board’s decodes as Module 00 Opening Contractor. Same contactor. Two different records, with two different message ids inside them — the English words are our decode table, not either board’s prose, which is why Contractor is spelled that way: it is the firmware’s own misspelling of contactor, faithfully carried through, and not a second name for the part. Neither file, read alone, says the two are the same event.

Both boards also publish a voltage for it, and they measured it separately: 107.997 V at the pack against 107.998 V at the main board, 1 mV apart. The line after that reads the second physical module at 107.971 V, which is how you can tell this motorcycle carries two modules — and why one download from it produces three files rather than two.

That second is not a rarity fished out of a long file. Across this pair of files there are 740 distinct seconds in which both boards wrote something. It is the norm at every switch-on and every switch-off.

On Gen3 the same thing happens and is much harder to see, because the two boards do not share a clock. At 16:43:51 — after the two files have been put on one axis — the main board’s record decodes as Module 2 Contactor is now Closed and the pack’s record really does contain the string Contactor closed. That pair is the exception this page keeps pointing at: on Gen3 the pack stores words and the main board stores none. Two seconds earlier the main board reads the module at 110,763 mV and the pack reads itself at 110,757 and then 110,760 mV: 6 and 3 mV apart. Compare two readings taken minutes apart rather than seconds and the figure grows by an order of magnitude — but that larger number is the pack quietly self-discharging in between, not a difference between two instruments. This page was drafted with that mistake in it.

One second of a Gen2 shutdown, both lanes — 08:17:21
MBB    487  08:17:21  INFO    Power Off                   power_on: False, power_state: Off, power_source_code: 1, power_source: Key Switch, is_key_switch: True, is_external_charger: False, is_onboard_charger: False, is_known_source: TrueMBB    488  08:17:21  INFO    Sevcon Turned Off           sevcon_power_on: False, power_state: Off, is_powered: False, controller_type: sevconMBB    489  08:17:21  INFO    Module 00 Opening Contractor  module_number: 0, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 107.998, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 108.812, battery_current_amps: 0, precharge_percent: NoneMBB    490  08:17:21  INFO    Module 01 Opening Contractor  module_number: 1, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 107.971, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 108.812, battery_current_amps: 0, precharge_percent: NoneMBB    491  08:17:21  INFO    Disabling External Chg 0 Charger 2MBB    492  08:17:21  INFO    Disabling External Chg 1 Charger 3BMS    456  08:17:21  INFO    Contactor was Opened        contactor_state: opened, pack_voltage_volts: 107.997, switched_voltage_volts: 107.52, precharge_percent: 99.55832106447401, discharge_current_amps: 0.134, pack_voltage_mv: 107997, switched_voltage_mv: 107520, discharge_current_ma: 134BMS    457  08:17:21  INFO    System Turned Off

Read down the lane column. The main board announces the power-off, turns the controller off, opens both modules and disables the chargers; the pack announces the contactor and then that the system is off. One event, two accounts, and only the pack publishes the pre-charge percentage. Generation 2, both lanes. Provenance: none of these 8 lines is device text — every one is our decode of a binary record.

The same moment on Gen3, once the clocks are reconciled — 16:43:51
MBB    398  16:43:51  INFO    Module 2 Contactor is now ClosedMBB    399  16:43:51  INFO    At least one contactor is now closedMBB    400  16:43:51  INFO    Disable flags changed       old: 10000101, new: 10000001MBB    401  16:43:51  WARNING  Fault cleared               ALL_CONTACTORS_OPENMBB    402  16:43:51  WARNING  Fault cleared               MODULE_AWAITING_CONNECTIONMBB    403  16:43:51  WARNING  Fault cleared               DC_DC_LOWMBB    404  16:43:51  INFO    Control flags changed       old: 10000000100000001110000, new: 10000000101000001110000BMS    399  16:43:51  INFO    BMS telemetry (Gen3)        SOC=75% CvLow=3955mV CvHigh=3958mV I=0mABMS    399  16:43:51  INFO    Contactor closed            Contactor close command receivedBMS    401  16:43:51  INFO    BMS telemetry (Gen3)        SOC=75% CvLow=3955mV CvHigh=3958mV I=0mA

In the raw files these lines are 605 s apart. Generation 3, both lanes. Provenance: 1 line is device text, written by the BMS; the other 9 are our decode of the bytes. That is the whole distinction drawn on one second: the main board’s ring-buffer records hold no strings at all, so every main-board line here is our decode, while the pack’s event records do hold one — and it is the pack, on the newer platform, that supplies the only words on this page a motorcycle really wrote.

Two clocks, and what happens when you ignore them

The two boards keep separate real-time clocks. On Gen2 they agree. On Gen3 they do not, and the gap is large enough to reverse conclusions rather than blur them.

The offsets were found by cross-correlating the aperiodic events in the two files and then confirmed against a shared physical event — a contactor closing is one moment, whatever each board calls it. The column that settles the argument is the last pair: how many distinct seconds hold writing from both boards, with the offset applied and without it.

Look at the Gen3 rows. Uncorrected, the two boards appear to share 1 and 3 seconds across whole files — which reads as “these boards never write at the same time”. Corrected, they share 68 and 72. Nothing about the motorcycles changed; the axis did.

The correlation also throws up rival peaks exactly 3,691 s away from the real one, on both Gen3 pairs. That is not a second candidate offset. It is the hibernation beat from “Scene: parked”, aliasing: the same periodic pattern matching itself one beat over. A method that reports the strongest peak without asking what the interval means will happily publish an offset of an hour.

The last column is the honest one. The offset itself drifts across the days a pair of files covers, and that drift is the error bar on any interval this page prints BETWEEN two lanes. It is also why the Gen3 switch-off window later on is published with one lane: the two events that would sit side by side there are closer together than the drift, so printing which board wrote first would be printing our own uncertainty.

Every pair of files behind a two-lane window on this page
MotorcycleGenerationOffset appliedCo-written seconds, correctedCo-written seconds, rawOffset drift across the pair
DS 201420 s3503500 s
FXS 201620 s74074010 s
SR 201720 s5454not measured
FXS 201920 s5305304 s
SR/F 20233-605 s6813 s
DSR/X 20233-2,697 s72316 s

All 4 Gen2 pairs measure an offset of 0 s; both Gen3 pairs do not. A negative offset means the main board runs ahead of the pack. The two numbers in the middle are the same measurement over the same two files, and the only difference between them is whether the offset was applied first.

Never compare a shared quantity on raw timestamps

State of charge, pack voltage and current are all published by both boards on Gen3. On the second pair in the table above, a comparison made on the raw file timestamps is reading the two boards 45 min apart, so it measures how much charging happened in between rather than whether the boards agree — and it never lands on zero, which reads as a disagreement. Reconcile the clocks, then compare. This is the single easiest way to publish a confident, wrong number about a Gen3.

Who sees what

The table below is the census behind every claim on this page about what a board can see. It counts, for each decode route, how many entries in the measured files actually carry each quantity.

Read one row across and the rule falls out. The motor rows are populated on Gen2 main boards and nowhere else. The cell rows are populated on battery management and nowhere else. The overlap — state of charge, pack voltage, pack current — is wide on Gen2 and has almost vanished on the Gen3 main board.

Where a main board does publish a state of charge, it is not measuring it. The glossary is explicit: “Coulomb-counted charge left in the pack. The MBB publishes it too, but does not measure it: it receives the BMS figure over CAN, which is why the two agree to the point in 94.4% of paired samples and the MBB is the one that lags.”

Entries carrying each quantity, counted on the files these extracts come from
QuantityGen2 MBB · MBBEntry — of 27,533Gen2 BMS · BMSEntry — of 32,436Gen3 MBB · Gen3MBBEntry — of 9,472Gen3 BMS · Gen3BMSEntry — of 3,607Gen3 BMS · BMSEntry — of 7,427
State of charge7,0189,1398519452,416
Pack voltage7,01818,6172271,1675,094
Pack current11,0069,01001,1142,397
Lowest cellnot decoded9,139not decoded1,2352,416
Highest cellnot decoded9,139not decoded1,1672,416
Cell spreadnot decoded9,139not decodednot decoded2,416
Cell at restnot decodednot decodednot decoded953not decoded
Amp-hours dischargednot decoded9,139not decodednot decoded2,416
Motor speed7,077not decoded0not decodednot decoded
Motor temperature5,567not decodednot decodednot decodednot decoded
Controller temperature5,567not decoded238not decodednot decoded
Pack temperature7,0189,13916802,416
Board or ambient temperature7,0189,138238not decoded2,416
Odometer5,626not decoded0not decodednot decoded
Road speed15,695not decoded0not decodednot decoded
Vehicle state2,921not decoded1,664not decodednot decoded
Pack statenot decoded9,008not decoded4282,395
Charge current ceiling0not decoded668not decodednot decoded
Module voltage3,988not decoded227not decodednot decoded

A count, not a tick, and the denominator is in the column head because it is the same for every row of a route. A tick would say “present” at 851 entries out of 9,472 and at 7,077 out of 27,533, and those are not the same claim. “not decoded” means this census does not count that channel for that decoder; a zero means it does and no entry in these files filled it. 17 files were counted in total. Read “not decoded” as “not among the channels counted for that decoder”, not as “absent from the file”: the census counts NORMALISED channels, the names the parser gives a value after decoding it, and a firmware that spells the same quantity differently still gets counted — the Gen2 main board prints module_voltage_volts: 107.998 in the shutdown window above and the last row counts it. One number in this table is not what it looks like, and it is the Gen3 main board's state of charge: 851 entries carry a value at that offset, but only 522 of them are a charge. The rest are 0x20, the ASCII space, read at the state-of-charge offset on a record that is not telemetry. The larger number is what a decoder sees; the smaller one is what the motorcycle meant.

A count is worth more than a tick here. A tick would put “present” next to a channel that appears in three per cent of entries and next to one that appears in every ride sample, and those two facts lead to opposite decisions about which file to open.

Five decode routes, and why the year does not predict the format

There is a widespread belief that Zero logs come in two flavours, old and new, split by model year. Neither half of that is true. The parser really has five routes, and the model years they cover overlap heavily.

A route is decided by what the bytes are, cross-checked against what the motorcycle is. Two of the five are the classic pair — a Gen2 main board and a Gen2 pack. Two are the ring-buffer decoders that read the Gen3 main board and the Gen3 pack. The fifth is the one that surprises owners: a Gen3 motorcycle whose battery management writes the Gen2 vocabulary. Its owner opens the file and finds lines that look like a 2016 DS.

That fifth row deserves saying carefully, because it is a label rather than a fifth format. The file is classified generation 3 because of the motorcycle, and it is decoded by the Gen2 battery-management decoder because that is what the bytes are. The right sentence is “your pack speaks the older dialect”, not “there is a fifth log format”.

No first-generation example is published here. The label exists in the classifier and lands on a handful of files that are plainly not first-generation motorcycles, so it is a classification fallback rather than a format. Saying we do not have one is more useful than inventing a specimen.

Five decode routes2014–2024 · one strip per routeGen2 main boardMBBEntry4 files · 27,533 entries201420162019Gen2 battery managementBMSEntry5 files · 32,436 entries2014201620172018Gen3 main board (ring buffer)Gen3MBBEntry4 files · 9,472 entries202120232024Gen3 battery management (ring buffer)Gen3BMSEntry3 files · 3,607 entries20212023Gen3 bike, Gen2 battery management (FXE)BMSEntry1 file · 7,427 entries2023Two lanes share one decoder (BMSEntry) — samecolour, 9 model years apart.
The five routes against model year. A mark is one motorcycle in the sample and the lane weight is its file count, so this is what was measured rather than what Zero shipped. Two things to take from it: 3 different routes carry a 2023 motorcycle, and the two lanes drawn in the same colour run the same decoder (BMSEntry) — the pack of a recent bike writing the dialect of the oldest bike here. No year is the year one format stops and another starts.

The five routes

Files and entries below are what was measured on the files these extracts were cut from, not an estimate over the whole corpus. Models and years are the motorcycles those files came from.

The five decode routes these extracts come from
RouteBoardEntry classFilesEntriesModels hereModel years
gen2-mbbMBBMBBEntry427,533DS, FXS2014–2019
gen2-bmsBMSBMSEntry532,436DS, DSR, SR, FXS2014–2018
gen3-mbbMBBGen3MBBEntry49,472SR/F, DSR/X2021–2024
gen3-bmsBMSGen3BMSEntry33,607DSR/X, SR/F2021–2023
gen3-bike-gen2-bmsBMSBMSEntry17,427FXE2023

The route is what the parser did with the bytes, not what the badge on the tank says. The last row is a Gen3 motorcycle whose battery management writes the Gen2 vocabulary: same decoder as the second row, different name. The years overlap on purpose — they are the model years of the motorcycles these extracts came from, and no two routes own a clean slice of the calendar.

What is published, and what is not

Six scenes against 5 routes is 30 cells, and this page fills 18 of them. The interesting part is the rest.

A grid that leaves a cell blank without saying why reads as “we did not look”. Every blank here has a recorded, measured reason — 10 of them in the generated data — and they are of three kinds. Some lanes genuinely do not write that scene. Some would repeat, with fewer fields, something another cell already shows better. And on the fifth route the main board decodes so incompletely that a window from it would show our decoder’s limits rather than a Zero log.

One cell is filled and marked reconstructed. It is a counter-example, kept on the page on purpose, and “Scene: parked” is where it is read.

Scene against decode route: what this page publishes, and what it does not
SceneGen2 MBB · MBBEntryGen2 BMS · BMSEntryGen3 MBB · Gen3MBBEntryGen3 BMS · Gen3BMSEntryGen3 BMS · BMSEntry
Switching on26 linesno window, reason recorded55 lines (2 windows)no window, reason recorded
Riding21 linesno window, reason recorded23 lines22 linesno window, reason recorded
Charging22 lines26 lines18 lines16 lines
Parked, key in your pocket18 lines · reconstructed16 lines25 linesno window, reason recorded
A fault20 lines25 lines13 linesno window, reason recorded
Switching off21 lines25 lines14 linesno window, reason recordedno window, reason recorded

18 of the 30 cells carry a window. A window is filed under its ANCHOR lane, so a two-lane window appears in one column only and the other board is still there in the extract. The blanks are not gaps in the search: 9 scene-and-route pairs carry a written, measured reason, 8 of them leaving the cell empty and 1 explaining why the only window there is the reconstructed counter-example; a 10th record covers the generation-one label, which this page does not publish at all. A cell that says only “—” has no window anchored on that route.

Windows, not whole files

Nothing here is a complete log. Each window is between 13 and 30 lines, which is enough to show a sequence and not enough to identify a motorcycle. A whole file would be re-identifiable and would teach less.

Scene: switching on

Switching on is the most stereotyped thing a Zero does, and it is the best place to start reading because the order never varies. Key, module scheme, contactors open, CAN link up, module registered, controller on, pre-charge, contactor closed, first telemetry. Every step has a reason and the next step depends on it.

Two things in the Gen2 window below tend to alarm people, and neither should. The first is that 2 of its 26 lines are at level errorDisabling CIB for non-timeout reason and CAN1 BIT0 error. count=1 — and the motorcycle proceeds to ride away regardless. A recurring error at every start-up is a ritual, not a fault; what matters is whether it is followed by the rest of the sequence.

The second is that the two boards interleave inside single seconds: 5 seconds in this window carry more than one line. Within one second the order between two boards is a stable tie-break, not evidence. Between boards it is not even that, until the clocks have been reconciled.

Pre-charge is the number to notice. Both boards publish it for the same contactor closing: 90.89% from the pack and 90.28% from the main board. It is the fraction of pack voltage the controller’s bus capacitors have been brought up to before the contactor is allowed to close. Closing early is what welds a contactor, which is why the pack refuses in the fault window further down.

One line, six cellsA real line out of a DS 20141BoardMBBWhich board wrote it. Each keeps its own file.2Sequence4203The firmware entry number. Gen3 has none.3Clock03:38:29That board’s clock. The two do not share one.4LevelINFOOne of five. An error can be a boot ritual.5EventKey OnWhat happened, named by the decoder.6Conditionskey_on: Truekey_state: Onis_key_on: Trueis_key_off: FalseThe decoded fields, 4 of them here.The five levelsdebuginfowarningerrorcriticalA level is the firmware’s idea of importance,not a verdict: a boot ERROR repeats every time.
The six cells, on a real line out of a DS 2014 being switched on. It is the same shape the log viewer in this app uses, so a line here and a line in your own upload look alike. The two cells that mislead people are the sequence — the firmware’s number, which a Gen3 main board simply does not write — and the level, which is the firmware’s idea of importance and not a verdict on your motorcycle.

Three switch-ons

The first window is a Gen2 pair on one clock. The second is the Gen3 equivalent, and reads as the same story in a different vocabulary: hibernate exit, board reset, switches read, immobiliser, faults armed with a countdown, module registered, pre-charge, contactor closed, faults cleared.

The third window answers a question the second one raises. A Gen3 wakes itself up roughly once an hour whether or not anybody is there, so how do you tell a real start-up from a scheduled wake? By one word. Board was reset carries its source, and here it reads Hib Wake Pin — a physical pin — where a scheduled wake says the real-time clock instead.

That window also shows the trap that makes Gen3 ordering delicate: 18 of its 25 lines fall inside the single second 15:57:08. Sorting a Gen3 merge by timestamp alone would arrange them arbitrarily and invent a start-up sequence that never happened. The order is timestamp, then source, then sequence.

And it contains the first view on this page of the 12 V accessory battery: Requesting 12v charge with DC-DC: 189472uV, Battery: 13158184uV, Combined: 12468852uV, Ambient: 17C. Microvolts, three readings, one line — and the auxiliary battery strands more riders than the traction pack does. Where you can see it at all is narrow rather than unique: only on the Gen3 main board, only on the Requesting 12v charge and Stopping 12v charge events, and 6 times in all — spread across 6 of the 19 windows published here, so you will meet it again further down. No Gen2 file in this corpus mentions it at all.

DS 2014 — Switching on · 26 lines over 7 s · lanes MBB + BMS
MBB   4202  03:38:28  INFO    Power On                    power_on: True, power_state: On, power_source_code: 1, power_source: Key Switch, is_key_switch: True, is_external_charger: False, is_onboard_charger: False, is_known_source: TrueBMS   3027  03:38:28  INFO    System Turned OffBMS   3028  03:38:28  DEBUG   CAN NOT Receiving SyncsBMS   3029  03:38:28  DEBUG   CAN Link Is DownBMS   3030  03:38:28  DEBUG   Entered Idle StateMBB   4203  03:38:29  INFO    Key On                      key_on: True, key_state: On, is_key_on: True, is_key_off: FalseMBB   4204  03:38:29  DEBUG   Module scheme changed from None mode to Stopped modeMBB   4205  03:38:30  INFO    Module 00 Opening Contractor  module_number: 0, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: NoneMBB   4206  03:38:31  INFO    Module 01 Opening Contractor  module_number: 1, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: NoneMBB   4207  03:38:31  ERROR   Disabling CIB for non-timeout reasonMBB   4208  03:38:32  INFO    Module 00 CAN Link UpBMS   3031  03:38:32  INFO    System Turned OnBMS   3032  03:38:32  DEBUG   Entered Running StateBMS   3033  03:38:32  DEBUG   CAN Link Is UpMBB   4209  03:38:33  INFO    Module 00 Registered        module_number: 0, event_type: Registered, event_code: 2, module_voltage_volts: 92.555, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: NoneMBB   4210  03:38:34  INFO    Sevcon Turned On            sevcon_power_on: True, power_state: On, is_powered: True, controller_type: sevconMBB   4211  03:38:35  ERROR   CAN1 BIT0 error. count=1MBB   4212  03:38:35  INFO    Sevcon CAN Link UpMBB   4213  03:38:35  DEBUG   Sevcon Contactor Drive ON.MBB   4214  03:38:35  INFO    Module 00 Closing Contractor  module_number: 0, event_type: Closing Contractor, event_code: 1, module_voltage_volts: 92.563, system_max_voltage_volts: 92.553, system_min_voltage_volts: 92.553, voltage_difference_volts: 0.0, battery_current_amps: 0, precharge_percent: 90.27581214956298MBB   4215  03:38:35  DEBUG   Module 00 Contactor is now ClosedMBB   4216  03:38:35  INFO    Enabling Charger 2MBB   4217  03:38:35  INFO    Enabling Charger 3BMS   3034  03:38:35  DEBUG   CAN Receiving SyncsBMS   3035  03:38:35  INFO    Contactor drive turned on   pack_voltage_volts: 92.562, switched_voltage_volts: 84.132, duty_cycle_percent: 34, pack_voltage_millivolts: 92562, switched_voltage_millivolts: 84132, voltage_difference_volts: 8.429999999999993BMS   3036  03:38:35  INFO    Contactor was Closed        contactor_state: closed, pack_voltage_volts: 92.562, switched_voltage_volts: 84.132, precharge_percent: 90.89259091203733, discharge_current_amps: -0.25, pack_voltage_mv: 92562, switched_voltage_mv: 84132, discharge_current_ma: -250

Decode route gen2-mbb (MBBEntry), generation 2. Provenance: 0 lines are device text and 26 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 350 of the same seconds across the whole pair. Timestamps: real.

SR/F 2023 — Switching on · 30 lines over 4 s · lanes MBB + BMS
BMS    394  16:43:48  WARNING  Fault set: INVALID/NO CELL TYPEBMS    395  16:43:48  WARNING  Fault cleared: INVALID/NO CELL TYPEBMS    396  16:43:48  INFO    QuicMBB    382  16:43:49  INFO    Registering Mod 2           110763mV, 4: brickMBB    383  16:43:49  INFO    Control flags changed       old: 10000000100000001110000, new: 10000000100010001110000MBB    384  16:43:49  INFO    LTSM state                  INIT to DISMBB    385  16:43:49  INFO    State change                from: PWSU, to: WAKEMBB    386  16:43:49  INFO    All Contactors openMBB    387  16:43:49  INFO    UnknownMBB    388  16:43:49  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB    389  16:43:49  INFO    Ch limits                   curr 0 cap 0 act 0 pow 0MBB    390  16:43:49  INFO    UnknownMBB    391  16:43:49  WARNING  Fault pending               DC_DC_LOW sets in 30 secondsMBB    392  16:43:49  WARNING  Fault pending               ALL_CONTACTORS_OPEN sets in 30 secondsMBB    393  16:43:49  WARNING  Fault pending               MODULE_AWAITING_CONNECTION sets in 30 secondsMBB    394  16:43:49  INFO    Control flags changed       old: 10000000100010001110000, new: 10000000100000001110000MBB    395  16:43:49  INFO    Precharging from module 2, Vpackoc  110763mVMBB    396  16:43:49  INFO    Connecting mod 2            V_mod: 110763mV, V_bus: 0mV, V_bus_oc: 0mVMBB    397  16:43:49  INFO    Current                     I_out: 0mA, I_lim_dischg: 980000mA, I_lim_chg: -152000mA, I_lim_CC: 100000mAMBB    398  16:43:51  INFO    Module 2 Contactor is now ClosedMBB    399  16:43:51  INFO    At least one contactor is now closedMBB    400  16:43:51  INFO    Disable flags changed       old: 10000101, new: 10000001MBB    401  16:43:51  WARNING  Fault cleared               ALL_CONTACTORS_OPENMBB    402  16:43:51  WARNING  Fault cleared               MODULE_AWAITING_CONNECTIONMBB    403  16:43:51  WARNING  Fault cleared               DC_DC_LOWMBB    404  16:43:51  INFO    Control flags changed       old: 10000000100000001110000, new: 10000000101000001110000BMS    399  16:43:51  INFO    BMS telemetry (Gen3)        SOC=75% CvLow=3955mV CvHigh=3958mV I=0mABMS    399  16:43:51  INFO    Contactor closed            Contactor close command receivedBMS    401  16:43:51  INFO    BMS telemetry (Gen3)        SOC=75% CvLow=3955mV CvHigh=3958mV I=0mABMS    402  16:43:52  INFO    BMS telemetry (Gen3)        SOC=75% CvLow=3954mV CvHigh=3957mV I=100mA

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 4 lines are device text and 26 are our decode of the bytes. The two boards keep separate clocks. The main board runs 605 s ahead of the pack here, and the lanes are merged with that offset applied: 68 co-written seconds across the pair with it, 1 without. Timestamps: real.

SR/F 2021 — Switching on · 25 lines over 4 min · lanes MBB
MBB   1670  15:53:21  INFO    Saving Stats, Hibernating for 3600 secMBB   1671  15:57:08  INFO    Exiting HibernateMBB   1672  15:57:08  INFO    Board was reset             Source: Hib Wake Pin, Power-On, Supply: WD, Power: ValMBB   1673  15:57:08  INFO    Kill Sw                     STOPMBB   1674  15:57:08  INFO    Key Sw                      ONMBB   1675  15:57:08  INFO    Control flags changed       old: 000000, new: 101110MBB   1676  15:57:08  INFO    Disable flags changed       old: 000000, new: 101101MBB   1677  15:57:08  INFO    State change                from: STRT, to: WAITMBB   1678  15:57:08  INFO    Control flags changed       old: 101110, new: 100111MBB   1679  15:57:08  INFO    Requesting 12v charge       DC-DC: 189472uV, Battery: 13158184uV, Combined: 12468852uV, Ambient: 17CMBB   1680  15:57:08  INFO    Disable flags changed       old: 101101, new: 001101MBB   1681  15:57:08  INFO    Control flags changed       old: 0100111, new: 1100111MBB   1682  15:57:08  INFO    UnknownMBB   1683  15:57:08  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB   1684  15:57:08  INFO    Ch limits                   curr 0 cap 0 act 0 pow 0MBB   1685  15:57:08  WARNING  Fault cleared               HVIL_OPENMBB   1686  15:57:08  INFO    Kickstand Sw                DOWNMBB   1687  15:57:08  INFO    Control flags changed       old: 000000001100111, new: 100000001110111MBB   1688  15:57:08  INFO    Control flags changed       old: 100000001110111, new: 100000001110110MBB   1689  15:57:09  WARNING  Fault pending               THROTTLE_MAP sets in 90 secondsMBB   1690  15:57:11  INFO    Disable flags changed       old: 1101, new: 0101MBB   1691  15:57:11  INFO    Kickstand Sw                UPMBB   1692  15:57:11  INFO    Control flags changed       old: 100000001110110, new: 100000001100110MBB   1693  15:57:11  INFO    Registering Mod 2           113673mV, 4: brickMBB   1694  15:57:11  INFO    LTSM state                  INIT to DIS

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 0 lines are device text and 25 are our decode of the bytes. Timestamps: real.

Read a start-up as a sequence, never as a stopwatch

The order of a boot burst is real. The spacing inside it may not be — see “Scene: parked, key in your pocket”. Present these lines numbered, follow the causal chain, and do not draw a conclusion from the number of seconds between two of them unless the window is labelled as having real timestamps.

Switching on: a Gen2 pair beside its Gen3 equivalent

Key on · DS 2014

Key onDS 2014Gen2Gen2 main boardMBBEntryMBBBMS26 lines · 7 srider’s log · redactedmeasured timestamps

Faults in this window 2 of 26 rows carry a warning or an error: 2 reach only the family they belong to, and none is left without a destination.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

26 righe, 14 secondi, dalla chiave girata alla prima riga di telemetria di guida. Gap misurati [1,0,1,1,0,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,1,0,4]: nessun passo uniforme, tutti fra 0 e 4 s. E la SOLA accensione a tempi veri che questo file contiene: la stessa identica raffica di dodici eventi, nello stesso ordine e con le stesse stringhe, compare altre due volte nello stesso file a passo UNIFORME, a 28 s (voci 225-236) e a 18.150 s (voci 1332-1343). Il boot vero dura sei secondi, non cinque minuti e non cinquanta ore — ed e per questo che questa finestra e la scena 8 vanno lette in coppia, sono lo stesso evento misurato bene e misurato male. Scelta a due corsie perche qui le due schede si accavallano al massimo: alle 03:38:32 scrivono entrambe.

  • L ordine di accensione e fisso e meccanico: chiave -> schema moduli -> contattori aperti -> CAN su -> modulo registrato -> Sevcon -> precarica -> contattore chiuso -> Riding. Non e una lista, e una sequenza con una causa per ogni passo.
  • Le due schede si alternano DENTRO lo stesso secondo: alle 03:38:32 il MBB scrive Module 00 CAN Link Up e il BMS System Turned On. Nessuno dei due file, da solo, mostra l accensione intera.
  • La precarica e un numero e lo dichiarano ENTRAMBE le schede sullo stesso evento: precharge_percent 90,89 sul BMS e 90,28 sul MBB. Due strumenti, non un disaccordo.
  • CIB timeout: 5009 ms e Disabling CIB for non-timeout reason sono di livello ERROR e compaiono a ogni singola accensione — 87 volte in questo solo file. Un ERROR ricorrente e un rituale di boot, non un guasto.
  • La voce 4.209 porta un serial_number in chiaro dentro il proprio testo grezzo: il seriale vero del pacco, otto caratteri, lo stesso che l intestazione BMS stampa come Pack serial number. E il motivo per cui Battery e un entita a se e non una colonna di Bike: il pacco sopravvive alla moto su cui e montato. In questa finestra il campo non compare affatto — la scena lo sopprime su tutta la corsia MBB, come dichiarato piu sotto — mentre nelle finestre che lo rendono, fault-gen2 e parked-gen2-smear, arriva alla pagina come [redacted].

Notes to the generator’s maintainer

  • Il badge di provenienza di ogni riga qui e our decode, e la ragione e' interessante: i nomi degli eventi Gen2 escono dalle TABELLE del decoder upstream, non dai byte del file, anche quando la stessa stringa e' presente nel file. Il generatore etichetta device text solo dove il RECORD prova di aver contenuto una stringa, cosa che solo il BMS Gen3 dichiara. Sottostimare si recupera, sovrastimare no. Questa nota diceva il contrario ed e' stata corretta dopo che una guardia ha confrontato la prosa col censimento reale dei badge della scena.
  • Questa finestra e la prova di redazione principale della Fase 2: farla fallire di proposito una volta e vedere scattare la verifica.

Fields in this window that need a caveat

  • capacitor_voltage_volts (MBB, not rendered) 4294967.196 e 0xFFFFFFFF in microvolt, cioe non misurato. Renderizzato tale e quale la pagina pubblica 4.294.967 V.
  • serial_number (MBB, not rendered) Due motivi, e servono entrambi perche la soppressione e per campo e per corsia, non per riga. Sulle righe Opening Contractor il valore e x seguito da un byte di controllo, oppure <: e un campo vuoto, non un seriale, e non va redatto come se lo fosse ne reso come dato. Sulle righe Registered della stessa corsia invece il seriale c e davvero (4.209 porta 8 caratteri veri), quindi sopprimere e anche la scelta piu prudente delle due. La stessa dichiarazione governa la colonna di upstream nel pannello verbatim: li il campo viene tolto perche non e elencato in hazard_fields_shown, e senza quella regola la redazione lo stampava come Serial Number: [redacted] su una riga che un seriale non lo contiene.
Only on this side · 34 fieldsbattery_ampsbattery_current_ampscontroller_typedischarge_current_ampsdischarge_current_maduty_cycle_percentevent_codeevent_typeis_external_chargeris_key_offis_key_onis_key_switchis_known_sourceis_onboard_chargeris_poweredkey_onand 18 more

Key on · SR/F 2023

Key onSR/F 2023Gen3Gen3 main board (ring buffer)Gen3MBBEntryMBBBMS30 lines · 4 srider’s log · redactedmeasured timestamps

Faults in this window 8 of 30 rows carry a warning or an error: 4 reach a code page of their own, 4 reach only the family they belong to, and none is left without a destination. 2 names here are carried by neither dash table — INVALID/NO CELL TYPE, ALL_CONTACTORS_OPEN. The rows say so rather than offering the nearest-looking code.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

E l EQUIVALENTE GEN3 della figura F, ed e diventato tale solo dopo aver riconciliato i due orologi. Su questa moto il MBB corre 605 s avanti al BMS: clock_offset_s -605, trovato per correlazione incrociata sui tick aperiodici e confermato da un evento fisico condiviso, perche Module 2 Contactor is now Closed sul MBB e Contactor closed - Contactor close command received sul BMS sono la stessa chiusura e nei due file distano esattamente 605 s. Rimesse su un solo orologio le due schede scrivono NELLO STESSO SECONDO: alle 16:43:51 la chiusura del contattore compare su entrambe. Due secondi prima ci sono i due voltmetri: il MBB legge il modulo 2 a 110.763 mV, il BMS legge il pacco a 110.757 e poi 110.760 mV. Tre e sei millivolt, non ventitre. I 23 mV che questa scena pubblicava erano autoscarica e non strumenti: le due letture stavano a 604 s l una dall altra e in quei dieci minuti, a -194 mA, il pacco scende da 110.760 mV (16:43:48) a 110.740 (16:53:52). Gap sull asse unito [0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1]: quattro secondi in tutto. Nella coppia intera i secondi co-scritti sono 68 sull asse pubblicato e UNO lasciando l offset a zero — e quell uno era tutta la prova su cui poggiava la frase le due schede Gen3 non si incontrano mai.

  • Due schede, un evento, lo stesso secondo, anche su Gen3: alle 16:43:51 il MBB scrive Module 2 Contactor is now Closed e il BMS scrive Contactor closed - Contactor close command received. Nei file grezzi quelle due righe distano 605 s, perche i due orologi non sono lo stesso orologio.
  • Due voltmetri: 110.763 mV sul MBB e 110.757 / 110.760 mV sul BMS, a due secondi. Lo scarto e di 3-6 mV. Confrontare due letture prese a dieci minuti di distanza ne dava 23, che e la scarica del pacco e non la differenza fra due strumenti.
  • Il MBB non misura le celle: CvLow 3954 mV, CvHigh 3957 mV e SOC 75% stanno solo sulla riga BMS. Il MBB non pubblica nessuno dei tre.
  • Il MBB pubblica la tensione dello STESSO modulo tre volte sotto tre nomi nello stesso secondo: Registering Mod 2 (110763mV), Precharging from module 2, Vpackoc (110763mV) e Connecting mod 2 (V_mod 110763mV).
  • Perche esiste la precarica, in una riga: Connecting mod 2 dice V_mod 110763mV e V_bus 0mV. Il condensatore del controller e a zero e il pacco a 110 V; chiudere secco farebbe un arco.
  • L accensione arma TRE guasti di proposito, ciascuno con un conto alla rovescia di 30 s (DC_DC_LOW, ALL_CONTACTORS_OPEN, MODULE_AWAITING_CONNECTION), e li cancella quando il contattore chiude. Un Fault pending non e un guasto.

Notes to the generator’s maintainer

  • source_no e None su tutte e 2.397 le voci di questo MBB: l ancora e per forza parse_index, verificata con timestamp e event_contains.
  • Il badge di provenienza e obbligatorio e MISTO in questa finestra: le righe MBB sono device text, le righe BMS sono our decode (il raw_line persistito e B2 entry #NNN [52B]).
  • clock_offset_s -605 e non -602: -602 e il picco della correlazione su tutta la sovrapposizione, -605 e lo scarto misurato sul contattore DENTRO questa finestra. La coppia deriva di 3 s nei quattro giorni che copre e la finestra vale piu del totale, perche a -602 la scena stamperebbe la chiusura del contattore del BMS PRIMA della precarica del MBB, che e fisicamente impossibile.

Fields in this window that need a caveat

  • pack_soc (MBB, not rendered) Alle righe 393 e 402 vale 32, che e 0x20, lo spazio ASCII letto all offset del SoC su un record che NON e di stato. Misurato: tutte e 78 le righe a 32 di questo file sono State change / Fault pending / Fault cleared, mai un record binario di stato. La copertura vera del SoC sul MBB Gen3 e 77 voci su 2.397, il 3,2%, non il 6% di §4.3.
Only on this side · 40 fieldsCvHighCvLowII_lim_CCI_lim_chgI_lim_dischgI_outSOCV_busV_bus_ocV_modambient_tempbms_report_modebms_sequencebms_state_codecell_high_mvand 24 more

Scene: riding

This is the one scene where the two platforms are not shown side by side, and the reason is not presentational. They do not measure the same quantities. Putting them in two aligned columns would imply that the missing entries are gaps, when they are the whole point.

A Gen2 main board riding is rich: motor revolutions, odometer, motor and controller temperature, pack voltage and current, all on one line, once a minute. A Gen3 main board riding writes state of charge and four temperatures — and no motor speed, no road speed, no odometer and no pack current, in 0 of 9,472 entries across the files measured here. Everything else about a Gen3 ride lives in the battery file.

That is why this app does not reconstruct Gen3 rides from the main-board file, and why a third-party tool that reads only the main board reports a Gen3 log as almost empty.

Who sees whatEntries carrying each normalised channelThe motorcyclemain board onlyGen2 MBBof 27,533Gen2 BMSof 32,436Gen3 MBBof 9,472Gen3 BMSof 3,607Road speed15,6950Motor speed7,0770Odometer5,6260Motor temperature5,567Controller temperature5,567238Vehicle state2,9211,664Charge current ceiling0668Module voltage227The overlapboth boards publish itGen2 MBBof 27,533Gen2 BMSof 32,436Gen3 MBBof 9,472Gen3 BMSof 3,607State of charge7,0189,139851945Pack voltage7,01818,6172271,167Pack current11,0069,01001,114Pack temperature7,0189,1391680Board or ambient temperature7,0189,138238The packbattery management onlyGen2 MBBof 27,533Gen2 BMSof 32,436Gen3 MBBof 9,472Gen3 BMSof 3,607Lowest cell9,1391,235Highest cell9,1391,167Cell spread9,139Cell at rest953Amp-hours discharged9,139Pack state9,008428A number is entries carrying it, with its shareof the file as a bar underneath.0 — the channel is counted here and nothing filled it.An arrow points at the file of the pair thathas it. A dash — it is not counted on this one.
The census from “Two boards”, drawn. Sorted by where a channel lives, the rule stops being a table to read and becomes a shape: the motorcycle block is a main-board block, the pack block is a battery-management block, and they never cross. A count rather than a tick, because a tick would say the same word at 851 entries and at 15,695, and those two facts send you to different files. Two kinds of blank are kept apart on purpose: a dashed 0 means the census counts the channel for that decoder and no entry filled it; a dash means it does not count it there at all. Read a dash as “not among the counted channels”, not as “absent from the file” — the module-voltage row is where those come apart, because the Gen2 main board publishes a module voltage in the extracts on this page under a field name the census does not carry.

A Gen2 ride

6 min of real riding, odometer 30,800 km, motor turning between 1,349 and 2,768 rpm. Both boards sample once a minute, 9 s out of step with each other, and it is exactly that stagger that makes the window readable as two lanes.

One sample is regenerative braking and it is unmistakable once you know the sign: at 15:25:15 the pack current is -15 A and the motor current is -37 A while the odometer keeps advancing. Negative is into the pack. The channel alone cannot tell you this is braking rather than a wall socket — the odometer beside it can.

Cell sag is visible inside a single record. At 15:23:06 the lowest cell reads 3.813 V under load and 3.848 V unloaded: 35 mV of sag, published by the pack as two separate numbers on the same line. The unloaded figure is not the third member of a high-low pair; it is the same cell measured with the load off.

FXS 2016 — Riding · 21 lines over 6 min · lanes MBB + BMS
BMS    880  15:22:06  INFO    Discharge level             amp_hours: 5, state_of_charge_percent: 75, current_amps: 8, voltage_low_cell_volts: 3.841, voltage_unloaded_cell_volts: 3.864, voltage_high_cell_volts: 3.875, voltage_balance_mv: 34, pack_temp_celsius: 33, bms_temp_celsius: 45, pack_voltage_volts: 107.793, pack_voltage_mv: 107793, mode: Bike OnBMS    881  15:22:06  INFO    SOC Data                    soc_raw_1: 1358, soc_raw_2: 5381, soc_raw_3: 5618, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 79, soc_percent_2: 75, soc_percent_3: 75, balance_or_current: 8MBB    929  15:22:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 75, pack_voltage_volts: 108.28, motor_temp_celsius: 44, controller_temp_celsius: 32, motor_rpm: 1349, battery_current_amps: 4, motor_current_amps: 5, ambient_temp_celsius: 28, odometer_km: 30800, mods: 11, mods_raw: 3BMS    882  15:23:06  INFO    Discharge level             amp_hours: 5, state_of_charge_percent: 74, current_amps: 12, voltage_low_cell_volts: 3.813, voltage_unloaded_cell_volts: 3.848, voltage_high_cell_volts: 3.85, voltage_balance_mv: 37, pack_temp_celsius: 33, bms_temp_celsius: 44, pack_voltage_volts: 107.082, pack_voltage_mv: 107082, mode: Bike OnBMS    883  15:23:06  INFO    SOC Data                    soc_raw_1: 1410, soc_raw_2: 5390, soc_raw_3: 5698, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 79, soc_percent_2: 74, soc_percent_3: 74, balance_or_current: 13MBB    930  15:23:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 74, pack_voltage_volts: 106.924, motor_temp_celsius: 46, controller_temp_celsius: 32, motor_rpm: 1744, battery_current_amps: 23, motor_current_amps: 48, ambient_temp_celsius: 28, odometer_km: 30800, mods: 11, mods_raw: 3BMS    884  15:24:06  INFO    Discharge level             amp_hours: 5, state_of_charge_percent: 72, current_amps: 12, voltage_low_cell_volts: 3.8, voltage_unloaded_cell_volts: 3.841, voltage_high_cell_volts: 3.833, voltage_balance_mv: 33, pack_temp_celsius: 32, bms_temp_celsius: 44, pack_voltage_volts: 106.714, pack_voltage_mv: 106714, mode: Bike OnBMS    885  15:24:06  INFO    SOC Data                    soc_raw_1: 1556, soc_raw_2: 5409, soc_raw_3: 5859, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 78, soc_percent_2: 73, soc_percent_3: 72, balance_or_current: 15MBB    931  15:24:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 72, pack_voltage_volts: 106.419, motor_temp_celsius: 48, controller_temp_celsius: 32, motor_rpm: 2303, battery_current_amps: 23, motor_current_amps: 36, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3BMS    886  15:25:06  INFO    Discharge level             amp_hours: 6, state_of_charge_percent: 69, current_amps: 6, voltage_low_cell_volts: 3.814, voltage_unloaded_cell_volts: 3.841, voltage_high_cell_volts: 3.841, voltage_balance_mv: 27, pack_temp_celsius: 33, bms_temp_celsius: 44, pack_voltage_volts: 107.039, pack_voltage_mv: 107039, mode: Bike OnBMS    887  15:25:06  INFO    SOC Data                    soc_raw_1: 1697, soc_raw_2: 5439, soc_raw_3: 6030, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 77, soc_percent_2: 73, soc_percent_3: 69, balance_or_current: 10MBB    932  15:25:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 71, pack_voltage_volts: 108.3, motor_temp_celsius: 49, controller_temp_celsius: 31, motor_rpm: 2028, battery_current_amps: -15, motor_current_amps: -37, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3BMS    888  15:26:06  INFO    Discharge level             amp_hours: 6, state_of_charge_percent: 66, current_amps: 28, voltage_low_cell_volts: 3.707, voltage_unloaded_cell_volts: 3.791, voltage_high_cell_volts: 3.746, voltage_balance_mv: 39, pack_temp_celsius: 32, bms_temp_celsius: 44, pack_voltage_volts: 104.253, pack_voltage_mv: 104253, mode: Bike OnBMS    889  15:26:06  INFO    SOC Data                    soc_raw_1: 1899, soc_raw_2: 5501, soc_raw_3: 6248, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 76, soc_percent_2: 68, soc_percent_3: 66, balance_or_current: 33MBB    933  15:26:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 68, pack_voltage_volts: 104.898, motor_temp_celsius: 50, controller_temp_celsius: 32, motor_rpm: 2568, battery_current_amps: 38, motor_current_amps: 54, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3BMS    890  15:27:06  INFO    Discharge level             amp_hours: 6, state_of_charge_percent: 63, current_amps: 20, voltage_low_cell_volts: 3.723, voltage_unloaded_cell_volts: 3.795, voltage_high_cell_volts: 3.756, voltage_balance_mv: 33, pack_temp_celsius: 32, bms_temp_celsius: 43, pack_voltage_volts: 104.667, pack_voltage_mv: 104667, mode: Bike OnBMS    891  15:27:06  INFO    SOC Data                    soc_raw_1: 2121, soc_raw_2: 5582, soc_raw_3: 6502, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 75, soc_percent_2: 68, soc_percent_3: 63, balance_or_current: 29MBB    934  15:27:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 66, pack_voltage_volts: 105.744, motor_temp_celsius: 51, controller_temp_celsius: 31, motor_rpm: 2768, battery_current_amps: 18, motor_current_amps: 22, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3BMS    892  15:28:06  INFO    Discharge level             amp_hours: 6, state_of_charge_percent: 61, current_amps: 12, voltage_low_cell_volts: 3.749, voltage_unloaded_cell_volts: 3.802, voltage_high_cell_volts: 3.774, voltage_balance_mv: 25, pack_temp_celsius: 32, bms_temp_celsius: 43, pack_voltage_volts: 105.304, pack_voltage_mv: 105304, mode: Bike OnBMS    893  15:28:06  INFO    SOC Data                    soc_raw_1: 2283, soc_raw_2: 5718, soc_raw_3: 6692, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 75, soc_percent_2: 69, soc_percent_3: 61, balance_or_current: 21MBB    935  15:28:15  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 64, pack_voltage_volts: 104.734, motor_temp_celsius: 51, controller_temp_celsius: 31, motor_rpm: 2546, battery_current_amps: 36, motor_current_amps: 51, ambient_temp_celsius: 27, odometer_km: 30800, mods: 11, mods_raw: 3

Decode route gen2-mbb (MBBEntry), generation 2. Provenance: 0 lines are device text and 21 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 740 of the same seconds across the whole pair. Timestamps: real. This is the maintainer’s own motorcycle.

A Gen3 ride, in two files that do not overlap

The pack file first, because it is the one with the ride in it. 22 consecutive samples, every gap exactly 60 seconds, state of charge falling from 79% to 68% — and not one datum of speed or distance. On Gen3, the kilometres are not in the battery log.

Voltage under load is the other lesson in that block. Pack voltage runs from 111.156 V down to 106.578 V and back as the current rises and falls. Voltage under load measures the load, not the charge.

The sign trap appears again, with a twist: 2 of those 22 samples are negative — the second reads I=-39157mA at 4.25 kW — and the pack labels both of them mode: Charge. Anyone counting charges by reading that field finds two of them in the middle of one journey. The field is not wrong; it is answering a narrower question than the one being asked.

Now the main board of a Gen3 that was genuinely being ridden. It is not silent — 5 state records in this window, one a minute, every one of them carrying a state of charge, and 3 of them carrying the four temperatures on the alternate sample. What it never carries is revolutions, speed, current or distance.

What it carries instead is the rider. Indicators on and cancelled, cruise control on and off, traction control blinking for an instant — and cruise set at 1860 RPM with imax 5000, then 1988 RPM with imax 5000. The motorcycle sets cruise control in motor revolutions because, in this file, it does not know its own speed.

The discharge ceiling moves sample by sample: curr 1341 cap 1341 act 2147483647 pow 148716 on the first one here. That is the power the pack is willing to give at that instant, in watts, and it falls as the state of charge does.

DSR/X 2023 — Riding · 22 lines over 21 min · lanes BMS
BMS   1199  10:01:06  INFO    BMS telemetry (Gen3)        SOC=79% CvLow=3965mV CvHigh=3971mV I=31930mABMS   1200  10:02:06  INFO    BMS telemetry (Gen3)        SOC=79% CvLow=3955mV CvHigh=3961mV I=40322mABMS   1201  10:03:06  INFO    BMS telemetry (Gen3)        SOC=78% CvLow=3920mV CvHigh=3928mV I=123580mABMS   1202  10:04:06  INFO    BMS telemetry (Gen3)        SOC=78% CvLow=3962mV CvHigh=3968mV I=-11907mABMS   1203  10:05:06  INFO    BMS telemetry (Gen3)        SOC=78% CvLow=3921mV CvHigh=3928mV I=66737mABMS   1204  10:06:06  INFO    BMS telemetry (Gen3)        SOC=77% CvLow=3915mV CvHigh=3922mV I=56974mABMS   1205  10:07:06  INFO    BMS telemetry (Gen3)        SOC=76% CvLow=3919mV CvHigh=3926mV I=48135mABMS   1206  10:08:06  INFO    BMS telemetry (Gen3)        SOC=76% CvLow=3918mV CvHigh=3925mV I=41343mABMS   1207  10:09:06  INFO    BMS telemetry (Gen3)        SOC=75% CvLow=3905mV CvHigh=3910mV I=53807mABMS   1208  10:10:06  INFO    BMS telemetry (Gen3)        SOC=74% CvLow=3893mV CvHigh=3899mV I=61759mABMS   1209  10:11:06  INFO    BMS telemetry (Gen3)        SOC=74% CvLow=3871mV CvHigh=3878mV I=75607mABMS   1210  10:12:06  INFO    BMS telemetry (Gen3)        SOC=73% CvLow=3876mV CvHigh=3882mV I=59185mABMS   1211  10:13:06  INFO    BMS telemetry (Gen3)        SOC=72% CvLow=3889mV CvHigh=3894mV I=33928mABMS   1212  10:14:06  INFO    BMS telemetry (Gen3)        SOC=72% CvLow=3859mV CvHigh=3868mV I=76913mABMS   1213  10:15:06  INFO    BMS telemetry (Gen3)        SOC=71% CvLow=3872mV CvHigh=3879mV I=47499mABMS   1214  10:16:06  INFO    BMS telemetry (Gen3)        SOC=71% CvLow=3857mV CvHigh=3863mV I=61271mABMS   1215  10:17:06  INFO    BMS telemetry (Gen3)        SOC=70% CvLow=3848mV CvHigh=3856mV I=56316mABMS   1216  10:18:06  INFO    BMS telemetry (Gen3)        SOC=69% CvLow=3842mV CvHigh=3849mV I=62188mABMS   1217  10:19:06  INFO    BMS telemetry (Gen3)        SOC=69% CvLow=3843mV CvHigh=3850mV I=55281mABMS   1218  10:20:06  INFO    BMS telemetry (Gen3)        SOC=68% CvLow=3855mV CvHigh=3860mV I=21136mABMS   1219  10:21:06  INFO    BMS telemetry (Gen3)        SOC=68% CvLow=3873mV CvHigh=3879mV I=-39157mABMS   1220  10:22:06  INFO    BMS telemetry (Gen3)        SOC=68% CvLow=3801mV CvHigh=3810mV I=121073mA

Decode route gen3-bms (Gen3BMSEntry), generation 3. Provenance: 0 lines are device text and 22 are our decode of the bytes. Timestamps: real.

SR/F 2021 — Riding · 23 lines over 5 min · lanes MBB
MBB   2120  17:10:43  INFO    UnknownMBB   2121  17:11:08  INFO    Control flags changed       old: 100000001100110, new: 100000001000110MBB   2122  17:11:43  INFO    UnknownMBB   2123  17:11:43  INFO    Disch limits                curr 1341 cap 1341 act 2147483647 pow 148716MBB   2124  17:11:43  INFO    Ch limits                   curr 140 cap 140 act 140 pow 15526MBB   2125  17:12:11  INFO    Right blinker activatedMBB   2126  17:12:16  INFO    Blinker cancelledMBB   2127  17:12:18  INFO    Cruise control turned on because of button pressMBB   2128  17:12:29  INFO    Cruise control set at 1860 RPM with imax 5000MBB   2129  17:12:43  INFO    UnknownMBB   2130  17:12:58  INFO    Cruise control set at 1988 RPM with imax 5000MBB   2131  17:13:21  INFO    Cruise control turned off because of button pressMBB   2132  17:13:43  INFO    UnknownMBB   2133  17:13:43  INFO    Disch limits                curr 1335 cap 1335 act 1335 pow 146316MBB   2134  17:13:43  INFO    Ch limits                   curr 140 cap 140 act 2147483647 pow 15344MBB   2135  17:13:49  INFO    Control flags changed       old: 100000001000110, new: 100000001100110MBB   2136  17:14:17  INFO    Control flags changed       old: 100000001100110, new: 100000001000110MBB   2137  17:14:18  INFO    MTC light changed           Old: 1 New: 2MBB   2138  17:14:18  INFO    MTC light changed           Old: 2 New: 1MBB   2139  17:14:43  INFO    UnknownMBB   2140  17:15:18  INFO    Right blinker activatedMBB   2141  17:15:27  INFO    Blinker cancelledMBB   2142  17:15:28  INFO    Left blinker activated

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 0 lines are device text and 23 are our decode of the bytes. Timestamps: real.

Why these are two blocks and not two columns

Aligning a Gen2 ride against a Gen3 ride would put an empty cell where the motor revolutions should be and invite the reader to treat it as a small omission. It is not: it is the reason Gen3 rides are reconstructed from the pack file and not from the vehicle file. Every other scene on this page is shown side by side. This one is not, on purpose.

Riding a Gen2: both boards, one clock

RidingFXS 2016Gen2Gen2 main boardMBBEntryMBBBMS21 lines · 6 min 9 smaintainer’s own logmeasured timestamps

Measured cadence in this window: MBB every 1 min over 7 entries.

Two boards, one clock

Both lanes are shown on their own timestamps. No correction was applied.

The two clocks agree: the best shift is +0 s, inside the 5 s this method can resolve.

measured 0 sco-written seconds 740 at this offset · 740 at nonedrift ±10 speak 2.17× the nearest rival
ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

Nove minuti di guida vera, odometro 30760 -> 30767 km. Gap [31,9,51,0,9,51,0,9,51,0,9,51,0,9,51,9,60,60,60,60]: le due schede campionano ENTRAMBE a 60 s ma sfasate di 9 s, ed e proprio quell alternanza 9/51 a rendere la scena leggibile a due corsie. Il rilevatore di interpolazione trova una sola run, di passo 60,0 s, che e cadenza di firmware. Scelta questa e non la corsa equivalente sulla DS 2014 (gap 60 s puliti) per due ragioni misurate: e la moto del manutentore, e contiene alle 15:25:15 un campione a battery_current_amps -15 e motor_current_amps -37, cioe la RIGENERAZIONE, che sulle altre finestre di guida della famiglia non capita.

  • Il motore lo vede SOLO il MBB: motor_rpm, motor_temp_celsius, controller_temp_celsius e odometer_km non compaiono su nessuna riga BMS di questa finestra. Le celle le vede SOLO il BMS: voltage_low/high_cell_volts e voltage_balance_mv non compaiono su nessuna riga MBB.
  • Il sag e dentro un record solo: voltage_low_cell_volts 3.813 contro voltage_unloaded_cell_volts 3.848 nella STESSA riga. 35 mV di caduta sotto carico, pubblicati dal BMS come due numeri distinti.
  • Alle 15:25:15 battery_current_amps e -15 e motor_current_amps -37 mentre l odometro avanza: e frenata rigenerativa. Positivo uguale scarica. Il canale da solo non separa la rigenerazione dalla ricarica a muro.
  • I due SoC coincidono al punto (MBB 74% alle 15:23:15, BMS 74% alle 15:23:06). Su tutta la coppia: 1.048 confronti entro 30 s, differenza 0 in 989 casi. Se divergono di piu di un punto non e rumore.
  • Nella riga SOC Data pack_voltage_volts vale 26.0 e nella riga Discharge level a un secondo di distanza vale 107.082: lo stesso nome di campo, due significati, nello stesso file. Il primo e la taglia del pacco, non una tensione.

Notes to the generator’s maintainer

  • Moto del manutentore: e la sola scena, con la 6, in cui il consenso e pieno.
  • Questa e la meta Gen2 della scena guida. NON va affiancata alla meta Gen3 (scene 11 e 12): §6 traccia qui la linea netta.

Fields in this window that need a caveat

  • pack_voltage_mv (BMS, rendered with the caveat) Sulle righe SOC Data vale 26000 / 26.0 V su TUTTE le occorrenze di questo file (57400 sulla FXS 2019, 114800 su un pacco a 4 brick): e una costante di configurazione del pacco, non una tensione. La misura vera e sulla riga Discharge level accanto, 107.082 V. Va reso, ed etichettato: il contrasto fra le due righe adiacenti e la lezione.
0 s
0 s
+9 s
+1 min
+1 min
+1 min 9 s
+2 min
+2 min
+2 min 9 s
+3 min
+3 min
+3 min 9 s
+4 min
+4 min
+4 min 9 s
+5 min
+5 min
+5 min 9 s
+6 min
+6 min
+6 min 9 s

Riding a Gen3: everything the ride knows is in the battery file

RidingDSR/X 2023Gen3Gen3 battery management (ring buffer)Gen3BMSEntryBMS22 lines · 21 minrider’s log · redactedmeasured timestamps

Measured cadence in this window: BMS every 1 min over 22 entries.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived

Why 2 rows are banded “Charging” in a riding window. Those samples carry a negative pack current and the pack’s own mode: Charge, and the classifier here is the app’s, unmodified. It is regenerative braking: Negative current is energy entering the pack. While riding that is regen; while parked and plugged in it is the charger. The current channel cannot tell them apart — only the surrounding session can. Leaving the band as the app draws it, and explaining it, is the point of the window — a page that recoloured these rows would be teaching you to read a channel that cannot carry the distinction.

Editor’s note on this window — why it was chosen, and what it teaches (Italian)

Ventidue campioni consecutivi, ventuno gap tutti esattamente 60 s: cadenza reale di firmware. Una corsia sola per obbligo e non per scelta, ed e questo il punto della linea netta: il MBB Gen3 di questa STESSA moto non porta nessuna corrente, nessun giro motore, nessun odometro e nessuna velocita — battery_amps, motor_rpm, odometer_km e speed_kmh sono a zero valorizzati su tutte e 2.382 le voci. Finestra scelta perche contiene DUE campioni a corrente negativa in mezzo a venti positivi (-11.907 mA e -39.157 mA): sono rigenerazione, e il parser li etichetta mode Charge. E la prova visibile dell invariante di lib/current-sign.ts, cioe che il canale da solo non separa la rigenerazione dalla ricarica a muro.

  • Il BMS Gen3 in guida e la scheda ricca: SoC, cella minima e massima, corrente, tensione di pacco e kW ogni 60 secondi. Il BMS Gen2 non ha questa completezza, e il MBB Gen3 non ha niente di tutto questo.
  • Positivo uguale scarica. I due campioni negativi in mezzo alla guida sono rigenerazione: -39,157 A a 4,25 kW rientrano nel pacco a meta discesa, mentre il giro continua.
  • Il campo mode dice Charge su quelle due righe. Chi conta le cariche leggendo mode ne trova due dentro un giro solo. Serve il contesto, non il campo.
  • La tensione affonda con la corrente e risale quando molla: 111,156 V a 31,9 A e 106,578 V a 121,1 A. La cella minima segue, 3.965 -> 3.801 mV. La tensione sotto carico misura il carico, non la carica.
  • Undici punti di SoC in ventuno minuti e non un solo dato di velocita o di distanza: su Gen3 i chilometri percorsi NON sono nel log del pacco.

Notes to the generator’s maintainer

  • raw_line persistito: B2 entry #NNN [52B]. Il badge our decode e obbligatorio su ogni riga — il significato sta interamente nei campi decodificati.
  • Meta BMS della scena guida Gen3. Va resa in un blocco separato dalla 12 e da entrambe separata dalla 2: §6, niente colonne allineate.

Fields in this window that need a caveat

  • mode (BMS, rendered with the caveat) Vale Charge sui due campioni di rigenerazione in mezzo alla guida. Letto da solo, il campo conta due cariche dentro un giro. Va reso, perche l equivoco e la lezione, ma etichettato.

Scene: charging

Charging is where the two platforms differ most in sampling rate, and the difference is not cosmetic: it decides which questions the file can answer at all.

A Gen2 samples a charge every 600 seconds. A Gen3 pack samples every 60 seconds, and its main board — charging, not riding — every 320 seconds. Three different cadences, and the third one shows that the interval depends on what the motorcycle is doing, not on the board or the generation.

The consequence is concrete. A charge that stops and restarts inside one Gen2 sampling interval leaves no trace at all, while on a Gen3 pack it is a visible sample — not because it did not happen on the Gen2, but because nothing was written while it did.

One charging sample every 600 seconds — or every 60

Two real charges on one ruler, then the same 10 min of each, then the second in which the Gen3 pack stops taking current. Nothing is summarised: every dot is a line that exists in a log file, and the spacing between dots is the spacing in the file.

  • Gen2 · DS 2014 · MBBone sample every 600 s → 2 in 10 min
  • Gen3 · DSR/X 2023 · BMSone sample every 60 s → 6 in 10 min
scale 1 of 3
The two windows on one ruler — 1 h 40 min across

The whole Gen3 scene is 3 min 25 s long, which is shorter than one Gen2 sampling interval. It holds 6 samples. The Gen2 holds 2 in the same stretch.

one 600 s interval — the panels belowGen2DS 201411 samples · 1 h 40 minGen3DSR/X 20236 samples · 3 min 25 s — the entire Gen3 window
scale 2 of 3
One Gen2 sampling interval — 600 s, at true scale

Both scenes start at zero and are drawn on the same seconds. The Gen2 line is 2 dots and one straight segment; that segment is an interpolation, and between its ends the file says nothing at all. The Gen3 line is 6 dots, and it runs out of window before the Gen2 takes its second reading.

State of charge

full scale, 0–100 · %

Pack current

+ discharge / − charge / regen · A

Below the zero rule the current is going into pack. positive = discharge (out of pack) · negative = charge or regen (into pack).

scale 3 of 3
The Gen3 window alone — 3 min 25 s across

Two consecutive samples share the second at +3 min 24 s: the stored current_ma goes from −15,864 mA to −13 mA, and the lowest cell relaxes 9 mV while nothing is flowing. Relaxation, not loss — the pack has not given anything back. There is no Gen2 line on these two panels because there is no Gen2 sample to draw: the next one is due at +10 min.

Pack current

Gen3 pack only · A

Lowest cell

axis cropped to 18 mV · mV
Every plotted value, as a table17 samples
Gen2 · DS 2014 · MBB · route gen2-bms · 11 samples over 1 h 40 min
elapsedlog clockeventState of charge (%)Pack current (A)as stored
008:41:40Charging77−9 A−9 A
+10 min08:51:40Charging79−10 A−10 A
+20 min09:01:40Charging81−9 A−9 A
+30 min09:11:40Charging83−13 A−13 A
+40 min09:21:40Charging84−13 A−13 A
+50 min09:31:40Charging86−12 A−12 A
+60 min09:41:40Charging88−14 A−14 A
+70 min09:51:40Charging90−14 A−14 A
+80 min10:01:40Charging92−9 A−9 A
+90 min10:11:40Charging94−13 A−13 A
+100 min10:21:40Charging100−1 A−1 A
Gen3 · DSR/X 2023 · BMS · route gen3-bms · 6 samples over 3 min 25 s
elapsedlog clockeventState of charge (%)Pack current (A)as storedLowest cell (mV)Pack mode
007:41:39BMS telemetry (Gen3)59−15.872 A−15,872 mA3,806Charge
+1 min07:42:39BMS telemetry (Gen3)59−15.914 A−15,914 mA3,808Charge
+2 min07:43:39BMS telemetry (Gen3)59−15.880 A−15,880 mA3,810Charge
+3 min07:44:39BMS telemetry (Gen3)59−15.923 A−15,923 mA3,812Charge
+3 min 24 s07:45:03BMS telemetry (Gen3)59−15.864 A−15,864 mA3,813Charge
+3 min 24 s07:45:03BMS telemetry (Gen3)590.000 A−13 mA3,804Idle
Charge sampling cadence, Gen2 against Gen3. Gen2 is a DS 2014 read on its main board (gen2-mbb), where the Charging lines carry pack_soc and battery_amps together every 600 s. Gen3 is a DSR/X 2023 read on its pack (gen3-bms), where the telemetry records carry soc, current_ma and the cell voltages every 60 s — its main board is not an alternative, because on that route the current channel is empty and the state-of-charge field is suppressed as unreliable. Current is stored in amperes on one and in milliamps on the other; both are plotted in amperes and both readings are in the table. The interruption at +3 min 24 s is a contactor event, not a charger fault: the line that names it is ␂Sending first contactor close command to PackIO — the leading is a control byte the decoded string really carries — and it does not contain the word charger. This window ends 1 s after it, so the restart is outside the drawing. The straight Gen2 segment in the middle panel is drawn between two measured ends and measures nothing in between; that gap is the subject of the figure, not an artefact of it. The lowest-cell axis is cropped to 18 mV out of roughly 3,800 mV, because a 9 mV relaxation is invisible on a full-scale cell axis. Dates in these two scenes are shifted by a whole number of days, so every interval is exact and no absolute date survives.

Four charges

The Gen2 window is the tail of a long charge rather than its start, because the interesting things happen at the top. Charged To Full arrives at 84% with the highest cell at 4.028 V. That is not a bug: the pack’s criterion for full is cell voltage, not the counter on the dash.

Half an hour later the pack corrects itself out loud: SOC off by over 5%, adjusted from 94% to 100%, followed by a switch to voltage-based estimation. The jump is a recalibration of the coulomb counter, not six percentage points of energy entering the pack. A tool that treats it as energy will over-count that charge.

The two boards agree while all this is going on. In this window every main-board sample has a pack sample 1 second away, and all 5 of the 5 pairs agree exactly: 77/77, 79/79, 81/81, 83/83, 84/84. The main board is not measuring this; it is relaying it.

The Gen3 window is the interruption. Between I=-15864mA and I=-13mA — both inside the second 07:45:03 — the current disappears and the lowest cell relaxes from 3,813 to 3,804 mV, 9 mV of recovery. That is relaxation, not degradation: nothing left the pack.

The name of the event is in the next line, and it is not the word anyone expects: Sending first contactor close command to PackIO. A second later the main board’s own record decodes as State change with from: CHRG, to: HIB. Note also which board has which number: the charge ceiling is the main board’s (Ch limitscurr 89 cap 89 act 89 pow 0) and the current and the cell voltages are the pack’s. Neither board holds both.

Two samples share the second 07:45:03, and that is the ring buffer writing two records inside one second, not a mistake in the extraction. It is left in because normalising it away would hide the resolution the format really has.

The third window is the same charge finishing, seen by the Gen3 main board, and it contains a taper you can read without an ammeter. The charge current ceiling falls from 41 A to 37 A inside the window as the pack fills — constant current becoming constant voltage. The charger also declares its own size: 3kW appears 6 times here, twice per record, once per charging unit. Counting hardware means counting those lines.

The fourth window is the fifth route. A 2023 FXE, charging, and every periodic line is called Discharge level — the event name does not change when the current reverses. What says it is charging is the field: mode: Charge. Anybody filtering that motorcycle’s charges by event name finds none of them.

Two more things in that window are worth carrying away. Charged To Full arrives at 90% and the charge continues to 94% in the lines after it — the same behaviour as the 2014 DS above, nine model years apart. And amp_hours counts DOWN, 11 to 3, because on a charge it is the amp-hours still missing rather than the amp-hours delivered.

DS 2014 — Charging · 22 lines over 100 min · lanes MBB + BMS
MBB   4358  08:41:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 77, pack_voltage_volts: 110.208, battery_current_amps: -9, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   3132  08:41:41  INFO    Discharge level             amp_hours: 21, state_of_charge_percent: 77, current_amps: -10, voltage_low_cell_volts: 3.934, voltage_unloaded_cell_volts: 3.93, voltage_high_cell_volts: 3.939, voltage_balance_mv: 5, pack_temp_celsius: 40, bms_temp_celsius: 47, pack_voltage_volts: 110.233, pack_voltage_mv: 110233, mode: ChargeMBB   4359  08:51:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 79, pack_voltage_volts: 110.867, battery_current_amps: -10, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   3133  08:51:41  INFO    Discharge level             amp_hours: 19, state_of_charge_percent: 79, current_amps: -14, voltage_low_cell_volts: 3.956, voltage_unloaded_cell_volts: 3.95, voltage_high_cell_volts: 3.961, voltage_balance_mv: 5, pack_temp_celsius: 40, bms_temp_celsius: 47, pack_voltage_volts: 110.853, pack_voltage_mv: 110853, mode: ChargeMBB   4360  09:01:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 81, pack_voltage_volts: 111.548, battery_current_amps: -9, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   3134  09:01:41  INFO    Discharge level             amp_hours: 18, state_of_charge_percent: 81, current_amps: -9, voltage_low_cell_volts: 3.983, voltage_unloaded_cell_volts: 3.979, voltage_high_cell_volts: 3.987, voltage_balance_mv: 4, pack_temp_celsius: 40, bms_temp_celsius: 48, pack_voltage_volts: 111.595, pack_voltage_mv: 111595, mode: ChargeMBB   4361  09:11:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 83, pack_voltage_volts: 112.208, battery_current_amps: -13, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   3135  09:11:41  INFO    Discharge level             amp_hours: 16, state_of_charge_percent: 83, current_amps: -11, voltage_low_cell_volts: 4.007, voltage_unloaded_cell_volts: 4.002, voltage_high_cell_volts: 4.012, voltage_balance_mv: 5, pack_temp_celsius: 40, bms_temp_celsius: 48, pack_voltage_volts: 112.284, pack_voltage_mv: 112284, mode: ChargeBMS   3136  09:18:32  INFO    Charged To Full             amp_hours: 15, state_of_charge_percent: 84, voltage_low_cell_volts: 4.024, voltage_high_cell_volts: 4.028, voltage_balance_mv: 4, pack_temp_celsius: 40, bms_temp_celsius: 48, pack_voltage_volts: 112.739, event_type: charge_completeMBB   4362  09:21:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 84, pack_voltage_volts: 112.935, battery_current_amps: -13, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   3137  09:21:41  INFO    Discharge level             amp_hours: 14, state_of_charge_percent: 84, current_amps: -13, voltage_low_cell_volts: 4.033, voltage_unloaded_cell_volts: 4.027, voltage_high_cell_volts: 4.037, voltage_balance_mv: 4, pack_temp_celsius: 40, bms_temp_celsius: 48, pack_voltage_volts: 112.987, pack_voltage_mv: 112987, mode: ChargeMBB   4363  09:31:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 86, pack_voltage_volts: 113.668, battery_current_amps: -12, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseMBB   4364  09:41:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 88, pack_voltage_volts: 114.361, battery_current_amps: -14, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseMBB   4365  09:51:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 90, pack_voltage_volts: 115.054, battery_current_amps: -14, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseMBB   4366  09:56:50  INFO    MBB BT RX Buffer Overflow   Data:MBB   4367  10:01:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 92, pack_voltage_volts: 115.844, battery_current_amps: -9, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseMBB   4368  10:11:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 94, pack_voltage_volts: 116.547, battery_current_amps: -13, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   3138  10:12:42  DEBUG   SOC off by over 5%, adjusted from 94% to 100%MBB   4369  10:18:42  INFO    MBB BT RX Buffer Overflow   Data:BMS   3139  10:19:34  INFO    SOC adjusted for voltage    old_capacity_microamp_hours: 0, old_state_of_charge_percent: 100, new_capacity_microamp_hours: 0, new_state_of_charge_percent: 100, low_cell_voltage_millivolts: 4159, capacity_change_microamp_hours: 0, soc_change_percent: 0BMS   3140  10:19:51  DEBUG   Switching to voltage SOC mode. SOC: 100%, VSOC: 100%, I: -2250mAMBB   4370  10:21:40  INFO    Charging                    pack_temp_high_celsius: 40, pack_temp_low_celsius: 36, ambient_temp_celsius: 29, state_of_charge_percent: 100, pack_voltage_volts: 116.454, battery_current_amps: -1, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: False

Decode route gen2-bms (BMSEntry), generation 2. Provenance: 0 lines are device text and 22 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 350 of the same seconds across the whole pair. Timestamps: real.

DSR/X 2023 — Charging · 18 lines over 3 min · lanes MBB + BMS
BMS   1036  07:41:39  INFO    BMS telemetry (Gen3)        SOC=59% CvLow=3806mV CvHigh=3812mV I=-15872mABMS   1037  07:42:39  INFO    BMS telemetry (Gen3)        SOC=59% CvLow=3808mV CvHigh=3815mV I=-15914mABMS   1038  07:43:39  INFO    BMS telemetry (Gen3)        SOC=59% CvLow=3810mV CvHigh=3817mV I=-15880mABMS   1039  07:44:39  INFO    BMS telemetry (Gen3)        SOC=59% CvLow=3812mV CvHigh=3819mV I=-15923mABMS   1040  07:45:03  INFO    BMS telemetry (Gen3)        SOC=59% CvLow=3813mV CvHigh=3820mV I=-15864mABMS   1044  07:45:03  INFO    BMS telemetry (Gen3)        SOC=59% CvLow=3804mV CvHigh=3812mV I=-13mABMS   1195  07:45:03  INFO    Sending first contactor close command to PackIOBMS   1400  07:45:03  INFO    BMS → HIBERNATE             Entering ZERO_BMS_STATE_HIBERNATEMBB   1018  07:45:04  INFO    State change                from: CHRG, to: HIBMBB   1019  07:45:04  INFO    MTC pending change by 3     from: 0, to: 0MBB   1020  07:45:04  INFO    UnknownMBB   1021  07:45:04  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB   1022  07:45:04  INFO    Ch limits                   curr 89 cap 89 act 89 pow 0MBB   1023  07:45:04  INFO    UnknownMBB   1024  07:45:04  INFO    Control flags changed       old: 100000001110001, new: 000000001110001MBB   1025  07:45:04  INFO    Disable flags changed       old: 10000001, new: 10000101MBB   1026  07:45:04  INFO    State change                from: HIB, to: WAITMBB   1027  07:45:04  INFO    Stopping 12v charge         DC-DC: 13245360uV, Battery: 13219720uV, Combined: 13142844uV, Ambient: 37C

Decode route gen3-bms (Gen3BMSEntry), generation 3. Provenance: 1 line is device text and 17 are our decode of the bytes. The two boards keep separate clocks. The main board runs 2,697 s ahead of the pack here, and the lanes are merged with that offset applied: 72 co-written seconds across the pair with it, 3 without. Timestamps: real.

SR/F 2021 — Charging · 26 lines over 35 min · lanes MBB
MBB    112  16:18:25  INFO    UnknownMBB    113  16:23:45  INFO    UnknownMBB    114  16:23:45  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB    115  16:23:45  INFO    Ch limits                   curr 41 cap 41 act 41 pow 0MBB    116  16:23:45  INFO    3kWMBB    117  16:23:45  INFO    3kWMBB    118  16:29:05  INFO    UnknownMBB    119  16:29:05  INFO    UnknownMBB    120  16:34:25  INFO    UnknownMBB    121  16:34:25  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB    122  16:34:25  INFO    Ch limits                   curr 41 cap 41 act 41 pow 0MBB    123  16:34:25  INFO    3kWMBB    124  16:34:25  INFO    3kWMBB    125  16:39:45  INFO    UnknownMBB    126  16:45:05  INFO    UnknownMBB    127  16:45:05  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB    128  16:45:05  INFO    Ch limits                   curr 37 cap 37 act 37 pow 0MBB    129  16:45:05  INFO    3kWMBB    130  16:45:05  INFO    3kWMBB    131  16:50:25  INFO    UnknownMBB    132  16:50:25  INFO    UnknownMBB    133  16:53:34  INFO    Control flags changed       old: 100000001110001, new: 100010001110001MBB    134  16:53:34  INFO    State change                from: CHRG, to: HIBMBB    135  16:53:34  INFO    MTC pending change by 3     from: 0, to: 0MBB    136  16:53:34  INFO    Disable flags changed       old: 100001, new: 100101MBB    137  16:53:34  INFO    Stopping 12v charge         DC-DC: 13235104uV, Battery: 13255616uV, Combined: 13201452uV, Ambient: 27C

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 0 lines are device text and 26 are our decode of the bytes. Timestamps: real.

FXE 2023 — Charging · 16 lines over 60 min · lanes BMS
BMS   1340  18:03:49  INFO    Discharge level             amp_hours: 11, state_of_charge_percent: 84, current_amps: -5, voltage_low_cell_volts: 3.967, voltage_unloaded_cell_volts: 3.953, voltage_high_cell_volts: 3.969, voltage_balance_mv: 2, pack_temp_celsius: 29, bms_temp_celsius: 36, pack_voltage_volts: 111.097, pack_voltage_mv: 111097, mode: ChargeBMS   1341  18:03:49  INFO    SOC Data                    soc_raw_1: 9048, soc_raw_2: 56469, soc_raw_3: 11478, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 81, soc_percent_2: 84, soc_percent_3: 84, balance_or_current: -10BMS   1342  18:13:56  INFO    Discharge level             amp_hours: 10, state_of_charge_percent: 86, current_amps: -5, voltage_low_cell_volts: 3.983, voltage_unloaded_cell_volts: 3.969, voltage_high_cell_volts: 3.986, voltage_balance_mv: 3, pack_temp_celsius: 29, bms_temp_celsius: 36, pack_voltage_volts: 111.571, pack_voltage_mv: 111571, mode: ChargeBMS   1343  18:13:56  INFO    SOC Data                    soc_raw_1: 8249, soc_raw_2: 56607, soc_raw_3: 10561, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 82, soc_percent_2: 85, soc_percent_3: 86, balance_or_current: -10BMS   1344  18:23:56  INFO    Discharge level             amp_hours: 9, state_of_charge_percent: 88, current_amps: -5, voltage_low_cell_volts: 4.001, voltage_unloaded_cell_volts: 3.988, voltage_high_cell_volts: 4.003, voltage_balance_mv: 2, pack_temp_celsius: 29, bms_temp_celsius: 36, pack_voltage_volts: 112.064, pack_voltage_mv: 112064, mode: ChargeBMS   1345  18:23:56  INFO    SOC Data                    soc_raw_1: 7313, soc_raw_2: 56591, soc_raw_3: 9659, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 84, soc_percent_2: 87, soc_percent_3: 88, balance_or_current: -10BMS   1346  18:33:56  INFO    Discharge level             amp_hours: 8, state_of_charge_percent: 89, current_amps: -5, voltage_low_cell_volts: 4.018, voltage_unloaded_cell_volts: 4.005, voltage_high_cell_volts: 4.021, voltage_balance_mv: 3, pack_temp_celsius: 29, bms_temp_celsius: 37, pack_voltage_volts: 112.547, pack_voltage_mv: 112547, mode: ChargeBMS   1347  18:33:56  INFO    SOC Data                    soc_raw_1: 6405, soc_raw_2: 56612, soc_raw_3: 8749, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 85, soc_percent_2: 89, soc_percent_3: 89, balance_or_current: -10BMS   1348  18:37:58  INFO    Charged To Full             amp_hours: 8, state_of_charge_percent: 90, voltage_low_cell_volts: 4.025, voltage_high_cell_volts: 4.028, voltage_balance_mv: 3, pack_temp_celsius: 29, bms_temp_celsius: 38, pack_voltage_volts: 112.73, event_type: charge_completeBMS   1349  18:37:58  INFO    SOC adjusted for voltage    old_capacity_microamp_hours: 6057409, old_state_of_charge_percent: 90, new_capacity_microamp_hours: 5663423, new_state_of_charge_percent: 90, low_cell_voltage_millivolts: 4025, capacity_change_microamp_hours: -393986, soc_change_percent: 0BMS   1350  18:43:56  INFO    Discharge level             amp_hours: 5, state_of_charge_percent: 91, current_amps: -5, voltage_low_cell_volts: 4.036, voltage_unloaded_cell_volts: 4.023, voltage_high_cell_volts: 4.039, voltage_balance_mv: 3, pack_temp_celsius: 29, bms_temp_celsius: 38, pack_voltage_volts: 113.062, pack_voltage_mv: 113062, mode: ChargeBMS   1351  18:43:56  INFO    SOC Data                    soc_raw_1: 5435, soc_raw_2: 56960, soc_raw_3: 5197, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 91, soc_percent_2: 90, soc_percent_3: 91, balance_or_current: -9BMS   1352  18:53:56  INFO    Discharge level             amp_hours: 4, state_of_charge_percent: 93, current_amps: -5, voltage_low_cell_volts: 4.054, voltage_unloaded_cell_volts: 4.041, voltage_high_cell_volts: 4.057, voltage_balance_mv: 3, pack_temp_celsius: 29, bms_temp_celsius: 41, pack_voltage_volts: 113.555, pack_voltage_mv: 113555, mode: ChargeBMS   1353  18:53:56  INFO    SOC Data                    soc_raw_1: 4478, soc_raw_2: 56928, soc_raw_3: 4291, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 93, soc_percent_2: 92, soc_percent_3: 93, balance_or_current: -10BMS   1354  19:03:56  INFO    Discharge level             amp_hours: 3, state_of_charge_percent: 94, current_amps: -5, voltage_low_cell_volts: 4.073, voltage_unloaded_cell_volts: 4.06, voltage_high_cell_volts: 4.075, voltage_balance_mv: 2, pack_temp_celsius: 29, bms_temp_celsius: 41, pack_voltage_volts: 114.085, pack_voltage_mv: 114085, mode: ChargeBMS   1355  19:03:56  INFO    SOC Data                    soc_raw_1: 3515, soc_raw_2: 56893, soc_raw_3: 3382, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 95, soc_percent_2: 94, soc_percent_3: 94, balance_or_current: -10

Decode route gen3-bike-gen2-bms (BMSEntry), generation 3. Provenance: 0 lines are device text and 16 are our decode of the bytes. Timestamps: real.

The same field name, two meanings, one file

In the FXE window the periodic line reports pack_voltage_mv: 111097 and the configuration line one second later reports pack_voltage_mv: 57400 under the identical name. The first is a measurement of the pack. On a SOC Data line pack_voltage_mv is the pack’s nominal capacity in milliamp-hours, not a voltage. It never moves inside a file — the 4 windows here that carry one hold 26,000, 57,400, 104,000, 114,800 and nothing else — while the Discharge level line a second away carries the same field name and a voltage that does move. The pack confirms it in its own words elsewhere in the corpus: Adjusted Capacity was 0, resetting to 114800000 uAH based on 4 bricks, which is 114,800 mAh at four bricks and 57,400 at two. Rendered without a caveat, the second one puts a motorcycle on the page running at 57.4 V — a number with no meaning at all, because it is a capacity read as a voltage.

Charging: Gen2 beside Gen3

Charging · DS 2014

ChargingDS 2014Gen2Gen2 battery managementBMSEntryMBBBMS22 lines · 1 h 40 minrider’s log · redactedmeasured timestamps

Measured cadence in this window: MBB every 10 min over 8 entries.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

La CODA di una ricarica completa di sei ore e mezza (SoC 22 -> 100, 40 campioni MBB a passo esattamente 600 s), non l inizio, perche e nella coda che succede la cosa che la prosa non sa raccontare: il BMS dichiara Charged To Full all 84% e mezz ora dopo si autocorregge saltando da 94% a 100%. Gap [1,599,1,599,1,599,1,411,188,1,599,600,600,310,290,600,62,360,52,17,109]: 599/600 e la cadenza vera del firmware in carica, il resto sono eventi asincroni. Le due schede scrivono in lockstep a un secondo di distanza e il loro SoC coincide esattamente su cinque coppie consecutive (77/77, 79/79, 81/81, 83/83, 84/84).

  • In carica il campionamento e ogni 600 secondi. Dieci minuti fra un punto e l altro: la pausa del caricatore che Gen3 mostra al singolo campione qui e invisibile per costruzione, non per caso.
  • battery_current_amps -9 e current_amps -10 sono corrente CHE ENTRA nel pacco. E lo stesso campo che in guida e positivo: negativo uguale carica.
  • Charged To Full NON significa 100%: arriva a SoC 84% con la cella piu alta a 4.028 V. Il criterio e la tensione di cella, non il contatore.
  • Il contatore coulombico deriva e il BMS lo ricalibra: SOC off by over 5%, adjusted from 94% to 100% seguito da Switching to voltage SOC mode. Il salto 94 -> 100 e una correzione, non sei punti di energia entrati nel pacco.
  • mbb_charge_enabled: True e bms_charge_enabled: False convivono su ogni riga Charging: le due schede hanno due permessi di carica distinti, e la carica procede lo stesso.

Notes to the generator’s maintainer

  • Questa e la meta Gen2 dell affiancamento della carica; la meta Gen3 e la scena 13. Il contrasto da mettere in evidenza e 600 s contro 60 s, non chi carica meglio.
  • Dati per la figura C: qui SoC e corrente sono su righe MBB Charging a passo 600 s e su righe BMS Discharge level a un secondo di distanza.
Only on this side · 38 fieldsah_dischargedambient_tempambient_temp_celsiusamp_hoursbalance_mvbattery_ampsbattery_current_ampsbms_charge_enabledbms_temp_celsiusboard_temp_ccapacity_change_microamp_hourscurrent_ampsevent_typeis_charginglow_cell_voltage_millivoltsmbb_charge_enabledand 22 more

Charging · DSR/X 2023

ChargingDSR/X 2023Gen3Gen3 battery management (ring buffer)Gen3BMSEntryMBBBMS18 lines · 3 min 25 srider’s log · redactedmeasured timestamps
ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

E l interruzione di carica che §6 cita, a due corsie e finalmente sullo stesso orologio. Questa moto ha il MBB 2.697 s avanti al BMS (clock_offset_s -2697, confermato su sei chiusure di contattore dello stesso giorno e sull aritmetica dell ibernazione), e sull asse grezzo le righe MBB che cadevano in questa finestra venivano da quarantacinque minuti prima: la scena affiancava due momenti diversi e li stampava a tre secondi. Corretto l asse, quello che si vede e UNA SOLA DECISIONE PRESA DA DUE SCHEDE A UN SECONDO DI DISTANZA: alle 07:45:03 il BMS registra la corrente che sparisce (-15.864 mA e, nello stesso secondo, -13 mA), manda Sending first contactor close command to PackIO ed entra in HIBERNATE; alle 07:45:04 il MBB scrive State change from: CHRG, to: HIB. Il limite di carica lo dichiara solo il MBB (Ch limits curr 89), la corrente e la tensione di cella le misura solo il BMS: nessuna delle due schede ha entrambi i numeri. Gap sull asse unito [60,60,60,24,0,0,0,1,0,0,0,0,0,0,0,0,0] — i 60 s sono la cadenza vera del BMS, il 24 e il campione fuori cadenza che accompagna l evento, l 1 e il ritardo fra le due schede.

  • Il caricatore si ferma e il BMS lo vede al singolo campione: da -15.864 mA a -13 mA dentro lo stesso secondo. Su Gen2, a 600 s di passo, questo buco non esiste come dato.
  • Con la corrente a zero la cella si rilassa di 9 mV, da 3.813 a 3.804 mV, sempre dentro quel secondo. E rilassamento, non degrado: il pacco non ha perso nulla.
  • Non e una pausa del caricatore, e il CONTATTORE: il nome giusto dell evento sta in Sending first contactor close command to PackIO, una riga che non contiene la parola caricatore.
  • Il limite di carica lo dichiara il MBB (Ch limits curr 89), la corrente e le celle le misura il BMS. Nessuna delle due schede ha entrambi i numeri, e la carica non si spiega con uno solo.
  • Su Gen3 il SoC delle due schede COINCIDE, una volta che gli orologi sono allineati: su 23 campioni lungo questa carica lo scarto MBB-BMS sta fra -1 e +3 punti ed e zero in 5 casi. Sui timestamp grezzi lo stesso confronto dava da -9 a -4 punti e mai zero, e quei sei punti di divario erano quarantacinque minuti di carica a 15 A.

Notes to the generator’s maintainer

  • Due campioni condividono il secondo 07:45:03 e non e un errore nostro: e il ring buffer che scrive due record. Va mostrato e spiegato, non normalizzato.
  • Dati per la figura C insieme alla scena 3: qui la cadenza e 60 s con un campione fuori passo sull evento, li 600 s senza alcun buco.
  • La finestra si ferma prima della ripartenza (il contattore richiude 12 s dopo, e li le due schede scrivono nello STESSO secondo) perche in mezzo il MBB stampa ventuno righe di flag e la scena passerebbe da 18 a 57 righe. Il confronto MBB/BMS di una grandezza condivisa — SoC, tensione, corrente — non va MAI fatto sui timestamp grezzi: e questa la scena che lo dimostra.

Fields in this window that need a caveat

  • source_no (BMS, rendered with the caveat) In questa finestra #1041, #1044 e #1047 compaiono DUE VOLTE ciascuno e #1195 e #1400 cadono in mezzo alla serie #1040-#1051. Il contatore del BMS Gen3 non e univoco (189 duplicati su 1184 distinti nel file) ne monotono (decresce 10 volte su 1.373 voci). Sono voci DIVERSE: il generatore non deve deduplicare, e non deve ordinare su questo campo.
Only on this side · 30 fieldsAmbientBatteryCombinedCvHighCvLowDC-DCISOCbms_report_modebms_sequencebms_state_codebms_state_namecell_ocv_low_mvcharge_limit_currcharge_limit_powcontactor_stateand 14 more

Scene: parked, key in your pocket

This is the scene with the biggest gap between the two platforms, and the one most owners have never been told about.

A parked Gen3 writes to its own log for ever. The main board declares an hour of sleep, wakes 3,600 seconds later — to the second — does its work and goes back down. Add the time it spends awake and the true period is 3,691 s. That is 23 wakes a day, in a garage, indefinitely.

A parked Gen2 is the opposite. Its battery management wakes roughly once a day — the blocks in the window below sit 86,401 s apart, which is a day and a second — and its main board writes nothing at all while the motorcycle is off. On the firmware in these files the word for hibernation does not appear in the main board’s vocabulary — and that is a fact about firmware, not about the generation. One Gen2 main board in this corpus contradicts it: the FXS 2019 in the switching-off section switches itself off with Bike Shut Off After Idle For 3600 seconds. and does hibernate. Its window is on this page because a drawing that says “Gen2 does not do this” needs its own counter-example printed beside it.

Does it write when switched off?One parked day, to scale0h12h24hGen3 main board23 in the daydeclares 3,600 s · sleeps 3,600 s · true beat 3,691 sGen2 battery management1 in the dayone block every 86,401 s — a day and a secondGen2 main boardnonenot one entrynothing at all — on these firmwares, not on all Gen286,400 does not divide by 3,691: thebeat is not an hour, so the wakes walk laterevery day and the count is 23, not 24.
A parked Gen3 writes for ever and a parked Gen2 barely writes at all. The main board declares 3,600 s of sleep and keeps it to the second, but it is awake 91 s on top, so the real period is 3,691 s — not an hour. That is why the count is 23 and why the wakes drift a little later every day. The bottom lane is not an omission: no window is published there because there is nothing to publish, and the reason is recorded in the generated data rather than left to inference. Read that lane as “not on these firmwares”, not as “not on Gen2”: the self-switch-off window in the switching-off section is a Gen2 main board that does hibernate, and it is on this page on purpose.

One hour of a Gen3, three days of a Gen2

The Gen3 window covers exactly one beat, on both lanes. At 08:24:18 the main board’s record decodes as Timed out in PW Startup — it has been waiting for a key that never came, and it gives up. 30 s later it declares Saving Stats, Hibernating for 3600 sec. At 09:24:48 both boards wake in the SAME second, and the reasons say which one is in charge: the main board reports Hib Wake RTC, a timer, while the pack’s reset line reads Board was reset. Source: Hib Wake Pin, Power-On, Supply WD, Power Val, a wire.

The pack’s own arithmetic is the giveaway. It asked for a day of sleep and got 3,330 seconds, because it does not have a wake-up clock of its own: the main board wakes it. A declared sleep on the pack is a request. A declared sleep on the main board is a duration, and it keeps it to the second.

Both of these boards also write an error at every single wake — the pack’s input-output manager returning a failure and a cellbox checksum complaining. They are left in the window rather than filtered out, because the fault section below is where they get explained, and a page that quietly removes the noise teaches you to be alarmed by it later.

The Gen2 window is three days on one lane. Four identical four-line blocks — settings read, a periodic sample, a state-of-charge record, a status word — 86,401 seconds apart. In 3.0 days the state of charge does not move from 65%, the coulomb counter does not move from 36 Ah, and the cells hold to the millivolt: 8 of the 11 channels on that line are identical from the first sample to the last. Exactly 3 channels move. One is the measured pack voltage, 105.202 V down to 105.194 V — 8 mV in three days. The others are the temperatures: pack_temp_celsius by 1 °C and bms_temp_celsius by 2 °C, and the voltage does not fall in a line — it rises on the second day before it drops, which is why the number quoted is the difference between the two ends and not a rate. That is not a footnote to the voltage, it is the explanation of it — a pack cooling in a garage reads a little lower, and eight millivolts is what a couple of degrees is worth. A parked pack is not doing nothing; it is following the room.

That window is also the cleanest demonstration of a trap that recurs across every Gen2 file on this page. The periodic line reports the pack voltage as a measurement — it changes — and the record one line below reports pack_voltage_mv: 114800 under the same field name on every one of the file’s lines. On a SOC Data line pack_voltage_mv is the pack’s nominal capacity in milliamp-hours, not a voltage. It never moves inside a file — the 4 windows here that carry one hold 26,000, 57,400, 104,000, 114,800 and nothing else — while the Discharge level line a second away carries the same field name and a voltage that does move. The pack confirms it in its own words elsewhere in the corpus: Adjusted Capacity was 0, resetting to 114800000 uAH based on 4 bricks, which is 114,800 mAh at four bricks and 57,400 at two. The two lines are adjacent in the extract, which is the most convincing way to learn it and the most dangerous way to publish it.

DSR/X 2023 — Parked, key in your pocket · 25 lines over 61 min · lanes MBB + BMS
MBB    247  08:24:18  INFO    Timed out in PW StartupMBB    248  08:24:18  INFO    State change                from: PWSU, to: HIBMBB    249  08:24:18  INFO    MTC pending change by 3     from: 0, to: 0MBB    250  08:24:18  INFO    Stopping 12v charge         DC-DC: 281776uV, Battery: 13153056uV, Combined: 12340647uV, Ambient: 33CMBB    251  08:24:18  INFO    UnknownMBB    252  08:24:18  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB    253  08:24:18  INFO    Ch limits                   curr 0 cap 0 act 0 pow 0MBB    254  08:24:18  INFO    UnknownMBB    255  08:24:18  INFO    Control flags changed       old: 10000000100000001110000, new: 10000000000000001110000BMS    673  08:24:18  INFO    BMS → IDLE                  Entering ZERO_BMS_STATE_IDLEBMS    675  08:24:18  INFO    BMS telemetry (Gen3)        SOC=34% CvLow=3604mV CvHigh=3607mV I=-55mAMBB    256  08:24:48  INFO    Saving Stats, Hibernating for 3600 secBMS    675  08:29:18  INFO    BMS → HIBERNATE             Entering ZERO_BMS_STATE_HIBERNATEBMS    677  08:29:18  INFO    BMS telemetry (Gen3)        SOC=34% CvLow=3604mV CvHigh=3607mV I=-7mABMS    677  08:29:18  INFO    Entering HibernateMBB    257  09:24:48  INFO    Board was reset             Source: Hib Wake RTC, Power-On, Supply: WD, Power: ValBMS    678  09:24:48  INFO    Exiting HibernateBMS    679  09:24:48  INFO    Board was reset. Source: Hib Wake Pin, Power-On, Supply WD, Power ValBMS    680  09:24:48  INFO    EnteringBMS    681  09:24:48  INFO    Discharge enabledBMS    682  09:24:48  ERROR   ERROR: ZeroBmsManagePackIo returned ZERO_RET_FAILBMS    683  09:24:48  INFO    ring ZERO_BMS_STATE_STANDBYBMS    685  09:24:48  INFO    BMS telemetry (Gen3)        BMS sleep CvLow=3604mV CvHigh=3607mVBMS    685  09:24:48  INFO    400000 baud: cellbox_id 1 CRC invalid. value: 0x02, crc_read: 0MBB    258  09:24:49  INFO    MBB RTC treated valid.

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 8 lines are device text and 17 are our decode of the bytes. The two boards keep separate clocks. The main board runs 2,697 s ahead of the pack here, and the lanes are merged with that offset applied: 72 co-written seconds across the pair with it, 3 without. Timestamps: real.

DSR 2018 — Parked, key in your pocket · 16 lines over 3.0 days · lanes BMS
── 2022-07-11 ──────────────────────────────────────────BMS   5843  16:29:43  INFO    Successfully Read Settings in 1 attempt(s)BMS   5844  16:29:43  INFO    Discharge level             amp_hours: 36, state_of_charge_percent: 65, current_amps: 0, voltage_low_cell_volts: 3.754, voltage_unloaded_cell_volts: 3.753, voltage_high_cell_volts: 3.759, voltage_balance_mv: 5, pack_temp_celsius: 15, bms_temp_celsius: 15, pack_voltage_volts: 105.202, pack_voltage_mv: 105202, mode: IdleBMS   5845  16:29:43  INFO    SOC Data                    soc_raw_1: 34428, soc_raw_2: 96205, soc_raw_3: 36220, pack_voltage_mv: 114800, pack_voltage_volts: 114.8, soc_percent_1: 69, soc_percent_2: 64, soc_percent_3: 65, balance_or_current: -1BMS   5846  16:29:44  INFO    HDCR = 0x000C204E,  HDSTAT = 0x00000000── 2022-07-12 ──────────────────────────────────────────BMS   5847  16:29:45  INFO    Successfully Read Settings in 1 attempt(s)BMS   5848  16:29:45  INFO    Discharge level             amp_hours: 36, state_of_charge_percent: 65, current_amps: 0, voltage_low_cell_volts: 3.754, voltage_unloaded_cell_volts: 3.753, voltage_high_cell_volts: 3.759, voltage_balance_mv: 5, pack_temp_celsius: 15, bms_temp_celsius: 15, pack_voltage_volts: 105.203, pack_voltage_mv: 105203, mode: IdleBMS   5849  16:29:45  INFO    SOC Data                    soc_raw_1: 34428, soc_raw_2: 96205, soc_raw_3: 36220, pack_voltage_mv: 114800, pack_voltage_volts: 114.8, soc_percent_1: 69, soc_percent_2: 64, soc_percent_3: 65, balance_or_current: -1BMS   5850  16:29:46  INFO    HDCR = 0x000C204E,  HDSTAT = 0x00000000── 2022-07-13 ──────────────────────────────────────────BMS   5851  16:29:47  INFO    Successfully Read Settings in 1 attempt(s)BMS   5852  16:29:47  INFO    Discharge level             amp_hours: 36, state_of_charge_percent: 65, current_amps: 0, voltage_low_cell_volts: 3.754, voltage_unloaded_cell_volts: 3.753, voltage_high_cell_volts: 3.759, voltage_balance_mv: 5, pack_temp_celsius: 15, bms_temp_celsius: 14, pack_voltage_volts: 105.199, pack_voltage_mv: 105199, mode: IdleBMS   5853  16:29:47  INFO    SOC Data                    soc_raw_1: 34428, soc_raw_2: 96205, soc_raw_3: 36220, pack_voltage_mv: 114800, pack_voltage_volts: 114.8, soc_percent_1: 69, soc_percent_2: 64, soc_percent_3: 65, balance_or_current: -1BMS   5854  16:29:48  INFO    HDCR = 0x000C204E,  HDSTAT = 0x00000000── 2022-07-14 ──────────────────────────────────────────BMS   5855  16:29:49  INFO    Successfully Read Settings in 1 attempt(s)BMS   5856  16:29:49  INFO    Discharge level             amp_hours: 36, state_of_charge_percent: 65, current_amps: 0, voltage_low_cell_volts: 3.754, voltage_unloaded_cell_volts: 3.753, voltage_high_cell_volts: 3.759, voltage_balance_mv: 5, pack_temp_celsius: 14, bms_temp_celsius: 13, pack_voltage_volts: 105.194, pack_voltage_mv: 105194, mode: IdleBMS   5857  16:29:49  INFO    SOC Data                    soc_raw_1: 34428, soc_raw_2: 96205, soc_raw_3: 36220, pack_voltage_mv: 114800, pack_voltage_volts: 114.8, soc_percent_1: 69, soc_percent_2: 64, soc_percent_3: 65, balance_or_current: -1BMS   5858  16:29:50  INFO    HDCR = 0x000C204E,  HDSTAT = 0x00000000

Decode route gen2-bms (BMSEntry), generation 2. Provenance: 0 lines are device text and 16 are our decode of the bytes. Timestamps: real.

If you are trying to work out whether a motorcycle was ridden or merely woke up, the wake reason is the fastest answer on Gen3 and the four-line block is the fastest answer on Gen2. Neither requires reading any telemetry at all.

When the clock is ours and not the motorcycle’s

Some timestamps inside a Gen2 log are reconstructed. Not imprecise — reconstructed: an interval divided by the number of entries that have to fit inside it. The order is real. The spacing is arithmetic.

The window below is on the page because it is wrong in an instructive way, and it is labelled as such. Its main-board lane has 11 entries at a step of exactly 18,150 s. Those are the same events, in the same order, with the same strings, that occupy 7 s in the switch-on window at the top of this page — where the measured gaps are 0 and 1 seconds. 7 s of switching on, spread across 2.1 days.

The damage is nameable rather than abstract. That reconstruction places a Power On in the middle of the night while the motorcycle was standing in a garage. Nobody turned a key then.

And in the very same window, the battery-management lane keeps its real beat — gaps of 86,401 and 86,402 and 43,013 s, not identical to each other, which is exactly what a real cadence looks like. A working clock and an invented one, side by side, in one extract.

How the extractor tells them apart: a run of identical gaps is only accepted when the interval is one the firmware is known to use. Everything else is labelled. The allowlist is short, and two of its entries were added only after a window it rejected turned out to be genuine.

1 of the 19 windows on this page carries reconstructed timestamps. It is on the page because a labelled counter-example is worth more than a footnote, and because the app’s own data model carries a flag for exactly this: the badge is driven by that stored flag rather than by an estimate made while rendering.

The intervals the extractor accepts as a real cadence
IntervalWhat writes it
0 s and 1 sEvents sharing a second. Ordinary, and the reason a Gen3 window is ordered by (timestamp, source, sequence) and never by timestamp alone.
60 sPeriodic telemetry while the motorcycle is on — a Gen2 ride sample, a Gen3 pack sample, a Gen3 vehicle-state record.
320 sThe Gen3 main board’s charging cadence. It is not the riding cadence and not the Gen2 charging cadence.
600 sThe Gen2 charging cadence, on both boards.
3,330 sWhat a Gen3 pack actually sleeps for. It asks for 86,400 s and the main board wakes it early.
3,691 sThe Gen3 hibernation beat: 3,600 s of declared sleep plus the 91 s the board spends awake.
86,400 sThe sleep a Gen3 pack declares — published as a declaration, never as a duration.

Any other run of identical gaps is treated as reconstruction and the window is labelled. The allowlist as the extractor holds it is 0, 1, 60, 320, 600, 3,330, 3,691, 86,400 seconds. It is a list rather than a rule because two of its members — 320 s and 3,330 s — were discovered by finding a window the first version of the rule rejected and then proving the interval was the firmware’s.

DS 2014 — Parked, key in your pocket · 18 lines over 2.5 days · lanes MBB + BMS
── 2022-02-03 ──────────────────────────────────────────BMS    558  20:54:13  INFO    Discharge level             amp_hours: 12, state_of_charge_percent: 87, current_amps: 0, voltage_low_cell_volts: 3.972, voltage_unloaded_cell_volts: 3.972, voltage_high_cell_volts: 3.976, voltage_balance_mv: 4, pack_temp_celsius: 31, bms_temp_celsius: 36, pack_voltage_volts: 111.274, pack_voltage_mv: 111274, mode: IdleBMS    559  20:54:14  INFO    Entering Hibernate── 2022-02-04 ──────────────────────────────────────────MBB   1332  01:23:40  DEBUG   Reset: Power-On, ExternalMBB   1333  06:26:10  INFO    Power On                    power_on: True, power_state: On, power_source_code: 1, power_source: Key Switch, is_key_switch: True, is_external_charger: False, is_onboard_charger: False, is_known_source: TrueMBB   1334  11:28:40  INFO    Key On                      key_on: True, key_state: On, is_key_on: True, is_key_off: FalseMBB   1335  16:31:10  DEBUG   Module scheme changed from None mode to Stopped modeBMS    560  20:54:15  INFO    Discharge level             amp_hours: 13, state_of_charge_percent: 85, current_amps: 0, voltage_low_cell_volts: 3.968, voltage_unloaded_cell_volts: 3.968, voltage_high_cell_volts: 3.972, voltage_balance_mv: 4, pack_temp_celsius: 22, bms_temp_celsius: 21, pack_voltage_volts: 111.168, pack_voltage_mv: 111168, mode: IdleMBB   1336  21:33:40  INFO    Module 00 Opening Contractor  module_number: 0, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, serial_number: [redacted], precharge_percent: None── 2022-02-05 ──────────────────────────────────────────MBB   1337  02:36:10  INFO    Module 01 Opening Contractor  module_number: 1, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, serial_number: [redacted], precharge_percent: NoneMBB   1338  07:38:40  ERROR   Disabling CIB for non-timeout reasonMBB   1339  12:41:10  DEBUG   Module scheme changed from Stopped mode to Running modeMBB   1340  17:43:40  DEBUG   Module scheme changed from Running mode to Stopped modeBMS    561  20:54:17  INFO    Discharge level             amp_hours: 13, state_of_charge_percent: 85, current_amps: 0, voltage_low_cell_volts: 3.967, voltage_unloaded_cell_volts: 3.967, voltage_high_cell_volts: 3.971, voltage_balance_mv: 4, pack_temp_celsius: 20, bms_temp_celsius: 19, pack_voltage_volts: 111.139, pack_voltage_mv: 111139, mode: IdleMBB   1341  22:46:10  INFO    Module 00 CAN Link Up── 2022-02-06 ──────────────────────────────────────────MBB   1342  03:48:40  INFO    Module 00 Registered        module_number: 0, event_type: Registered, event_code: 2, module_voltage_volts: 111.125, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, battery_current_amps: 0, serial_number: [redacted], precharge_percent: NoneBMS    562  08:51:10  INFO    Exiting HibernateBMS    563  08:51:10  DEBUG   Entered Idle StateBMS    564  08:51:10  INFO    System Turned On

Decode route gen2-mbb (MBBEntry), generation 2. Provenance: 0 lines are device text and 18 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 350 of the same seconds across the whole pair. Timestamps: RECONSTRUCTED — the order is the bike’s, the spacing is not.

How to spot it in your own file

Look for a run of five or more consecutive entries whose gaps are all identical to the second, at an interval that is not a firmware cadence. Real periodic telemetry produces gaps that hover around a value — 86,401 and 86,402, not 86,401 twice. Perfectly identical spacing over a long stretch is the signature of a division, and the events inside it did not happen when it says they did.

Parked: the Gen3 hourly beat against a Gen2 night

Parked · DSR/X 2023

ParkedDSR/X 2023Gen3Gen3 main board (ring buffer)Gen3MBBEntryMBBBMS25 lines · 1 h 31 srider’s log · redactedmeasured timestamps

Faults in this window 1 of 25 rows carries a warning or an error: 1 reaches only the family it belongs to, and none is left without a destination.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

E il battito orario a due corsie, e la corsia BMS diventa leggibile solo dopo aver riconciliato gli orologi: su questa moto il MBB corre 2.697 s avanti al BMS. La versione precedente di questa scena, costruita sui timestamp grezzi, insegnava che il pacco non si sveglia col MBB ma sedici minuti dopo. E il contrario: LE DUE SCHEDE SI SVEGLIANO NELLO STESSO SECONDO. Alle 09:24:48 il MBB scrive Board was reset Source: Hib Wake RTC e il BMS scrive Exiting Hibernate e Board was reset. Source: Hib Wake Pin — e Hib Wake Pin contro Hib Wake RTC dice anche chi sveglia chi. Un ora prima, sempre nello stesso secondo, il MBB scrive Timed out in PW Startup e il BMS entra in IDLE dichiarando il 34%. Fra i due estremi il conto torna al secondo: il MBB dichiara Hibernating for 3600 sec alle 08:24:48 e si risveglia alle 09:24:48, 3.600 s esatti, ed e il suo gap di corsia; il BMS chiede 86.400 s alle 08:29:18 e ne ottiene 3.330, ed e il suo. Aggiungendo i 61 s fra il risveglio precedente e il Timed out con cui la finestra comincia si ottengono i 3.691 s che sono il periodo vero: 33 dei 39 intervalli fra Hibernating in questo file misurano esattamente 3691, e 47 su 60 sull altra DSR/X. Gap sull asse unito [0,0,0,0,0,0,0,0,0,0,30,270,0,0,3330,0,0,0,0,0,0,0,0,1].

  • Una Gen3 parcheggiata scrive nel proprio log per sempre: 91 s di veglia piu 3.600 dichiarati uguale 3.691 s misurati, ora dopo ora. Ventiquattro risvegli al giorno, in garage, a chiave in tasca.
  • Timed out in PW Startup e la centralina che aspetta sessantun secondi una chiave che non arriva e rinuncia. E la riga che distingue un risveglio automatico da un avvio vero — l altra meta e Hib Wake RTC contro Hib Wake Pin.
  • Le due schede si svegliano nello STESSO SECONDO, e si vede chi comanda: il MBB si sveglia su Hib Wake RTC, cioe un timer, il BMS su Hib Wake Pin, cioe un filo. I sedici minuti di sfasamento che questa scena mostrava erano la differenza fra i due orologi, non una fase.
  • Il BMS dichiara 86.400 secondi di sonno e ne dorme 3.330: e il MBB a svegliarlo prima. Una durata dichiarata dal firmware non e una misura — ma il MBB, che dichiara 3.600 s, quelli li dorme tutti, al secondo.
  • Stessa sveglia, due veglie diverse: il MBB torna a dormire 30 s dopo il Timed out, il BMS 300 s dopo essere entrato in IDLE. Il periodo pero e lo stesso per entrambi, perche a scandirlo e una scheda sola.

Notes to the generator’s maintainer

  • 3691 e 3330 vanno in time_allowlist, o la guardia declassa la scena piu importante della pagina.
  • ERROR: ZeroBmsManagePackIo returned ZERO_RET_FAIL e cellbox CRC invalid compaiono a OGNI risveglio: sono normali. Vanno lasciati e annotati, perche la scena 17 li spiega.
  • Questa scena NON alimenta piu la figura F. Sull asse corretto la finestra copre un periodo intero del battito e non contiene piu una lettura di tensione del MBB: le due letture affiancabili sono quelle della scena 9, che ora cadono nello stesso secondo. Meglio una casella in meno che una figura costruita su due letture a un ora di distanza.

Fields in this window that need a caveat

  • pack_soc (MBB, not rendered) Vale 32 alle righe L248 e L251, entrambe State change / record non di stato: e 0x20, non uno stato di carica.
  • hibernation_declared (BMS, rendered with the caveat) Il BMS dichiara 86400 sec di sonno 21 volte in questo file e torna dopo 3.330 s. Se la pagina pubblica il numero dichiarato come una durata, dice una cosa falsa.
Only on this side · 32 fieldsAmbientBatteryCombinedCvHighCvLowDC-DCIPowerSOCSourceSupplybms_report_modebms_sequencebms_state_codebms_state_namecell_ocv_low_mvand 16 more

Parked · DSR 2018

ParkedDSR 2018Gen2Gen2 battery managementBMSEntryBMS16 lines · 3 drider’s log · redactedmeasured timestamps
ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

Sedici righe, tre giorni, quattro blocchi di risveglio identici da quattro righe l uno. Gap [0,0,1, 86401, 0,0,1, 86401, 0,0,1, 86401, 0,0,1]: 86.401 s e 24 h piu un secondo, ed e una cadenza REALE di firmware, non una ricostruzione — misurata su quattro moto della stessa famiglia con mediana 86.402 s su 199+131+20+812 blocchi consecutivi. E l esatto contraltare del battito orario Gen3: Gen3 dichiara 3600 s e ne impiega 3691, Gen2 non dichiara nulla e ne impiega 86.401. Finestra scelta anche perche e libera dai doppioni di giro del ring buffer che affliggono il 42,1% di questo file. Una corsia sola per un motivo contato: negli stessi tre giorni di calendario il MBB ha scritto esattamente quattro voci, con gap 69.028 s ripetuti tre volte, cioe una raffica di boot fabbricata.

  • Una Gen2 ferma sveglia il proprio BMS una volta al giorno, non una volta all ora: blocco fisso di quattro righe (Successfully Read Settings -> Discharge level -> SOC Data -> HDCR) ogni 86.401 s. Contro le 24 righe al giorno di una Gen3, e tutto il contrasto in un disegno.
  • Un pacco fermo perde quasi nulla: in 72 ore SoC resta 65, amp_hours resta 36 e le celle tengono 3.754 / 3.753 / 3.759 V al millivolt. L unica cosa che si muove e la tensione misurata: 105.202 -> 105.194 V, otto millivolt in tre giorni.
  • Nella stessa coppia di righe adiacenti, Discharge level dice pack_voltage_volts 105.202 (misura, si muove) e SOC Data dice 114.8 (costante su tutte e 2.087 le righe del file). 114800 non sono millivolt: sono i mAh nominali del pacco.
  • mode: Idle e la parola del BMS per parcheggiata; lo stesso campo dice Bike On in guida e Charge sotto carica. Lo stato del pacco e un campo, non un evento.
  • current_amps: 0 con mode Idle vuol dire sotto la risoluzione del sensore, non zero esatto — ed e coerente con gli 8 mV che il pacco perde davvero in tre giorni.

Notes to the generator’s maintainer

  • Il MBB di questa moto non e una corsia di questa scena: in quei tre giorni ha scritto 4 voci e i loro gap sono 69.028 s ripetuti, cioe ricostruiti. Dichiararlo a parole, non disegnarlo sullo stesso asse.
  • Nota per la figura D: 86.401 s va nella time_allowlist o la guardia declassa questa scena a reconstructed.

Fields in this window that need a caveat

  • pack_voltage_mv (BMS, rendered with the caveat) Sulle righe SOC Data vale 114800 su tutte e 2.087 le occorrenze di questo file, e 57400 su un pacco a 2 brick: e la capacita nominale in mAh, non una tensione — il BMS lo dice da solo altrove (Adjusted Capacity was 0, resetting to 114800000 uAH based on 4 bricks). La misura vera e sulla riga Discharge level immediatamente sopra, 105.202 V. Le due righe sono adiacenti in questa finestra: e il modo piu pulito di insegnarlo, ma senza etichetta la pagina pubblica una tensione falsa.
Only on this side · 21 fieldsah_dischargedamp_hoursbalance_mvbalance_or_currentbms_temp_celsiusboard_temp_ccurrent_ampspack_temp_cpack_temp_celsiuspack_voltage_voltssoc_percent_1soc_percent_2soc_percent_3soc_raw_1soc_raw_2soc_raw_3and 5 more

Scene: something goes wrong

The two generations report faults in genuinely different registers, and both registers are useful once you know which one you are reading.

A Gen2 fault is a sentence in English with the measurements inside it. A Gen3 fault is a code word, usually with a cause declared in the same second and a counterpart that clears it later. Gen2 tells you what it saw; Gen3 tells you what it decided.

Neither is much use as a single line. What makes a fault line mean something is how often it recurs, and whether anything downstream changed.

Three faults

The Gen2 window is a pack refusing to connect itself, and it explains itself completely in one line: Did not close contactor due to lack of precharge. Unswitched: 96633mV, Switched: 114727 mV. The measurement, the comparison and the verdict are all there. Eighteen volts between the two sides of a contactor that is about to close is not something to close through.

It refuses twice and then closes, at a pre-charge of 106.6%. Above one hundred per cent is not a decoding bug: the switched side is higher than the pack, so the ratio exceeds one. The same window carries the pack’s own declaration of what it is — Pack:2016_monolith,Numbricks:4 — which is the sort of line that settles an argument about hardware without anybody opening anything.

The Gen3 window is a named fault with a declared cause. Fault set twice, Fault cleared three times, and in the same second as the first one the main board says why: Sending NotificationBMS_LOW_VOLTAGE_CUTOFF. The module had just registered at 102.573 V.

Two things in that window are easy to misread. First, Fault cleared at shutdown does not mean repaired: it means the state machine has left the state where that condition is evaluated. Second, the dashboard lamp finally appears — OBD-II MIL state changed to — and it is the only line in a Zero log that corresponds to something the rider can see without opening a file.

The same window also contains a declared sleep of Hibernating for 31536000 sec — a year — followed by the board being awake 5 seconds later. A request is not a duration.

The third window is the case that matters most for anyone reading their own log: a fault that is not a fault. The pack cannot read a cellbox at one baud rate, retries slower, gets a bad checksum, raises Fault set: INVALID/NO CELL TYPE and clears it in the SAME second, then passes Quick Self Test Passed and goes back to measuring. That sequence happens at every wake. A fault raised and cleared inside one second, repeating, is a self-test, not a defect.

That window is also where the firmware’s own truncation is visible. Its last line is Saving stats, — cut off mid-sentence by the motorcycle’s buffer, not by us. Repairing it would be inventing text.

SR 2017 — A fault · 20 lines over 60 s · lanes MBB + BMS
BMS   6059  17:47:26  INFO    SOC Data                    soc_raw_1: 70820, soc_raw_2: 76252, soc_raw_3: 80844, pack_voltage_mv: 104000, pack_voltage_volts: 104.0, soc_percent_1: 23, soc_percent_2: 9, soc_percent_3: 8, balance_or_current: 30BMS   6060  17:47:26  INFO    Discharge Cutback           cutback_percent: 48, cutback_raw_value: 123, cutback_ratio: 0.4823529411764706BMS   6061  17:47:26  INFO    376ABMS   6062  17:47:26  INFO    System Turned OffBMS   6063  17:47:27  DEBUG   CAN Link Is DownBMS   6064  17:47:27  DEBUG   Entered Idle StateMBB   5730  17:47:28  INFO    Module 01 Registered        module_number: 1, event_type: Registered, event_code: 2, module_voltage_volts: 96.446, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 0.0, battery_current_amps: 0, serial_number: [redacted], precharge_percent: NoneBMS   6065  17:47:28  INFO    System Turned OnBMS   6066  17:47:28  INFO    Rev: [redacted],Build: [redacted]BMS   6067  17:47:28  INFO    Pack:2016_monolith,Numbricks:4BMS   6068  17:47:28  DEBUG   Entered Running StateBMS   6069  17:47:28  DEBUG   CAN NOT Receiving SyncsBMS   6070  17:47:28  INFO    Contactor was Opened        contactor_state: opened, pack_voltage_volts: 96.527, switched_voltage_volts: 96.374, precharge_percent: 99.84149512571612, discharge_current_amps: 0.0, pack_voltage_mv: 96527, switched_voltage_mv: 96374, discharge_current_ma: 0BMS   6071  17:47:28  DEBUG   CAN Link Is UpBMS   6072  17:47:29  DEBUG   CAN Receiving SyncsBMS   6073  17:47:34  ERROR   Did not close contactor due to lack of precharge.  Unswitched: 96633mV, Switched: 114727 mVBMS   6074  17:47:36  ERROR   Did not close contactor due to lack of precharge.  Unswitched: 96654mV, Switched: 108819 mVBMS   6075  17:47:38  INFO    Contactor drive turned on   pack_voltage_volts: 96.678, switched_voltage_volts: 103.069, duty_cycle_percent: 33, pack_voltage_millivolts: 96678, switched_voltage_millivolts: 103069, voltage_difference_volts: 6.391000000000005BMS   6076  17:47:38  INFO    Contactor was Closed        contactor_state: closed, pack_voltage_volts: 96.678, switched_voltage_volts: 103.069, precharge_percent: 106.61060427398166, discharge_current_amps: 0.0, pack_voltage_mv: 96678, switched_voltage_mv: 103069, discharge_current_ma: 0BMS   6077  17:48:26  INFO    Discharge level             amp_hours: 81, state_of_charge_percent: 8, current_amps: 10, voltage_low_cell_volts: 3.437, voltage_unloaded_cell_volts: 3.47, voltage_high_cell_volts: 3.45, voltage_balance_mv: 13, pack_temp_celsius: 27, bms_temp_celsius: 32, pack_voltage_volts: 96.411, pack_voltage_mv: 96411, mode: Bike On

Decode route gen2-bms (BMSEntry), generation 2. Provenance: 0 lines are device text and 20 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 54 of the same seconds across the whole pair. Timestamps: real.

DSR/X 2024 — A fault · 25 lines over 3 min · lanes MBB
MBB   2311  04:21:56  WARNING  Fault pending               THROTTLE_MAP sets in 90 secondsMBB   2312  04:21:58  INFO    Registering Mod 2           102573mV, 4: brickMBB   2313  04:21:58  WARNING  Fault set                   MODULE_DISABLEDMBB   2314  04:21:58  INFO    Sending Notification        BMS_LOW_VOLTAGE_CUTOFFMBB   2315  04:21:58  INFO    LTSM state                  INIT to DISMBB   2316  04:22:55  WARNING  Fault set                   MODULE_ELIGIBILITY_TIMEOUTMBB   2317  04:22:55  WARNING  Fault cleared               THROTTLE_MAPMBB   2318  04:24:38  INFO    Key Sw                      OFFMBB   2319  04:24:38  INFO    Control flags changed       old: 1000000100000001110110, new: 1000000100000001111000MBB   2320  04:24:38  INFO    State change                from: WAIT, to: HIBMBB   2321  04:24:38  INFO    MTC pending change by 3     from: 0, to: 0MBB   2322  04:24:38  INFO    UnknownMBB   2323  04:24:38  INFO    Disch limits                curr 0 cap 0 act 0 pow 0MBB   2324  04:24:38  INFO    Ch limits                   curr 0 cap 0 act 0 pow 0MBB   2325  04:24:38  INFO    UnknownMBB   2326  04:24:38  WARNING  Fault cleared               MODULE_DISABLEDMBB   2327  04:24:38  WARNING  Fault cleared               MODULE_ELIGIBILITY_TIMEOUTMBB   2328  04:24:38  INFO    Control flags changed       old: 1000000100000001111000, new: 1000000000000001110000MBB   2329  04:24:38  INFO    Stopping 12v charge         DC-DC: 266392uV, Battery: 13014600uV, Combined: 12201453uV, Ambient: 26CMBB   2330  04:25:08  INFO    Saving Stats, Hibernating for 31536000 secMBB   2331  04:25:13  INFO    Exiting HibernateMBB   2332  04:25:13  INFO    Board was reset             Source: Hib Wake Pin, Power-On, Supply: WD, Power: ValMBB   2333  04:25:13  INFO    MBB RTC treated valid.MBB   2334  04:25:13  INFO    OBD-II MIL state changed to  1MBB   2335  04:25:13  INFO    Kill Sw                     STOP

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 0 lines are device text and 25 are our decode of the bytes. Timestamps: real.

SR/F 2021 — A fault · 13 lines over 62 min · lanes BMS
BMS    316  16:52:40  INFO    BMS telemetry (Gen3)        SOC=49% CvLow=3691mV CvHigh=3696mV I=0mABMS    316  17:48:10  ERROR   ERROR: ZeroBmsManagePackIo returned ZERO_RET_FAILBMS    318  17:48:10  INFO    BMS telemetry (Gen3)        BMS sleep CvLow=3691mV CvHigh=3696mVBMS    318  17:48:11  INFO    1000000 baud: Could not read eeprom from cellbox 1BMS    319  17:48:11  INFO    400000 baud: cellbox_id 1 CRC invalid. value: 0x02, crc_read: 0x20, crc_cBMS    320  17:48:11  WARNING  Fault set: INVALID/NO CELL TYPEBMS    321  17:48:11  WARNING  Fault cleared: IBMS    322  17:48:11  INFO    Quick Self Test PassedBMS    325  17:49:11  INFO    BMS telemetry (Gen3)        SOC=49% CvLow=3691mV CvHigh=3696mV I=0mABMS    325  17:54:11  INFO    BMS → HIBERNATE             Entering ZERO_BMS_STATE_HIBERNATEBMS    327  17:54:11  INFO    BMS telemetry (Gen3)        SOC=49% CvLow=3691mV CvHigh=3696mV I=0mABMS    327  17:54:11  INFO    Entering HibernateBMS    328  17:54:12  INFO    Saving stats,

Decode route gen3-bms (Gen3BMSEntry), generation 3. Provenance: 8 lines are device text and 5 are our decode of the bytes. Timestamps: real.

Count before you conclude

A single fault line is an event; the same fault several times in one afternoon is the condition of the motorcycle that day. Whenever a fault appears in this app it is shown with how often it recurs in the same file, because the count changes the answer more often than the wording does.

The failure that writes no fault line at all

Not every defect announces itself. When the onboard charger on a 2017 DS died, the log gained no error, no fault code and no warning — it lost a line it used to write. Every Power On record carries power_source, and a wake whose source is the onboard charger is the motorcycle powering itself up because the charger asked. That bike wrote 69 of them across twenty-three months, then none at all for 68 continuously covered days while 116 key wakes carried on. The owner kept charging from an accessory unit, so the charge count never moved; only the source did. Read the fault lines to find what broke loudly, and the power_source of the wakes to find what stopped quietly.

A fault, in each vocabulary

Fault · SR 2017

FaultSR 2017Gen2Gen2 battery managementBMSEntryMBBBMS20 lines · 1 minrider’s log · redactedmeasured timestamps

Faults in this window 2 of 20 rows carry a warning or an error: 2 reach a code page of their own, and none is left without a destination.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

SR 2017 con Powertank, e la scelgo al posto dei due guasti di isolamento (DS 2014 e FXS 06548) perche quelli insegnano due schede un guasto due nomi, che e gia la lezione della scena 6, mentre questo insegna una cosa che nessun altra finestra del corpus insegna: un guasto che si spiega DA SOLO, con misura, soglia e verdetto nella stessa riga. Il pacco monolite e a 96,6 V, il Powertank a 114,7 V, e il BMS rifiuta di chiudere il contattore due volte di fila. E la seconda meta di una storia che il file racconta per mesi: Contactor Welded 16 volte, Powertank voltage too high and monolith ineligible for connection 13, e 17 righe Module 1 not connected in cui il differenziale cresce da 10 mV a 3.019 mV contro un Allowed diff dichiarato di 984 mV. Gap [0,0,0,1,0,1,0,0,0,0,0,0,0,1,5,2,2,0,48], nessuna run interpolata.

  • Una riga di guasto Gen2 puo contenere misura, soglia e verdetto insieme: Unswitched: 96633mV, Switched: 114727 mV. Diciotto volt di differenza fra due pacchi che dovrebbero unirsi, e il rifiuto e la conseguenza.
  • Il BMS rifiuta due volte e alla terza chiude, con precharge_percent 106,6 — sopra il 100% perche il lato commutato e piu alto del pacco. Una percentuale di precarica sopra cento non e un bug del nostro decoder.
  • Su una moto a due pacchi il numero di modulo e il punto: Module 01 Registered porta il proprio serial_number, diverso da quello del modulo 00, perche il Power Tank ha un identita separata dal monolite. Entrambi i seriali sono redatti; la cosa da notare e che sono DUE.
  • System Turned Off seguito da System Turned On due secondi dopo e un RESET del BMS, non lo spegnimento della moto: la moto resta accesa e il MBB non registra nulla di simile.
  • La riga BMS 6061 e letteralmente 376A: un frammento. Il firmware tronca i propri messaggi e il nostro decoder non deve ripararli — quello che vedete e il buffer della moto.

Notes to the generator’s maintainer

  • Il BMS dichiara in chiaro Rev:47,Build:2024-02-01_002628 e Pack:2016_monolith,Numbricks:4: firmware e identita del pacco vanno redatti e la redazione va provata.
  • Il nome file di questa moto ha il VIN in minuscolo. E il promemoria che parse_file legge il nome, non i byte: passare sempre il percorso reale.

Fields in this window that need a caveat

  • pack_voltage_mv (BMS, rendered with the caveat) Anche qui la riga SOC Data porta una costante di configurazione e non una tensione.
Only on this side · 53 fieldsah_dischargedamp_hoursbalance_mvbalance_or_currentbattery_ampsbattery_current_ampsbms_temp_celsiusboard_temp_ccapacitor_voltage_voltscell_high_mvcell_low_mvcell_min_mvcontactor_statecurrent_acurrent_ampscutback_percentand 37 more

Fault · DSR/X 2024

FaultDSR/X 2024Gen3Gen3 main board (ring buffer)Gen3MBBEntryMBB25 lines · 3 min 17 srider’s log · redactedmeasured timestamps

Faults in this window 6 of 25 rows carry a warning or an error: 6 reach only the family they belong to, and none is left without a destination. 3 names here are carried by neither dash table — THROTTLE_MAP, MODULE_DISABLED, MODULE_ELIGIBILITY_TIMEOUT. The rows say so rather than offering the nearest-looking code.

ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

Un guasto con un NOME, con una causa dichiarata dal firmware, con una conseguenza visibile al pilota e con una fine — e sta negli ultimi ottanta minuti del file, cioe era in corso quando il proprietario ha scaricato il log. La catena e leggibile riga per riga: il modulo si registra a 102,573 V, il MBB alza MODULE_DISABLED nello stesso secondo e nello stesso secondo dice PERCHE (Sending Notification BMS_LOW_VOLTAGE_CUTOFF); 57 s dopo il modulo non e ancora idoneo e arriva MODULE_ELIGIBILITY_TIMEOUT; 103 s dopo il pilota gira la chiave e la moto va in ibernazione. Ricorre: MODULE_DISABLED 5 volte, MODULE_ELIGIBILITY_TIMEOUT 4, BMS_LOW_VOLTAGE_CUTOFF 5, tutte fra le 04:13 e le 05:33 dello stesso giorno. Gap [2,0,0,0,57,0,103,0 x11,30,5,0,0,0,0], misti. E la meta Gen3 dell affiancamento del guasto: la 5 mostra un guasto Gen2 che si spiega con i numeri, questa un guasto Gen3 che si nomina con un codice.

  • Un guasto Gen3 ha un nome in codice e spesso una causa dichiarata nello stesso secondo: Fault set (MODULE_DISABLED) seguito da Sending Notification (BMS_LOW_VOLTAGE_CUTOFF). Gen2 non ha questo vocabolario, ha frasi in inglese con dentro i numeri.
  • Fault cleared allo spegnimento NON vuol dire riparato: vuol dire che quello stato non e piu valutato. Qui i due guasti si cancellano insieme perche la macchina a stati esce da WAIT, non perche qualcosa sia migliorato.
  • Un errore che ricorre cinque volte in ottanta minuti non e un episodio: e la condizione della moto quel giorno. La pagina deve dare il conteggio accanto alla riga, non la riga da sola.
  • OBD-II MIL state changed to (1) e la spia sul cruscotto. E l unico punto del log che il pilota vede anche senza aprire un log — la giunzione fra cio che il file dice e cio che la moto mostra.
  • Hibernating for 31536000 sec e un anno di sonno richiesto dal firmware, e cinque secondi dopo la scheda e sveglia. Come per il BMS della scena 15, la richiesta non e la durata.

Notes to the generator’s maintainer

  • Il numero fra parentesi di Registering Mod 2 (102573mV) e la sola tensione di pacco che il MBB Gen3 pubblichi: 60 voci su 2.455 in questo file.
  • Deep-link obbligatorio dalle righe di guasto verso /learn/error-codes/[code], che oggi ha 170 pagine SSG senza traffico interno.

Fields in this window that need a caveat

  • hibernation_declared (MBB, rendered with the caveat) Saving Stats, Hibernating for 31536000 sec sono 365 giorni, e la scheda si risveglia cinque secondi dopo. La richiesta non e la durata.
Only on this side · 23 fieldsAmbientBatteryCombinedDC-DCPowerSourceSupplycharge_limit_currcharge_limit_powdischarge_limit_currdischarge_limit_powfrommodule_brick_countmodule_voltage_mvnewoldand 7 more

Scene: switching off

Switching off is the mirror of switching on, and on Gen2 it is the scene where the two boards overlap most — which is why the drawing in “Two boards” is cut from it.

On Gen3 it is not a shutdown at all. It is a state transition into sleep, and the last line of the sequence is already the first line of the hourly beat. A Gen3 does not switch off; it goes to sleep and starts counting.

Three ways to stop

The first window opens inside the ride, so the transition is visible rather than just the ending. Disarmed is the same periodic record as Riding with the motor at zero: the motorcycle does not stop sampling when you stop, it changes the label.

Seven seconds after the contactor opens the pack records Current Sensor Zeroed and recalibrates the zero of its ammeter, because the current is now guaranteed to be nothing. That is where the accuracy of the coulomb counter comes from, and it can only happen with the motorcycle standing still.

The second window is a Gen2 that switched ITSELF off. Bike Shut Off After Idle For 3600 seconds. — an hour of inactivity at the end of a charge — with power_source: Onboard Charger rather than the key. Nobody was there. The same field is what separates a rider turning a key from a charger waking the motorcycle, and it changes what the whole event means.

That window also shows an empty battery slot honestly: module 00 reads 114.387 V and module 01 reads 0 V. Zero volts in a slot is an EMPTY slot, not a flat pack. And it is the one window on this page that only decodes correctly when the parser is told the model (FXS): given the same bytes under the same filename without it, the file routes to a different decoder and yields thousands of unknown events and no rides at all. The route on this page is therefore partly our input, which is why the model is published alongside it.

The Gen3 window is 14 lines, 13 of them inside a single second. There is no contactor to open here: there is State changefrom: STOP, to: HIB — and 30 s later Saving Stats, Hibernating for 3600 sec, which is where “Scene: parked” begins.

One line in it is a good final lesson about reading numbers off a log: curr 885 cap 885 act 2147483647 pow 92305. The middle value is 2,147,483,647, which is a sentinel meaning “no limit”, while the two beside it are real measurements. A limit and a non-limit share one line, and only one of them is a number.

FXS 2016 — Switching off · 25 lines over 44 min · lanes MBB + BMS
MBB    483  08:16:09  INFO    Riding                      pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 66, pack_voltage_volts: 106.447, motor_temp_celsius: 72, controller_temp_celsius: 36, motor_rpm: 2142, battery_current_amps: 20, motor_current_amps: 37, ambient_temp_celsius: 26, odometer_km: 30700, mods: 11, mods_raw: 3BMS    454  08:17:02  INFO    Discharge level             amp_hours: 4, state_of_charge_percent: 63, current_amps: 0, voltage_low_cell_volts: 3.842, voltage_unloaded_cell_volts: 3.858, voltage_high_cell_volts: 3.874, voltage_balance_mv: 32, pack_temp_celsius: 32, bms_temp_celsius: 39, pack_voltage_volts: 107.837, pack_voltage_mv: 107837, mode: Bike OnBMS    455  08:17:02  INFO    SOC Data                    soc_raw_1: 2321, soc_raw_2: 6127, soc_raw_3: 4982, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 81, soc_percent_2: 75, soc_percent_3: 63, balance_or_current: 6MBB    484  08:17:04  DEBUG   Module scheme changed from Running mode to Stopped modeMBB    485  08:17:04  DEBUG   Module mode Change Does Not Require DisconnectMBB    486  08:17:09  INFO    Disarmed                    pack_temp_high_celsius: 34, pack_temp_low_celsius: 32, state_of_charge_percent: 68, pack_voltage_volts: 107.939, motor_temp_celsius: 67, controller_temp_celsius: 34, motor_rpm: 0, battery_current_amps: 0, motor_current_amps: 0, ambient_temp_celsius: 26, odometer_km: 30700, modules_status: 3, vehicle_state: disarmedMBB    487  08:17:21  INFO    Power Off                   power_on: False, power_state: Off, power_source_code: 1, power_source: Key Switch, is_key_switch: True, is_external_charger: False, is_onboard_charger: False, is_known_source: TrueMBB    488  08:17:21  INFO    Sevcon Turned Off           sevcon_power_on: False, power_state: Off, is_powered: False, controller_type: sevconMBB    489  08:17:21  INFO    Module 00 Opening Contractor  module_number: 0, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 107.998, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 108.812, battery_current_amps: 0, precharge_percent: NoneMBB    490  08:17:21  INFO    Module 01 Opening Contractor  module_number: 1, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 107.971, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 108.812, battery_current_amps: 0, precharge_percent: NoneMBB    491  08:17:21  INFO    Disabling External Chg 0 Charger 2MBB    492  08:17:21  INFO    Disabling External Chg 1 Charger 3BMS    456  08:17:21  INFO    Contactor was Opened        contactor_state: opened, pack_voltage_volts: 107.997, switched_voltage_volts: 107.52, precharge_percent: 99.55832106447401, discharge_current_amps: 0.134, pack_voltage_mv: 107997, switched_voltage_mv: 107520, discharge_current_ma: 134BMS    457  08:17:21  INFO    System Turned OffBMS    458  08:17:22  DEBUG   CAN NOT Receiving SyncsBMS    459  08:17:22  DEBUG   CAN Link Is DownBMS    460  08:17:22  DEBUG   Entered Idle StateBMS    461  08:17:28  INFO    Current Sensor Zeroed       old_value_millivolts: 2465, new_value_millivolts: 2462, correction_factor: 38, old_value_volts: 2.465, new_value_volts: 2.462, adjustment_millivolts: -3BMS    462  08:17:29  INFO    Discharge level             amp_hours: 4, state_of_charge_percent: 63, current_amps: 0, voltage_low_cell_volts: 3.852, voltage_unloaded_cell_volts: 3.864, voltage_high_cell_volts: 3.882, voltage_balance_mv: 30, pack_temp_celsius: 32, bms_temp_celsius: 39, pack_voltage_volts: 108.081, pack_voltage_mv: 108081, mode: IdleBMS    463  08:17:29  INFO    SOC Data                    soc_raw_1: 2269, soc_raw_2: 6127, soc_raw_3: 4982, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 81, soc_percent_2: 75, soc_percent_3: 63, balance_or_current: 4BMS    464  08:17:51  INFO    Voltage Across Contactor: 39631mV (Okay)BMS    465  08:50:24  INFO    Discharge level             amp_hours: 4, state_of_charge_percent: 63, current_amps: 0, voltage_low_cell_volts: 3.877, voltage_unloaded_cell_volts: 3.889, voltage_high_cell_volts: 3.906, voltage_balance_mv: 29, pack_temp_celsius: 33, bms_temp_celsius: 41, pack_voltage_volts: 108.706, pack_voltage_mv: 108706, mode: IdleBMS    466  08:50:24  INFO    SOC Data                    soc_raw_1: 2269, soc_raw_2: 6127, soc_raw_3: 4982, pack_voltage_mv: 26000, pack_voltage_volts: 26.0, soc_percent_1: 81, soc_percent_2: 78, soc_percent_3: 63, balance_or_current: 4BMS    467  08:50:25  INFO    Entering HibernateMBB    493  08:59:50  DEBUG   Reset: Power-On, External

Decode route gen2-bms (BMSEntry), generation 2. Provenance: 0 lines are device text and 25 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 740 of the same seconds across the whole pair. Timestamps: real. This is the maintainer’s own motorcycle.

FXS 2019 — Switching off · 21 lines over 27 min · lanes MBB + BMS
MBB   3176  11:08:22  INFO    Charging                    pack_temp_high_celsius: 29, pack_temp_low_celsius: 25, ambient_temp_celsius: 29, state_of_charge_percent: 92, pack_voltage_volts: 113.152, battery_current_amps: -5, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   2072  11:18:14  INFO    Discharge level             amp_hours: 3, state_of_charge_percent: 93, current_amps: -5, voltage_low_cell_volts: 4.062, voltage_unloaded_cell_volts: 4.048, voltage_high_cell_volts: 4.067, voltage_balance_mv: 5, pack_temp_celsius: 29, bms_temp_celsius: 45, pack_voltage_volts: 113.818, pack_voltage_mv: 113818, mode: ChargeBMS   2073  11:18:14  INFO    SOC Data                    soc_raw_1: 3970, soc_raw_2: 55726, soc_raw_3: 3343, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 95, soc_percent_2: 93, soc_percent_3: 93, balance_or_current: -10MBB   3177  11:18:22  INFO    Charging                    pack_temp_high_celsius: 29, pack_temp_low_celsius: 25, ambient_temp_celsius: 29, state_of_charge_percent: 93, pack_voltage_volts: 113.621, battery_current_amps: -5, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseBMS   2074  11:28:14  INFO    Discharge level             amp_hours: 2, state_of_charge_percent: 95, current_amps: -6, voltage_low_cell_volts: 4.08, voltage_unloaded_cell_volts: 4.066, voltage_high_cell_volts: 4.084, voltage_balance_mv: 4, pack_temp_celsius: 29, bms_temp_celsius: 45, pack_voltage_volts: 114.317, pack_voltage_mv: 114317, mode: ChargeBMS   2075  11:28:14  INFO    SOC Data                    soc_raw_1: 3032, soc_raw_2: 55696, soc_raw_3: 2453, pack_voltage_mv: 57400, pack_voltage_volts: 57.4, soc_percent_1: 96, soc_percent_2: 95, soc_percent_3: 95, balance_or_current: -10MBB   3178  11:28:22  INFO    Charging                    pack_temp_high_celsius: 29, pack_temp_low_celsius: 25, ambient_temp_celsius: 30, state_of_charge_percent: 95, pack_voltage_volts: 114.197, battery_current_amps: -5, mods: 1, mbb_charge_enabled: True, bms_charge_enabled: FalseMBB   3179  11:35:26  INFO    Calex 720W Charger 0 Disconnected  charger_id: 0, charger_name: Calex 720W, state_code: 0, state: Disconnected, is_connected: False, is_calex_720w: True, is_calex_1200w: False, is_external_charger: False, is_known_charger: TrueMBB   3180  11:35:26  INFO    Calex 1200W Charger 1 Disconnected  charger_id: 1, charger_name: Calex 1200W, state_code: 0, state: Disconnected, is_connected: False, is_calex_720w: False, is_calex_1200w: True, is_external_charger: False, is_known_charger: TrueMBB   3181  11:35:26  INFO    Disabling Calex 720W Charger 0MBB   3182  11:35:26  INFO    Disabling Calex 1200W Charger 1MBB   3183  11:35:26  INFO    Disabling External Chg 0 Charger 2MBB   3184  11:35:26  INFO    Disabling External Chg 1 Charger 3MBB   3185  11:35:26  INFO    Sevcon Turned Off           sevcon_power_on: False, power_state: Off, is_powered: False, controller_type: sevconMBB   3186  11:35:27  INFO    Bike Shut Off After Idle For 3600 seconds.MBB   3187  11:35:27  INFO    Power Off                   power_on: False, power_state: Off, power_source_code: 4, power_source: Onboard Charger, is_key_switch: False, is_external_charger: False, is_onboard_charger: True, is_known_source: TrueMBB   3188  11:35:27  INFO    Module 00 Opening Contractor  module_number: 0, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 114.387, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 114.75, battery_current_amps: 0, precharge_percent: NoneMBB   3189  11:35:27  INFO    Module 01 Opening Contractor  module_number: 1, event_type: Opening Contractor, event_code: 0, module_voltage_volts: 0.0, system_max_voltage_volts: 0.0, system_min_voltage_volts: 0.0, voltage_difference_volts: 0.0, capacitor_voltage_volts: 114.75, battery_current_amps: 0, precharge_percent: NoneBMS   2076  11:35:27  INFO    System Turned OffBMS   2077  11:35:28  DEBUG   CAN Link Is DownBMS   2078  11:35:28  DEBUG   Entered Idle State

Decode route gen2-mbb (MBBEntry), generation 2. Provenance: 0 lines are device text and 21 are our decode of the bytes. Both lanes are already on one clock: the measured offset between the two boards is 0 s, and they wrote in 530 of the same seconds across the whole pair. Timestamps: real.

DSR/X 2023 — Switching off · 14 lines over 30 s · lanes MBB
MBB   2309  19:34:09  INFO    Key Sw                      OFFMBB   2310  19:34:09  INFO    Control flags changed       old: 1000000100000001110110, new: 1000000100000001111000MBB   2311  19:34:09  INFO    State change                from: STOP, to: HIBMBB   2312  19:34:09  INFO    MTC pending change by 3     from: 0, to: 0MBB   2313  19:34:09  INFO    UnknownMBB   2314  19:34:09  INFO    Disch limits                curr 885 cap 885 act 2147483647 pow 92305MBB   2315  19:34:09  INFO    Ch limits                   curr 161 cap 161 act 2147483647 pow 16792MBB   2316  19:34:09  INFO    UnknownMBB   2317  19:34:09  INFO    Control flags changed       old: 1000000100000001111000, new: 1000000000000001110000MBB   2318  19:34:09  INFO    Disable flags changed       old: 000, new: 100MBB   2319  19:34:09  INFO    Stopping 12v charge         DC-DC: 13183824uV, Battery: 13163312uV, Combined: 13135518uV, Ambient: 31CMBB   2320  19:34:09  INFO    Kill Sw                     STOPMBB   2321  19:34:09  INFO    Disable flags changed       old: 100, new: 101MBB   2322  19:34:39  INFO    Saving Stats, Hibernating for 3600 sec

Decode route gen3-mbb (Gen3MBBEntry), generation 3. Provenance: 0 lines are device text and 14 are our decode of the bytes. Timestamps: real.

Switching off: the same instant on two boards

ShutdownFXS 2016Gen2Gen2 battery managementBMSEntryMBBBMS25 lines · 43 min 41 smaintainer’s own logmeasured timestamps

Two boards, one clock

Both lanes are shown on their own timestamps. No correction was applied.

The two clocks agree: the best shift is +0 s, inside the 5 s this method can resolve.

measured 0 sco-written seconds 740 at this offset · 740 at nonedrift ±10 speak 2.17× the nearest rival
ActivityRidingChargingParkedWarningError
Provenancedevice text⌁ our decodeƒ derived
Editor’s note on this window — why it was chosen, and what it teaches (Italian)

QUESTA E LA FIGURA F, ed e sulla moto del manutentore, cioe la storia di consenso piu pulita che il corpus permetta per la figura piu condivisibile della pagina. La prova numerica: nello stesso secondo (08:17:21) il BMS0 scrive Contactor was Opened ... pack_voltage_volts 107.997 e il MBB scrive Module 00 Opening Contractor ... module_voltage_volts 107.998. Un millivolt. E c e una TERZA lettura nella stessa riga successiva, modulo 01 a 107.971 V, che e l altro pacco fisico. Non e un caso pescato: in questa coppia ci sono 740 secondi in cui entrambe le schede hanno scritto (69 con BMS1: la corsia giusta e BMS0), e lo scarto misurato su 14 spegnimenti consecutivi vale -1, -2, -2, -4, +1, -21, +2, -3, -2, -1, -4, -7 mV. Ho scartato la DS 2014 (6 mV, piu leggibile ma moto altrui) e la DSR 2018 (3 mV) proprio per il consenso. Gap [29,0,7,53,0,7,53,0,2,0,5,12,0,0,0,0,0,0,0,1,0,0,6,1], nessuna run interpolata. La finestra apre DENTRO la guida, cosi il lettore vede la transizione e non solo uno spegnimento a freddo.

  • Lo stesso contattore ha due nomi nello stesso secondo: Contactor was Opened sul BMS, Module 00 Opening Contractor sul MBB. Nessuno dei due file, letto da solo, dice che sono lo stesso evento.
  • Due voltmetri su due schede: 107.997 V e 107.998 V. Un millivolt di differenza e strumentazione, non disaccordo — e nessuna delle due schede sa il numero dell altra.
  • La riga dopo legge il modulo 01 a 107.971 V: la FXS 2016 ha DUE moduli fisici, ciascuno col proprio BMS. E il motivo per cui uno scarico produce tre file e non due.
  • Current Sensor Zeroed sette secondi dopo lo spegnimento (2465 -> 2462 mV, correzione -3 mV): il BMS ricalibra lo zero dell amperometro quando la corrente e garantita nulla. Da qui viene la precisione del conteggio coulombico, e succede solo a moto ferma.
  • Disarmed e la stessa riga di stato di Riding con motor_rpm 0 e vehicle_state disarmed: la moto non smette di campionare quando ci si ferma, cambia etichetta.

Notes to the generator’s maintainer

  • Figura F: i due numeri da richiamare graficamente sono 107.997 (BMS) e 107.998 (MBB), piu 107.971 come terza lettura sul secondo pacco.
  • Moto del manutentore. E l unica scena a due corsie della pagina in cui il consenso e pieno: se una sola immagine di questa pagina viene ricondivisa fuori contesto, deve essere questa.

Fields in this window that need a caveat

  • serial_number (MBB, not rendered) Vale x sulle righe Opening Contractor: campo vuoto, non un seriale.
  • pack_voltage_mv (BMS, rendered with the caveat) Costante 26000 sulle righe SOC Data della finestra; la misura e su Discharge level.
0 s
+53 s
+53 s
+55 s
+55 s
+1 min
+1 min 12 s
+1 min 12 s
+1 min 12 s
+1 min 12 s
+1 min 12 s
+1 min 12 s
+1 min 12 s
+1 min 12 s
+1 min 13 s
+1 min 13 s
+1 min 13 s
+1 min 19 s
+1 min 20 s
+1 min 20 s
+1 min 42 s
+34 min 15 s
+34 min 15 s
+34 min 16 s
+43 min 41 s

The field glossary

Every field name that appears anywhere on this page has an entry here: what it means, its unit, which board writes it, and — where it matters — what its sign means.

The glossary is not documentation written alongside the extracts. It is a gate on them. The extractor refuses to publish a line containing a field name that has no entry, so a new field learned by the parser breaks the build rather than appearing here unexplained.

The sign

This is the single most common misreading of a Zero log, so it gets its own table and one sentence: positive is out of the pack.

A negative current is energy going INTO the battery. I=-15864mA in the charging window above is a motorcycle drawing 15.9 A from a wall socket, not a motorcycle discharging at that rate.

And the channel by itself will not tell you whether that is the wall or the brakes. Both are current in the same direction with the same sign. What separates them is context — an advancing odometer, a vehicle state, a charger line — and this page contains one window of each so the two can be compared directly.

Every field on this page whose sign carries meaning
FieldBoardUnitWhat the sign means
IBMSmApositive = out of the pack (discharge) · negative = into the pack
battery_ampsMBBApositive = out of the pack (discharge) · negative = into the pack
battery_current_ampsMBBApositive = out of the pack (discharge) · negative = into the pack
current_aBMSApositive = out of the pack (discharge) · negative = into the pack
current_ampsBMSApositive = out of the pack (discharge) · negative = into the pack
current_maBMSmApositive = out of the pack (discharge) · negative = into the pack
motor_ampsMBBApositive = driving · negative = regenerating
motor_current_ampsMBBApositive = driving · negative = regenerating
power_kwbothkWpositive = out of the pack · negative = into the pack

Positive is out of the pack. Every one of these fields is the same channel in both directions, which is why the channel alone cannot tell regenerative braking from a wall socket: both are current entering the battery. What separates them is the surrounding session, not the number.

One place in the codebase owns this

The rule above is not restated wherever it is needed. It lives in a single module that every chart, badge and export in this app reads, so that a sign convention cannot drift between two screens showing the same ride. The glossary entries in the table are generated from the same source the extracts are.

Fields that do not mean what they are called

Some fields carry a value that is not the quantity the name promises, and printing them uncritically is how a page publishes something false while every step of the arithmetic is correct.

The extractor marks each of these on each affected window, with one of two instructions. SUPPRESSED means the value would be untrue if published as a reading, so it is dropped from the composed row; 8 values on this page are suppressed for that reason. It is not deleted: the interactive window still prints the stored raw line underneath, suppressed fields and all, because a page that quietly edited what the database holds would be committing a second version of the same offence — this table is the explanation that goes with it. SHOWN WITH A CAVEAT means the value is printed in the row itself, because the misunderstanding it invites is worth having in front of you — usually with the correct number on the adjacent line.

The list is not a list of decoder bugs. Every one of these is a real field in a real record whose name promises more than the record delivers.

Fields that mean something other than what they are called
FieldBoardHandlingWindowsWhat it really is
actMBBshown with a caveat1A limit of 2,147,483,647 is 2^31 − 1: the sentinel for “no limit”. The curr and pow values on the same line are real measurements. A limit and a non-limit share one line.
capacitor_voltage_voltsMBBsuppressed2Reads 4,294,967.196 on some lines. The channel is in millivolts — on the same line the module reads 107.998 V and the capacitor 108.812 V, which is 108,812 mV on a ~108 V pack — so the stored raw value is 4,294,967,196, and that is 0xFFFFFF9C: −100 read as an unsigned 32-bit integer instead of a signed one. A negative hundred millivolts with its sign bit in the wrong place, not a reading. Rendered as a number the page publishes four million volts. An earlier draft of this row called it “0xFFFFFFFF microvolts, the not-measured sentinel”, and both halves were wrong: 0xFFFFFFFF would print 4,294,967.295, which is not the number in the file. Getting a sentinel wrong on the list of fields that mean something other than what they are called is the exact failure this list is for.
hibernation_declaredBMS, MBBshown with a caveat2A declared sleep is a request, not a duration. A pack that asks for 86,400 s gets 3,330; a main board that asks for 31,536,000 s — a year — is awake five seconds later.
modeBMSshown with a caveat1The pack records mode: Charge on the two regenerating samples in the middle of a ride. Read on its own, the field counts two charges inside one journey.
pack_socMBBsuppressed5On a Gen3 main board this slot reads 32 on records that are not state records — 32 is 0x20, the ASCII space, read at the offset where a state of charge would be. Published as a percentage it puts a motorcycle at 32% three minutes after it finished charging. The rule that drops it is keyed on the RECORD, not on a list of windows: a State change, Fault pending or Fault cleared record on that board reading exactly 32 is the artefact, and anything else is published. It was a per-window list first, and the two windows that actually contain one of those records were not on it.
pack_voltage_mvBMSshown with a caveat6On a SOC Data line pack_voltage_mv is the pack’s nominal capacity in milliamp-hours, not a voltage. It never moves inside a file — the 4 windows here that carry one hold 26,000, 57,400, 104,000, 114,800 and nothing else — while the Discharge level line a second away carries the same field name and a voltage that does move. The pack confirms it in its own words elsewhere in the corpus: Adjusted Capacity was 0, resetting to 114800000 uAH based on 4 bricks, which is 114,800 mAh at four bricks and 57,400 at two. The two lines sit next to each other in several extracts here, which is the most convincing way to learn it and the most dangerous way to publish it.
raw_lineBMSshown with a caveat1The stored raw line on Gen3 is our own record header, not the motorcycle’s words. Passing it off as device text would be the easiest lie this page could tell, which is why every line here carries a provenance badge instead.
serial_numberMBBsuppressed3Carries nothing on Opening Contractor lines — a single stray character, not a serial. Where the suppression applies it is dropped from the composed row rather than blanked, because the placeholder this page uses for a real serial would claim there had been something there to remove. It does not apply everywhere: 3 of the 5 windows that hold the field drop it and 2 still print serial_number: [redacted] in the row, because this particular suppression is declared per window in the extractor rather than derived from the record. That is the same gap pack_soc had, and it is why that one is now decided by the record type instead. Recorded here rather than fixed in the page: the composed line comes from the extractor, and editing a published extract inside the renderer would be a worse cure than the fault.
source_noBMSshown with a caveat1The Gen3 pack’s own record counter is neither unique nor monotonic: numbers repeat inside a single window and arrive out of order. They are different records. Never deduplicate on it and never sort by it.
timestampMBBshown with a caveat1Every main-board timestamp in that one window is reconstructed. The order is the bike’s; the spacing is a division. The app’s own LogEntry carries a timestamp_inferred column for exactly this, and that column, not a guess made in the page, is what drives the badge.

8 field values are suppressed outright across the 19 windows — they would be false if printed. The rest are shown, because the misunderstanding they invite is the thing worth learning. Nothing on this list is a decoder bug: every one is a real field in a real record whose name promises more than the record delivers.

The one that moves between generations

pack_temp_low is the trap with no visible symptom. The glossary is explicit: “Decoded coolest pack temperature ON GEN2. On GEN3 the same slot carries the MOTOR temperature — the app maps it to motor_temp_c there. The name is the parser's, not the bike's, and this is the one field whose meaning moves with the generation.” In the Gen3 riding window above, the two temperature numbers on a state record are therefore not the warmest and coolest parts of one pack. The second is the motor. A page that read them as a pair would publish a 13-degree spread inside a battery that does not have one.

Every field on this page

115 field names appear in the 406 lines published above. They are listed here in full, with the board that writes each one and whether the value was written by the motorcycle, decoded by us, or computed by us.

Every field name that appears in the 406 published lines
FieldMeaningUnitBoardWritten by
AmbientAir temperature in a Gen3 12 V charge-request line.°CMBBdecode
Battery12 V accessory battery voltage in microvolts.µVMBBdecode
CombinedCombined 12 V rail voltage in microvolts.µVMBBdecode
CvHighHighest cell voltage in a Gen3 BMS telemetry record.mVBMSdecode
CvLowLowest cell voltage in a Gen3 BMS telemetry record.mVBMSdecode
DC-DC12 V converter output in a Gen3 12 V charge-request line, in MICROVOLTS.µVMBBdecode
IPack current in a Gen3 BMS telemetry record, in milliamps.mABMSdecode
I_lim_CCCeiling for the constant-current phase of a charge.AMBBdecode
I_lim_chgCharge ceiling the charger is honouring.AMBBdecode
I_lim_dischgDischarge ceiling the charger is honouring.AMBBdecode
I_outCurrent the charger is delivering, in a Gen3 charger-status line.AMBBdecode
NewValue a Gen3 line has adopted.MBBdecode
OldValue a Gen3 line is replacing.MBBdecode
PowerWhat powered a Gen3 board reset.MBBdecode
SOCState of charge in a Gen3 BMS telemetry record, sampled every 60 s.%BMSdecode
SourceWhat woke or reset the board — a hibernate wake pin, a power-on, a watchdog.MBBdecode
SupplyWhich supply rail was behind a board reset.MBBdecode
V_busDC bus voltage in a Gen3 charger-status line.VMBBdecode
V_bus_ocDC bus voltage with the charger not delivering.VMBBdecode
V_modModule voltage in a Gen3 charger-status line.VMBBdecode
adjustment_millivoltsHow far a calibration moved the stored value.mVBMSderived
ambient_temp_celsiusAir temperature at the main board.°CMBBdecode
amp_hoursCoulomb counter since the last full charge. It is the pack's own bookkeeping, not a measurement of what is left.AhBMSdecode
balance_or_currentA slot a 2020-era BMS2 pack reuses for either balancing or current, depending on the record layout.BMSdecode
battery_current_ampsPack current as the MAIN BOARD reports it. Relayed from the BMS.AMBBdecode
bms_charge_enabledThe pack's permission to charge, as relayed to the main board.MBBdecode
bms_temp_celsiusTemperature of the BMS circuit board itself, not of the cells.°CBMSdecode
capacitor_voltage_voltsVoltage the controller's bus capacitors have reached during pre-charge.VMBBdecode
capacity_change_microamp_hoursHow far a correction moved the stored capacity.µAhBMSderived
charger_idWhich charger the bike is addressing. Zero numbers the onboard chargers and any accessory ones together.MBBdecode
charger_nameHuman name of the charger the bike recognised.MBBdecode
contactor_stateOpen or closed. Closed is the moment the pack is electrically connected to the bike; the main board logs the same event under a different name in the same second.BMSdecode
controller_temp_celsiusTemperature of the motor controller.°CMBBdecode
controller_typeWhich controller family the bike carries.MBBdecode
correction_factorMultiplier a calibration applied to the stored value.BMSderived
current_ampsCurrent through the pack, measured by the BMS. The sign is the thing beginners get wrong: a negative number is energy ENTERING the battery. The channel alone cannot say whether that is the wall or regen — only the surrounding session can.ABMSdecode
cutback_percentHow far the BMS is pulling power back, as a percentage.%BMSdecode
cutback_ratioThe same cutback as a fraction.BMSderived
cutback_raw_valueUnscaled register behind the cutback figures.BMSdecode
discharge_current_ampsCurrent the BMS measures leaving the pack on a contactor message.ABMSdecode
discharge_current_maThe same discharge current in milliamps.mABMSdecode
duty_cycle_percentShare of the time a switching element is on.%BMSdecode
event_codeNumeric form of the event type.bothdecode
event_typeUpstream's classification of the entry. Note the app's LogEntry.event_type column is a different thing — it holds the event NAME.bothdecode
fromThe state a Gen3 transition line is leaving.MBBdecode
is_calex_1200wWhether the bike identified a 1200 W Calex charging module.MBBdecode
is_calex_720wWhether the bike identified a 720 W Calex charging module.MBBdecode
is_connectedWhether the charger reports a live connection.MBBdecode
is_external_chargerTrue for a charger that is not the onboard unit. Worth reading NEXT TO the onboard flag rather than on its own: an owner whose onboard charger has failed usually keeps charging from an accessory one, so the charge sessions continue and only the source changes. A count of charges alone therefore shows nothing.MBBdecode
is_key_offKey switch in the off position.MBBdecode
is_key_onKey switch in the on position.MBBdecode
is_key_switchWhether the key switch, rather than something else, is the actor on this line.MBBdecode
is_known_chargerWhether the bike matched the charger to one it knows.MBBdecode
is_known_sourceWhether the bike matched the supply to one it knows.MBBdecode
is_onboard_chargerTrue when the charger built into the motorcycle is what woke it, false for an accessory one. Measured on one 2017 DS whose onboard charger died: 69 wakes carrying this flag over twenty-three months, then zero across 68 continuously covered days while 116 key wakes carried on. Nothing in the file says broken; the flag simply stops appearing, and the key wakes beside it are what prove the motorcycle was still in use rather than parked.MBBdecode
is_poweredWhether the subsystem the line is about currently has power.MBBdecode
key_onKey switch state as a boolean.MBBdecode
key_stateKey switch state as a word.MBBdecode
low_cell_voltage_millivoltsLowest cell voltage on the capacity-correction message.mVBMSdecode
mbb_charge_enabledThe main board's permission to charge. Both this and the pack's must be true.MBBdecode
modeWhat the pack believes it is doing: Charge, Discharge or Idle. It is the BMS's own verdict, and it is what separates a negative current at the wall from a negative current under regen.BMSdecode
modsWhich battery module slots the main board can see.MBBdecode
mods_rawUnscaled bit field behind mods.MBBdecode
module_numberWhich battery module the line is about, counted from zero.bothdecode
module_voltage_voltsTerminal voltage of ONE battery module. A bike with an empty second slot reports 0.0 V for it, which is normal and not a fault.Vbothdecode
modules_statusPer-module status word.MBBdecode
motor_current_ampsCurrent through the motor windings, on the controller side of the pack. Only the main board sees this; the BMS never does.AMBBdecode
motor_rpmRevolutions per minute at the motor. This is the field that makes a Gen2 MBB log a record of a RIDE: the Gen3 main board does not write it, which is why Gen3 rides are not reconstructed from that file.rpmMBBdecode
motor_temp_celsiusMotor winding temperature. Only the main board has this — a BMS file never does, on any generation.°CMBBdecode
newThe same bit field after the change. The bit that moved is the whole message.MBBdecode
new_capacity_microamp_hoursStored pack capacity after a correction.µAhBMSdecode
new_state_of_charge_percentThe charge estimate the BMS adopted after a capacity correction.%BMSdecode
new_value_millivoltsThe millivolt figure a calibration adopted.mVBMSdecode
new_value_voltsThe voltage a calibration adopted.VBMSdecode
odometer_kmTotal distance on the bike. PUBLISHED ROUNDED TO 100 km on this page: a full odometer reading identifies one machine across otherwise unrelated extracts, which is why log_redaction.PUBLISHABLE_READINGS leaves it out of public payloads entirely.kmMBBdecode
oldBit field before a change — disable flags, control flags.MBBdecode
old_capacity_microamp_hoursStored pack capacity before a correction.µAhBMSdecode
old_state_of_charge_percentThe charge estimate the BMS held before a capacity correction rewrote it.%BMSdecode
old_value_millivoltsThe millivolt figure a calibration held before it was rewritten.mVBMSdecode
old_value_voltsThe voltage a calibration held before it was rewritten.VBMSdecode
pack_temp_celsiusTemperature inside the pack, measured by the BMS.°CBMSdecode
pack_temp_high_celsiusWarmest module the main board has been told about.°CMBBdecode
pack_temp_low_celsiusCoolest module the main board has been told about.°CMBBdecode
pack_voltage_millivoltsPack terminal voltage in millivolts, on the BMS message that carries it that way.mVBMSdecode
pack_voltage_mvPack terminal voltage in millivolts, as the BMS stores it.mVBMSdecode
pack_voltage_voltsTerminal voltage of the whole pack. Both boards measure this independently, with their own voltmeters: on one real shutdown the BMS read 103.056 V and the MBB 103.048 V in the same second. The 8 mV is two instruments, not an error.Vbothdecode
power_onTrue on a power-up line, false on a power-down.MBBdecode
power_sourceWhat turned the motorcycle on. The firmware writes a code — 1 key switch, 2 and 3 an external charger, 4 the onboard charger — and every Power On and Power Off record carries it. A wake whose source is the ONBOARD CHARGER is the motorcycle powering itself up because the charger asked: it is the click an owner hears from the garage, written down. That makes this the one field on this page whose ABSENCE is a diagnosis, not whose value is — see the callout beside the fault scene.MBBdecode
power_source_codeNumeric form of the power source.MBBdecode
power_stateOn or off, as the main board's power event records it.MBBdecode
precharge_percentHow far the bus capacitors have been brought up to pack voltage before the contactor closes. Closing early is what welds a contactor.%bothdecode
serial_numberThe module or pack serial. ALWAYS [redacted] on this page: it is a durable identifier that survives a transplant into another bike, and it is the single reason section 10 of this article exists.bothdevice
sevcon_power_onWhether the Sevcon motor controller has power. On shutdown it goes false a second after the contactor opens, and that ordering is the shutdown sequence.MBBdecode
soc_change_percentHow far a capacity correction moved the charge estimate.%BMSderived
soc_percent_1One of three parallel charge estimates a 2020-era BMS2 pack reports. They can disagree; the pack picks one.%BMSdecode
soc_percent_2Second of the three parallel charge estimates on a BMS2 pack.%BMSdecode
soc_percent_3Third of the three parallel charge estimates on a BMS2 pack.%BMSdecode
soc_raw_1Unscaled register behind soc_percent_1.BMSdecode
soc_raw_2Unscaled register behind soc_percent_2.BMSdecode
soc_raw_3Unscaled register behind soc_percent_3.BMSdecode
stateGen3 vehicle state machine: RUN, STOP, HIB, WAIT, PWSU, WAKE, CHRG, STRT.MBBdecode
state_codeNumeric form of the vehicle state.MBBdecode
state_of_charge_percentCoulomb-counted charge left in the pack. The MBB publishes it too, but does not measure it: it receives the BMS figure over CAN, which is why the two agree to the point in 94.4% of paired samples and the MBB is the one that lags.%bothdecode
switched_voltage_millivoltsSwitched-side voltage in millivolts, on the message that carries it that way.mVBMSdecode
switched_voltage_mvSwitched-side voltage in millivolts.mVBMSdecode
switched_voltage_voltsVoltage on the far side of the contactor. Compared against pack voltage it is how the BMS decides whether it is safe to close.VBMSdecode
system_max_voltage_voltsHighest module voltage the main board can see across the whole system.VMBBdecode
system_min_voltage_voltsLowest module voltage the main board can see across the whole system.VMBBdecode
toThe state a Gen3 transition line is entering.MBBdecode
vehicle_stateGen2 vehicle state as a word.MBBdecode
voltage_balance_mvHighest cell minus lowest cell. Verified across 54,858 real lines: this is exactly high - low, and it is the balance number, not a separate measurement.mVBMSderived
voltage_difference_voltsGap between two voltages measured on the same message — module against bus, or system maximum against minimum.Vbothderived
voltage_high_cell_voltsThe strongest cell group in the pack under load.VBMSdecode
voltage_low_cell_voltsThe weakest cell group in the pack under load. Paired with the highest cell it gives the spread, which is the single most useful number about pack health.VBMSdecode
voltage_unloaded_cell_voltsOpen-circuit reading of the lowest cell. It is NOT part of the high/low pair: it is the same cell measured with the load off, so it sits above the loaded figure.VBMSdecode

115 of the 159 field names the extractor knows appear in the extracts on this page; the rest belong to records these windows do not contain. The “written by” column is the one to read first: device means the motorcycle wrote that name, decode means we recovered the value from the bytes, and derived means we computed it and the bike never stored it at all. The extractor refuses to publish a line containing a field name with no entry here, which is how a new field in the parser breaks the build instead of appearing on this page unexplained.

Three entries above disagree with the extracts on this page

Believe the extract, not the glossary row, in all three cases. One. The pack_voltage_volts row quotes a pair of shutdown readings and calls the gap between the two boards eight millivolts; the extraction pass could not reproduce that pair in the files it was taken from, and the two-lane shutdown printed in “Scene: switching off” measures 1 mV instead, on two lines you can read side by side. Two. The pack_voltage_mv row calls the field the pack’s terminal voltage in millivolts, which is true on a Discharge level line and false on a SOC Data line — the trap the table above this one exists to describe, and the reason the same field appears in both tables. Three. The pack_soc row puts the field on 155 of 2,397 entries of a Gen3 main board, or 6%; the census in “Five decode routes” counts 851 of 9,472 over more files, and the extractor’s own re-measurement puts the honest figure lower still — 77 of 2,397, 3.2% — because the other 78 are the 0x20 artefact described above and are not a state of charge at all. All three glossary texts are generated, so the corrections belong in the extractor and not in this page. They are recorded here rather than quietly dropped, because a glossary that contradicts its own examples is worse than one that says where it is out of date.

Reading your own

Nothing on this page requires an account, and neither does the quickest way to look at your own file. There is a decoder on this site that runs entirely inside your browser: you choose a .bin, it reads the header and the records locally, and nothing is uploaded anywhere. It is the right first step if all you want is to know which generation you have and what the file contains.

If you want the sessions, the battery history, the charge costs and the fault timeline, that needs the full parser and an upload. The same rules apply to your file as to the extracts here: two boards, five routes, and a state of charge on the main board that came over a bus rather than out of a measurement.

Whichever route you take, the reading order that works is the one this page is arranged in.

  1. 1Find out which files you have. A main-board file and a battery-management file are not interchangeable, and a battery file on its own does not know which motorcycle it is from — you will be asked.
  2. 2Work out the decode route before believing any field. The route is what decides whether Discharge level in your file is the Gen2 dialect or something else, and the model year will not tell you.
  3. 3Pick the scene you care about, and go to the file that can see it. Rides on Gen3 are in the battery file. The odometer is only ever on a Gen2 main board.
  4. 4Read the sign before reading the magnitude. Positive is out of the pack, and that one line is where most confusion about a Zero log starts.
  5. 5Check the recurrence of anything that looks alarming. An error at every start-up and a fault raised and cleared in the same second are both normal, and both look serious in isolation.
If you only remember one thing from this page, make it this: before you believe a number in a Zero log, ask which board wrote it and whether the words are the motorcycle’s or somebody’s decode. Both questions have changed an answer on this page at least once.

How these extracts were made safe to publish

These lines came from real riders’ motorcycles, and a raw log line is more identifying than it looks. Masking a vehicle identification number is not enough: an odometer reading, a timestamp to the second, a module serial and a pack voltage together single out one machine.

So four things were done, in this order. Every line went through the same redaction the app uses for public payloads, which fails closed — it over-removes rather than risk a miss. Every window was moved by a whole number of days, so that every interval inside it stays exact while the absolute date is gone, and the amount of the shift is deliberately not published. Odometer readings were rounded to the nearest 100 km. And then the extractor scanned its own output for every vehicle identification number in the corpus, for serial shapes, for hardware addresses and for coordinate pairs, and raised on a hit.

The last step is the one that makes the first three worth anything. 5 redaction probes are recorded against these windows, and each one is confirmed PRESENT in the text before redaction runs. A redaction check over clean text proves nothing at all — it passes whether or not the redaction works. The probes exist so the check has something to find.

Two windows come from the maintainer’s own motorcycle, and the drawing most likely to be shared out of context was deliberately cut from one of them.

  • No whole files. Every window is a few dozen lines, because a whole file is re-identifiable and teaches nothing extra.
  • No sample .bin downloads. If you want to try a decoder, use it on your own file in your own browser.
  • Serial numbers appear as [redacted] and are the reason this section exists: a pack serial survives being moved into another motorcycle, so it identifies hardware rather than a moment.

Frequently asked questions

Why does my Gen3 log look almost empty?

Because you are probably reading the main-board file, and on Gen3 that board writes almost no ride telemetry. Across the Gen3 main-board files measured for this page, motor speed, road speed, odometer and pack current are present in 0 of 9,472 entries. It is not silent — it writes the vehicle state, a state of charge once a minute while riding and four temperatures every other sample — but everything a ride is usually reconstructed from is in the battery-management file instead, one sample every 60 seconds. A tool that opens only the main-board file will report a Gen3 log as nearly blank, and it will be reporting the tool’s reach rather than the motorcycle’s.

Why do I get two files instead of one?

Because a Zero has at least two computers that keep logs, and they see different things. The main bike board sees the vehicle — key, kickstand, immobiliser, motor, controller, odometer, chargers, 12 V network. The battery management sees the pack — cells one by one, balancing, pack current, temperatures, the contactor, the coulomb counter. Neither is a subset of the other. In the shutdown window on this page they log the same contactor opening in the same second under two different names, with two independently measured voltages 1 mV apart. A motorcycle with two physical battery modules produces three files rather than two, for the same reason.

Why does the battery file not know which motorcycle it came from?

Because it does not contain the vehicle identification number, on either generation. The main-board file header carries a serial number, a VIN, a firmware revision, a board revision, a model and an initial date. The battery-management header carries an initial date, a BMS serial and a pack serial, and stops. That is why this app asks you which motorcycle a battery file belongs to when it cannot work it out, and it is also why the pack is modelled as a thing with its own identity: a pack serial survives being transplanted into another motorcycle, so it identifies hardware rather than a vehicle.

Why is the current negative while the motorcycle is charging?

Because positive means out of the pack. Every current field in a Zero log follows that convention, so a negative number is energy entering the battery: I=-15864mA in the charging window above is a motorcycle charging at about 15.9 A. The harder half is that the sign alone will not tell you whether that is a wall socket or regenerative braking, because they are the same current in the same direction. The Gen3 riding window on this page contains two negative samples in the middle of a journey that the pack itself labels mode: Charge — so even the pack’s own word for it is not enough. Context settles it, not the channel.

Why is there an ERROR line every time I switch the motorcycle on?

Because some start-up messages are logged at error level and are part of the ritual. In the Gen2 switch-on window on this page, 2 of 26 lines are level errorDisabling CIB for non-timeout reason and CAN1 BIT0 error. count=1 — and the motorcycle goes straight on to close its contactor and ride away. One of them, Disabling CIB for non-timeout reason, recurs across 2 windows of the 19 published here rather than appearing once. The Gen3 equivalent is a fault raised and cleared inside the same second, and the parked window on this page contains one at its hourly wake. Take that as measured over the windows on this page — 7 lines at error or above, 2 of the distinct messages appearing in more than one window — and not as a count over your own whole file, which is the number to check for yourself. What makes a fault meaningful is recurrence plus a downstream consequence: a message that appears at every start-up and is followed by a normal sequence is telling you the sequence ran, not that something is wrong.

Why do two files from the same motorcycle decode into different formats?

Because there are 5 decode routes rather than two, and the model year does not predict which one a file takes. The route depends on the board, the firmware and what the bytes actually are. One route on this page is a 2023 motorcycle whose battery management writes the same vocabulary as a Gen2 from 2014 — the owner opens the file and finds lines that look a decade old, which is correct rather than corrupted. The right way to describe it is that the pack speaks the older dialect, not that a fifth log format exists. Read the route, not the year.

Every line and every number on this page is produced by backend/scripts/gen_log_samples.py from an editorial selection of windows in backend/scripts/log_samples_scenes.json, run against the real corpus: 363 log files, 24 vehicle identification numbers checked against the output, timestamps in UTC. The extractor merges the two files of a pair onto one clock, measures the offset between the two boards rather than assuming it, refuses or labels a window whose gaps are uniform at an interval no firmware uses, redacts every line and then scans its own output for the identifiers it claims to have removed. The prose is written by hand and every number and every log line in it is interpolated from the generated module, so a sentence whose evidence moves breaks the build instead of going stale. Three claims this page was drafted from were WRONG and were corrected by that process: a gap between two voltmeters quoted several times larger than the 1 mV the extract shows, a second gap that was self-discharge between two readings taken minutes apart rather than a difference between instruments, and a pair of Gen3 boards said never to write at the same time, which write together in 68 seconds once their clocks are reconciled. Extracted 2026-09-03.

This is an independent, community-maintained reference produced by reverse engineering and by measurement against real log files. It is not published, endorsed or reviewed by Zero Motorcycles, and no field description here should be treated as an official specification. Record layouts and firmware vocabularies change between models and firmware revisions — two motorcycles of the same family in this corpus disagree about whether hibernation exists — so verify against your own files before relying on anything here. Nothing on this page is a substitute for a qualified technician, and nothing here authorises work on the high-voltage system.