Skip to content

Commit 474feb5

Browse files
authored
Add support for General Motors TPMS (merbanan#3191)
1 parent 309003c commit 474feb5

File tree

5 files changed

+149
-0
lines changed

5 files changed

+149
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
360360
[272] Landis & Gyr Gridstream Power Meters 19.2k
361361
[273] Landis & Gyr Gridstream Power Meters 38.4k
362362
[274] Revolt ZX-7717 power meter
363+
[275] GM-Aftermarket TPMS
363364
364365
* Disabled by default, use -R n or a conf file to enable
365366

conf/rtl_433.example.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ convert si
504504
protocol 272 # Landis & Gyr Gridstream Power Meters 19.2k
505505
protocol 273 # Landis & Gyr Gridstream Power Meters 38.4k
506506
protocol 274 # Revolt ZX-7717 power meter
507+
protocol 275 # GM-Aftermarket TPMS
507508

508509
## Flex devices (command line option "-X")
509510

include/rtl_433_devices.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@
282282
DECL(gridstream192) \
283283
DECL(gridstream384) \
284284
DECL(revolt_zx7717) \
285+
DECL(tpms_gm) \
285286

286287
/* Add new decoders here. */
287288

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ add_library(r_433 STATIC
254254
devices/tpms_eezrv.c
255255
devices/tpms_elantra2012.c
256256
devices/tpms_ford.c
257+
devices/tpms_gm.c
257258
devices/tpms_hyundai_vdo.c
258259
devices/tpms_jansite.c
259260
devices/tpms_jansite_solar.c

src/devices/tpms_gm.c

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/** @file
2+
General Motors Aftermarket TPMS.
3+
4+
Copyright (C) 2025 Eric Blevins
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation; either version 2 of the License, or
9+
(at your option) any later version.
10+
*/
11+
12+
#include "decoder.h"
13+
14+
/**
15+
General Motors Aftermarket TPMS.
16+
17+
Data was detected and initially captured using:
18+
19+
rtl_433 -X 'n=name,m=OOK_MC_ZEROBIT,s=120,l=0,r=15600'
20+
21+
22+
Data layout, 130 bits:
23+
AAAAAAAAAAAAFFFFDDDDIIIIIIPPTTCCX
24+
0000000000004c90007849176600536d0
25+
26+
27+
- A: preamble 0x000000000000
28+
- F: Flags
29+
- D: Device type or prefix
30+
- I: Device uniquie identifier
31+
- P: Pressure
32+
- T: Temperature
33+
- C: CheckSum, modulo 256
34+
35+
Format string:
36+
37+
ID:10h FLAGS:4h KPA:2h TEMP:2h CHECKSUM:2h
38+
39+
The only status data detected is learn mode and low battery.
40+
Bit 5 of status indicates low battery when set to 1.
41+
Bits 0,1,8 are set to 0 to indicate learn mode and 1 for operational mode.
42+
The sensors drop to learn mode when detecting a large pressure drop
43+
or when activated with the EL-50448 learning tool.
44+
45+
In learn mode with zero pressure they only transmit when activated by
46+
the learning tool.
47+
Once presurized they will transmit in learn mode and within a couple
48+
minutes switch to sending in operatioinal mode every two minutes.
49+
50+
*/
51+
52+
static int tpms_gm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
53+
{
54+
if (bitbuffer->num_rows != 1) {
55+
return DECODE_ABORT_EARLY;
56+
}
57+
58+
if (bitbuffer->bits_per_row[0] != 130) {
59+
return DECODE_ABORT_LENGTH;
60+
}
61+
62+
static uint8_t const preamble_pattern[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
63+
64+
int pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);
65+
if (pos < 0) {
66+
return DECODE_ABORT_EARLY;
67+
}
68+
69+
// Buffer for extracted bytes
70+
uint8_t b[17] = {0};
71+
bitbuffer_extract_bytes(bitbuffer, 0, 0, b, 130);
72+
73+
// Checksum skips preamble
74+
uint8_t computed_checksum = 0;
75+
for (int i = 6; i < 15; i++) {
76+
computed_checksum += b[i];
77+
}
78+
if ((computed_checksum & 0xFF) != b[15]) {
79+
return DECODE_FAIL_MIC;
80+
}
81+
82+
// Convert ID to an integer
83+
uint64_t sensor_id = ((uint64_t)b[8] << 32) | ((uint64_t)b[9] << 24) | (b[10] << 16) | (b[11] << 8) | b[12];
84+
int flags = (b[6] << 8) | b[7];
85+
86+
int pressure_raw = b[13];
87+
int temperature_raw = b[14];
88+
89+
// Adding 3.75 made my sensors accurate
90+
// But I think it might be best to allow the user to
91+
// to add their own offset when consuming the data
92+
float pressure_kpa = (pressure_raw * 2.75);
93+
float temperature_c = temperature_raw - 60;
94+
95+
// Extract bits correctly based on little-endian order
96+
int bit8 = (flags >> 8) & 1;
97+
int bit1 = (flags >> 1) & 1;
98+
int bit0 = (flags >> 0) & 1;
99+
100+
// Flags bits
101+
int learn_mode = ((bit8 == 0) && (bit1 == 0) && (bit0 == 0));
102+
int battery_ok = !((flags >> 5) & 1);
103+
104+
/* clang-format off */
105+
data_t *data = data_make(
106+
"model", "", DATA_STRING, "GM-Aftermarket",
107+
"type", "", DATA_STRING, "TPMS",
108+
"id", "", DATA_INT, sensor_id,
109+
"flags", "", DATA_INT, flags,
110+
"learn_mode", "", DATA_INT, learn_mode,
111+
"battery_ok", "", DATA_INT, battery_ok,
112+
"pressure_kPa", "", DATA_DOUBLE, pressure_kpa,
113+
"temperature_C", "", DATA_DOUBLE, temperature_c,
114+
"mic", "Integrity", DATA_STRING, "CHECKSUM",
115+
NULL);
116+
117+
/* clang-format on */
118+
119+
decoder_output_data(decoder, data);
120+
return 1;
121+
}
122+
123+
/** Output fields for rtl_433 */
124+
static char const *const output_fields[] = {
125+
"model",
126+
"type",
127+
"id",
128+
"flags",
129+
"learn_mode",
130+
"battery_ok",
131+
"pressure_kPa",
132+
"temperature_C",
133+
"mic",
134+
NULL,
135+
};
136+
137+
r_device const tpms_gm = {
138+
.name = "GM-Aftermarket TPMS",
139+
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
140+
.short_width = 120,
141+
.long_width = 0,
142+
.reset_limit = 15600,
143+
.decode_fn = &tpms_gm_decode,
144+
.fields = output_fields,
145+
};

0 commit comments

Comments
 (0)