MH Studio

Development and tinkering

Recent Posts

  • Headlight HID Retrofit
  • Solar Monitoring With Python
  • Grid-Assisted Solar, pt. 2
  • Grid-Assisted Solar, pt. 1
  • Reverse-engineering TPMS sensors

Archives

  • July 2017
  • June 2017
  • May 2017
  • April 2017
  • March 2017
  • February 2017
  • November 2015

Categories

  • gadgets
  • house
  • programming
  • tinkering
  • vehicles

Powered by Genesis

Reverse-engineering TPMS sensors

2017-03-03 by Mitchel 2 Comments

I like to challenge myself regularly in order to keep my mind active and open to new information. One of my long-running ideas is to create an “infotainment” system for my car that is capable of displaying and logging vehicle sensors in addition to the standard media-playing functionality. Many of the vehicle sensors can be accessed through the OBD2 interface, but my car doesn’t have a factory tire-pressure monitoring system. This means I need to get a bit more creative to acquire that data. Enter my attempt to reverse-engineer some standard TPMS sensors.

Reverse-engineering tire-pressure monitoring system (TPMS) sensors takes a combination of research, educated guessing, and experimentation. Wikipedia has a good article for background information on TPMS: https://en.wikipedia.org/wiki/Tire-pressure_monitoring_system

For more information on what hardware is needed and what software to use, see: https://github.com/jboone/tpms
This is also the software which I will be using for this post.

Overview of the steps:

  • Determine if your vehicle has direct or indirect TPMS
  • Assuming direct TPMS, determine the sensor radio frequency band (likely 315MHz or 433MHz)
  • Get a software radio receiver that can capture complex radio spectrum data at your target frequency
  • Capture data while driving, ideally in an area with few other vehicles to minimize interference
  • Examine capture files, looking for transmissions that are clear and strong

The following steps will require repeated experimentation:

  • Pick a clear capture file to determine the modulation and approximate bandwidth
  • Determine symbol rate by measuring time between bits
  • Determine preamble (this is one of the more difficult steps to get right)
  • Use these parameters to try to decode more data for more sample points

This process will only work with direct TPMS. Indirect TPMS doesn’t have sensors in the tires, so there is no information transmitted and nothing to capture using this method.
There are many options for software-defined radios. The most commonly available ones are based on the Realtek R820T2 chipset.

Examining capture files:

Finding the center frequency
Frequency shifted to center, highlighting peak frequencies
Deviation too small
Deviation too large
Symbol rate too low
Symbol rate too high
Ideal deviation and symbol rate

Once you have identified the radio encoding parameters that your sensors use, you can continue to work at determining the packet structure. This requires a fair amount of guesswork, but can be made easier by collecting data under controlled conditions. For example, identifying the pressure field can be done by capturing data while changing the tire pressure. Generally, TPMS sensors will go to sleep when the vehicle is parked, but rapidly changing pressure should put them into an alarm state. This can also help you determine how the sensor values correspond to actual tire pressure. By removing and re-adding air from the tire in recorded steps (and pausing in between), you should see a clear trend in the sensor data. Note that changing the pressure will also affect the temperature slightly.

Here is an example of the graphed data when varying pressure in a single tire.

Pressure sensor value vs. time
Temperature sensor value vs. time

When examining all your captured and decoded data, the sensor ID field will have 4-5 distinct values depending on how many sensors your vehicle has. Pressure and temperature will both vary while driving. There may also be a few status fields and a CRC/checksum. It is likely that fields will be 8 bits wide, so that’s a good guess to start.

I only have 3 functioning sensors, but here are some examples of the interesting fields that I was able to decipher:
bits[00,32]: sensor ID
bits[32,40]: status flags?
bits[40,48]: pressure (psi * 2.72 or so)
bits[48,56]: temperature (units not yet determined)
bits[56,64]: more status flags?
bits[64,72]: checksum (initial value: 0x00, polynomial: 0x01, XOR value: 0x00)

I’ve been able to identify all the information that I consider to be important (sensor ID, pressure, and temperature), but there is still experimentation to be done for the other values. So far, this process has taken me a few weeks of poking and prodding. If you decide to take on a similar challenge, I wish you the best of luck!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
(py-tpms) mitchel@ubuntu:~/Development/py-tpms$ packet_stats.py --rangestats 00,32 < all_decoded.txt
Range 0:32
ca19265a   3390645850 11001010000110010010011001011010: 124 ****************************************************************************************************************************
ca19266c   3390645868 11001010000110010010011001101100:  98 **************************************************************************************************
ca19269b   3390645915 11001010000110010010011010011011: 124 ****************************************************************************************************************************
 
(py-tpms) mitchel@ubuntu:~/Development/py-tpms$ packet_stats.py --rangestats 32,40 < all_decoded.txt
Range 32:40
        1            1 00000001:  64 ****************************************************************
        2            2 00000010:  62 **************************************************************
        3            3 00000011:  78 ******************************************************************************
       c1          193 11000001:  45 *********************************************
       c2          194 11000010:  50 **************************************************
       c3          195 11000011:  47 ***********************************************
 
(py-tpms) mitchel@ubuntu:~/Development/py-tpms$ packet_stats.py --rangestats 40,48 < all_decoded.txt
Range 40:48
       55           85 01010101:   1 *
       56           86 01010110:   7 *******
       57           87 01010111:   6 ******
       58           88 01011000:  15 ***************
       59           89 01011001:   8 ********
       5a           90 01011010:   9 *********
       5b           91 01011011:   8 ********
       5c           92 01011100:  10 **********
       5d           93 01011101:  15 ***************
       5e           94 01011110:  18 ******************
       5f           95 01011111:   8 ********
       60           96 01100000:  13 *************
       61           97 01100001:   6 ******
       66          102 01100110:   5 *****
       67          103 01100111:   3 ***
       68          104 01101000:   5 *****
       69          105 01101001:   7 *******
       6a          106 01101010:   9 *********
       6b          107 01101011:  11 ***********
       6c          108 01101100:   9 *********
       6d          109 01101101:   5 *****
       6e          110 01101110:  19 *******************
       6f          111 01101111:  18 ******************
       70          112 01110000:   8 ********
       71          113 01110001:   8 ********
       72          114 01110010:  23 ***********************
       73          115 01110011:   7 *******
       74          116 01110100:  12 ************
       75          117 01110101:   6 ******
       76          118 01110110:  10 **********
       77          119 01110111:  11 ***********
       78          120 01111000:  11 ***********
       79          121 01111001:   7 *******
       7a          122 01111010:   9 *********
       7b          123 01111011:   2 **
       7c          124 01111100:   1 *
       7d          125 01111101:   2 **
       7e          126 01111110:   7 *******
       7f          127 01111111:   7 *******
 
(py-tpms) mitchel@ubuntu:~/Development/py-tpms$ packet_stats.py --rangestats 48,56 < all_decoded.txt
Range 48:56
       2c           44 00101100:   3 ***
       2d           45 00101101:  16 ****************
       2e           46 00101110:  17 *****************
       2f           47 00101111:  11 ***********
       30           48 00110000:  12 ************
       31           49 00110001:   9 *********
       32           50 00110010:  17 *****************
       33           51 00110011:   7 *******
       34           52 00110100:  12 ************
       35           53 00110101:  14 **************
       36           54 00110110:  21 *********************
       37           55 00110111:  18 ******************
       38           56 00111000:  11 ***********
       39           57 00111001:  12 ************
       3a           58 00111010:  15 ***************
       3b           59 00111011:   6 ******
       3c           60 00111100:  16 ****************
       3d           61 00111101:  12 ************
       3e           62 00111110:  19 *******************
       3f           63 00111111:   5 *****
       40           64 01000000:  14 **************
       41           65 01000001:   9 *********
       42           66 01000010:   6 ******
       43           67 01000011:   5 *****
       44           68 01000100:  12 ************
       45           69 01000101:  18 ******************
       46           70 01000110:  11 ***********
       47           71 01000111:  13 *************
       48           72 01001000:   5 *****
 
(py-tpms) mitchel@ubuntu:~/Development/py-tpms$ packet_stats.py --rangestats 56,64 < all_decoded.txt
Range 56:64
        1            1 00000001:   1 *
        3            3 00000011:   2 **
        4            4 00000100:   2 **
       13           19 00010011:   2 **
       15           21 00010101:   1 *
       17           23 00010111:   2 **
       19           25 00011001:   3 ***
       1c           28 00011100:   3 ***
       1d           29 00011101:   2 **
       21           33 00100001:   1 *
       29           41 00101001:   2 **
       35           53 00110101:   2 **
       3a           58 00111010:   1 *
       3e           62 00111110:   5 *****
       40           64 01000000:   2 **
       43           67 01000011:   1 *
       4a           74 01001010:   3 ***
       4c           76 01001100:   2 **
       52           82 01010010:   1 *
       59           89 01011001:   3 ***
       5b           91 01011011:   4 ****
       61           97 01100001:   3 ***
       6d          109 01101101:   2 **
       6f          111 01101111:   1 *
       74          116 01110100:   1 *
       7e          126 01111110:   1 *
       80          128 10000000:   1 *
       84          132 10000100:   1 *
       85          133 10000101:   2 **
       88          136 10001000:   2 **
       8b          139 10001011:   2 **
       8c          140 10001100:   2 **
       92          146 10010010:   2 **
       95          149 10010101:   1 *
       96          150 10010110:   5 *****
       99          153 10011001:   2 **
       9a          154 10011010:   3 ***
       9b          155 10011011:   1 *
       9c          156 10011100:   2 **
       9d          157 10011101:   2 **
       a1          161 10100001:   3 ***
       a3          163 10100011:   4 ****
       a4          164 10100100:   1 *
       a8          168 10101000:   1 *
       ab          171 10101011:   1 *
       b3          179 10110011:   1 *
       b8          184 10111000:   2 **
       b9          185 10111001:   1 *
       ba          186 10111010:   1 *
       bb          187 10111011:   1 *
       bc          188 10111100:   3 ***
       c0          192 11000000:   1 *
       c2          194 11000010:   2 **
       c5          197 11000101:   2 **
       c7          199 11000111:   1 *
       ca          202 11001010:   2 **
       d1          209 11010001:   2 **
       d3          211 11010011:   2 **
       d4          212 11010100:   3 ***
       d5          213 11010101:   3 ***
       d9          217 11011001:   1 *
       da          218 11011010:   1 *
       db          219 11011011:   2 **
       e0          224 11100000:   2 **
       e3          227 11100011:   2 **
       e4          228 11100100:   2 **
       fe          254 11111110:   1 *
       ff          255 11111111: 217 *************************************************************************************************************************************************************************************************************************

Filed Under: tinkering

Comments

  1. Dmitry says

    2017-04-17 at 03:45

    Hi Mitchel!
    Which one Partnumber of your TPMS-sensor?
    Do you have any progress after month of experiments?

    Reply
    • Mitchel says

      2017-04-17 at 22:04

      Hi Dmitry,
      I don’t actually know the part number for my sensors. I made a guess based on the compatible replacements from various auto parts stores.

      I’ve started working on using a hardware radio to do the decoding for me. Unfortunately, I’ve been working on other projects as well so I haven’t made much progress on that. I will definitely post about it when I do!

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *