Skip to content

Commit 9f0f8c3

Browse files
authored
Fix J1939 PGN getter/setter to use full 18bits. Fixes #474 (#475)
* Fix J1939 PGN getter/setter to use full 18bits. Fixes #474 Remove the criteria that PF must be >= 240 for PS field to be set as this seems to be incorrect. (It may be that for some specific case the PS need to not be used, but it isn't documented here and in general the PGN should be all 18 bits) Modifed Tests: test_canmatrix_get_frame_by_pgn() seemed to be using made up data and didn't accound for the DP and EDP fields in expected PGN test_canid_repr() Modified the expected repr value to include the PS field in the PGN. test_canid_parse_values() Modified expected PGN to include the PS field, Moved the list of test_data into parametrized cases on a new test. New Tests: test_frame_j1939_id_from_components() Tests constructing a full 29-bit CAN ID from the Priority, Source, and PGN test_frame_decode_j1939_id() Tests extracting Priority Source and PGN from an Arbiration ID * Add the global desistantion Address 0xFF to the BAM arbitration IDs * Improve the fix, to more explicitly handle the difference between PDU-Format 1 and PDU-Format 2, (where the PF >= 240 comes in). The PGN setter now will now set the arbitration ID to contain the destination address, but when the PGN getter is used it will strip the destination field off. I think that it is unlikely for the PGN setter to be used with PDU-F1 PGN containing a destination. But in the case that it is I think it makes more sense to not throw the destination address field away.
1 parent 08ec80e commit 9f0f8c3

File tree

4 files changed

+94
-40
lines changed

4 files changed

+94
-40
lines changed

src/canmatrix/canmatrix.py

+26-16
Original file line numberDiff line numberDiff line change
@@ -606,25 +606,25 @@ def j1939_pgn(self):
606606
def pgn(self):
607607
if not self.extended:
608608
raise J1939needsExtendedIdetifier
609+
# PGN is bits 8-25 of the 29-Bit Extended CAN-ID
610+
# Made up of PDU-S (8-15), PDU-F (16-23), Data Page (24) & Extended Data Page (25)
611+
# If PDU-F >= 240 the PDU-S is interpreted as Group Extension
612+
# If PDU-F < 240 the PDU-S is interpreted as a Destination Address
613+
_pgn = 0
614+
if self.j1939_pdu_format == 2:
615+
_pgn += self.j1939_ps
616+
_pgn += self.j1939_pf << 8
617+
_pgn += self.j1939_dp << 16
618+
_pgn += self.j1939_edp << 17
609619

610-
ps = (self.id >> 8) & 0xFF
611-
pf = (self.id >> 16) & 0xFF
612-
_pgn = pf << 8
613-
if pf >= 240:
614-
_pgn += ps
615620
return _pgn
616621

617622
@pgn.setter
618623
def pgn(self, value): # type: (int) -> None
619624
self.extended = True
620-
ps = value & 0xff
621-
pf = (value >> 8) & 0xFF
622-
_pgn = pf << 8
623-
if pf >= 240:
624-
_pgn += ps
625-
626-
self.id &= 0xff0000ff
627-
self.id |= (_pgn & 0xffff) << 8 # default pgn is None -> mypy reports error
625+
_pgn = value & 0x3FFFF
626+
self.id &= 0xfc0000ff
627+
self.id |= (_pgn << 8 & 0x3FFFF00) # default pgn is None -> mypy reports error
628628

629629

630630

@@ -640,7 +640,7 @@ def j1939_tuple(self): # type: () -> typing.Tuple[int, int, int]
640640
def j1939_destination(self):
641641
if not self.extended:
642642
raise J1939needsExtendedIdetifier
643-
if self.j1939_pf < 240:
643+
if self.j1939_pdu_format == 1:
644644
destination = self.j1939_ps
645645
else:
646646
destination = None
@@ -669,11 +669,21 @@ def j1939_pf(self):
669669
raise J1939needsExtendedIdetifier
670670
return (self.id >> 16) & 0xFF
671671

672+
@property
673+
def j1939_pdu_format(self):
674+
return 1 if (self.j1939_pf < 240) else 2
675+
676+
@property
677+
def j1939_dp(self):
678+
if not self.extended:
679+
raise J1939needsExtendedIdetifier
680+
return (self.id >> 24) & 0x1
681+
672682
@property
673683
def j1939_edp(self):
674684
if not self.extended:
675685
raise J1939needsExtendedIdetifier
676-
return (self.id >> 24) & 0x03
686+
return (self.id >> 25) & 0x1
677687

