Help me reverse-engineer some bitstrings
January 19, 2012 12:56 AM   Subscribe

Hackers of MeFi, assemble! I have an undocumented serial protocol I want to reverse engineer - help me turn some mysterious bit strings into meaningful data.

I recently bought a V-Fit 10kpt treadmill for use in a treadmill desk arrangement, which is working out well. Like most treadmills it has a fairly rubbish interface on a control unit; the control unit talks to the treadmill itself over a single-wire serial protocol, which I've reverse-engineered enough to replace the control unit with my own microprocessor + desktop application.

So far so good, but my solution involves capturing the serial traffic at a given speed as a bit sequence and then playing that bit sequence back to set that speed on the treadmill. I'm like the man in the chinese room experiment.

This is where you, the good people of ask metafilter, come in; can you help me turn these meaningless bits into comprehensible data?

At a given speed the device plays out a 120 bit sequence at 1200 baud, repeated every 7.2 milliseconds; here is an example bit sequence (this is a recording of the logic levels on the wire with no further processing). Each of these 120 bit sequences is a speed command.
bin: 00001011 00110000 10000011 00000010 00110000 01010111 00000000 01110100 00111111 00000000 01110001 01000111 01011011 00110110 00010011
hex: 0x0b 0x30 0x83 0x02 0x30 0x57 0x00 0x74 0x3f 0x00 0x71 0x47 0x5b 0x36 0x13
I've broken this into bytes as its length is divisible by 8, and I can't see any obvious pattern of start and stop bits like you'd get in normal RS232.

It turns out that every command starts with 0x0b 0x30 0x83 0x02 0x30, and ends with 0x13, so I'll leave those off from now on; from there we have one special command, played when the device is stopped:
stop (bin): 00000111 00000000 01110000 00000111 00000000 01110001 01000111 00011110 01110110 
stop (hex): 0x7 0x0 0x70 0x7 0x0 0x71 0x47 0x1e 0x76
and then at least 96 speed commands, going from 0.5kph to 10kph in 0.1kph increments. Every speed command is prefixed with 0x57, presumably identifying it as a speed, followed by 3 variable bytes, followed by 0x00 0x71 0x47, followed by two variable bytes whose final nibble is always 6. Here are some examples (leading 0x57 omitted):
0.5kph 00000000 01110100 00111111 00000000 01110001 01000111 01011011 00110110
        0x0 0x74 0x3f 0x0 0x71 0x47 0x5b 0x36
0.6kph 01000000 00110011 10000011 00000000 01110001 01000111 01101100 10110110
        0x40 0x33 0x83 0x0 0x71 0x47 0x6c 0xb6
0.7kph 01000000 00110110 11100011 00000000 01110001 01000111 00001001 10110110
        0x40 0x36 0xe3 0x0 0x71 0x47 0x9 0xb6
0.8kph 01000000 00110000 10110011 00000000 01110001 01000111 01010111 11110110
        0x40 0x30 0xb3 0x0 0x71 0x47 0x57 0xf6
0.9kph 01000000 00110101 01001111 00000000 01110001 01000111 00100010 01110110
        0x40 0x35 0x4f 0x0 0x71 0x47 0x22 0x76
1.0kph 01000000 00110010 00011011 00000000 01110001 01000111 01111001 00110110
        0x40 0x32 0x1b 0x0 0x71 0x47 0x79 0x36
1.1kph 01000000 00110111 10111011 00000000 01110001 01000111 00011111 00110110
        0x40 0x37 0xbb 0x0 0x71 0x47 0x1f 0x36
1.2kph 00100000 00110001 11000011 00000000 01110001 01000111 00101010 11110110
        0x20 0x31 0xc3 0x0 0x71 0x47 0x2a 0xf6
...

9.9kph 01000100 01110110 10110011 00000000 01110001 01000111 00001000 00110110
        0x44 0x76 0xb3 0x0 0x71 0x47 0x8 0x36
10kph 01000100 01110000 11001011 00000000 01110001 01000111 01010110 01110110
        0x44 0x70 0xcb 0x0 0x71 0x47 0x56 0x76
My guesses so far on speed command format are:
  • First 3 bytes could be a 24-bit floating point; they are not lexically ordered against speed which fits this possibility
  • Final two bytes could be a checksum of some sort; the device has lots of ambient EMF because of the motor, and it would be bad if a few bit errors could stop the belt or run it at 10kph suddenly
  • Middle bytes are just a separator, maybe again to provide some bit error / timing error stability
One other point of interest: the devices speaking this protocol are AVR ATMega8 microprocessors, so any common data they use could be involved (I don't get why the engineers here didn't just use the built-in standard 8N1 serial?!?!). The micros' flash memories have their lock bits set, so I can't just download their programs and reverse-engineer directly (first thing I tried).

Over to you, metafilter; thanks!
posted by larkery to Technology (13 answers total) 9 users marked this as a favorite
 
Are there any other settings for the treadmill - e.g. random programs, incline, etc. Could the middle unvarying digits be other possibilities that you're not checking for currently?
posted by BigCalm at 3:13 AM on January 19, 2012


Can you post the complete list of speed commands to pastebin or similar? Unlabeled raw data is fine.
posted by anon_for_this at 3:49 AM on January 19, 2012


bit 0: low
bit 1 - 10: data + parity
bit 11 - 13: high
posted by anon_for_this at 4:52 AM on January 19, 2012


Does it track anything else like distance run?
posted by empath at 5:51 AM on January 19, 2012




Best answer: Based on the provided data and the protocol I posted above, this is the data format:

byte 4: upper speed component
byte 5: middle speed component
byte 8: lower speed component

Here is the byte stream decoded per the protocol I posted earlier. The serial line is pulled down for one clock cycle, 8 bytes are sent in little endian order, a parity bit is sent, then the line is release for two clock cycles:
                                                                     byte 4         byte 5                                       byte 8
                                                                     xxxxxxxx-      xxxxxxxx-                                    xxxxxxxx-
0.5kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 000000001 11 0 100001111 11 0 000000001 11 0 001010001 11 0 101101100 11 0 110000100 11
0.6kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100000000 11 0 011100000 11 0 000000001 11 0 001010001 11 0 110110010 11 0 110000100 11
0.7kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100000000 11 0 110111000 11 0 000000001 11 0 001010001 11 0 000100110 11 0 110000100 11
0.8kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100000000 11 0 000101100 11 0 000000001 11 0 001010001 11 0 101011111 11 0 110000100 11
0.9kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100000000 11 0 101010011 11 0 000000001 11 0 001010001 11 0 010001001 11 0 110000100 11
1.0kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100000000 11 0 010000110 11 0 000000001 11 0 001010001 11 0 111100100 11 0 110000100 11
1.1kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100000000 11 0 111101110 11 0 000000001 11 0 001010001 11 0 001111100 11 0 110000100 11
1.2kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 010000000 11 0 001110000 11 0 000000001 11 0 001010001 11 0 010101011 11 0 110000100 11

9.9kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100010001 11 0 110101100 11 0 000000001 11 0 001010001 11 0 000100000 11 0 110000100 11
10.kph 0 000101100 11 0 000100000 11 0 000001000 11 0 000010101 11 0 100010001 11 0 000110010 11 0 000000001 11 0 001010001 11 0 101011001 11 0 110000100 11
When you combine the upper speed component (byte 4) and middle speed component (byte 5) you get an increasing number. byte 7 is presumably just a minor component added to that number. Here is byte 4 and 5 shown in hex:
byte 4:     byte 5:          
00000000    10000111  0x00E1
10000000    01110000  0x010E
10000000    11011100  0x013B
10000000    00010110  0x0168
10000000    10101001  0x0195
10000000    01000011  0x01C2
10000000    11110111  0x01EF
01000000    00111000  0x021C

10001000    11010110  0x116B
10001000    00011001  0x1198
The numbers are monotonically increasing by a factor of 0x2D, so you can control the treadmill by specifying different numbers in byte positions 4 and 5. The desired speed formula is:

(desired kph - 0.5) * 450 + 225

For example 20kph would be about 0x2328.
posted by anon_for_this at 6:43 AM on January 19, 2012 [3 favorites]


I meant to say 8 *bits* are sent in little endian order. And the minor speed component is byte 8, not 7.
posted by anon_for_this at 6:45 AM on January 19, 2012


Response by poster: Excellent stuff, thanks very much anon_for_this; sorry for the delay / lack of feedback, I posted before going to work, UK time.

I will get to implementing this later on, it's always nice to replace a large table of constants with a small function. Will find out what byte 8 does and get back to you as well.
posted by larkery at 8:48 AM on January 19, 2012


Response by poster: OK, one implementation implemented - the method works, but byte 3 has to have the correct value to work, so it's not just some fine control byte but is another check byte of some kind.

If anyone fancies a look, here are the triplets of values; what is connecting 'em?
0x0 0x0 0x3c
-------------
0x0 0xe1 0x6d
0x1 0xe 0x9b
0x1 0x3b 0xc8
0x1 0x68 0xf5
0x1 0x95 0x22
0x1 0xc2 0x4f
0x1 0xef 0x7c
0x2 0x1c 0xaa
0x2 0x49 0xd7
0x2 0x76 0x4
0x2 0xa3 0x31
0x2 0xd0 0x5e
0x2 0xfd 0x8b
0x3 0x2a 0xb9
0x3 0x57 0xe6
0x3 0x84 0x13
0x3 0xb1 0x40
0x3 0xdf 0x6e
0x4 0xc 0x9c
0x4 0x39 0xc9
0x4 0x66 0xf6
0x4 0x93 0x23
0x4 0xc0 0x50
0x4 0xed 0x7d
0x5 0x1a 0xab
0x5 0x47 0xd8
0x5 0x74 0x5
0x5 0xa1 0x32
0x5 0xce 0x5f
0x5 0xfb 0x8c
0x6 0x28 0xba
0x6 0x55 0xe7
0x6 0x82 0x14
0x6 0xaf 0x41
0x6 0xdc 0x6e
0x7 0x9 0x9c
0x7 0x36 0xc9
0x7 0x63 0xf6
0x7 0x90 0x23
0x7 0xbe 0x51
0x7 0xeb 0x7e
0x8 0x18 0xac
0x8 0x45 0xd9
0x8 0x72 0x6
0x8 0x9f 0x33
0x8 0xcc 0x60
0x8 0xf9 0x8d
0x9 0x26 0xbb
0x9 0x53 0xe8
0x9 0x80 0x15
0x9 0xad 0x42
0x9 0xda 0x6f
0xa 0x7 0x9d
0xa 0x34 0xca
0xa 0x61 0xf7
0xa 0x8e 0x24
0xa 0xbb 0x51
0xa 0xe8 0x7e
0xb 0x15 0xac
0xb 0x42 0xd9
0xb 0x6f 0x6
0xb 0x9d 0x34
0xb 0xca 0x61
0xb 0xf7 0x8e
0xc 0x24 0xbc
0xc 0x51 0xe9
0xc 0x7e 0x16
0xc 0xab 0x43
0xc 0xd8 0x70
0xd 0x5 0x9e
0xd 0x32 0xcb
0xd 0x5f 0xf8
0xd 0x8c 0x25
0xd 0xb9 0x52
0xd 0xe6 0x7f
0xe 0x13 0xad
0xe 0x40 0xda
0xe 0x6d 0x7
0xe 0x9a 0x34
0xe 0xc7 0x61
0xe 0xf4 0x8e
0xf 0x21 0xbc
0xf 0x4e 0xe9
0xf 0x7c 0x17
0xf 0xa9 0x44
0xf 0xd6 0x71
0x10 0x3 0x9f
0x10 0x30 0xcc
0x10 0x5d 0xf9
0x10 0x8a 0x26
0x10 0xb7 0x53
0x10 0xe4 0x80
0x11 0x11 0xae
0x11 0x3e 0xdb
0x11 0x6b 0x8
0x11 0x98 0x35

posted by larkery at 10:37 AM on January 19, 2012


So it's async serial 8-odd-2. That's an odd setting but many UARTs should handle it. The ATmega8 USART can, at any rate.

1/450 km/hour is a weird unit; maybe it's just an internal encoder count? It's kind of close to being miles/hour in a fixed-point-binary representation (mph + mph/256), but not quite.

Just guessing, but does byte 8 make the mod-256 sum of the other bytes (including 6 and 7) come out to 0?
posted by hattifattener at 10:46 AM on January 19, 2012


Best answer: (Er, if you negate it, I guess. At any rate, in those triples, A+B-C=116 mod 256.)
posted by hattifattener at 10:55 AM on January 19, 2012


Response by poster: That's the one; just figured that myself. Cheers everyone - question resolved entirely.

I expect the units are just something like PWM mark/space ratio; the motor controller is probably PWM, but I haven't investigated further than trying to download the program off it as it has big fat cables which exceed my RDA of volts & amps.
posted by larkery at 11:51 AM on January 19, 2012


Response by poster: For the record, the check byte is just the sum of bits 2-8 of the message sent (mod 255), which is typically 0x08 + 0x20 + (control = 0x50 or 0x0) + speed 1 + speed 2 + 0x0 + 0x14.
posted by larkery at 1:25 AM on January 20, 2012


« Older Help me find more information about Orange Bowl...   |   Eliminating my dream job based on vision concerns? Newer »
This thread is closed to new comments.