• 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
4
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.
 
I have only just started to look into the issue, as i bought a bike with the old Purion display and Active Line Plus, but there was no battery. I would like to avoid having to buy a 850$ battery, yes you read that right-that's what they cost new in Norway, and used ones that does not have damaged PCB is hard to find.. I have the rear rack PowerPack.
Seems I would need to buy a cheaper of the other PowerPacks with other mount to do some more research. I know it is possible to do the activation with 3rd party application, as there is a guy making and selling the module in Belgium, but I doubt he would give away or sell the procedure for it..
 
OK, I understand. Only realistic way I see: Try to get a used battery. Rear mount or frame mount have the same connector, only the mount does not work. I used a rear mount connector in my setup for doing the above mentioned tests with a frame battery. You can separate the connector from the mount easily.
 
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.
But if you parallel an additional battery the bike will give error 550 due to the current OUT of the BMS not matching the curren IN to the motor...
 
Last edited:
No, it doesn't. Never had problems. No Info on the Bus concerning Current (Bosch Classic Line).
Oh my bad, I was thinking of the BES2 and 3 systems, good to hear BES1 didn't do that stupid check. With the newer versions they check the current and compare.
 
Back
Top