[ 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.
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.
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.
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.
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-eflag 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-canbustype=withinterface=, 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.
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.
Standalone driver for UDS Service 0x11 (ECUReset). The same reset action wid.py issues as a side-effect of its-rflag, 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 (duplicatetcmkey,$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.
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.
