• Hello ES! We could use some help to get us past the finish line on building the new knowledgebase for the forum.
    Can you donate? Please see our fundraising page. Thank you!

Reading a Bosch Classic Line / PowerPack (frame) battery over CAN — 125 kbit/s, full status frame decoded (no motor, no handshake)

theo740

New here
Joined
Jun 25, 2026
Messages
2
Location
1Passwort@es
Hi all,

I wanted to share a working method to read a Bosch Classic Line (Gen 1, ~2011–2015) PowerPack frame battery over CAN on the bench — no motor, no display, and without touching the crypto handshake. The battery happily broadcasts its status in plaintext; you just have to find the right bus settings and decode the format. I verified everything across 6 batteries plus a discharge run.

Hardware
- ESP32 (built-in TWAI/CAN) + SN65HVD230 transceiver, a
- The battery's own 5 V keeps its processor awake; I fed 5 V from the ESP32 into the enable pin.

Connector pinout (frame battery, 5 equal-size contacts in a row)
- Pin 1: CAN-L
- Pin 2: +36 V (B+)
- Pin 3: +5 V (enable — without this the BMS sleeps and sends nothing)
- Pin 4: GND (B−)
- Pin 5: CAN-H

You only need 3 wires for reading: CAN-L, CAN-H, GND — plus 5 V on pin 3 to wake it. (CAN is differential, so if you get nothing, just swap H/L — I had to.)

The key finding: the bus runs at 125 kbit/s — NOT the 500k you read about for the newer generations. At 500k I saw a completely dead bus; after fixing wiring an autobaud sniffer locked onto 125 kbit/s and clean frames appeared.

What the battery sends on its own (≈ every 250 ms, alte:
- 0x30 — status (decoded below)
- 0x31 — mostly zeros; byte 1 is a 0/1 flag/variant

Frame 0x30 decode (8 bytes):

┌──────┬────────────────────────────────────────────────────────────────┐
│ Byte │ Meaning │
├──────┼────────────────────────────────────────────────────────────────┤
│ 0 │ 00 (reserved) │
├──────┼────────────────────────────────────────────────────────────────┤
│ 1 │ 0xC8 — constant on all batteries (model/type ID) │
├──────┼────────────────────────────────────────────────────────────────┤
│ 2 │ Pack voltage in whole volts (BMS-internal, ~±1 ) |
├──────┼────────────────────────────────────────────────────────────────┤
│ 3 │ Temperature in °C │
├──────┼───────────────────────────────────────────────
│ 4 │ State of charge in % │
├──────┼────────────────────────────────────────────────────────────────┤
│ 5 │ Number of charge LED bars │
├──────┼────────────────────────────────────────────────────────────────┤
│ 6 │ 00 (reserved) │
├──────┼────────────────────────────────────────────────────────────────┤
│ 7 │ fixed per-battery value (serial/ID — NOT a checksum) │
└──────┴────────────────────────────────────────────────────────────────┘

Example (battery at 40.6 V, 5 bars, 26 °C):
00 C8 28 1A 5A 05 00 50
→ B2 0x28 = 40 V · B3 0x1A = 26 °C · B4 0x5A = 90 % · B5 = 5 bars

How each byte was verified
- Voltage (B2): measured terminal voltage on 6 packs (37.7→37, 38.4→38, 40.3→40 …). It's whole-volt resolution only.
- SoC (B4) and bars (B5): matched the displayed LED bar0 %).
- Temperature (B3): I predicted one pack was "the coolest" — its B3 came out lowest (26 vs 30–35). Three packs known to be at the same temperature clustered at 32–34.
- Byte 7: I ran an exhaustive CRC-8 / checksum brute-focharge test settled it: over 25 frames B2, B3, B4 and B5 all changed while B7 stayed constant. So it's not a checksum of the payload — it's a fixed per-unit identifier. (Different packs have different B7: 140/80/61/110/65/80.)

What does NOT work (so you don't waste time):
- The battery ignores active requests — RTR scan 0x00–0x3F: no reply; the known challenge IDs 0x72/0x73: no 0x80/0x81. Standalone it only
ever emits 0x30/0x31.
- Full telemetry (per-cell voltages, current) and the power-output enable are gated behind the crypto handshake, which is still unbroken publicly. But remember: that handshake is authentication, not encryption — the status data above is plaintext and freely readable.

So for diagnostics (reading voltage / SoC / temperature off any Classic pack) you don't need to crack anything. Next step on my side is to sniff a complete system (motor + display + battery) to see the full operating-mode traffic.

Happy to answer questions, and I'd love confirmation from anyone with a Classic pack — especially whether your byte 7 is also constant per pack and what value you see.

Cheers.
 
Hi! Great work and writeup!

So in theory, not taking into consideration the potential handshake between motor and battery, one should be able to activate the bike without the OE battery by sending the frames 0x30 and 0x31, and the motor will interpret that as a healthy signal from the battery?

I am trying to get the bike to accept a non bosch battery, and i have gotten the bike to seemingly turn on, but since it does not recieve any confirmation that the battery is ready, it will not activate assist. Only battery level, light icon and km/h icon shows on display, and it can change assist modes with +/-.

Thanks!
 
Thanks, glad it helped!
Short version: 0x30 + 0x31 are enough to make the display happy, but they'll never activate assist — and your symptom is exactly what I'd expect.
Both frames are pure status broadcasts, no authentication in them. I decoded 0x30: B2 = pack voltage (whole volts), B3 = temp °C, B4 = SoC %, B5 = LED bars, B7 = a fixed per-pack ID byte. So they just feed the display telemetry — hence battery level, icons and mode +/- work, but no motor power.
Assist enable sits behind a challenge/response handshake the motor runs against the battery (newer gens: 0x72/0x73 → 0x80/0x81, crypto never publicly broken). The status frames don't satisfy that, so the motor never gets its "genuine & ready" confirmation. On the bench a real pack answered nothing active — no reply to RTR scans or 0x72/0x73 — it only does the handshake when the motor initiates it. So faking a battery means answering that challenge, which is the unsolved part.
If you just want non-Bosch cells, by far the easiest route is to reuse a genuine Bosch BMS board from a dead OE pack and wire your cells to it — handshake is then native, nothing to crack.
What motor/display gen do you have, and do you see any 0x7x/0x8x traffic when powering up with the OE battery?
What I am doing sometimes is putting an additional (non Bosch) battery (same voltage) in parallel and let the original Bosch do the communication.
 
Back
Top