Sponsored
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
I wanted to add...

The causal reader who sees that this service can turn things off and on and goes through the list of things that it can control might jump to the conclusion that this is the kind of thing that the Tazer does when it does it's Light Show.

I wanted to confirm that this, in fact, is exactly what the Tazer is doing. It uses the I/O Control by Identifier service to turn off and on these very things as part of it's Light Show. It's sending out I/O Control by Identifier commands to the vehicle's BCM.

If someone wanted a light show that was in some way more intricate or complex (or responds to particular events, such as a door opening, or someone sitting down in the front passenger seat), such a thing should be more than possible to accomplish.

I/O Control by Identifier
Allows you to turn things off and on with your Wrangler or Gladiator.

BCM $D014 Front Left Turn Lamp​
BCM $D015 Front Right Turn Lamp​
BCM $D0A0 Front Left Fog Lamps​
BCM $D0A1 Front Right Fog Lamps​
BCM $D0A2 Back Left Turn Signal​
BCM $D0A3 Back Right Turn Signal​
BCM $D0A4 Left Marker Lights​
BCM $D0A5 Right Marker Lights​
BCM $D0A8 Left Low-beam​
BCM $D0A9 Right Low-beam​
BCM $D0AA Left High-beam​
BCM $D0AB Right High-beam​
BCM $D0AE Back Left Turn Lamp​
BCM $D0AF Back Rear Turn Lamp​
BCM $D1A4 Front Left Park Lamp​
BCM $D1A5 Front Right Park Lamp​
BCM $D1A6 Back Parking Lamp​
BCM $D1A7 Back Parking Lamp​
BCM $D1A8 Left Reverse Lamp​
BCM $D1A9 Right Reverse Lamp​
BCM $D1B2 License Plate Lamp​
BCM $D1B8 Left Daylight Lamp​
BCM $D1B9 Right Daylight Lamp​
Sponsored

 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Additional note on the RID and IOID scripts:

I've yet to hear any feedback from someone else who's tried these. But I wanted to make note that if you're missing the iso-tp package (or you're getting Python errors about isotp), you may need to install the package directly from the source.

If so, here are the steps for that:
cd python-can-isotp​
sudo pip3 install .

Hope you all have a chance this weekend to try these out.
Let me know!
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Getting Close to Write Data by Identifier (WID)

I'm pretty sure we've discussed this before, but I was giving myself a refresher before finally trying to implement it this weekend.

Just now, when updating a Data Identifier on the BCM with JSCAN, I saw the following:

can1 620 [8] [SF] ln: 2 data: 3E 00 00 00 00 00 00 - [SRQ] TesterPresent
can1 504 [8] [SF] ln: 2 data: 7E 00 AD 03 00 C8 30 - [PSR] TesterPresent
can1 620 [8] [SF] ln: 2 data: 10 03 00 00 00 00 00 - [SRQ] DiagSessionControl
can1 504 [8] [SF] ln: 6 data: 50 03 00 14 00 C8 30 - [PSR] DiagSessionControl
can1 620 [8] [SF] ln: 3 data: 22 01 22 00 00 00 00 - [SRQ] ReadDataByIdentifier
can1 504 [8] [FF] ln: 11 data: 62 01 22 54 31 A2 - [PSR] ReadDataByIdentifier
can1 620 [8] [FC] FC: 0 = CTS # BS: 0 = off # STmin: 0x00 = 0 ms
can1 504 [8] [CF] sn: 1 data: 5B 4C 0E 51 B8 31 A2
can1 620 [8] [SF] ln: 3 data: 22 01 22 00 00 00 00 - [SRQ] ReadDataByIdentifier
can1 504 [8] [FF] ln: 11 data: 62 01 22 54 31 A2 - [PSR] ReadDataByIdentifier
can1 620 [8] [FC] FC: 0 = CTS # BS: 0 = off # STmin: 0x00 = 0 ms
can1 504 [8] [CF] sn: 1 data: 5B 4C 0E 51 B8 31 A2
can1 620 [8] [SF] ln: 2 data: 10 03 00 00 00 00 00 - [SRQ] DiagSessionControl
can1 504 [8] [SF] ln: 6 data: 50 03 00 14 00 C8 A2 - [PSR] DiagcSessionControl
can1 620 [8] [FF] ln: 11 data: 2E 01 22 54 31 A2 - [SRQ] WriteDataByIdentifier
can1 504 [8] [FC] FC: 0 = CTS # BS: 8 # STmin: 0x14 = 20 ms
can1 620 [8] [CF] sn: 1 data: 5B B4 0E 51 B8 00 00


The sequence of events started with a check:
  1. The tool sends a "Tester Present" code
  2. The tool enters a diagnostic session
  3. The tool reads the data
Then when it came to update the data:
  1. The tool reads the data
  2. The tool enters a diagnostic session
  3. The tool writes the data
Based on everything I'm seeing, I don't think "Tester Present" is actually required for the update. I also think that the new "ioid" (I/O by Identifier) tool could quickly and easily be repurposed to do a quick-and-dirty Update Data by Identifier.

Here's a similar session were we use our new "ioid" tool to honk the horn:

can1 620 [8] [SF] ln: 2 data: 10 03 00 00 00 00 00 - [SRQ] DiagcSessionControl
can1 504 [8] [SF] ln: 6 data: 50 03 00 14 00 C8 30 - [PSR] DiagSessionControl
can1 620 [8] [SF] ln: 5 data: 2F D0 AD 03 01 00 00 - [SRQ] IOControlByIdentifier
can1 504 [8] [SF] ln: 4 data: 6F D0 AD 03 00 C8 30 - [PSR] IOControlByIdentifier
can1 620 [8] [SF] ln: 5 data: 2F D0 AD 03 00 00 00 - [SRQ] IOControlByIdentifier
can1 504 [8] [SF] ln: 4 data: 6F D0 AD 03 00 C8 30 - [PSR] IOControlByIdentifier


I'm shooting for a working example this weekend. After that, we might want to figure out the best way we want to do these (like to read the data and store it somewhere before updating it) so that we can (usually) revert things back in case things goes wrong.

I think we're inches away from that big milestone we've been shooting for, for some time. Here's hoping we've laid enough groundwork to make this as easy as it looks like it's going to be!

PS: Yes, I'll use my own vehicle as the guinea pig here. If I blow it, likely worst case should be that I'll need a new BCM installed. 😂
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Getting Close to Write Data by Identifier (WID)

I'm pretty sure we've discussed this before, but I was giving myself a refresher before finally trying to implement it this weekend.
That didn't take long.
I told ChatGPT the following five things:
  1. Instead of restricting the data argument to a size of two bytes, allow the argument to be anywhere from 1 to 65535 bytes long.
  2. Instead of an InputOutputbyIdentifier, modify the script to perform a WriteDatabyIdentifier.
  3. Using the function that enters an extended diagnostic session as a template, allow a new optional argument of -r to be called to perform an ECU reset.
  4. If send_ecu_reset should be called, it needs to be called after send_write_data_by_identifier has a successful response.
  5. Add an optional -y argument, and when it is not specified, the program should exit immediately saying, "You don't seem very sure. Use the -y argument when you're ready." [This is to prevent accidents, given how similar some of these names (rid, wid, ioid) and arguments are.]
The results looked fantastic!

From there, I manually adjusted some of the timeout values by hand to make them more liberal. The code is ready to test, but at the very least, I'm going to hold off until tomorrow to test it when I'm fresh.

If anyone has any ideas of a BCM (or other ECU) value somewhere that I can change that doesn't blow anything up, and that results in some change that could be visibly confirmed, I am absolutely open to suggestions here.

