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”.
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=-15864mAis a motorcycle charging, not discharging. And the channel alone will not settle it — the Gen3 ride window contains two negative samples the pack itself labelsmode: 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.
- Board —
MBBfor the Main Bike Board,BMSfor 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 675appears 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.
- Level —
debug,info,warning,errororcritical, exactly as the app colours it. A recurringerroris 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: valuehalf of the line, which is where the numbers live.
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.
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.
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.
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
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/RomeprintsUTC+2.0and moves every entry timestamp by two hours, whileInitial 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:
absentis notempty. 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.
| Header line | Gen2 MBB · MBBEntry | Gen2 BMS · BMSEntry | Gen3 MBB · Gen3MBBEntry | Gen3 BMS · Gen3BMSEntry | Gen3 BMS · BMSEntry |
|---|---|---|---|---|---|
| Serial number | redacted | — | Unknown | — | — |
| VIN | redacted | — | redacted | — | — |
| Firmware rev. | redacted | — | Unknown | — | — |
| Board rev. | redacted | — | Unknown | — | — |
| Model | published | — | Unknown | — | — |
| Initial date | date, shifted | date, shifted | Unknown | label, no value | date, shifted |
| Timezone | parser’s host | parser’s host | parser’s host | parser’s host | parser’s host |
| Printing | published | published | published | published | published |
| BMS serial number | — | redacted | — | label, no value | line not printed |
| Pack serial number | — | redacted | — | label, no value | redacted |
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.
| Measured | Gen2 MBB · MBBEntry | Gen2 BMS · BMSEntry | Gen3 MBB · Gen3MBBEntry | Gen3 BMS · Gen3BMSEntry | Gen3 BMS · BMSEntry |
|---|---|---|---|---|---|
| Identity lines the tool prints | 6 | 3 | 6 | 3 | 3 |
| of those, read from the file | 6 | 3 | 0 | 0 | 2 |
| the VIN survives a rename | yes | no VIN line | no — it is the file name | no VIN line | no VIN line |
| our decoder finds a VIN in the bytes | yes | no | yes | no | no |
| how our decoder recognised the board | magic | magic | gen3-header | gen3-header | magic |
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.
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.
| Measured | Gen2 MBB · MBBEntry | Gen2 BMS · BMSEntry | Gen3 MBB · Gen3MBBEntry | Gen3 BMS · Gen3BMSEntry | Gen3 BMS · BMSEntry |
|---|---|---|---|---|---|
| File size, bytes | 131,072 | 131,072 | 131,200 | 131,328 | 262,144 |
| Entries the tool declares | 4,392 | 3,152 | 2,383 | 781 | 7,427 |
| Entries we decode | 4,392 | 3,152 | 2,382 | 1,373 | 7,427 |
| Days covered, by the tool | 27 | 22 | 264 | 20,587 | 11,298 |
| Days covered, by us | 27 | 22 | 264 | 858 | 67 |
| Rows dated outside 2010–2040, the tool | 0 | 0 | 0 | 13 | 1 |
| Rows dated outside 2010–2040, us | 0 | 0 | 0 | 0 | 0 |
| Rows that are control characters only | 0 | 0 | 518 | 210 | 0 |
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.
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.
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.
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.
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.
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.
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.
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, 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.
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.
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.
| Motorcycle | Generation | Offset applied | Co-written seconds, corrected | Co-written seconds, raw | Offset drift across the pair |
|---|---|---|---|---|---|
| DS 2014 | 2 | 0 s | 350 | 350 | 0 s |
| FXS 2016 | 2 | 0 s | 740 | 740 | 10 s |
| SR 2017 | 2 | 0 s | 54 | 54 | not measured |
| FXS 2019 | 2 | 0 s | 530 | 530 | 4 s |
| SR/F 2023 | 3 | -605 s | 68 | 1 | 3 s |
| DSR/X 2023 | 3 | -2,697 s | 72 | 3 | 16 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.”
| Quantity | Gen2 MBB · MBBEntry — of 27,533 | Gen2 BMS · BMSEntry — of 32,436 | Gen3 MBB · Gen3MBBEntry — of 9,472 | Gen3 BMS · Gen3BMSEntry — of 3,607 | Gen3 BMS · BMSEntry — of 7,427 |
|---|---|---|---|---|---|
| State of charge | 7,018 | 9,139 | 851 | 945 | 2,416 |
| Pack voltage | 7,018 | 18,617 | 227 | 1,167 | 5,094 |
| Pack current | 11,006 | 9,010 | 0 | 1,114 | 2,397 |
| Lowest cell | not decoded | 9,139 | not decoded | 1,235 | 2,416 |
| Highest cell | not decoded | 9,139 | not decoded | 1,167 | 2,416 |
| Cell spread | not decoded | 9,139 | not decoded | not decoded | 2,416 |
| Cell at rest | not decoded | not decoded | not decoded | 953 | not decoded |
| Amp-hours discharged | not decoded | 9,139 | not decoded | not decoded | 2,416 |
| Motor speed | 7,077 | not decoded | 0 | not decoded | not decoded |
| Motor temperature | 5,567 | not decoded | not decoded | not decoded | not decoded |
| Controller temperature | 5,567 | not decoded | 238 | not decoded | not decoded |
| Pack temperature | 7,018 | 9,139 | 168 | 0 | 2,416 |
| Board or ambient temperature | 7,018 | 9,138 | 238 | not decoded | 2,416 |
| Odometer | 5,626 | not decoded | 0 | not decoded | not decoded |
| Road speed | 15,695 | not decoded | 0 | not decoded | not decoded |
| Vehicle state | 2,921 | not decoded | 1,664 | not decoded | not decoded |
| Pack state | not decoded | 9,008 | not decoded | 428 | 2,395 |
| Charge current ceiling | 0 | not decoded | 668 | not decoded | not decoded |
| Module voltage | 3,988 | not decoded | 227 | not decoded | not 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.
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.
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.
| Route | Board | Entry class | Files | Entries | Models here | Model years |
|---|---|---|---|---|---|---|
| gen2-mbb | MBB | MBBEntry | 4 | 27,533 | DS, FXS | 2014–2019 |
| gen2-bms | BMS | BMSEntry | 5 | 32,436 | DS, DSR, SR, FXS | 2014–2018 |
| gen3-mbb | MBB | Gen3MBBEntry | 4 | 9,472 | SR/F, DSR/X | 2021–2024 |
| gen3-bms | BMS | Gen3BMSEntry | 3 | 3,607 | DSR/X, SR/F | 2021–2023 |
| gen3-bike-gen2-bms | BMS | BMSEntry | 1 | 7,427 | FXE | 2023 |
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 | Gen2 MBB · MBBEntry | Gen2 BMS · BMSEntry | Gen3 MBB · Gen3MBBEntry | Gen3 BMS · Gen3BMSEntry | Gen3 BMS · BMSEntry |
|---|---|---|---|---|---|
| Switching on | 26 lines | no window, reason recorded | 55 lines (2 windows) | — | no window, reason recorded |
| Riding | 21 lines | no window, reason recorded | 23 lines | 22 lines | no window, reason recorded |
| Charging | — | 22 lines | 26 lines | 18 lines | 16 lines |
| Parked, key in your pocket | 18 lines · reconstructed | 16 lines | 25 lines | — | no window, reason recorded |
| A fault | — | 20 lines | 25 lines | 13 lines | no window, reason recorded |
| Switching off | 21 lines | 25 lines | 14 lines | no window, reason recorded | no 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 error — Disabling 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.
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.
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.
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.
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
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.
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_numberin chiaro dentro il proprio testo grezzo: il seriale vero del pacco, otto caratteri, lo stesso che l intestazione BMS stampa comePack serial number. E il motivo per cuiBatterye un entita a se e non una colonna diBike: 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.
battery_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 moreKey on · SR/F 2023
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.
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.
CvHighCvLowII_lim_CCI_lim_chgI_lim_dischgI_outSOCV_busV_bus_ocV_modambient_tempbms_report_modebms_sequencebms_state_codecell_high_mvand 24 moreScene: 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.
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.
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.
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.
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
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.
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.
Riding a Gen3: everything the ride knows is in the battery file
Measured cadence in this window: BMS every 1 min over 22 entries.
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
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.
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).
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 · ALowest cell
axis cropped to 18 mV · mVEvery plotted value, as a table — 17 samples
| elapsed | log clock | event | State of charge (%) | Pack current (A) | as stored |
|---|---|---|---|---|---|
| 0 | 08:41:40 | Charging | 77 | −9 A | −9 A |
| +10 min | 08:51:40 | Charging | 79 | −10 A | −10 A |
| +20 min | 09:01:40 | Charging | 81 | −9 A | −9 A |
| +30 min | 09:11:40 | Charging | 83 | −13 A | −13 A |
| +40 min | 09:21:40 | Charging | 84 | −13 A | −13 A |
| +50 min | 09:31:40 | Charging | 86 | −12 A | −12 A |
| +60 min | 09:41:40 | Charging | 88 | −14 A | −14 A |
| +70 min | 09:51:40 | Charging | 90 | −14 A | −14 A |
| +80 min | 10:01:40 | Charging | 92 | −9 A | −9 A |
| +90 min | 10:11:40 | Charging | 94 | −13 A | −13 A |
| +100 min | 10:21:40 | Charging | 100 | −1 A | −1 A |
| elapsed | log clock | event | State of charge (%) | Pack current (A) | as stored | Lowest cell (mV) | Pack mode |
|---|---|---|---|---|---|---|---|
| 0 | 07:41:39 | BMS telemetry (Gen3) | 59 | −15.872 A | −15,872 mA | 3,806 | Charge |
| +1 min | 07:42:39 | BMS telemetry (Gen3) | 59 | −15.914 A | −15,914 mA | 3,808 | Charge |
| +2 min | 07:43:39 | BMS telemetry (Gen3) | 59 | −15.880 A | −15,880 mA | 3,810 | Charge |
| +3 min | 07:44:39 | BMS telemetry (Gen3) | 59 | −15.923 A | −15,923 mA | 3,812 | Charge |
| +3 min 24 s | 07:45:03 | BMS telemetry (Gen3) | 59 | −15.864 A | −15,864 mA | 3,813 | Charge |
| +3 min 24 s | 07:45:03 | BMS telemetry (Gen3) | 59 | 0.000 A | −13 mA | 3,804 | Idle |
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 limits — curr 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.
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.
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.
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.
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
Measured cadence in this window: MBB every 10 min over 8 entries.
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.
ah_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 moreCharging · DSR/X 2023
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.
AmbientBatteryCombinedCvHighCvLowDC-DCISOCbms_report_modebms_sequencebms_state_codebms_state_namecell_ocv_low_mvcharge_limit_currcharge_limit_powcontactor_stateand 14 moreScene: 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.
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.
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.
── 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.
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.
| Interval | What writes it |
|---|---|
| 0 s and 1 s | Events sharing a second. Ordinary, and the reason a Gen3 window is ordered by (timestamp, source, sequence) and never by timestamp alone. |
| 60 s | Periodic telemetry while the motorcycle is on — a Gen2 ride sample, a Gen3 pack sample, a Gen3 vehicle-state record. |
| 320 s | The Gen3 main board’s charging cadence. It is not the riding cadence and not the Gen2 charging cadence. |
| 600 s | The Gen2 charging cadence, on both boards. |
| 3,330 s | What a Gen3 pack actually sleeps for. It asks for 86,400 s and the main board wakes it early. |
| 3,691 s | The Gen3 hibernation beat: 3,600 s of declared sleep plus the 91 s the board spends awake. |
| 86,400 s | The 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.
── 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
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.
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.
AmbientBatteryCombinedCvHighCvLowDC-DCIPowerSOCSourceSupplybms_report_modebms_sequencebms_state_codebms_state_namecell_ocv_low_mvand 16 moreParked · DSR 2018
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.
ah_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 moreScene: 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 Notification — BMS_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.
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.
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.
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
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.
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 Registeredporta il proprioserial_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.
ah_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 moreFault · DSR/X 2024
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.
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.
AmbientBatteryCombinedDC-DCPowerSourceSupplycharge_limit_currcharge_limit_powdischarge_limit_currdischarge_limit_powfrommodule_brick_countmodule_voltage_mvnewoldand 7 moreScene: 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 change — from: 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.
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.
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.
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
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.
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.
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.
| Field | Board | Unit | What the sign means |
|---|---|---|---|
| I | BMS | mA | positive = out of the pack (discharge) · negative = into the pack |
| battery_amps | MBB | A | positive = out of the pack (discharge) · negative = into the pack |
| battery_current_amps | MBB | A | positive = out of the pack (discharge) · negative = into the pack |
| current_a | BMS | A | positive = out of the pack (discharge) · negative = into the pack |
| current_amps | BMS | A | positive = out of the pack (discharge) · negative = into the pack |
| current_ma | BMS | mA | positive = out of the pack (discharge) · negative = into the pack |
| motor_amps | MBB | A | positive = driving · negative = regenerating |
| motor_current_amps | MBB | A | positive = driving · negative = regenerating |
| power_kw | both | kW | positive = 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.
| Field | Board | Handling | Windows | What it really is |
|---|---|---|---|---|
| act | MBB | shown with a caveat | 1 | A 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_volts | MBB | suppressed | 2 | Reads 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_declared | BMS, MBB | shown with a caveat | 2 | A 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. |
| mode | BMS | shown with a caveat | 1 | The 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_soc | MBB | suppressed | 5 | On 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_mv | BMS | shown with a caveat | 6 | 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 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_line | BMS | shown with a caveat | 1 | The 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_number | MBB | suppressed | 3 | Carries 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_no | BMS | shown with a caveat | 1 | The 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. |
| timestamp | MBB | shown with a caveat | 1 | Every 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.
| Field | Meaning | Unit | Board | Written by |
|---|---|---|---|---|
| Ambient | Air temperature in a Gen3 12 V charge-request line. | °C | MBB | decode |
| Battery | 12 V accessory battery voltage in microvolts. | µV | MBB | decode |
| Combined | Combined 12 V rail voltage in microvolts. | µV | MBB | decode |
| CvHigh | Highest cell voltage in a Gen3 BMS telemetry record. | mV | BMS | decode |
| CvLow | Lowest cell voltage in a Gen3 BMS telemetry record. | mV | BMS | decode |
| DC-DC | 12 V converter output in a Gen3 12 V charge-request line, in MICROVOLTS. | µV | MBB | decode |
| I | Pack current in a Gen3 BMS telemetry record, in milliamps. | mA | BMS | decode |
| I_lim_CC | Ceiling for the constant-current phase of a charge. | A | MBB | decode |
| I_lim_chg | Charge ceiling the charger is honouring. | A | MBB | decode |
| I_lim_dischg | Discharge ceiling the charger is honouring. | A | MBB | decode |
| I_out | Current the charger is delivering, in a Gen3 charger-status line. | A | MBB | decode |
| New | Value a Gen3 line has adopted. | — | MBB | decode |
| Old | Value a Gen3 line is replacing. | — | MBB | decode |
| Power | What powered a Gen3 board reset. | — | MBB | decode |
| SOC | State of charge in a Gen3 BMS telemetry record, sampled every 60 s. | % | BMS | decode |
| Source | What woke or reset the board — a hibernate wake pin, a power-on, a watchdog. | — | MBB | decode |
| Supply | Which supply rail was behind a board reset. | — | MBB | decode |
| V_bus | DC bus voltage in a Gen3 charger-status line. | V | MBB | decode |
| V_bus_oc | DC bus voltage with the charger not delivering. | V | MBB | decode |
| V_mod | Module voltage in a Gen3 charger-status line. | V | MBB | decode |
| adjustment_millivolts | How far a calibration moved the stored value. | mV | BMS | derived |
| ambient_temp_celsius | Air temperature at the main board. | °C | MBB | decode |
| amp_hours | Coulomb counter since the last full charge. It is the pack's own bookkeeping, not a measurement of what is left. | Ah | BMS | decode |
| balance_or_current | A slot a 2020-era BMS2 pack reuses for either balancing or current, depending on the record layout. | — | BMS | decode |
| battery_current_amps | Pack current as the MAIN BOARD reports it. Relayed from the BMS. | A | MBB | decode |
| bms_charge_enabled | The pack's permission to charge, as relayed to the main board. | — | MBB | decode |
| bms_temp_celsius | Temperature of the BMS circuit board itself, not of the cells. | °C | BMS | decode |
| capacitor_voltage_volts | Voltage the controller's bus capacitors have reached during pre-charge. | V | MBB | decode |
| capacity_change_microamp_hours | How far a correction moved the stored capacity. | µAh | BMS | derived |
| charger_id | Which charger the bike is addressing. Zero numbers the onboard chargers and any accessory ones together. | — | MBB | decode |
| charger_name | Human name of the charger the bike recognised. | — | MBB | decode |
| contactor_state | Open 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. | — | BMS | decode |
| controller_temp_celsius | Temperature of the motor controller. | °C | MBB | decode |
| controller_type | Which controller family the bike carries. | — | MBB | decode |
| correction_factor | Multiplier a calibration applied to the stored value. | — | BMS | derived |
| current_amps | Current 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. | A | BMS | decode |
| cutback_percent | How far the BMS is pulling power back, as a percentage. | % | BMS | decode |
| cutback_ratio | The same cutback as a fraction. | — | BMS | derived |
| cutback_raw_value | Unscaled register behind the cutback figures. | — | BMS | decode |
| discharge_current_amps | Current the BMS measures leaving the pack on a contactor message. | A | BMS | decode |
| discharge_current_ma | The same discharge current in milliamps. | mA | BMS | decode |
| duty_cycle_percent | Share of the time a switching element is on. | % | BMS | decode |
| event_code | Numeric form of the event type. | — | both | decode |
| event_type | Upstream's classification of the entry. Note the app's LogEntry.event_type column is a different thing — it holds the event NAME. | — | both | decode |
| from | The state a Gen3 transition line is leaving. | — | MBB | decode |
| is_calex_1200w | Whether the bike identified a 1200 W Calex charging module. | — | MBB | decode |
| is_calex_720w | Whether the bike identified a 720 W Calex charging module. | — | MBB | decode |
| is_connected | Whether the charger reports a live connection. | — | MBB | decode |
| is_external_charger | True 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. | — | MBB | decode |
| is_key_off | Key switch in the off position. | — | MBB | decode |
| is_key_on | Key switch in the on position. | — | MBB | decode |
| is_key_switch | Whether the key switch, rather than something else, is the actor on this line. | — | MBB | decode |
| is_known_charger | Whether the bike matched the charger to one it knows. | — | MBB | decode |
| is_known_source | Whether the bike matched the supply to one it knows. | — | MBB | decode |
| is_onboard_charger | True 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. | — | MBB | decode |
| is_powered | Whether the subsystem the line is about currently has power. | — | MBB | decode |
| key_on | Key switch state as a boolean. | — | MBB | decode |
| key_state | Key switch state as a word. | — | MBB | decode |
| low_cell_voltage_millivolts | Lowest cell voltage on the capacity-correction message. | mV | BMS | decode |
| mbb_charge_enabled | The main board's permission to charge. Both this and the pack's must be true. | — | MBB | decode |
| mode | What 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. | — | BMS | decode |
| mods | Which battery module slots the main board can see. | — | MBB | decode |
| mods_raw | Unscaled bit field behind mods. | — | MBB | decode |
| module_number | Which battery module the line is about, counted from zero. | — | both | decode |
| module_voltage_volts | Terminal 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. | V | both | decode |
| modules_status | Per-module status word. | — | MBB | decode |
| motor_current_amps | Current through the motor windings, on the controller side of the pack. Only the main board sees this; the BMS never does. | A | MBB | decode |
| motor_rpm | Revolutions 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. | rpm | MBB | decode |
| motor_temp_celsius | Motor winding temperature. Only the main board has this — a BMS file never does, on any generation. | °C | MBB | decode |
| new | The same bit field after the change. The bit that moved is the whole message. | — | MBB | decode |
| new_capacity_microamp_hours | Stored pack capacity after a correction. | µAh | BMS | decode |
| new_state_of_charge_percent | The charge estimate the BMS adopted after a capacity correction. | % | BMS | decode |
| new_value_millivolts | The millivolt figure a calibration adopted. | mV | BMS | decode |
| new_value_volts | The voltage a calibration adopted. | V | BMS | decode |
| odometer_km | Total 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. | km | MBB | decode |
| old | Bit field before a change — disable flags, control flags. | — | MBB | decode |
| old_capacity_microamp_hours | Stored pack capacity before a correction. | µAh | BMS | decode |
| old_state_of_charge_percent | The charge estimate the BMS held before a capacity correction rewrote it. | % | BMS | decode |
| old_value_millivolts | The millivolt figure a calibration held before it was rewritten. | mV | BMS | decode |
| old_value_volts | The voltage a calibration held before it was rewritten. | V | BMS | decode |
| pack_temp_celsius | Temperature inside the pack, measured by the BMS. | °C | BMS | decode |
| pack_temp_high_celsius | Warmest module the main board has been told about. | °C | MBB | decode |
| pack_temp_low_celsius | Coolest module the main board has been told about. | °C | MBB | decode |
| pack_voltage_millivolts | Pack terminal voltage in millivolts, on the BMS message that carries it that way. | mV | BMS | decode |
| pack_voltage_mv | Pack terminal voltage in millivolts, as the BMS stores it. | mV | BMS | decode |
| pack_voltage_volts | Terminal 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. | V | both | decode |
| power_on | True on a power-up line, false on a power-down. | — | MBB | decode |
| power_source | What 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. | — | MBB | decode |
| power_source_code | Numeric form of the power source. | — | MBB | decode |
| power_state | On or off, as the main board's power event records it. | — | MBB | decode |
| precharge_percent | How far the bus capacitors have been brought up to pack voltage before the contactor closes. Closing early is what welds a contactor. | % | both | decode |
| serial_number | The 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. | — | both | device |
| sevcon_power_on | Whether the Sevcon motor controller has power. On shutdown it goes false a second after the contactor opens, and that ordering is the shutdown sequence. | — | MBB | decode |
| soc_change_percent | How far a capacity correction moved the charge estimate. | % | BMS | derived |
| soc_percent_1 | One of three parallel charge estimates a 2020-era BMS2 pack reports. They can disagree; the pack picks one. | % | BMS | decode |
| soc_percent_2 | Second of the three parallel charge estimates on a BMS2 pack. | % | BMS | decode |
| soc_percent_3 | Third of the three parallel charge estimates on a BMS2 pack. | % | BMS | decode |
| soc_raw_1 | Unscaled register behind soc_percent_1. | — | BMS | decode |
| soc_raw_2 | Unscaled register behind soc_percent_2. | — | BMS | decode |
| soc_raw_3 | Unscaled register behind soc_percent_3. | — | BMS | decode |
| state | Gen3 vehicle state machine: RUN, STOP, HIB, WAIT, PWSU, WAKE, CHRG, STRT. | — | MBB | decode |
| state_code | Numeric form of the vehicle state. | — | MBB | decode |
| state_of_charge_percent | 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. | % | both | decode |
| switched_voltage_millivolts | Switched-side voltage in millivolts, on the message that carries it that way. | mV | BMS | decode |
| switched_voltage_mv | Switched-side voltage in millivolts. | mV | BMS | decode |
| switched_voltage_volts | Voltage on the far side of the contactor. Compared against pack voltage it is how the BMS decides whether it is safe to close. | V | BMS | decode |
| system_max_voltage_volts | Highest module voltage the main board can see across the whole system. | V | MBB | decode |
| system_min_voltage_volts | Lowest module voltage the main board can see across the whole system. | V | MBB | decode |
| to | The state a Gen3 transition line is entering. | — | MBB | decode |
| vehicle_state | Gen2 vehicle state as a word. | — | MBB | decode |
| voltage_balance_mv | Highest 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. | mV | BMS | derived |
| voltage_difference_volts | Gap between two voltages measured on the same message — module against bus, or system maximum against minimum. | V | both | derived |
| voltage_high_cell_volts | The strongest cell group in the pack under load. | V | BMS | decode |
| voltage_low_cell_volts | The 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. | V | BMS | decode |
| voltage_unloaded_cell_volts | Open-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. | V | BMS | decode |
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.
- 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.
- 2Work out the decode route before believing any field. The route is what decides whether
Discharge levelin your file is the Gen2 dialect or something else, and the model year will not tell you. - 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.
- 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.
- 5Check the recurrence of anything that looks alarming. An
errorat every start-up and a fault raised and cleared in the same second are both normal, and both look serious in isolation.
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
.bindownloads. 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 error — Disabling 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.
Sources:
- zero-motorcycle-community/zero-log-parser — the upstream open-source parser this project builds on
- zero-log-parser — LOG_STRUCTURE.md, the community reference for the Gen1/Gen2 record layout
- zero-log-parser issue #16, "Support SR/F (Gen3) logs"
- Zero Motorcycles — owner manuals (official downloads)
- Pre-charge circuits — background on why a contactor is not closed directly
Continue reading
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.