Skip to content

Commit

Permalink
j1939-22 (j1939 fd)
Browse files Browse the repository at this point in the history
implemented so far:
- multi-pg send/receive with possibility to enter a time limit
- Broadcast Announce Message (BAM) send/receive with up to 4 concurrent sessions and up to 15300 bytes of data per session
- RTS/CTS (Destination Specific) send/receive with up to 8 concurrent sessions and up to 16777215 bytes of data per session

added j1939-22 transport protocol and multi-pg examples

the data-link-layer (j1939-21 or j1939-22) can be selected when creating the ECU instance.
the data link layer cannot be changed dynamically, because j1939 does not allow mixing of data link layers either.
  • Loading branch information
juergen committed Jun 27, 2021
1 parent 99ec5f3 commit 546e4e7
Show file tree
Hide file tree
Showing 14 changed files with 1,738 additions and 677 deletions.
59 changes: 30 additions & 29 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@ SAE J1939 for Python
:alt: Documentation build Status


An implementation of the CAN SAE J1939 standard for Python.
This implementation was taken from https://github.com/benkfra/j1939, as no
further development took place.
An implementation of the CAN SAE J1939 standard for Python.
This is the first J1939-22 (J1939-FD) implementation!

If you experience a problem or think the stack would not behave properly, do
If you experience a problem or think the stack would not behave properly, do
not hesitate to open a ticket or write an email.
Pullrequests are of course even more welcome!

The project uses the python-can_ package to support multiple hardware drivers.
At the time of writing the supported interfaces are
The project uses the python-can_ package to support multiple hardware drivers.
At the time of writing the supported interfaces are

* CAN over Serial
* CAN over Serial / SLCAN
* CANalyst-II
* IXXAT Virtual CAN Interface
* Kvaser’s CANLIB
* Kvasers CANLIB
* NEOVI Interface
* NI-CAN
* PCAN Basic API
Expand All @@ -41,14 +40,14 @@ At the time of writing the supported interfaces are
Overview
--------

An SAE J1939 CAN Network consists of multiple Electronic Control Units (ECUs).
Each ECU can have one or more Controller Applications (CAs). Each CA has its
own (unique) Address on the bus. This address is either acquired within the
An SAE J1939 CAN Network consists of multiple Electronic Control Units (ECUs).
Each ECU can have one or more Controller Applications (CAs). Each CA has its
own (unique) Address on the bus. This address is either acquired within the
address claiming procedure or set to a fixed value. In the latter case, the CA
has to announce its address to the bus to check whether it is free.

The CAN messages in a SAE J1939 network are called Protocol Data Units (PDUs).
This definition is not completely correct, but close enough to think of PDUs
This definition is not completely correct, but close enough to think of PDUs
as the CAN messages.


Expand All @@ -57,17 +56,22 @@ Features

* one ElectronicControlUnit (ECU) can hold multiple ControllerApplications (CA)
* ECU (CA) Naming according SAE J1939/81
* (under construction) full featured address claiming procedure according SAE J1939/81
* full support of transport protocol (up to 1785 bytes) according SAE J1939/21 for sending and receiveing
* full featured address claiming procedure according SAE J1939/81
* full support of transport protocol (up to 1785 bytes) according SAE J1939/21 for sending and receiving

- Connection Mode Data Transfers (CMDT)
- Broadcast Announce Message (BAM)
* support of Multi-PG according SAE J1939/22
- currently FEFF (Flexible Data Rate Extended Frame Format) supported only
* full support of fd-transport protocol according SAE J1939/22 (J1939-FD) for sending and receiving

* (under construction) Requests (global and specific)
- RTS/CTS (Destination Specific) Transfer with up to 8 concurrent sessions and up to 16777215 bytes of data per session
- Broadcast Announce Message (BAM) with up to 4 concurrent sessions and up to 15300 bytes of data per session