It's going to be critical to have a good test.
Right now, I don't have one!
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Okay, it's morning and I'm getting things together for the first Write Data by Identifier experiment. I modified some of the timings by hand to make them longer (waiting more time until there's a timeout error). Then I used ChatGPT to make three more changes:
  1. "Please log (in human-readable format) all commands (with a valid syntax) to the file "/home/pi/modules/log.txt". Before doing so, it should check to see if the directory /home/pi/modules exists, and if it does not, it should create it and check that the directory was created successfully before continuing."
  2. "Always request an extended diagnostic session and remove it as an optional argument. Show me the specific code differences that result."
  3. Add an optional "-d" debug argument that changes the logging.basicConfig level from logging.WARNING to logging.DEBUG
So it'll always enter an extended diagnostic session (no command line argument needed), and it'll log everything it does to "/home/pi/modules/log.txt". Log entries should look like this:

Code:
2023-03-18 05:29:47,748 WID MODULE: bcm, ID: 0xA051, DATA: 800D03, RESET: True
2023-03-18 05:29:48,260 FAILED TO ENTER EXTENDED DIAGNOSTIC SESSION
If you run into problems and you use the "-d" argument for debug level output, the logs are more intense and tell a lot more about what's going on:


Code:
2023-03-18 09:20:15,957 WID MODULE: bcm, ID: 0xA051, DATA: 800D03, RESET: True
2023-03-18 09:20:15,959 can config: {'can_filters': [{'can_id': 1284, 'can_mask': 2047}], 'channel': 'can1', 'interface': 'socketcan'}
2023-03-18 09:20:15,969 Created a socket
2023-03-18 09:20:15,969 Binding socket to channel=can1
2023-03-18 09:20:15,969 Bound socket.
2023-03-18 09:20:15,970 Sending : <620> (8)      b'0210030000000000'
2023-03-18 09:20:15,970 We've been asked to write a message to the bus
2023-03-18 09:20:15,970 sending: Timestamp:        0.000000        ID: 0620    S Rx                DL:  8    02 10 03 00 00 00 00 00
2023-03-18 09:20:16,970 FAILED TO ENTER EXTENDED DIAGNOSTIC SESSION
In other news, I did some research and found what I'm going to try for my initial example. It is at data identifier $A051. It's only three bytes long. The highest bit of the last byte contains a flag that determines if you have dynamic grid lines in your rear view camera or not. The rest of the bits have to do with the AUX switches.

On my vehicle, the contents of $A051 are: 0x800D83
The update I intent to make is: 0x800D03

Unless anyone sees any problems, that's what I'm going to try to do: alter the BCM's configuration data so that I remove the grid lines from my rear view camera.
 

Sponsored

OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
I guess I'm going to liveblog this.

I wasn't paying attention and my latest values for $A051 weren't what they were when I checked earlier. I must have played with the AUX switches since then...
rid bcm 0xA051
00 3C 83

Not paying attention, I go ahead and apply the update. (That shouldn't harm anything, though.) But I got a weird response...

wid -r -y bcm 0xA051 0x800D03
NO SUCH IDENTIFIER

Best as I can understand, the ECU gave me a result code that said that $A051 wasn't a valid identifier. So I go back and double check the contents of $A051, just to make sure nothing had changed...
rid bcm 0xA051
80 0D 03

OH! IT DID CHANGE! IT CHANGED EXACTLY WHAT WE SET IT TO! SUCCESS! Kinda...

Since the code terminated with "NO SUCH IDENTIFIER", there was no need to perform an ECU reset. So it didn't happen. I climb into the vehicle to see if the rear view camera guidelines have changed...

Jeep Wrangler JL JEEP HACKING CAN-C / CAN-IHS / UDS ! (Reverse Engineering) IMG_2053


Nope! The guidelines are still there. (Assuming that's what the parameter I changed actually does.) I'm going to see if shutting down the vehicle and leaving it alone for 20 minutes will be enough for it to take hold.

More updates as they happen. But... SUCCESS!
We managed to change BCM data!
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
UPDATE:

I coded a quick ECU reset routine. I ran it. It didn't have any immediate effect on the rear view camera guidelines. I'm letting the vehicle sleep for another 15 minutes. It may be that this change does absolutely nothing at all? Last thing I'll try is a battery cable pull.

I wish I had a better test case. I'm open to suggestions.

The wid script needs a bit more refinement and I need to make sure the ECU reset function works before I put the source code out here.

Thoughts, anyone? And perhaps I should be doing the ECU reset against the RADIO instead of the BCM?
 

Obi Wan

Well-Known Member
First Name
Obi
Joined
May 14, 2022
Threads
15
Messages
316
Reaction score
1,306
Location
Tatooine, Outer Rim
Website
starwars.fandom.com
Vehicle(s)
2023 JLU SPORT S 2.0, 2004 TJ SPORT 5.3LS
Occupation
Jedi Knight
@jmccorm, I just discovered this thread. As a mechanical engineer / avid electrical, electronic and computer enthusiast, you're inspiring me beyond words. The work and effort you've put into this is amazing to say the least. Most threads of this nature die in a few weeks, but here you are all this time later.

My JL may be getting upgrades in the future.
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
The Write Data by Identifier script WORKS!
(Ungracefully.)


Working with location $A051, which holds the configuration of the AUX switches, I set them up so that AUX3 was latched to IGNITION. When I read the variable, I got this...

rid.py bcm a051
00 1D 43

My vehicle was on and the engine running. I overwrote $A051 with a snapshot of that location that I took when AUX3 was latched to BATTERY...

wid -r -y -d bcm 0xA051 0x800D83
Extended diagnostic session established.
Response: 7F 2E 78
SUCCESS
Response: 6E A0 51
Failed to perform ECU reset.

I don't know if it was necessary or not, but I fiddled around a bit with trying to do a BCM reset a few times. Then I shut the vehicle off. AUX3 was now latched to battery. So the Write Data by Identifier to $A051 works!

Two issues were identified...
  1. Stupid tricks with AUX switches redux. I restored $A051 back to it's original value (latched to ignition). But like the AUX commands that redracer documented sometime back, it seems that you can go from IGNITION to BATTERY, but if you're running on BATTERY and you switch the configuration over to IGNITION, it doesn't hold. (And in my case, it kept running on battery.) But I'm going to mark this down as a BCM (mis)feature and otherwise a validation that the Write Data by Identifer script works.

  2. Foreign ISO-TP messages causing hiccups. Stray (uncaptured messages, unanticipated messages, or messages from other devices like the Tazer) ISO-TP messages seem to be what's throwing off the WID script.

    In the above example, when it sent an Extended Diagnostic Session request, what it got back was an acknowledge of a Tester Present message! And when it sent an ECU Reset request, what it got back was an acknowledgement of the Write Data by Identifier request, which was one step previous to what's going on.

    I suspect (have not yet proven) that my Tazer is seeing ISO-TP responses, and is replying with a Tester Present message. The Tazer gets it's response, but it also disrupts what my own code is looking for.
So I'm going to have to put some more smarts into the code where we wait for a response from the module and go with the first response we get. Instead, it should be waiting through the timeout period only for the message which applies to the current request, ignoring all others.

For now, I'm going to put A BETA VERSION of the wid script here. It looks like it does the updates just fine, but the ECU resets may or may not be happening, and it's on-screen output (and error codes) could use some improvement.

Python:
#!/usr/bin/python3

CODE REMOVED
SEE SUBSEQUENT POSTS FOR A NEWER VERSION.
Use 100% at your own peril. This is a sharp tool.
(That means that it could be dangerous, or exceedingly useful.)
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
The Write Data by Identifier script WORKS!
BUILT-IN HELP PAGE:
usage: wid [-h] [-d] [-y] [-r] module_name identifier data​
positional arguments:
module_name Module name​
identifier Identifier (hexadecimal, with or without '0x' prefix)​
data Data (hexadecimal, with or without '0x' prefix)​
optional arguments:
-h, --help show this help message and exit​
-d, --debug Enable debug logging​
-y, --yes Confirm that you want to proceed with the operation​
-r, --reset Perform ECU reset​

I've fixed the problem with the ECU resets not happening. The new version of the code ignores the Tazer when it gets jealous and starts tattling with "Tester Present" to the ECU.

You must make your own decision if you're going to try out this code or not, but it's at the point where I'm comfortable enough using it on my own vehicle for manual changes by hand.

Your feedback is important, especially if you encounter any error messages. The "-d" debug option is appreciated, as it documents byte-level traffic into "/home/pi/modules/log.txt".

I'm off to make some brand new discoveries with this tool. Wish me luck!

Python:
#!/usr/bin/python3
import os
import logging
import can
import isotp
import time
import sys
import argparse

# Write Data by Identifier (wid) by Josh McCormick for
# the Jeep Wrangler JL and Jeep Gladiator JT community.
# Written with assistance by ChatGPT 4.0!

def parse_hexadecimal(hexadecimal):
    if hexadecimal.startswith("0x") or hexadecimal.startswith("0X"):
        hexadecimal = hexadecimal[2:]
    try:
        return int(hexadecimal, 16)
    except ValueError:
        print(f"Invalid hexadecimal value: {hexadecimal}")
        sys.exit(1)

parser = argparse.ArgumentParser()
parser.add_argument("module_name", help="Module name")
parser.add_argument("identifier", help="Identifier (hexadecimal, with or without '0x' prefix)")
parser.add_argument("data", help="Data (hexadecimal, with or without '0x' prefix)")
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug logging")
parser.add_argument("-y", "--yes", action="store_true", help="Confirm that you want to proceed with the operation")
parser.add_argument("-r", "--reset", action="store_true", help="Perform ECU reset")
args = parser.parse_args()

if not args.yes:
    print("You don't seem very sure. Use the -y argument when you're ready.")
    sys.exit(1)

module = args.module_name.lower()
identifier = parse_hexadecimal(args.identifier)

data = args.data

if data.startswith("0x") or data.startswith("0X"):
    data = data[2:]

data_len = len(data)

if len(hex(identifier)[2:]) > 4:
    print("Identifier should be a two-byte hexadecimal number.")
    sys.exit(1)

data_len = len(data)
if data_len < 2 or data_len % 2 != 0 or data_len > 65535 * 2:
    print("Data should be a hexadecimal string with a length between 1 and 65535 bytes.")
    sys.exit(1)

data_bytes = bytearray()
for i in range(0, data_len, 2):
    byte_str = data[i:i+2]
    try:
        data_bytes.append(parse_hexadecimal(byte_str))
    except ValueError:
        print(f"Invalid data byte: {byte_str}")
        sys.exit(1)

def ensure_directory_exists(directory):
    if not os.path.exists(directory):
        try:
            os.makedirs(directory)
        except OSError as e:
            print(f"Error creating directory {directory}: {e}")
            sys.exit(1)

def setup_logging():
    log_directory = "/home/pi/modules"
    ensure_directory_exists(log_directory)
    log_file = os.path.join(log_directory, "log.txt")
    log_level = logging.DEBUG if args.debug else logging.WARNING
    logging.basicConfig(filename=log_file, level=log_level, format="%(asctime)s %(message)s")

def log_command(module_name, identifier, data, reset):
    command = f"WID MODULE: {module_name}, ID: {identifier}, DATA: {data}, RESET: {reset}"
    logging.warning(command)

def send_request(stack, request, expected_responses, timeout):
    stack.send(request)
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()
        response = stack.recv()
        if response is not None:
            for expected_response in expected_responses:
                if response[:len(expected_response)] == expected_response:
                    return response
            response = None
    return response

def send_extended_diagnostic_session(stack):
    request = bytearray([0x10, 0x03])
    expected_responses = [
        bytearray([0x50, 0x03]),
        bytearray([0x7F, 0x10])
    ]
    return send_request(stack, request, expected_responses, 2)

def send_ecu_reset(stack):
    request = bytearray([0x11, 0x01])
#    expected_responses = [
#        bytearray([0x51, 0x01]),
#        bytearray([0x7F, 0x11])
#    ]
    expected_responses = [
        bytearray([0x51, 0x01])
    ]
    return send_request(stack, request, expected_responses, 10)

def send_write_data_by_identifier(stack, identifier, data_bytes):
    request = bytearray([0x2E, (identifier >> 8) & 0xFF, identifier & 0xFF]) + data_bytes

#    expected_responses = [
#        bytearray([0x6E, (identifier >> 8) & 0xFF, identifier & 0xFF]),
#        bytearray([0x7F, 0x2E])
#    ]

    expected_responses = [
        bytearray([0x6E, (identifier >> 8) & 0xFF, identifier & 0xFF])
    ]

    # Timeout starts at 5 seconds.
    # Starting at 100 bytes of data, timeout becomes 10 seconds.
    # Starting at 200 bytes of data, timeout becomes 20 seconds. And so on.
    timeout = 5 + 5 * (len(data_bytes) // 100)
    return send_request(stack, request, expected_responses, timeout)

MODULE_INFO = {
    "bcm": ("620", "504", "can1"),   # BODY CONTROL MODULE
    "rf": ("740", "4C0", "can1"),    # RF HUB (UNCONFIRMED)
    "ipcm": ("742", "4C2", "can1"),  # INSTRUMENT PANEL CLUSTER MODULE
    "evic": ("742", "4C2", "can1"),  # EVIC is an alias for IPCM
    "airbag": ("744", "4C4", "can1"),   # OCCUPANT RESTRAINT / AIRBAG MODULE
    "shift": ("749", "4C9", "can1"), # ELECTRONIC SHIFTER MODULE
    "tcm": ("74B", "4CB", "can1"), # TRANSFER CASE MODULE
    "pcm": ("75A", "4DA", "can1"), # POWERTRAIN CONTROL MODULE
    "scm": ("763", "4E3", "can1"),  # STEERING COLUMN MODULE
    "hvac": ("783", "503", "can0"),  # HEATING/VENT/AIR-CONDITIONING MODULE
    # "unknown2": ("792", "512", "can1"),  # UNKNOWN
    "cscm": ("7BC", "53C", "can0"),  # INTEGRATED CENTERE STACK CONTROL  MODULE
    # "unknown3": ("7BE", "53E", "can1"),  # UNKNOWN
    "radio": ("7BF", "53F", "can0"), # UCONNECT RADIO MODULE
    "unknown2": ("7e0", "7e8", "can1"),   # UNKNOWN MODULE
    "tcm": ("7e1", "7e9", "can1"),   # TRANSMISSION CONTROL MODULE
}

if module in {k.lower(): v for k, v in MODULE_INFO.items()}:
    txid, rxid, can_channel = MODULE_INFO[module]
    txid = int(txid, 16)
    rxid = int(rxid, 16)
else:
    print("No such module found. Please provide a valid module name.")
    print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(1)

setup_logging()
log_command(module, args.identifier, data, args.reset)

can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)
params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
stack = isotp.CanStack(bus=bus, address=address, params=params)

response = send_extended_diagnostic_session(stack)

if response is None:
    print ("No response received from ECU.")
    logging.warning("NO RESPONSE FROM ECU")
    sys.exit(1)
else:
    if response[0] != 0x50:
        print("Response code: ",response)
        print("Failed to enter extended diagnostic session.")
        logging.warning("FAILED TO ENTER EXTENDED DIAGNOSTIC SESSION")
        sys.exit(1)
    else:
        print("Extended diagnostic session established.")
        logging.warning("EXTENDED DIAGNOSTIC SESSION ESTABLISHED")

response = send_write_data_by_identifier(stack, identifier, data_bytes)
print ("SEND WRITE DATA BY IDENTIFIER")

if response is not None:
    if response[0] != 0x6E:
        print("Unexpected response:",' '.join(f'{byte:02X}' for byte in response))
        print("UNEXPECTED RESPONSE")
        logging.warning("UNEXPECTED RESPONSE ")
        sys.exit(6)
    # if response[0] == 0x7F and response[1] == ((identifier >> 8) & 0xFF) and response[2] == (identifier & 0xFF):
    if response[0] == 0x6E:
        print("SUCCESS")
        logging.warning("SUCCESS")
        if args.reset:
            reset_response = send_ecu_reset(stack)
            if reset_response is None or reset_response[0] != 0x51:
                print("Response:",' '.join(f'{byte:02X}' for byte in reset_response))
                print("Failed to perform ECU reset.")
                logging.warning("FAILED TO PERFORM ECU RESET")
                sys.exit(1)
            else:
                print("ECU reset successful.")
                logging.warning ("ECU RESET SUCCESSFUL")
        sys.exit(0)
    else:
        print("Unexpected response: ",response)
        print("Unknown problem writing data to identifier.")
        print("UNKNOWN PROBLEM WRITING DATA TO IDENTIFIER")
        sys.exit(1)
else:
    print("No response received before timing out.")
    logging.warning("TIMEOUT WHILE WRITING DATA TO IDENTIFIER")
    exit(5)
DISCLAIMER:
No guarantee or warranty provided. Use at your own risk. This is a sharp tool which can be highly useful, or dangerous, in part due to who's using it.​

EXAMPLE (real-world):
# rid bcm 0146​
00 30 76 41 E4 A3 C7 10​
# wid -r -y -d bcm 0146 4030F741E4A3C793​
Extended diagnostic session established.​
SEND WRITE DATA BY IDENTIFIER​
SUCCESS​
ECU reset successful.​
# rid bcm 0146​
40 30 F7 41 E4 A3 C7 93​
# wid -r -y -d bcm 0146 00307641E4A3C710​
Extended diagnostic session established.​
SEND WRITE DATA BY IDENTIFIER​
SUCCESS​
ECU reset successful.​
# rid bcm 0146​
00 30 76 41 E4 A3 C7 10​

I was able to read a data identifier on the BCM, update that data identifier, verify that the update was made successfully, change it back to it's original value, and then verify that change.

That's exactly the behavior you'd want to see.

UPDATE (MARCH 26TH, 2023):
If you are getting errors when you run the code, you might try updating your python-can module with "pip install -U python-can". I also recommend I direct install of the latest version of the ISO-TP module as follows:

Bash:
git clone https://github.com/pylessard/python-can-isotp.git
cd python-can-isotp
sudo pip3 install .
With those two packages updated and installed, you shouldn't have any problem running this code. If you do, please let us know.
 
Last edited:

Sponsored

OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
ECU Reset Script
Send a soft or hard reset to an ECU.

HELP PAGE:

usage:
ecureset [-h] [-d] [-s] [-p] [-m] [module]

positional arguments:
module_name Module name

optional arguments:
-h, --help show this help message and exit
-d, --debug Enable debug logging
-s, --soft Perform a soft ECU reset
-p, --power Perform a rapid power cycle reset
-m, --modules Display a list of module names

SOURCE CODE:

Python:
#!/usr/bin/python3
import os
import logging
import can
import isotp
import time
import sys
import argparse

MODULE_INFO = {
    "bcm": ("620", "504", "can1"),   # BODY CONTROL MODULE
    "rf": ("740", "4C0", "can1"),    # RF HUB (UNCONFIRMED)
    "ipcm": ("742", "4C2", "can1"),  # INSTRUMENT PANEL CLUSTER MODULE
    "evic": ("742", "4C2", "can1"),  # EVIC is an alias for IPCM
    "airbag": ("744", "4C4", "can1"),   # OCCUPANT RESTRAINT / AIRBAG MODULE
    "shift": ("749", "4C9", "can1"), # ELECTRONIC SHIFTER MODULE
    "tcm": ("74B", "4CB", "can1"), # TRANSFER CASE MODULE
    "pcm": ("75A", "4DA", "can1"), # POWERTRAIN CONTROL MODULE
    "scm": ("763", "4E3", "can1"),  # STEERING COLUMN MODULE
    "hvac": ("783", "503", "can0"),  # HEATING/VENT/AIR-CONDITIONING MODULE
    # "unknown2": ("792", "512", "can1"),  # UNKNOWN
    "cscm": ("7BC", "53C", "can0"),  # INTEGRATED CENTERE STACK CONTROL  MODULE
    # "unknown3": ("7BE", "53E", "can1"),  # UNKNOWN
    "radio": ("7BF", "53F", "can0"), # UCONNECT RADIO MODULE
    "unknown2": ("7e0", "7e8", "can1"),   # UNKNOWN MODULE
    "tcm": ("7e1", "7e9", "can1"),   # TRANSMISSION CONTROL MODULE
}

def parse_hexadecimal(hexadecimal):
    if hexadecimal.startswith("0x") or hexadecimal.startswith("0X"):
        hexadecimal = hexadecimal[2:]
    try:
        return int(hexadecimal, 16)
    except ValueError:
        print(f"Invalid hexadecimal value: {hexadecimal}")
        sys.exit(1)

parser = argparse.ArgumentParser()
parser.add_argument("module_name", help="Module name", nargs='?')
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug logging")
reset_group = parser.add_mutually_exclusive_group()
reset_group.add_argument("-s", "--soft", action="store_true", help="Perform a soft ECU reset")
reset_group.add_argument("-p", "--power", action="store_true", help="Perform a rapid power cycle reset")
parser.add_argument("-m", "--modules", action="store_true", help="Display a list of module names")
args = parser.parse_args()

if args.modules:
    print("Available modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(0)

if not args.module_name:
    print("Error: Module name is required. Use -h for help.")
    sys.exit(1)

module = args.module_name.lower()

def ensure_directory_exists(directory):
    if not os.path.exists(directory):
        try:
            os.makedirs(directory)
        except OSError as e:
            print(f"Error creating directory {directory}: {e}")
            sys.exit(1)
def setup_logging():
    log_directory = "/home/pi/modules"
    ensure_directory_exists(log_directory)
    log_file = os.path.join(log_directory, "log.txt")
    log_level = logging.DEBUG if args.debug else logging.WARNING
    logging.basicConfig(filename=log_file, level=log_level, format="%(asctime)s %(message)s")

def log_command(module_name):
    command = f"ECURESET MODULE: {module_name}"
    logging.warning(command)

def send_request(stack, request, expected_responses, timeout):
    stack.send(request)
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()
        response = stack.recv()
        if response is not None:
            for expected_response in expected_responses:
                if response[:len(expected_response)] == expected_response:
                    return response
            response = None
    return response

def send_ecu_reset(stack, reset_type):
    request = bytearray([0x11, reset_type])
    expected_responses = [
        bytearray([0x51, reset_type])
    ]
    return send_request(stack, request, expected_responses, 5)

if module in {k.lower(): v for k, v in MODULE_INFO.items()}:
    txid, rxid, can_channel = MODULE_INFO[module]
    txid = int(txid, 16)
    rxid = int(rxid, 16)
else:
    print("No such module found. Please provide a valid module name.")
    print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(1)

setup_logging()
log_command(module)
can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)
params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
stack = isotp.CanStack(bus=bus, address=address, params=params)

reset_type = 0x04 if args.power else 0x03 if args.soft else 0x01
reset_response = send_ecu_reset(stack, reset_type)

if reset_response is None or reset_response[0] != 0x51 or reset_response[1] != reset_type:
    print("Failed to perform ECU reset.")
    logging.info("FAILED TO PERFORM ECU RESET")
    sys.exit(1)
print("ECU RESET SUCCESSFUL")
logging.warning("SUCCESS")
EDIT: Added a "-p" option which performs a power cycle. Is mutually exclusive with "-s" for a soft reset.
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Over in the Gladiator Forum, a user Jimmy07 identified exactly what some of our configuration messages are in the CAN spreadsheet. 15 of them, in fact! I'm in the process of putting them in the spreadsheet AND associating these with their matching Read Data by Identifier locations in the BCM.

UPDATE: Added to the spreadsheet.
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Latest versions of the new UDS scripts:

"rid" READ DATA BY IDENTIFIER
Python:
#!/usr/bin/python3

import can
import isotp
import time
import argparse

# Read Data by Identifier
# Written by Josh McCormick with the help of ChatGPT
# for the Wrangler JL and Gladiator JT community.

def send_tester_is_present(can_channel, rxid, txid, timeout):
    # Set up the ISOTP connection with padding configuration
    can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
    bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
    address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)

    # Set up the ISOTP connection parameters
    params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
    stack = isotp.CanStack(bus=bus, address=address, params=params)

    # Send the UDS Request Data by Identifier message with padding
    uds_tester_present = bytearray([0x3E, 0x00])
    stack.send(uds_tester_present)

    # Wait for a response
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()  # Process any received messages
        response = stack.recv()
        if response is not None:
            break
    bus.shutdown()
    return response
#    return None

def send_request_data_by_identifier(can_channel, rxid, txid, identifier,timeout):
    # Set up the ISOTP connection with padding configuration
    can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
    bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
    address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)

    # Set up the ISOTP connection parameters
    params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
    stack = isotp.CanStack(bus=bus, address=address, params=params)

    # Send the UDS Request Data by Identifier message with padding
#    uds_tester_present = bytearray([0x3E, 0x80])
#    stack.send(uds_tester_present)
    uds_request = bytearray([0x22, (identifier >> 8) & 0xFF, identifier & 0xFF])
    stack.send(uds_request)

    # Wait for a response
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()  # Process any received messages
        response = stack.recv()
        if response is not None:
            break
    bus.shutdown()
    return response

MODULE_INFO = {
    "bcm": ("620", "504", "can1"),   # BODY CONTROL MODULE
    "rf": ("740", "4C0", "can1"),    # RF HUB (UNCONFIRMED)
    "ipcm": ("742", "4C2", "can1"),  # INSTRUMENT PANEL CLUSTER MODULE
    "evic": ("742", "4C2", "can1"),  # EVIC is an alias for IPCM
    "airbag": ("744", "4C4", "can1"),   # OCCUPANT RESTRAINT / AIRBAG MODULE
    "shift": ("749", "4C9", "can1"), # ELECTRONIC SHIFTER MODULE
    "tcm": ("74B", "4CB", "can1"), # TRANSFER CASE MODULE
    "pcm": ("75A", "4DA", "can1"), # POWERTRAIN CONTROL MODULE
    "scm": ("763", "4E3", "can1"),  # STEERING COLUMN MODULE
    "hvac": ("783", "503", "can0"),  # HEATING/VENT/AIR-CONDITIONING MODULE
    # "unknown2": ("792", "512", "can1"),  # UNKNOWN
    "cscm": ("7BC", "53C", "can0"),  # INTEGRATED CENTERE STACK CONTROL  MODULE
    # "unknown3": ("7BE", "53E", "can1"),  # UNKNOWN
    "radio": ("7BF", "53F", "can0"), # UCONNECT RADIO MODULE
    "unknown2": ("7e0", "7e8", "can1"),   # UNKNOWN MODULE
    "tcm": ("7e1", "7e9", "can1"),   # TRANSMISSION CONTROL MODULE
}

def parse_identifier(identifier):
    if identifier.startswith("0x") or identifier.startswith("0X"):
        base = 16
    elif identifier.startswith("0o") or identifier.startswith("0O"):
        base = 8
    elif identifier.startswith("0b") or identifier.startswith("0B"):
        base = 2
    else:
        base = 16
    try:
        return int(identifier, base)
    except ValueError:
        raise argparse.ArgumentTypeError(f"Invalid identifier value: {identifier}")

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("module", type=str, help="module name, or actual location as txid:rxid:bus", nargs='?')
    parser.add_argument("identifier", type=parse_identifier, help="identifier in hexadecimal (e.g., 0xf190)", nargs='?')
    parser.add_argument("-a", "--ascii", action="store_true", help="prints the payload as an ASCII string")
    parser.add_argument("-d", "--decimal", action="store_true", help="prints the payload as decimal numbers")
    parser.add_argument("-t", "--timeout", type=float, default=2, help="response timeout in seconds (default: 2)")
    parser.add_argument("-m", "--modules", action="store_true", help="list available modules and exit")
    args = parser.parse_args()

    if args.modules:
        print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
        exit(0)

    if not args.module or not args.identifier:
        parser.print_help()
        exit(10)

    module = args.module.lower()
    identifier = args.identifier

    if module in {k.lower(): v for k, v in MODULE_INFO.items()}:
        txid, rxid, can_channel = MODULE_INFO[module]
        txid = int(txid, 16)
        rxid = int(rxid, 16)
    elif ':' in module:
      parts = module.split(':')
      if len(parts) == 3:
          try:
              txid = int(parts[0], 16)
              rxid = int(parts[1], 16)
              can_channel = parts[2]
          except ValueError:
              print("Invalid module format. Please provide a valid module name or format like '0x620:0x504:can1' or '620:504:can1'.")
              print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
              exit(11)
      else:
          print("Invalid module format. Please provide a valid module name or format like '0x620:0x504:can1' or '620:504:can1'.")
          print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
          exit(11)
    else:
        print("No such module found. Please provide a valid module name or format like '0x620:0x504:can1' or '620:504:can1'.")
        print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
        exit(11)

    # Let the ECU know that a tester is present
    tester_present_response = send_tester_is_present(can_channel, rxid, txid, args.timeout)

#    # Check if the response is positive (0x7E), otherwise print a warning
#    if tester_present_response is None or tester_present_response[0] != 0x7E:
#        print("Warning: Tester is Present message not acknowledged.")

    response = send_request_data_by_identifier(can_channel, rxid, txid, identifier, args.timeout)

    if response is not None:
        reply = response[0:3]
        status = reply[0:1]
        id = reply[1:3]

        # DEBUG LINES TO PRINT THE UDS RESPONSE CODE AND ASSOCIATED DATA ID
        # print("Status: ", ' '.join(f'{byte:02X}' for byte in status))
        # print("DataID: ", ' '.join(f'{byte:02X}' for byte in id))

        # NEGATIVE RESPONSE - REQUESTED DATAID DOES NOT EXIT
        if status == bytearray([0x7F]):
            print("NEGATIVE RESPONSE")
            exit(1)

        # ANY OTHER RESPONSE IS UNKNOWN OR UNEXPECTED, SO WE PRINT IT
        if status != bytearray([0x62]):
            print('UNKNOWN RESPONSE CODE:', ' '.join(f'{byte:02X}' for byte in status))
            exit(2)

        # Check if id matches the supplied identifier
        if (identifier >> 8) == id[0] and (identifier & 0xFF) == id[1]:
            payload = response[3:]

            if args.ascii:
                print(payload.decode('ascii', errors='replace'))
            elif args.decimal:
                print(' '.join(str(byte) for byte in payload))
            else:
                print(' '.join(f'{byte:02X}' for byte in payload))
        else:
            print("ERROR: A different Data ID was received.")
            exit(3)

    else:
        print("No response received within",args.timeout,"seconds.")
        exit(9)

if __name__ == "__main__":
    main()
"wid" WRITE DATA BY IDENTIFIER:
Python:
#!/usr/bin/python3
import os
import logging
import can
import isotp
import time
import sys
import argparse

# Write Data by Identifier
# Written by Josh McCormick with the help of ChatGPT
# for the Wrangler JL and Gladiator JT community.

def parse_hexadecimal(hexadecimal):
    if hexadecimal.startswith("0x") or hexadecimal.startswith("0X"):
        hexadecimal = hexadecimal[2:]
    try:
        return int(hexadecimal, 16)
    except ValueError:
        print(f"Invalid hexadecimal value: {hexadecimal}")
        sys.exit(1)

parser = argparse.ArgumentParser()
parser.add_argument("module_name", help="Module name")
parser.add_argument("identifier", help="Identifier (hexadecimal, with or without '0x' prefix)")
parser.add_argument("data", help="Data (hexadecimal, with or without '0x' prefix)")
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug logging")
parser.add_argument("-y", "--yes", action="store_true", help="Confirm that you want to proceed with the operation")
parser.add_argument("-r", "--reset", action="store_true", help="Perform ECU reset")
args = parser.parse_args()

if not args.yes:
    print("You don't seem very sure. Use the -y argument when you're ready.")
    sys.exit(1)

module = args.module_name.lower()
identifier = parse_hexadecimal(args.identifier)

data = args.data

if data.startswith("0x") or data.startswith("0X"):
    data = data[2:]

data_len = len(data)

if len(hex(identifier)[2:]) > 4:
    print("Identifier should be a two-byte hexadecimal number.")
    sys.exit(1)

data_len = len(data)
if data_len < 2 or data_len % 2 != 0 or data_len > 65535 * 2:
    print("Data should be a hexadecimal string with a length between 1 and 65535 bytes.")
    sys.exit(1)

data_bytes = bytearray()
for i in range(0, data_len, 2):
    byte_str = data[i:i+2]
    try:
        data_bytes.append(parse_hexadecimal(byte_str))
    except ValueError:
        print(f"Invalid data byte: {byte_str}")
        sys.exit(1)

def ensure_directory_exists(directory):
    if not os.path.exists(directory):
        try:
            os.makedirs(directory)
        except OSError as e:
            print(f"Error creating directory {directory}: {e}")
            sys.exit(1)

def setup_logging():
    log_directory = "/home/pi/modules"
    ensure_directory_exists(log_directory)
    log_file = os.path.join(log_directory, "log.txt")
    log_level = logging.DEBUG if args.debug else logging.WARNING
    logging.basicConfig(filename=log_file, level=log_level, format="%(asctime)s %(message)s")

def log_command(module_name, identifier, data, reset):
    command = f"WID MODULE: {module_name}, ID: {identifier}, DATA: {data}, RESET: {reset}"
    logging.warning(command)

def send_request(stack, request, expected_responses, timeout):
    stack.send(request)
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()
        response = stack.recv()
        if response is not None:
            for expected_response in expected_responses:
                if response[:len(expected_response)] == expected_response:
                    return response
            response = None
    return response

def send_tester_present(stack):
    request = bytearray([0x3E, 0x80])
    expected_responses = [
        bytearray([0x7E, 0x80]),
        bytearray([0x7F, 0x3E])
    ]
    return send_request(stack, request, expected_responses, 1)


def send_extended_diagnostic_session(stack):
    request = bytearray([0x10, 0x03])
    expected_responses = [
        bytearray([0x50, 0x03]),
        bytearray([0x7F, 0x10])
    ]
    return send_request(stack, request, expected_responses, 2)

def send_ecu_reset(stack):
    request = bytearray([0x11, 0x01])
#    expected_responses = [
#        bytearray([0x51, 0x01]),
#        bytearray([0x7F, 0x11])
#    ]
    expected_responses = [
        bytearray([0x51, 0x01])
    ]
    return send_request(stack, request, expected_responses, 10)

def send_write_data_by_identifier(stack, identifier, data_bytes):
    request = bytearray([0x2E, (identifier >> 8) & 0xFF, identifier & 0xFF]) + data_bytes

#    expected_responses = [
#        bytearray([0x6E, (identifier >> 8) & 0xFF, identifier & 0xFF]),
#        bytearray([0x7F, 0x2E])
#    ]

    expected_responses = [
        bytearray([0x6E, (identifier >> 8) & 0xFF, identifier & 0xFF])
    ]

    # Timeout starts at 5 seconds.
    # Starting at 100 bytes of data, timeout becomes 10 seconds.
    # Starting at 200 bytes of data, timeout becomes 20 seconds. And so on.
    timeout = 5 + 5 * (len(data_bytes) // 100)
    return send_request(stack, request, expected_responses, timeout)

MODULE_INFO = {
    "bcm": ("620", "504", "can1"),   # BODY CONTROL MODULE
    "rf": ("740", "4C0", "can1"),    # RF HUB (UNCONFIRMED)
    "ipcm": ("742", "4C2", "can1"),  # INSTRUMENT PANEL CLUSTER MODULE
    "evic": ("742", "4C2", "can1"),  # EVIC is an alias for IPCM
    "airbag": ("744", "4C4", "can1"),   # OCCUPANT RESTRAINT / AIRBAG MODULE
    "shift": ("749", "4C9", "can1"), # ELECTRONIC SHIFTER MODULE
    "tcm": ("74B", "4CB", "can1"), # TRANSFER CASE MODULE
    "pcm": ("75A", "4DA", "can1"), # POWERTRAIN CONTROL MODULE
    "scm": ("763", "4E3", "can1"),  # STEERING COLUMN MODULE
    "hvac": ("783", "503", "can0"),  # HEATING/VENT/AIR-CONDITIONING MODULE
    # "unknown2": ("792", "512", "can1"),  # UNKNOWN
    "cscm": ("7BC", "53C", "can0"),  # INTEGRATED CENTERE STACK CONTROL  MODULE
    # "unknown3": ("7BE", "53E", "can1"),  # UNKNOWN
    "radio": ("7BF", "53F", "can0"), # UCONNECT RADIO MODULE
    "unknown2": ("7e0", "7e8", "can1"),   # UNKNOWN MODULE
    "tcm": ("7e1", "7e9", "can1"),   # TRANSMISSION CONTROL MODULE
}

if module in {k.lower(): v for k, v in MODULE_INFO.items()}:
    txid, rxid, can_channel = MODULE_INFO[module]
    txid = int(txid, 16)
    rxid = int(rxid, 16)
else:
    print("No such module found. Please provide a valid module name.")
    print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(1)

setup_logging()
log_command(module, args.identifier, data, args.reset)

can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)
params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
stack = isotp.CanStack(bus=bus, address=address, params=params)

send_tester_present(stack)
response = send_extended_diagnostic_session(stack)

if response is None:
    print ("No response received from ECU.")
    logging.warning("NO RESPONSE FROM ECU")
    sys.exit(1)
else:
    if response[0] != 0x50:
        print("Response code: ",response)
        print("Failed to enter extended diagnostic session.")
        logging.warning("FAILED TO ENTER EXTENDED DIAGNOSTIC SESSION")
        sys.exit(1)
    else:
        print("Extended diagnostic session established.")
        logging.warning("EXTENDED DIAGNOSTIC SESSION ESTABLISHED")

response = send_write_data_by_identifier(stack, identifier, data_bytes)
print ("SEND WRITE DATA BY IDENTIFIER")

if response is not None:
    if response[0] != 0x6E:
        print("Unexpected response:",' '.join(f'{byte:02X}' for byte in response))
        print("UNEXPECTED RESPONSE")
        logging.warning("UNEXPECTED RESPONSE ")
        sys.exit(6)
    # if response[0] == 0x7F and response[1] == ((identifier >> 8) & 0xFF) and response[2] == (identifier & 0xFF):
    if response[0] == 0x6E:
        print("SUCCESS")
        logging.warning("SUCCESS")
        if args.reset:
            response = send_ecu_reset(stack)
            if response is None or response[0] != 0x51:
                print("Unexpected response:",' '.join(f'{byte:02X}' for byte in response))
                print("Failed to perform ECU reset.")
                logging.warning("FAILED TO PERFORM ECU RESET")
                sys.exit(1)
            else:
                print("ECU reset successful.")
                logging.warning ("ECU RESET SUCCESSFUL")
        sys.exit(0)
    else:
        print("Unexpected response:",' '.join(f'{byte:02X}' for byte in response))
        print("Unknown problem writing data to identifier.")
        print("UNKNOWN PROBLEM WRITING DATA TO IDENTIFIER")
        sys.exit(1)
else:
    print("No response received before timing out.")
    logging.warning("TIMEOUT WHILE WRITING DATA TO IDENTIFIER")
    exit(5)
"ioid" I/O BY IDENTIFIER:
Python:
#!/usr/bin/python3

import can
import isotp
import time
import sys
import argparse

# I/O by Identifier
# Written by Josh McCormick with the help of ChatGPT
# for the Wrangler JL and Gladiator JT community.

def parse_hexadecimal(hexadecimal):
    if hexadecimal.startswith("0x") or hexadecimal.startswith("0X"):
        hexadecimal = hexadecimal[2:]
    try:
        return int(hexadecimal, 16)
    except ValueError:
        print(f"Invalid hexadecimal value: {hexadecimal}")
        sys.exit(1)

parser = argparse.ArgumentParser()
parser.add_argument("module_name", help="Module name")
parser.add_argument("identifier", help="Identifier (hexadecimal)")
parser.add_argument("data", help="Data (hexadecimal)")
parser.add_argument("-e", "--extended", action="store_true", help="Request extended diagnostic session")
args = parser.parse_args()

module = args.module_name.lower()
identifier = parse_hexadecimal(args.identifier)
data = parse_hexadecimal(args.data)

if len(hex(identifier)[2:]) > 4 or len(hex(data)[2:]) > 4:
    print("Identifier and data should both be two-byte hexadecimal numbers.")
    sys.exit(1)

def send_tester_is_present(can_channel, rxid, txid, timeout=1):
    can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
    bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
    address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)
    params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
    stack = isotp.CanStack(bus=bus, address=address, params=params)

    # Send the UDS Tester is Present message with padding
    uds_tester_present = bytearray([0x3E, 0x80])
    stack.send(uds_tester_present)

    # Wait for a response
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()  # Process any received messages
        response = stack.recv()
        if response is not None:
            break
    bus.shutdown()
    return response

