Identifying Steering Wheel Button Packets - An Example
  • 0
  • I thought it might be helpful to share an example of how to go from fresh CBT to capturing and actioning steering wheel button pushes.

    This example is from a VW Golf Mk5, so the process may not work exactly the same for other cars, but should give newbies some tips to get started.

    The process is as follows:

    1. Connect CBT hardware

    2. Log & Process data

    3. Identify packets

    4. Intercept and action

  • 0
  • <b>Connect CBT Hardware</b>

    1. Download the latest CBT firmware from Derek's Github site, build and install onto your CBT.

    2. Identify where you are going to tap into the target CANBus network.

    In the VW Golf, the most direct way to grab the steering wheel buttons is to tap the CANBus directly off the steering wheel itself, but that's quite fiddly and splicing the wires there can be quite difficult.

    An easier way is to pull out the radio and tap the infotainment bus at the back of the radio, but keep in mind that this will limit your ability to send button commands back onto the CANBus due to the way VW implement the two low-speed busses. You can still capture and parse all the button push data from this point.

    1. Splice CAN High into wire #3 on the ribbon cable provided with your CBT, and CAN Low into wire #4. For the moment you can leave everything else unwired, as you'll get power from the USB connection.

    2. Connect a serial terminal program to your CBT. On the Mac I am using CoolTerm. Configure it to view, log and send HEX data.

    3. Open the 'Send String' feature of CoolTerm and issue the command "011001" (in HEX mode). The CBT should return an English string with an error value - either '0' or 'B' most likely.

    4. If you get the 'B' code, the baud rate setting is wrong. In the VW that's because the low speed busses run at 100kbps but the CBT's default is 125kbps. Use the auto-detect code to set the correct baud rate by entering "010801". Once the serial terminal reports that the correct baud has been found, restart the CBT by cycling the power (disconnecting and reconnecting your USB cable).

    5. Try the "011001" command again. The 'B' error code should have disappeared.

  • 0
  • <b>Log & Process Data</b>

    Now that you've got the CBT connected, it's time to start logging data.

    1. Turn the car on - either engine running or engine off but all electronics on. In VWs the steering wheel controls do not work with just ACC power on the ignition.

    2. Enable logging on the CBT by issuing "030101". You'll start to see a torrent of data appear in your serial terminal. How much data depends on how chatty your car is over the CANBus - the VW is quite chatty.

    3. Setup CoolTerm to log the raw hex data to a text file.

    4. Start logging to the text file, then press one of the steering wheel buttons about 10 times in a regular tempo. For example, I press the button every second for 10 seconds.

    5. Stop logging and disconnect the CBT.

    6. Open your log file in a text editor with Find & Replace features. I use BBEdit on the Mac. It should look something like this:

    <code>06 5F 01 5A 5A 5A 31 4B 5A 37 08 01
    0D 03 51 00 00 00 00 00 00 00 00 08
    01 0D 03 59 60 01 00 00 00 2B 00 88
    08 01 0D 03 5B 00 00 00 5D 20 19 01
    00 08 01 0D 03 C3 00 00 00 00 80 A0
    00 5F 08 01 0D 04 39 0B 01 00 00 00
    00 00 00 06 01 0D 05 31 00 00 00 00
    00 00 00 00 04 01 0D 02 C1 00 00 00
    00 22 00 00 00 05 01 0D</code>

    1. CoolTerm puts in line breaks to tidy up the data but they're in the wrong places. The first step is to remove them altogether, which in BBEdit means finding the newline character '\r' and replacing it with a space ' '. This will give you:

    <code>03 01 06 5F 01 5A 5A 5A 31 4B 5A 37 08 01 0D 03 01 03 51 00 00 00 00 00 00 00 00 08 01 0D 03 01 03 59 60 01 00 00 00 2B 00 88 08 01 0D 03 01 03 5B 00 00 00 5D 20 19 01 00 08 01 0D 03 01 03 C3 00 00 00 00 80 A0 00 5F 08 01 0D 03 01 04 39 0B 01 00 00 00 00 00 00 06 01 0D 03 01 05 31 00 00 00 00 00 00 00 00 04 01 0D 03 01 02 C1 00 00 00 00 22 00 00 00 05 01 0D</code>

    1. You may notice a repeating '01 0D 03 01' in that sequence. the '03 01' is how the CBT starts a packet dump, and the '01 0D' is how CBT ends the dump. Those values are irrelevant to your data, so I clear them out and replace with a line break character. That is, find '01 0D 03 01' and replace with '\r'. This will give you the raw CAN data with one line per packet.

    <code>06 5F 01 5A 5A 5A 31 4B 5A 37 08
    03 51 00 00 00 00 00 00 00 00 08
    03 59 60 01 00 00 00 2B 00 88 08
    03 5B 00 00 00 5D 20 19 01 00 08
    03 C3 00 00 00 00 80 A0 00 5F 08
    04 39 0B 01 00 00 00 00 00 00 06
    05 31 00 00 00 00 00 00 00 00 04
    02 C1 00 00 00 00 22 00 00 00 05</code>

    1. From this data you can see the protocol emerge. The first 2 bytes are the identifier of the module on the CAN network, the next 7 are the data itself, and the final byte is the length of the packet (i.e. how much of the 7 data bytes are actual valid information).

    2. At this point we need to use something more powerful than a text editor to identify the data, so I open Excel and import the data into a spreadsheet. Use the 'fixed width' import option and you'll end up with 11 columns of data, and one row per packet.

    3. Add a header row at the top and label the rows to make it easier to sort:

    <img src="" />

    1. Now sort the data by the first 2 columns. This will give you a list of packets sorted by the controller that sent them out.

    2. Scroll down the list and you'll see that some controllers send hundreds of packets, and some controllers send only a few. You know that you only pushed the steering wheel button 10 times, so the controller you're after should have sent only a small number of packets.

    Don't just look for 10 packets exactly however - in the VW at least each button push can cause 1, 2 or 3 packets to be sent.

    Delete from your data any controllers that have sent more than around 30 packets. You should be left with a small set of controllers to check.

  • 0
  • <b>Identify Packets</b>

    Here we use a bit of trial-and-error to figure out what packets are being sent by our steering wheel.

    1. Re-connect the CBT to the serial terminal, start logging to a new file in the terminal program and activate the Bus logging on the CBT using '030101'. The torrent of packets should start again.

    2. Issue the command to filter the CBT packets by looking for a particular controller (Frame ID). Do this by looking at the values in column A & B of your spreadsheet, and issuing the filter command to the CBT with those values. For example, to filter on the first controller in the example data (06 5F) you would send the CBT the command "030101065F".

    3. When that command is processed by the CBT, you'll immediately notice the rate of data in your serial terminal drops away. This is a good thing as it means you're successfully filtering on just one controller.

    4. Whilst watching the data go past (watch the ASCII version of the data rather than the HEX version as it's easier to spot character patterns that way), press your steering wheel button several times. If the data doesn't appear to change, you're likely filtering on the wrong controller. Go back to Step 2, change the Controller (Frame ID) you're filtering against by picking another one from the Excel spreadsheet and try again.

    5. When you find the right controller, you'll notice that the data changes when you push the steering wheel buttons. At this point you can try some of the other buttons on the wheel - you should see the data change again but in a slightly different way than before.

    Note that the steering wheel controller in the VW is chatty even when no buttons are being pushed, so there will always be data flowing even if you're not pushing the buttons.

    1. Save your terminal HEX log, and repeat steps 6-8 from the previous post to get each packet into a readable format.

    2. From there the data patterns should be visible without further processing. For example, the VW 'Volume Up' button emerges as follows:

    <code>02 C1 00 00 00 00 22 00 00 00 05
    02 C1 00 00 00 00 22 00 00 00 05
    05 C1 06 00 00 00 00 00 00 00 01
    02 C1 00 00 00 00 22 00 00 00 05
    05 C1 06 00 00 00 00 00 00 00 01
    02 C1 00 00 00 00 22 00 00 00 05
    05 C1 00 00 00 00 00 00 00 00 01
    02 C1 00 00 00 00 22 00 00 00 05
    05 C1 00 00 00 00 00 00 00 00 01
    02 C1 00 00 00 00 22 00 00 00 05
    05 C1 00 00 00 00 00 00 00 00 01
    02 C1 00 00 00 00 22 00 00 00 05
    05 C1 00 00 00 00 00 00 00 00 01</code>

    The idle state of the wheel (05 C1) is to send out 1 byte packets with value 0x00. There are 2 packets that jump out in that log where the value is 0x06 instead. By repeating our test it becomes apparent that the full packet

    <code>05 C1 06 00 00 00 00 00 00 00 01</code>

    is what is sent out when the Volume Up button is pushed.

  • 0
  • <b>Intercept & Action</b>

    Now we want to actually do something with those button pushes, so we need to modify the CBT code to intercept and action those pushes.

    The best way to do this is to create your own Middleware and incorporate the code into there, however the quick and easy way is to find the <code>void processMessage( Message msg ){</code> function at the bottom of the CanBusTriple code file.

    1. This function is called every time a full packet arrives on the CANBus. We need to write some code to parse the received packet and match it against the values we identified in the last step. For example, the Volume Up button would be matched by:

    <code>if(msg.busId ==1 && msg.frame_id==0x5C1 && msg.frame_data[0]==0x06){</code>

    If you're only connected to 1 CANBus at the moment then the msg.busId part is redundant, but it comes in handy when you're grabbing data off multiple busses simultaneously and only want to take action to packets received on 1 particular bus.

    1. Once you've grabbed the packet and identified it's the one you want, you can perform whatever action you like at this point - starting with simply logging in plain text what button was pushed:

    <code>Serial.println("Volume Up");</code>

    1. You're done - you have now got your CBT to intercept a button push and perform an action in your custom code.

  • 0
  • i like this approach, i will try it later. thanks

  • 0
  • No problems at all - hopefully all that is of some help!

  • 0
  • In phase "Connect CBT Hardware", I am stuck at step 3. wiring the low speed can bus. I am using this diagram which indicates my low speed can is on OBD wire 3 and 11:
    In my latest attempt, I rewired the OBD cable and I first only connected the red wire to OBD pin 3 and set the baud rate to 250kbps in the source code. No data. Next, I connected the green wire to OBD pin 11 as well; now I have the Airbag warning and 2 fuses blown again :-(
    The things I tried before this are described in another thread:
    I really wish I knew how to do the wiring so I can log low speed can data....

  • 0
  • administrators

    Thanks James!

    Once I get the Beta release of the app done this will be quite a bit easier.

  • 0
  • @Derek‌ -no worries!

    @henk_kuipers‌ I don't know a thing about Volvos but CAN is a low voltage data line and shouldn't be fused anywhere. If connecting the two pins to your CBT blows a fuse I'd check your wiring to see exactly what those 2 pins do.

    I'd pull the OBD connector from the car and trace the physical wires to confirm they are what you expect them to be. I've found discrepancies between the VW wiring manuals and the reality of the cars, so check that's not the issue in your case alsoz.

  • 0
  • @henk_kuipers - woa blowing fuses... scary. Yeah this definitly should not be happening. Here are a couple of things to check:

    1. If you rewired the OBD cable that came with your CBT. Be careful as the wires do not seem to tin (take on solder) very well. you may have had a connection come lose that is now shorting things out. You might want to open it up and give it a visual inspection.
    2. Again if you are rewiring the cable that came with the CBT. Be careful that you have not rotated the PCB inside the plastic shroud that mates with the cars OBD port. If this some how got rotated... Pin 16 on the PCB will now be mating with the Pin 1 of your vehicle... you can check you have it the right way by looking at the shape of the shroud in this picture and making sure your pin 1 is in the right location. <a href="">OBD-II Pinout </a>
    3. If you have a simple DMM you should do some voltage and short circuit tests.
    4. Make sure you get the pin numbers right going into the CBT. That two row male header can be confusing. See the below pinout with more pin numbers shown.
      <img src="" />

  • 0
  • Disconnect the battery and measure between the two can lines. Since they're terminated with two 120 resistors parallel, you should measure 60 ohms. Remember to do resistance measurements only with the battery disconnected!

    This is a handy page to check some things on a CANbus.

  • 0
  • Thanks for all the helpful ideas guys! Hopefully, my next attempt is more succesful now :-) I am getting better and better at soldering wires, that's for sure :))

  • 0
  • My latest attempt is in another thread because it looks like KidTurbo's problem:
    <a href=""></a>

  • 0
  • Wiring trouble is over so I am trying to make sense of the low speed CAN data. I continued to follow the steps above (very useful description, thanks!). I gathered data in different settings that make sense to compare but now I need to set message filters to do some trial and error. However, when issuing the logging with filtering commands I receive 1 message (in my case JSON output) and then logging stops. A need to power cycle the CBT to get logging going again and I don't succeed in setting any message filters at all. I use CoolTerm and send HEX commands like:
    03010100D800C8 - log Bus 1 and filter message ID 00D8 and 00C8
    03010100400058 - log Bus 1 and filter message ID 0040 and 0058
    The ID's are valid ID's from my logfiles. Is my syntax wrong?

  • 0
  • <b>Am I sending correct CAN packets?</b>
    I am making some modest progress. Because I couldn't get the serial filter command to work I have changed the JSON output code in SerialCommand.h so it shows Message ID and payload of only one particular message ID. A very basic filter. By trial and error, I found the messages of events that occur when for instance the mirrors are folded, the doors are locked, the windows go up/down and the fan (up/down). Finally some meaning in all that data :-)
    On to the next challenge, send some CAN packets to make something happen. No successful attempts so far. Hopefully someone can see what is going wrong. For example, when I fold the mirrors I see these messages:
    "id":"50","payload":["0","5B","40","5","D9","3","4","0"] - folding
    "id":"50","payload":["0","5B","40","5","89","3","4","0"] - finished
    "id":"50","payload":["0","5B","40","5","E9","3","4","0"] - unfolding
    "id":"50","payload":["0","5B","40","5","89","3","4","0"] - finished
    Logging in HEX looks like this:
    00 50 00 5B 40 05 89 03 04 00 08
    00 50 00 5B 40 05 D9 03 04 00 08
    00 50 00 5B 40 05 E9 03 04 00 08
    So, I was hoping I could trigger the 'folding/unfolding mirrors " action by sending the following packets:
    I thought:
    0201 = send on bus 1, 0050 = message id, 005B4005D9030400=payload, 08 = length
    But nothing happens. I tried the windows and door lock commands too but no luck. I think I have to recycle the CBT after each failure to try again, so that's what I do...
    <b>Another question:</b> in CoolTerm you can set the Transmit option "Terminate send string data" with 0D 0A, should this setting be active? I tried it with and without but it didn't make a difference.

  • 0
  • <b>Two things made it easier for me to identify messages:</b>

    1. Change the code so you get JSON output (remove comment "//" before the #define JSON_OUT in SerialCommand.h as mentioned serveral places on the forum).
    2. I altered the JSON output code a bit so I could filter one single message ID , this way I could see if messages change when pressing buttons. It is not a big deal but it helped me a lot in finding out what is going on, this is what I changed:

    Original code:

    #ifdef JSON_OUT
    Output to serial as json string
    activeSerial->print(F("{"packet": {"status":""));
    activeSerial->print( msg.busStatus,HEX);
    activeSerial->print( busses[msg.busId-1].name );

    if (msg.frame_id==0x0078)
    for (int i=0; i<8; i++) {
    if( i<7 ) activeSerial->print(F("",""));

    Changed code that replaces the above:

    #ifdef JSON_OUT

    if (msg.frame_id==0x0078) // <b>->change this message ID figure out if it changes when you press buttons in the car</b>
    for (int i=0; i<8; i++) {
    if( i<7 ) activeSerial->print(F("",""));
    Disadvantage of this method is that you need to compile and upload the code for each message ID you want to test. It's a drag but it works.

  • 0
  • If I have specific message id's how can I look just that data up?


    AEMnet Revision 2013-05-29
    CAN 2.0
    29 bit format unless otherwise specified
    500 kBit/sec unless otherwise specified
    8 data bytes/message
    All multi-byte data packed big endian unless specified (most significant byte transmitted first)
    All bits numbered with the LSB = bit0, MSB = bit7

    Message ID: 0x01F0A000
    Source: AEM V2 EMS/Infinity (P/N 30-6XXX/30-71XX)
    Target: Any
    Rate: 16.6ms continuous
    Byte Label Data Type Scaling Offset Range
    0 Engine Speed 16 bit unsigned 0.39063 rpm/bit 0 0 to 25,599.94
    2 Engine Load* 16 bit unsigned 0.00261230481157781 5.48000026028603 0 to 99.998 %
    4 Throttle 16 bit unsigned 0.0015259 %/bit 0 0 to 99.998 %
    6 Air Temp 8 bit signed, 2's complement 1 Deg C/bit 0 -128 to 127 C
    7 Coolant Temp 8 bit signed, 2's complement 1 Deg C/bit 0 -128 to 127 C

    Log Capture 2015-04-04 15-04-13.txt

  • 0
  • Hi TheBoz, have you tried to change the code of SerialCommand.h the way I did? Have you downloaded the source from Github and done a compile & upload? Your logging looks like HEX data saved in ASCII format which is not readable, it's easier to save it in HEX format when you are logging HEX data. When you use JSON output it is better to save it in RAW format.
    Mind you, when Derek has finished the app filtering messages will be MUCH easier ;-)

  • 26
  • 9436
  • Log in to reply