Skip to content

Commit 546e4e7

Browse files
author
juergen
committed
j1939-22 (j1939 fd)
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.
1 parent 99ec5f3 commit 546e4e7

14 files changed

+1738
-677
lines changed

README.rst

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,21 @@ SAE J1939 for Python
1212
:alt: Documentation build Status
1313

1414

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

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

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

2625
* CAN over Serial
2726
* CAN over Serial / SLCAN
2827
* CANalyst-II
2928
* IXXAT Virtual CAN Interface
30-
* Kvaser’s CANLIB
29+
* Kvasers CANLIB
3130
* NEOVI Interface
3231
* NI-CAN
3332
* PCAN Basic API
@@ -41,14 +40,14 @@ At the time of writing the supported interfaces are
4140
Overview
4241
--------
4342

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

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

5453

@@ -57,17 +56,22 @@ Features
5756

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

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

66-
* (under construction) Requests (global and specific)
68+
- RTS/CTS (Destination Specific) Transfer with up to 8 concurrent sessions and up to 16777215 bytes of data per session
69+
- Broadcast Announce Message (BAM) with up to 4 concurrent sessions and up to 15300 bytes of data per session
70+
71+
* Requests (global and specific)
6772
* correct timeout and deadline handling
6873
* (under construction) almost complete testcoverage
69-
* (under construction) diagnostic messages (see https://github.com/juergenH87/python-can-j1939/tree/master/examples/diagnostic_message.py)
70-
74+
* diagnostic messages (see https://github.com/juergenH87/python-can-j1939/tree/master/examples/diagnostic_message.py)
7175
- support of DM1 Tool and ECU functionaliy
7276
- support of DM11 Tool functionaliy
7377
- support of DM22 Tool functionaliy
@@ -139,7 +143,7 @@ To simply receive all passing (public) messages on the bus you can subscribe to
139143
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000)
140144
# ecu.connect(bustype='ixxat', channel=0, bitrate=250000)
141145
# ecu.connect(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000)
142-
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
146+
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
143147
144148
# subscribe to all (global) messages on the bus
145149
ecu.subscribe(on_message)
@@ -150,7 +154,7 @@ To simply receive all passing (public) messages on the bus you can subscribe to
150154
ecu.disconnect()
151155
152156
if __name__ == '__main__':
153-
main()
157+
main()
154158
155159
A more sophisticated example in which the CA class was overloaded to include its own functionality:
156160

@@ -166,7 +170,7 @@ A more sophisticated example in which the CA class was overloaded to include its
166170
167171
# compose the name descriptor for the new ca
168172
name = j1939.Name(
169-
arbitrary_address_capable=0,
173+
arbitrary_address_capable=0,
170174
industry_group=j1939.Name.IndustryGroup.Industrial,
171175
vehicle_system_instance=1,
172176
vehicle_system=1,
@@ -200,7 +204,7 @@ A more sophisticated example in which the CA class was overloaded to include its
200204
def ca_timer_callback1(cookie):
201205
"""Callback for sending messages
202206
203-
This callback is registered at the ECU timer event mechanism to be
207+
This callback is registered at the ECU timer event mechanism to be
204208
executed every 500ms.
205209
206210
:param cookie:
@@ -227,7 +231,7 @@ A more sophisticated example in which the CA class was overloaded to include its
227231
def ca_timer_callback2(cookie):
228232
"""Callback for sending messages
229233
230-
This callback is registered at the ECU timer event mechanism to be
234+
This callback is registered at the ECU timer event mechanism to be
231235
executed every 500ms.
232236
233237
:param cookie:
@@ -264,7 +268,7 @@ A more sophisticated example in which the CA class was overloaded to include its
264268
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000)
265269
# ecu.connect(bustype='ixxat', channel=0, bitrate=250000)
266270
# ecu.connect(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000)
267-
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
271+
# ecu.connect(bustype='nican', channel='CAN0', bitrate=250000)
268272
# ecu.connect('testchannel_1', bustype='virtual')
269273
270274
# add CA to the ECU
@@ -276,27 +280,24 @@ A more sophisticated example in which the CA class was overloaded to include its
276280
ca.add_timer(5, ca_timer_callback2)
277281
# by starting the CA it starts the address claiming procedure on the bus
278282
ca.start()
279-
283+
280284
time.sleep(120)
281285
282286
print("Deinitializing")
283287
ca.stop()
284288
ecu.disconnect()
285289
286290
if __name__ == '__main__':
287-
main()
291+
main()
292+
288293
289294
Credits
290295
-------
296+
This implementation was taken from https://github.com/benkfra/j1939, as no further development took place.
291297

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

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

299301

300302
.. _python-can: https://python-can.readthedocs.org/en/stable/
301303
.. _Copperhill technologies: http://copperhilltech.com/a-brief-introduction-to-the-sae-j1939-protocol/
302-
.. _CANopen project of Christian Sandberg: http://canopen.readthedocs.io/en/stable/

examples/j1939_22_multi_pg.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import logging
2+
import time
3+
import j1939
4+
5+
6+
logging.getLogger('j1939').setLevel(logging.DEBUG)
7+
logging.getLogger('can').setLevel(logging.DEBUG)
8+
9+
10+
def on_message(priority, pgn, sa, timestamp, data):
11+
"""Receive incoming messages from the bus
12+
13+
:param int priority:
14+
Priority of the message
15+
:param int pgn:
16+
Parameter Group Number of the message
17+
:param int sa:
18+
Source Address of the message
19+
:param int timestamp:
20+
Timestamp of the message
21+
:param bytearray data:
22+
Data of the PDU
23+
"""
24+
print("PGN {} length {}".format(pgn, len(data)), timestamp)
25+
26+
27+
def ca_timer_callback1(ca):
28+
"""Callback for sending messages
29+
30+
This callback is registered at the ECU timer event mechanism to be
31+
executed every 500ms.
32+
33+
:param cookie:
34+
A cookie registered at 'add_timer'. May be None.
35+
"""
36+
# wait until we have our device_address
37+
if ca.state != j1939.ControllerApplication.State.NORMAL:
38+
# returning true keeps the timer event active
39+
return True
40+
41+
# create data with 20 bytes
42+
data = [j1939.ControllerApplication.FieldValue.NOT_AVAILABLE_8] * 20
43+
44+
# sending broadcast message
45+
# the following two PGNs are packed into one multi-pg, due to time-limit of 10ms and same destination address (global)
46+
ca.send_pgn(0, 0xFD, 0xED, 6, data, time_limit=0.01)
47+
ca.send_pgn(0, 0xFE, 0x32, 6, data, time_limit=0.01)
48+
49+
# sending normal peer-to-peer message, destintion address is 0x04
50+
# the following PGNs are transferred separately, because time limit == 0
51+
ca.send_pgn(0, 0xE0, 0x04, 6, data)
52+
ca.send_pgn(0, 0xD0, 0x04, 6, data)
53+
54+
# returning true keeps the timer event active
55+
return True
56+
57+
58+
def main():
59+
print("Initializing")
60+
61+
# create the ElectronicControlUnit (one ECU can hold multiple ControllerApplications) with j1939-22 data link layer
62+
ecu = j1939.ElectronicControlUnit(data_link_layer='j1939-22', max_cmdt_packets=200)
63+
64+
# can fd Baud: 500k/2M
65+
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', fd=True,
66+
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)
67+
68+
# subscribe to all (global) messages on the bus
69+
ecu.subscribe(on_message)
70+
71+
# compose the name descriptor for the new ca
72+
name = j1939.Name(
73+
arbitrary_address_capable=0,
74+
industry_group=j1939.Name.IndustryGroup.Industrial,
75+
vehicle_system_instance=1,
76+
vehicle_system=1,
77+
function=1,
78+
function_instance=1,
79+
ecu_instance=1,
80+
manufacturer_code=666,
81+
identity_number=1234567
82+
)
83+
84+
# create the ControllerApplications
85+
ca = j1939.ControllerApplication(name, 0x1)
86+
ecu.add_ca(controller_application=ca)
87+
# callback every 0.5s
88+
ca.add_timer(0.500, ca_timer_callback1, ca)
89+
ca.start()
90+
91+
time.sleep(120)
92+
93+
print("Deinitializing")
94+
ca.stop()
95+
ecu.disconnect()
96+
97+
if __name__ == '__main__':
98+
main()
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import logging
2+
import time
3+
import j1939
4+
5+
6+
logging.getLogger('j1939').setLevel(logging.DEBUG)
7+
logging.getLogger('can').setLevel(logging.DEBUG)
8+
9+
10+
def on_message(priority, pgn, sa, timestamp, data):
11+
"""Receive incoming messages from the bus
12+
13+
:param int priority:
14+
Priority of the message
15+
:param int pgn:
16+
Parameter Group Number of the message
17+
:param int sa:
18+
Source Address of the message
19+
:param int timestamp:
20+
Timestamp of the message
21+
:param bytearray data:
22+
Data of the PDU
23+
"""
24+
print("PGN {} length {}".format(pgn, len(data)), timestamp)
25+
26+
27+
def ca_timer_callback1(ca):
28+
"""Callback for sending messages
29+
30+
This callback is registered at the ECU timer event mechanism to be
31+
executed every 500ms.
32+
33+
:param cookie:
34+
A cookie registered at 'add_timer'. May be None.
35+
"""
36+
# wait until we have our device_address
37+
if ca.state != j1939.ControllerApplication.State.NORMAL:
38+
# returning true keeps the timer event active
39+
return True
40+
41+
# create data with 500 bytes
42+
data = [j1939.ControllerApplication.FieldValue.NOT_AVAILABLE_8] * 500
43+
44+
# sending transport protocol broadcast message (BAM)
45+
successful = ca.send_pgn(0, 0xFD, 0xED, 6, data)
46+
if not successful:
47+
print( 'error occurred while sending BAM' )
48+
49+
# sending transport protocol peer-to-peer message (rts/cts), destintion address is 0x04
50+
successful = ca.send_pgn(0, 0xE0, 0x04, 6, data)
51+
if not successful:
52+
print( 'error occurred while sending rts/cts message' )
53+
54+
# returning true keeps the timer event active
55+
return True
56+
57+
58+
def main():
59+
print("Initializing")
60+
61+
# create the ElectronicControlUnit (one ECU can hold multiple ControllerApplications) with j1939-22 data link layer
62+
ecu = j1939.ElectronicControlUnit(data_link_layer='j1939-22', max_cmdt_packets=200)
63+
64+
# can fd Baud: 500k/2M
65+
ecu.connect(bustype='pcan', channel='PCAN_USBBUS1', fd=True,
66+
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)
67+
68+
# subscribe to all (global) messages on the bus
69+
ecu.subscribe(on_message)
70+
71+
# compose the name descriptor for the new ca
72+
name = j1939.Name(
73+
arbitrary_address_capable=0,
74+
industry_group=j1939.Name.IndustryGroup.Industrial,
75+
vehicle_system_instance=1,
76+
vehicle_system=1,
77+
function=1,
78+
function_instance=1,
79+
ecu_instance=1,
80+
manufacturer_code=666,
81+
identity_number=1234567
82+
)
83+
84+
# create the ControllerApplications
85+
ca = j1939.ControllerApplication(name, 0x1)
86+
ecu.add_ca(controller_application=ca)
87+
# callback every 0.5s
88+
ca.add_timer(0.500, ca_timer_callback1, ca)
89+
ca.start()
90+
91+
time.sleep(120)
92+
93+
print("Deinitializing")
94+
ca.stop()
95+
ecu.disconnect()
96+
97+
if __name__ == '__main__':
98+
main()

j1939/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
from .name import Name
55
from .message_id import MessageId
66
from .parameter_group_number import ParameterGroupNumber
7-
from .diagnostic_messages import *
7+
from .diagnostic_messages import *

0 commit comments

Comments
 (0)