678688
@property
679689
def j1939_priority(self):
@@ -684,7 +694,7 @@ def j1939_priority(self):
684694
@j1939_priority.setter
685695
def j1939_priority(self, value): # type: (int) -> None
686696
self.extended = True
687-
self.id = (self.id & 0x2ffffff) | ((value & 0x7) << 26)
697+
self.id = (self.id & 0x3ffffff) | ((value & 0x7) << 26)
688698

689699
@property
690700
def j1939_str(self): # type: () -> str

src/canmatrix/j1939_decoder.py

-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ def decode(self, arbitration_id, can_data, matrix = None):
7878

7979
elif arbitration_id.pgn == canmatrix.ArbitrationId.from_pgn(0xEBFF).pgn:
8080
# transfer data
81-
8281
self._data = self._data + can_data[1:min(8, self.bytes_left + 1)]
8382
self.bytes_left = max(self.bytes_left - 7, 0)
8483

src/canmatrix/tests/test_canmatrix.py

+56-9
Original file line numberDiff line numberDiff line change
@@ -597,8 +597,58 @@ def test_frame_calc_j1939_id():
597597
frame.source = 0x22
598598
frame.pgn = 0xAAAA
599599
frame.priority = 3
600-
assert frame.arbitration_id.id == 0xcaa0022
601-
600+
assert frame.arbitration_id.id == 0xCAAAA22
601+
602+
@pytest.mark.parametrize(
603+
'priority, pgn, source, id',
604+
(
605+
(0, 0, 0, 0),
606+
(1, 1, 1, 0x4000101),
607+
(2, 2, 2, 0x8000202),
608+
(3, 0xAAAA, 0x22, 0xCAAAA22),
609+
(0, 0x1F004, 0xEE, 0x1F004EE),
610+
(3, 0x1F004, 0xEE, 0xDF004EE),
611+
(7, 0x1FFFF, 0xFF, 0x1DFFFFFF),
612+
(3, 0, 0xB, 0xC00000B),
613+
(3, 0xEF27, 0xFD, 0xCEF27FD),
614+
(3, 0xFFCA, 0xFD, 0xCFFCAFD),
615+
(3, 0, 3, 0xC000003),
616+
(3, 0xF002, 3, 0xCF00203),
617+
(6, 0xFE4A, 3, 0x18FE4A03),
618+
(3, 0x103, 5, 0xC010305),
619+
), )
620+
def test_frame_j1939_id_from_components(priority, pgn, source, id):
621+
# we have to set all j1939 properties in the __init__ otherwise the setters crash
622+
frame = canmatrix.canmatrix.Frame()
623+
frame.source = source
624+
frame.pgn = pgn
625+
frame.priority = priority
626+
assert hex(frame.arbitration_id.id) == hex(id)
627+
628+
@pytest.mark.parametrize(
629+
'priority, pgn, source, id',
630+
(
631+
(0, 0, 0, 0),
632+
(1, 0, 1, 0x4000101),
633+
(2, 0, 2, 0x8000202),
634+
(3, 0xAA00, 0x22, 0xCAAAA22),
635+
(0, 0x1F004, 0xEE, 0x1F004EE),
636+
(3, 0x1F004, 0xEE, 0xDF004EE),
637+
(7, 0x1FFFF, 0xFF, 0x1DFFFFFF),
638+
(3, 0, 0xB, 0xC00000B),
639+
(3, 0xEF00, 0xFD, 0xCEF27FD),
640+
(3, 0xFFCA, 0xFD, 0xCFFCAFD),
641+
(3, 0, 3, 0xC000003),
642+
(3, 0xF002, 3, 0xCF00203),
643+
(6, 0xFE4A, 3, 0x18FE4A03),
644+
(3, 0x100, 5, 0xC010305),
645+
), )
646+
def test_frame_decode_j1939_id(source, pgn, priority, id):
647+
# we have to set all j1939 properties in the __init__ otherwise the setters crash
648+
frame = canmatrix.canmatrix.Frame(arbitration_id=canmatrix.ArbitrationId(id=id, extended=True))
649+
assert hex(frame.source) == hex(source)
650+
assert hex(frame.pgn) == hex(pgn)
651+
assert hex(frame.priority) == hex(priority)
602652

603653
def test_frame_add_transmitter(empty_frame):
604654
empty_frame.add_transmitter("BCM")
@@ -781,18 +831,15 @@ def test_canid_parse_values():
781831
can_id = canmatrix.ArbitrationId(id=0x01ABCD02, extended=True)
782832
assert can_id.j1939_source == 0x02
783833
assert can_id.j1939_destination == 0xcd
784-
assert can_id.j1939_pgn == 0xAB00
834+
assert can_id.j1939_pgn == 0x1AB00
785835
assert can_id.j1939_destination == 0xCD
786836
assert can_id.j1939_priority == 0
787-
assert can_id.j1939_tuple == (0xCD, 0xAB00, 2)
837+
assert can_id.j1939_tuple == (0xCD, 0x1AB00, 2)
788838

