A Calustra - Eloy Coto Pereiro Home About Books

Reverse engineering an RS232 device

on

For a couple of years, one project I have wanted to pursue is building a Lap Counter TV application for our local Slot Racing Club.

At the club, we use a lap counter technology that is 20 years old, featuring an RS232 interface from which I can retrieve data. I have experimented with a few setups, and at a baud rate of 4800, I began receiving promising hexadecimal values. Now, I need to decipher them

My journey started with the following data:

e0 09 15 02 0b 00 00 3e a1 00 06 00 00 00 00 00 00 00 10 00 eb e0 09 15 02 0b 00 00 3e a1 00 06 00 00 00 00 00 00 00 10 00 eb e0 09 15 02 0b 00 00 3e a1 00 06 00 00 00 00 00 00 00 10 00 eb e0 0a 15 02 0b 00 00 00 a2 00 00 00 00 00 00 00 00 00 ce 00 eb e0 0a 15 02 0b 00 00 00 a2 00 00 00 00 00 00 00 00 00 ce 00 eb e0 0a 15 02 0b 00 00 00 a2 00 00 00 00 00 00 00 00 00 ce 00 eb e0 0b 15 02 0b 00 00 00 a3 00 00 00 00 00 00 00 00 00 d0 00 eb e0 0b 15 02 0b 00 00 00 a3 00 00 00 00 00 00 00 00 00 d0 00 eb e0 0b 15 02 0b 00 00 00 a3 00 00 00 00 00 00 00 00 00 d0 00 eb e0 0c 15 02 0b 00 00 1b a9 00 00 00 00 00 aa aa aa aa 9a 00 eb e0 0c 15 02 0b 00 00 1b a9 00 00 00 00 00 aa aa aa aa 9a 00 eb e0 0c 15 02 0b 00 00 1b a9 00 00 00 00 00 aa aa aa aa 9a 00 eb e0 0d 15 02 0b 00 00 1b a9 a8 00 00 00 01 00 02 52 90 80 00 eb e0 0d 15 02 0b 00 00 1b a9 a8 00 00 00 01 00 02 52 90 80 00 eb e0 0d 15 02 0b 00 00 1b a9 a8 00 00 00 01 00 02 52 90 80 00 eb e0 0e 15 02 0b 00 00 1b a9 a8 00 00 00 02 00 01 28 60 27 00 eb e0 0e 15 02 0b 00 00 1b a9 a8 00 00 00 02 00 01 28 60 27 00 eb e0 0e 15 02 0b 00 00 1b a9 a8 00 00 00 02 00 01 28 60 27 00 eb e0 0f 15 02 0b 00 00 1b a9 00 00 00 00 03 00 02 13 40 4d 00 eb e0 0f 15 02 0b 00 00 1b a9 00 00 00 00 03 00 02 13 40 4d 00 eb e0 0f 15 02 0b 00 00 1b a9 00 00 00 00 03 00 02 13 40 4d 00 eb e0 10 15 02 0b 00 00 1b a9 a8 00 00 00 04 00 01 23 80 46 00 eb e0 10 15 02 0b 00 00 1b a9 a8 00 00 00 04 00 01 23 80 46 00 eb e0 10 15 02 0b 00 00 1b a9 a8 00 00 00 04 00 01 23 80 46 00 eb e0 11 15 02 0b 00 00 1b a9 00 00 00 00 05 00 02 71 90 ff 00 eb e0 11 15 02 0b 00 00 1b a9 00 00 00 00 05 00 02 71 90 ff 00 eb e0 11 15 02 0b 00 00 1b a9 00 00 00 00 05 00 02 71 90 ff 00 eb e0 12 15 02 0b 00 00 1c a9 00 00 00 00 06 00 11 63 50 c3 00 eb e0 12 15 02 0b 00 00 1c a9 00 00 00 00 06 00 11 63 50 c3 00 eb e0 12 15 02 0b 00 00 1c a9 00 00 00 00 06 00 11 63 50 c3 00 eb e0 13 15 02 0b 00 00 1b a9 00 00 00 00 06 00 01 72 70 e2 00 eb e0 13 15 02 0b 00 00 1b a9 00 00 00 00 06 00 01 72 70 e2 00 eb e0 13 15 02 0b 00 00 1b a9 00 00 00 00 06 00 01 72 70 e2 00 eb e0 14 15 02 0b 00 00 00 a4 00 00 00 00 00 00 00 00 00 da 00 eb e0 14 15 02 0b 00 00 00 a4 00 00 00 00 00 00 00 00 00 da 00 eb e0 14 15 02 0b 00 00 00 a4 00 00 00 00 00 00 00 00 00 da 00 eb

When you encounter a situation like this, you need to start identifying the pattern. The beginning of the pattern seems to be clear, in this case 'e0', and the ending byte should be something before 'e0', which appears to be 'eb' in this instance.