def send_extended_diagnostic_session(can_channel, rxid, txid):
    can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
    bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
    address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)
    params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
    stack = isotp.CanStack(bus=bus, address=address, params=params)

    stack.send(bytearray([0x10, 0x03]))
    start_time = time.time()
    response = None
    while time.time() - start_time < 0.5:
        stack.process()
        response = stack.recv()
        if response is not None:
            break
    bus.shutdown()
    return response

def send_io_control_by_identifier(can_channel, rxid, txid, identifier, data):
    can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
    bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
    address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)

    params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
    stack = isotp.CanStack(bus=bus, address=address, params=params)
    # Send the UDS Input Output Control by Identifier message with padding
    uds_request = bytearray([0x2F, (identifier >> 8) & 0xFF, identifier & 0xFF, (data >> 8) & 0xFF, data & 0xFF])
    stack.send(uds_request)

    # Wait for a response
    start_time = time.time()
    response = None
    while time.time() - start_time < 0.5:
        stack.process()  # Process any received messages
        response = stack.recv()
        if response is not None:
            break
    bus.shutdown()
    return response

MODULE_INFO = {
    "bcm": ("620", "504", "can1"),  # BODY
    "hvac": ("783", "503", "can0"),  # HVAC
    "radio": ("7BF", "53F", "can0"), # RADIO
}