789-
test_data = {0xc00000b : 0, 0xcef27fd : 61184, 0xcffcafd : 65482, 0xc000003 : 0, 0xcf00203 : 61442, 0x18fe4a03 : 65098, 0xc010305 : 256}
790-
for canId, pgn in test_data.items():
791-
assert canmatrix.ArbitrationId(id=canId, extended=True).pgn == pgn
792839

793840
def test_canid_repr():
794841
can_id = canmatrix.ArbitrationId(id=0x01ABCD02, extended=True)
795-
assert can_id.j1939_str == "DA:0xCD PGN:0xAB00 SA:0x02"
842+
assert can_id.j1939_str == "DA:0xCD PGN:0x1AB00 SA:0x02"
796843

797844

798845
# DecodedSignal tests
@@ -878,7 +925,7 @@ def test_canmatrix_get_frame_by_pgn(empty_matrix, empty_frame):
878925
empty_frame.arbitration_id.id = 0xA123456
879926
empty_frame.arbitration_id.extended = True
880927
empty_matrix.add_frame(empty_frame)
881-
assert empty_matrix.frame_by_pgn(0x1234) == empty_frame
928+
assert empty_matrix.frame_by_pgn(0x21234) == empty_frame
882929

883930
def test_canmatrix_get_frame_by_wrong_pgn(empty_matrix, empty_frame):
884931
empty_frame.arbitration_id.id = 0xAB123456

src/canmatrix/tests/test_j1939_decoder.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ def test_j1939_decoder():
2727
t = canmatrix.j1939_decoder.j1939_decoder()
2828

2929
# BAM
30-
(type, signals) = t.decode(canmatrix.ArbitrationId(id = 0xec0000, extended= True),
30+
(type, signals) = t.decode(canmatrix.ArbitrationId(id = 0xecFF00, extended= True),
3131
bytearray([0x20,10,0,1,0xff,0x66,0x1,0]), matrix)
3232
assert "BAM " in type
3333
# print (type, signals)
3434

3535
# data 1
36-
(type, signals) = t.decode(canmatrix.ArbitrationId(id = 0xeb0000, extended= True),
36+
(type, signals) = t.decode(canmatrix.ArbitrationId(id = 0xebFF00, extended= True),
3737
bytearray([0x0,1,1,1,1,1,1,1]), matrix)
3838
assert "BAM data" in type
3939
#print (type, signals)
4040

4141
# data 2
42-
(type, signals) = t.decode(canmatrix.ArbitrationId(id = 0xeb0000, extended= True),
42+
(type, signals) = t.decode(canmatrix.ArbitrationId(id = 0xebFF00, extended= True),
4343
bytearray([0x1,1,1,1,1,1,1,1]), matrix)
4444
assert "BAM last data" in type
4545
# print (type, signals)
@@ -54,17 +54,15 @@ def test_j1939_decoder():
5454
can_data[i], matrix)
5555

5656
print ("-------- test data -------- ")
57-
test_frames = collections.OrderedDict ([
58-
(0xcef27fd , "fffae1ff00ffff"),
59-
(0xcffcafd , "c0fffffffffff800"),
60-
(0xcf00203 , "cc00000000b812ff"),
61-
(0xfe4a03 , "fffcffffffffffff"),
62-
(0xc010305 , "ccfffaffff204e0a"),
63-
(0x0CF00400, "F4DEDE3028FFF0FF")])
57+
test_frames = collections.OrderedDict([
58+
(0xcef27fd, ("fffae1ff00ffff", "")),
59+
(0xcffcafd, ("c0fffffffffff800", "")),
60+
(0xcf00203, ("cc00000000b812ff", "J1939 known: ETC1")),
61+
(0xfe4a03, ("fffcffffffffffff", "J1939 known: ETC7")),
62+
(0xc010305, ("ccfffaffff204e0a", "J1939 known: TC1")),
63+
(0x0CF00400, ("F4DEDE3028FFF0FF", "J1939 known: EEC1"))])
6464

65-
expected = ["EEC1","TC1","ETC7","ETC1"]
66-
for arb_id, asc_data in test_frames.items():
65+
for arb_id, (asc_data, expected) in test_frames.items():
6766
(type, signals) = t.decode(canmatrix.ArbitrationId(id=arb_id, extended=True),
6867
bytearray.fromhex(asc_data), matrix)
69-
if type is not None and "J1939 known" in type:
70-
assert expected.pop() in type
68+
assert expected in type

0 commit comments

Comments
 (0)