[ UDS Write Operations on FCA / Stellantis ]

UDS (Unified Diagnostic Services, ISO 14229) write operations on FCA / Stellantis vehicles have real-world consequences. The same protocol that lets you blink the 3rd brake light for a demo can leave your horn stuck on, your immobilizer confused, or an ECU bricked into needing a dealer reflash. Reading via Service 0x22 or OBD-II Mode 01 is safe and cheap; writing via Service 0x2F, 0x2E, 0x31, or 0x27 is not. This page is the shared scaffolding that the actuator-control scripts on the site sit on top of, with the safety discipline brought to the front instead of buried in each script's docstring.

[ Scripts implementing this pattern ]

Three scripts on this site write to FCA ECUs via UDS, all following the scaffolding documented in the sections below: NM wake → Extended Diagnostic Session → service-specific request → cleanup. They live here so you can browse the patterns and the implementations on the same page.

3rd_brakelight.sh — 05.2026
Demo of UDS Service 0x2F (IOControlByIdentifier) on a JEEP platform —
toggles the cargo-area / 3rd brake light on and off via the request sequence
walked through in the Bus & Message
Reference: NM wake → Extended Diagnostic Session → IOControl ON
→ IOControl OFF, plus a background TesterPresent keepalive so the ECU's
S3 timer doesn't drop the session and a SIGINT trap that issues
returnControlToECU on exit (never leaves the brake light in test-controlled
state).

UDS rides on CAN-C alongside the broadcast traffic but uses an entirely
different framing — every request gets a positive or negative response
from the target ECU, and writes (clearing DTCs — Diagnostic Trouble
Codes, the standardised fault codes like P0420 or U0101 that ECUs store
when something goes wrong — forcing actuators, programming) are
what 2018+ FCA vehicles gate behind the Secure
Gateway Module. Direct CAN access via the 13-way connectors behind the
glovebox bypasses the SGW; UDS over the OBD-II port does not.

CLI: --once (single toggle for testing), --verbose (echoes every cansend),
--help. Python alternative for serious UDS work:
udsoncan.
horn.sh / 3honk.sh — 05.2026
Two implementations of the same UDS-driven horn honk. Both target the
BCM ($620 / $504) and use Service 0x2F on DID $D0AD; byte-level
walkthrough at Bus & Message
Reference. The difference is which can-utils tool generates the
frames:

    horn.sh    cansend with hand-padded 8-byte CAN frames. Adds CLI
               flags (--press DUR_MS, --burst N, --tap-ms MS,
               --gap-ms MS, --verbose, --help), a SIGINT trap that
               silences the horn + issues returnControlToECU on exit,
               and a background TesterPresent keepalive so the ECU's
               S3 timer doesn't drop the session mid-honk.

    3honk.sh   isotpsend, which prepends the PCI length byte and pads
               the frame to 8 bytes for you. Hardcoded three quick
               taps, no CLI.

