python-can-j1939 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- j1939/Dm14Query.py +331 -0
- j1939/Dm14Server.py +399 -0
- j1939/__init__.py +11 -0
- j1939/controller_application.py +363 -0
- j1939/diagnostic_messages.py +448 -0
- j1939/electronic_control_unit.py +499 -0
- j1939/error_info.py +93 -0
- j1939/j1939_21.py +543 -0
- j1939/j1939_22.py +845 -0
- j1939/memory_access.py +387 -0
- j1939/message_id.py +51 -0
- j1939/name.py +268 -0
- j1939/parameter_group_number.py +136 -0
- j1939/version.py +1 -0
- python_can_j1939-0.1.0.dist-info/METADATA +325 -0
- python_can_j1939-0.1.0.dist-info/RECORD +19 -0
- python_can_j1939-0.1.0.dist-info/WHEEL +5 -0
- python_can_j1939-0.1.0.dist-info/licenses/LICENSE +22 -0
- python_can_j1939-0.1.0.dist-info/top_level.txt +1 -0
j1939/j1939_22.py
ADDED
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
from .parameter_group_number import ParameterGroupNumber
|
|
2
|
+
from .message_id import MessageId, FrameFormat
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class J1939_22:
|
|
10
|
+
class TpControlType:
|
|
11
|
+
RTS = 0 # Destination Specific Request_To_Send
|
|
12
|
+
CTS = 1 # Destination Specific Clear_To_Send
|
|
13
|
+
EOM_STATUS = 2 # Destination Specific or Global Destination End_of_Message Status
|
|
14
|
+
EOM_ACK = 3 # Destination Specific End_of_Message Acknowledge
|
|
15
|
+
BAM = 4 # Global Destination Broadcast Announce Message
|
|
16
|
+
ABORT = 15 # Destination Specific Connection Abort
|
|
17
|
+
|
|
18
|
+
class Adt: # assurance data type
|
|
19
|
+
NO_ADT = 0 # no assurance Data
|
|
20
|
+
MS_CS = 1 # Manufacturer specific cybersecurity assurance data
|
|
21
|
+
MS_FS = 2 # Manufacturer specific functional safety assurance
|
|
22
|
+
MS_COMBINED_CS_FS = 3 # Manufacturer specific combined cybersecurity followed by functional safety assurance
|
|
23
|
+
|
|
24
|
+
class ConnectionAbortReason:
|
|
25
|
+
BUSY = 1 # Already in one or more connection managed sessions and cannot support another
|
|
26
|
+
RESOURCES = 2 # System resources were needed for another task so this connection managed session was terminated
|
|
27
|
+
TIMEOUT = 3 # A timeout occured
|
|
28
|
+
# 4..250 Reserved by SAE
|
|
29
|
+
CTS_WHILE_DT = 4 # according AUTOSAR: CTS messages received when data transfer is in progress
|
|
30
|
+
# 251..255 Per J1939/71 definitions - but there are none?
|
|
31
|
+
|
|
32
|
+
class DataLength:
|
|
33
|
+
TP = 60
|
|
34
|
+
MULTI_PG = 60
|
|
35
|
+
|
|
36
|
+
class Timeout:
|
|
37
|
+
"""Timeouts according SAE J1939/22"""
|
|
38
|
+
Tr = 0.200 # Maximum Response Time
|
|
39
|
+
Th = 0.500 # Maximum time, for responder, between transmits of consecutive CTS messages during hold
|
|
40
|
+
T1 = 0.750 # Transport Segment Interval
|
|
41
|
+
T2 = 1.250 # Maximum time, for responder, to receive a DT segment after a CTS - Originator Failure
|
|
42
|
+
T3 = 1.250 # Maximum time, for originator, to receive a CTS after last DT segment - Responder Failure
|
|
43
|
+
T4 = 1.050 # Maximum time, for originator, to receive the next CTS messages since the previous “hold” CTS to hold a connection open
|
|
44
|
+
T5 = 3.000 # Maximum time, for originator, to receive EOMA after sending EOMS
|
|
45
|
+
|
|
46
|
+
class SendBufferState:
|
|
47
|
+
WAITING_CTS = 0 # waiting for CTS
|
|
48
|
+
SENDING_RTS_CTS = 1 # sending rts/cts packages
|
|
49
|
+
SENDING_BAM = 2 # sending broadcast packages
|
|
50
|
+
SENDING_EOM_STATUS = 3 # sending end of message
|
|
51
|
+
WAITING_EOM_ACK = 4 # waiting for end of message acknowledge
|
|
52
|
+
EOM_ACK_RECEIVED = 5 # eom acknowledge received successfully
|
|
53
|
+
TRANSMISSION_FINISHED = 6 # finished, remove buffer
|
|
54
|
+
|
|
55
|
+
class Acknowledgement:
|
|
56
|
+
ACK = 0
|
|
57
|
+
NACK = 1
|
|
58
|
+
AccessDenied = 2
|
|
59
|
+
CannotRespond = 3
|
|
60
|
+
|
|
61
|
+
def __init__(self, send_message, job_thread_wakeup, notify_subscribers, max_cmdt_packets, minimum_tp_rts_cts_dt_interval, minimum_tp_bam_dt_interval, ecu_is_message_acceptable):
|
|
62
|
+
# Receive buffers
|
|
63
|
+
self._rcv_buffer = {}
|
|
64
|
+
# Send buffers
|
|
65
|
+
self._snd_buffer = {}
|
|
66
|
+
# Multi-PG Send buffers
|
|
67
|
+
self._multi_pg_snd_buffer = {}
|
|
68
|
+
|
|
69
|
+
# List of ControllerApplication
|
|
70
|
+
self._cas = []
|
|
71
|
+
|
|
72
|
+
self._LUT_FD_DLC = []
|
|
73
|
+
for i in range(9): self._LUT_FD_DLC.append(i)
|
|
74
|
+
for _ in range(4): self._LUT_FD_DLC.append(12)
|
|
75
|
+
for _ in range(4): self._LUT_FD_DLC.append(16)
|
|
76
|
+
for _ in range(4): self._LUT_FD_DLC.append(20)
|
|
77
|
+
for _ in range(4): self._LUT_FD_DLC.append(24)
|
|
78
|
+
for _ in range(8): self._LUT_FD_DLC.append(32)
|
|
79
|
+
for _ in range(16): self._LUT_FD_DLC.append(48)
|
|
80
|
+
for _ in range(16): self._LUT_FD_DLC.append(64)
|
|
81
|
+
|
|
82
|
+
# minimum time between two tp rts/cts dt frames, not necessary for standard conforming applications,
|
|
83
|
+
# (they would use RTS/CTS flow control), but helps to talk to others without patching the library
|
|
84
|
+
self._minimum_tp_rts_cts_dt_interval = minimum_tp_rts_cts_dt_interval
|
|
85
|
+
|
|
86
|
+
# minimum time between two tp bam dt frames, inital value is 10ms
|
|
87
|
+
# specified time range in j1939-22: 10-200ms
|
|
88
|
+
if minimum_tp_bam_dt_interval == None:
|
|
89
|
+
self._minimum_tp_bam_dt_interval = 0.010
|
|
90
|
+
else:
|
|
91
|
+
self._minimum_tp_bam_dt_interval = minimum_tp_bam_dt_interval
|
|
92
|
+
|
|
93
|
+
# Up to 4 concurrent BAM sessions per originator address are allowed
|
|
94
|
+
self.__bam_session_list = [True] * 4
|
|
95
|
+
|
|
96
|
+
# Up to 8 concurrent RTS/CTS sessions per originator and responder address pair are allowed.
|
|
97
|
+
self.__rts_cts_session_list = [True] * 8
|
|
98
|
+
|
|
99
|
+
# number of packets that can be sent/received with CMDT (Connection Mode Data Transfer)
|
|
100
|
+
self._max_cmdt_packets = max_cmdt_packets
|
|
101
|
+
|
|
102
|
+
# Lock protecting _rcv_buffer, _snd_buffer, and _multi_pg_snd_buffer — accessed from
|
|
103
|
+
# both the Notifier thread (notify/process_tp_*) and the protocol job thread (async_job_thread).
|
|
104
|
+
self._buffer_lock = threading.Lock()
|
|
105
|
+
|
|
106
|
+
self.__job_thread_wakeup = job_thread_wakeup
|
|
107
|
+
self.__send_message = send_message
|
|
108
|
+
self.__notify_subscribers = notify_subscribers
|
|
109
|
+
self.__ecu_is_message_acceptable = ecu_is_message_acceptable
|
|
110
|
+
|
|
111
|
+
def add_ca(self, ca):
|
|
112
|
+
self._cas.append(ca)
|
|
113
|
+
|
|
114
|
+
def remove_ca(self, device_address):
|
|
115
|
+
for ca in self._cas:
|
|
116
|
+
if device_address == ca._device_address_preferred:
|
|
117
|
+
self._cas.remove(ca)
|
|
118
|
+
return True
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def _buffer_hash(self, session_num, src_address, dest_address):
|
|
122
|
+
"""Calculates a hash value for the given address pair
|
|
123
|
+
|
|
124
|
+
:param src_address:
|
|
125
|
+
The Source-Address the connection should bound to.
|
|
126
|
+
:param dest_address:
|
|
127
|
+
The Destination-Address the connection should bound to.
|
|
128
|
+
|
|
129
|
+
:return:
|
|
130
|
+
The calculated hash value.
|
|
131
|
+
|
|
132
|
+
:rtype: int
|
|
133
|
+
"""
|
|
134
|
+
return ((session_num & 0xF) << 16) | ((src_address & 0xFF) << 8) | (dest_address & 0xFF)
|
|
135
|
+
|
|
136
|
+
def _buffer_hash_mpg(self, frame_format, msg_counter, src_address, dest_address):
|
|
137
|
+
"""Calculates a hash value for the given multi-pg arguments
|
|
138
|
+
|
|
139
|
+
:param frame_format:
|
|
140
|
+
The frame-format (FBFF, FEFF) the connection should bound to.
|
|
141
|
+
:param msg_counter:
|
|
142
|
+
The message counter the connection should bound to.
|
|
143
|
+
:param src_address:
|
|
144
|
+
The Source-Address the connection should bound to.
|
|
145
|
+
:param dest_address:
|
|
146
|
+
The Destination-Address the connection should bound to.
|
|
147
|
+
|
|
148
|
+
:return:
|
|
149
|
+
The calculated hash value.
|
|
150
|
+
|
|
151
|
+
:rtype: int
|
|
152
|
+
"""
|
|
153
|
+
return ((frame_format & 0xFF) << 24) | ((msg_counter & 0xFF) << 16) | ((src_address & 0xFF) << 8) | (dest_address & 0xFF)
|
|
154
|
+
|
|
155
|
+
def _buffer_unhash(self, hash):
|
|
156
|
+
"""Calculates session-number, source-address and destination-address for the given hash value
|
|
157
|
+
|
|
158
|
+
:param hash:
|
|
159
|
+
The hash to be unhased
|
|
160
|
+
|
|
161
|
+
:return:
|
|
162
|
+
The session-number, source-address and destination-address
|
|
163
|
+
"""
|
|
164
|
+
return ((hash >> 16) & 0xFF), ((hash >> 8) & 0xFF), (hash & 0xFF)
|
|
165
|
+
|
|
166
|
+
def _buffer_unhash_mpg(self, hash):
|
|
167
|
+
"""Calculates frame_format, msg_counter, source-address and destination-address for the given hash value
|
|
168
|
+
|
|
169
|
+
:param hash:
|
|
170
|
+
The hash to be unhased
|
|
171
|
+
|
|
172
|
+
:return:
|
|
173
|
+
The session-number, source-address and destination-address
|
|
174
|
+
"""
|
|
175
|
+
return ((hash >> 24) & 0xFF), ((hash >> 16) & 0xFF), ((hash >> 8) & 0xFF), (hash & 0xFF)
|
|
176
|
+
|
|
177
|
+
def __get_bam_session(self):
|
|
178
|
+
for idx, i in enumerate(self.__bam_session_list):
|
|
179
|
+
if i == True:
|
|
180
|
+
self.__bam_session_list[idx] = False
|
|
181
|
+
return idx
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
def __put_bam_session(self, session):
|
|
185
|
+
self.__bam_session_list[session] = True
|
|
186
|
+
|
|
187
|
+
def __get_rts_cts_session(self):
|
|
188
|
+
for idx, i in enumerate(self.__rts_cts_session_list):
|
|
189
|
+
if i == True:
|
|
190
|
+
self.__rts_cts_session_list[idx] = False
|
|
191
|
+
return idx
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def __put_rts_cts_session(self, session):
|
|
195
|
+
self.__rts_cts_session_list[session] = True
|
|
196
|
+
|
|
197
|
+
def send_pgn(self, data_page, pdu_format, pdu_specific, priority, src_address, data, time_limit, frame_format, tos=2, trailer_format=0):
|
|
198
|
+
pgn = ParameterGroupNumber(data_page, pdu_format, pdu_specific)
|
|
199
|
+
data_length = len(data)
|
|
200
|
+
|
|
201
|
+
if data_length <= self.DataLength.TP:
|
|
202
|
+
if (tos != 2) or (trailer_format != 0):
|
|
203
|
+
print('currently "SAE J1939 with no assurance data" trailer format supported only')
|
|
204
|
+
|
|
205
|
+
if pgn.is_pdu1_format:
|
|
206
|
+
cpgn = pgn.value & 0xFFF00
|
|
207
|
+
dst_address = pdu_specific
|
|
208
|
+
else:
|
|
209
|
+
cpgn = pgn.value
|
|
210
|
+
dst_address = ParameterGroupNumber.Address.GLOBAL
|
|
211
|
+
|
|
212
|
+
if (frame_format==FrameFormat.FBFF):
|
|
213
|
+
priority = 0
|
|
214
|
+
if (dst_address!=ParameterGroupNumber.Address.GLOBAL):
|
|
215
|
+
logger.info('FBFF message must be a broadcast type')
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
# create header dict
|
|
219
|
+
cpg = {'priority': (priority & 0x7), 'tos': (tos & 0x7), 'tf': (trailer_format & 0x7), 'cpgn': (cpgn & 0x3FFFF), 'data_length': data_length, 'data': data.copy()}
|
|
220
|
+
|
|
221
|
+
# send immediately
|
|
222
|
+
if time_limit == 0:
|
|
223
|
+
self.__send_multi_pg(frame_format, [cpg], src_address, dst_address)
|
|
224
|
+
else:
|
|
225
|
+
session = 0
|
|
226
|
+
deadline = time.monotonic() + time_limit
|
|
227
|
+
with self._buffer_lock:
|
|
228
|
+
while True:
|
|
229
|
+
hash = self._buffer_hash_mpg(frame_format, session, src_address, dst_address)
|
|
230
|
+
if hash not in self._multi_pg_snd_buffer:
|
|
231
|
+
self._multi_pg_snd_buffer[hash] = {'deadline': deadline, 'cpg': [cpg], 'fill_level': 4 + data_length}
|
|
232
|
+
break
|
|
233
|
+
elif (self._multi_pg_snd_buffer[hash]['fill_level'] <= (self.DataLength.TP - data_length)):
|
|
234
|
+
# update fill level
|
|
235
|
+
self._multi_pg_snd_buffer[hash]['fill_level'] += 4 + data_length
|
|
236
|
+
# update deadline
|
|
237
|
+
if self._multi_pg_snd_buffer[hash]['deadline'] > deadline:
|
|
238
|
+
self._multi_pg_snd_buffer[hash]['deadline'] = deadline
|
|
239
|
+
# append c-pg
|
|
240
|
+
self._multi_pg_snd_buffer[hash]['cpg'].append(cpg)
|
|
241
|
+
break
|
|
242
|
+
else:
|
|
243
|
+
# trigger sending
|
|
244
|
+
self._multi_pg_snd_buffer[hash]['deadline'] = time.monotonic()
|
|
245
|
+
self.__job_thread_wakeup()
|
|
246
|
+
# get next buffer
|
|
247
|
+
session += 1
|
|
248
|
+
else:
|
|
249
|
+
# if the PF is between 0 and 239, the message is destination dependent when pdu_specific != 255
|
|
250
|
+
# if the PF is between 240 and 255, the message can only be broadcast
|
|
251
|
+
if (pdu_specific == ParameterGroupNumber.Address.GLOBAL) or ParameterGroupNumber(0, pdu_format, pdu_specific).is_pdu2_format:
|
|
252
|
+
dest_address = ParameterGroupNumber.Address.GLOBAL
|
|
253
|
+
session_num = self.__get_bam_session()
|
|
254
|
+
if session_num == None:
|
|
255
|
+
#print('bam session not available')
|
|
256
|
+
return False
|
|
257
|
+
else:
|
|
258
|
+
dest_address = pdu_specific
|
|
259
|
+
session_num = self.__get_rts_cts_session()
|
|
260
|
+
if session_num == None:
|
|
261
|
+
#print('rts/cts session not available')
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
# init sequence
|
|
265
|
+
buffer_hash = self._buffer_hash(session_num, src_address, dest_address)
|
|
266
|
+
|
|
267
|
+
message_size = data_length
|
|
268
|
+
num_segments = int(message_size / self.DataLength.TP ) + ((message_size % self.DataLength.TP ) != 0)
|
|
269
|
+
|
|
270
|
+
# set default priority
|
|
271
|
+
if priority == None: priority = 7
|
|
272
|
+
|
|
273
|
+
# get chunks from data
|
|
274
|
+
chunk_size = self.DataLength.TP
|
|
275
|
+
data_list = [list(data[i:i + chunk_size]) for i in range(0, data_length, chunk_size)]
|
|
276
|
+
|
|
277
|
+
# if the PF is between 240 and 255, the message can only be broadcast
|
|
278
|
+
if dest_address == ParameterGroupNumber.Address.GLOBAL:
|
|
279
|
+
|
|
280
|
+
# send BAM
|
|
281
|
+
self.__send_tp_bam(priority, src_address, session_num, pgn.value, message_size, num_segments)
|
|
282
|
+
|
|
283
|
+
# init new buffer for this connection
|
|
284
|
+
with self._buffer_lock:
|
|
285
|
+
self._snd_buffer[buffer_hash] = {
|
|
286
|
+
'pgn': pgn.value,
|
|
287
|
+
'priority': priority,
|
|
288
|
+
'session': session_num,
|
|
289
|
+
'message_size': message_size,
|
|
290
|
+
'num_segments': num_segments,
|
|
291
|
+
'data': data_list,
|
|
292
|
+
'state': self.SendBufferState.SENDING_BAM,
|
|
293
|
+
'deadline': time.monotonic() + self._minimum_tp_bam_dt_interval,
|
|
294
|
+
'src_address' : src_address,
|
|
295
|
+
'dest_address' : ParameterGroupNumber.Address.GLOBAL,
|
|
296
|
+
'next_packet_to_send' : 0,
|
|
297
|
+
}
|
|
298
|
+
else:
|
|
299
|
+
# send RTS/CTS
|
|
300
|
+
pgn.pdu_specific = 0 # this is 0 for peer-to-peer transfer
|
|
301
|
+
# init new buffer for this connection
|
|
302
|
+
with self._buffer_lock:
|
|
303
|
+
self._snd_buffer[buffer_hash] = {
|
|
304
|
+
'pgn': pgn.value,
|
|
305
|
+
'priority': priority,
|
|
306
|
+
'session': session_num,
|
|
307
|
+
'message_size': message_size,
|
|
308
|
+
'num_segments': num_segments,
|
|
309
|
+
'data': data_list,
|
|
310
|
+
'state': self.SendBufferState.WAITING_CTS,
|
|
311
|
+
'deadline': time.monotonic() + self.Timeout.T3,
|
|
312
|
+
'src_address' : src_address,
|
|
313
|
+
'dest_address' : pdu_specific,
|
|
314
|
+
'next_packet_to_send' : 0,
|
|
315
|
+
'next_wait_on_cts': 0,
|
|
316
|
+
}
|
|
317
|
+
self.__send_tp_rts(priority, src_address, pdu_specific, session_num, pgn.value, message_size, num_segments, min(self._max_cmdt_packets, num_segments))
|
|
318
|
+
|
|
319
|
+
self.__job_thread_wakeup()
|
|
320
|
+
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
def __send_multi_pg(self, frame_format, cpg_list, src_address, dst_address):
|
|
324
|
+
# deadline reached
|
|
325
|
+
priority = 7
|
|
326
|
+
data = []
|
|
327
|
+
for cpg in cpg_list:
|
|
328
|
+
priority = min(cpg['priority'], priority)
|
|
329
|
+
data.append( (cpg['tos'] << 5) | (cpg['tf'] << 2) | ((cpg['cpgn'] >> 16) & 0x3) )
|
|
330
|
+
data.append( ((cpg['cpgn'] >> 8) & 0xFF) )
|
|
331
|
+
data.append( (cpg['cpgn'] & 0xFF) )
|
|
332
|
+
data.append( cpg['data_length'] )
|
|
333
|
+
data.extend( cpg['data'])
|
|
334
|
+
|
|
335
|
+
# padding
|
|
336
|
+
next_valid_fd_length = self._LUT_FD_DLC[len(data)]
|
|
337
|
+
if next_valid_fd_length < 0:
|
|
338
|
+
next_valid_fd_length = 0
|
|
339
|
+
|
|
340
|
+
# padding with service header 0
|
|
341
|
+
padding_cnt = 0
|
|
342
|
+
while len(data)<next_valid_fd_length:
|
|
343
|
+
if padding_cnt < 3:
|
|
344
|
+
data.append(0)
|
|
345
|
+
padding_cnt += 1
|
|
346
|
+
else:
|
|
347
|
+
data.append(0xAA)
|
|
348
|
+
|
|
349
|
+
if frame_format == FrameFormat.FBFF:
|
|
350
|
+
self.__send_message(src_address, False, data, fd_format=True)
|
|
351
|
+
else:
|
|
352
|
+
mid = MessageId(priority=priority,
|
|
353
|
+
parameter_group_number=ParameterGroupNumber.PGN.FEFF_MULTI_PG | (dst_address & 0xFF),
|
|
354
|
+
source_address=src_address)
|
|
355
|
+
self.__send_message(mid.can_id, True, data, fd_format=True)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def async_job_thread(self, now):
|
|
359
|
+
|
|
360
|
+
next_wakeup = now + 5.0 # wakeup in 5 seconds
|
|
361
|
+
|
|
362
|
+
with self._buffer_lock:
|
|
363
|
+
# check receive buffers for timeout
|
|
364
|
+
for bufid in list(self._rcv_buffer):
|
|
365
|
+
buf = self._rcv_buffer[bufid]
|
|
366
|
+
if buf['deadline'] != 0:
|
|
367
|
+
if buf['deadline'] > now:
|
|
368
|
+
if next_wakeup > buf['deadline']:
|
|
369
|
+
next_wakeup = buf['deadline']
|
|
370
|
+
else:
|
|
371
|
+
# deadline reached
|
|
372
|
+
logger.info('Deadline reached for rcv_buffer src 0x%02X dst 0x%02X', buf['src_address'], buf['dest_address'] )
|
|
373
|
+
if buf['dest_address'] != ParameterGroupNumber.Address.GLOBAL:
|
|
374
|
+
self.__send_tp_abort(buf['dest_address'], buf['src_address'], buf['session'], self.ConnectionAbortReason.TIMEOUT, buf['pgn'])
|
|
375
|
+
del self._rcv_buffer[bufid]
|
|
376
|
+
self.__put_rts_cts_session(buf['session'])
|
|
377
|
+
else:
|
|
378
|
+
del self._rcv_buffer[bufid]
|
|
379
|
+
self.__put_bam_session(buf['session'])
|
|
380
|
+
# TODO: should we notify our CAs about the cancelled transfer?
|
|
381
|
+
|
|
382
|
+
# check multi-pg send buffers for timeout
|
|
383
|
+
for bufid in list(self._multi_pg_snd_buffer):
|
|
384
|
+
buf = self._multi_pg_snd_buffer[bufid]
|
|
385
|
+
if buf['deadline'] > now:
|
|
386
|
+
if next_wakeup > buf['deadline']:
|
|
387
|
+
next_wakeup = buf['deadline']
|
|
388
|
+
else:
|
|
389
|
+
# deadline reached
|
|
390
|
+
frame_format, session_num, src_address, dst_address = self._buffer_unhash_mpg(bufid)
|
|
391
|
+
self.__send_multi_pg(frame_format, buf['cpg'], src_address, dst_address)
|
|
392
|
+
del self._multi_pg_snd_buffer[bufid]
|
|
393
|
+
|
|
394
|
+
# check send buffers
|
|
395
|
+
for bufid in list(self._snd_buffer):
|
|
396
|
+
buf = self._snd_buffer[bufid]
|
|
397
|
+
if buf['deadline'] != 0:
|
|
398
|
+
if buf['deadline'] > now:
|
|
399
|
+
if next_wakeup > buf['deadline']:
|
|
400
|
+
next_wakeup = buf['deadline']
|
|
401
|
+
else:
|
|
402
|
+
# deadline reached
|
|
403
|
+
if buf['state'] == self.SendBufferState.WAITING_CTS:
|
|
404
|
+
logger.info('Deadline WAITING_CTS reached for snd_buffer src 0x%02X dst 0x%02X', buf['src_address'], buf['dest_address'] )
|
|
405
|
+
self.__send_tp_abort(buf['src_address'], buf['dest_address'], buf['session'], self.ConnectionAbortReason.TIMEOUT, buf['pgn'])
|
|
406
|
+
del self._snd_buffer[bufid]
|
|
407
|
+
self.__put_rts_cts_session(buf['session'])
|
|
408
|
+
# TODO: should we notify our CAs about the cancelled transfer?
|
|
409
|
+
|
|
410
|
+
elif buf['state'] == self.SendBufferState.SENDING_RTS_CTS:
|
|
411
|
+
while buf['next_packet_to_send'] < buf['num_segments']:
|
|
412
|
+
package = buf['next_packet_to_send']
|
|
413
|
+
self.__send_tp_dt(buf['src_address'], buf['dest_address'], buf['session'], package+1, buf['data'][package])
|
|
414
|
+
|
|
415
|
+
buf['next_packet_to_send'] += 1
|
|
416
|
+
# send end of message status
|
|
417
|
+
if (package+1) == buf['num_segments']:
|
|
418
|
+
self.__send_tp_eom_status(buf['src_address'], buf['dest_address'], buf['session'], buf['message_size'], buf['num_segments'], buf['pgn'])
|
|
419
|
+
buf['deadline'] = time.monotonic() + self.Timeout.T5
|
|
420
|
+
buf['state'] = self.SendBufferState.WAITING_EOM_ACK
|
|
421
|
+
break
|
|
422
|
+
elif package == buf['next_wait_on_cts']:
|
|
423
|
+
# wait on next cts
|
|
424
|
+
buf['state'] = self.SendBufferState.WAITING_CTS
|
|
425
|
+
buf['deadline'] = time.monotonic() + self.Timeout.T3
|
|
426
|
+
break
|
|
427
|
+
elif self._minimum_tp_rts_cts_dt_interval != None:
|
|
428
|
+
buf['deadline'] = time.monotonic() + self._minimum_tp_rts_cts_dt_interval
|
|
429
|
+
break
|
|
430
|
+
|
|
431
|
+
# recalc next wakeup
|
|
432
|
+
if next_wakeup > buf['deadline']:
|
|
433
|
+
next_wakeup = buf['deadline']
|
|
434
|
+
|
|
435
|
+
elif buf['state'] == self.SendBufferState.WAITING_EOM_ACK:
|
|
436
|
+
# TODO: should we inform the application about the eom ack timeout?
|
|
437
|
+
del self._snd_buffer[bufid]
|
|
438
|
+
self.__put_rts_cts_session(buf['session'])
|
|
439
|
+
|
|
440
|
+
elif buf['state'] == self.SendBufferState.EOM_ACK_RECEIVED:
|
|
441
|
+
# TODO: should we inform the application about the successful transmission?
|
|
442
|
+
del self._snd_buffer[bufid]
|
|
443
|
+
self.__put_rts_cts_session(buf['session'])
|
|
444
|
+
|
|
445
|
+
elif buf['state'] == self.SendBufferState.SENDING_BAM:
|
|
446
|
+
# send next broadcast message...
|
|
447
|
+
package = buf['next_packet_to_send']
|
|
448
|
+
self.__send_tp_dt(buf['src_address'], buf['dest_address'], buf['session'], package+1, buf['data'][package])
|
|
449
|
+
buf['next_packet_to_send'] += 1
|
|
450
|
+
|
|
451
|
+
if buf['next_packet_to_send'] < buf['num_segments']:
|
|
452
|
+
buf['deadline'] = time.monotonic() + self._minimum_tp_bam_dt_interval
|
|
453
|
+
# recalc next wakeup
|
|
454
|
+
if next_wakeup > buf['deadline']:
|
|
455
|
+
next_wakeup = buf['deadline']
|
|
456
|
+
else:
|
|
457
|
+
buf['state'] = self.SendBufferState.SENDING_EOM_STATUS
|
|
458
|
+
# recalc next wakeup
|
|
459
|
+
buf['deadline'] = time.monotonic() + self._minimum_tp_bam_dt_interval
|
|
460
|
+
if next_wakeup > buf['deadline']:
|
|
461
|
+
next_wakeup = buf['deadline']
|
|
462
|
+
|
|
463
|
+
elif buf['state'] == self.SendBufferState.SENDING_EOM_STATUS:
|
|
464
|
+
# done
|
|
465
|
+
self.__send_tp_eom_status(buf['src_address'], buf['dest_address'],
|
|
466
|
+
buf['session'],
|
|
467
|
+
buf['message_size'], buf['num_segments'], buf['pgn'])
|
|
468
|
+
del self._snd_buffer[bufid]
|
|
469
|
+
self.__put_bam_session(buf['session'])
|
|
470
|
+
elif buf['state'] == self.SendBufferState.TRANSMISSION_FINISHED:
|
|
471
|
+
del self._snd_buffer[bufid]
|
|
472
|
+
else:
|
|
473
|
+
logger.critical('unknown SendBufferState %d', buf['state'])
|
|
474
|
+
del self._snd_buffer[bufid]
|
|
475
|
+
|
|
476
|
+
return next_wakeup
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _process_tp_cm(self, mid, dest_address, data, timestamp):
|
|
480
|
+
"""Processes a Transport Protocol Connection Management (TP.CM) message
|
|
481
|
+
|
|
482
|
+
:param j1939.MessageId mid:
|
|
483
|
+
A MessageId object holding the information extracted from the can_id.
|
|
484
|
+
:param int dest_address:
|
|
485
|
+
The destination address of the message
|
|
486
|
+
:param bytearray data:
|
|
487
|
+
The data contained in the can-message.
|
|
488
|
+
:param float timestamp:
|
|
489
|
+
The timestamp the message was received (mostly) in fractions of Epoch-Seconds.
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
# check minimum tp-cm length
|
|
493
|
+
if len(data) < 12:
|
|
494
|
+
logger.info('tp-cm with incorrect dlc received, id', mid )
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
src_address = mid.source_address
|
|
498
|
+
control_byte = data[0] & 0xF
|
|
499
|
+
session_num = (data[0] >> 4) & 0xF
|
|
500
|
+
message_size = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8) | ((data[3] & 0xFF) << 16)
|
|
501
|
+
segment_num = (data[4] & 0xFF) | ((data[5] & 0xFF) << 8) | ((data[6] & 0xFF) << 16)
|
|
502
|
+
pgn = (data[9] & 0xFF) | ((data[10] & 0xFF) << 8) | ((data[11] & 0xFF) << 16)
|
|
503
|
+
|
|
504
|
+
with self._buffer_lock:
|
|
505
|
+
if control_byte == self.TpControlType.RTS:
|
|
506
|
+
buffer_hash = self._buffer_hash(session_num, src_address, dest_address)
|
|
507
|
+
num_segments = data[7] # Maximum number of segments that can be sent in response to one CTS.
|
|
508
|
+
|
|
509
|
+
if buffer_hash in self._rcv_buffer:
|
|
510
|
+
# according SAE J1939-22 we have to send an ABORT if an active
|
|
511
|
+
# transmission is already established
|
|
512
|
+
self.__send_tp_abort(dest_address, src_address, session_num, self.ConnectionAbortReason.BUSY, pgn)
|
|
513
|
+
self.__put_rts_cts_session(session_num)
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
# limit max number segments
|
|
517
|
+
num_segments = min(num_segments, segment_num)
|
|
518
|
+
|
|
519
|
+
# open new buffer for this connection
|
|
520
|
+
self._rcv_buffer[buffer_hash] = {
|
|
521
|
+
'pgn': pgn,
|
|
522
|
+
'session': session_num,
|
|
523
|
+
'message_size': message_size, # total message size, number of bytes
|
|
524
|
+
'num_segments': segment_num, # total number of segments
|
|
525
|
+
'next_packet': 1,
|
|
526
|
+
'next_cts_border': min(self._max_cmdt_packets, num_segments),
|
|
527
|
+
'num_segments_max_rec': min(self._max_cmdt_packets, num_segments),
|
|
528
|
+
'data': [],
|
|
529
|
+
'deadline': time.monotonic() + self.Timeout.T2,
|
|
530
|
+
'src_address' : src_address,
|
|
531
|
+
'dest_address' : dest_address,
|
|
532
|
+
}
|
|
533
|
+
self.__send_tp_cts(dest_address, src_address, session_num, self._rcv_buffer[buffer_hash]['num_segments_max_rec'], 1, pgn)
|
|
534
|
+
self.__job_thread_wakeup()
|
|
535
|
+
|
|
536
|
+
elif control_byte == self.TpControlType.CTS:
|
|
537
|
+
buffer_hash = self._buffer_hash(session_num, dest_address, src_address)
|
|
538
|
+
num_segments = data[7] # Maximum number of segments that can be sent
|
|
539
|
+
if buffer_hash not in self._snd_buffer:
|
|
540
|
+
self.__send_tp_abort(dest_address, src_address, session_num, self.ConnectionAbortReason.RESOURCES, pgn)
|
|
541
|
+
self.__put_rts_cts_session(session_num)
|
|
542
|
+
return
|
|
543
|
+
if num_segments == 0:
|
|
544
|
+
# SAE J1939/22
|
|
545
|
+
# receiver requests a pause
|
|
546
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.Th
|
|
547
|
+
self.__job_thread_wakeup()
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
num_segments_all = self._snd_buffer[buffer_hash]['num_segments']
|
|
551
|
+
self._snd_buffer[buffer_hash]['next_packet_to_send'] = segment_num - 1
|
|
552
|
+
segments_to_be_sent = num_segments_all - self._snd_buffer[buffer_hash]['next_packet_to_send']
|
|
553
|
+
if num_segments > num_segments_all:
|
|
554
|
+
logger.debug("CTS: Allowed more packets %d than complete transmission %d", num_segments, num_segments_all)
|
|
555
|
+
num_segments = num_segments_all
|
|
556
|
+
if num_segments > self._max_cmdt_packets:
|
|
557
|
+
logger.debug("CTS: Allowed more packets %d than transmitters max-cmdt-number %d", num_segments, self._max_cmdt_packets)
|
|
558
|
+
num_segments = self._max_cmdt_packets
|
|
559
|
+
if num_segments > segments_to_be_sent:
|
|
560
|
+
logger.debug("CTS: Allowed more packets %d than needed to complete transmission %d", num_segments, segments_to_be_sent)
|
|
561
|
+
num_segments = segments_to_be_sent
|
|
562
|
+
|
|
563
|
+
self._snd_buffer[buffer_hash]['next_wait_on_cts'] = self._snd_buffer[buffer_hash]['next_packet_to_send'] + num_segments - 1
|
|
564
|
+
|
|
565
|
+
self._snd_buffer[buffer_hash]['state'] = self.SendBufferState.SENDING_RTS_CTS
|
|
566
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic() # wake up immediately
|
|
567
|
+
self.__job_thread_wakeup()
|
|
568
|
+
|
|
569
|
+
elif control_byte == self.TpControlType.EOM_STATUS:
|
|
570
|
+
buffer_hash = self._buffer_hash(session_num, src_address, dest_address)
|
|
571
|
+
if buffer_hash not in self._rcv_buffer:
|
|
572
|
+
self.__put_rts_cts_session(session_num)
|
|
573
|
+
return
|
|
574
|
+
pgn = self._rcv_buffer[buffer_hash]['pgn']
|
|
575
|
+
if (self._rcv_buffer[buffer_hash]['message_size'] == message_size) and (self._rcv_buffer[buffer_hash]['num_segments'] == segment_num):
|
|
576
|
+
self.__notify_subscribers(mid.priority, pgn, src_address, dest_address, timestamp, self._rcv_buffer[buffer_hash]['data'])
|
|
577
|
+
if dest_address != ParameterGroupNumber.Address.GLOBAL:
|
|
578
|
+
self.__send_tp_eom_ack(dest_address, src_address, session_num, message_size, segment_num, pgn)
|
|
579
|
+
else:
|
|
580
|
+
self.__send_tp_abort(dest_address, src_address, session_num, self.ConnectionAbortReason.RESOURCES, pgn)
|
|
581
|
+
del self._rcv_buffer[buffer_hash]
|
|
582
|
+
self.__put_rts_cts_session(session_num)
|
|
583
|
+
|
|
584
|
+
elif control_byte == self.TpControlType.EOM_ACK:
|
|
585
|
+
buffer_hash = self._buffer_hash(session_num, dest_address, src_address)
|
|
586
|
+
if buffer_hash not in self._snd_buffer:
|
|
587
|
+
self.__send_tp_abort(dest_address, src_address, session_num, self.ConnectionAbortReason.RESOURCES, pgn)
|
|
588
|
+
self.__put_rts_cts_session(session_num)
|
|
589
|
+
return
|
|
590
|
+
# TODO: should we inform the application about the successful transmission?
|
|
591
|
+
# Notify subscribers here to be used for the memory access server to know when to send operation complete
|
|
592
|
+
self.__notify_subscribers(mid.priority, pgn, mid.source_address, dest_address, timestamp, data)
|
|
593
|
+
self._snd_buffer[buffer_hash]['state'] = self.SendBufferState.EOM_ACK_RECEIVED
|
|
594
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic() # wake up immediately
|
|
595
|
+
self.__job_thread_wakeup()
|
|
596
|
+
|
|
597
|
+
# BAM FD.TP.CM received
|
|
598
|
+
elif control_byte == self.TpControlType.BAM:
|
|
599
|
+
buffer_hash = self._buffer_hash(session_num, src_address, dest_address)
|
|
600
|
+
if buffer_hash in self._rcv_buffer:
|
|
601
|
+
# buffer already in use
|
|
602
|
+
logger.info('bam receive buffer already in use 0x%x', buffer_hash )
|
|
603
|
+
del self._rcv_buffer[buffer_hash]
|
|
604
|
+
self.__put_bam_session(session_num)
|
|
605
|
+
return
|
|
606
|
+
|
|
607
|
+
# init new buffer for this connection
|
|
608
|
+
self._rcv_buffer[buffer_hash] = {
|
|
609
|
+
'pgn': pgn,
|
|
610
|
+
'session': session_num,
|
|
611
|
+
'message_size': message_size, # Total message size, number of bytes
|
|
612
|
+
'num_segments': segment_num, # Total number of segments
|
|
613
|
+
'next_packet': 1,
|
|
614
|
+
'data': [],
|
|
615
|
+
'deadline': time.monotonic() + self.Timeout.T1,
|
|
616
|
+
'src_address' : src_address,
|
|
617
|
+
'dest_address' : dest_address,
|
|
618
|
+
}
|
|
619
|
+
self.__job_thread_wakeup()
|
|
620
|
+
|
|
621
|
+
elif control_byte == self.TpControlType.ABORT:
|
|
622
|
+
# if abort received before transmission established -> cancel transmission
|
|
623
|
+
buffer_hash = self._buffer_hash(session_num, dest_address, src_address)
|
|
624
|
+
if buffer_hash in self._snd_buffer and self._snd_buffer[buffer_hash]['state'] == self.SendBufferState.WAITING_CTS:
|
|
625
|
+
# cancel transmission
|
|
626
|
+
self._snd_buffer[buffer_hash]['state'] = self.SendBufferState.TRANSMISSION_FINISHED
|
|
627
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic()
|
|
628
|
+
# TODO: any more abort responses?
|
|
629
|
+
else:
|
|
630
|
+
raise RuntimeError('Received TP.CM with unknown control_byte %d', control_byte)
|
|
631
|
+
|
|
632
|
+
def _process_tp_dt(self, mid, dest_address, data, timestamp):
|
|
633
|
+
|
|
634
|
+
# check minimum tp-dt length
|
|
635
|
+
if len(data) <= 4:
|
|
636
|
+
logger.info('tp-dt with incorrect dlc received, id', mid )
|
|
637
|
+
return
|
|
638
|
+
|
|
639
|
+
src_address = mid.source_address
|
|
640
|
+
dtfi = data[0] & 0xF # Data Transfer Format Indicator
|
|
641
|
+
session_num = (data[0] >> 4) & 0xF
|
|
642
|
+
segment_num = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8) | ((data[3] & 0xFF) << 16)
|
|
643
|
+
|
|
644
|
+
if segment_num == 0:
|
|
645
|
+
logger.critical('segment number of 0 is not valid.')
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
buffer_hash = self._buffer_hash(session_num, src_address, dest_address)
|
|
649
|
+
|
|
650
|
+
with self._buffer_lock:
|
|
651
|
+
if buffer_hash not in self._rcv_buffer:
|
|
652
|
+
logger.critical('buffer error process dt 0x%x', buffer_hash)
|
|
653
|
+
return
|
|
654
|
+
|
|
655
|
+
if self._rcv_buffer[buffer_hash]['next_packet'] != segment_num:
|
|
656
|
+
logger.critical('packet error. required: '+ str(self._rcv_buffer[buffer_hash]['next_packet']) + ' received: ' + str(segment_num) )
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
# get data
|
|
660
|
+
self._rcv_buffer[buffer_hash]['data'].extend(data[4:])
|
|
661
|
+
|
|
662
|
+
self._rcv_buffer[buffer_hash]['next_packet'] = segment_num + 1
|
|
663
|
+
|
|
664
|
+
# message is complete with sending an acknowledge
|
|
665
|
+
if len(self._rcv_buffer[buffer_hash]['data']) >= self._rcv_buffer[buffer_hash]['message_size']:
|
|
666
|
+
logger.info('finished RCV of PGN {} with size {}'.format(self._rcv_buffer[buffer_hash]['pgn'], self._rcv_buffer[buffer_hash]['message_size']))
|
|
667
|
+
# shorten data to message_size
|
|
668
|
+
self._rcv_buffer[buffer_hash]['data'] = self._rcv_buffer[buffer_hash]['data'][:self._rcv_buffer[buffer_hash]['message_size']]
|
|
669
|
+
# finished reassembly
|
|
670
|
+
if dest_address != ParameterGroupNumber.Address.GLOBAL:
|
|
671
|
+
# set deadline for waiting on eom status
|
|
672
|
+
self._rcv_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.T1
|
|
673
|
+
self.__job_thread_wakeup()
|
|
674
|
+
return
|
|
675
|
+
|
|
676
|
+
# send clear to send
|
|
677
|
+
if (dest_address != ParameterGroupNumber.Address.GLOBAL) and (segment_num >= self._rcv_buffer[buffer_hash]['next_cts_border']):
|
|
678
|
+
# send cts
|
|
679
|
+
number_of_packets_that_can_be_sent = min( self._rcv_buffer[buffer_hash]['num_segments_max_rec'], self._rcv_buffer[buffer_hash]['num_segments'] - self._rcv_buffer[buffer_hash]['next_cts_border'] )
|
|
680
|
+
next_packet_to_be_sent = self._rcv_buffer[buffer_hash]['next_cts_border'] + 1
|
|
681
|
+
self.__send_tp_cts(dest_address, src_address, session_num, number_of_packets_that_can_be_sent, next_packet_to_be_sent, self._rcv_buffer[buffer_hash]['pgn'])
|
|
682
|
+
|
|
683
|
+
# calculate next packet number at which a CTS is to be sent
|
|
684
|
+
self._rcv_buffer[buffer_hash]['next_cts_border'] = min(self._rcv_buffer[buffer_hash]['next_cts_border'] + self._rcv_buffer[buffer_hash]['num_segments_max_rec'],
|
|
685
|
+
self._rcv_buffer[buffer_hash]['num_segments'])
|
|
686
|
+
|
|
687
|
+
self._rcv_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.T2
|
|
688
|
+
self.__job_thread_wakeup()
|
|
689
|
+
return
|
|
690
|
+
|
|
691
|
+
self._rcv_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.T1
|
|
692
|
+
|
|
693
|
+
def _process_multi_pg(self, mid : MessageId, dest_address, data, timestamp):
|
|
694
|
+
# currently "SAE J1939 with no assurance data" trailer format supported only
|
|
695
|
+
src_address = mid.source_address
|
|
696
|
+
|
|
697
|
+
while True:
|
|
698
|
+
if len(data) <= 4:
|
|
699
|
+
break
|
|
700
|
+
tos = (data[0] >> 5) & 0x7
|
|
701
|
+
# padding service
|
|
702
|
+
if tos == 0:
|
|
703
|
+
break
|
|
704
|
+
|
|
705
|
+
trailer_format = (data[0] >> 2) & 0x7
|
|
706
|
+
cpgn = ((data[0] & 0x3) << 16) | (data[1] << 8) | data[2]
|
|
707
|
+
payload_length = (data[3] & 0xFF)
|
|
708
|
+
if (tos == 2) and (trailer_format == 0):
|
|
709
|
+
# SAE J1939 with no assurance data
|
|
710
|
+
self.__notify_subscribers(mid.priority, cpgn, src_address, dest_address, timestamp, data[4:(4+payload_length)].copy())
|
|
711
|
+
else:
|
|
712
|
+
# TODO
|
|
713
|
+
print('other tos/tf formats currently not supported')
|
|
714
|
+
|
|
715
|
+
# trim data
|
|
716
|
+
data = data[(4+payload_length):]
|
|
717
|
+
|
|
718
|
+
def __send_tp_abort(self, src_address, dest_address, session_num, reason, pgn_value):
|
|
719
|
+
self.__send_tp_cm(src_address, dest_address, self.TpControlType.ABORT, session_num, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, reason, pgn_value)
|
|
720
|
+
|
|
721
|
+
def __send_tp_rts(self, priority, src_address, dest_address, session_num, pgn_value, message_size, num_segments, max_cmdt_packets, adt=Adt.NO_ADT):
|
|
722
|
+
self.__send_tp_cm(src_address, dest_address, self.TpControlType.RTS, session_num, message_size, num_segments, max_cmdt_packets, adt, pgn_value, priority)
|
|
723
|
+
|
|
724
|
+
def __send_tp_cts(self, src_address, dest_address, session_num, num_segments_that_can_be_sent, next_packet, pgn_value):
|
|
725
|
+
request_code = 0
|
|
726
|
+
self.__send_tp_cm(src_address, dest_address, self.TpControlType.CTS, session_num, 0xFFFFFF, next_packet, num_segments_that_can_be_sent, request_code, pgn_value)
|
|
727
|
+
|
|
728
|
+
def __send_tp_eom_status(self, src_address, dest_address, session_num, message_size, num_segments, pgn_value, size_of_assurance_data=0, adt=Adt.NO_ADT):
|
|
729
|
+
self.__send_tp_cm(src_address, dest_address, self.TpControlType.EOM_STATUS, session_num, message_size, num_segments, size_of_assurance_data, adt, pgn_value)
|
|
730
|
+
|
|
731
|
+
def __send_tp_eom_ack(self, src_address, dest_address, session_num, message_size, num_segments, pgn_value):
|
|
732
|
+
self.__send_tp_cm(src_address, dest_address, self.TpControlType.EOM_ACK, session_num, message_size, num_segments, 0xFF, 0xFF, pgn_value)
|
|
733
|
+
|
|
734
|
+
def __send_tp_bam(self, priority, src_address, session_num, pgn_value, message_size, num_segments):
|
|
735
|
+
self.__send_tp_cm(src_address, ParameterGroupNumber.Address.GLOBAL, self.TpControlType.BAM, session_num, message_size, num_segments, 0xFF , 0, pgn_value, priority)
|
|
736
|
+
|
|
737
|
+
def __send_tp_cm(self, src_address, dest_address,
|
|
738
|
+
TpControlType : TpControlType, session_num, message_size,
|
|
739
|
+
num_segments, # total number of segments or next segment number to be sent
|
|
740
|
+
byte_7, # maximum number of segments or num of segments that can be sent or assurance data Size
|
|
741
|
+
byte_8, # assurance data type or request code or teason code:
|
|
742
|
+
pgn,
|
|
743
|
+
priority=7):
|
|
744
|
+
|
|
745
|
+
pgn_tp_cm = ParameterGroupNumber(0, (ParameterGroupNumber.PGN.FD_TP_CM>>8) & 0xFF, dest_address)
|
|
746
|
+
mid = MessageId(priority=priority, parameter_group_number=pgn_tp_cm.value, source_address=src_address)
|
|
747
|
+
|
|
748
|
+
data = [0] * 12
|
|
749
|
+
data[0] = ( (TpControlType & 0xF) | ((session_num & 0xF) << 4))
|
|
750
|
+
data[1] = ( message_size & 0xFF )
|
|
751
|
+
data[2] = ( (message_size >> 8) & 0xFF )
|
|
752
|
+
data[3] = ( (message_size >> 16) & 0xFF )
|
|
753
|
+
data[4] = ( num_segments & 0xFF )
|
|
754
|
+
data[5] = ( (num_segments >> 8) & 0xFF )
|
|
755
|
+
data[6] = ( (num_segments >> 16) & 0xFF )
|
|
756
|
+
data[7] = ( byte_7 & 0xFF )
|
|
757
|
+
data[8] = ( byte_8 & 0xFF )
|
|
758
|
+
data[9] = ( pgn & 0xFF )
|
|
759
|
+
data[10] = ( (pgn >> 8) & 0xFF )
|
|
760
|
+
data[11] = ( (pgn >> 16) & 0xFF )
|
|
761
|
+
# 13 up to 64 Assurance Data of full message calculated using AD Type. Total length = Size in byte 8.
|
|
762
|
+
self.__send_message(mid.can_id, True, data, fd_format=True)
|
|
763
|
+
|
|
764
|
+
def __send_tp_dt(self, src_address, dest_address, session_num, segment_num, data, Dtfi=0):
|
|
765
|
+
pgn = ParameterGroupNumber(0, (ParameterGroupNumber.PGN.FD_TP_DT>>8) & 0xFF, dest_address)
|
|
766
|
+
mid = MessageId(priority=7, parameter_group_number=pgn.value, source_address=src_address)
|
|
767
|
+
|
|
768
|
+
data.insert(0, (Dtfi & 0xF) | ((session_num & 0xF) << 4))
|
|
769
|
+
data.insert(1, segment_num & 0xFF)
|
|
770
|
+
data.insert(2, (segment_num >> 8) & 0xFF)
|
|
771
|
+
data.insert(3, (segment_num >> 16) & 0xFF)
|
|
772
|
+
|
|
773
|
+
next_valid_fd_length = 0
|
|
774
|
+
if len(data)>=(self.DataLength.TP+4):
|
|
775
|
+
data = data[:(self.DataLength.TP+4)]
|
|
776
|
+
else:
|
|
777
|
+
# padding
|
|
778
|
+
next_valid_fd_length = self._LUT_FD_DLC[len(data)]
|
|
779
|
+
if next_valid_fd_length < 0: next_valid_fd_length = 0
|
|
780
|
+
|
|
781
|
+
while len(data)<next_valid_fd_length:
|
|
782
|
+
data.append(255)
|
|
783
|
+
|
|
784
|
+
self.__send_message(mid.can_id, True, data, fd_format=True)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def notify(self, can_id, data, timestamp):
|
|
788
|
+
"""Feed incoming CAN message into this ecu.
|
|
789
|
+
|
|
790
|
+
If a custom interface is used, this function must be called for each
|
|
791
|
+
29-bit standard message read from the CAN bus.
|
|
792
|
+
|
|
793
|
+
:param int can_id:
|
|
794
|
+
CAN-ID of the message (always 29-bit)
|
|
795
|
+
:param bytearray data:
|
|
796
|
+
Data part of the message (0 - 8 bytes)
|
|
797
|
+
:param float timestamp:
|
|
798
|
+
The timestamp field in a CAN message is a floating point number
|
|
799
|
+
representing when the message was received since the epoch in
|
|
800
|
+
seconds.
|
|
801
|
+
Where possible this will be timestamped in hardware.
|
|
802
|
+
"""
|
|
803
|
+
mid = MessageId(can_id=can_id)
|
|
804
|
+
pgn = ParameterGroupNumber()
|
|
805
|
+
pgn.from_message_id(mid)
|
|
806
|
+
|
|
807
|
+
# peer to peer
|
|
808
|
+
# pdu_specific is destination Address
|
|
809
|
+
pgn_value = pgn.value & 0x1FF00
|
|
810
|
+
dest_address = pgn.pdu_specific # may be Address.GLOBAL
|
|
811
|
+
|
|
812
|
+
# iterate all CAs to check if we have to handle this destination address
|
|
813
|
+
if dest_address != ParameterGroupNumber.Address.GLOBAL:
|
|
814
|
+
if not self.__ecu_is_message_acceptable(dest_address): # simple peer-to-peer reception without adding a controller-application
|
|
815
|
+
reject = True
|
|
816
|
+
for ca in self._cas:
|
|
817
|
+
if ca.message_acceptable(dest_address):
|
|
818
|
+
reject = False
|
|
819
|
+
break
|
|
820
|
+
if reject == True:
|
|
821
|
+
return
|
|
822
|
+
|
|
823
|
+
if pgn_value == ParameterGroupNumber.PGN.FEFF_MULTI_PG:
|
|
824
|
+
self._process_multi_pg(mid, dest_address, data, timestamp)
|
|
825
|
+
elif pgn_value == ParameterGroupNumber.PGN.ADDRESSCLAIM:
|
|
826
|
+
for ca in self._cas:
|
|
827
|
+
ca._process_addressclaim(mid, data, timestamp)
|
|
828
|
+
elif pgn_value == ParameterGroupNumber.PGN.REQUEST:
|
|
829
|
+
for ca in self._cas:
|
|
830
|
+
if ca.message_acceptable(dest_address):
|
|
831
|
+
ca._process_request(mid, dest_address, data, timestamp)
|
|
832
|
+
elif pgn_value == ParameterGroupNumber.PGN.FD_TP_CM:
|
|
833
|
+
self._process_tp_cm(mid, dest_address, data, timestamp)
|
|
834
|
+
elif pgn_value == ParameterGroupNumber.PGN.FD_TP_DT:
|
|
835
|
+
self._process_tp_dt(mid, dest_address, data, timestamp)
|
|
836
|
+
elif pgn_value == ParameterGroupNumber.PGN.TP_CM:
|
|
837
|
+
logger.info('j1939-21 transport protocol cm not allowed in j1939-22 network')
|
|
838
|
+
elif pgn_value == ParameterGroupNumber.PGN.DATATRANSFER:
|
|
839
|
+
logger.info('j1939-21 transport protocol dt not allowed in j1939-22 network')
|
|
840
|
+
elif pgn.is_pdu2_format:
|
|
841
|
+
# direct broadcast
|
|
842
|
+
self.__notify_subscribers(mid.priority, pgn.value, mid.source_address, ParameterGroupNumber.Address.GLOBAL, timestamp, data)
|
|
843
|
+
else:
|
|
844
|
+
self.__notify_subscribers(mid.priority, pgn_value, mid.source_address, dest_address, timestamp, data)
|
|
845
|
+
|