if module in {k.lower(): v for k, v in MODULE_INFO.items()}:
    txid, rxid, can_channel = MODULE_INFO[module]
    txid = int(txid, 16)
    rxid = int(rxid, 16)
else:
    print("No such module found. Please provide a valid module name.")
    print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(1)

# Let the ECU know that a tester is present
tester_present_response = send_tester_is_present(can_channel, rxid, txid)

if args.extended:
    response = send_extended_diagnostic_session(MODULE_INFO[module][2], int(MODULE_INFO[module][1], 16), int(MODULE_INFO[module][0], 16))

    if response is None or response[0] != 0x50:
        print("Failed to enter extended diagnostic session.")
        sys.exit(1)
response = send_io_control_by_identifier(MODULE_INFO[module][2], int(MODULE_INFO[module][1], 16), int(MODULE_INFO[module][0], 16), identifier, data)

if response is not None:
    if response[0] == 0x7F:
        print("NO SUCH IDENTIFIER")
        sys.exit(6)
    if response[0] == 0x6F and response[1] == ((identifier >> 8) & 0xFF) and response[2] == (identifier & 0xFF):
        print("SUCCESS")
        sys.exit(0)
    else:
        print("Response received:")
else:
    print("No response received within 0.5 seconds.")
    exit(5)

for byte in response:
    print(f"{byte:02X}", end=" ")
sys.exit(7)
"ecureset" ECU RESET:
Python:
#!/usr/bin/python3
import os
import logging
import can
import isotp
import time
import sys
import argparse

# ECU Reset
# Written by Josh McCormick with the help of ChatGPT
# for the Wrangler JL and Gladiator JT community.

MODULE_INFO = {
    "bcm": ("620", "504", "can1"),   # BODY CONTROL MODULE
    "rf": ("740", "4C0", "can1"),    # RF HUB (UNCONFIRMED)
    "ipcm": ("742", "4C2", "can1"),  # INSTRUMENT PANEL CLUSTER MODULE
    "evic": ("742", "4C2", "can1"),  # EVIC is an alias for IPCM
    "airbag": ("744", "4C4", "can1"),   # OCCUPANT RESTRAINT / AIRBAG MODULE
    "shift": ("749", "4C9", "can1"), # ELECTRONIC SHIFTER MODULE
    "tcm": ("74B", "4CB", "can1"), # TRANSFER CASE MODULE
    "pcm": ("75A", "4DA", "can1"), # POWERTRAIN CONTROL MODULE
    "scm": ("763", "4E3", "can1"),  # STEERING COLUMN MODULE
    "hvac": ("783", "503", "can0"),  # HEATING/VENT/AIR-CONDITIONING MODULE
    # "unknown2": ("792", "512", "can1"),  # UNKNOWN
    "cscm": ("7BC", "53C", "can0"),  # INTEGRATED CENTERE STACK CONTROL  MODULE
    # "unknown3": ("7BE", "53E", "can1"),  # UNKNOWN
    "radio": ("7BF", "53F", "can0"), # UCONNECT RADIO MODULE
    "unknown2": ("7e0", "7e8", "can1"),   # UNKNOWN MODULE
    "tcm": ("7e1", "7e9", "can1"),   # TRANSMISSION CONTROL MODULE
}

def parse_hexadecimal(hexadecimal):
    if hexadecimal.startswith("0x") or hexadecimal.startswith("0X"):
        hexadecimal = hexadecimal[2:]
    try:
        return int(hexadecimal, 16)
    except ValueError:
        print(f"Invalid hexadecimal value: {hexadecimal}")
        sys.exit(1)

parser = argparse.ArgumentParser()
parser.add_argument("module_name", help="Module name", nargs='?')
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug logging")
group = parser.add_mutually_exclusive_group()
group.add_argument("-s", "--soft", action="store_true", help="Perform a soft ECU reset")
group.add_argument("-p", "--powercycle", action="store_true", help="Perform a full power cycle")
parser.add_argument("-m", "--modules", action="store_true", help="Display a list of module names")
args = parser.parse_args()

if args.modules:
    print("Available modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(0)

if not args.module_name:
    print("Error: Module name is required. Use -h for help.")
    sys.exit(1)

module = args.module_name.lower()

def ensure_directory_exists(directory):
    if not os.path.exists(directory):
        try:
            os.makedirs(directory)
        except OSError as e:
            print(f"Error creating directory {directory}: {e}")
            sys.exit(1)
def setup_logging():
    log_directory = "/home/pi/modules"
    ensure_directory_exists(log_directory)
    log_file = os.path.join(log_directory, "log.txt")
    log_level = logging.DEBUG if args.debug else logging.WARNING
    logging.basicConfig(filename=log_file, level=log_level, format="%(asctime)s %(message)s")

def log_command(module_name):
    command = f"ECURESET MODULE: {module_name}"
    logging.warning(command)

def send_request(stack, request, expected_responses, timeout):
    stack.send(request)
    start_time = time.time()
    response = None
    while time.time() - start_time < timeout:
        stack.process()
        response = stack.recv()
        if response is not None:
#            print(" ")
#            print("RESPONSE: ",' '.join(f'{byte:02X}' for byte in response))
            for expected_response in expected_responses:
#                print("EXPECTED: ",' '.join(f'{byte:02X}' for byte in expected_response))
                if response[:len(expected_response)] == expected_response:
                    return response
            response = None
    return response

def send_tester_present(stack):
    request = bytearray([0x3E, 0x00])
    expected_responses = [
        bytearray([0x7E, 0x00]),
    ]
    return send_request(stack, request, expected_responses, 1)

def send_ecu_reset(stack, reset_type):
    request = bytearray([0x11, reset_type])
    expected_responses = [
        bytearray([0x51, reset_type])
    ]
    return send_request(stack, request, expected_responses, timeout)

if module in {k.lower(): v for k, v in MODULE_INFO.items()}:
    txid, rxid, can_channel = MODULE_INFO[module]
    txid = int(txid, 16)
    rxid = int(rxid, 16)
else:
    print("No such module found. Please provide a valid module name.")
    print("Pre-defined modules:", " ".join(MODULE_INFO.keys()))
    sys.exit(1)

setup_logging()
log_command(module)
can_filters = [{"can_id": rxid, "can_mask": 0x7FF}]
bus = can.interface.Bus(can_channel, bustype='socketcan', can_filters=can_filters)
address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=txid, rxid=rxid)
params = {'tx_data_min_length': 8, 'tx_padding': 0x00}
stack = isotp.CanStack(bus=bus, address=address, params=params)

# Send tester present message before ECU reset
tester_present_response = send_tester_present(stack)
#if tester_present_response is None or tester_present_response[0] != 0x7E or tester_present_response[1] != 0x00:
#    print("Warning: Tester Is Present call was not successful.")
#    logging.info("FAILED TO SEND TESTER PRESENT MESSAGE")
#    sys.exit(1)

timeout=5
reset_type = 0x03 if args.soft else 0x01
if args.powercycle:
    reset_type=0x04
    timeout=0

reset_response = send_ecu_reset(stack, reset_type)

if reset_response is None or reset_response[0] != 0x51 or reset_response[1] != reset_type:
    if args.powercycle:
        print("COMMAND SENT (NO CONFIRMATION EXPECTED)")
        logging.warning("SUCCESS IMPLIED")
        sys.exit(0)
    else:
        print("Failed to perform ECU reset.")
        logging.info("FAILED TO PERFORM ECU RESET")
        logging.warning("FAILED RESET")
        sys.exit(1)
if args.powercycle:
    print ("ECU POWER CYCLE SUCCESSFUL (ACKNOWLEDGEMENT WAS UNEXPECTED)")
else:
    print("ECU RESET SUCCESSFUL")
logging.warning("SUCCESS")
No warranty expressed or implied with this code. Use at your own risk. The "wid" Write Data by Identifier script is a sharp tool, as such, it can either be very dangerous to your vehicle, or very useful.
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Most threads of this nature die in a few weeks, but here you are all this time later.
I may be the most active right now, but it's a community! There are quite a few others, particularly @redracer who's been at this even longer than I have. Figuring out how to access the Wrangler has been an interesting challenge for me. Even more so today, now that I've gotten some nagging health issues out of the way.

My JL may be getting upgrades in the future.
I think we've found a lot of things that can be read (as far as vehicle status), but we've found very few things that we can modify (other than turning some BCM module components off/on and simulating button presses).

I'm hoping that much more profound discoveries are yet to come! I'd absolutely kill to find the right combination of things that would set the transmission into something equivalent to sport mode! And if we find those, they're going to be easily duplicated by those who have put together a similar setup as the rest of us.

My focus so far hasn't been on powertrain/transmission and performance, but ChatGPT tells me, there are some great tunables to be found specifically with the Jeep Wrangler JL...
  1. Shift Points: Adjusting the RPM at which gear shifts occur can optimize performance or fuel efficiency, depending on the desired outcome.
  2. Shift Firmness: Changing the firmness of shifts can alter the driving experience, with firmer shifts providing a sportier feel and smoother shifts offering a more comfortable ride.
  3. Torque Converter Lockup: Modifying the lockup settings of the torque converter can influence fuel efficiency and performance. More aggressive lockup settings might improve fuel economy but could lead to a less comfortable driving experience.
  4. Throttle Response: Adjusting the throttle response can make the vehicle feel more responsive or smoother, depending on the desired outcome.
  5. Turbocharger Controls: Adjusting the boost pressure or wastegate settings on a turbocharged engine can increase power output, but it may also cause additional stress on engine components and increase the risk of failure.
One way or another, I suspect we're going to stumble across one or more of these by end-of-year. I'm looking forward to us making some great discoveries this year, now that we have the tool to inspect and modify the right ECU parameters.

ChatGPT knows things it doesn't want to fully discuss...

Transmission Shift Firmness - SHIFT FIRMNESS - GEAR 1, SHIFT FIRMNESS - GEAR 2, SHIFT FIRMNESS - GEAR 3, etc. (each gear has its own I/O identifier)​
Shift Points - SHIFT RPM - GEAR 1, SHIFT RPM - GEAR 2, SHIFT RPM - GEAR 3, etc. (each gear has its own I/O identifier)​
Throttle Pedal Position (TPP) Sensor Scaling - the adjustment of the TPP sensor's output for better accuracy, often listed as a table of values for different throttle pedal positions, such as "TPP VOLTAGE - 10%", "TPP VOLTAGE - 50%", etc.​
Wastegate Control - often listed as a table of values for different boost pressures, such as "WASTEGATE DUTY CYCLE - 10 PSI", "WASTEGATE DUTY CYCLE - 15 PSI", etc.​

At least, for any parameter, it's giving me an idea of what kind of data I'm looking for, which is useful. ChatGPT will talk shop with me, but it won't spill the beans as much as I'd hoped for.

UPDATE (MARCH 22nd, 2022):
This was found to be a ChatGPT 3.5 "hallucination". Pure fiction presented as fact. The information failed to stay consistent between ChatGPT sessions. And ChatGPT 4.0 refused to participate, saying that the DIDs were proprietary and that we should seek a service manual. Sorry, folks. But it is a good guideline as far as the kinds of things to look for, at least.
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,162
Reaction score
1,303
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
OH SHI---
I think I just hit GOLD with ChatGPT!
  1. Fuel Rail Pressure (FRP) - DID 2211 (Engine Control Module - ECM)
  2. Exhaust Gas Temperature (EGT) - DID 2215 (ECM)
  3. Boost Pressure - DID 2220 (ECM)
  4. Intake Manifold Pressure (IMP) - DID 2221 (ECM)
  5. Transmission Fluid Pressure - DID 220A (Transmission Control Module - TCM)
  6. Transmission Fluid Level - DID 2214 (TCM)
  7. Transmission Gear Ratio - DID 221C (TCM)
  8. Torque Converter Clutch Slip - DID 221D (TCM)
  9. Park/Neutral Position Switch Status - DID 221E (TCM)
  10. Vehicle Acceleration - DID 221F (TCM)
  11. Wheel Speed - DID 2222 (Anti-Lock Brake System - ABS)
  12. Brake Pedal Position - DID 2223 (ABS)
  13. Steering Wheel Angle - DID 2310 (Electronic Stability Control - ESC)
  14. Battery Voltage - DID 1203 (Body Control Module - BCM)
  15. HVAC Temperature - DID 2101 (HVAC Control Module)
  16. Ambient Air Temperature - DID 2102 (HVAC Control Module)
  17. Fuel Tank Pressure (FTP) - DID 2212 (ECM)
  18. Fuel System Status - DID 2213 (ECM)
  19. Engine Oil Temperature (EOT) - DID 2219 (ECM)
  20. Transmission Oil Temperature (TOT) - DID 221B (TCM)
  21. Transmission Shift Lever Position - DID 2217 (TCM)
  22. Transmission Fluid Temperature Warning - DID 2227 (TCM)
  23. Accelerator Pedal Position (APP) - DID 2210 (ECM or Accelerator Pedal Module - APM)
  24. Brake Pressure - DID 2216 (ABS)
  25. Brake System Warning Lamp Status - DID 2225 (ABS)

  26. Adaptive Front Lighting System (AFS) - DID 2311 (Body Control Module - BCM)
  27. Tire Pressure - DID 222F (Tire Pressure Monitoring System - TPMS)
  28. Lane Departure Warning (LDW) - DID 2315 (BCM)
  29. Blind Spot Monitoring (BSM) - DID 2316 (BCM)
  30. Adaptive Cruise Control (ACC) - DID 2317 (BCM or Radar Sensor Module)
  31. Engine Coolant Temperature Warning - DID 2226 (ECM)
Even more...
  1. Brake Pedal Switch Status - DID 2228 (ABS or TCM)
  2. Steering Angle Sensor Status - DID 2313 (Electronic Stability Control - ESC)
  3. Lane Keeping Assist (LKA) - DID 2314 (BCM)
  4. Battery State of Charge (SOC) - DID 2207 (ECM or Battery Management System - BMS)
  5. Hybrid/Electric Drive System Status - DID 22FD (Hybrid/Electric Control Module - HCM/ECM)
  6. Electric Motor Temperature - DID 22E0 (HCM/ECM)
  7. Battery Cell Voltage - DID 2218 (BMS)
  8. Charge/Discharge Power Limit - DID 22E4 (HCM/ECM)
  9. Hybrid/Electric Vehicle Speed - DID 22E1 (HCM/ECM)
  10. Regenerative Brake Control - DID 22E2 (HCM/ECM)
  11. Electric Motor Torque - DID 22E3 (HCM/ECM)
  12. Ignition Coil Status - DID 221A (ECM)
  13. Transmission Oil Level - DID 2214 (TCM)
  14. Automatic Transmission Fluid Wear - DID 2206 (TCM)
  15. Engine Oil Life - DID 222A (ECM)
And still more...
  1. Adaptive Suspension System - DID 2319 (Body Control Module - BCM)
  2. Power Steering System - DID 231A (Electronic Power Steering - EPS)
  3. Fuel Rail Pressure Control - DID 222B (ECM)
  4. Engine Oil Pressure - DID 222C (ECM)
  5. Engine Oil Level - DID 222D (ECM)
  6. Diesel Exhaust Fluid (DEF) Level - DID 222E (ECM)
  7. Diesel Particulate Filter (DPF) Status - DID 222F (ECM)
  8. Exhaust Gas Recirculation (EGR) Valve Position - DID 2230 (ECM)
  9. EGR System Status - DID 2231 (ECM)
  10. Nitrogen Oxide (NOx) Sensor - DID 2232 (ECM)
  11. Diesel Exhaust Fluid (DEF) Quality - DID 2233 (ECM)
  12. Cylinder Head Temperature - DID 2234 (ECM)
  13. Intake Manifold Runner Control (IMRC) - DID 2235 (ECM)
  14. Powertrain Control Module (PCM) ID - DID 1600 (ECM)
  15. Diagnostic Trouble Codes (DTCs) - DID 19, 0A (ECM)
...and here are some of the very parameters I just mentioned that I was looking for:
  1. Shift Points - DID 2217 (TCM)
  2. Shift Firmness - DID 2217 (TCM)
  3. Torque Converter Lockup - DID 221D (TCM)
  4. Throttle Response - DID 11, Byte 0B (ECM or APM)
  5. Turbocharger Control - DID 2220 (ECM)
These could all be ChatGPT "hallucinations". I guess we'll have to check them out and see if they make sense or not!

(Where it lists "ECM", that should mean the Powertrain Control Module.)
This could be AWESOME... but now I'm thinking it's hallucinating again.

UPDATE (MARCH 22nd, 2022):
This was found to be a ChatGPT 3.5 "hallucination". Pure fiction presented as fact. The information failed to stay consistent between ChatGPT sessions. And ChatGPT 4.0 refused to participate, saying that the DIDs were proprietary and that we should seek a service manual. Sorry, folks. But it is a good guideline as far as the kinds of things to look for, at least.
Sponsored

 
Last edited:
 



Top