When to pick which:

  - For a one-byte actuator command, the two are functionally
    equivalent. Use horn.sh if you want the press/burst CLI surface
    and the safety scaffolding.

  - Once a request grows past 7 UDS bytes — WriteDataByIdentifier
    with multi-byte values, SecurityAccess seed/key exchange,
    ReadDTCInformation streams — isotpsend handles multi-frame
    ISO-TP framing. cansend can't, and you'd have to write the FF + CF
    + Flow-Control state machine yourself.

  - When adding a SIGINT trap or other safety wrapping around an
    actuator with real-world side effects, horn.sh's cansend version
    is easier to start from (every frame is explicit; you can see
    what's on the wire).

Don't run either at 2am in a dense neighbourhood. Both originals by
jmccorm; horn.sh polished, 3honk.sh preserved closer to its minimal
form.
2k.sh — 05.2026
Holds engine RPM at 2000 via UDS Service 0x31 (RoutineControl) on the
Engine Control Module ($7E0 / $7E8). Press Ctrl+C to release. Differences
from the horn / brake-light demos:

  - Target ECM uses OBD-II-style 11-bit IDs ($7E0 / $7E8), not the
    FCA-internal $620 / $504 BCM pair.
  - Session sub-function 0x92 (Chrysler manufacturer-specific), not
    0x03 (extended).
  - Service 0x31 (RoutineControl, startRoutine 0x07D0) instead of
    0x2F (IOControlByIdentifier).

There's no explicit cancel command. The script holds the session open
by sending TesterPresent every 0.25s; once Ctrl+C stops that, the
ECM's S3 timer expires the session and the RPM hold is released on
its own.

The routine identifier 0x07D0 = 2000 decimal looks suspiciously like
the target RPM encoded directly. Worth testing whether
31 05 05 DC (0x05DC = 1500) sets 1500 RPM; not yet
confirmed.

Safety: engine running, park brake set, transmission
in PARK, not in a closed garage. Hit Ctrl+C if anything sounds wrong.
"Lightly tested" by jmccorm — worked for cold-morning warm-up,
failure modes not explored. Original by jmccorm; preserved with
header annotations by magikh0e.
ioid.py — 05.2026
Generic UDS Service 0x2F (IOControlByIdentifier) driver. Drives any
DID against any known module from three command-line arguments —
the same machinery that horn.sh,
3rd_brakelight.sh, and
3honk.sh implement per-DID, generalised
into one tool. Use it to drive any of the
40+ BCM IOControl DIDs catalogued below
without writing a new shell script per target.

Usage:

    ioid [-e] MODULE IDENTIFIER DATA

Examples (the canonical "blare the horn" sequence):

    ioid -e bcm D0AD 0301        # blare
    ioid -e bcm D0AD 0300        # stop blaring

The two-byte DATA argument follows the standard Service 0x2F
short-term-adjustment convention: 03 = control mode
shortTermAdjustment, 01 = state ON, 00 =
state OFF. Some DIDs accept richer state encodings (wiper speed at
$D1AA may take more than two values, for example).

The -e flag opens an Extended Diagnostic Session
(Service 0x10 subfunction 0x03) before the IOControl request. Most
BCM writes need an extended session; back-to-back invocations within
the ECU's S3 timeout (~5 s) keep the session open without
re-issuing -e, but using it every time is safe.

Known modules: bcm ($620 / $504 on CAN-C),
hvac ($783 / $503 on CAN-IHS),
radio ($7BF / $53F on CAN-IHS). BCM is the only
one with verified write targets so far; hvac / radio entries are
kept for exploration. Note this script does NOT issue a
returnControlToECU on exit — it relies on session timeout to
release control automatically. For "blare then stop blaring" pairs
that's fine; for prolonged actuator holds, follow up with an
explicit OFF state from a second invocation.

Originally by jmccorm (03.2023). Polish by magikh0e: replaced
deprecated python-can bustype= with
interface=, dropped a redundant dict-comprehension
that built a new dict just to do a containment check on already-
lowercase keys, and pre-resolved MODULE_INFO ints at load-time.
wid.py — 05.2026
The persistent-write counterpart to ioid.py. Uses Service 0x2E
(WriteDataByIdentifier) to write directly to ECU NVRAM — changes
PERSIST across battery disconnect, ignition cycles, and session
timeout. This is the sharpest tool on the page; burning a bad
value can leave a module needing a dealer reflash to recover.

Usage:

    wid [-d] [-y] [-r] MODULE IDENTIFIER DATA

The -y argument is REQUIRED to actually run. Without it the
script prints a polite reminder and exits non-zero. Treat this as
intentional: this service has no rollback, and the safety guard
makes "I'll just retype the last command" require a deliberate
re-affirmation.

The round-trip safety pattern that jmccorm demonstrated against
BCM DID $0146 on a real vehicle:

    rid bcm 0146                                  # capture original
        00 30 76 41 E4 A3 C7 10
    wid -r -y -d bcm 0146 4030F741E4A3C793        # write modified value
    rid bcm 0146                                  # verify it took
        40 30 F7 41 E4 A3 C7 93
    wid -r -y -d bcm 0146 00307641E4A3C710        # restore original
    rid bcm 0146                                  # verify restore
        00 30 76 41 E4 A3 C7 10

The -r flag issues an ECU hardReset (UDS Service 0x11
subfunction 0x01) after a successful write. Many BCM configuration
changes don't visibly take effect until the ECU restarts; use -r
for changes you want to observe immediately, omit it for quietly
applied changes you'd rather not announce.

The "Tazer problem": the original version of this
script accepted EITHER a positive response (0x6E / 0x51) OR a
negative response (0x7F XX) and returned on whichever came first.
That worked on a clean bus but failed intermittently on rigs with
a Tazer device on the same bus — the Tazer's TesterPresent
chatter would generate negative-response noise that the original
code matched, causing false-positive failures. The polished
version matches POSITIVE responses only; negative responses fall
through to the timeout path so the script waits for the actual
write confirmation. jmccorm's fix.

Known modules (13 total via the MODULE_INFO dict at the top of the
script): bcm, rf, ipcm/evic, airbag, shift, tccm, pcm, scm, hvac,
cscm, radio, ecm, tcm. Most are exploration entries; BCM is the
verified target with the 40+ DID catalog below. Debug logging via
-d writes byte-level traffic to
/home/pi/modules/log.txt.

Companion read tool: jmccorm uses a parallel rid
script (Service 0x22 ReadDataByIdentifier) for the
read-modify-verify-restore pattern shown above. Not yet on this
site as a standalone tool, but
read_vin_uds.py demonstrates
the same Service 0x22 read against a different DID and serves as a
starting template for any module.

Originally by jmccorm (03.2023). Polish by magikh0e: fixed the
duplicate tcm key (Transfer Case and Transmission both
used "tcm" — second assignment silently overwrote the first;
Transfer Case renamed to tccm); corrected the
$7E0/$7E8 "unknown" annotation to ecm
(verified elsewhere on this site by 2k.sh); replaced deprecated
bustype= with interface=; and pre-resolved
MODULE_INFO ints.
ecureset.py — 05.2026
Standalone driver for UDS Service 0x11 (ECUReset).  The same reset
action wid.py issues as a side-effect of
its -r flag, but exposed as its own tool so you can
reboot a module without writing anything to it first — useful for
clearing wedged states, forcing a fresh DTC scan, testing module
startup behaviour, or recovering a module that's stuck in an
extended diagnostic session.

Usage:

    ecureset [-d] [-s | -p] MODULE
    ecureset -m                    # list known module names

Reset type (ISO 14229 subfunction codes):

    default      0x01  hardReset                       (cold-boot equivalent)
    -s, --soft   0x03  softReset                       (graceful, module's choice)
    -p, --power  0x04  enableRapidPowerShutDown        (housekeeping + power cycle)

ISO 14229 also defines 0x02 keyOffOnReset, 0x05
disableRapidPowerShutDown, and a manufacturer-specific range.  This
script exposes the three you'd realistically want in a session;
extend MODULE_INFO and the argparse group to add others.

Completes jmccorm's UDS driver trio on this page:

    ioid.py       Service 0x2F    short-term actuator control
    wid.py        Service 0x2E    persistent NVRAM writes
    ecureset.py   Service 0x11    ECU restart

All three share the same MODULE_INFO catalog, the same ISO-TP
scaffolding, and the same log destination
(/home/pi/modules/log.txt).

Originally by jmccorm (2023). Polish by magikh0e: same set of fixes
as wid.py (duplicate tcm key, $7E0/$7E8
"unknown" -> ecm, bustype= ->
interface=, pre-resolved ints), plus documenting the
ISO 14229 subfunction-name mapping so the reader knows -p maps to
enableRapidPowerShutDown (subfunction 0x04) rather than a custom
code.

[ Reads vs writes — why they're different ]

The UDS protocol treats reads and writes symmetrically at the wire level. Both are request/response, both use the same module ID pairs, both follow the +0x40 positive-response rule. But the operational distinction is significant:

Reads (Service 0x22 ReadDataByIdentifier, OBD-II Mode 01)
    - Idempotent: no state change on the ECU
    - Safe to retry, safe to spam, safe to script naively
    - Pass through the SGW unauthenticated on 2018+ FCA
    - Worst case: you get NRC 0x12 (subFunctionNotSupported) or
      NRC 0x31 (requestOutOfRange) for a DID the ECU doesn't know
    - Bench-testing not strictly required

Writes (Services 0x2F, 0x2E, 0x31, 0x27)
    - State-changing: the ECU does something different after
    - Some writes persist across battery disconnect (0x2E to NVRAM)
    - Most are gated by the SGW on 2018+ FCA OBD-II access
    - Cleanup is YOUR responsibility -- if the script dies mid-write,
      the ECU may stay in test-controlled state
    - Worst case: bricked module, stuck immobilizer, ABS fault that
      needs a dealer reflash to clear
    - Bench-testing on a salvage ECU is the responsible default
      when stakes are real

Everything below is about the write side. For the read side, see the dedicated UDS Read Operations guide (Service 0x22, module catalog, DID discovery, SGW pass-through) and the OBD-II section of the Bus & Message Reference for the standardised-read counterpart.

[ The shared pattern ]

Every UDS-write script on the site ( 3rd_brakelight.sh, horn.sh / 3honk.sh, 2k.sh) follows the same four-step scaffolding. The DID changes, the service might change, the cleanup model varies, but the spine stays the same:

1. WAKE the bus (optional, only needed if you can't guarantee the target ECU is already awake):

cansend $WAKE_BUS  2D3#0700000000000000
sleep 0.1

2. ENTER an extended diagnostic session on the target ECU:

cansend $UDS_BUS  $REQ_ID#02 10 SS 00 00 00 00 00
# SS = 0x03 extended, 0x92 Chrysler-specific, etc.

3. WRITE — the service-specific request. Pick from Service 0x2F (IOControlByIdentifier, toggle an actuator), 0x2E (WriteDataByIdentifier, persist a value), 0x31 (RoutineControl, start/stop a routine), or 0x27 (SecurityAccess, seed/key, if gated).

4. CLEANUP — either explicit (toggle-and-release) or implicit (let the session time out), depending on which write you used. See the cleanup-patterns section below.

The optional fifth piece is a background TesterPresent keepalive (Service 0x3E) that prevents the ECU's S3 timer from dropping the session during long writes. Needed for any write that holds state for more than a few seconds; not needed for fast toggles.

Every script on the site does (1) + (2), and a different combination of (3) and (4) depending on what it's trying to accomplish.

[ Service overview — when to reach for which ]

  Service 0x2F  IOControlByIdentifier
  -----------------------------------
    Short-term actuator takeover. The ECU forces the addressed I/O
    to a specified state for the duration of the session, then
    reverts. Use for: horn, brake light, lights, any actuator where
    you want point-in-time control. Frame layout:

        02 2F  HH LL  CC  SS                  request
              \__DID__/  ctrl state
                          0x03 = shortTermAdjustment
                          0x00 = returnControlToECU (releases takeover)
                          0x01..0xFF = additional control flavours

    Demo scripts: 3rd_brakelight.sh,
    horn.sh,
    3honk.sh


  Service 0x2E  WriteDataByIdentifier
  -----------------------------------
    Persistent write to ECU NVRAM. The new value survives ignition
    cycles, battery disconnect, and session termination. Use for:
    permanent vehicle configuration changes -- regional settings,
    comfort options, calibration bytes. Frame layout:

        XX 2E  HH LL  D0 D1 D2 ...             request
              \__DID__/  \__data bytes__/

    Where things get dangerous: 0x2E does NOT roll back if you write
    a value the ECU later rejects. Always 0x22-read the DID first to
    capture the original, then 0x2E-write your change, then 0x22-read
    the result to verify. If the read-back doesn't match, write the
    original back IMMEDIATELY before the session times out.

    No on-site demo script for this yet. Bench-testing strongly
    recommended.


  Service 0x31  RoutineControl
  -----------------------------------
    Start, stop, or query a routine running on the ECU. The routine
    runs as long as the diagnostic session stays open; ending the
    session ends the routine. Use for: continuous-behaviour writes
    like RPM hold, automated tests, brake-bleed routines, key-learn
    sequences. Three subfunctions:

        02 31 01  HH LL                        requestResults
        02 31 02  HH LL                        stopRoutine
        02 31 05  HH LL                        startRoutine
                 \__routine ID__/

    On Chrysler, the routine identifier sometimes encodes the
    target value directly (2k.sh uses RID 0x07D0 = 2000 decimal =
    target RPM; whether 0x05DC = 1500 RPM is still untested).

    Demo script: 2k.sh


  Service 0x27  SecurityAccess
  -----------------------------------
    Seed/key unlock for ECUs that gate write services behind
    cryptographic challenge-response. The ECU returns a seed value;
    the tester computes a key from the seed using a vendor-specific
    algorithm; the tester sends the key back; the ECU unlocks the
    deeper write services for the rest of the session.

        02 27 SS                                requestSeed
              sub: 0x01, 0x03, 0x05... (level)
        N  27 SS  SS SS SS SS ...               sendKey

    Most consumer-grade actuator writes on FCA we've seen do NOT
    require 0x27 unlock; the SGW handles their access control
    instead. ECU programming, immobilizer-related operations, and
    some key-learn flows DO require 0x27 with manufacturer-specific
    seed/key algorithms. Out of scope for this site.


  Service 0x11  ECUReset
  -----------------------------------
    Reboot a target module.  Not a "write" in the strict sense
    (no state is being committed via this service), but covered
    here because it pairs with the writes above -- many BCM
    configuration changes via 0x2E don't visibly take effect until
    the ECU restarts.  Subfunctions:

        02 11 01                                hardReset
        02 11 02                                keyOffOnReset
        02 11 03                                softReset
        02 11 04                                enableRapidPowerShutDown
        02 11 05                                disableRapidPowerShutDown

    Positive response: 51 SS (subfunction echo).

    Demo script: ecureset.py
    exposes 0x01 (default), 0x03 (-s), and 0x04 (-p).
    wid.py issues 0x11 sub 0x01
    internally when -r is passed.

[ Cleanup patterns ]

Two distinct cleanup models on the site. They're not interchangeable — the model you choose has to match what the write actually does.

TOGGLE-AND-RELEASE. Used by 3rd_brakelight.sh, horn.sh, 3honk.sh. Service 0x2F IOControlByIdentifier. The script issues an explicit OFF (state byte 0x00) and a returnControlToECU (control byte 0x00) at exit, including from a SIGINT trap. If the script crashes mid-toggle, the ECU is left in test-controlled state until something else writes the DID — bad for a stuck-on horn, bad for a brake light at noon. Sketch:

trap cleanup INT TERM
cleanup() {
    # Explicit OFF
    cansend $BUS  $REQ#052F${DID}03000000
    # returnControlToECU
    cansend $BUS  $REQ#042F${DID}00000000
    exit 0
}

enter_session
iocontrol_on   # state=0x01
sleep 0.5
iocontrol_off  # state=0x00
cleanup

HOLD-VIA-TESTERPRESENT. Used by 2k.sh. Service 0x31 RoutineControl. No explicit cancel command. The script holds the session open by sending TesterPresent every ~0.25s. The moment Ctrl+C kills that loop, the ECU's S3 timer expires the session and the routine releases automatically. Elegant when it fits — no possibility of "script died, ECU stuck" because the protocol handles cleanup. Sketch:

enter_session
start_routine
while true; do
    sleep 0.25
    cansend $BUS  $REQ#023E800000000000   # TesterPresent
done
# Ctrl+C kills the loop. S3 timer fires. Routine releases.

WHEN TO PICK WHICH. Reach for toggle-and-release for point-in-time changes (single action, then revert immediately); 0x2F IOControl flows; anything where the ECU needs an explicit "go back to normal" message. Reach for hold-via-TesterPresent for continuous-behaviour writes (the action IS the duration); 0x31 RoutineControl flows; anything where session-timeout naturally means "stop".

[ Known writable targets on JEEP platforms ]

Verified write targets on the JEEP-platform vehicles this site is built around. All require direct CAN access via the behind-the-glovebox connectors on 2018+ vehicles; OBD-II-port access is gated by the SGW.

With on-site demo scripts. These are the targets a script in Scripts implementing this pattern above exercises end-to-end:

  Target       Service  Module             Effect                Demo script
  -----------  -------  ----------------   --------------------- ------------------------
  $D1B3        0x2F     BCM ($620/$504)    3rd brake light       3rd_brakelight.sh
                                            on/off (short-term)
  $D0AD        0x2F     BCM ($620/$504)    Horn on/off           horn.sh / 3honk.sh
                                            (short-term)
  RID 0x07D0   0x31     ECM ($7E0/$7E8)    Engine RPM hold       2k.sh
                                            at 2000 RPM
                                            (manuf session 0x92)

BCM IOControl DID catalog (JL Wrangler / JT Gladiator platform). Community-collected list of $620/$504 BCM identifiers that respond to Service 0x2F (IOControlByIdentifier) short-term-adjustment requests. Each one drives a specific lamp, actuator, or lock circuit directly from a UDS session — bypassing the normal switch / fob / stalk path. No on-site demo script yet for most of these; the scaffold from 3rd_brakelight.sh or horn.sh retargets with one DID change.

  EXTERIOR LIGHTING -- TURN SIGNALS
    $D014    Front Left  Turn Lamp
    $D015    Front Right Turn Lamp
    $D0A2    Back  Left  Turn Signal
    $D0A3    Back  Right Turn Signal
    $D0AE    Back  Left  Turn Lamp           (separate from $D0A2 -- bulb vs signal?)
    $D0AF    Back  Right Turn Lamp           (separate from $D0A3 -- bulb vs signal?)

  EXTERIOR LIGHTING -- HEAD / FOG / DRL
    $D0A0    Front Left  Fog Lamps
    $D0A1    Front Right Fog Lamps
    $D0A8    Left  Low-beam
    $D0A9    Right Low-beam
    $D0AA    Left  High-beam
    $D0AB    Right High-beam
    $D1B8    Left  Daylight (DRL) Lamp
    $D1B9    Right Daylight (DRL) Lamp
    $F1A3    Left  Signature Lamp            (JL trim-level dependent)
    $F1A4    Right Signature Lamp            (JL trim-level dependent)

  EXTERIOR LIGHTING -- MARKER / PARK / REVERSE
    $D0A4    Left  Marker Lights
    $D0A5    Right Marker Lights
    $D1A4    Front Left  Park Lamp
    $D1A5    Front Right Park Lamp
    $D1A6    Back  Parking Lamp              (one of two -- L vs R unclear)
    $D1A7    Back  Parking Lamp              (one of two -- L vs R unclear)
    $D1A8    Left  Reverse Lamp
    $D1A9    Right Reverse Lamp
    $D1B2    License Plate Lamp

  EXTERIOR LIGHTING -- BRAKE
    $D1B3    CHMSL (centre high-mount stop lamp)   <- 3rd_brakelight.sh

  INTERIOR LIGHTING
    $D1BE    Courtesy Lamp
    $D1BF    Reading Lamp
    $D1C1    Foot Well
    $F1A6    Gladiator Bed Lamp              (Gladiator-only, JT trim)

  WIPERS / WASHERS
    $D0AC    Rear Wiper
    $D0C0    Washer Sprayer
    $D1AA    Front Wiper (speed)
    $D1AB    Front Wiper (on/off)
    $D200    Front Off-Road Camera Washer    (trim-level dependent)

  LOCKS
    $D0CE    All Door Locks
    $D1BC    Unlock Driver Door
    $D1BD    Unlock Driver and Passenger Doors
    $F1A5    Swing Gate Lock                 (Wrangler swing-out tailgate)

  AUDIBLE
    $D0AD    Horn                            <- horn.sh / 3honk.sh
             Remember to honk AND unhonk -- the script issues an
             explicit OFF + returnControlToECU so the relay doesn't
             stay closed after the session times out.

The lock DIDs are worth a second look from a security standpoint — $D1BC and $D1BD give you UDS-controlled door unlock at the BCM, which is a different attack surface than $1C0 RKE-broadcast unlock. If you're inside the SGW boundary (behind-the-glovebox CAN access), no authentication is required.

Candidate targets (UNVERIFIED). Other BCM / non-BCM identifiers and command IDs gleaned from community RE work but not yet confirmed on this site:

  $26F      Sway bar disconnect request        (see BMR #candidate-ids)
  $25D      Front/rear locker request          (see BMR #candidate-ids)
  Various   Mirror fold / unfold
  Various   Trailer brake controller routines
  Various   Configuration DIDs (door count, region, options)

The candidate targets above are documented in the BMR candidate-IDs section. The hardest part of building a new UDS-write script is usually NOT the scaffolding (which is identical across all of them) — it's confirming the right DID / RID / control byte combination for the action you want, on the specific platform you have. Sniff first with a known-good UDS tool (JScan, wiTECH, Autel) using the JScan UDS intro technique, then script.

Configuration DIDs (Service 0x2E, persistent writes). The DIDs above are all IOControl (Service 0x2F) targets — they toggle a circuit for the duration of the session and revert when the session ends. A separate category of writes uses Service 0x2E (WriteDataByIdentifier) to persistently modify BCM configuration NVRAM — door count, region, sold-options bitmaps, etc. These persist across battery disconnects.

The driver for these is wid.py in Scripts implementing this pattern above. One confirmed BCM Service 0x2E DID from jmccorm's testing:

  $0146  CSM1 -- BCM configurable security data (8 bytes)
         Documented at BMR Service 0x22 read walkthrough
         as the example Read DID; jmccorm has verified the
         round-trip write-modify-restore cycle against this DID
         using wid.py.  Exact field semantics inside the 8 bytes
         not yet decoded.

Other candidate DIDs for JL Wrangler configuration writes — not yet verified on the site:

  ???    BCM door-count configuration
         Updating this so the BCM thinks no doors are installed
         would re-enable Remote Start with the doors removed,
         without the sense-voltage hacks at the door switches.
         The DID number itself isn't yet known -- discover via
         JScan or wiTECH's configuration menu, sniff what they
         emit on 0x2E, then replicate.

  ???    BCM region / market configuration
         Affects DRL behaviour, speedometer unit selection,
         language defaults, etc.

  ???    BCM sold-options bitmap
         Per-vehicle option flags (heated seats, fog lights,
         remote start equipped, etc.)

Service 0x2E does NOT auto-rollback — if you write a value the ECU later refuses, the bad value stays committed. wid.py exposes this discipline via its -y required confirmation and the recommended rid → wid → rid (verify) → wid (restore if needed) round-trip flow. See the Service overview above for the 0x2E gotchas and the Safety section for the discipline that keeps this from bricking a module.

[ SGW interaction ]

On 2018+ FCA / Stellantis vehicles the Secure Gateway Module gates all UDS writes that arrive via the OBD-II port. The distinction matters per-script:

  OBD-II port access (the diagnostic connector under the dash):

      Allowed unauthenticated:  Mode 01 reads, Service 0x22 reads
                                of specific DID ranges, basic
                                identification queries
      Blocked unauthenticated:  Service 0x2F (IOControl),
                                Service 0x2E (WriteDataByIdentifier),
                                Service 0x31 (RoutineControl),
                                Service 0x04 (ClearDTCs),
                                Service 0x27 (SecurityAccess flows
                                that touch ECU programming)

      Result on blocked writes: NRC 0x33 (securityAccessDenied)
                                or the request silently drops with
                                no response at all.

      Workaround: AutoAuth subscription + certified scan tool.
                  Per-session token; can't be scripted from a Pi.

  Behind-the-glovebox 13-way connectors:

      Allowed unauthenticated:  EVERYTHING. These connectors sit
                                INSIDE the SGW boundary; traffic
                                sourced here reaches the internal
                                CAN-C and CAN-IHS buses directly
                                without the SGW seeing it.
      Required:                 Physical access (~10 minutes of
                                glovebox disassembly); a CAN
                                interface that talks SocketCAN
                                (Pi + Waveshare 2-channel HAT,
                                or any USB-CAN adapter).

      This is why all the UDS-write scripts on this site assume
      direct CAN access. The 3rd_brakelight / horn / 2k scripts
      all use can0 / can1 named interfaces wired to the
      behind-glovebox tap, not the OBD-II port.

Reads (Service 0x22, Mode 01) work through the OBD-II port unchanged — that's why obd.sh and getVIN.sh don't have this restriction. The split is reads vs writes, not what kind of cable you have.

[ Safety ]

  RULES THAT APPLY TO EVERY UDS WRITE
  ===================================

  1. Bench-test before vehicle-test. A salvage ECU on a workbench
     is much cheaper than a dealer reflash.

  2. Always 0x22-read before 0x2E-write. Capture the original
     value so you can write it back if the change doesn't behave.

  3. Send returnControlToECU on exit for every 0x2F write.
     Trap SIGINT, write it in the exit path, write it AGAIN at
     normal exit. Better to send it twice than to send it zero
     times.

  4. Use Service 0x31 only when the action is naturally continuous.
     RoutineControl works because the protocol handles cleanup;
     forcing 0x31 patterns onto point-in-time actions just hides
     the cleanup problem behind a TesterPresent loop.

  5. Don't poke at unknown DIDs blindly. The candidate-IDs section
     of the BMR is full of "this looks like it might be X" entries.
     Validate with a known-good UDS tool's reaction BEFORE you
     script anything.

  6. Engine-RPM writes (2k.sh and similar) need transmission
     in PARK, parking brake set, ventilation. The script can't
     enforce any of this; it can only refuse to run if something
     is obviously wrong, and it doesn't even do that yet.

  7. If you see NRC 0x33 (securityAccessDenied) and you're going
     through the OBD-II port, you need direct CAN access -- not
     a longer timeout, not more retries. NRC 0x33 means "the
     SGW said no."

  8. If you see NRC 0x12 (subFunctionNotSupported) or 0x7F (unknown
     service), the target ECU genuinely doesn't speak that service
     or sub-function. Try a different session sub (0x03 vs 0x92)
     or a different module.

  9. WriteDataByIdentifier (0x2E) to immobilizer / key-learn /
     security DIDs is in a separate risk tier from actuator writes.
     Wrong values here brick the immobilizer; brick the immobilizer
     and the vehicle won't start until you pay a locksmith. We don't
     have demo scripts for that category on this site, and won't.

[ See also ]