* Requests (global and specific)
* correct timeout and deadline handling
* (under construction) almost complete testcoverage
* (under construction) diagnostic messages (see https://github.com/juergenH87/python-can-j1939/tree/master/examples/diagnostic_message.py)

* diagnostic messages (see https://github.com/juergenH87/python-can-j1939/tree/master/examples/diagnostic_message.py)
- support of DM1 Tool and ECU functionaliy
- support of DM11 Tool functionaliy
- support of DM22 Tool functionaliy
Expand Down Expand Up @@ -139,7 +143,7 @@ To simply receive all passing (public) messages on the bus you can subscribe to
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000)
# ecu.connect(bustype='ixxat', channel=0, bitrate=250000)
# ecu.connect(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000)
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
# subscribe to all (global) messages on the bus
ecu.subscribe(on_message)
Expand All @@ -150,7 +154,7 @@ To simply receive all passing (public) messages on the bus you can subscribe to
ecu.disconnect()
if __name__ == '__main__':
main()
main()
A more sophisticated example in which the CA class was overloaded to include its own functionality:

Expand All @@ -166,7 +170,7 @@ A more sophisticated example in which the CA class was overloaded to include its
# compose the name descriptor for the new ca
name = j1939.Name(
arbitrary_address_capable=0,
arbitrary_address_capable=0,
industry_group=j1939.Name.IndustryGroup.Industrial,
vehicle_system_instance=1,
vehicle_system=1,
Expand Down Expand Up @@ -200,7 +204,7 @@ A more sophisticated example in which the CA class was overloaded to include its
def ca_timer_callback1(cookie):
"""Callback for sending messages
This callback is registered at the ECU timer event mechanism to be
This callback is registered at the ECU timer event mechanism to be
executed every 500ms.
:param cookie:
Expand All @@ -227,7 +231,7 @@ A more sophisticated example in which the CA class was overloaded to include its
def ca_timer_callback2(cookie):
"""Callback for sending messages
This callback is registered at the ECU timer event mechanism to be
This callback is registered at the ECU timer event mechanism to be
executed every 500ms.
:param cookie:
Expand Down Expand Up @@ -264,7 +268,7 @@ A more sophisticated example in which the CA class was overloaded to include its
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000)
# ecu.connect(bustype='ixxat', channel=0, bitrate=250000)
# ecu.connect(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000)
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
# ecu.connect('testchannel_1', bustype='virtual')
# add CA to the ECU
Expand All @@ -276,27 +280,24 @@ A more sophisticated example in which the CA class was overloaded to include its
ca.add_timer(5, ca_timer_callback2)
# by starting the CA it starts the address claiming procedure on the bus
ca.start()
time.sleep(120)
print("Deinitializing")
ca.stop()
ecu.disconnect()
if __name__ == '__main__':
main()
main()
Credits
-------
This implementation was taken from https://github.com/benkfra/j1939, as no further development took place.

This implementation was initially inspired by the `CANopen project of Christian Sandberg`_.
Thanks for your great work!

Most of the informations about SAE J1939 are taken from the papers and the book of
`Copperhill technologies`_ and from my many years of experience in J1939 of course :-)



.. _python-can: https://python-can.readthedocs.org/en/stable/
.. _Copperhill technologies: http://copperhilltech.com/a-brief-introduction-to-the-sae-j1939-protocol/
.. _CANopen project of Christian Sandberg: http://canopen.readthedocs.io/en/stable/
98 changes: 98 additions & 0 deletions examples/j1939_22_multi_pg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import logging
import time
import j1939


logging.getLogger('j1939').setLevel(logging.DEBUG)
logging.getLogger('can').setLevel(logging.DEBUG)


def on_message(priority, pgn, sa, timestamp, data):
"""Receive incoming messages from the bus
:param int priority:
Priority of the message
:param int pgn:
Parameter Group Number of the message
:param int sa:
Source Address of the message
:param int timestamp:
Timestamp of the message
:param bytearray data:
Data of the PDU
"""
print("PGN {} length {}".format(pgn, len(data)), timestamp)


def ca_timer_callback1(ca):
"""Callback for sending messages
This callback is registered at the ECU timer event mechanism to be
executed every 500ms.
:param cookie:
A cookie registered at 'add_timer'. May be None.
"""
# wait until we have our device_address
if ca.state != j1939.ControllerApplication.State.NORMAL:
# returning true keeps the timer event active
return True

# create data with 20 bytes
data = [j1939.ControllerApplication.FieldValue.NOT_AVAILABLE_8] * 20

# sending broadcast message
# the following two PGNs are packed into one multi-pg, due to time-limit of 10ms and same destination address (global)
ca.send_pgn(0, 0xFD, 0xED, 6, data, time_limit=0.01)
ca.send_pgn(0, 0xFE, 0x32, 6, data, time_limit=0.01)

# sending normal peer-to-peer message, destintion address is 0x04
# the following PGNs are transferred separately, because time limit == 0
ca.send_pgn(0, 0xE0, 0x04, 6, data)
ca.send_pgn(0, 0xD0, 0x04, 6, data)

