Skip to content

Commit ec214a1

Browse files
authored
Merge pull request #110 from khauersp/feature/improve-ma
Improve some memory access functionality
2 parents b6acd66 + fb32385 commit ec214a1

File tree

6 files changed

+149
-55
lines changed

6 files changed

+149
-55
lines changed

j1939/Dm14Query.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,33 @@ def __init__(self, ca: j1939.ControllerApplication, user_level=7) -> None:
4545
self.exception_queue = queue.Queue()
4646
self.user_level = user_level
4747

48+
def unsubscribe_all(self) -> None:
49+
"""
50+
Unsubscribes all message handlers
51+
"""
52+
self._ca.unsubscribe(self._parse_dm15)
53+
self._ca.unsubscribe(self._parse_dm16)
54+
55+
def reset_query(self) -> None:
56+
"""
57+
Resets query to remove transaction specific data
58+
"""
59+
self.state = QueryState.IDLE
60+
self._dest_address = None
61+
self.address = None
62+
self.object_count = 0
63+
self.object_byte_size = 1
64+
self.signed = False
65+
self.return_raw_bytes = False
66+
self.direct = 0
67+
self.command = None
68+
self.bytes = bytearray()
69+
self.mem_data = None
70+
self.data_queue = queue.Queue()
71+
self.exception_queue = queue.Queue()
72+
self.unsubscribe_all()
73+
74+
4875
def _wait_for_data(self) -> None:
4976
"""
5077
Determines whether to send data or wait to receive data based on the command type. If the command is a write command, then the data is sent.

j1939/Dm14Server.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,10 @@ def parse_dm14(
161161
self.state = ResponseState.SEND_PROCEED
162162

163163
case ResponseState.WAIT_OPERATION_COMPLETE:
164-
self.state = ResponseState.IDLE
165-
self.sa = None
166-
self._ca.unsubscribe(self.parse_dm14)
164+
self.reset_server()
167165

168166
case _:
167+
self.reset_server()
169168
raise ValueError("Invalid state")
170169

171170
def _send_dm15(
@@ -222,6 +221,7 @@ def _send_dm15(
222221
data[length - 3] = edcp
223222

224223
case _:
224+
self.reset_server()
225225
raise ValueError("Invalid state")
226226
self._ca.send_pgn(0, (pgn >> 8) & 0xFF, sa & 0xFF, 6, data)
227227

@@ -255,12 +255,12 @@ def _parse_dm16(
255255

256256
if pgn != j1939.ParameterGroupNumber.PGN.DM16 or sa != self.sa:
257257
return
258-
259258
length = min(data[0], len(data) - 1)
260259
self.data_queue.put(data[1 : length + 1])
261260
self._ca.unsubscribe(self._parse_dm16)
262261
self._ca.subscribe(self.parse_dm14)
263262
self.state = ResponseState.SEND_OPERATION_COMPLETE
263+
264264
self._send_dm15(
265265
self.length,
266266
self.direct,
@@ -329,11 +329,19 @@ def verify_key(self, seed: int, key: int) -> bool:
329329
)
330330
return self._key_from_seed(seed) == key
331331

332-
def reset_query(self) -> None:
332+
def unsubscribe_all(self) -> None:
333+
"""
334+
Unsubscribes all message handlers
335+
"""
336+
self._ca.unsubscribe(self.parse_dm14)
337+
self._ca.unsubscribe(self._parse_dm16)
338+
339+
def reset_server(self) -> None:
333340
"""
334-
Resets query to initial state
341+
Resets server to remove transaction specific data
335342
"""
336343
self.state = ResponseState.IDLE
344+
self.data_queue = queue.Queue()
337345
self.sa = None
338346
self.seed = None
339347
self.key = None
@@ -346,8 +354,7 @@ def reset_query(self) -> None:
346354
self.edcp = 0x07
347355
self.status = j1939.Dm15Status.PROCEED.value
348356
self.direct = 0
349-
self._ca.unsubscribe(self.parse_dm14)
350-
self._ca.unsubscribe(self._parse_dm16)
357+
self.unsubscribe_all()
351358

352359
def respond(
353360
self,

j1939/electronic_control_unit.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,10 @@ def _notify_subscribers(self, priority, pgn, sa, dest, timestamp, data):
374374
logger.debug("notify subscribers for PGN {}".format(pgn))
375375
# notify only the CA for which the message is intended
376376
# each CA receives all broadcast messages
377-
for dic in self._subscribers:
377+
378+
# TODO: this is ineffecient but there exists a possibility of removing subscribers during callback
379+
# and adding new ones in while this is going and it can impact message receivement
380+
for dic in self._subscribers.copy():
378381
if (dic['dev_adr'] == None) or (dest == ParameterGroupNumber.Address.GLOBAL) or (callable(dic['dev_adr']) and dic['dev_adr'](dest)) or (dest == dic['dev_adr']):
379382
dic['cb'](priority, pgn, sa, timestamp, data)
380383

j1939/memory_access.py

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ class DMState(Enum):
77
REQUEST_STARTED = 2
88
WAIT_RESPONSE = 3
99
WAIT_QUERY = 4
10+
SERVER_CLEANUP = 5
1011

1112

1213
class MemoryAccess:
1314
def __init__(self, ca: j1939.ControllerApplication) -> None:
1415
"""
1516
Makes an overarching Memory access class
17+
1618
:param ca: Controller Application
1719
"""
1820
self._ca = ca
@@ -22,14 +24,33 @@ def __init__(self, ca: j1939.ControllerApplication) -> None:
2224
self.state = DMState.IDLE
2325
self.seed_security = False
2426
self._notify_query_received = None
25-
self._seed_key_valid = None
2627
self._proceed_function = None
2728

29+
def _handle_error(self, priority: int, pgn: int, sa: int, timestamp: int, data: bytearray, error_code: int) -> None:
30+
"""
31+
Handles errors by resetting the state and unsubscribing from DM14 messages
32+
33+
:param priority: Priority of the message
34+
:param pgn: Parameter Group Number of the message
35+
:param sa: Source Address of the message
36+
:param timestamp: Timestamp of the message
37+
:param data: Data of the PDU
38+
:param error_code: Error code to be set
39+
"""
40+
self.server.error = error_code
41+
self.server.set_busy(True)
42+
self.server.parse_dm14(
43+
priority, pgn, sa, timestamp, data
44+
)
45+
self.server.set_busy(False)
46+
self.reset()
47+
2848
def _listen_for_dm14(
2949
self, priority: int, pgn: int, sa: int, timestamp: int, data: bytearray
3050
) -> None:
3151
"""
3252
Listens for dm14 messages and passes them to the appropriate function
53+
3354
:param priority: Priority of the message
3455
:param pgn: Parameter Group Number of the message
3556
:param sa: Source Address of the message
@@ -64,15 +85,7 @@ def _listen_for_dm14(
6485
if self.proceed:
6586
self._notify_query_received() # notify incoming request
6687
else:
67-
self.server.error = 0x100
68-
self.server.set_busy(True)
69-
self.server.parse_dm14(
70-
priority, pgn, sa, timestamp, data
71-
)
72-
self.server.set_busy(False)
73-
self.server.reset_query()
74-
self.state = DMState.IDLE
75-
self.server.error = 0x0
88+
self._handle_error(priority, pgn, sa, timestamp, data, 0x100)
7689

7790
case DMState.REQUEST_STARTED:
7891
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
@@ -101,32 +114,19 @@ def _listen_for_dm14(
101114
if self.proceed:
102115
self._notify_query_received() # notify incoming request
103116
else:
104-
self.server.error = 0x100
105-
self.server.set_busy(True)
106-
self.server.parse_dm14(
107-
priority, pgn, sa, timestamp, data
108-
)
109-
self.server.set_busy(False)
110-
self.server.reset_query()
111-
self.state = DMState.IDLE
112-
self.server.error = 0x0
117+
self._handle_error(priority, pgn, sa, timestamp, data, 0x100)
113118
else:
114-
self.server.error = 0x1003
115-
self.server.set_busy(True)
116-
self.server.parse_dm14(
117-
priority, pgn, sa, timestamp, data
118-
)
119-
self.server.set_busy(False)
120-
self.state = DMState.IDLE
121-
self.server.error = 0x0
119+
self._handle_error(priority, pgn, sa, timestamp, data, 0x1003)
122120

123121
case DMState.WAIT_QUERY:
124122
self.server.set_busy(True)
125123
self.server.parse_dm14(priority, pgn, sa, timestamp, data)
126124
self.server.set_busy(False)
125+
case DMState.SERVER_CLEANUP:
126+
self.state = DMState.IDLE
127127
case _:
128128
pass
129-
129+
130130
def respond(
131131
self,
132132
proceed: bool,
@@ -137,6 +137,7 @@ def respond(
137137
) -> list:
138138
"""
139139
Responds with requested data and error code, if applicable, to a read request
140+
140141
:param bool proceed: whether the operation is good to proceed
141142
:param list data: data to be sent to device
142143
:param int error: error code to be sent to device
@@ -145,14 +146,15 @@ def respond(
145146
"""
146147
if data is None:
147148
data = []
148-
if self.state is DMState.WAIT_RESPONSE:
149-
self._ca.unsubscribe(self._listen_for_dm14)
150-
self.state = DMState.IDLE
151-
return_data = self.server.respond(proceed, data, error, edcp, max_timeout)
152-
self._ca.subscribe(self._listen_for_dm14)
153-
return return_data
154-
else:
149+
150+
if self.state is not DMState.WAIT_RESPONSE:
155151
return data
152+
153+
self._ca.unsubscribe(self._listen_for_dm14)
154+
return_data = self.server.respond(proceed, data, error, edcp, max_timeout)
155+
self.state = DMState.SERVER_CLEANUP if self.server.state.value != DMState.IDLE.value else DMState.IDLE
156+
self._ca.subscribe(self._listen_for_dm14)
157+
return return_data
156158

157159
def read(
158160
self,
@@ -167,6 +169,7 @@ def read(
167169
) -> list:
168170
"""
169171
Make a dm14 read Query
172+
170173
:param int dest_address: destination address of the message
171174
:param int direct: direct address of the message
172175
:param int address: address of the message
@@ -189,9 +192,10 @@ def read(
189192
return_raw_bytes,
190193
max_timeout,
191194
)
192-
self.state = DMState.IDLE
195+
self.reset()
193196
return data
194197
else:
198+
self.reset()
195199
raise RuntimeWarning("Process already Running")
196200

197201
def write(
@@ -205,6 +209,7 @@ def write(
205209
) -> None:
206210
"""
207211
Send a write query to dest_address, requesting to write values at address
212+
208213
:param int dest_address: destination address of the message
209214
:param int direct: direct address of the message
210215
:param int address: address of the message
@@ -218,7 +223,7 @@ def write(
218223
self.query.write(
219224
dest_address, direct, address, values, object_byte_size, max_timeout
220225
)
221-
self.state = DMState.IDLE
226+
self.reset()
222227

223228
def set_seed_generator(self, seed_generator: callable) -> None:
224229
"""
@@ -229,7 +234,8 @@ def set_seed_generator(self, seed_generator: callable) -> None:
229234

230235
def set_seed_key_algorithm(self, algorithm: callable) -> None:
231236
"""
232-
set seed-key algorithm to be used for key generation
237+
Sets seed-key algorithm to be used for key generation
238+
233239
:param callable algorithm: seed-key algorithm
234240
"""
235241
self.seed_security = True
@@ -238,28 +244,34 @@ def set_seed_key_algorithm(self, algorithm: callable) -> None:
238244

239245
def set_verify_key(self, verify_key: callable) -> None:
240246
"""
241-
set verify key function to be used for verifying the key
247+
Sets verify key function to be used for verifying the key
248+
242249
:param callable verify_key: verify key function
243250
"""
244251
self.server.set_verify_key(verify_key)
245252

246253
def set_notify(self, notify: callable) -> None:
247254
"""
248-
set notify function to be used for notifying the user of memory accesses
255+
Sets notify function to be used for notifying the user of memory accesses
256+
249257
:param callable notify: notify function
250258
"""
251259
self._notify_query_received = notify
252260

253261
def set_proceed(self, proceed: callable) -> None:
254262
"""
255-
set proceed function to determine if a memory query is valid or not
263+
Sets proceed function to determine if a memory query is valid or not
264+
256265
:param callable proceed: proceed function
257266
"""
258267
self._proceed_function = proceed
259268

260-
def reset_query(self) -> None:
269+
def reset(self) -> None:
261270
"""
262-
reset query for the server
271+
Resets both server and query to remove transaction specific data
263272
"""
273+
self.state = DMState.IDLE
274+
self._ca.unsubscribe(self._listen_for_dm14)
264275
self._ca.subscribe(self._listen_for_dm14)
265-
self.server.reset_query()
276+
self.server.reset_server()
277+
self.query.reset_query()

test/test_ecu.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,25 @@ def test_add_bus_filters(feeder):
200200
]
201201
feeder.ecu.add_bus_filters(filters)
202202
assert feeder.ecu._bus.filters == filters
203+
204+
def test_subscribe(feeder):
205+
"""
206+
Test subscribing to callback
207+
"""
208+
call_count = 0
209+
210+
def callback(priority: int, pgn: int, sa: int, timestamp: int, data: bytearray):
211+
nonlocal call_count
212+
call_count += 1
213+
214+
feeder.ecu.subscribe(callback)
215+
216+
feeder.can_messages = [
217+
(Feeder.MsgType.CANRX, 0x00FEB201, [1, 2, 3, 4, 5, 6, 7, 8], 0.0),
218+
]
219+
220+
feeder.pdus = [(Feeder.MsgType.PDU, 65202, [1, 2, 3, 4, 5, 6, 7, 8])]
221+
222+
feeder.receive()
223+
224+
assert call_count == 1

0 commit comments

Comments
 (0)