Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ 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
as the CAN messages.

============================== ===========
Library Version Python
------------------------------ -----------
2.0.13+ 3.9+
============================== ===========


Features
--------
Expand Down
131 changes: 65 additions & 66 deletions j1939/Dm14Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,48 +124,47 @@ def parse_dm14(
self.length = len(data)
self.direct = data[1] >> 4

match self.state:
case ResponseState.IDLE:
self.pgn = pgn
self.sa = sa
self.status = j1939.Dm15Status.PROCEED.value
self.address = data[2 : (self.length - 2)]
self.direct = data[1] >> 4
self.command = ((data[1] - 1) & 0x0F) >> 1
self.pointer_type = (data[1] >> 4) & 0x1
self.object_count = data[0]
self.access_level = (data[self.length - 1] << 8) + data[self.length - 2]
self.data = data
if self._key_from_seed is not None:
self.state = ResponseState.WAIT_FOR_KEY
self._pgn = j1939.ParameterGroupNumber.PGN.DM15
self._send_dm15(
self.length,
self.direct,
self.status,
self.state,
self.object_count,
self.sa,
)
else:
self.state = ResponseState.SEND_PROCEED

case ResponseState.WAIT_FOR_KEY:
self.length = len(data)
self.address = data[2 : (self.length - 2)]
self.command = ((data[1] - 1) & 0x0F) >> 1
self.object_count = data[0]
self.key = (data[self.length - 1] << 8) + data[self.length - 2]
self.data = data
if self.state == ResponseState.IDLE:
self.pgn = pgn
self.sa = sa
self.status = j1939.Dm15Status.PROCEED.value
self.address = data[2: (self.length - 2)]
self.direct = data[1] >> 4
self.command = ((data[1] - 1) & 0x0F) >> 1
self.pointer_type = (data[1] >> 4) & 0x1
self.object_count = data[0]
self.access_level = (data[self.length - 1] << 8) + data[self.length - 2]
self.data = data
if self._key_from_seed is not None:
self.state = ResponseState.WAIT_FOR_KEY
self._pgn = j1939.ParameterGroupNumber.PGN.DM15
self._send_dm15(
self.length,
self.direct,
self.status,
self.state,
self.object_count,
self.sa,
)
else:
self.state = ResponseState.SEND_PROCEED

case ResponseState.WAIT_OPERATION_COMPLETE:
self.state = ResponseState.IDLE
self.sa = None
self._ca.unsubscribe(self.parse_dm14)
elif self.state == ResponseState.WAIT_FOR_KEY:
self.length = len(data)
self.address = data[2: (self.length - 2)]
self.command = ((data[1] - 1) & 0x0F) >> 1
self.object_count = data[0]
self.key = (data[self.length - 1] << 8) + data[self.length - 2]
self.data = data
self.state = ResponseState.SEND_PROCEED

case _:
raise ValueError("Invalid state")
elif self.state == ResponseState.WAIT_OPERATION_COMPLETE:
self.state = ResponseState.IDLE
self.sa = None
self._ca.unsubscribe(self.parse_dm14)

else:
raise ValueError("Invalid state")

def _send_dm15(
self,
Expand Down Expand Up @@ -195,33 +194,33 @@ def _send_dm15(
self._pgn = j1939.ParameterGroupNumber.PGN.DM15
data = [0xFF] * length
data[1] = (direct << 4) + (status << 1) + 1
match state:
case ResponseState.WAIT_FOR_KEY:
self.seed = self._seed_generator()
data[0] = 0x00
data[length - 2] = self.seed & 0xFF
data[length - 1] = self.seed >> 8

case ResponseState.SEND_PROCEED:
data[0] = object_count

case ResponseState.SEND_OPERATION_COMPLETE:
self.command = j1939.Command.OPERATION_COMPLETED.value
data[0] = 0x00
data[1] = (direct << 4) + (self.command << 1) + 1
self.state = ResponseState.WAIT_OPERATION_COMPLETE

case ResponseState.SEND_ERROR:
status = j1939.Dm15Status.OPERATION_FAILED.value
data[0] = 0x00
data[1] = (direct << 4) + (status << 1) + 1
data[length - 6] = error & 0xFF
data[length - 5] = (error >> 8) & 0xFF
data[length - 4] = error >> 16
data[length - 3] = edcp

case _:
raise ValueError("Invalid state")
if self.state == ResponseState.WAIT_FOR_KEY:
self.seed = self._seed_generator()
data[0] = 0x00
data[length - 2] = self.seed & 0xFF
data[length - 1] = self.seed >> 8

elif self.state == ResponseState.SEND_PROCEED:
data[0] = object_count

elif self.state == ResponseState.SEND_OPERATION_COMPLETE:
self.command = j1939.Command.OPERATION_COMPLETED.value
data[0] = 0x00
data[1] = (direct << 4) + (self.command << 1) + 1
self.state = ResponseState.WAIT_OPERATION_COMPLETE

elif self.state == ResponseState.SEND_ERROR:
status = j1939.Dm15Status.OPERATION_FAILED.value
data[0] = 0x00
data[1] = (direct << 4) + (status << 1) + 1
data[length - 6] = error & 0xFF
data[length - 5] = (error >> 8) & 0xFF
data[length - 4] = error >> 16
data[length - 3] = edcp

else:
raise ValueError("Invalid state")

self._ca.send_pgn(0, (pgn >> 8) & 0xFF, sa & 0xFF, 6, data)

def _send_dm16(self) -> None:
Expand Down
123 changes: 61 additions & 62 deletions j1939/memory_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,50 @@ def _listen_for_dm14(
:param data: Data of the PDU
"""
if pgn == j1939.ParameterGroupNumber.PGN.DM14:
match self.state:
case DMState.IDLE:
if self.server.state.value == DMState.IDLE.value:
self.state = DMState.REQUEST_STARTED
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
if not self.seed_security:
self.state = DMState.WAIT_RESPONSE
self._ca.unsubscribe(self._listen_for_dm14)
if self.state == DMState.IDLE:
if self.server.state.value == DMState.IDLE.value:
self.state = DMState.REQUEST_STARTED
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
if not self.seed_security:
self.state = DMState.WAIT_RESPONSE
self._ca.unsubscribe(self._listen_for_dm14)
if self._proceed_function is not None:
self.proceed = self._proceed_function(
self.server.command,
int.from_bytes(
bytes=self.server.address,
byteorder="little",
signed=False,
),
self.server.pointer_type,
self.server.length,
self.server.object_count,
0xFFFF, # placeholder for key
self.server.sa,
self.server.access_level,
0x0, # placeholder for seed
) # call proceed function and pass in basic parameters
if self.proceed:
self._notify_query_received() # notify incoming request
else:
self.server.error = 0x100
self.server.set_busy(True)
self.server.parse_dm14(
priority, pgn, sa, timestamp, data
)
self.server.set_busy(False)
self.server.reset_query()
self.state = DMState.IDLE
self.server.error = 0x0

elif self.state == DMState.REQUEST_STARTED:
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
if self.server.state == j1939.ResponseState.SEND_PROCEED:
self.state = DMState.WAIT_RESPONSE
if self.seed_security:
if self.server.verify_key(
self.server.seed, self.server.key
):
if self._proceed_function is not None:
self.proceed = self._proceed_function(
self.server.command,
Expand All @@ -56,10 +92,10 @@ def _listen_for_dm14(
self.server.pointer_type,
self.server.length,
self.server.object_count,
0xFFFF, # placeholder for key
self.server.key,
self.server.sa,
self.server.access_level,
0x0, # placeholder for seed
self.server.seed,
) # call proceed function and pass in basic parameters
if self.proceed:
self._notify_query_received() # notify incoming request
Expand All @@ -73,59 +109,22 @@ def _listen_for_dm14(
self.server.reset_query()
self.state = DMState.IDLE
self.server.error = 0x0
else:
self.server.error = 0x1003
self.server.set_busy(True)
self.server.parse_dm14(
priority, pgn, sa, timestamp, data
)
self.server.set_busy(False)
self.state = DMState.IDLE
self.server.error = 0x0

case DMState.REQUEST_STARTED:
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
if self.server.state == j1939.ResponseState.SEND_PROCEED:
self.state = DMState.WAIT_RESPONSE
if self.seed_security:
if self.server.verify_key(
self.server.seed, self.server.key
):
if self._proceed_function is not None:
self.proceed = self._proceed_function(
self.server.command,
int.from_bytes(
bytes=self.server.address,
byteorder="little",
signed=False,
),
self.server.pointer_type,
self.server.length,
self.server.object_count,
self.server.key,
self.server.sa,
self.server.access_level,
self.server.seed,
) # call proceed function and pass in basic parameters
if self.proceed:
self._notify_query_received() # notify incoming request
else:
self.server.error = 0x100
self.server.set_busy(True)
self.server.parse_dm14(
priority, pgn, sa, timestamp, data
)
self.server.set_busy(False)
self.server.reset_query()
self.state = DMState.IDLE
self.server.error = 0x0
else:
self.server.error = 0x1003
self.server.set_busy(True)
self.server.parse_dm14(
priority, pgn, sa, timestamp, data
)
self.server.set_busy(False)
self.state = DMState.IDLE
self.server.error = 0x0

case DMState.WAIT_QUERY:
self.server.set_busy(True)
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
self.server.set_busy(False)
case _:
pass
elif self.state == DMState.WAIT_QUERY:
self.server.set_busy(True)
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
self.server.set_busy(False)
else:
pass

def respond(
self,
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Intended Audience :: Developers",
"Topic :: Scientific/Engineering"
],
Expand Down