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
  1. Battery State of Charge (SOC) - DID 2207 (ECM or Battery Management System - BMS)
  2. Hybrid/Electric Drive System Status - DID 22FD (Hybrid/Electric Control Module - HCM/ECM)
  3. Electric Motor Temperature - DID 22E0 (HCM/ECM)
  4. Battery Cell Voltage - DID 2218 (BMS)
  5. Charge/Discharge Power Limit - DID 22E4 (HCM/ECM)
  6. Hybrid/Electric Vehicle Speed - DID 22E1 (HCM/ECM)
  7. Regenerative Brake Control - DID 22E2 (HCM/ECM)
  8. Electric Motor Torque - DID 22E3 (HCM/ECM)
I went to validate what ChatGPT 3.5 was feeding us. When I started a new ChatGPT session to ask more information about the DIDs, the numbers (and to some degree, their functions) had changed completely.

VERDICT: This was a ChatGPT hallucination. At least, at a literal level. However, I think it still may be providing good guidance on the kinds of parameters we're going to find in the ECUs. Particularly good as a starting point of what to look for in the 4xe.

Wish it wasn't so. It was feeding me lie after lie with a straight face. When cross-checked against ChatGPT 4.0, it simply said that the DIDs were "proprietary" and refused to go any further. So at least the ChatGPT 3.5 hallucination was mildly helpful, IMHO.

Bummer.
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
On overcast days when the auto headlights turn on, I always find myself overriding the dash brightness to max as the highest dim value just isn't enough.
I can agree with you 100%. That's an awfully good piece of feedback.

I believe it's called the Panel Dimmer Switch. We're still a long ways from being able to accept requests like this, but I'll keep an eye out for that feature if we stumble across it. I'd want it, too.
 

Bzinsky

Well-Known Member
Joined
Apr 5, 2022
Threads
19
Messages
769
Reaction score
850
Location
Usa
Vehicle(s)
2022 rubicon 4xe
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!
There is a software limitation in all wrangler 4xe’s that will not allow you to program the differential gearing beyond a value 4.5. Which means as of now the 4xe is hardware limited to 4.11’s. I’m guessing if tazer couldn’t figure it out you couldn’t either?

I’m guessing you’d need a 4xe to try that on.

Also with 4xe’s, when certain conditions are met, they enter “Fuel Oil Refresh Mode” or FORM.

FORM allows you to run the 4xe in a parallel hybrid mode and because of this it sort of acts like a sport mode. I’ve emailed this suggestion to Tazer to include it as a sport mode. I believe a simple way to do this would be to modify sensor data for ambient temperature, so the vehicle thinks it’s cold AF outside.
 
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
There is a software limitation in all wrangler 4xe’s that will not allow you to program the differential gearing beyond a value 4.5. Which means as of now the 4xe is hardware limited to 4.11’s. I’m guessing if tazer couldn’t figure it out you couldn’t either?
Reading what you said, that means that there IS a publicly exposed variable for tire size that can be modified with the Write Data by Identifier service.

But the code (program) inside of the ECU itself sets a hard limit to what the number could be. This would mean disassembling, studying, and altering the ECU and then flashing back a new image of the ECU to your vehicle. Nobody that I know of, save for the big performance tunes, attempt to reflash your ECU. It's mega-involved. And likely warranty voiding.

While technically possible, it's impractical, and probably low-value enough that nobody is going to spend the great deal of time and effort necessary to track that down.

Right now, we (our public effort) are catching up with what the bulk of everyone else (AlphaOBD, JScan, Tazer) has been doing so far. We may actually get into disassembling some of the ECUs, but it isn't even on our radar at this point unless someone joins us who has a hankering to go in that direction.

I do have some experience reverse engineering someone else's code. Years ago, I ran the Tempest Code Project which attempted to disassemble and understand the arcade game Tempest. I wouldn't mind doing the same here!