Great, we have identified a pattern of 21 bytes! Now, every time a lap is counted, the serial port sends some information. Let's delve into it by creating a table that includes the byte position, hexadecimal value, and decimal values!

Example for lap2:

|    0 |    1 |    2 |    3 |    4 |    5 |    6 |    7 |    8 |    9 |   10 |   11 |   12 |   13 |   14 |   15 |   16 |   17 |   18 |   19 |   20
| 0xe0 | 0x1a | 0x15 | 0x02 | 0x0b | 0x00 | 0x00 | 0x1b | 0xa9 | 0x00 | 0x00 | 0x00 | 0x00 | 0x02 | 0x00 | 0x06 | 0x23 | 0x00 | 0x2b | 0x00 | 0xeb
|  224 |   26 |   21 |    2 |   11 |    0 |    0 |   27 |  169 |    0 |    0 |    0 |    0 |    2 |    0 |    6 |   35 |    0 |   43 |    0 |  235

Example for lap 3:

|    0 |    1 |    2 |    3 |    4 |    5 |    6 |    7 |    8 |    9 |   10 |   11 |   12 |   13 |   14 |   15 |   16 |   17 |   18 |   19 |   20
| 0xe0 | 0x1b | 0x15 | 0x02 | 0x0b | 0x00 | 0x00 | 0x1b | 0xa9 | 0x00 | 0x00 | 0x00 | 0x00 | 0x03 | 0x00 | 0x06 | 0x70 | 0x70 | 0xea | 0x00 | 0xeb
|  224 |   27 |   21 |    2 |   11 |    0 |    0 |   27 |  169 |    0 |    0 |    0 |    0 |    3 |    0 |    6 |  112 |  112 |  234 |    0 |  235

And in the final lap, I got the following:

|    0 |    1 |    2 |    3 |    4 |    5 |    6 |    7 |    8 |    9 |   10 |   11 |   12 |   13 |   14 |   15 |   16 |   17 |   18 |   19 |   20
| 0xe0 | 0x1f | 0x15 | 0x02 | 0x0b | 0x00 | 0x00 | 0x1b | 0xa9 | 0xa8 | 0x00 | 0x00 | 0x00 | 0x06 | 0x00 | 0x02 | 0x81 | 0x00 | 0x36 | 0x00 | 0xeb
|  224 |   31 |   21 |    2 |   11 |    0 |    0 |   27 |  169 |  168 |    0 |    0 |    0 |    6 |    0 |    2 |  129 |    0 |   54 |    0 |  235
...
|    0 |    1 |    2 |    3 |    4 |    5 |    6 |    7 |    8 |    9 |   10 |   11 |   12 |   13 |   14 |   15 |   16 |   17 |   18 |   19 |   20
| 0xe0 | 0x20 | 0x15 | 0x02 | 0x0b | 0x00 | 0x00 | 0x1c | 0xa9 | 0x00 | 0x00 | 0x00 | 0x00 | 0x06 | 0x00 | 0x80 | 0x17 | 0x70 | 0x14 | 0x00 | 0xeb
|  224 |   32 |   21 |    2 |   11 |    0 |    0 |   28 |  169 |    0 |    0 |    0 |    0 |    6 |    0 |  128 |   23 |  112 |   20 |    0 |  235

The good thing about this lap counter is that it has a display, so I can see the information I need. In this case, the information should be:

Looking at these patterns, we can identify key elements:

Ok, let's dig into the numbers. Clearly, it's not reporting the current time in decimal; it's using a different notation. The lap counter's display is an 8-digit display, and we have 4 bytes of information. Maybe it's using 4 bits for each digit?

Let's analyze byte number 15; there's some interesting information there. For byte 15, if the number is smaller than 10, it appears to be in decimal. However, when the value is greater, it does not match the direct decimal interpretation. For example, for the final time, when the value is 0x80 or 128 in decimal, the display value was 80 seconds.

Electronics normally works in 0 and 1, let's put the value in binary:

HEX: 0x80, decial: 128, binary: 10000000

We need an 8, and we found it, we can split the binary in 2 parts:

We found how the data is sent, let's parse it:

def parse(x):
    return "{}{}".format(x>>4, x&0xf)

def parse_lap_time(data):
    if len(data) != 21:
        raise ValueError("Invalid data length. Expected 21 bytes.")

    data_type = data[7]
    lap = data[13]
    secondsa = data[14]
    secondsb = data[15]
    millisecondsa = data[16]
    millisecondsb = data[17]

    time = "LAP:{} Seconds:'{}{}' mili: '{}{}'".format(
            parse(lap),
            parse(secondsa),
            parse(secondsb),
            parse(millisecondsa),
            parse(millisecondsb))

    if data_type == 0x1B:
        print("Is lap time", time)
    elif data_type == 0x1C:
        print("Is final time", time)

This parser can handle the lap data and distinguish between lap times and the final time. From here, we can develop further applications to process and display this data. It's been exciting to dive back into this type of reverse engineering!

Tags

Related articles: