[ Bus & Message Reference ]

Reverse-engineering reference for FCA / Stellantis CAN bus: bus topology (CAN-C vs CAN-IHS), TIPM/TIPMCGW gateway behavior, and decoded message IDs lifted from the scripts on the parent page with byte-level evidence — $1C0 RKE payloads, $122 ignition state, $350 RTC layout, UDS service path used by 3rd_brakelight.sh ($2D3 / $620 / DID $D1B3), and the full JEEP live-data message map extracted from pyJeepCan.py. Includes candidate IDs for fields commonly broadcast but not yet decoded here, plus the usual model/year caveats.

NOTE — platform specificity:  Most of the message IDs,
byte layouts, and decoded payloads documented here were captured on
JEEP-platform vehicles (Wrangler / Gladiator / Grand Cherokee / similar).
FCA / Stellantis reuses some IDs across its lineup (Chrysler, Dodge,
Ram, Alfa Romeo) but reassigns others aggressively between platforms
and model years — CAN-IHS content in particular varies a lot.

Treat every ID and byte offset on this page as a starting point for
your own candump verification, not a universal spec. The
Caveats section at the bottom has the longer
version of this disclaimer.

[ Glossary — abbreviations used here and in the parent-page scripts ]

Standards & protocols

  OBD-II      On-Board Diagnostics II (SAE J1979 / ISO 15031). The
                emissions-mandated diagnostic protocol every 2007+ vehicle
                speaks. Exposes a fixed set of standard PIDs over CAN-C
                at IDs $7DF (broadcast request) and $7E8-$7EF (per-ECU
                response). See OBD-II over CAN-C.
  UDS         Unified Diagnostic Services (ISO 14229). The
                full-featured diagnostic protocol that dealers' scan
                tools speak. Layered on top of ISO-TP. Adds session
                management, security access, ReadDataByIdentifier
                ($22), WriteDataByIdentifier ($2E), IOControlByIdentifier
                ($2F), RoutineControl ($31), etc. OBD-II is the
                public-API subset of UDS. See UDS over
                CAN-C.
  ISO-TP      ISO 15765-2 transport-layer protocol. Lets UDS
                payloads larger than 7 bytes ride on top of CAN's
                8-byte frames via single-frame / first-frame /
                consecutive-frame / flow-control packetisation.
  SAE J1979   The OBD-II PID standard. Defines the modes (0x01
                live data, 0x02 freeze frame, 0x03 stored DTCs, 0x04
                clear DTCs, 0x09 vehicle info) and the standard PID
                catalog within each.

Message-level identifiers

  PID         Parameter ID. In OBD-II Mode 0x01, a one-byte
                identifier for a specific live-data signal — 0x0C
                engine RPM, 0x0D vehicle speed, 0x05 coolant temp, etc.
  DID         Data Identifier. In UDS, a two-byte identifier for
                a data point read via Service 0x22 or written via
                Service 0x2E. E.g., DID $F190 = VIN, DID $D1B3 = 3rd
                brake light state.
  DTC         Diagnostic Trouble Code. The fault codes a scan
                tool reads. Each is a four-character string (P0301,
                B1234, etc.) encoded in two bytes. See
                DTC anatomy.
  MIL         Malfunction Indicator Lamp. The "check engine"
                light. Also called CEL (Check Engine Light) but MIL
                is the standards term. Driven by stored DTCs.

Modules & gateways

  ECU         Electronic Control Unit. Generic term for any
                in-vehicle computer module. Sometimes used specifically
                for the engine ECU, more often as a catch-all
                (the BCM is "the body ECU," the PCM is "the powertrain
                ECU," and so on).
  BCM         Body Control Module. Doors, locks, lighting,
                wipers, horn, interior comforts. Usually owns the
                $1C0 lock/unlock message and a lot of CAN-IHS chatter.
  PCM         Powertrain Control Module. Engine + transmission
                control. Speaks OBD-II Mode 0x01 over $7E8.
  SCCM        Steering Column Control Module. Cruise / wiper /
                turn-signal stalks; often the horn.
  RFH         Radio Frequency Hub. RKE (remote keyless entry) /
                TPMS receiver.
  EPS         Electric Power Steering controller.
  IPC / IPCM  Instrument Panel Cluster (Module). Dashboard
                gauges + warning-light driver.
  HVAC        Heating, Ventilation, Air Conditioning module.
  RBM         Radio & Body Module — Stellantis variant that
                combines radio head-unit and BCM functions in one box
                (later platforms).
  SGW         Secure Gateway Module. 2018+ FCA gateway that
                filters writes to safety-critical IDs. See the
                dedicated SGW
                guide.
  TIPM /
   TIPMCGW     Totally Integrated Power Module (with Central
                Gateway). Pre-2018 Jeep gateway, less restrictive than
                SGW. See TIPM section.

Other

  BMR         Bus & Message Reference — this page.
                Scripts on the parent page use "BMR" as shorthand
                when pointing back here.
  TPMS        Tire Pressure Monitoring System. RFH-managed on
                most FCA platforms.
  RKE         Remote Keyless Entry. The $1C0 lock/unlock message
                is the canonical RKE-style payload.
  VIN         Vehicle Identification Number. 17-character unique
                identifier. Readable via OBD-II Mode 0x09 or UDS
                Service 0x22 DID $F190.
  RTC         Real-Time Clock — the vehicle's onboard clock
                state. Lives at $350 on this platform.
  NM          Network Management. The CAN-IHS wake / sleep
                coordination protocol that uses $2D3.
  RPM         Revolutions Per Minute. Engine speed, reported via
                OBD-II PID 0x0C.
  EVIC        Electronic Vehicle Information Center. The
                center-dash text display on older JEEP platforms.
                Receives $328 music-info text.

[ CAN-C vs CAN-IHS — bus topology ]

In Chrysler / Stellantis (FCA) architecture, two physical CAN networks serve
different purposes and run at different speeds:

CAN-C       (500 kbps) — high-speed powertrain / chassis bus. Carries engine,
            transmission, ABS, ESP, steering angle, and other safety-critical
            ECUs.

CAN-IHS     (125 kbps) — Interior High Speed body / comfort bus. Carries HVAC,
            doors, locks, lighting, radio, RKE (remote keyless entry), seats.

When the same message ID appears on both buses, payload size is a reliable
discriminator on JEEP platforms:

    1-byte payload  ⇒  CAN-IHS
    8-byte payload  ⇒  CAN-C

For the physical wiring side of the topology (which connectors carry
which bus, where the TIPM/CGW lives in the wiring tree, module-to-module
distribution) see CAN-C-BUS-LAYOUT.pdf in the
JL wiring-diagram pack.

New here? The Jeep CAN bus primer
covers the bus from first principles — Waveshare HAT setup,
listen-only safety, TTCAN scheduling, sleep / wake behavior, the
$12B 1-byte-vs-8-byte payload trick for verifying which
adapter is on which bus, and a safe-to-send sample cansend
to dip your toes in. Read that first if "CAN-C vs CAN-IHS, but which
adapter sees which?" is the question you opened this page with.

[ SGW + CAN bus harness — pinouts and splices ]

Textual companion to CAN-C-BUS-LAYOUT.pdf in the
JL wiring-diagram pack,
expanded with the per-connector pin tables from the factory connector
reference. Covers the OBD-II port, both SGW connectors (C1 for CAN-C
+ ignition / ground, C2 for CAN-IHS + power / ground), main-trunk
splice points, and the eTorque-specific PCM / MGU / BPCM branch.
Most useful for probing or harness-side debugging behind the
glovebox.

The SGW gates BOTH CAN buses

Important: the Security Gateway Module is not just a CAN-C filter.
It sits between the OBD-II port and BOTH the in-vehicle CAN-C and
CAN-IHS buses, using two separate physical connectors:

    SGW C1   12-pin connector  -- CAN-C + ignition / ground
    SGW C2    8-pin connector  -- CAN-IHS + battery / ground

The "DIAGNOSTIC CAN" lines (both buses) enter the SGW from the OBD-II
DLC; the SGW's filtering policy is applied, and post-filter traffic
goes out to the in-vehicle trunks. The "AT" suffix that appears on
downstream wires (CAN C(+) AT, CAN IHS(+) AT, etc.) means
"After [the gateway]" -- i.e. post-SGW segment that body / chassis /
powertrain modules actually listen on.

What the SGW gating policy allows and blocks is documented at
Secure Gateway Module.

OBD-II Data Link Connector (DLC1)

Part numbers:
    Full repair kit:      05083231AA
    Terminal repair kit:  not separately identified

End view (connector facing you):

           pin 16                          pin 9
           pin  8                          pin 1

DLC1 / OBD-II connector pinout — factory reference

Pin assignments (full 16-cavity table; "N0" entries are unpopulated):

    Cav  Circuit  Gauge   Color    Function
    ---  -------  ------  -------  ------------------------------------
     1   N0       --      NA       NO CONNECT
     2   N0       --      NA       NO CONNECT
     3   D434     0.35    BG       DIAGNOSTIC CAN IHS(+)
     4   Z911     0.50    BK/YE    GROUND
     5   Z911     0.50    BK/VT    GROUND
     6   D428     0.35    BG       DIAGNOSTIC CAN C(+)
     7   N0       --      NA       NO CONNECT
     8   N0       --      NA       NO CONNECT
     9   N0       --      NA       NO CONNECT
    10   N0       --      NA       NO CONNECT
    11   D433     0.35    BG/YE    DIAGNOSTIC CAN IHS(-)
    12   N0       --      NA       NO CONNECT
    13   N0       --      NA       NO CONNECT
    14   D427     0.35    YE       DIAGNOSTIC CAN C(-)
    15   N0       --      NA       NO CONNECT
    16   A900     0.50    GY/RD    FUSED B(+)

The CAN-C diagnostic pair (pins 6 / 14) is what every ISO 15765 OBD-II
scanner expects -- this is the standard. The CAN-IHS diagnostic pair
on pins 3 / 11 is the manufacturer-discretionary usage; FCA /
Stellantis runs the body-bus diagnostic side over those two pins
specifically on this platform. Standard OBD-II tools won't talk to
the IHS side, but DIY harness work over the DLC absolutely can if
both pairs are wired through.

Wire-color mapping to follow:

    D427 (YE)        CAN-C diagnostic (-)
    D428 (BG)        CAN-C diagnostic (+)
    D433 (BG/YE)     CAN-IHS diagnostic (-)
    D434 (BG)        CAN-IHS diagnostic (+)

All four of these conductors land on the SGW (D427/D428 on C1 pins
10/3, D433/D434 on C2 pins 7/2) -- so every byte that arrives at the
DLC is filtered before reaching the in-vehicle trunks.

SGW connector C1 (12-pin) — CAN-C + ignition / ground

Part numbers:
    Full repair kit:      68080557AA
    Terminal repair kit:  68316921AA  (0.64mm Female Unsealed Tin,
                                       18ga, qty 10)

End view (connector facing you):

           pin 6           pin 1
           pin 12          pin 7

SGW connector C1 — factory reference (12-pin, CAN-C + ignition / ground)

Pin assignments:

    Cav  Circuit  Gauge   Color    Function
    ---  -------  ------  -------  -------------------------------------
     1   N0       --      NA       NO CONNECT
     2   N0       --      NA       NO CONNECT
     3   D428     0.35    BG       DIAGNOSTIC CAN C(+)         <-- from DLC1 pin 6 / 14 pair
     4   D436     0.35    GY       CAN C(+) AT                 --> to "AT" trunk
     5   D351     0.35    WH/BN    CAN C(+)                    --> to primary vehicle trunk
     6   Z912     0.75    BK/OG    GROUND
     7   F949     0.50    PK/GN    FUSED IGNITION RUN/START CONTROL OUTPUT
     8   N0       --      NA       NO CONNECT
     9   N0       --      NA       NO CONNECT
    10   D427     0.35    YE       DIAGNOSTIC CAN C(-)         <-- from DLC1 pin 6 / 14 pair
    11   D435     0.35    YE/VT    CAN C(-) AT                 --> to "AT" trunk
    12   D350     0.35    YE/GN    CAN C(-)                    --> to primary vehicle trunk

Three distinct CAN-C pairs on this single connector:

    pins  3 / 10  -- diagnostic side  (D428 / D427)
    pins  4 / 11  -- "AT" trunk       (D436 / D435)
    pins  5 / 12  -- primary trunk    (D351 / D350)

The split between the primary trunk (5/12) and the "AT" trunk (4/11)
is the SGW's physical separation of post-gateway traffic into two
downstream segments. Powertrain-side modules (PCM, ABS, ESP, MGU,
BPCM) tend to land on the primary trunk; body-adjacent modules
(radio, Emergency Assistance, body-domain stuff) tend to land on the
"AT" trunk. Both are post-filter; the split is for fault-isolation
and routing, not policy.

SGW connector C2 (8-pin) — CAN-IHS + power / ground

Part numbers:
    Full repair kit:      68496366AA
    Terminal repair kit:  68316921AA  (same as C1: 0.64mm Female
                                       Unsealed Tin, 18ga)

End view (connector facing you):

           pin 4           pin 1
           pin 8           pin 5

SGW connector C2 — factory reference (8-pin, CAN-IHS + power / ground)

Pin assignments:

    Cav  Circuit  Gauge   Color    Function
    ---  -------  ------  -------  ------------------------------------
     1   D437     0.35    DB       CAN IHS(+) AT             --> to "AT" trunk
     2   D434     0.35    BG       DIAGNOSTIC CAN IHS(+)     <-- from DLC1
     3   D353     0.35    VT/WH    CAN IHS(+)                --> to primary IHS trunk
     4   Z912     0.75    BK/OG    GROUND
     5   A902     0.35    RD       FUSED B(+)
     6   D438     0.35    WH       CAN IHS(-) AT             --> to "AT" trunk
     7   D433     0.35    BG/YE    DIAGNOSTIC CAN IHS(-)     <-- from DLC1
     8   D352     0.35    YE/DB   CAN IHS(-)                --> to primary IHS trunk

Same three-pair pattern as C1, just on a smaller connector and for
the lower-speed body bus:

    pins  2 / 7   -- diagnostic side  (D434 / D433)
    pins  1 / 6   -- "AT" trunk       (D437 / D438)
    pins  3 / 8   -- primary IHS trunk (D353 / D352)

C2 has its own independent ground (Z912) and B(+) feed (A902) so the
SGW continues to power-up independently of the C1-side ignition feed
-- the IHS-side filter logic stays alive even when the ignition is
off, which is what allows the SGW to enforce policy on a sleeping bus
and on RKE-driven IHS wakes (see $2D3 NM-wake
section above).

Vehicle-side CAN-C trunk splices

    Splice       Role
    --------     ----------------------------------------------------
    S2800        Main-trunk splice  (multi-module fan-out)
    S2801        Main-trunk splice
    S2802        Main-trunk splice
    S2803        Main-trunk splice
    S17150       eTorque-branch splice  (EPT-specific)
    S17151       eTorque-branch splice  (EPT-specific)

The S28xx splices are the natural taps for probing the main CAN-C
trunk; the S171xx splices are eTorque-only and absent on non-eTorque
trims.  Wire identifiers on the trunk are D435 / D436 (the "AT"
pair) and D350 / D351 (the primary pair) -- distinct from the
diagnostic-side D427 / D428 pair, which is how you tell pre-SGW vs
post-SGW wiring at a glance behind the glovebox.

eTorque (EPT) branch

    Module                  Connector    Pins        Wire IDs       Colors
    ---------------------   ----------   --------    -----------    --------
    Powertrain Control      C1           12, 13      D662 / D672    0.50 WH/DB, WH/BU
    (PCM, eTORQUE variant)
    Motor Generator Unit    -            4, 10       D630 / D629    0.35 OG/WH, YE
    Battery Pack Control    C1           3, 4 +      D662 / D672    0.35 WH/DB, WH/BU
    Module (BPCM)                        5, 11

"EPT" on the wire labels = ELECTRIC POWERTRAIN. These connections only
exist on eTorque-equipped trims (2.0L turbo eTorque, 3.6L Pentastar
eTorque on JL/JT). On non-eTorque vehicles the BPCM / MGU don't exist,
the wires don't run, and the PCM has only its normal CAN-C(+)/(-) pair
(no -EPT suffix).

Trim-variant routing (a real gotcha)

Several downstream wire colors split by trim. The diagram shows
EXCEPT EXPORT vs EXPORT and PREMIUM vs BASE variants of D435/D436
running to slightly different terminations:

    EXCEPT EXPORT  -- North American + most ROW markets
    EXPORT         -- export-spec trim (different harness routing)
    PREMIUM        -- premium audio / equipment package
    BASE           -- base audio / equipment package

Same logical bus, same message IDs, same protocol — but the wire
identifier (D435 vs D436, vs trim-specific variants) on a given pin
can differ across trims. A capture taken on a Premium-trim JL Wrangler
won't have wire labels identical to the same capture on a Base-trim
JT Gladiator. Bus traffic identical; harness identifiers not.

Implication for harness-side debugging: don't assume that "wire D435
at module X" on one diagram excerpt is the SAME conductor as "wire
D435 at module X" on a different-trim diagram. The bus signal at the
ECU pin is identical; the wire-label database differs.

[ TIPM / TIPMCGW — the bus gateway ]

The TIPM (Totally Integrated Power Module), or TIPMCGW on platforms that include
the Central Gateway, sits between CAN-C and CAN-IHS. It processes and selectively
re-broadcasts messages across the boundary, so the same logical event — an
unlock command, an ignition state change, a security-system trigger — can be
observed on both buses even though it originated on only one of them.

This is why payload size is a useful heuristic for "where did this actually come
from" when the same ID shows up on both buses (see topology note above).

[ Message ID $1C0 — Remote Lock / Unlock (RKE) ]

$1C0  (0x1C0 / 448 decimal)

On the FCA CAN bus, $1C0 lives in the body-control message area alongside
$1C8 and $291, but each of those IDs carries a distinct signal — $1C0
is the actual Remote Lock / Unlock command broadcast (RKE / body control),
typically generated when a key fob lock/unlock command is processed and
originating from the Body Control Module (BCM) or the RF Hub / Wireless
Control Module. $291 in the same neighbourhood carries lighting state, NOT
RKE (see #id-291 below for that one's decode).

Natural home is CAN-IHS, since locks and RKE are interior body functions. If
you observe $1C0 on CAN-C as well, that is almost certainly the TIPM/TIPMCGW
gateway re-broadcasting it for modules on that bus that need to react — e.g.
unlock-on-approach interactions with powertrain or security state.

New messages are sent every ~100 ms while the vehicle is awake; the payload
contains an ID byte representing the last fob command (or an idle-state ID
when no command was received). When the vehicle enters sleep mode, $1C0 stops
broadcasting until the vehicle wakes.

This is the same ID consumed by the Remote_WiFi.sh
script on the parent page — it sniffs $1C0 for fob-driven lock/unlock events
and uses the double-unlock sequence as a covert trigger for the Pi's WiFi radio.

Observed 6-byte payloads on the JEEP platform Remote_WiFi.sh was developed
against (byte 0 = command code, byte 3 = active/idle flag):

    1C0#21 00 00 90 00 00      Lock
    1C0#23 00 00 90 00 00      1st Unlock
    1C0#24 00 00 90 00 00      2nd Unlock     (double-tap)
    1C0#00 00 00 80 00 00      Idle           (no recent command)

Other bytes appear to carry fob ID / button-hold metadata but have not been
positively identified on this platform — see caveats below before assuming
this byte map holds for your year/model.

For the wiring side (BCM / RF-hub / door-lock actuator interconnect), see
VEHICLE-THEFT-SECURITY-SYSTEM.pdf in the
JL wiring-diagram pack.

[ Message ID $25D — Radio mute state (CAN-C) ]

$25D  (0x25D / 605 decimal)  —  CAN-C

Broadcast by the radio head unit while the vehicle is awake. Carries the
current MUTE state of the audio system in byte 3 (zero-indexed):

    byte 3   0x00 = unmuted (audio playing through speakers)
             0x03 = muted

Other bytes have not been decoded on this platform; observed frames look
like  $25D  XX XX XX 00 XX XX XX XX  with byte 3 varying in lock-step
with the mute toggle.

The toggle command itself (steering-wheel MUTE button) is NOT sent on
$25D. It rides on the wake bus as

    $2D3  07 00 01 00 00 00 00 00

— same arbitration ID as the standard NM wake frame, with byte 2 = 0x01
instead of 0x00. The radio treats that frame as a hardware mute-button
press, toggles its internal state, and re-broadcasts the new value on
$25D. Full $2D3 byte map at #id-2d3 below. See
mute.sh for the read-then-toggle pattern,
including the retry-on-bus-quiet wake-frame escalation when no $25D
traffic is observed within the listen window.

PLATFORM NOTE: $25D appears in the
candidate-IDs section below with a
completely different meaning observed on JL Wrangler — locker request
command codes (0x23 = rear, 0x13 = front+rear, 0x43 = unlock both, all
in byte 0). The radio-mute decode above is from a different FCA platform.
FCA reassigns IDs across platforms / model years, so the same ID can
carry entirely different semantics on a different vehicle. Verify on
YOUR vehicle before trusting either decode.

[ Message ID $2D3 — NM wake / center-dash button events (CAN-IHS) ]

$2D3  (0x2D3 / 723 decimal)  —  CAN-IHS

Network Management (NM) frame.  ECUs in low-power state monitor $2D3
to know when to wake up; once an ECU sees a frame on this ID it
transitions from sleep to operational and starts servicing the rest
of the bus.  The same arbitration ID also carries center-dash button
events — the button bitmap rides in byte 2.

Observed payloads:

    $2D3  07 00 00 00 00 00 00 X0     NM wake          ("no button")
    $2D3  07 00 01 00 00 00 00 00     wake + MUTE press

    byte 0   = 0x07         NM frame type / source identifier
    byte 1   = 0x00
    byte 2   = button bitmap
                 0x00 = no button (pure NM wake)
                 0x01 = MUTE pressed
                 other values not yet observed on this platform but
                 likely follow the same one-bit-per-button pattern
                 (volume up/down, source, scan, etc.)
    bytes 3-6 = 0x00
    byte 7    high nibble = arbitrary (BCM ignores it for routing,
                            but a random value per invocation
                            defeats stuck-button detection in some
                            firmware versions — see wake.sh)
              low  nibble = 0x0 (reserved / unobserved)

USAGE PATTERNS

    Pure wake before a sensitive operation:
        cansend can0 2D3#0700000000000000
    or, with random byte-7 high nibble to look like fresh activity:
        cansend can0 2D3#07000000000000${RND}0

    Button-press emulation (mute):
        cansend can0 2D3#0700010000000000

The 3rd_brakelight.sh / horn.sh / 3honk.sh UDS scripts on the parent
page issue a pure-wake variant of this frame before opening their
diagnostic session, because UDS commands on a sleeping ECU silently
drop.  The mute.sh script uses the
button-press variant (byte 2 = 0x01) as the actual mute toggle, since
the radio listens for the same hardware button signal the steering
wheel emits.  The wake.sh tiny helper
is just a one-shot version of the pure-wake send with a fresh random
byte-7 nibble on every call.

PLATFORM NOTE
   The $2D3 frame and its NM-wake role are widely shared across FCA /
   Stellantis platforms.  The button-bitmap byte 2 interpretation is
   more variable -- the only positively-decoded button on the JEEP
   platform mute.sh was developed against is the MUTE button (0x01).
   Other button codes are sniff-and-discover territory.

[ Message ID $328 — EVIC music-info text (CAN-IHS) ]

$328  (0x328 / 808 decimal)  —  CAN-IHS

The radio head unit broadcasts $328 to update the three text lines of
the EVIC music-information page (the metadata region of the cluster
display between the speedometer and tachometer). Each "page update" is
a sequence of $328 frames followed by a single all-zero $328 terminator
frame that commits the new lines.

Per-frame layout for a text payload:

    328  <line>0   <seq><func>   c0 c1 c2 c3 c4 c5

    byte 0  high nibble = remaining-frames counter, counting DOWN from
            (total_frames - 1) to 0.  So a 4-frame payload ships as
            byte 0 = 0x30, 0x20, 0x10, 0x00 in order.  Low nibble = 0.
    byte 1  high nibble = 4 for the FIRST frame of a string, 0 for
                          every continuation frame (sequence marker).
            low  nibble = function -- which of the three text lines:
                          1 = input name        (top line)
                          2 = artist            (middle line)
                          3 = title             (bottom line)
    bytes 2-7  three characters, encoded as UTF-16BE pairs:
                 00 c0   00 c1   00 c2
               where each high byte is 0x00 and each low byte is the
               ASCII code of one character.  Strings are right-padded
               with 00 00 pairs to a multiple of three characters.

Terminator (all bytes = 0x00) commits the lines to the display:

    328  00 00 00 00 00 00 00 00

Without the terminator the EVIC keeps showing the previous text.

The radio also natively broadcasts $328 frames whenever a real audio
source updates — SiriusXM track changes, Bluetooth metadata updates,
USB next-song presses. Hijacking the message means temporarily masking
whatever the radio was about to display; once the user changes track
or pauses the radio refreshes its own state and overwrites your text.

See evic.sh for the encoder. The script
takes a line number (1/2/3) and a free-form string, encodes the
UTF-16BE pairs, and emits the frame sequence with the right counter
and seq-marker bytes.

[ Message ID $291 — Headlights / turn signals / interior lighting (CAN-IHS + CAN-C) ]

$291  (0x291 / 657 decimal)  —  CAN-IHS  (re-broadcast on CAN-C
                                              by the TIPM/CGW gateway)

Native home is CAN-IHS — $291 is body-control state (lights are body
functions, not powertrain).  But the TIPM/CGW gateway re-broadcasts
this ID onto CAN-C for modules on that bus that need to react to
light changes, which means $291 is observable on EITHER bus on most
FCA platforms.  Scripts on this site disagree on which bus to read
from (backlight.sh uses can1=CAN-IHS in its labelling; lights.sh
uses can1=CAN-C) -- both work in practice because the gateway
mirrors the frames.  Use whichever bus is most convenient for your
wiring.

Broadcasts the current state of the headlight stalk and interior
lighting controls while the vehicle is awake. Five independent
signals decoded so far, each in its own byte of the 8-byte payload:

    byte 2   Running lights      LOW NIBBLE carries the flag:
                                  0x_0 = off
                                  0x_1 = on
                                  0x_3 = on (alternate code, possibly
                                          auto-headlight-controlled
                                          mode)
                                  High nibble carries other state we
                                  haven't fully decoded; observed
                                  values include 0x00 and 0x10.
                                  Practical check: `byte2 & 0x0F`
                                  matches 1 or 3 -> running lights on.
                                  See backlight.sh
                                  for an in-the-wild low-nibble test.
    byte 3   Turn signals /     HIGH NIBBLE carries the blinker state:
             hazards              0x0_ = no blinker
                                  0x4_ = left blinker
                                  0x8_ = right blinker
                                  0xC_ = hazards (both)
                                  Low nibble not yet decoded.
                                  See lights.sh
                                  for a file-as-flag exporter.
    byte 5   Cabin lights        0x00 = off
                                  0x01 = on
    byte 6   Interior dimmer     0x22 = lowest brightness
                                  0xC8 = highest brightness
                                  (linear 0x22..0xC8 = 34..200 dec,
                                   ~166-step display brightness range)
    byte 7   Headlights          0x00 = off
                                  0x02 = low beam
                                  0x04 = high beam

Bytes 0, 1, 4 not yet decoded — likely carry additional lighting
state (fog lights, auto-headlight mode, daytime running lights as a
distinct signal, etc).

Notable combinations:

    running=0, headlights=0    all off
    running=1, headlights=0    parking / DRL / running lights only
    running=1, headlights=2    low beam (running automatically on)
    running=1, headlights=4    high beam (running automatically on)

That is: $291 byte 7 carries the BEAM state, and byte 2 carries the
running-lights state separately. Both being non-zero is the normal
"headlights on" condition; running=1 alone is parking-lights-only.

Relationship to $1C0 / $1C8: $291 sits alongside the RKE messages
in the body-control area of CAN-IHS, but it specifically carries
lighting state — not RKE. The "$1C0 / $1C8 / $291 message
group" framing in older notes is overly broad; treat $291 as a
separate body-control broadcast on its own.

Potential collision: the candidate-IDs MEDIUM table below lists
$214 as a headlight-and-turn-signal candidate carrying bit flags.
$291 is the authoritative entry for headlight beam state on this
platform; $214 may be a related but different signal — the BCM
sometimes broadcasts switch position separately from the actual
output state. Investigate further if your application cares about
the distinction.

[ Message ID $122 — Ignition / virtual key state (CAN-IHS) ]

$122  (0x122 / 290 decimal)  —  CAN-IHS only

Broadcast every ~100 ms while the vehicle is awake; carries the virtual
ignition switch state (OFF / ACCESSORY / RUN / START). The vehicle stops
sending $122 entirely when it enters sleep mode — absence of $122 is the
sleep-state signal.

The Blackbox_monitor.sh script
treats the payload as a single big-endian integer and uses bit 26
(0x04000000) as the run-state flag:

    payload >= 0x04000000   ⇒  RUN / START   (engine running, ACC + RUN)
    payload <  0x04000000   ⇒  OFF / ACCESSORY

That bit-26 threshold is sufficient to drive on/off automation (start log
on RUN, terminate log on OFF). For finer-grained discrimination, the
first two bytes of $122 carry specific state codes:

    bytes 0-1 hex   state
    -------------   -----
    0000 / 0001     Off
    0301 / 0302     Kill        (ignition explicitly killed)
    0502 / 1502     Accessory
    4501            Start       (starter cranking, key-driven)
    5D01            Crank       (crank in progress, possibly
                                 remote-start related)
    4401            Remote-Run  (engine running from a remote start)
    0402            Run         (engine running, normal key-driven)

These codes are decoded by the
log_reader.sh replay tool and
mapped to short human-readable labels (Off / Kill / Acc / Strt / Crnk
/ RRun / RAcc / Run / Unk) for one-line-per-tick rendering. The
"RAcc" label is a derived state: byte 0-1 is 4401 (Remote-Run) but
RPM is zero, meaning the engine isn't actually turning -- accessory-
mode equivalent during remote start.

[ Message ID $077 — Engine / vehicle state (CAN-C) ]

$077  (0x077 / 119 decimal)  —  CAN-C

Engine / vehicle running-state broadcast on CAN-C. Bytes 0-1 form a
16-bit state code; observed values on the JEEP platform:

    0x0422   Engine running (most common code)
    0x4421   Engine running (alternate / mode-dependent)
    0x5D21   Remote start in progress
    < 0x0400  Vehicle off / accessory / pre-crank
    > 0x0399  Vehicle on  (engine may or may not be running)

The script uses these thresholds to fire callbacks:

    state < 0x0400               vehicle off / engine shutdown
    state >= 0x0400              vehicle powered on
    state in {0x0422, 0x4421}    engine running (fires enginestarted
                                  once per cold start)

Bytes 2-7 of $077 aren't decoded by the current
autocollect.sh script; they
likely carry additional sub-mode or transmission state. Candidate
follow-on candump-diff exercise: trigger known states (key-on / start
/ remote start / shift through PRNDL / kill engine) and watch which
bytes beyond 0-1 change.

Relationship to $122: $077 and $122 both encode "is the engine
running?" but on different buses and with different formats. $122 is
on CAN-IHS and uses a bit-26 threshold across the full 32-bit
payload (see #id-122 above). $077 is on CAN-C
and uses specific 16-bit state codes. Either signal is sufficient
for on/off automation; the framework-vs-recorder choice is more
about how many hook points you need than which signal you watch.

Pre-2018 note: a script using $077 byte-pattern comparison via bash
`-gt` / `-lt` against the raw hex string will crash on any state
containing A-F (like 0x5D21). The legacy autocollect.txt has a
hard-coded `5D21 -> 5555` workaround; the tidied autocollect.sh
converts via $((16#$state)) so all hex values compare correctly.

[ Message ID $08B — Individual wheel speeds (CAN-C) ]

$08B  (0x08B / 139 decimal)

Broadcast on CAN-C every 1/50th of a second (20 ms cadence). Carries
the live speed of each of the four wheels in MPH * 20 — raw integer,
no long-period averaging, just the most recent ABS-sensor reading the
ESC module has on hand.

Dividing the raw value by 20 yields MPH with a resolution of 0.05 MPH
per count, which is finer-grained than anything the dashboard
speedometer renders. Four wheel slots fit in the 8-byte payload as
2-byte big-endian fields:

    bytes 0-1   front left   wheel speed   (raw / 20 = MPH)
    bytes 2-3   front right  wheel speed   (raw / 20 = MPH)
    bytes 4-5   rear  left   wheel speed   (raw / 20 = MPH)
    bytes 6-7   rear  right  wheel speed   (raw / 20 = MPH)

Useful for ABS / traction-control work, slip-angle inference, and any
case where you want per-corner motion rather than the gateway's already-
averaged value.

$08B vs $340 — primary speedometer mismatch

When the $08B per-wheel readings are compared against the vehicle's
primary speedometer source (CAN-IHS $340,
which the transmission module broadcasts and the dashboard renders),
the dashboard reads consistently HIGHER than the wheels — on the
order of 1–2 MPH, persistently, not just a transient blip.

That's not measurement noise. The dashboard speedometer is intentionally
biased upward across most automakers to comply with regulations
(UNECE R39, FMVSS 101 in spirit) that forbid an under-reading speedo
but allow over-reading. The bias is typically 1–3 MPH at highway
speeds.

Implication for tooling: if you want the TRUE wheel speed for telemetry,
slip calculations, or comparison against GPS, prefer $08B over $340.
If you want what the driver SEES, use $340. Don't blend the two
sources into a single "vehicle speed" reading without picking which
semantic you mean.

[ Message ID $2C2 — Battery voltage (CAN-IHS) ]

$2C2  (0x2C2 / 706 decimal)

Broadcast frequently on CAN-IHS while the vehicle is awake. Byte 2 of
the 8-byte payload encodes battery voltage in tenths-of-a-volt:

    byte 2   battery voltage   raw / 10 = volts
                               (e.g. 0x78 = 120 = 12.0V, 0x86 = 13.4V)

Other bytes carry additional power-distribution data the community
has not fully decoded; partial captures suggest at least one bit
flips when the alternator is producing vs draining. Capture-and-diff
with the engine off vs running is the easiest way to map those.

If the entire payload reads as 0xFF * 8, the bus has either gone to
sleep or the BCM hasn't re-broadcast since a wake event — treat
that as a sentinel and either wake the bus (one $2D3 NM-wake frame)
or wait for the next natural transmission.

Worked example — reading battery voltage with the on-site battery.sh helper:

    candump   can0 2C2 # 14 B3 78 00 00 00 00 00
                                \/
                                |
                                +-- byte 2 = 0x78 = 120 decimal

    Decode    raw / 10 = 120 / 10 = 12.0 V

    Script    $ ./battery.sh
              12.0 vdc

The script uses a 2-second candump window with an ID filter (candump -L can0,02C2:0fff), retries once after sending a $2D3 NM-wake frame if the bus is asleep, and treats an all-FF payload as a hard error. Useful for cron-driven health checks of a parked vehicle or as a quick "is the battery sagging under load?" probe during ignition / accessory transitions.

[ Message ID $350 — Vehicle clock / RTC ]

$350  (0x350 / 848 decimal)

Broadcast once per second on both CAN-C and CAN-IHS while the vehicle is
awake. Carries the dashboard clock as raw hex (NOT BCD), one field per byte:

    byte 0   seconds              (0x00..0x3B)
    byte 1   minutes              (0x00..0x3B)
    byte 2   hours                (0x00..0x17)
    bytes 3-4  year               (big-endian, e.g. 0x07E7 = 2023)
    byte 5   month                (0x01..0x0C)
    byte 6   day                  (0x01..0x1F)

A payload of all 0xFF means the clock has never been initialized (vehicle
disconnected from battery, or set-from-radio has not run). The
getVehicleTime.sh script uses this
as an error sentinel.

Because the message ID and payload format are stable across platforms (the
dashboard clock has to render somewhere), $350 is one of the more reliable
"is this bus even alive" probes during initial reverse-engineering.

[ Message ID $358 — Compass heading ]

$358  (0x358 / 856 decimal)

Broadcasts the dashboard compass heading as an 8-position cardinal
direction code in the LOW NIBBLE of byte 0:

    byte 0 nibble:
        0x0  N    (North)
        0x1  NE   (Northeast)
        0x2  E    (East)
        0x3  SE   (Southeast)
        0x4  S    (South)
        0x5  SW   (Southwest)
        0x6  W    (West)
        0x7  NW   (Northwest)
        0xF  unknown / no fix (compass not yet calibrated, or
              vehicle disconnected from the magnetometer)

The high nibble of byte 0 and the rest of the payload not yet decoded
— likely carries finer-grained heading, calibration state, or
tilt-compensation data. The 8-direction code is sufficient for the
"which way is the car pointing" display the dashboard compass icon
shows, but a real-degree heading would need deeper decode.

Decoded by the log_reader.sh
replay tool to render the DIR field on each tick line.

[ Message ID $3E0 — Vehicle Identification Number (VIN) ]

$3E0  (0x3E0 / 992 decimal)  —  CAN-IHS

Broadcasts the vehicle's 17-character VIN across three sequential
frames at roughly 0.1 s intervals. Each frame carries:

    byte 0      sequence id  (0x00, 0x01, or 0x02)
    bytes 1-7   seven VIN characters as raw ASCII (in hex)

The 17 VIN characters don't divide evenly into 7-byte payloads
(7 + 7 + 3 = 17), so the third frame's trailing bytes are 0x00 padding.
Strip the nulls during reassembly.

Frames may arrive out of order on a busy bus — assemble by
sequence id, not arrival order. Example capture (CAN-IHS, awake
vehicle):

    can0 3E0#00 31 43 36 4A 4A 54 41    "00 1C6JJTA"   (frame 0, chars 0-6)
    can0 3E0#01 47 35 4C 34 30 30 32    "01 G5L4002"   (frame 1, chars 7-13)
    can0 3E0#02 33 34 35 00 00 00 00    "02 345"       (frame 2, chars 14-16 + pad)

Reassembled VIN: 1C6JJTAG5L4002345 (17 characters).

ISO-standard alternative: UDS Service 0x22 (ReadDataByIdentifier) on
DID 0xF190 returns the VIN via the diagnostic protocol on whatever
module owns it. Broadcast on $3E0 is easier when available
(no session control, no $7E0 / $7E8 etc.); UDS is the right fallback
when the platform doesn't broadcast.

Compatibility note: some older / non-Wrangler FCA platforms may
broadcast VIN on $380 instead of $3E0. Always candump on YOUR vehicle
to confirm before scripting. The
getVIN.sh script's CAN_ID variable is
the one knob to change if your VIN lives elsewhere.

[ UDS over CAN-C — service path used by 3rd_brakelight.sh ]

UDS (Unified Diagnostic Services, ISO 14229) rides on CAN-C with a
request/response framing completely different from the broadcast-style
messages above. Every request gets a positive or negative response from
the target ECU.

The 3rd_brakelight.sh script uses
the following sequence to toggle the cargo-area / 3rd brake light:

    1.  NM wake frame on $WAKE_BUS.  cansend can0 2D3#0700000000000000
        Wakes ECUs out of low-power
        sleep before the diagnostic
        session begins below.

    2.  Enter Extended Diagnostic    cansend can1 620#0210030000000000
        Session (Service 0x10,                     |  |  +- subfn 0x03
        sub-function 0x03).                        |  +---- service 0x10
                                                   +------- PCI len = 2

    3.  TesterPresent keepalive,     cansend can1 620#023E800000000000
        sent every ~2 seconds from                 |  |  +- sub 0x80
        a background loop so the                   |  |     (suppress positive
        ECU's S3 timer (typically                  |  |     response, quieter)
        ~5 seconds) never drops our                |  +---- service 0x3E
        session.                                   +------- PCI len = 2

    4.  IOControlByIdentifier        cansend can1 620#052FD1B303010000
        (Service 0x2F), DID                        |  |    |  |  +-- state
        $D1B3 (3rd brake light),                   |  |    |  +----- control byte
        control 0x03 (short-term                   |  |    +-------- DID lo
        adjustment), state 0x01 (ON):              |  +------------- DID hi / svc
                                                   +---------------- PCI len = 5

    5.  Turn it OFF with state 0x00: cansend can1 620#052FD1B303000000

    6.  On exit / Ctrl+C, hand       cansend can1 620#042FD1B300000000
        control back to the ECU                    |  |    |  +----- control 0x00:
        (returnControlToECU). Note:                |  |    |         returnControlToECU
        NO state byte -- this                      |  |    +-------- DID lo
        request is one byte shorter                |  +------------- DID hi / svc
        than the shortTermAdjustment.              +---------------- PCI len = 4

Notable:

  - $2D3 on the wake bus is a Network Management (NM) frame, not UDS.
    Full byte map at #id-2d3.
  - $620 is the UDS REQUEST arbitration ID for the BCM; this ECU's
    RESPONSE arrives on $504. The request/response delta isn't a fixed
    constant on FCA platforms — each module has its own pair, you
    discover them by triggering an interaction from a known-good UDS
    tool (JScan, wiTECH, etc.) while watching candump. See
    Reverse Engineering UDS with JScan
    for the technique.

  - Positive response service byte = request service byte + 0x40. So
    request 0x10 (DiagnosticSessionControl) yields response 0x50;
    request 0x2F (IOControlByIdentifier) yields 0x6F; request 0x22
    (ReadDataByIdentifier) yields 0x62; request 0x31 (RoutineControl)
    yields 0x71. Negative responses are always 0x7F followed by the
    original service byte and a one-byte NRC (e.g. 0x33 =
    securityAccessDenied, 0x12 = subFunctionNotSupported).

  - Service 0x2F (IOControlByIdentifier) is your knob for forcing
    actuators via diagnostics. Service 0x22 (ReadDataByIdentifier) is
    the safe read-only equivalent — always read a DID via 0x22
    before any 0x2F write so you know how to undo your change. Service
    0x31 (RoutineControl, sub 0x05 start / 0x02 stop / 0x03 results)
    is the heavier-weight cousin: starts a routine on the target ECU
    that runs as long as the diagnostic session stays open. Used by
    2k.sh to hold engine RPM via the ECM.

  - Two cleanup models for actuator writes:
        TOGGLE-AND-RELEASE   IOControl ON, then explicitly IOControl
                             OFF + returnControlToECU on exit. Used
                             by horn.sh / 3rd_brakelight.sh (with a
                             SIGINT trap to cover Ctrl+C).
        HOLD-VIA-TESTERPRESENT
                             Start a routine, then keep the session
                             alive with TesterPresent. Stop pinging
                             and the session times out, which
                             automatically releases the routine. No
                             explicit cancel command. Used by 2k.sh.

  - Session sub-functions seen on this platform:
        0x01  defaultSession            (always available)
        0x03  extendedDiagnosticSession (BCM honor; horn / brake)
        0x92  Chrysler manufacturer-    (ECM honors; RPM hold via
              specific session         RoutineControl)

  - The same walkthrough works verbatim for any IOControl- or
    RoutineControl-addressable target on the same ECU. Known module
    ID pairs and DIDs / routine IDs on this JEEP platform:

        $620 / $504  Body Control Module (BCM)
            $D0AD  horn          (see horn.sh)
            $D1B3  3rd brake     (see 3rd_brakelight.sh)
            ...    40+ more BCM IOControl DIDs (turn signals, fog
                   lights, low/high beams, marker / park / reverse
                   lamps, wipers, washer, door locks, courtesy /
                   reading / footwell lights) on JL Wrangler / JT
                   Gladiator. Full catalog at UDS
                   Write Operations: Known writable targets.

        $783 / $503  HVAC Module
            $D020  battery voltage (read-only via Service 0x22,
                   8-bit value = volts * 10)

        $7E0 / $7E8  Engine Control Module (ECM)
            (OBD-II 11-bit IDs, NOT FCA-internal -- different
             namespace from the BCM / HVAC pairs above)
            Routine ID 0x07D0 = 2000 RPM target via Service 0x31
            sub 0x05 startRoutine. Untested hypothesis: the routine
            identifier IS the RPM value in BE-decimal, so 0x05DC
            would set 1500 RPM, 0x09C4 would set 2500 RPM, etc.
            See 2k.sh.

    Substitute the DID / routine ID — and the module's request/
    response IDs — and you've got a new script. The scaffolding
    (NM wake + Session Control + TesterPresent + actuator command)
    is reusable across all of them. For the cross-script perspective
    on this pattern (cleanup models, service comparison, known
    writable targets, SGW gating, safety rules), see
    UDS Write Operations on FCA /
    Stellantis.
  - Steps 3 and 6 are the safety-correct additions the tidied script adds
    over a naive "loop forever sending 0x2F" implementation. Without
    TesterPresent the session can time out on bus quiet; without
    returnControlToECU the ECU stays in test-controlled state until
    something else writes the DID or the session expires on its own.

Python alternative for UDS work: udsoncan
(referenced in 3rd_brakelight.sh's notes).

UDS over the OBD-II port specifically on 2018+ FCA vehicles is gated by the
Secure Gateway Module — see the
SGW / SGM reference for what gets
through unauthenticated and what doesn't. Direct CAN access via the
13-way connectors behind the glovebox is NOT gated by the SGW, which is
one of the main reasons that access point is useful for research work.

[ Module ID catalog — known UDS endpoints ]

Catalog of UDS arbitration ID pairs observed on the FCA / Stellantis JEEP platform, accumulated from on-vehicle reverse engineering. Tx is the request ID the tester sends; Rx is the response ID the module replies on. The Rx = Tx + offset relationship varies by module (0xBC offset for the 0x6xx range, 0x180 offset for the 0x7xx range, just +0x08 for the OBD-II-standardised 0x7Ex range).

The same module pairs power the script catalog in wid.py, ioid.py, and ecureset.py — if you want to talk UDS to one of these modules from Python, those scripts' MODULE_INFO dict is keyed off this same table.

  Tx     Rx     Bus       Module
  -----  -----  --------  -------------------------------------------
  $47E   $47F   ?         Security Gateway Module           (see SGW guide)
  $620   $504   CAN-IHS   Body Control Module               (BCM -- horn, brake light, etc.)
  $740   $4C0   ?         RF Hub                            (RKE receiver)
  $742   $4C2   ?         Instrument Panel Cluster (IPCM/EVIC)
  $743   $4C3   ?         Tire Pressure Monitoring (TPMS)
  $744   $4C4   ?         Airbag / Occupant Restraint
  $747   $4C7   CAN-C     Anti-lock Braking System (ABS)
  $749   $4C9   ?         Electronic Shifter
  $74A   $4CA   CAN-IHS   Sway Bar                          (Wrangler disconnect; cf. $26F broadcast)
  $74B   $4CB   CAN-C     Drive train FDC
  $753   $4D3   ?         Adaptive Cruise Control (ACC)
  $75A   $4DA   ?         Park Assist
  $762   $4E2   ?         Electric Power Steering (EPS)
  $763   $4E3   ?         Steering Column Module (SCM)
  $783   $503   ?         HVAC                              (battery V at DID $D020)
  $784   $504   ?         Driver Door (?)
  $785   $505   ?         Passenger Door (?)
  $792   $512   CAN-IHS   Unknown
  $794   $514   ?         Central Vision Processing
  $7BC   $53C   ?         Integrated Center Stack Control (CSCM)
  $7BE   $53E   ?         Amplifier
  $7BF   $53F   CAN-IHS   Uconnect Radio Module
  $7E0   $7E8   CAN-C     Powertrain CM / ECM               (2k.sh, obd.sh, obd2.py)
  $7E1   $7E9   ?         Transmission CM (TCM)
  $7E2   $7EA   ?         Hybrid Control Processor
  $7E7   $7EF   ?         Battery Pack Control Module

Notes on the entries:

For module IDs that exist in this table but DON'T have an on-site demo script targeting them, the path to making one is: wid.py + the appropriate module key, plus a Service 0x22 read for discovery first to avoid bricking anything. The 9-rule safety section on the UDS Writes page applies in full.

[ Worked example — Service 0x22 ReadDataByIdentifier on the BCM ]

The walkthrough above used Service 0x2F (IOControlByIdentifier) as the write path that 3rd_brakelight.sh follows. Service 0x22 is the read counterpart — ask an ECU for the value stored under any Data Identifier (DID); no session unlock required for most DIDs. Full read-side architecture (module catalog, DID discovery patterns, ISO-TP framing, SGW pass-through rules) lives on the dedicated UDS Read Operations reference guide; this section walks one read byte-by-byte as a concrete companion example.

Worked example: read DID $0146 (CSM1 — a BCM configurable-security data field) on a 2021 Jeep Wrangler via the behind-the-glovebox CAN-C connector, using isotpsend / isotprecv from can-utils. Two terminals. Terminal 1 sets up the listener (blocks until a response arrives):

isotprecv -s 620 -d 504 -p 00:00 -P l can1

Terminal 2 sends the request:

echo "22 01 46" | isotpsend -s 620 -d 504 -p 00:00 -P l can1

Terminal 1 prints the response. If the first attempt sees nothing, the bus was probably asleep — the isotpsend frame itself wakes it, so a second invocation should succeed. Observed response on the test vehicle: 62 01 46 00 30 76 41 E4 A3 C7 10. Decoded byte-by-byte:

    Request   22 01 46
              \_/  \__/
               |     |
               |     +-- DID = $0146 (CSM1 -- BCM configurable security data)
               +-------- Service 0x22 ReadDataByIdentifier

    Response  62 01 46 00 30 76 41 E4 A3 C7 10
              \_/ \__/ \_____________________/
               |   |              |
               |   |              +-- 8 bytes of payload from DID $0146
               |   +----------------- DID echo (== request DID)
               +--------------------- 0x62 = 0x22 + 0x40 (positive response)

Same +0x40 positive-response rule covered for the 0x2F walkthrough above: service byte + 0x40 on success, 7F <service> <NRC> on failure. For example 7F 22 13 would indicate "incorrectMessageLengthOrInvalidFormat" against the read request; 7F 22 31 "requestOutOfRange" (DID not implemented on this ECU). isotprecv handles ISO-TP reassembly transparently — the 11 payload bytes above arrive as a single-frame or first-frame + consecutive-frame pair on the wire, but you see the reassembled content.

Single-threaded interaction caveat. Unlike broadcast traffic (where candump and event handlers can multi-thread freely), direct UDS module interaction needs to be serialised. Two concurrent isotpsends against the same ECU can interleave at the wire, the ECU may answer one but not the other, and a separate listener may pick up the "wrong" response. The standard fix is a single-threaded gateway pattern: serialise every 0x22 / 0x2F / 0x2E / 0x31 call through one process so requests stay paired with their responses.

Python equivalent. For the same VIN read implemented with the python-can + python-can-isotp + udsoncan stack instead of can-utils CLI tools, see read_vin_uds.py on the parent page. Same BCM, same DID 0xF190, same request / response on the wire — just scripted in Python with ISO-TP framing and UDS protocol handled by libraries instead of by hand.

Forward-looking notes (jmccorm's research direction for this technique, not yet implemented on this site):

[ Decoded BCM read-only DIDs ]

BCM DIDs whose payload fields have been decoded (not just verified as "returns 0x62 on read"). As more DIDs get their internal structure mapped, this list grows. Companion to the BCM IOControl DID catalog on the UDS Writes page, which lists the 0x2F-controllable actuators on the same module.

$0146 — CSM1 (BCM configurable security data, 8 bytes)

Used as the worked example in Service 0x22 ReadDataByIdentifier above. Round-trip Service 0x2E write verified by jmccorm against this DID. Internal field semantics not yet mapped — treat as an 8-byte opaque blob whose bit-level meaning is TBD.

$A01A — Oil-change statistics (6 bytes)

Service 0x22 read. Three big-endian 16-bit fields tracking driving metrics since the last oil-change reset on the dash:

    Read DID $A01A
    --------------
    Request:   22 A0 1A
    Response:  62 A0 1A b0 b1 b2 b3 b4 b5
                                \___/ \___/ \___/
                              |     |     |
                              |     |     +-- kilometres since reset (b4 * 256 + b5)
                              |     +-------- hours since reset (b2 * 256 + b3)
                              +-------------- engine revs since reset (b0 * 256 + b1) * 1000

Worked example, captured live from a real vehicle (jmccorm's vehicle, read via the rid companion to wid.py with -d decimal output):

  $ rid -d bcm A01A
  13 152 0 38 2 245

Decodes as:

  Engine revolutions: (13 * 256 + 152) * 1000 =  3,480,000 revs
  Hours running:      (0 * 256 + 38)        =          38 hours
  Distance:           (2 * 256 + 245)       =         757 km  (~470 mi)

The "× 1000" multiplier on the revs field is the trick — the BCM stores revs in thousands rather than counting every individual crank rotation (which would overflow a 16-bit counter in roughly an hour of driving). A full 16-bit value here = 65 535 × 1000 = 65.5 million revs before wraparound, which is realistic for typical oil-change intervals.

Equivalent hex view (same payload, no -d flag):

  $25D-style read of $A01A:
      Request:    cansend $UDS_BUS  $REQ#03 22 A0 1A 00 00 00 00
      Response:   $RESP#08 62 A0 1A 0D 98 00 26 02 F5
      Payload:    0D 98 00 26 02 F5
                  └─┬─┘ └─┬─┘ └─┬─┘
                    │     │     └── 02F5h =   757 km
                    │     └──────── 0026h =    38 hours
                    └────────────── 0D98h × 1000 = 3,480,000 revs

Use cases beyond curiosity:

[ OBD-II over CAN-C — the standardised cousin of UDS ]

OBD-II (SAE J1979 / ISO 15031) is the standardised emissions-diagnostic
protocol every car sold since ~1996 supports. It rides on the same CAN-C
bus and the same $7E0 / $7E8 ECM arbitration-ID pair as UDS, but uses a
different service set:

    Service 0x01  ShowCurrentData       -- live sensor data by PID
    Service 0x02  ShowFreezeFrame       -- snapshot at last DTC
    Service 0x03  ShowDTCs              -- read stored fault codes
    Service 0x04  ClearDTCs             -- clear fault codes (WRITE)
    Service 0x05  TestResults_O2_NonCAN
    Service 0x06  TestResults           -- continuous monitor data
    Service 0x07  ShowPendingDTCs
    Service 0x08  Control               -- on-board system control
    Service 0x09  RequestVehicleInfo    -- VIN, calibration ID
    Service 0x0A  ShowPermanentDTCs

Positive-response convention is identical to UDS: service byte + 0x40.
Service 0x01 requests yield 0x41 responses with the PID echoed in the
next byte and data after. Mode 0x09 multi-frame VIN responses use the
same ISO-TP framing UDS uses.

Practical prerequisites for first-time Mode 01 queries

Two things commonly trip up first-time OBD-II Mode 01 queries on
the JEEP platform:

1. The vehicle must be powered on.  Accessory mode or
   Run/Start with the engine off will pass most queries.  With the
   ignition fully off, the ECM stops responding to $7E0 within a few
   seconds of the bus going quiet -- you'll see no response frame at
   all, not even a Negative Response Code.

2. Send on CAN-C, not CAN-IHS.  Live powertrain data
   only rides the CAN-C bus.  Sending the request frame on the
   CAN-IHS bus produces no response (the ECM isn't listening there).
   On the on-site Pi rig that's normally can1
   (CAN-C, 500 kbps) -- see the primer
   for the can0 vs can1
   naming gotcha.

The request always uses ID $7E0 (ECM functional
request); listen for the response on $7E8 (ECM
physical response).  Padding to 8 bytes is convention but not strictly
required -- can-utils pads automatically when you use cansend's
short-form syntax.

Worked example 1 — read PID 0x0C (Engine RPM) on the ECM:

    Request   can1 7E0 # 02 01 0C 00 00 00 00 00
               \  /   \  /  \/  \/
                |      |    |    |
                |      |    |    +-- PID echoed back in response
                |      |    +------- Service 0x01 ShowCurrentData
                |      +------------ PCI length = 2 (service + PID)
                +-------------------- ECM request ID

    Response  can1 7E8 # 04 41 0C 11 B8 00 00 00
                       \  /  \/  \/  \-+-/
                        |   |   |     |
                        |   |   |     +-- 2 data bytes: 0x11 0xB8
                        |   |   |         = 17, 184 decimal
                        |   |   +-------- PID echo
                        |   +------------ 0x41 = 0x01 + 0x40 (positive)
                        +---------------- PCI length = 4

    Formula (from the OBD-II spec): RPM = ((A * 256) + B) / 4
                                          ((17 * 256) + 184) / 4
                                        = 1134 RPM

Worked example 2 — read PID 0x05 (Engine coolant temperature) on the ECM. This one uses a single-byte data field with an offset-from-40 decode — the kind of thing that's easy to miss if you don't check the PID's decode rule in the spec:

    Request   can1 7E0 # 02 01 05 00 00 00 00 00
                                |
                                +-- PID 0x05 = engine coolant temp

    Response  can1 7E8 # 03 41 05 27 00 00 00 00
                                \/  \-+-/
                                |     |
                                |     +-- 1 data byte: 0x27 = 39 decimal
                                +-------- PID echo (0x05)

    Formula (from the OBD-II spec): coolant_C = A - 40
                                          39 - 40
                                        = -1 °C (about 30 °F — cold-start reading)

The A - 40 offset is shared by every temperature PID in the Mode 01 spec (0x05 coolant, 0x0F intake air, 0x46 ambient air, 0x5C engine oil). It maps the 8-bit unsigned range [0, 255] to the signed Celsius range [-40, +215]. At -40 you'd see the byte read 0; at +215 °C you'd see 0xFF. Anything in between follows the same subtraction.

OBD-II PIDs every modern car supports (selection):

    0x05  Engine coolant temperature   A - 40 = degC
    0x0C  Engine RPM                   ((A*256) + B) / 4
    0x0D  Vehicle speed                A = km/h
    0x0F  Intake air temperature       A - 40 = degC
    0x10  MAF air flow rate            ((A*256) + B) / 100 = g/s
    0x11  Throttle position            A * 100 / 255 = %
    0x42  Control module voltage       ((A*256) + B) / 1000 = V
    0x46  Ambient air temperature      A - 40 = degC
    0x5C  Engine oil temperature       A - 40 = degC

Full standardised list: https://en.wikipedia.org/wiki/OBD-II_PIDs

FCA / JEEP-specific PIDs that aren't in the public spec are documented
on the second tab of the JL
Wrangler RE spreadsheet.

When to reach for OBD-II vs UDS:

    OBD-II Mode 01:   read-only live data, standardised across
                      manufacturers, no session unlock needed, passes
                      SGW unauthenticated on 2018+ FCA. Most "what is
                      the engine doing right now" queries.

    UDS  Service 0x22: manufacturer-specific DIDs (battery V at the
                      HVAC module, configuration parameters,
                      calibration values not in the OBD-II spec).
                      Also read-only.

    UDS  Service 0x2F / 0x2E / 0x31: writes / actuator control / ECU
                      programming. Need session unlock, gated by SGW
                      on 2018+ FCA OBD-II ports.

The on-site script obd.sh demonstrates
the request/response flow for any user-provided PID. Two-stage retry,
single-frame vs first-frame PCI handling, decimal-byte output suitable
for piping into formula calculators.

[ DTC anatomy — what the 5-character code means ]

Every OBD-II Diagnostic Trouble Code (DTC) is 5 characters — one
letter + four hex digits.  Each position carries semantic meaning, so
you can usually tell what subsystem a code targets just by looking at
the string, before you ever consult a code-lookup table.

Position 1 -- domain (top 2 bits of byte 0 in the wire encoding):

    P    Powertrain      engine, transmission, ignition, fuel,
                         emissions
    B    Body            interior comfort -- HVAC, lighting, locks,
                         seats, doors
    C    Chassis         brakes, ABS, ESP, steering, suspension
    U    Network         class-2 / CAN / inter-module communication

Position 2 -- code source (bits 5-4 of byte 0):

    Px0xx   government-required code (SAE J2012 standardised)
    Px1xx   manufacturer-specific code (reported but not mandated)
    Px2xx   manufacturer-specific
    Px3xx   manufacturer-specific (rare)

    The standard / manufacturer split matters for write-up coverage:
    P0xxx codes are documented in the SAE spec and on public databases
    (obd-codes.com, repairpal.com).  P1xxx codes are FCA / Stellantis-
    specific and are documented in shop manuals or community spreadsheets
    rather than the public OBD-II reference.

Position 3 -- subsystem (powertrain, P-codes only):

    Px1xx    fuel and air metering
    Px2xx    fuel and air metering (injector circuits specifically)
    Px3xx    ignition system or misfire
    Px4xx    auxiliary emission control (EVAP, EGR, secondary air)
    Px5xx    vehicle speed control + idle control
    Px6xx    computer output circuit (PCM signals to actuators)
    Px7xx    transmission
    Px8xx    transmission (specific to control modules)
    Px9xx    transmission / control-module input + output signals

    Reading a code with no lookup table:
       P0301  P-domain, government code, ignition subsystem,
               fault 01 -- cylinder 1 misfire.
       P0420  P-domain, government code, auxiliary emission
               control, fault 20 -- catalyst efficiency below
               threshold (bank 1).
       P0171  P-domain, government code, fuel/air metering,
               fault 71 -- system too lean (bank 1).

Positions 4-5 -- fault number within the subsystem (00-FF):

    The specific issue.  Documented in the SAE J2012 reference for
    P0xxx codes; in the OEM service manual for P1xxx.

For chassis (C), body (B), and network (U) codes the third-position
subsystem map differs from the P-code one above.  C0xxx covers ABS /
ESP / steering; B0xxx covers airbags / SRS / occupant restraint;
U0xxx covers CAN bus loss-of-communication codes (e.g.
U0100 = lost comm with ECM, U0101
= lost comm with TCM).

For a 569-code JK Wrangler (2007-2018) catalog grouped by domain
and subsystem, see the
JK Wrangler DTC Code Reference.
Most P0xxx codes there transfer directly to JL / JT / Grand Cherokee
/ Ram (SAE J2012-standardised); B / C / U codes are JK-specific and
may differ on other FCA platforms.

[ DTC severity types — Type A vs Type B ]

OBD-II distinguishes two categories of emissions-related DTC, which
behave differently with respect to the malfunction-indicator lamp (MIL)
and freeze-frame data:

    Type A  (severe)
        - Emissions-related.
        - Illuminates the MIL after ONE failed driving cycle.
        - Stores a freeze frame snapshot after one failed cycle.

    Type B  (less severe / monitored)
        - Emissions-related.
        - Sets a pending DTC after one failed cycle.
        - Clears the pending DTC after one successful cycle.
        - Illuminates the MIL after TWO consecutive failed cycles.
        - Stores a freeze frame after two consecutive failed cycles.

This is why dtc.sh has separate
--mode confirmed (Service 0x03 -- MIL is on)
and --mode pending (Service 0x07 -- detected
once but not yet confirmed) flags.  A code present only in pending
suggests a Type B fault that's hit once but not twice in a row;
checking pending periodically can give you early warning before the
MIL actually comes on.

There's also Service 0x0A ShowPermanentDTCs — emissions codes
that the ECM is required (by regulation) to keep across a clear-codes
cycle until verified-fixed through completed drive monitors.  These
are the codes that re-appear immediately after a tool-driven clear if
the underlying fault is still present; they can't be hidden by a
visit to the parts store.

[ JEEP live-data message map (from pyJeepCan.py) ]

The pyJeepCan.py dashboard monitor
hard-codes the IDs, buses, and byte offsets below. Treat as a starting
catalog — verified against the script's target platform; YMMV elsewhere.

    ID       Bus       Field           Bytes   Decode
    -----    -------   -------------   -----   ----------------------------
    $02B     CAN-C     Roll            0-1     (hi<<8|lo) - 2048, /10
                       Tilt            2-3     same
                       Yaw             4-5     same
    $023     CAN-C     Steer Angle     0-1     (hi<<8|lo) - 0x1000
                       Steer Rate      2-3     same
    $093     CAN-C     Gear (PRNDL)    2       0x50=P 0x52=R 0x4E=N 0x44=D
                                               0x31..0x38 = "1".."8" (mt)
    $127     CAN-C     IAT             0       (byte - 40), °C→°F
                       Coolant temp    1       same
    $128     CAN-C     PS Temp         1       (byte * 9/5) + 32 → °F
                       PS PSI          2       byte * 4 * 0.145038
    $13D     CAN-C     Oil Pressure    2       byte * 4 * 0.145038
                       Oil Temp        3       (byte - 40), °C→°F
    $277     CAN-C     Transfer case   0       0x00=4x2H 0x10=4x4H 0x40=4x4L
                                               0x02/0x20=N 0x80=Shifting
    $2C2     CAN-IHS   Battery V       2       byte / 10  (volts) -- see $2C2 section
    $322     CAN-IHS   RPM             0-1     hi<<8 | lo  (0xFFFF = engine off)
                       Speed (MPH)     2-3     ((hi<<8|lo) / 200), 1 decimal

Offsets and constants in the table are the script's; bytes are 0-indexed
from the start of the 8-byte CAN payload. Bus assignments are the script's
hard-coded canIHS / canC labels (vcan0 / vcan1 in the test harness).

Additional IDs decoded by the
log_reader.sh replay tool but not
yet promoted to a full BMR section -- byte offsets and formulas live
in that script's source for now, candidates for full BMR coverage as
they get verified across more platforms:

    ID       Field                  Source bytes / formula notes
    -----    --------------------   ----------------------------
    $079     Brake pedal pressure   bytes 0 first 3 nibbles, /22.5 = %
    $07B     Accelerator pedal      bytes 0-2 scaled (pedal %), bytes
                                    6-8 alternate (throttle valve %)
    $340     Transmission gear      derived from chars 9-10, 19-20
                                    (PRNDL + speed)
    $3D2     Odometer               bytes 0-2 raw, * 50 / 8 / 100 = mi
    $0AB     Alternate gear         bytes 4-7, possibly DCT-related
                                    (not present on all transmissions)

[ Candidate IDs — useful next-additions (UNVERIFIED) ]

Common fields broadcast on FCA / Stellantis CAN that pyJeepCan.py does
NOT yet decode, with candidate message IDs from community RE work
(notably the JL Wrangler RE spreadsheet). Treat the
specific IDs as starting points — they vary by model year and
platform, sometimes drastically. Confirm with candump on your own
vehicle before wiring any of these into automation.

    Confidence: HIGH (commonly broadcast on every modern FCA platform,
                      IDs vary)
    -----------------------------------------------------------------
    Field              Likely Bus   Typical decode
    ---------------    ----------   --------------------------
    Fuel level %       CAN-IHS      byte / 2.55  (0..100%)
    Outside temp       CAN-IHS      (byte / 2) - 40  (deg C)
    Throttle position  CAN-C        byte / 2.55  (0..100%)
    Brake switch       CAN-IHS      single bit flag
    Cruise set speed   CAN-IHS      raw byte = MPH
    TPMS (4 tires)     CAN-IHS      byte / 4 per tire = PSI
    Odometer           CAN-IHS      32-bit big-endian, units 0.1 mi


    Confidence: MEDIUM (well-documented on JL Wrangler, may apply to
                        Grand Cherokee / Ram / Durango / similar)
    -----------------------------------------------------------------
    ID       Likely bus   Field                Notes
    $308     CAN-IHS      Fuel level           1 byte payload
    $3DA     CAN-IHS      Fuel level (alt)     alternate on some years
    $29C     CAN-IHS      Outside ambient      byte 0 or 1, deg C + offset
    $129     CAN-C        Throttle position    byte 2 or 3, near IAT msg
    $14A     CAN-C        Throttle (alt)       alternate on some years
    $371     CAN-IHS      TPMS pressures       4 bytes: FL FR RL RR
    $2D0     CAN-IHS      Cruise control       byte 0 = set spd, b3 flags
    $2A8     CAN-IHS      Door states          bit-packed, 1 = open
    $2FA     CAN-IHS      Window position      byte per window 0..255
    $214     CAN-IHS      Headlight / turn     bit flags in lighting msg
                                                (NOTE: $291 carries the
                                                 authoritative headlight
                                                 beam state -- see
                                                 #id-291 above. $214
                                                 may be switch position
                                                 vs $291's output state.)
    $2FB     CAN-IHS      Wiper state          bit flag in lighting msg
    $3F1     CAN-IHS      HVAC fan speed       byte 0..7
    $3F4     CAN-IHS      HVAC mode (alt)      alternate on some years
    $1F1     CAN-IHS      Seatbelts            bit flag per seat
    $3E0     CAN-IHS      Odometer             4 bytes BE, units 0.1 mi
    $416     CAN-IHS      Odometer (alt)       alternate on some years


    Wrangler / Gladiator off-road controls -- electronic sway bar
    + e-lockers (observed 05.2026 on JL Wrangler / JT Gladiator
    platforms with the e-disconnect sway bar and front/rear
    electronic lockers; all UNVERIFIED, not yet round-tripped
    against known-good triggers in every state combination)
    -----------------------------------------------------------------
    Sway bar (electronic disconnect):

      $26F  00 40 FF 16 44 7F 7F 7F   Sway Bar Request (button press)
                                       -- captured on JT during a
                                       sway-bar-button press. Toggles
                                       between lock/unlock states;
                                       direction is implicit (whatever
                                       state isn't current). Bytes 3-4
                                       (16 44) vary across captures
                                       and may carry button-event
                                       sequence info; the original
                                       capture showed FF FF in those
                                       slots. Still UNVERIFIED.

      $371  51 02 00 00 00 00 00 00   unlock in progress
      $371  70 01 00 00 00 00 00 00   unlocked
      $371  42 02 00 00 00 00 00 00   lock in progress
      $371  61 00 00 00 00 00 00 00   locked

      NOTE: $371 collides with the TPMS guess in the MEDIUM table
      above. On this Wrangler $371 carries sway bar state, NOT TPMS
      pressures. Same ID can mean different things across model years
      / platforms / option packages -- exhibit A for "always candump
      on YOUR own vehicle first." The TPMS row stays in the MEDIUM
      table because it's documented elsewhere on other platforms;
      both can be true on different vehicles.

    Lockers (electronic differential):

      Request ($25D) -- byte 0 nibble 0 looks like the command code:
                        2 = rear, 1 = front+rear, 4 = unlock both.

        $25D  23 FC 00 00 7F 7F 7F 7F   Rear locker request
        $25D  13 FC 00 00 7F 7F 7F 7F   Front + rear locker request
        $25D  43 FC 00 00 7F 7F 7F 7F   Unlock both request

      NOTE: $25D collides with a verified-on-a-different-platform decode
      where byte 3 carries radio mute state (see $25D
      radio mute above). Locker decode is JL-Wrangler-specific; mute
      decode is from a different FCA platform. Same ID, two semantics,
      two vehicles. Always candump YOUR vehicle before trusting either
      interpretation.

      Status ($2C2) -- earlier captures suggested byte 2 toggled
                       between 0x78 (unlocked) and 0x77 (locked)
                       during actuation:

        $2C2  14 B3 78 00              "Locker unlocked"   (0x78 = 12.0V)
        $2C2  14 B3 77 00              "Locker(s) locked"  (0x77 = 11.9V)

      RESOLVED: $2C2 byte 2 is BATTERY VOLTAGE, not locker state.
      See the dedicated $2C2 section above and
      battery.sh for a worked reader.
      The 0x78 -> 0x77 transition observed during locker actuation
      was the alternator/battery sagging under the locker motor's
      current draw (12.0V -> 11.9V is exactly the kind of dip you'd
      expect from a ~20A actuator pull). The real locker-state
      signal lives in some other ID we haven't spotted yet -- likely
      $277 (transfer-case message) per the "related" note below.

      For the locker actuator's wiring side (motor + driver, separate
      from the request packet), see AXLE-LOCKER-SYSTEM.pdf
      in the JL wiring-diagram pack -- useful when scope-probing the
      motor leads to correlate with the bus traffic above.

      Related, partially correlated:

        $277  changes during locker operation. Already in the
              live-data map as transfer case at byte 0, which is
              consistent -- the lockers sit mechanically downstream
              of the transfer case, so $277 carrying both t-case
              position AND locker state across different bytes would
              be the expected layout. Bytes beyond byte 0 not yet
              decoded.


    Confidence: LOW (observed in some captures, very platform/year
                     dependent — treat as research leads, not facts)
    -----------------------------------------------------------------
    - Charging voltage / alternator output (separate from battery V
      on some platforms, often in ID adjacent to $2C2)
    - Fuel injector pulse width / fuel rail pressure (diesel mostly)
    - MAF / MAP sensor (sometimes broadcast for dashboard gauges)
    - Wideband lambda / O2 (usually UDS-only, not broadcast)
    - Compass heading (derived from steering+accel+GPS, CAN-IHS)
    - GPS coordinates (Uconnect-equipped only, often encrypted)


How to discover unknown signals on YOUR vehicle:

    1.  candump -L can0,can1 any > baseline.log     # 30s idle
    2.  trigger the thing  (press brake, flip lights, open door)
    3.  candump -L can0,can1 any > triggered.log    # 30s during

    Diff the message IDs whose byte patterns changed in step 3 but
    not in step 1. The ID with newly-toggled bytes is your candidate.
    SavvyCAN's "signal hunter" view does this visually if you prefer
    a GUI over diff.

Brake pedal is the easiest first target — flip the pedal on/off
and watch which byte in a 100 ms-cadence message toggles 0/1.

[ JT (Gladiator) deltas vs JL (Wrangler) ]

The Gladiator (JT) shares most of its CAN architecture with the Wrangler
(JL) — same powertrain controllers, same diagnostic layout, same
$7E0/$7E8 ECM pair on CAN-C, same general SGW behavior on 2018+ MY. Most
JL-derived IDs and DIDs documented above transfer directly. A few
platform-specific deltas are worth knowing before treating JL captures as
authoritative on a JT.

Sway-bar control packet on $26F

The JT and JL both expose front-sway-bar disconnect / reconnect requests
on $26F, but the JT's button-press payload differs slightly. From
community captures on Rubicon trims:

    # JL Wrangler  — sway-bar request
    can1 26F # 00 40 FF FF FF 7F 7F 7F

    # JT Gladiator — sway-bar request (note byte 3 / 4)
    can1 26F # 00 40 FF 16 44 7F 7F 7F

Lock / unlock progress feedback on $371 (returned by the sway-bar
controller) appears consistent across both platforms. If you're porting a
JL sway-bar replay script to a JT, swap the $26F payload and leave the
$371 watcher unchanged.

Source: JL Wrangler Forums (community RE, not Stellantis-published).
For the actuator-side circuit (motor + driver, not the request packet),
see AUTOMATIC-SWAY-BAR.pdf in the
JL wiring-diagram pack.

Drivetrain / transfer-case differences

The JT broadcasts different gear-ratio values and a different transfer-
case state map on the 4WD / Rock-Trac / Command-Trac status messages
compared to the JL. Any PID or message-byte interpretation that involves
transfer-case state needs to be re-validated against the specific JT
build (Sport / Rubicon / Mojave / etc.) before being trusted — the
JL mapping will read but the value bytes mean something different.

Wiring side: TRANSFER-CASE.pdf in the
JL wiring-diagram pack
covers the t-case motor and control circuit on JL; most of it transfers
directly to the JT but the gear-ratio reporting differs at the broadcast
level, not the wiring level.

Bed / tailgate / utility (JT-only IDs)

Anything related to the pickup bed — tailgate latch state, cargo-bed
lighting, bed power outlet status — is JT-only and will not appear
in any JL-derived capture or DBC. These IDs are still being mapped by
the community; expect partial coverage on the
JL/JT RE spreadsheet
and a fair amount of UNKNOWN entries.

Standard OBD-II Mode 01 PIDs — confirmed on JT

For the universal Mode 01 PIDs queried via $7E0 -> $7E8, the JT supports
the expected set. Community-confirmed working PIDs (in addition to the
9 documented in the OBD-II section above):

    0x1F  Run time since engine start  ((A*256) + B) seconds
    0x21  Distance with MIL on         ((A*256) + B) km
    0x2F  Fuel tank level              A * 100 / 255 = %
    0x43  Absolute load value          ((A*256) + B) * 100 / 255 = %
    0x44  Commanded equivalence ratio  ((A*256) + B) * 2 / 65535
    0xA6  Odometer                     ((A*256*256*256) + ...) * 0.1 km

A sample obd.sh sweep on a community
member's Gladiator returned values for Vehicle Speed, Timing Advance,
Intake Air Temperature, Throttle Position, Run Time, Distance With MIL
On, Fuel Tank Level, Catalyst Temp Bank 1 Sensor 1, Control Module
Voltage, Absolute Load, Ambient Air Temp, Engine Fuel Rate, Reference
Torque, and Odometer — all of which work the same on the JT as on
the JL since they share the powertrain controller.

Source: JL Wrangler Forums sample sweeps.

Useful UDS DIDs on the JT

Stellantis-specific data not exposed via standard OBD-II Mode 01 is
reachable on the JT through UDS service $22 (ReadDataByIdentifier) against
individual ECUs, same as on the JL. The
read_vin_uds.py helper and the
worked example in the UDS section above
apply unchanged.

Particularly useful DIDs (mostly BCM, addressed at $720 / $728 unless
noted):

    $F190  VIN                          17-byte ASCII (multi-frame)
    $F18C  ECU serial number            ASCII
    $F195  System supplier ECU sw ver   ASCII
    $0146  CSM1 sensor data             8 bytes  (see $0146 decode above)
    $0147  CSM2 sensor data             8 bytes
    $014E  CSM3 sensor data             8 bytes
    $A01A  Oil-change statistics        6 bytes  (see $A01A decode above)
    $D0AD  Horn I/O control             write target via service $2F

Read-only DID query via service $22 (ReadDataByIdentifier) using the
on-site rid helper (read-data-by-identifier):

    # Read the VIN from the BCM (multi-frame ASCII response)
    rid bcm f190

    # Read the ECU serial number
    rid bcm f18c

rid wraps the $22 request, ISO-TP single / first-frame / consecutive-
frame reassembly, and pretty-prints the response payload — the
bcm argument resolves to the BCM's request /
response pair ($720 / $728) from the module-ID catalog, and the trailing
hex is the 2-byte DID.

Writes work the same way via service $2F (IOControlByIdentifier) using
the ioid helper:

    # Sound the horn (BCM, DID $D0AD, controlState 0x03, controlOption 0x01)
    ioid -e bcm D0AD 0301

    # Stop the horn (controlState 0x03, controlOption 0x00)
    ioid -e bcm D0AD 0300

ioid wraps the $2F frame, ISO-TP framing, and session-unlock dance so
you don't have to hand-roll the cansend payload — -e bcm
resolves to the same BCM request / response pair, and the trailing hex
is appended verbatim as the controlState + controlOption bytes the
IOControlByIdentifier service expects.

The pair-up is symmetrical: rid reads, ioid turns
things on. wid covers persistent writes via
service $2E (WriteDataByIdentifier) when you need a value to stick across
ignition cycles instead of being a momentary I/O actuation.

The full DID catalog (BCM + RFH + IPC + others) is on the
JL/JT RE spreadsheet
— coverage is good but not complete; expect partial breakdowns on
some entries.

Source: Jeep Gladiator Forum, JL/JT RE spreadsheet.

Caveats specific to the JT

1.  Model year matters even within the JT.  A 2020 Gladiator and a 2024
    Gladiator do not have identical CAN behavior — over-the-air and
    dealer software updates have shifted message timing and payload
    layouts. The CAN-bus format is reportedly shifting again on
    Wranglers / Gladiators with uConnect 5 and the digital dashboard,
    so 2024+ JT documentation may diverge from anything captured on
    earlier JT MYs.

2.  SGW affects what you can do at the OBD-II port.  Reading PIDs and
    DIDs generally works through the SGW unauthenticated. Writing
    (service $2F IOControl, $11 ECUReset, $2E WriteDataByIdentifier, $31
    RoutineControl) requires either AutoAuth, an SGW bypass cable, or
    direct connection to CAN-C / CAN-IHS behind the SGW via the 13-way
    star connectors behind the glove box. See the
    Secure Gateway Module guide
    for the bypass-cable build.

3.  Everything in this section is community reverse-engineering, not an
    official Stellantis DBC. Coverage is partial, some byte-level
    interpretations remain "best guess," and Stellantis has not
    published a JT DBC at any point. Validate on your own vehicle
    before automating anything destructive.

[ Caveats — before you trust any of these IDs ]

A few things to keep in mind before committing to an interpretation of any
FCA message ID, $1C0 included:

1.  Model / year variance is huge.  FCA has reassigned IDs between platforms.
    CAN-IHS messages on a 2008 Durango and a 2010 Wrangler are completely
    different, even when CAN-C content is mostly consistent. So $1C0 on a
    2014 Grand Cherokee may not mean what it means on a 2020 Wrangler JL.
    Always confirm on your own vehicle before automating anything destructive.

2.  Payload structure is not fully documented.  Which byte represents
    lock vs unlock vs trunk vs panic, which carries the key fob ID, which
    holds the button-hold flag — most of that is community reverse-engineering
    work (notably the JL Wrangler RE spreadsheet maintained by the
    forum community), not a published spec.

3.  None of this is an official FCA / Stellantis specification.  Stellantis
    does not publish their DBC files. Everything here is community RE.
    The authoritative reference for any given vehicle would be FCA wiTECH
    service information or a purchased / leaked DBC for that exact platform.

When in doubt: log first, act later. candump on a stationary vehicle, replay
fob actions one at a time, and confirm the ID and payload behavior on YOUR
platform before wiring any of it into automation.

[ See also ]