Sponsored

redracer

Well-Known Member
First Name
Robert
Joined
Aug 22, 2017
Threads
20
Messages
576
Reaction score
650
Location
Manteca, CA
Vehicle(s)
2023 4xe Rubicon
@redracer - It looks like I can use another copy of your /boot/config.txt and your /etc/network/interfaces.d/* files. It isn't seeing any of the CAN networks for some reason and I suspect OS more than hardware here.
I'll get those to you tomorrow. Unfortunately, the Jeep dealer held my 4xe since Thursday to repair a bad A/C Compressor (Module). They called yesterday to confirm a Monday pickup. Then I can pull those off of my current Pi.
Sponsored

 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
I'll get those to you tomorrow. Unfortunately, the Jeep dealer held my 4xe since Thursday to repair a bad A/C Compressor (Module). They called yesterday to confirm a Monday pickup. Then I can pull those off of my current Pi.
Thanks! And when you've got your vehicle back, if you could, please let me know in raspi-config if (under advanced settings) you have predictable network names enabled or not. I think that they should be enabled so that can0 and can1 don't get swapped?

I'm not for sure here but I'm grasping for straws at the problem I have every time I try to rebuilt a Raspberry Pi for the Wrangler and I end up having to wrestle with it for days until I can get a configuration that boots and sees both CAN networks. I don't know why my configuration has to be so difficult for it, but I suppose anything we're doing here is pretty non-standard.
 

MissAnnThorpe

Well-Known Member
First Name
Ann
Joined
Dec 10, 2021
Threads
1
Messages
48
Reaction score
38
Location
Seattle
Vehicle(s)
21 JLU Sahara 4xE
With chat gpts help I now have it looking at rpm and throttle to trigger exhaust state
I know you know already, but please be very, very, careful with generative AI, especially for something that has write access to your vehicle's CAN bus.

They make the same kinds of mistakes humans make and seem to have had this thread in their training, which is likely how they generated their instructions.

LLMs really good at imitation and humans aren't perfect. A useful tool but make sure you understand every line before you use it.

Fwiw, I have found Bing to generate the highest quality code, but they're far more useful just to bounce ideas off, especially in creative mode.
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
PRO TIP:
Don't swap your microSD card between different Raspberry Pis.
Even if the two devices are 100% identical.


I managed to troubleshoot my thorny issues with a new Raspbian install.

It was due to the fact that I have a "production" Raspberry Pi (inside the vehicle) and a "test/development" Raspberry Pi (on my desk). When you swap your microSD card between Raspberry Pis, things just break. The apparent cause is a device naming and/or device numbering mismatch between the CAN devices.

My advice to anyone wanting to have a separate Raspberry Pi (outside of their vehicle) to work on would be for it to always have it's own microSD card, and never swap cards between the two devices. Otherwise, symptoms include:
  1. Garbage on both CAN devices
  2. The can0 and can1 become swapped with each other
  3. The system locks up during boot (after reading /etc/network/interfaces.d)
I also recommend enabling persistent device numbering in raspi-config under the "Advanced" options. Once things are set correctly, that should help to avoid inadvertently letting this problem sneak up on you at a later date.

(Crossing my fingers! Hoping tomorrow morning brings me closer to Python 3.)
 

MissAnnThorpe

Well-Known Member
First Name
Ann
Joined
Dec 10, 2021
Threads
1
Messages
48
Reaction score
38
Location
Seattle
Vehicle(s)
21 JLU Sahara 4xE
ChatGPT will improve over time. Eventually it'll likely replace us software engineers.
I meant to respond to your post and @jmccorm above.

I don't think that's likely. They're more useful as programmer's assistants. There will still be a human prompting and committing. Design will be more flexible, refactoring faster, getting full test coverage easier, and they can spot design issues and logic errors. And they can explain themselves in plain English to anybody. It's groundbreaking, sure, but I think replacement is premature.
 

Sponsored

OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
@redracer - When you get time, can you see if this Python script performs the same as your radioreset script you provided long ago? (If it doesn't work, the only thing I figured I may have done is swapped the txid and the rxid. Those should be the transmit and receive IDs for the uConnect radio.) Also, request.data holds the type of reset to be performed.

Python:
#!/usr/bin/python3

import udsoncan

with udsoncan.Connection('can0', txid=0x7BF, rxid=0x53F) as conn:
    request = udsoncan.Request(service=udsoncan.services.ECUReset)
    request.data = b'\x01'
    response = conn.send(request)
EDIT:

Values for reset types --

$01 = Hard reset (simulated battery disconnect)
$02 = KeyOfOn Reset (normal off/on reset)
$03 = Software Reset (restarts module)
$04 = Enable Rapid Power Shutdown (not for ignition powered modules)
$05 = Disable Rapid Power Shutdown (not for ignition powered modules)

 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
If someone has time...

Would you mind trying these scripts and report back if they do anything or not? Or do they print an error message?

SHUT OFF AUX3:
Python:
#!/usr/bin/python3

import can
import time

# define the bus
bus = can.interface.Bus(channel='can0', bustype='socketcan_native')

# sync and sleep
bus.send(can.Message(arbitration_id=0x314, data=[0x5A, 0x03, 0x04]))
time.sleep(0.1)
bus.send(can.Message(arbitration_id=0x314, data=[0x5A, 0x03, 0x02]))
time.sleep(0.1)

# sync and sleep
bus.send(can.Message(arbitration_id=0x314, data=[0x59, 0x03, 0x04]))
time.sleep(0.1)
bus.send(can.Message(arbitration_id=0x314, data=[0x59, 0x03, 0x02]))
HONK 3 TIMES:
Python:
#!/usr/bin/python3

import can
import isotp
import udsoncan
import time

# Set up the CAN buses
can0 = can.interface.Bus(channel='can0', bustype='socketcan_native')
can1 = can.interface.Bus(channel='can1', bustype='socketcan_native')

# Send the CAN message to wake up the bus
msg = can.Message(arbitration_id=0x2D3, data=[0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], is_extended_id=False)
can0.send(msg)
time.sleep(0.2)

# Set up the ISO-TP socket to communicate over the second CAN bus
tp_sock = isotp.socket()
tp_sock.set_fc_opts(stmin=0x01, bs=0x10)
tp_sock.bind(interface=can1, rxid=0x504, txid=0x620)

# Set up the UDS client and enter into an extended diagnostic session
uds_client = udsoncan.UDSClient(tp_sock)
uds_client.change_session(udsoncan.Session.ExtendedDiagnosticSession)

# Honk the horn three times
for i in range(3):
    # Turn the horn on
    uds_client.io_control(0xd0ad, b"\x03\x01", timeout=0.1)
    time.sleep(0.01)

    # Turn the horn off
    uds_client.io_control(0xd0ad, b"\x03\x00", timeout=0.1)
    time.sleep(0.025)
MYHONK (rapid honk):
Code:
#!/usr/bin/python3

import can
import time
import isotp
import udsoncan

# Set up the CAN buses
#can0 = can.interface.Bus(channel='can0', bustype='socketcan_native')
#can1 = can.interface.Bus(channel='can1', bustype='socketcan_native')
can0 = can.interface.Bus(channel='can0', bustype='socketcan')
can1 = can.interface.Bus(channel='can1', bustype='socketcan')

# Send the CAN message to wake up the bus
msg = can.Message(arbitration_id=0x2D3, data=[0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], is_extended_id=False)
can0.send(msg)
time.sleep(0.1)

# Set up the ISO-TP socket to communicate over the second CAN bus
tp_sock = isotp.socket()
tp_sock.set_fc_opts(stmin=0x01, bs=0x10)
tp_sock.bind(interface=can1, rxid=0x504, txid=0x620)

# Set up the UDS client and enter into an extended diagnostic session
uds_client = udsoncan.UDSClient(tp_sock)
uds_client.change_session(udsoncan.Session.ExtendedDiagnosticSession)
time.sleep(0.1)

# Honk the horn three times
for i in range(3):
    uds_client.io_control(0xd0ad, b"\x03\x01", timeout=0.1)
    time.sleep(0.015)
    uds_client.io_control(0xd0ad, b"\x03\x00", timeout=0.1)
    time.sleep(0.035)

# Send an extra OFF just in case the previous one gets lost
uds_client.io_control(0xd0ad, b"\x03\x00", timeout=0.5)
time.sleep(0.5)
WAKE THE CAN BUS (does nothing, just look for errors):
Code:
#!/usr/bin/env python3

import random
import can
import time

# create CAN bus object
can0_bus = can.interface.Bus(bustype='socketcan', channel='can0')

# generate a random number between 0 and 3 to simulate a button press
fake_button = random.randint(0, 3)

# create a message to wake up the bus
wake_message = can.Message(
    arbitration_id=0x2D3,
    data=[0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + fake_button],
    extended_id=False
)

# send the wake-up message
can0_bus.send(wake_message)

# Sleep for 0.1 seconds
time.sleep(0.1)
RADIO RESET #2 (another script to reset the uConnect Radio):
Code:
#!/usr/bin/python3

import isotp
import logging
import udsoncan

# Set up logging
logging.basicConfig(level=logging.DEBUG)

# Define constants
CAN_INTERFACE = 'can0'
CAN_TX_ADDRESS = 0x7BF
CAN_RX_ADDRESS = 0x53F

# Initialize ISO-TP stack
isotp_params = {
    'stmin': 0,         # minimum separation time between consecutive frames (ms)
    'blocksize': 1,     # number of consecutive frames per block
    'wftmax': 0,        # maximum number of wait frame transmissions
    'll_data_length': 8 # length of each CAN message payload
}
tp = isotp.TransportLayer(CAN_INTERFACE, rxid=CAN_TX_ADDRESS, txid=CAN_RX_ADDRESS, params=isotp_params)

# Initialize UDS client
with udsoncan.UDSClient(tp) as client:
    # Send ECU reset request with data of 01
    response = client.ecu_reset(udsoncan.DidLengthFormat.LONG, b'\x01')

    # Check response status
    if response.positive:
        print('ECU reset successful')
    else:
        print(f'ECU reset failed with negative response: {response.negative_response.service} - {response.negative_response.code}')
 

MissAnnThorpe

Well-Known Member
First Name
Ann
Joined
Dec 10, 2021
Threads
1
Messages
48
Reaction score
38
Location
Seattle
Vehicle(s)
21 JLU Sahara 4xE
Would you mind trying these scripts and report back if they do anything or not? Or do they print an error message?
I haven't enabled write access or the can C network, so I can't test your code, but if you post the error message, I might be able to help figure out the cause anyway.
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Creating Python 3 Scripts for the Wrangler JL with ChatGPT

This was quite the adventure. It looks like it's possible to write Python 3 code for the Wrangler using ChatGPT. But there are quite a few things that can get you.

First and foremost are the versions of the Python-CAN module. Current version of Python-CAN is 4.1.0. To determine what version of Python-CAN you're using, you'd type "pip show python-can". I'm also at version 4.1.0, which is good.

Unfortunately, ChatGPT is aware of Python-CAN versions 2.x, 3.x, and 4.x. You HAVE to specify that you want it to write to your version of the Python-CAN module, otherwise, the code is going to have problems. (I particularly ran into this when it came to CAN filters.) And I suspect that I was running into the same thing when I tried the UDSonCAN module (with it being aware of versions 0.x and 1.x).

My Raspberry Pi has Python 3.9.2 (determined by typing "python --version"). ChatGPT is familiar with Python 2.7, 3.4-3.9, and 3.10. So you need to tell it to write for Python 3.9.2.

I'm also thinking that, aside from knowing the formal language specifications, ChatGPT has ingested some CAN code without any particular version number associated with that. So it's going to pull in some solutions without regard to version numbers. When that happens, you need to feed it any error message you received and to remind it what versions of Python and Python modules it should be writing towards.

IF YOU DO NOT SPECIFY A PYTHON VERSION AND YOUR MODULE VERSION(S), CHATGPT WILL USE WHATEVER IT FEELS LIKE WHEN CREATING CODE. THIS IS BAD!

(This is the reason why everything I was coding in Python was breaking with one ridiculous error after the other. Especially all the advanced UDS features I was trying.)

There's yet another problem which you need to be careful of, and it's particular to the Wrangler. We don't have one CAN bus. We have TWO. So anything that you're doing needs to be applied to the correct CAN bus, and your code needs to wait for a message from either CAN bus. (Not wait for a message from can0, then wait for a message from can1.) You also be careful not to let the CAN ID from one bus get confused with the CAN ID from another bus, but using filters will help with this.

Here's a very basic example made with ChatGPT:

PROMPT:
I want a Python 3.9.2 script (using the Python-CAN module 4.1.0) that listens to the CAN bus (can1) for a message with an ID of 0x023. Each time is receives a message with an identifier 0x023, it prints (in hexadecimal) the value of the first word. The script runs in an infinite loop. Begin by specifying a shell interpreter.

RESPONSE:
Code:
#!/usr/bin/env python3
import can

# Set up the CAN bus
bus = can.interface.Bus(channel='can1', bustype='socketcan')

# Set the ID we want to filter for
CAN_ID = 0x023

# Infinite loop to listen for messages
while True:
    message = bus.recv()
    if message.arbitration_id == CAN_ID:
        # Get the value of the first word
        value = (message.data[0] << 8) + message.data[1]
        print(f"Received message with ID 0x{CAN_ID:x}. First word value is 0x{value:04x}")
Not bad! Once you have some code that works, you can go back on it and iterate some more. You can have it look at multiple different values and print them out...

PROMPT:
Each line in the table below contains a vehicle parameter, a CAN bus, a CAN message ID, data in the form of an offset and size (in that message), and in what format the data should be shown.

I want a Python 3.9.2 script (using the Python-CAN module 4.1.0) that constantly reads and evaluates CAN bus messages, printing any that match. Begin by specifying a shell interpreter.

"Intake Air Temperature" can1 0x127 [0th byte] [value in decimal]
"Engine Coolant Temperature" can1 0x127 [1st byte] [value in decimal]
"Barometric Pressure" can1 0x127 [2nd byte] [value in decimal]

RESPONSE (untested):
Code:
#!/usr/bin/python3
import can

# Initialize the CAN bus
bus = can.interface.Bus(bustype='socketcan', channel='can1')

# Set the ID to filter for
CAN_ID = 0x127

# Define the message handler function
def handle_message(message):
    if message.arbitration_id == CAN_ID:
        data = message.data
        print("Intake Air Temperature:", data[0])
        print("Engine Coolant Temperature:", data[1])
        print("Barometric Pressure:", data[2])

# Set up the message filter
can_filter = {"can_id": CAN_ID, "can_mask": 0x7FF}

# Add the filter to the bus
bus.set_filters([can_filter])

# Run the loop
while True:
    message = bus.recv()
    handle_message(message)
You can add CAN filters (which you should be using so that you aren't processing EVERY message on the CAN bus to see if it matches or not).

PROMPT:
Please add a CAN filter for only the appropriate CAN IDs, making sure that no CAN ID is added twice.

RESPONSE (untested):
Code:
#!/usr/bin/python3
import can

params = [
    ("Intake Air Temperature", "can1", 0x127, 0, 1, "decimal"),
    ("Engine Coolant Temperature", "can1", 0x127, 1, 1, "decimal"),
    ("Barometric Pressure", "can1", 0x127, 2, 1, "decimal")
]

bus = can.interface.Bus(bustype='socketcan', channel='can1')
can_ids = list(set([p[2] for p in params]))
filters = [can.Filter(can_id=id, can_mask=0x7FF) for id in can_ids]
msgs = [can.Message(arbitration_id=p[2], is_extended_id=False) for p in params]

bus.set_filters(filters)

while True:
    recv_msg = bus.recv()
    for i, msg in enumerate(msgs):
        if recv_msg.arbitration_id == msg.arbitration_id:
            data = recv_msg.data[p[3]:p[3]+p[4]]
            if p[5] == "decimal":
                value = int.from_bytes(data, byteorder='big')
            print(params[i][0], ":", value)
Finally, I was able to issue a very complex prompt which ended up with (mostly) working code. (It wasn't giving me battery volts or RPMs, but the rest worked ok.)

PROMPT:
Each line in the table below contains:

1. a vehicle parameter
2. any label that goes after the data
3. a CAN bus
4. a CAN message ID
5. data in the form of an offset and size (in that message)
6. in what format the data should be shown (decimal or hexadecimal)
7. any mathematical operation that needs to be performed on the data before showing it

I want a Python 3.9.2 script using python-can 4.1.0 module that constantly reads and evaluates CAN bus messages, printing any that match. Please add a CAN filter for only the appropriate CAN IDs, making sure that no CAN ID is added twice. The filter should be created dynamically based upon table data. Use the Python-CAN library. While the data is constantly being read from the CAN bus, all the vehicle parameters should be displayed once per second. Begin by specifying a shell interpreter.

"Steering Angle" "half degrees" can1 0x023 [0th word] [value in decimal] "-4096"
"Intake Air Temperature" "C" can1 0x127 [0th byte] [value in decimal] "*1"
"Engine Coolant Temperature" "C" can1 0x127 [1st byte] [value in decimal] "*1"
"Barometric Pressure" "kPa" can1 0x127 [2nd byte] [value in decimal] "*1"
"Engine RPM" "rpm" can1 0x0B5 [1st word] [value in decimal] "*1"
"Vehicle Speed" "MPH" can1 0x1F7 [3rd word] [value in decimal] "*1"
"Ignition Status" "hex" can0 0x122 [0th word] [value in hexadecimal] "*1"
"Aux Battery" "volts" can0 0x2C2 [2nd byte] [value in decimal] "*0.10"

RESPONSE (partially works):
Code:
#!/usr/bin/env python3

import can
import time

params = [
    ("Steering Angle", "half degrees", "can1", 0x023, 0, 2, "decimal", "-4096"),
    ("Intake Air Temperature", "C", "can1", 0x127, 0, 1, "decimal", "*1"),
    ("Engine Coolant Temperature", "C", "can1", 0x127, 1, 1, "decimal", "*1"),
    ("Barometric Pressure", "kPa", "can1", 0x127, 2, 1, "decimal", "*1"),
    ("Engine RPM", "rpm", "can1", 0x0B5, 0, 2, "decimal", "*1"),
    ("Vehicle Speed", "MPH", "can1", 0x1F7, 6, 2, "decimal", "*1"),
    ("Ignition Status", "hex", "can0", 0x122, 0, 2, "hexadecimal", "*1"),
    ("Aux Battery", "volts", "can0", 0x2C2, 2, 1, "decimal", "/10")
]

can_bus1 = can.interface.Bus(bustype='socketcan', channel='can1')
can_bus0 = can.interface.Bus(bustype='socketcan', channel='can0')

can_ids1 = list(set([p[3] for p in params if p[2] == "can1"]))
can_ids0 = list(set([p[3] for p in params if p[2] == "can0"]))

filters1 = [{"can_id": id, "can_mask": 0x7FF} for id in can_ids1]
filters0 = [{"can_id": id, "can_mask": 0x7FF} for id in can_ids0]

can_bus1.set_filters(filters1)
can_bus0.set_filters(filters0)

while True:
    data_dict = {}
    start_time = time.time()
    while time.time() - start_time < 1:
        recv_msg = can_bus1.recv() or can_bus0.recv()
        for param in params:
            if recv_msg.arbitration_id == param[3] and recv_msg.channel == param[2]:
                data = recv_msg.data[param[4]:param[4]+param[5]]
                if param[6] == "decimal":
                    value = int.from_bytes(data, byteorder='big', signed=True)
                elif param[6] == "hexadecimal":
                    value = "0x" + data.hex()
                for op in param[7:]:
                    if isinstance(op, str) and op.startswith("*"):
                        value *= int(op[1:])
                    elif isinstance(op, str) and op.startswith("/"):
                        value /= float(op[1:])
                data_dict[param[0]] = f"{value} {param[1]}"

    for k, v in data_dict.items():
        print(f"{k}: {v}")
OUTPUT:
Steering Angle: 4114 half degrees
Engine RPM: 0 rpm
Vehicle Speed: 0 MPH
Intake Air Temperature: 88 C
Engine Coolant Temperature: 122 C

Barometric Pressure: 100 kPa
Engine RPM: 0 rpm
Steering Angle: 4114 half degrees
Vehicle Speed: 0 MPH
Intake Air Temperature: 88 C
Engine Coolant Temperature: 122 C
Barometric Pressure: 100 kPa

Engine RPM: 0 rpm
Steering Angle: 4114 half degrees
Vehicle Speed: 0 MPH
Intake Air Temperature: 88 C
Engine Coolant Temperature: 123 C
Barometric Pressure: 100 kPa


...it looks like the can0 variables aren't getting updated. Which is one of the challenges here working with the Wrangler and having two CAN busses.

The good news? I think I'm on my way to writing code (particularly UDS code) in Python that's going to unlock some of the advanced features we've been looking forward to. But I'm sure there are plenty more challenges yet to stumble across along the way.
 
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
recv_msg = can_bus1.recv() or can_bus0.recv()
Found part of the bug (why it was only seeing one bus). It needs a timeout...

Code:
recv_msg = can_bus1.recv(timeout=0.0001) or can_bus0.recv(timeout=0.0001)
Otherwise, it is going to wait forever for a message on can0 and NEVER read any messages from can1. But creating a timeout introduces null messages, which you won't be able to process. So you have to deal with those by putting a condition ahead of the code that follows...

Code:
    if recv_msg is not None:
That's it. You know, in it's current form, ChatGPT is way too fragile to put programmers in danger. (But I think that generative AI, in general, has a strong chance of being disruptive for helping non-programmers create simple programs.)

What I am finding this to be is an incredible educational aid to putting an unfamiliar computer language to work. I know next to nothing about Python, but I'm able to take general programming principles to put Python, Python-CAN, and UDSonCAN to work on real-world problems. This stuff is fantastic!

ALSO: ChatGPT just upgraded my subscription to GPT 4.0. The solutions are starting to look even smarter, IMHO.

Here is my new writing prompt for the start of a sesion (likely overkill, but)...

You will be writing code only for Python 3.9.2 and only for the Python-CAN module version 4.1.0. If you use the udsoncan module, you will write only to version 1.16. If you use the can-isotp module, you will write only to version 1.8. You will be writing code for a Jeep Wrangler JL, which has two CAN busses, can0 and can1. can0 might be referred to as CAN-IHS (125000 bits), while can1 might be referred to as CAN-C (500000 bits). The CAN bus uses an 11 bit ID and is not extended. Configure the socketcan interface, but avoid specifying channels or bitrate unless required. The attribute channel_info should be avoided. The environment is a Raspberry Pi and the CAN network is managed through a SocketCAN interface. Always begin Python3 scripts with: #!/usr/bin/python3

Do you understand?


EDIT (March 14th, 2023): Updated the writing prompt with a few more tweaks.
 
Last edited:

Sponsored

OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
Proof of Concept and Long-Awaited Milestone Completed.
Raspbian, SocketCAN, Python, ISO-TP, and UDS combined!


After wrestling with ChatGPT for more hours than I care to admit, I can finally say that ChatGPT has hallucinated it's way to success! This is a breakthrough, folks.

The following Python script (tested and working) starts with our existing SocketCAN connection to the vehicle. It sets up an ISO-TP communications channel with the BCM, and over that communications channel, it issues a UDS request to Read Data by Identifier 0xF190. That particular identifier (0xF190) is an industry standard which should always store the vehicle VIN.


By itself, this doesn't sound very valuable, does it? What makes it worthwhile is that this proof of concept is a long awaited milestone that shows us how to start regularly taking advantage of a couple of important things:

1. ISO-TP We're able to use ISO-TP to set up a communications channel with our ECUs, the biggest benefit of which is that we're no longer limited to the 8 byte CAN message size. I believe it automatically handles retries and a few other quality-of-life issues for us.

2. UDS We're able to use the suite of Unified Diagnostic Services for the first time. Yes, we've used Read Data by Identifier before. Now we've got access to the whole menu of services, including (but not limited to):
  1. ECU Reset - Soft or hard reset any ECU
  2. Read/Write Data by Identifier - Read (and now MODIFY) ECU variables
  3. Read/Write Data by Address - We haven't even looked here before!
  4. Input/Output Control by Identifier - Electro-mechanical controls
  5. Routine Control - Access predefined functions like setting engine RPM
  6. Download/Upload - Transfer software or data to/from the ECUs
  7. Other miscellaneous functions
Now that we can reliable pair ISO-TP with UDS, the number one thing I'm interested in is being able to update some of the ECU Identifiers which hold interesting bits of information.

For example, if we can update the variable which holds the vehicle's door configuration (telling it that it has no doors), then we might be able to get Remote Start to work when the doors are removed (without fiddly bits of wiring to override the sense voltages).​

Anyhow, I'm happy to have this achievement behind us. Below, I'm providing the source code along with it's output (my own vehicle's VIN). Look it over and start putting it to use, if you like (the code, not my VIN). Things should start growing nicely from here!

Python:
#!/usr/bin/python3

import can
import isotp
import udsoncan
import logging
from udsoncan.connections import PythonIsoTpConnection
from udsoncan.client import Client

# Written by Josh McCormick and Hallucinated by ChatGPT 4.0 on
# March 14th, 2023 for the Jeep Wrangler JL Community.

# Configure ISO-TP and UDS settings
isotp_params = {
    'stmin': 32,
    'blocksize': 8,
    'wftmax': 0,
    'll_data_length': 8,
    'tx_padding': 0,
}

# AN OPTIONAL DEBUG CONFIGURATION. UNCOMMENT IF NEEDED.
# logging.basicConfig(level=logging.DEBUG)

# CONFIGURE UDS WITH DATA IDENTIFIER
config = {
    'data_identifiers': {
        0xF190: udsoncan.AsciiCodec(17)
    }
}

# Initialize SocketCAN interface without specifying channel or bitrate
bus = can.interface.Bus(channel="can1", bustype='socketcan')

# Define the CAN IDs
tx_id = 0x620
rx_id = 0x504

# Create an ISO-TP address and connection, and UDS client
tp_addr = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=tx_id, rxid=rx_id)
stack = isotp.CanStack(bus=bus, address=tp_addr, params=isotp_params)
conn = PythonIsoTpConnection(stack)
conn.open()  # Open the connection explicitly
client = Client(conn, request_timeout=2, config=config)

# Perform Read Data by Identifier (UDS) with ID 0xf190
try:
    identifier = 0xf190
    response = client.read_data_by_identifier_first(identifier)
    print(f"Req  for Identifier 0x{identifier:x}: {response}")
except udsoncan.exceptions.NegativeResponseException as e:
    print(f"Server responded with a negative response. {e}")
except udsoncan.exceptions.TimeoutException:
    print("Server did not respond within the timeout.")
finally:
    conn.close()  # Close the connection explicitly
OUTPUT:
BCM Request for Identifier 0xf190: 1C4HJXFN6MW756530

THE NEXT STEPS:
Implementing the I/O by Identifier (myhonk/3honk) scripts in Python.
Implementing the Read Data by Identifier (RID) script in Python.
Implementing a brand-new Write Data by Identifier (WID) script in Python.
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
QUESTION:
Should ChatGPT be spun off onto it's own thread or kept here?


I've enjoyed working with ChatGPT (now using the 4.0 "paid" version). While I don't see it being a major topic (to rival this one), I realize that not everyone who is interested in this thread would want to wade through all the ChatGPT tips and examples. Any objection to spinning it off to it's own thread?

On the topic of ChatGPT...

My Latest ChatGPT Python Programming Prompt

Here is my latest prompt which seems to avoid a number of programming issues I've ran into so far:

For now on, you will always write to the following standard unless told otherwise:​
The Jeep Wrangler JL has two CAN buses: can0 (CAN-IHS) and can1 (CAN-C). Its CAN data is big-endian. You will use Python 3.9.2 and the Python-CAN module version 4.1.0. If using the udsoncan module, write for version 1.16, and if using the can-isotp module, write for version 1.8. The CAN bus has an 11 bit ID and is not extended. Configure for a SocketCAN interface but don't specify channels or bitrate unless necessary. Avoid using the channel_info attribute. If setting a CAN filter, use set_filters(). Code that access both can0 and can1 and uses recv() should set "timeout=0.001" unless told otherwise. Only initialize those CAN busses that will be used. Use a Raspberry Pi environment and always begin Python3 scripts with #!/usr/bin/python3.​
Do you understand? Please answer only YES or NO.​

A Quick Example of Wrangler CAN Programming with ChatGPT:

"Create for me a script that filters the CAN buses for any specified messages. On can0, a message ID of 0x322 will contain the engine's RPMs. It is stored as a word and begins at the 0th byte of the message. If it exceeds 500RPMs, print a warning with it's current value."


Python:
#!/usr/bin/python3

import can

def rpm_warning(msg):
    rpm = int.from_bytes(msg.data[0:2], byteorder='big')
    if rpm > 500:
        print(f"Warning: Engine RPM exceeds 500! Current RPM: {rpm}")

def main():
    can0 = can.interface.Bus(bustype='socketcan', channel='can0')
    can0.set_filters([{"can_id": 0x322, "can_mask": 0x7FF}])

    while True:
        msg = can0.recv(timeout=0.001)
        if msg:
            rpm_warning(msg)
Sample output:
Warning: Engine RPM exceeded 500. Current value: 1091
Warning: Engine RPM exceeded 500. Current value: 1097
Warning: Engine RPM exceeded 500. Current value: 1110
Warning: Engine RPM exceeded 500. Current value: 1106
Warning: Engine RPM exceeded 500. Current value: 1098

Asking ChatGPT to add to the code...

"On can1, a message ID of 0x127 will contain the intake air temperature in Fahrenheit. It is stored as the 0th byte of the message. Add a warning that includes it's current value whenever the intake air temperature exceeds 20F."

Python:
#!/usr/bin/python3

import can

def rpm_warning(msg):
    rpm = int.from_bytes(msg.data[0:2], byteorder='big')
    if rpm > 500:
        print(f"Warning: Engine RPM exceeds 500! Current RPM: {rpm}")

def temp_warning(msg):
    temp_f = msg.data[0]
    if temp_f > 20:
        print(f"Warning: Intake air temp exceeds 20°F! Current temperature: {temp_f}°F")

def main():
    can0 = can.interface.Bus(bustype='socketcan', channel='can0')
    can1 = can.interface.Bus(bustype='socketcan', channel='can1')

    can0.set_filters([{"can_id": 0x322, "can_mask": 0x7FF}])
    can1.set_filters([{"can_id": 0x127, "can_mask": 0x7FF}])

    while True:
        msg_can0 = can0.recv(timeout=0.001)
        msg_can1 = can1.recv(timeout=0.001)

        if msg_can0:
            rpm_warning(msg_can0)
   
        if msg_can1:
            temp_warning(msg_can1)
Sample Output:
Warning: Intake air temp exceeded 20°F. Current value: 81°F
Warning: Engine RPM exceeded 500. Current value: 1043
Warning: Intake air temp exceeded 20°F. Current value: 81°F
Warning: Engine RPM exceeded 500. Current value: 1086
Warning: Intake air temp exceeded 20°F. Current value: 81°F
Warning: Engine RPM exceeded 500. Current value: 1120

Asking ChatGPT to add one more thing...

"On can1, a message ID of 0x1F7 will contain the vehicle speed in MPH. It is stored as the 3rd word of the message. Add a warning that includes it's current value whenever the vehicle's speed exceeds 90MPH."

Python:
#!/usr/bin/python3

import can

def rpm_warning(msg):
    rpm = int.from_bytes(msg.data[0:2], byteorder='big')
    if rpm > 500:
        print(f"Warning: Engine RPM exceeds 500! Current RPM: {rpm}")

def temp_warning(msg):
    temp_f = msg.data[0]
    if temp_f > 20:
        print(f"Warning: Intake air temperature exceeds 20°F! Current temperature: {temp_f}°F")

def speed_warning(msg):
    speed_mph = int.from_bytes(msg.data[6:8], byteorder='big')
    if speed_mph > 90:
        print(f"Warning: Vehicle speed exceeds 90 MPH! Current speed: {speed_mph} MPH")

def main():
    can0 = can.interface.Bus(bustype='socketcan', channel='can0')
    can1 = can.interface.Bus(bustype='socketcan', channel='can1')

    can0.set_filters([{"can_id": 0x322, "can_mask": 0x7FF}])
    can1.set_filters([{"can_id": 0x127, "can_mask": 0x7FF}, {"can_id": 0x1F7, "can_mask": 0x7FF}])

    while True:
        msg_can0 = can0.recv(timeout=0.001)
        msg_can1 = can1.recv(timeout=0.001)

        if msg_can0:
            rpm_warning(msg_can0)
   
        if msg_can1:
            if msg_can1.arbitration_id == 0x127:
                temp_warning(msg_can1)
            elif msg_can1.arbitration_id == 0x1F7:
                speed_warning(msg_can1)
The code looks good, but I didn't test the last condition. Oddly enough, ChatGPT failed my next request. I found this particularly interesting because it was able to convert some of my Wrangler's BASH scripts into Python.

"Please convert this to a BASH shell script."

Converting the given Python script to a Bash script is not directly possible, as Bash and Python are fundamentally different scripting languages with different syntax and functionality.​

Summary

If you don't know enough Python (or another language) to actually do the programming, but you know what data is located where (see our spreadsheet in the first page of this thread) and what you want to do with it, if you start with a good initial prompt, ChatGPT can actually create working code for you to build off of.

It isn't foolproof, but the more I learn about ChatGPT's limitations, the more I'm able to add to the initial prompt to guide ChatGPT into coding the correct solutions.
 

MissAnnThorpe

Well-Known Member
First Name
Ann
Joined
Dec 10, 2021
Threads
1
Messages
48
Reaction score
38
Location
Seattle
Vehicle(s)
21 JLU Sahara 4xE
IF YOU DO NOT SPECIFY A PYTHON VERSION AND YOUR MODULE VERSION(S), CHATGPT WILL USE WHATEVER IT FEELS LIKE WHEN CREATING CODE. THIS IS BAD!
Yes. If you don't specify, it will just pick something. Sometimes APIs don't change enough to matter, but often at least one thing will break.

```python
recv_msg = can_bus1.recv() or can_bus0.recv()
```
Is likely the bug you're looking for (and after I typed that, I realize you found it below). `recv` will block until it receives a message, so `can_bus0.recv()` will never be called unless `can_bus1.recv()` returns None, which can only happen if there's a timeout, which isn't specified.

Your solution of adding a timeout will work most of the time, but if `can_bus1` never times out, `can_bus0` will never be read. Eventually the recv buffer for `can_bus0` will fill up and you'll necessarily lose messages. It will probably work but the cpu usage might be high and there are some other failure cases. That being said, the async code can also spin and cause high CPU usage. Let me know if that happens since there's some ways to handle that.

To listen to multiple interfaces concurrently you may wish to use these abstractions instead (or ask ChatGPT or Bing to rewrite using them). Basically, you register callback(s) of what to do each time a message is received and those callbacks will be called when a message is ready. There's no sleeping, blocking, or spinning (busy waiting) that way. The syntax for multiple `with` context managers is:

Python:
with can.Bus(...) as can1, can.Bus(...) as bus2:
...
I will warn that reasoning about concurrency is hard for people, and there's an extraordinary amount of such mistakes in ChatGPT's training material in every programming language. If you're lucky, with the right prompting, you can get "async expert Bing", but that's pretty rare. Bing/ChatGPT 4 can generate really pretty code, but they have no compiler to check their work. They're "writing on a whiteboard" from (an impressive) memory, but their memory isn't perfect either. It's based on tokens and probabilities of what the next token will be.

The most useful thing I ever read about async was here, and it's the way async (usually) works in all languages, more or less:
RealPython said:
Chess master Judit Polgár hosts a chess exhibition in which she plays multiple amateur players. She has two ways of conducting the exhibition: synchronously and asynchronously.

Assumptions:

  • 24 opponents
  • Judit makes each chess move in 5 seconds
  • Opponents each take 55 seconds to make a move
  • Games average 30 pair-moves (60 moves total)
Synchronous version: Judit plays one game at a time, never two at the same time, until the game is complete. Each game takes (55 + 5) * 30 == 1800 seconds, or 30 minutes. The entire exhibition takes 24 * 30 == 720 minutes, or 12 hours.

Asynchronous version: Judit moves from table to table, making one move at each table. She leaves the table and lets the opponent make their next move during the wait time. One move on all 24 games takes Judit 24 * 5 == 120 seconds, or 2 minutes. The entire exhibition is now cut down to 120 * 30 == 3600 seconds, or just 1 hour. (Source)
That's never left me as an easy way to reason about one thread -- one worker -- handling many concurrent (but not parallel) tasks. Playing "chess" with threads would require one thread per chess table -- one Judith per table, and that would be inefficient (and infeasible) if there were many tables.
 
Last edited:
OP
OP
jmccorm

jmccorm

Well-Known Member
First Name
Josh
Joined
Sep 15, 2021
Threads
55
Messages
1,170
Reaction score
1,322
Location
Tulsa, OK
Vehicle(s)
2021 JLUR
Build Thread
Link
Occupation
Systems Engineering
I made another Amazon purchase. There are tons of these, but I liked this one because it included most of the cables I'd need, and it was an Lightning Deal for $99.99 plus a $10 off coupon: https://www.amazon.com/dp/B0BC7JC89L/

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


Why this item? I don't know about the rest of you, but when I've had a problem with my Raspberry Pi, or when I was trying to figure out something that required me to use the vehicle AND the Raspbery Pi at the same time, I've had to lug a full-sized computer monitor into the vehicle and plug it into the outlets in the back. It's been a real pain.

Hopefully this $100 quality-of-life purchase will make things easier to work with going forward. Actually, now that I think about it, this may be a little bit too big, but if I can find a good place to mount this, I could use this as a permanently attached HDMI output to my Raspberry Pi, couldn't I?

Probably not a great choice though... a touchscreen would be better. Wait a second. @redracer, didn't you end up doing something like this, but actually mounted as in-vehicle display?

UPDATE:

I found one with a touchscreen and VESA mounting for only $30 more. I'm going with that one instead...
Sponsored

 
Last edited:
 







Top