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
ChatGPT might be enough of an assist to work through the "send data by identifier" problem I was having in the CAN-TP protocol. (The one where you need to send a block of data to a module of a specific size using the special command that was made for that process.)

I've got a bit more cleaning work to do on my vehicle, but I'm putting myself as cautiously optimistic for a breakthrough using ChatGPT looking over my shoulder.
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'm trying to get back up-to-speed with where everything ended up here. Things really started to get complex with all the ISO-14229 [Unified Diagnostic Services] and ISO-15765 [ISO-TP messaging protocol].

I think the next real breakthrough will have to be in figuring out how to craft a Unified Diagnostic Services SID $2E "Write Data by Identifier" in order to push a new set of data into a module. With that, we can start making much more substantial discoveries. But that's easier said than done.

I've lost all my old documents, so starting out again from scratch makes things difficult. So I'm starting by getting my feet wet. I used ChatGPT to refactor a small portion of the RID script. As it turns out, we can store a module's name/source id/destination id/bus as an array, which gets rid of a rats nest of "IF" statements.

Other than that, no much here. Just getting my feet wet again (and keeping the Wrangler on a charger so I don't drain the battery).


Bash:
#!/bin/bash

error() {
  echo "N/A"
  # Exit with a failure result code
  exit 1
}

initialize () {
  # Issue a "Get Data By Identifier" command to the specified module
  # and wait (with timeout) for a response.

  ( sleep 0.02 ; \
    echo "22 $ID" | /usr/bin/isotpsend -s $SOURCE -d $DEST -p 00:00 -P l $BUS ) &

  # Collect the response. If no response in 8 seconds, abort.
  # NOTE: Most of the time, 8 seconds is excessive. But some Identifiers
  #       will actually take this much time, if not more.

  COMMAND="timeout -s 1 8 /usr/bin/isotprecv -s $SOURCE -d $DEST -p 00:00 -P l $BUS"

  RESPONSE="$( $COMMAND )"

# Get rid of the last character (trailing space)
  RESPONSE=${RESPONSE}

  # We could get rid of ALL SPACING between hexadecimal numbers,
  # but let's not do that. Code left here in case it is useful elsewhere.
  # DATA=$( echo ${RESPONSE:9} | tr -d "[:blank:]" )

  # POPULATE A FEW VARIABLES WE'LL USE BELOW

  DATA=$( echo ${RESPONSE:9} )
  RID="${RESPONSE:0:2}"
  SID="22"
  SUCCESS="$( printf "%X" $(( 0x$SID + 0x40 )) )"
  OK=99

  if [ "$RID" == "$SUCCESS" ] ; then
    OK=0
    echo "SUCCESS"
    echo "${RESPONSE:9}"

    # We need a better solution, but for now, we're storing data as
    # FILENAMES under the /home/pi/modules/ directory. This works
    # up until the point we have a lot of data to store, then the
    # filename becomes too big. In that case, instead of storing the
    # data as a filename, it creates a file called FILE and stores
    # the data in there. Yuck, yuck, yuck!

    if [ ! -d /home/pi/modules/$MODULE/$OPERATION/${ID:0:2}${ID:3:2} ] ; then
      mkdir -p /home/pi/modules/$MODULE/$OPERATION/${ID:0:2}${ID:3:2}
    fi
    touch /home/pi/modules/$MODULE/$OPERATION/${ID:0:2}${ID:3:2}/"${DATA}"
    if [ $? -ne 0 ] ; then
        echo $DATA | cat > /home/pi/modules/$MODULE/$OPERATION/${ID:0:2}${ID:3:2}/FILE
    fi
  fi

  if [ "$RID" == "7F" ] ; then
    OK=1
    echo "NEGATIVE RESPONSE"
    echo "$RESPONSE"
  fi

  if [ "$OK" == "99" ]
    then
      echo "UNKNOWN RESPONSE"
      echo ${RESPONSE}
    fi

exit $OK

}

# MAIN CODE BEGINS HERE

MODULE=$( echo $1 | tr '[:upper:]' '[:lower:]' )
OPERATION=rid
OK=0

# In this section, we translate module names into the concrete variables
# we need to use.

declare -A MODULE_INFO=(
  ["bcm"]="620 504 can1"  # BODY CONTROL MODULE
  ["sccm"]="763 4E3 can1"  # STEERING COLUMN CONTROL MODULE
  ["radio"]="7BF 53F can0"  # UCONNECT RADIO MODULE
  ["ipcm"]="742 4C2 can1"  # INSTRUMENT PANEL CLUSTER MODULE
  ["evic"]="742 4C2 can1"  # EVIC is an alias for IPCM
  ["hvac"]="783 503 can0"  # HEATING/VENT/AIR-CONDITIONING MODULE
)

OK=0

# Get the module information from the associative array
if [ "${MODULE_INFO[$MODULE]+exists}" ]; then
  OK=1
  read -r SOURCE DEST BUS <<< "${MODULE_INFO[$MODULE]}"
  # DEBUG OUTPUT
  # echo "Module: $MODULE, Source: $SOURCE, Destination: $DEST, Bus: $BUS"
else
  echo "INVALID MODULE SPECIFIED: $MODULE"
  exit 99
fi

shift 1

INPUT=$( printf "%04X" 0x$@ 2>/dev/null )
if [ $? -ne 0 ]
  then
     echo "INVALID IDENTIFIER SPECIFIED: $@"
     exit 1
  fi

ID="${INPUT:0:2} ${INPUT:2:2}"
# echo "INPUT: $INPUT   ID: $ID"

# Send our UDS request and store the response.
initialize
 
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
Python Code Samples (for Raspberry Pi)?

Going forward, I'm going to want to write my code around the UDSONCAN (Unified Diagnostic Message processing) and CAN-ISOTP modules (large data session and fragmentation management) for Python. But before I can even go there, I need to start by learning how to do just simple CAN work with Python.

Does anyone have some (simple) Python code samples that they can share? Starting off simple would be great. Like listening on the CAN bus for an event or two to happen and then display them on the screen? The simpler the better (no graphical components, in particular).
 
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 - I found your "redracer.py" and (not understanding Python) ran it through ChatGPT for a little refactoring. The on-screen formatting was 95% preserved and your code makes a bit more sense to me this way. I thought I'd share it back in case you wanted to steal how it handle on-screen labels or the cleaner way it set the CAN mask filters.

Aside from text positioning, please let me know if this code needs fixing in some way because I know next to nothing about Python, much less using it for CAN! I have no idea what these "try:" and "finally:" sections are supposed to be telling the code to do, but I left them in place.

I'm over my head right now, but that's a good thing.

Python:
#!/usr/bin/python3
      # jeep canbus live data display

# import libraries
import can
import curses

# Define the text labels for the data fields
FIELD_LABELS = [
    ('Batt', 'Vdc'),
    ('Roll', 'Tilt', 'Yaw'),
    (' '),
    ('RPM', 'MPH', 'Gear', '4x4'),
    (' '),
    ('Steering Angle', ' ', 'Turn', 'Temp', 'Pres'),
    (' '),
    ('Tire psi',),
    (' '),
    ('Brake Act', 'Pedal', 'Motion'),
    (' '),
    ('Temps IAT', ' ', 'Coolant'),
    (' '),
    ('Lights',)
]

# Define the CAN IDs and masks for filtering
CAN_FILTER = [
    {'can_id': 0x2C2, 'can_mask': 0xFFF},
    {'can_id': 0x02B, 'can_mask': 0xFFF},
    {'can_id': 0x322, 'can_mask': 0xFFF},
    {'can_id': 0x023, 'can_mask': 0xFFF},
    {'can_id': 0x077, 'can_mask': 0xFFF},
    {'can_id': 0x127, 'can_mask': 0xFFF},
    {'can_id': 0x128, 'can_mask': 0xFFF},
    {'can_id': 0x037, 'can_mask': 0xFFF},
    {'can_id': 0x071, 'can_mask': 0xFFF},
    {'can_id': 0x093, 'can_mask': 0xFFF},
    {'can_id': 0x2FF, 'can_mask': 0xFFF},
    {'can_id': 0x30A, 'can_mask': 0xFFF},
    {'can_id': 0x277, 'can_mask': 0xFFF},
    {'can_id': 0x128, 'can_mask': 0xFFF},
    {'can_id': 0x291, 'can_mask': 0xFFF},
    {'can_id': 0x079, 'can_mask': 0xFFF},
    {'can_id': 0x296, 'can_mask': 0xFFF}
]

def main():

    # Initialize the curses screen
    stdscr = curses.initscr()

    try:
        # Display the field labels on the screen
        for y, row in enumerate(FIELD_LABELS):
            for x, label in enumerate(row):
                stdscr.addstr(y+1, x*13+1, label)
        stdscr.refresh()

        # Set up the CAN bus interface and filters
        bus = can.interface.Bus('', bustype='socketcan', filter=CAN_FILTER)
        # TODO: add code to receive and display CAN data

        # iterate through all messages received
        for msg in bus:
          # match a can id and can bus
          if msg.arbitration_id == 0x2C2 and msg.channel == 'can0':
            # place data using str to convert float data to a string
            # this is using the 3rd hex word, deviding by 10
            stdscr.addstr(1,6,'%-5s' % str(msg.data[2] / 10))
            # once the message has been processes, refresh the screen with the data
            stdscr.addstr(1,20,'%-6s' % str(((msg.data[0]<<8) + msg.data[1]) / 100))
            stdscr.refresh()
          # match another message
          if msg.arbitration_id == 0x02B and msg.channel == 'can1':
            # place data, multiple words each in its own spot
            stdscr.addstr(2,6,'%-6s' % str(((msg.data[0]<<8) + msg.data[1] - 2048) / 10))
            stdscr.addstr(2,20,'%-6s' % str(((msg.data[2]<<8) + msg.data[3] - 2032) / 10))
            stdscr.addstr(2,30,'%-6s' % str(((msg.data[4]<<8) + msg.data[5] - 2048) / 10))
            # once the message has been processes, refresh the screen with the data
            stdscr.refresh()
          if msg.arbitration_id == 0x322 and msg.channel == 'can0':
            rpmstr =  str(((msg.data[0]<<8) +  msg.data[1]))
            if rpmstr == "65535":
              rpmstr = str(0)
            stdscr.addstr(4,6,'%-6s' % rpmstr)
            stdscr.addstr(4,20,'%-4s' % str(((msg.data[2]<<8) + msg.data[3]) / 200))
            # once the message has been processes, refresh the screen with the data
            stdscr.refresh()
          if msg.arbitration_id == 0x023 and msg.channel == 'can1':
            stdscr.addstr(6,16,'%-6s' % str(((msg.data[0]<<8) + msg.data[1]) - 0x1000))
            stdscr.addstr(6,32,'%-6s' % str(((msg.data[2]<<8) + msg.data[3]) - 0x1000))
            stdscr.refresh()
          if msg.arbitration_id == 0x079 and msg.channel == 'can1':
            stdscr.addstr(10,12,'%-5s' % str(((msg.data[0]<<8) + msg.data[1]) &0x0FFF))
            stdscr.addstr(10,25,'%-5s' % str((msg.data[2]<<8) + msg.data[3]))
            if (msg.data[0] & 0xF0) == 0x80:
              movingstat = 'Stopped'
            elif (msg.data[0] & 0xF0) == 0x00:
              movingstat = 'Moving'
            else:
              movingstat = str(msg.data[0] & 0xF0)
            stdscr.addstr(10,40,'%-10s' % movingstat)
            stdscr.refresh()
          if msg.arbitration_id == 0x127 and msg.channel == 'can1':
            stdscr.addstr(12,15,'%-3s' % str(round((((msg.data[0] - 40) * (9 / 5)) + 32))))
            stdscr.addstr(12,30,'%-3s' % str(round((((msg.data[1] - 40) * (9 / 5)) + 32))))
            stdscr.addstr(12,35,'%-3s' % str(msg.data[2]))
            stdscr.addstr(12,40,'%-3s' % str(msg.data[6]))
            stdscr.refresh()
          if msg.arbitration_id == 0x291 and msg.channel == 'can1':
            stdscr.addstr(14,10,'%-3s' % str(msg.data[0]))
            stdscr.addstr(14,14,'%-3s' % str(msg.data[1]))
            stdscr.addstr(14,18,'%-3s' % str(msg.data[2]))
            stdscr.addstr(14,22,'%-3s' % str(msg.data[3]))
            stdscr.addstr(14,26,'%-3s' % str(msg.data[4]))
            stdscr.addstr(14,30,'%-3s' % str(msg.data[5]))
            stdscr.addstr(14,34,'%-3s' % str(msg.data[6]))
            stdscr.addstr(14,38,'%-3s' % str(msg.data[7]))
            stdscr.refresh()
          if msg.arbitration_id == 0x037 and msg.channel == 'can1':
            stdscr.addstr(14,10,'%-6s' % str((msg.data[0]<<8) + msg.data[1]))
            stdscr.addstr(14,20,'%-6s' % str(msg.data[5]))
            stdscr.refresh()
          if msg.arbitration_id == 0x071 and msg.channel == 'can1':
            stdscr.addstr(14,10,'%-6s' % str((msg.data[0]<<8) + msg.data[1]))
            stdscr.addstr(14,20,'%-6s' % str(msg.data[4]))
            stdscr.refresh()
          if msg.arbitration_id == 0x093 and msg.channel == 'can1':
            if msg.data[2] == 0x4E:
              mtstatus = 'N'
            elif msg.data[2] == 0x52:
              mtstatus = 'R'
            elif msg.data[2] == 0x31:
              mtstatus = '1'
            elif msg.data[2] == 0x32:
              mtstatus = '2'
            elif msg.data[2] == 0x33:
              mtstatus = '3'
            elif msg.data[2] == 0x34:
              mtstatus = '4'
            elif msg.data[2] == 0x35:
              mtstatus = '5'
            elif msg.data[2] == 0x36:
              mtstatus = '6'
            else:
              mtstatus = '?'
            stdscr.addstr(4,30,'%-2s' % mtstatus)
            stdscr.refresh()
          if msg.arbitration_id == 0x277 and msg.channel == 'can1':
            if msg.data[1] == 0x02:
              mtstatus = 'N'
            elif msg.data[0] == 0x00:
              mtstatus = '2HI'
            elif msg.data[0] == 0x10:
              mtstatus = '4HI'
            elif msg.data[0] == 0x20:
              mtstatus = 'N'
            elif msg.data[0] == 0x40:
              mtstatus = '4LO'
            elif msg.data[0] == 0x80:
              mtstatus = 'SHIFT'
            else:
              mtstatus = msg.data[0]
            stdscr.addstr(4,40,'%-5s' % mtstatus)
            stdscr.refresh()
          if msg.arbitration_id == 0x277 and msg.channel == 'can1':
            stdscr.addstr(16,10,'%-6s' % str(msg.data[0]))
            stdscr.addstr(16,20,'%-6s' % str(msg.data[2]))
            stdscr.refresh()
          if msg.arbitration_id == 0x128 and msg.channel == 'can1':
            stdscr.addstr(6,45,'%-5s' % str(round((((msg.data[1]) * (9 / 5)) + 32))))
            stdscr.addstr(6,55,'%-5s' % str(msg.data[2]))
            stdscr.addstr(6,60,'%-5s' % str(msg.data[5]))
            stdscr.refresh()
          if msg.arbitration_id == 0x296 and msg.channel == 'can1':
            stdscr.addstr(8,15,'%-3s' % str(msg.data[3]))
            stdscr.addstr(8,20,'%-3s' % str(msg.data[4]))
            stdscr.addstr(8,25,'%-3s' % str(msg.data[5]))
            stdscr.addstr(8,30,'%-3s' % str(msg.data[6]))

    finally:
        # Clean up the curses screen
        curses.endwin()

if __name__ == '__main__':
    main()
 

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 - ChatGPT also started suggesting doing some crazy stuff with your code that I liked, but it didn't give me a full cut-and-paste so I'll just have to provide code snippets below. It looks like, based on an incoming msg.arbitration_id, it will automatically call the appropriate function based on a pre-defined map? Get this...

NOTE: May need bug fixes!

Python:
# Define a dictionary to map CAN IDs to the corresponding update function
CAN_ID_TO_FUNC = {
    0x2C2: update_batt,
    0x02B: update_roll_tilt_yaw,
    0x322: update_rpm_mph_gear_4x4,
    0x023: update_steering_angle_turn_temp_pres,
    0x079: update_brake_act_pedal_motion,
    0x127: update_temps_iat_coolant,
    0x291: update_tire_psi,
    0x037: update_lights,
    0x071: update_lights2,
    0x093: update_mt_gear,
    0x277: update_transfer_case,
    0x128: update_lights3,
    0x296: update_296
}

def update_batt(stdscr, data):
    stdscr.addstr(1, 6, '%-5s' % str(data[2] / 10))
    stdscr.addstr(1, 20, '%-6s' % str(((data[0] << 8) + data[1]) / 100))
    stdscr.refresh()

def update_roll_tilt_yaw(stdscr, data):
    roll = ((data[0] << 8) | data[1]) - 0x8000
    tilt = ((data[2] << 8) | data[3]) - 0x8000
    yaw = ((data[4] << 8) | data[5]) - 0x8000

    # Display the roll, tilt, and yaw data
    stdscr.addstr(1, 14, f'{roll:+6.2f} deg')
    stdscr.addstr(1, 30, f'{tilt:+6.2f} deg')
    stdscr.addstr(1, 46, f'{yaw:+6.2f} deg')
    stdscr.refresh()

def update_rpm_mph_gear_4x4(stdscr, data):
    rpm = (data[0] << 8) + data[1]
    mph = round(((data[2] << 8) + data[3]) / 200, 1)
    gear = data[4] & 0x0F
    if data[5] & 0x08:
        four_wheel_drive = '4WD'
    else:
        four_wheel_drive = '2WD'
    stdscr.addstr(3, 0, '{:<10}{:<10}{:<10}{:<10}'.format(rpm, mph, gear, four_wheel_drive))

def update_steering_angle_turn_temp_pres(stdscr, data):
    stdscr.addstr(6, 1, ' ' * 80)  # Clear previous data
    stdscr.addstr(6, 1, f'{data["Steering Angle"]:.2f}')
    stdscr.addstr(6, 15, data['Turn'])
    stdscr.addstr(6, 25, f'{data["Temp"]:.1f}')
    stdscr.addstr(6, 35, f'{data["Pres"]:.1f}')

def update_brake_act_pedal_motion(stdscr, data):
    # Display brake actuation data
    brake_act = data.get('brake_act')
    if brake_act is not None:
        stdscr.addstr(5, 1, f"Brake Act: {brake_act}")

    # Display pedal position data
    pedal_pos = data.get('pedal_pos')
    if pedal_pos is not None:
        stdscr.addstr(5, 20, f"Pedal Pos: {pedal_pos}")

    # Display motion status data
    motion_status = data.get('motion_status')
    if motion_status is not None:
        stdscr.addstr(5, 40, f"Motion: {motion_status}")

def update_temps_iat_coolant(stdscr, data):
    """
    Update the screen with the IAT and coolant temperatures.

    Args:
        stdscr: The curses screen object.
        data: The CAN message data.
    """
    # Decode the IAT and coolant temperatures
    iat_temp = round((data[0] - 40) * (9 / 5) + 32)
    coolant_temp = round((data[1] - 40) * (9 / 5) + 32)

    # Update the screen with the temperatures
    stdscr.addstr(12, 15, f'{iat_temp:3}')
    stdscr.addstr(12, 30, f'{coolant_temp:3}')
    stdscr.addstr(12, 35, f'{data[2]:3}')
    stdscr.addstr(12, 40, f'{data[6]:3}')

def update_tire_psi(stdscr, data):
    # Extract the tire pressure data from the CAN message
    tire_1 = data.data[0]
    tire_2 = data.data[1]
    tire_3 = data.data[2]
    tire_4 = data.data[3]

    # Update the display with the tire pressure data
    stdscr.addstr(4, 70, f"{tire_1} {tire_2} {tire_3} {tire_4}")
    stdscr.refresh()

def update_lights(stdscr, data):
    stdscr.addstr(22, 1, "Lights")
    stdscr.addstr(23, 1, "-------")
    stdscr.addstr(23, 2, "Light Mode: %d" % ((data[0] >> 5) & 0x07))
    stdscr.addstr(24, 2, "Light State: %d" % (data[0] & 0x1F))
    stdscr.refresh()

def update_lights2(stdscr, data):
    # Extract data from the CAN message
    light_1 = (data[0] >> 4) & 0x01
    light_2 = (data[0] >> 3) & 0x01
    light_3 = (data[0] >> 2) & 0x01
    light_4 = (data[0] >> 1) & 0x01
    light_5 = data[0] & 0x01
    light_6 = (data[1] >> 7) & 0x01
    light_7 = (data[1] >> 6) & 0x01
    light_8 = (data[1] >> 5) & 0x01
    light_9 = (data[1] >> 4) & 0x01
    light_10 = (data[1] >> 3) & 0x01
    light_11 = (data[1] >> 2) & 0x01
    light_12 = (data[1] >> 1) & 0x01
    light_13 = data[1] & 0x01
    light_14 = (data[2] >> 7) & 0x01
    light_15 = (data[2] >> 6) & 0x01
    light_16 = (data[2] >> 5) & 0x01
    light_17 = (data[2] >> 4) & 0x01
    light_18 = (data[2] >> 3) & 0x01
    light_19 = (data[2] >> 2) & 0x01
    light_20 = (data[2] >> 1) & 0x01
    light_21 = data[2] & 0x01
    light_22 = (data[3] >> 7) & 0x01
    light_23 = (data[3] >> 6) & 0x01
    light_24 = (data[3] >> 5) & 0x01
    light_25 = (data[3] >> 4) & 0x01
    light_26 = (data[3] >> 3) & 0x01
    light_27 = (data[3] >> 2) & 0x01
    light_28 = (data[3] >> 1) & 0x01
    light_29 = data[3] & 0x01

    # Print the lights status on the screen
    stdscr.addstr(0, 60, f'Lights: {light_1} {light_2} {light_3} {light_4} {light_5} {light_6} {light_7} {light_8} {light_9} {light_10} {light_11} {light_12} {light_13} {light_14} {light_15} {light_16} {light_17} {light_18} {light_19} {light_20} {light_21} {light_22} {light_23} {light_24} {light_25} {light_26} {light_27} {light_28} {light_29}')

    stdscr.refresh()

def update_mt_gear(stdscr, data):
    if data[2] == 0x4E:
        mtstatus = 'N'
    elif data[2] == 0x52:
        mtstatus = 'R'
    elif data[2] == 0x31:
        mtstatus = '1'
    elif data[2] == 0x32:
        mtstatus = '2'
    elif data[2] == 0x33:
        mtstatus = '3'
    elif data[2] == 0x34:
        mtstatus = '4'
    elif data[2] == 0x35:
        mtstatus = '5'
    elif data[2] == 0x36:
        mtstatus = '6'
    else:
        mtstatus = '?'
    stdscr.addstr(4, 30, '%-2s' % mtstatus)

    if data[0] & 0xF0 == 0x80:
        movingstat = 'Stopped'
    elif data[0] & 0xF0 == 0x00:
        movingstat = 'Moving'
    else:
        movingstat = str(data[0] & 0xF0)
    stdscr.addstr(4, 40, '%-10s' % movingstat)

def update_transfer_case(stdscr, data):
    if data[1] == 0x02:
        tcstatus = 'N'
    elif data[0] == 0x00:
        tcstatus = '2HI'
    elif data[0] == 0x10:
        tcstatus = '4HI'
    elif data[0] == 0x20:
        tcstatus = 'N'
    elif data[0] == 0x40:
        tcstatus = '4LO'
    elif data[0] == 0x80:
        tcstatus = 'SHIFT'
    else:
        tcstatus = data[0]
    stdscr.addstr(4, 50, '%-5s' % tcstatus)

def update_lights3(stdscr, data):
    # unpack data bytes
    headlights, highbeams, fog_lights, park_lights, brake_lights = data
   
    # format data for display
    headlights_str = 'On' if headlights else 'Off'
    highbeams_str = 'On' if highbeams else 'Off'
    fog_lights_str = 'On' if fog_lights else 'Off'
    park_lights_str = 'On' if park_lights else 'Off'
    brake_lights_str = 'On' if brake_lights else 'Off'
   
    # update screen
    stdscr.addstr(7, 50, f'{headlights_str:>3}')
    stdscr.addstr(8, 50, f'{highbeams_str:>3}')
    stdscr.addstr(9, 50, f'{fog_lights_str:>3}')
    stdscr.addstr(10, 50, f'{park_lights_str:>3}')
    stdscr.addstr(11, 50, f'{brake_lights_str:>3}')
    stdscr.refresh()

def update_296(stdscr, data):
    stdscr.addstr(8,15,'%-3s' % str(msg.data[3]))
    stdscr.addstr(8,20,'%-3s' % str(msg.data[4]))
    stdscr.addstr(8,25,'%-3s' % str(msg.data[5]))
    stdscr.addstr(8,30,'%-3s' % str(msg.data[6]))

# Initialize the curses screen
stdscr = curses.initscr()

try:
    # Display the field labels on the screen
    display_field_labels(stdscr)
    stdscr.refresh()

    # Set up the CAN bus interface and filters
    bus = can.interface.Bus('', bustype='socketcan', filter=CAN_FILTER)

    # Receive and process CAN data
    for msg in bus:
        if msg.arbitration_id in CAN_ID_TO_FUNC:
            CAN_ID_TO_FUNC[msg.arbitration_id](stdscr, msg)
            stdscr.refresh()

finally:
    # Clean up the curses screen
    curses.endwin()
 
Last edited:

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
nice! I have been meaning to get back into my python code, as now with my 4xe I have more data to discover.

Oh, and @jmccorm I don't know if you have taken another look, but I kept a repository of all of the bash scripts that you published, along with my python in a github repository. Have a look...

jeep-jl-powernet-scripts
 
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
Thanks for keeping that! I'll be reviewing that shortly.

With the help of ChatGPT, I think I've figured out enough of the udsoncan python module to be able to re-implement the RID script (the script that reads data by ID # from an ECU module).

It wrote me some code, so I'll be testing it out tomorrow and seeing if it works. If so, then I'm actually hopeful for getting that elusive "write data by identifier" script working. That'd be a script that allows us to plug our own values into some of the Wrangler's ECU modules. Being able to write into the modules means being able to substantially change the Wrangler's behavior.

So according to ChatGPT...

Read Data by Identifier
Here's an example of how to send a Read Data by Identifier (SID 22) message for ID $A051 on a module that sends at 0x620 and receives at 0x504, using python-can-isotp, and then reading the response back into a variable:

Python:
import can
import isotp

# set up CAN bus
can_bus = can.interface.Bus(bustype='socketcan', channel='can1', bitrate=500000)

# set up ISO-TP protocol parameters
tp_addr = isotp.Address(isotp.AddressingMode.NormalFixed_29bits, source_address=0x620, target_address=0x504)
tp_params = isotp.TransportProtocolParameters(st_min=10, st_max=100, bs=15, wft_max=2)

# create ISO-TP connection
tp_connection = isotp.CanTransport(can_bus, address=tp_addr, params=tp_params)

# send Read Data by Identifier (SID 22) message for ID $A051
data_identifier = 0xA051
request_data = bytes([data_identifier])
response_data = tp_connection.send(request_data, sid=0x22, did=0x00, max_chunk_size=7, timeout=1)

# print the response data
print(response_data)
Write Data by Identifier
Here's an example of how to send a Write Data by Identifier (SID 2E) message for ID $A051 with a payload of "$01234567890123456789012345" on a module that sends at 0x620 and receives at 0x504, using python-can-isotp, and then reading the response back into a variable:

Python:
import can
import isotp

# set up CAN bus
can_bus = can.interface.Bus(bustype='socketcan', channel='can1', bitrate=500000)

# set up ISO-TP protocol parameters
tp_addr = isotp.Address(isotp.AddressingMode.NormalFixed_29bits, source_address=0x620, target_address=0x504)
tp_params = isotp.TransportProtocolParameters(st_min=10, st_max=100, bs=15, wft_max=2)

# create ISO-TP connection
tp_connection = isotp.CanTransport(can_bus, address=tp_addr, params=tp_params)

# send Write Data by Identifier (SID 2E) message for ID $A051
data_identifier = 0xA051
payload = b'\x01\x23\x45\x67\x89\x01\x23\x45\x67\x89\x01\x23\x45'
request_data = bytes([data_identifier]) + payload
response_data = tp_connection.send(request_data, sid=0x2E, did=0x00, max_chunk_size=7, timeout=1)

# print the response data
print(response_data)
We'll see how testing goes tomorrow.
 
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 found someone who provided a sample code for Write Data By Identifier using UDSONCAN (Python):



Python:
import udsoncan
import sys
from os import path
from udsoncan.connections import IsoTPSocketConnection
from udsoncan.client import Client
from udsoncan import configs
from udsoncan import exceptions
from udsoncan import services


class DataRecord:
    address: int
    parse_type: int
    description: str

    def __init__(self, address, parse_type, description):
        self.address = address
        self.parse_type = parse_type
        self.description = description


class GenericStringCodec(udsoncan.DidCodec):
    def encode(self, val):
        return bytes(val)

    def decode(self, payload):
        return str(payload, "ascii")

    def __len__(self):
        raise udsoncan.DidCodec.ReadAllRemainingData


class GenericBytesCodec(udsoncan.DidCodec):
    def encode(self, val):
        return bytes(val)

    def decode(self, payload):
        return payload.hex()

    def __len__(self):
        raise udsoncan.DidCodec.ReadAllRemainingData


data_records = [
    DataRecord(0x0600, 1, "VW Coding Value"),
]

conn = IsoTPSocketConnection("can0", rxid=0x7E8, txid=0x7E0)

with Client(conn, request_timeout=5, config=configs.default_client_config) as client:
    client.config["data_identifiers"] = {}
    for data_record in data_records:
        if data_record.parse_type == 0:
            client.config["data_identifiers"][data_record.address] = GenericStringCodec
        else:
            client.config["data_identifiers"][data_record.address] = GenericBytesCodec

    client.config["data_identifiers"][0xF198] = GenericBytesCodec

    print("Opening extended diagnostic session...")
    client.change_session(
        services.DiagnosticSessionControl.Session.extendedDiagnosticSession
    )

    print("Reading ECU information...")
    for i in range(0, len(data_records)):
        did = data_records[i]
        try:
            response = client.read_data_by_identifier_first(did.address)
            print(did.description + " : " + response)
        except exceptions.NegativeResponseException as e:
            print(
                'Server refused our request for service %s with code "%s" (0x%02x)'
                % (e.response.service.get_name(), e.response.code_name, e.response.code)
            )
        except exceptions.InvalidResponseException as e:
            print("Server sent an invalid payload : %s" % e.response.original_payload)
        except exceptions.UnexpectedResponseException as e:
            print("Server sent an invalid payload : %s" % e.response.original_payload)

    print("Writing Workshop ID")
    client.write_data_by_identifier(
        0xF198,
        bytes(
            [
                0x42,  # Workshop code
                0x04,
                0x20,
                0x42,
                0xB1,
                0x3D,
            ]
        ),
    )
    client.write_data_by_identifier(0x0600, bytes.fromhex(sys.argv[1]))
I still don't know enough Python, but it looks like they're writing code into identifier 0xF198 into a module with a readID of 0x7E8 and a sendID of 0x7E0.
 
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'm getting a little disappointed. It seems that ChatGPT is able to do some really neat manipulations (even turning my shell script into Python code), but the actual code isn't working. It seems like it's missing some small piece of information what's going on and I'm ending up with code that either does nothing or errors out each time. :headbang:
 

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
I'm getting a little disappointed. It seems that ChatGPT is able to do some really neat manipulations (even turning my shell script into Python code), but the actual code isn't working. It seems like it's missing some small piece of information what's going on and I'm ending up with code that either does nothing or errors out each time. :headbang:
Hmm, that is frustrating.

One quick suggestion that I can offer from when I started learning python, is to make sure that you are using the right python version for what the code was intended. Python 3 has subtle differences than 1/2, enough that it breaks most code. Mostly it's in how libraries are loaded and functions are called.
 
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 quick suggestion that I can offer from when I started learning python, is to make sure that you are using the right python version for what the code was intended. Python 3 has subtle differences than 1/2, enough that it breaks most code. Mostly it's in how libraries are loaded and functions are called.
I think that's part of what I'm running into (I saw some errors about my Python 2 version being too old while I was using pip to install the udsoncan module.) That's really unfortunate, but I'm glad you pointed that out.

Code:
 raspberrypi /tmp # pip install --upgrade udsoncan
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting udsoncan
  Using cached https://files.pythonhosted.org/packages/ab/75/fadd1d9cf212605bebeb993fe06127b995593181e518d423825ffdc7020b/udsoncan-1.0.tar.gz
udsoncan requires Python '>=3.0' but the running Python is 2.7.16
It looks like I'm going to have to take two or three steps back before I can take some steps forward again. I've started backing up my existing SD cards and I'm going to start from scratch with a new Raspberry Pi OS Lite (64 bit) build (Februrary 21st, 2023 - Debian Bullseye) with no desktop environment.
 

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
I think that's part of what I'm running into (I saw some errors about my Python 2 version being too old while I was using pip to install the udsoncan module.) That's really unfortunate, but I'm glad you pointed that out.

Code:
 raspberrypi /tmp # pip install --upgrade udsoncan
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting udsoncan
  Using cached https://files.pythonhosted.org/packages/ab/75/fadd1d9cf212605bebeb993fe06127b995593181e518d423825ffdc7020b/udsoncan-1.0.tar.gz
udsoncan requires Python '>=3.0' but the running Python is 2.7.16
It looks like I'm going to have to take two or three steps back before I can take some steps forward again. I've started backing up my existing SD cards and I'm going to start from scratch with a new Raspberry Pi OS Lite (64 bit) build (Februrary 21st, 2023 - Debian Bullseye) with no desktop environment.
No steps back necessary. python 2 & 3 can co-exist, and usually both are installed. instead of using the python and pip commands, try using python3 and pip3.

The other way is to change the system default to python 3. Try using the command:
sudo update-alternatives --config python
If you see python 3 then just choose it and try again.
https://linuxconfig.org/how-to-change-from-default-to-alternative-python-version-on-debian-linux

As a note, all of the python that I have written for our project is for python 3
 

Ratbert

Well-Known Member
First Name
John
Joined
Jun 20, 2020
Threads
159
Messages
16,096
Reaction score
25,098
Location
PNW
Vehicle(s)
2022 AEV JL370 JLURD
Build Thread
Link
Occupation
Software Engineer
Clubs
 
I'm getting a little disappointed. It seems that ChatGPT is able to do some really neat manipulations (even turning my shell script into Python code), but the actual code isn't working. It seems like it's missing some small piece of information what's going on and I'm ending up with code that either does nothing or errors out each time. :headbang:
ChatGPT will improve over time. Eventually it'll likely replace us software engineers.
 
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 - 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.
Sponsored

 
 







Top