What the bus was actually saying - LS-CAN catalog, take two
New findings and corrections to CAN map
In the first decoding post I ended with a YAML file and many unknown IDs. Then the dashboard went into the car and started using that file for real. Wrong numbers on the screen are a fast way to learn which decodes were wrong.
So I went back to the car. Same Vectra C. Same method: listen only, then poke and watch - do one action, undo it, see which bytes change. This post covers three things:
- Fixes that mattered for the dashboard (clock, speed, wipers, brightness).
- A simple map of which modules sit on which CAN speed.
- A short list of IDs I still want to decode next.
Everything here comes from sniffing and poke-and-watch. A later post will talk about dealer tools. Not this one.
Files attached to this post:
LS_CAN_IDS.yaml- updated signal list (18 frames, 40 signals)CAN_LS_20261014.LOG- about 37 seconds of listen-only capture, same CSV format as before
If you still have the June CAN_LS.LOG and the old YAML, keep them open. Comparing the two logs helps a lot.
Where we left off
In Making sense of the noise I built a catalog by poke-and-watch: press one stalk, watch which bytes flip, write it down. That gave RPM, speed, doors, blinkers, outdoor temperature, and many "I saw this ID, but I do not know it yet" entries.
The dashboard post then used those names in LVGL with can_ls_read_phys() - so the screen reads signals by name instead of raw hex.
Half the bus was still unknown. The 0x621-0x629 group especially. The screen ignores it. That part is still a mystery.
One rule changed: if I cannot take one line from a real log and get a value that matches the car, I do not give that signal a name. Guessing was blocking the IDs I can still learn.
Poke-and-watch still works - and still tricks you
Same method as last time:
- Start the sniffer and wait for idle traffic.
- Do one action (for example left blinker).
- Note which IDs and bytes change.
- Undo the action.
- Check which bytes go back.
- Write it down.
Without that, you only have noise.
Three mistakes I kept making:
- Two things change together. Open a door and the interior light also changes. Now you have two changing columns and one story.
- Forum maps are not your car. Notes from Astra / GMLAN forums are useful hints. On this Vectra some of those IDs never appear. Some "wrong" IDs do appear.
- A number is not always what it looks like. A byte that goes up every second is not always "seconds." A value near the speed field is not always speed. A counter near the odometer is not always trip distance.
So I made the YAML smaller on purpose. The unknown IDs got a clearer plan.
Same car, new quiet session
October evening. Engine idle. Same LS-CAN tap on the OBD port (pin 1, single-wire, 33.3 kbps). Listen only. Same line format as June:
timestamp_us,bus,direction,id,dlc,byte0,byte1,...
About 37 seconds, 1572 frames, 35 IDs. Thirty-four of those IDs were already in the June log. The car did not invent a new language - only newer values.
Things that should move over months did move:
| Signal | June log | October log |
|---|---|---|
Odometer 0x190 |
00 01 40 FE 80 → 327,674 km |
00 01 4A 92 80 → 338,506 km |
Outdoor temp 0x445 |
00 7B → 21.5 °C |
00 67 → 11.5 °C |
Blinkers 0x260 |
left / off / right | same codes: 25, 00, 3A |
The four decode steps from the first post still work for these. The problems were elsewhere.
Fixes that mattered
Clock 0x440 - hour is a shift, not minus 0x80
From the October log, early in the session (wall clock about 20:40):
792471,LS-CAN,RX,0x440,8,A0,A8,64,02,00,68,A0,1A
Old idea: hour ≈ byte0 - 0x80. That gives 0xA0 - 0x80 = 32. There is no 32 o'clock.
What matches the capture:
- hour =
byte0 >> 3→0xA0 >> 3= 20 - minutes = if
byte1 >= 0x80thenbyte1 - 0x80, elsebyte1 / 4→0xA8 - 0x80= 40 - seconds =
byte2 / 4→0x64 / 4= 25
20:40:25. The next 0x440 lines increase byte2 by four each second (64, 68, 6C, 70...). That matches 0.25 s packing. The old -0x80 hour idea is wrong.
Speed 0x108 - packed, not two full speed bytes
Idle line:
96528,LS-CAN,RX,0x108,8,11,0A,E4,00,00,00,EA,BB
- Engine state
0x11→ running - RPM =
0x0AE4big-endian × 0.25 → 697 rpm - Speed on this Vectra is
(byte4 × 2) + (byte5 >> 7), not "byte4 and byte5 are both speed"
Here the speed bits are zero → 0 km/h. That is correct for a parked car. If you treat byte5 as a second speed byte, trip/status flags (often with 0x80) look like fake speed.
Wheel pulses are not the trip meter
Bytes 6-7 of 0x108 are a 16-bit counter. Factor: 1.5748 cm per tick. A few thousand raw counts are only tens of metres of wheel travel - not hundreds of kilometres of "trip."
Resetting the trip on the instrument cluster does not clear this counter. That is why the dashboard trip from the previous post adds speed/pulses in firmware. The bus does not send a clear "trip A" value on LS-CAN in an obvious place.
Wipers 0x175 - mode is one byte
Frame 0x175 comes from the CIM (column module): stalks, wash, wheel buttons, wiper speed. Older notes treated wiper mode as a messy 16-bit value. On this car the mode is simply byte 1:
| byte1 | Meaning |
|---|---|
| 16 | single wipe |
| 160 | intermittent |
| 176 | constant |
| 208 | constant fast |
Byte 4 still has speed levels (51 / 102 / 153 / 204 for levels 1-4). Same idea as before - just cleaner.
While I used the stalk, short bits also appeared in byte2 (0x10 / 0x20 for left/right). Frame 0x260 keeps the steady blinker state. Two frames, one action.
Interior brightness - 4% minimum when lights are on
959970,LS-CAN,RX,0x235,2,00,47
The percent math from the previous post is still correct. What I learned better is how the car behaves.
- Light switch off, door closed → backlight off.
- Lights on → you can dim down, but not to zero. The bus stops around
0x47≈ 4%. The cluster stays a little lit. - To turn the backlight fully off, flip the light switch to off. The dimmer knob alone will not do it.
So in this log, with lights on and a dark cabin, 0x47 is the lowest dim level, not "off."
What I removed from the YAML
The June YAML grew to about 38 frames / 50+ signals, including many "sample only" and Astra guesses. The new LS_CAN_IDS.yaml has 18 frames / 40 signals.
Still named and used:
0x100 wake, 0x108 engine/rpm/speed, 0x130 injection counter, 0x145 coolant (+ winter bit), 0x155 A/C, 0x170 key, 0x175 CIM, 0x190 odometer, 0x230 doors, 0x235 interior light, 0x23A light switch, 0x260 blinkers, 0x350 exterior lights, 0x360 clutch, 0x370 handbrake, 0x415 passenger window, 0x440 clock, 0x445 outdoor temp.
Still on the bus in October, still unnamed (17 IDs) - next targets:
0x090, 0x115, 0x11A, 0x140, 0x220, 0x305, 0x315, 0x330, 0x340, 0x405, 0x420, 0x525, 0x621, 0x622, 0x625, 0x626, 0x629.
A few notes:
0x500"battery" appears in some community maps. It is missing from both June and October Vectra LS-CAN logs. Removed.0x145- I dropped the cruise/phase guesses. Left: winter mode + coolant. Coolant is stillraw - 40°C.0x350stayed. It does appear on this bus (exterior lights / ignition flags). Older notes saying "not on Vectra LS" were wrong for my car. For ignition on the dashboard I still prefer0x108/0x170if they disagree with one bit.0x405/0x420are not the door-open icons. The dashboard already uses0x230for that - bits that stay set while a door is open. These higher IDs look more like short events from the door modules (for example lock button press). They stay in the log, but not in the named YAML, until I do a careful lock/unlock/window session - same style as blinkers.
A smaller YAML did not make the dashboard worse. It made the icons stop lying.
Which modules live on which CAN speed
As in the first CAN post, the Vectra C does not have one single CAN bus. It has three speeds. Different modules live on different buses.
| Network | Speed | Wire type | Typical modules |
|---|---|---|---|
| HS-CAN | 500 kbps | Dual wire | Engine, gearbox, ABS, power steering, CIM diagnostics, headlamp leveling |
| MS-CAN | ~95.6 kbps (older) / ~92.5 kbps (newer) | Dual wire | Radio (EHU), info display (DIS), phone module (UHP), DAB, climate panel (ECC) |
| LS-CAN (also called SW-CAN) | 33.3 kbps | Single wire (OBD pin 1) | Body, doors, cluster, underhood/rear modules, airbag, parking aid, … |
More detail for this Vectra C / Signum family:
HS-CAN - 500 kbps
Engine (Z16 / Z18 / Z19 / …), gearbox (AF / MTA / CVTronic), ABS/TC/ESP, EHPS (power steering), CIM (steering column - diagnostics on HS; stalk data also on LS), immobiliser, AHL/AFL (headlamp leveling).
MS-CAN - about 95.6 / 92.5 kbps
EHU (head unit), DIS (display on MS path), UHP (phone), DAB (~95.6). ECC climate around ~92.5 in my notes.
LS-CAN / SW-CAN - 33.3 kbps
BCM (body), UEC/REC (underhood / rear electrical centres), instrument cluster, DDM/PDM (driver / passenger door), SDM (airbag), PAS (parking aid), SLM (shift lever), TPMS, EHS (heater), SAC (climate on SW path), seat memory/heating, SRM (roof, if fitted), and DIS/TID/GID-CID entries that talk on SW-CAN.
The same box can appear on more than one network depending on year. DIS is a good example (MS vs "ON SWCAN"). My OBD socket still has no MS-CAN, so radio/climate wiring is a problem for later. This post and the dashboard only listen to LS-CAN on pin 1.