SHORT ANSWER:
I'd love to see us at that point. I think we could figure it out, but we probably won't go there anytime soon. Not at least not for the next year or two, minimum. And you would likely have warranty issues as a result of having foreign ECU software. :(
 
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
Also with 4xe’s, when certain conditions are met, they enter “Fuel Oil Refresh Mode” or FORM.

FORM allows you to run the 4xe in a parallel hybrid mode and because of this it sort of acts like a sport mode. I’ve emailed this suggestion to Tazer to include it as a sport mode. I believe a simple way to do this would be to modify sensor data for ambient temperature, so the vehicle thinks it’s cold AF outside.
Got it.

I missed that part of the message. One of the aftermarket performance kits does something similar with the sensors on the turbo to increase boost pressure without altering the ECU's software. (They intercept the data from the sensors, fooling the ECU with false data.)

*If* the ambient temperature sensor is the key to that, the problem there is that the sensor isn't a CAN bus device, but a thermistor that's hard-wired into the BCM.

The BCM reads the voltages on the sensor and determines what the ambient temperature is. Then it broadcasts it to the rest of the vehicle. You could intercept and rewrite that data as it comes out of the BCM. Quite possible! (CAN0=BCM, CAN1=THE CAN BUS).

Something *much* easier might be to (electronically) adjust the voltage on the sensor itself so it always reads much colder than it is. But you can't get too high on the voltage, or it might automatically trigger the heat defroster on the windshield and the heated seats and steering wheel.

It may or may not affect turbo performance or create a fault, since it'll read a very low ambient temperature, and then the temperature going into the turbo, intercooler, and air manifold are going to be way hotter (during the summer). It could also affect the operation of the HVAC.

The general idea of voltages vs temperatures:

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


Yes, you'd need a 4xe to experiment. I'd go with locating and adjusting the thermistor before I'd try a CAN bus hack. But both have possibilities and are not too outrageous to pull off.

The thermistor route should be much easier.
 

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
There is a software limitation in all wrangler 4xe’s that will not allow you to program the differential gearing beyond a value 4.5. Which means as of now the 4xe is hardware limited to 4.11’s. I’m guessing if tazer couldn’t figure it out you couldn’t either?

I’m guessing you’d need a 4xe to try that on.

Also with 4xe’s, when certain conditions are met, they enter “Fuel Oil Refresh Mode” or FORM.

FORM allows you to run the 4xe in a parallel hybrid mode and because of this it sort of acts like a sport mode. I’ve emailed this suggestion to Tazer to include it as a sport mode. I believe a simple way to do this would be to modify sensor data for ambient temperature, so the vehicle thinks it’s cold AF outside.
One thing that has changed. I've read that the gearing limitation has been removed from 4xe built after some date, and supposedly with the newest software updates.
 
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
One thing that has changed. I've read that the gearing limitation has been removed from 4xe built after some date, and supposedly with the newest software updates.
If your dealer is drawing blanks on how to solve your problem:

If you've got a friendly enough dealer, you could tell them that you're willing to pay for them to reflash the ECUs (I wouldn't know the names of which ones on the 4xe, but I'd start with the TCM) with the latest software, with the understanding that it may or may not solve your problem. Solving your problem wouldn't be their responsibility, just reflashing the ECUs with the latest software, and nothing more. And they'd be more willing to accept that.

If nobody else has gone that route before, it sounds like a good bet, but there is always some chance it wouldn't help. Someone has to be the first one to try and find out. But if I was a 4xe owner and I was needing to regear, I'd definitely give that a shot.

But as a disclaimer, I'm just making assumptions based on what you're telling me. And I don't know if dealers are willing to do that or not, but I do know that they've got the capability.

Otherwise... invoke JeepCares?
 

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
If your dealer is drawing blanks on how to solve your problem:

If you've got a friendly enough dealer, you could tell them that you're willing to pay for them to reflash the ECUs (I wouldn't know the names of which ones on the 4xe, but I'd start with the TCM) with the latest software, with the understanding that it may or may not solve your problem. Solving your problem wouldn't be their responsibility, just reflashing the ECUs with the latest software, and nothing more. And they'd be more willing to accept that.

If nobody else has gone that route before, it sounds like a good bet, but there is always some chance it wouldn't help. Someone has to be the first one to try and find out. But if I was a 4xe owner and I was needing to regear, I'd definitely give that a shot.

But as a disclaimer, I'm just making assumptions based on what you're telling me. And I don't know if dealers are willing to do that or not, but I do know that they've got the capability.

Otherwise... invoke JeepCares?
Oh, my preferred dealer was happy as a lark to charge an hour of shop time ($180) to run any/all available updates, pair my new remote start keys, and add the remote start sales code in. But, yes, other dealers are not so willing. I asked both a dealer in my home town and the selling dealer (80 miles away) for this and was quoted over $500 for just the keys to be paired.
 

Bzinsky

Well-Known Member
Joined
Apr 5, 2022
Threads
19
Messages
769
Reaction score
850
Location
Usa
Vehicle(s)
2022 rubicon 4xe
Reading what you said, that means that there IS a publicly exposed variable for tire size that can be modified with the Write Data by Identifier service.

But the code (program) inside of the ECU itself sets a hard limit to what the number could be. This would mean disassembling, studying, and altering the ECU and then flashing back a new image of the ECU to your vehicle. Nobody that I know of, save for the big performance tunes, attempt to reflash your ECU. It's mega-involved. And likely warranty voiding.

While technically possible, it's impractical, and probably low-value enough that nobody is going to spend the great deal of time and effort necessary to track that down.

Right now, we (our public effort) are catching up with what the bulk of everyone else (AlphaOBD, JScan, Tazer) has been doing so far. We may actually get into disassembling some of the ECUs, but it isn't even on our radar at this point unless someone joins us who has a hankering to go in that direction.

I do have some experience reverse engineering someone else's code. Years ago, I ran the Tempest Code Project which attempted to disassemble and understand the arcade game Tempest. I wouldn't mind doing the same here!

SHORT ANSWER:
I'd love to see us at that point. I think we could figure it out, but we probably won't go there anytime soon. Not at least not for the next year or two, minimum. And you would likely have warranty issues as a result of having foreign ECU software. :(
Well as of right now nobody is doing anything with the gpec5 ecu, nobody has cracked it!
 
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
BCM $A01A - OIL CHANGE STATISTICS

Here's another discovery that I wish I could claim credit for. You can determine how many engine revolutions, how many hours, and how many kilometers it's been since your last oil change. (Or since you've done an "oil change reset" on your dash.)

Ready?

# rid -d bcm A01A # Remember, -d is DECIMAL output
13 152 0 38 2 245

ENGINE REVOLUTIONS SINCE OIL CHANGE:
We take the first two numbers (in big-endian format) and multiply it by 1000. So...​
((13*256)+152)*1000 = 3,480,000 engine revs

HOURS SINCE OIL CHANGE:
We take the next two numbers (in big-endian format) to calculate the number of hours.​
(0*256)+38 = 38 hours
KILOMETERS SINCE OIL CHANGE:
We take the final two numbers (in big-endian format) to calculate the number of kilometers.​
(2*256)+245 = 757 kilometers (470 miles)

Enjoy!

And one very special thank-you to our benevolent anonymous patron for sharing this with us!
 

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
Read OBD Values (v 1.1)

The Python code below reads vehicle parameters via OBD-II standards and prints them to the screen. This is designed as an example of how to query the standard OBD parameters.

USAGE:
obd2 -p (Provides a list of supported PIDs)
obd2 [PID]

SAMPLE OUTPUT:
Rich (BB code):
Calculated Engine Load: 21.568627450980394 %
Engine Coolant Temperature: 82 C
Short Term Fuel Trim - Bank 1: -0.78125 %
Long Term Fuel Trim - Bank 1: -2.34375 %
Fuel Rail Pressure (gauge): 516 kPa (gauge)
Intake Manifold Absolute Pressure: 28 kPa (absolute)
Engine RPM: 951.0 rpm
Vehicle Speed: 0 km/h
Timing Advance: 6.0 %
Intake Air Temperature: 32 C
Throttle Position: 12.941176470588236 %
Run Time Since Engine Start: 522 seconds
Distance Traveled with MIL On: 0 km
Fuel Tank Level Input: 57.64705882352941 %
Warm-ups Since DTCs Cleared: 6
Distance Traveled Since DTCs Cleared: 220 km
Catalyst Temperature - Bank 1, Sensor 1: 416.0 C
Control module voltage: 14.159 V
Absolute Load Value: 23.529411764705884 %
Commanded Equivalence Ratio: 1.0
Relative Throttle Position: 2.7450980392156863 %
Ambient Air Temperature: 26 C
Commanded Throttle Actuator Control: 2.7450980392156863 %
Fuel Type: Gasoline Fuel Type
Engine Fuel Rate: 1.5 L/h
Actual Engine Torque Fraction: 8 %
Reference Torque: 400 Nm
Charge Air Cooler Temperature (CACT): [('Sensor 2', 51)] C
Commanded EGR and EGR Error: 9 %
Odometer: 28655.4 km
SOURCE CODE:
Python:
#!/usr/bin/env python3

import can
import sys
import time

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

pid_info = {
    0x00: ("PIDs supported [01-20]", ""),
    0x04: ("Calculated Engine Load", "%"),
    0x05: ("Engine Coolant Temperature", "C"),
    0x06: ("Short Term Fuel Trim - Bank 1", "%"),
    0x07: ("Long Term Fuel Trim - Bank 1", "%"),
    0x0A: ("Fuel Rail Pressure (gauge)", "kPa"),
    0x0B: ("Intake Manifold Absolute Pressure", "kPa"),
    0x0C: ("Engine RPM", "rpm"),
    0x0D: ("Vehicle Speed", "km/h"),
    0x0E: ("Timing Advance", "°"),
    0x0F: ("Intake Air Temperature", "C"),
    0x11: ("Throttle Position", "%"),
    0x13: ("O2 Sensors Present (Banks 1 and 2)", ""),
    0x15: ("O2 Sensor 1 - Voltage and Short Term Fuel Trim", "V and %"),
    0x1C: ("OBD Standards Supported by the Vehicle", ""),
    0x1F: ("Run Time Since Engine Start", "seconds"),
    0x20: ("PIDs supported [21-40]", ""),
    0x21: ("Distance Traveled with MIL On", "km"),
    0x2C: ("Commanded EGR", "%"),
    0x2D: ("EGR Error", "%"),
    0x2E: ("Commanded Evaporative Purge", "%"),
    0x2F: ("Fuel Tank Level Input", "%"),
    0x30: ("Warm-ups Since DTCs Cleared", ""),
    0x31: ("Distance Traveled Since DTCs Cleared", "km"),
    0x32: ("Evap. System Vapor Pressure", "Pa"),
    0x33: ("Absolute Barometric Pressure", "kPa"),
    0x34: ("O2S1_WR_lambda(1):Equivalence Ratio", ""),
    0x3C: ("Catalyst Temperature - Bank 1, Sensor 1", "C"),
    0x40: ("PIDs supported [41-60]", ""),
    0x41: ("Monitor status this drive cycle", ""),
    0x42: ("Control module voltage", "V"),
    0x43: ("Absolute Load Value", "%"),
    0x44: ("Commanded Equivalence Ratio", ""),
    0x45: ("Relative Throttle Position", "%"),
    0x46: ("Ambient Air Temperature", "C"),
    0x47: ("Absolute Throttle Position B", "%"),
    0x48: ("Absolute Throttle Position C", "%"),
    0x49: ("Accelerator Pedal Position D", "%"),
    0x4A: ("Accelerator Pedal Position E", "%"),
    0x4C: ("Commanded Throttle Actuator Control", "%"),
    0x4F: ("Turbocharger Compressor Inlet Pressure", "kPa"),
    0x51: ("Fuel Type", ""),
    0x5E: ("Engine Fuel Rate", "L/h"),
    0x60: ("PIDs supported [61-80]", ""),
    0x62: ("Actual Engine Torque Fraction", "%"),
    0x63: ("Reference Torque", "Nm"),
    0x68: ("Exhaust Gas Temperature Bank 1, Sensor 1", "C"),
    0x6B: ("Exhaust Gas Temperature Bank 1, Sensor 2", "C"),
    0x6D: ("Fuel Pressure Control System", ""),
    0x77: ("Charge Air Cooler Temperature (CACT)", "C"),
    0x80: ("PIDs supported [81-A0]", ""),
    0x8E: ("Commanded EGR and EGR Error", "%"),
    0x9D: ("Engine Fuel Rate (Alternative)", ""),
    0x9E: ("Engine Exhaust Flow Rate", ""),
    0xA0: ("PIDs supported [A1-C0]", ""),
    0xA6: ("Odometer", "km"),
}

def send_obd_query(bus, pid):
    # 1/10th of a second from now, send the OBD-II query with the
    # user-provided PID.
    bus = can.interface.Bus(bustype='socketcan', channel='can1')
    filter = [{"can_id": 0x7e0, "can_mask": 0x7FF}, {"can_id": 0x7e8, "can_mask": 0x7FF}]
    bus.set_filters(filter)
    msg = can.Message(arbitration_id=0x7E0, data=[0x02, 0x01, pid, 0, 0, 0, 0, 0],
                      is_extended_id=False)

    # Send the OBD-II query to the BCM.
    # NOTE: This REQUIRES the vehicle to be running.
    bus.send(msg)

    # Collect any messages on CAN-C which have an ID of $7E8.
    # After a predefined number of seconds, stop collecting.
    messages = []
    start_time = time.monotonic()
    while time.monotonic() - start_time < 2:
        recv_msg = bus.recv(timeout=0.5)
        if recv_msg is not None:
            if recv_msg.arbitration_id == 0x7E8:
                messages.append(recv_msg)
                break
    # print ("MESSAGE ",messages)
    # Only look for messages which are a successful response to our
    # particular OBD-II query.
    # UDS query. If we received more than one response, only use
    # the last one. And isolate the output so it only contains the
    # byte that we're looking for.
    response = None
    for message in messages[::-1]:
        if message.data[1] == 0x41 and message.data[2] == pid:
            response = message
            break
    if response is None:
        return None

    data = response.data[3:]
    data_hex = "".join(f"{b:02X}" for b in data)
    return data_hex

def bit_encoded_pid_support(data_hex, base_pid):
    data = bytearray.fromhex(data_hex)
    supported_pids = ""

    for byte_index, byte in enumerate(data):
        for bit_index in range(8):
            if byte & (1 << bit_index):
                supported_pid = base_pid + (byte_index * 8) + bit_index
                # Skip unused bits A0 through A6
                if supported_pid >= 0xA7:
                    supported_pids += f"0x{supported_pid:02X} "

    return supported_pids.strip()

def convert_obd_response(pid, data_hex):
    data = bytearray.fromhex(data_hex)
    if pid == 0x00:
        return bit_encoded_pid_support(data_hex, 0x01), "PIDs supported [01-20]"
    elif pid == 0x01:
        mil_on = bool(data[0] & (1 << 7))
        num_dtc = data[0] & 0b01111111
        engine_type = "Spark Ignition" if data[1] & (1 << 3) == 0 else "Compression Ignition"

        common_tests = {
            "Components": {
                "availability": bool(data[1] & (1 << 2)),
                "completeness": not bool(data[1] & (1 << 6))
            },
            "Fuel System": {
                "availability": bool(data[1] & (1 << 1)),
                "completeness": not bool(data[1] & (1 << 5))
            },
            "Misfire": {
                "availability": bool(data[1] & (1 << 0)),
                "completeness": not bool(data[1] & (1 << 4))
            }
        }

        if engine_type == "Spark Ignition":
            tests = {
                "EGR and/or VVT System": {"availability": bool(data[2] & (1 << 7)), "completeness": not bool(data[3] & (1 << 7))},
                "Oxygen Sensor Heater": {"availability": bool(data[2] & (1 << 6)), "completeness": not bool(data[3] & (1 << 6))},
                "Oxygen Sensor": {"availability": bool(data[2] & (1 << 5)), "completeness": not bool(data[3] & (1 << 5))},
                "Gasoline Particulate Filter": {"availability": bool(data[2] & (1 << 4)), "completeness": not bool(data[3] & (1 << 4))},
                "Secondary Air System": {"availability": bool(data[2] & (1 << 3)), "completeness": not bool(data[3] & (1 << 3))},
                "Evaporative System": {"availability": bool(data[2] & (1 << 2)), "completeness": not bool(data[3] & (1 << 2))},
                "Heated Catalyst": {"availability": bool(data[2] & (1 << 1)), "completeness": not bool(data[3] & (1 << 1))},
                "Catalyst": {"availability": bool(data[2] & (1 << 0)), "completeness": not bool(data[3] & (1 << 0))}
            }
        else:  # Compression Ignition (Diesel)
            tests = {
                "EGR and/or VVT System": {"availability": bool(data[2] & (1 << 7)), "completeness": not bool(data[3] & (1 << 7))},
                "PM filter monitoring": {"availability": bool(data[2] & (1 << 6)), "completeness": not bool(data[3] & (1 << 6))},
                "Exhaust Gas Sensor": {"availability": bool(data[2] & (1 << 5)), "completeness": not bool(data[3] & (1 << 5))},
                "Reserved1": {"availability": bool(data[2] & (1 << 4)), "completeness": not bool(data[3] & (1 << 4))},
                "Boost Pressure": {"availability": bool(data[2] & (1 << 3)), "completeness": not bool(data[3] & (1 << 3))},
                "Reserved2": {"availability": bool(data[2] & (1 << 2)), "completeness": not bool(data[3] & (1 << 2))},
                "NOx/SCR Monitor": {"availability": bool(data[2] & (1 << 1)), "completeness": not bool(data[3] & (1 << 1))},
                "NMHC Catalyst": {"availability": bool(data[2] & (1 << 0)), "completeness": not bool(data[3] & (1 << 0))}
            }

        # Combine common tests and engine type specific tests
        tests.update(common_tests)

        # Return the data
        result = {
            "MIL": "On" if mil_on else "Off",
            "Number of DTCs": num_dtc,
            "Engine Type": engine_type,
            "Tests": tests
        }
        return result, ""
    elif pid == 0x01:
        fuel_system_statuses = {
            0: "The motor is off",
            1: "Open loop due to insufficient engine temperature",
            2: "Closed loop, using oxygen sensor feedback to determine fuel mix",
            4: "Open loop due to engine load OR fuel cut due to deceleration",
            8: "Open loop due to system failure",
            16: "Closed loop, using at least one oxygen sensor but there is a fault in the feedback system"
        }
        system1_status = fuel_system_statuses.get(data[0], "Invalid response")
        system2_status = fuel_system_statuses.get(data[1], "Invalid response")
        return f"Fuel System #1: {system1_status} Fuel System #2: {system2_status}",""
    elif pid == 0x04:
        return data[0] * 100 / 255, "%"
    elif pid == 0x05:
        return data[0] - 40, "C"
    elif pid == 0x06:
        return (data[0] - 128) * 100 / 128, "%"
    elif pid == 0x07:
        return (data[0] - 128) * 100 / 128, "%"
    elif pid == 0x0A:
        return data[0] * 3, "kPa (gauge)"
    elif pid == 0x0B:
        return data[0], "kPa (absolute)"
    elif pid == 0x0C:
        return ((data[0] * 256) + data[1]) / 4, "rpm"
    elif pid == 0x0D:
        return data[0], "km/h"
    elif pid == 0x0E:
        return (data[0] / 2) - 64, "%"
    elif pid == 0x0F:
        return data[0] - 40, "C"
    elif pid == 0x11:
        return (data[0] * 100) / 255, "%"
    elif pid == 0x13:
        sensor_mapping = {
            0: "Bank 1 Sensor 1",
            1: "Bank 1 Sensor 2",
            2: "Bank 1 Sensor 3",
            3: "Bank 1 Sensor 4",
            4: "Bank 2 Sensor 1",
            5: "Bank 2 Sensor 2",
            6: "Bank 2 Sensor 3",
            7: "Bank 2 Sensor 4"
        }
        sensors = [sensor_mapping[i] for i in range(8) if data[0] & (1 << i)]
        return ', '.join(sensors), ""
    elif pid == 0x15:
        return data[0] * 0.005, "V", (data[1] - 128) * 100 / 128, "%" if data[1] != 0xFF else "sensor not used for trim calc"
    elif pid == 0x1C:
        obd_standards = {
            1: "OBD-II as defined by the CARB",
            2: "OBD as defined by the EPA",
            3: "OBD and OBD-II",
            4: "OBD-I",
            5: "Not OBD compliant",
            6: "EOBD (Europe)",
            7: "EOBD and OBD-II",
            8: "EOBD and OBD",
            9: "EOBD, OBD and OBD II",
            10: "JOBD (Japan)",
            11: "JOBD and OBD II",
            12: "JOBD and EOBD",
            13: "JOBD, EOBD, and OBD II",
            14: "Reserved",
            15: "Reserved",
            16: "Reserved",
            17: "Engine Manufacturer Diagnostics (EMD)",
            18: "Engine Manufacturer Diagnostics Enhanced (EMD+)",
            19: "Heavy Duty On-Board Diagnostics (Child/Partial) (HD OBD-C)",
            20: "Heavy Duty On-Board Diagnostics (HD OBD)",
            21: "World Wide Harmonized OBD (WWH OBD)",
            22: "Reserved",
            23: "Heavy Duty Euro OBD Stage I without NOx control (HD EOBD-I)",
            24: "Heavy Duty Euro OBD Stage I with NOx control (HD EOBD-I N)",
            25: "Heavy Duty Euro OBD Stage II without NOx control (HD EOBD-II)",
            26: "Heavy Duty Euro OBD Stage II with NOx control (HD EOBD-II N)",
            27: "Reserved",
            28: "Brazil OBD Phase 1 (OBDBr-1)",
            29: "Brazil OBD Phase 2 (OBDBr-2)",
            30: "Korean OBD (KOBD)",
            31: "India OBD I (IOBD I)",
            32: "India OBD II (IOBD II)",
            33: "Heavy Duty Euro OBD Stage VI (HD EOBD-IV)",
            34: "Reserved"
        }
        return obd_standards.get(data[0], "Unknown"), ""
    elif pid == 0x1F:
        return (data[0] * 256) + data[1], "seconds"
    elif pid == 0x20:
        return bit_encoded_pid_support(data_hex, 0x21), "PIDs supported [21-40]"
    elif pid == 0x21:
        return (data[0] * 256) + data[1], "km"
    elif pid == 0x2C:
        return data[0] * 100 / 255, "%"
    elif pid == 0x2D:
        return (data[0] - 128) * 100 / 128, "%"
    elif pid == 0x2E:
        return data[0] * 100 / 255, "%"
    elif pid == 0x2F:
        return data[0] * 100 / 255, "%"
    elif pid == 0x30:
        return data[0], ""
    elif pid == 0x31:
        return (data[0] * 256) + data[1], "km"
    elif pid == 0x32:
        return (((data[0] * 256) + data[1]) / 4), "Pa"
    elif pid == 0x33:
        return data[0], "kPa (absolute)"
    elif pid == 0x34:
        return ((data[0] * 256) + data[1]) / 32768, ""
    elif pid == 0x3C:
        return ((data[0] * 256) + data[1]) / 10 - 40, "C"
    elif pid == 0x40:
        return bit_encoded_pid_support(data_hex, 0x41), "PIDs supported [41-60]"
    elif pid == 0x41:
        mil_status = (data[1] & 0x80) != 0
        dtc_count = data[1] & 0x7F
        spark_ignition = (data[2] & 0x08) == 0
        common_tests = data[2]
        if spark_ignition:
            engine_specific_tests_avail = data[3]
            engine_specific_tests_complete = data[4]
        else:
            engine_specific_tests_avail = data[3]
            engine_specific_tests_complete = data[4]
        return {
            "MIL status": mil_status,
            "DTC count": dtc_count,
            "Spark ignition": spark_ignition,
            "Common tests": common_tests,
            "Engine specific tests available": engine_specific_tests_avail,
            "Engine specific tests complete": engine_specific_tests_complete
        }
    elif pid == 0x42:
        return ((data[0] * 256) + data[1]) / 1000, "V"
    elif pid == 0x43:
        return ((data[0] * 256) + data[1]) * 100 / 255, "%"
    elif pid == 0x44:
        return ((data[0] * 256) + data[1]) / 32768, ""
    elif pid == 0x45:
        return data[0] * 100 / 255, "%"
    elif pid == 0x46:
        return data[0] - 40, "C"
    elif pid == 0x47:
        return (data[0] * 100) / 255, "%"
    elif pid == 0x48:
        return (data[0] * 100) / 255, "%"
    elif pid == 0x49:
        return (data[0] * 100) / 255, "%"
    elif pid == 0x4A:
        return (data[0] * 100) / 255, "%"
    elif pid == 0x4C:
        return data[0] * 100 / 255, "%"
    elif pid == 0x4F:
        return (data[0], data[1], data[2], data[3] * 10), ("ratio", "V", "mA", "kPa")
    elif pid == 0x51:
        fuel_type_value = data[0]
        fuel_type_mapping = {
            0: "Not available",
            1: "Gasoline",
            2: "Methanol",
            3: "Ethanol",
            4: "Diesel",
            5: "LPG",
            6: "CNG",
            7: "Propane",
            8: "Electric",
            9: "Bifuel running Gasoline",
            10: "Bifuel running Methanol",
            11: "Bifuel running Ethanol",
            12: "Bifuel running LPG",
            13: "Bifuel running CNG",
            14: "Bifuel running Propane",
            15: "Bifuel running Electricity",
            16: "Bifuel running electric and combustion engine",
            17: "Hybrid gasoline",
            18: "Hybrid Ethanol",
            19: "Hybrid Diesel",
            20: "Hybrid Electric",
            21: "Hybrid running electric and combustion engine",
            22: "Hybrid Regenerative",
            23: "Bifuel running diesel",
        }
        return fuel_type_mapping.get(fuel_type_value, "Unknown"), "Fuel Type"
    elif pid == 0x5E:
        return ((data[0] * 256) + data[1]) / 20, "L/h"
    elif pid == 0x60:
        return data[0], "PIDs supported [61-80]"
        return bit_encoded_pid_support(data_hex, 0x61), "PIDs supported [61-80]"
    elif pid == 0x62:
        return data[0] - 125, "%"
    elif pid == 0x63:
        return (data[0] * 256) + data[1], "Nm"
    elif pid == 0x68:
        return data_hex, "C"
    elif pid == 0x6B:
        return data_hex, "unknown format"
    elif pid == 0x6D:
        return data_hex, "unknown format"
    elif pid == 0x77:
        sensor_temperatures = []
        if data[0] & (1 << 0):
            sensor_temperatures.append(("Sensor 1", data[1] - 40))
        if data[0] & (1 << 1):
            sensor_temperatures.append(("Sensor 2", data[2] - 40))
        if data[0] & (1 << 2):
            sensor_temperatures.append(("Sensor 3", data[3] - 40))
        return sensor_temperatures, "C"
    elif pid == 0x80:
        return bit_encoded_pid_support(data_hex, 0x81), "PIDs supported [81-A0]"
    elif pid == 0x8E:
        return data[0] - 125, "%"
    elif pid == 0x9D:
        return data_hex, "Engine Fuel Rate"
    elif pid == 0x9E:
        return data_hex, "Engine Exhaust Flow Rate"
    elif pid == 0xA0:
        return bit_encoded_pid_support(data_hex, 0xA1), "PIDs supported [A7-C0]"
    elif pid == 0xA6:
        return (data[0] * (2 ** 24) + data[1] * (2 ** 16) + data[2] * (2 ** 8) + data[3]) / 10, "km"
    else:
        return data_hex,""

def list_supported_pids():
    for pid, (description, unit) in pid_info.items():
        if not unit:
            unit = "no units"
        print(f"0x{pid:02X}: {description} ({unit})")

# Take the user-provided OBD-II PID and convert it to uppercase.
# If they did not provide a pid, print a help message and exit.

def main():
    if len(sys.argv) < 2:
        print(f"USAGE: {sys.argv[0]} [XX] [...]       or\n       {sys.argv[0]} -p to list available parameters")
        sys.exit(1)

    if sys.argv[1] == "-p":
        list_supported_pids()
        sys.exit(0)

    bus = can.interface.Bus(bustype='socketcan', channel='can1')

    for raw_pid in sys.argv[1:]:
        pid = raw_pid.upper()

        if pid.startswith("0X"):
            pid = pid[2:]
        if len(pid) == 1:
            pid = "0" + pid

        try:
            pid = int(pid, 16)
        except ValueError:
            print(f"ERROR: {raw_pid} is not a valid hexadecimal value")
            continue

        data_hex = send_obd_query(bus, pid)
        if data_hex is None:
            print(f"{raw_pid}: NO RESPONSE")
            continue

        value, unit = convert_obd_response(pid, data_hex)
        description = pid_info.get(pid, ("Unknown PID", ""))[0]
        print(f"{description}: {value} {unit}")

if __name__ == "__main__":
    main()
ADDITIONAL NOTES:
As this is an initial version, a few parameters may be cryptic or inaccurate (in an obvious way). If there's one you need fixed, let me know. And, yes, there's no way I could have done this without ChatGPT 4.0. Those conversions would have been a nightmare!
 
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
Read OBD Values (v 1.0)

The Python code below reads vehicle parameters via OBD-II standards and prints them to the screen. This is designed as an example of how to query the standard OBD parameters.
Interesting enough, if you use the following prompt with ChatGPT:

The following parameters describe a 2020 Jeep Wrangler Unlimited with a 2.0L turbo engine at idle. Does anything stand out to you?
[COPY/PASTE YOUR PARAMETERS HERE]

Then it will comment on your engine's statistics:

Based on the parameters provided, there are a few things that stand out to me:
  • The engine load is relatively low at 21.57%, which is to be expected at idle.
  • The engine coolant temperature of 82°C is within the normal operating range for most engines and indicates that the engine has reached its normal operating temperature.
  • The short term fuel trim (STFT) and long term fuel trim (LTFT) values are both negative, which suggests that the engine is running slightly rich at idle. However, the fuel trims are within a normal range, so this is not a cause for concern.
  • The fuel rail pressure is at 516 kPa (gauge), which is also within the normal range for most engines at idle.
  • The intake manifold absolute pressure (MAP) is at 28 kPa (absolute), which is also normal for idle conditions.
  • The engine RPM is at 951, which is within the expected range for idle speed.
  • The engine fuel rate of 1.5 L/h is consistent with idle fuel consumption for a typical 3.6L V6 engine, which is the engine used in most 2020 Jeep Wranglers.
  • The charge air cooler temperature (CACT) is at 51°C, which is within the normal range for most engines.
Overall, the parameters listed appear to be within normal ranges for a 2020 Jeep Wrangler at idle, and nothing stands out as a cause for concern.
Pretty neat! Note that if you have a vehicle newer then 2020, go ahead and tell it that it is the 2020 model, as it is familiar with some of that vehicle's parameters.
 

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
This is fun...

Code:
./obd2.py 0xA6
Odometer: 12312.8 km
./obd2.py 0x2D
EGR Error: 0.0 %
./obd2.py 0x0C
Engine RPM: 0.0 rpm
./obd2.py 0x0F
Intake Air Temperature: 15 C
./obd2.py 0x2F
Fuel Tank Level Input: 12.941176470588236 %
 
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
This is fun...

Code:
./obd2.py 0xA6
Odometer: 12312.8 km
./obd2.py 0x2D
EGR Error: 0.0 %
./obd2.py 0x0C
Engine RPM: 0.0 rpm
./obd2.py 0x0F
Intake Air Temperature: 15 C
./obd2.py 0x2F
Fuel Tank Level Input: 12.941176470588236 %
Cool! Here's a shell script that will pull ALL the OBD2 parameters in order. You'll find that some of them are broken in a really obvious way. I might circle back around to fix those, but if there's one or more you particularly need, let me know.

Bash:
#!/bin/bash

# loop through each value in the data array and print it
for value in 0x00 0x04 0x05 0x06 0x07 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x11 0x13 0x15 0x1C 0x1F 0x20 0x21 0x2C 0x2D 0x2E 0x2F 0x30 0x31 0x32 0x33 0x34 0x3C 0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4A 0x4C 0x4F 0x51 0x5E 0x60 0x62 0x63 0x68 0x6B 0x6D 0x77 0x80 0x8E 0x9D 0x9E 0xA0 0xA6
do
    echo "$value ======================================================="
    obd2a $value
    echo ""
done
 
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
Read OBD Values (v 1.1)

The Python code below reads vehicle parameters via OBD-II standards and prints them to the screen. This is designed as an example of how to query the standard OBD parameters.
Version 1.1 of the Read OBD Values script has been released. The new version can take an extended command line parameter, such as:

obd2 0x04 0x05 0x06 0x07 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x11 0x1C 0x1F 0x20 0x21 0x2C 0x2D 0x2E 0x2F 0x30 0x31 0x32 0x33 0x34 0x3C 0x42 0x43 0x44 0x45 0x46 0x47 0x49 0x4A 0x4C 0x4F 0x51 0x5E 0x62 0x63 0x8E 0xA6

That's all on one command line. It finishes it in 3 seconds (much faster than calling each one individually). If motivated, it might be possible to make this request even faster by sending the entire list to the ECU at once. (I believe it's possible, but I haven't tried anything more than sending one at a time.)

I know it'd be better to get lots of parameters as a single snapshot than for it's parameters to be skewed over the duration of a few seconds.
Sponsored

 
 







Top