# returning true keeps the timer event active
return True


def main():
print("Initializing")

# create the ElectronicControlUnit (one ECU can hold multiple ControllerApplications) with j1939-22 data link layer
ecu = j1939.ElectronicControlUnit(data_link_layer='j1939-22', max_cmdt_packets=200)

# can fd Baud: 500k/2M
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', fd=True,
f_clock_mhz=80, nom_brp=10, nom_tseg1=12, nom_tseg2=3, nom_sjw=1, data_brp=4, data_tseg1=7, data_tseg2=2, data_sjw=1)

# subscribe to all (global) messages on the bus
ecu.subscribe(on_message)

# compose the name descriptor for the new ca
name = j1939.Name(
arbitrary_address_capable=0,
industry_group=j1939.Name.IndustryGroup.Industrial,
vehicle_system_instance=1,
vehicle_system=1,
function=1,
function_instance=1,
ecu_instance=1,
manufacturer_code=666,
identity_number=1234567
)

# create the ControllerApplications
ca = j1939.ControllerApplication(name, 0x1)
ecu.add_ca(controller_application=ca)
# callback every 0.5s
ca.add_timer(0.500, ca_timer_callback1, ca)
ca.start()

time.sleep(120)

print("Deinitializing")
ca.stop()
ecu.disconnect()

if __name__ == '__main__':
main()
98 changes: 98 additions & 0 deletions examples/j1939_22_transport_protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import logging
import time
import j1939


logging.getLogger('j1939').setLevel(logging.DEBUG)
logging.getLogger('can').setLevel(logging.DEBUG)


def on_message(priority, pgn, sa, timestamp, data):
"""Receive incoming messages from the bus
:param int priority:
Priority of the message
:param int pgn:
Parameter Group Number of the message
:param int sa:
Source Address of the message
:param int timestamp:
Timestamp of the message
:param bytearray data:
Data of the PDU
"""
print("PGN {} length {}".format(pgn, len(data)), timestamp)


def ca_timer_callback1(ca):
"""Callback for sending messages
This callback is registered at the ECU timer event mechanism to be
executed every 500ms.
:param cookie:
A cookie registered at 'add_timer'. May be None.
"""
# wait until we have our device_address
if ca.state != j1939.ControllerApplication.State.NORMAL:
# returning true keeps the timer event active
return True

# create data with 500 bytes
data = [j1939.ControllerApplication.FieldValue.NOT_AVAILABLE_8] * 500

# sending transport protocol broadcast message (BAM)
successful = ca.send_pgn(0, 0xFD, 0xED, 6, data)
if not successful:
print( 'error occurred while sending BAM' )

# sending transport protocol peer-to-peer message (rts/cts), destintion address is 0x04
successful = ca.send_pgn(0, 0xE0, 0x04, 6, data)
if not successful:
print( 'error occurred while sending rts/cts message' )

# returning true keeps the timer event active
return True


def main():
print("Initializing")

# create the ElectronicControlUnit (one ECU can hold multiple ControllerApplications) with j1939-22 data link layer
ecu = j1939.ElectronicControlUnit(data_link_layer='j1939-22', max_cmdt_packets=200)

# can fd Baud: 500k/2M
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', fd=True,
f_clock_mhz=80, nom_brp=10, nom_tseg1=12, nom_tseg2=3, nom_sjw=1, data_brp=4, data_tseg1=7, data_tseg2=2, data_sjw=1)

# subscribe to all (global) messages on the bus
ecu.subscribe(on_message)

# compose the name descriptor for the new ca
name = j1939.Name(
arbitrary_address_capable=0,
industry_group=j1939.Name.IndustryGroup.Industrial,
vehicle_system_instance=1,
vehicle_system=1,
function=1,
function_instance=1,
ecu_instance=1,
manufacturer_code=666,
identity_number=1234567
)

# create the ControllerApplications
ca = j1939.ControllerApplication(name, 0x1)
ecu.add_ca(controller_application=ca)
# callback every 0.5s
ca.add_timer(0.500, ca_timer_callback1, ca)
ca.start()

time.sleep(120)

print("Deinitializing")
ca.stop()
ecu.disconnect()

if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion j1939/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
from .name import Name
from .message_id import MessageId
from .parameter_group_number import ParameterGroupNumber
from .diagnostic_messages import *
from .diagnostic_messages import *
Loading

0 comments on commit 546e4e7

Please sign in to comment.