Which decoded LS frames belong where (best guess - who needs the data / who likely sends it):
| Area | IDs | What they carry |
|---|---|---|
| Column (CIM) | 0x175 |
Stalks, wash, wheel buttons, wiper mode/speed |
| Body / doors | 0x230, 0x415 |
Door open, locks, window motion |
| Lights | 0x23A, 0x260, 0x350, 0x235 |
Light switch, blinkers, exterior lights, cabin brightness |
| Cluster / time / climate | 0x190, 0x440, 0x445, 0x155 |
Odometer, clock, outdoor temp, A/C bit |
| Powertrain copy on LS | 0x108, 0x145, 0x130, 0x360, 0x370 |
RPM/speed/pulses, coolant, injection counter, clutch, handbrake |
| Key / wake | 0x170, 0x100 |
Key position, bus wake |
Two useful points for the dashboard:
- Most of what the screen needs is already on LS-CAN. That is why firmware only starts the single-wire bus. HS-CAN (500 kbps) is better for deep engine/ABS data. For doors, stalks, and a simple RPM/speed display, LS is enough.
- The radio listens to wheel buttons - it does not create them. CIM keeps sending
0x175even if the head unit is unplugged. That matters if you replace the factory radio and keep the column.
One more idea that helps when you get stuck: asking a module with a diagnostic tool uses different IDs than idle broadcast. Knowing that the cluster answers request 0x255 does not tell you what 0x621 means when nobody asked. Sometimes the same real-world value appears in both places later (for example outdoor temp). Then you can compare. Until then: poke, sniff, match.
Soft guesses for unnamed IDs - next evening in the car, not YAML names yet:
| ID | Soft guess (not proven on my car) | Next test |
|---|---|---|
0x405 / 0x420 |
Door module button / lock events | One lock, unlock, trunk, or window at a time |
0x115 |
Brake or light switch | Brake alone, then light switch alone |
0x11A |
Gear position (Astra notes) | Move gear lever; ignore Astra notes if DLC differs |
0x305 |
Cluster buttons / lights (Astra notes) | Cluster buttons / light switch |
0x621-0x629 |
Fast mystery group | Quiet idle, then one stalk / door / key change; watch which byte moves |
Same YAML path in firmware
The dashboard still does not hard-code mystery math in UI widgets. Signals live in YAML. A small Python script builds can_ls_signals_gen.h. At runtime the code looks up names like "EngineRPMRaw" / "BlinkerModeCode" / "OutdoorTempRaw" and does extract → mask → scale (plus a few helpers for clock, packed speed, and brightness).
Same loop as in June. Only the YAML content got cleaner.
const can_ls_sig_t *sig = can_ls_sig_by_name(&CAN_LS_FRAMES[slot], "OutdoorTempRaw");
float c = can_ls_read_phys(cache[slot].data, cache[slot].dlc, sig);
/* 0x67 → 11.5 °C on the October log */
Try one line yourself
Open CAN_LS_20261014.LOG, find the ID in the new YAML, and use the four steps. Example - outdoor temp:
792471,LS-CAN,RX,0x445,2,00,67
- extract
data[1] = 0x67 = 103 - mask full byte
- scale
103 × 0.5 + (-40) = 11.5 °C
Or blinkers during the stalk test:
10693002,LS-CAN,RX,0x260,3,25,40,80 → left (37)
12332090,LS-CAN,RX,0x260,3,00,40,80 → off
13373257,LS-CAN,RX,0x260,3,3A,40,80 → right (58)
If an ID is missing from the YAML, that is on purpose. The bus is still talking. I just do not want to pretend I know what it says - yet.
What is next
This "take two" did not decode many new IDs. It stopped naming ones I cannot prove, fixed wrong values on the dashboard, and gave me a clearer list for the next evening in the car.
Next steps - still poke-and-watch:
- One-action tests for gear, lock, fog, door buttons, brake - first targets:
0x115,0x11A,0x305,0x405,0x420. - Longer quiet idle, then one clear change, against the
0x621group (622,625,626,629). Fast traffic alone is not a decode. A byte that flips with a stalk is. - Optional: compare a real value (door open, outdoor temp, …) with a diagnostic tool on the matching module later. Idle sniffing alone will not be enough.
- Hardware from the dashboard post is still true: make a proper PCB for the P4 stack, think before removing EHU/DIS, keep the instrument cluster until HS warning lights and the
0x621group are less of a black hole.
Open the new YAML next to CAN_LS_20261014.LOG, decode a few lines by hand. If something does not match your car - good. That is how the June file got smaller, and how the next IDs will get names.