|
| 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