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_21.py
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
from .parameter_group_number import ParameterGroupNumber
|
|
2
|
+
from .message_id import MessageId
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class J1939_21:
|
|
10
|
+
class ConnectionMode:
|
|
11
|
+
RTS = 16
|
|
12
|
+
CTS = 17
|
|
13
|
+
EOM_ACK = 19
|
|
14
|
+
BAM = 32
|
|
15
|
+
ABORT = 255
|
|
16
|
+
|
|
17
|
+
class ConnectionAbortReason:
|
|
18
|
+
BUSY = 1 # Already in one or more connection managed sessions and cannot support another
|
|
19
|
+
RESOURCES = 2 # System resources were needed for another task so this connection managed session was terminated
|
|
20
|
+
TIMEOUT = 3 # A timeout occured
|
|
21
|
+
# 4..250 Reserved by SAE
|
|
22
|
+
CTS_WHILE_DT = 4 # according AUTOSAR: CTS messages received when data transfer is in progress
|
|
23
|
+
# 251..255 Per J1939/71 definitions - but there are none?
|
|
24
|
+
|
|
25
|
+
class Timeout:
|
|
26
|
+
"""Timeouts according SAE J1939/21"""
|
|
27
|
+
Tr = 0.200 # Response Time
|
|
28
|
+
Th = 0.500 # Holding Time
|
|
29
|
+
T1 = 0.750
|
|
30
|
+
T2 = 1.250
|
|
31
|
+
T3 = 1.250
|
|
32
|
+
T4 = 1.050
|
|
33
|
+
# timeout for multi packet broadcast messages 50..200ms
|
|
34
|
+
Tb = 0.050
|
|
35
|
+
|
|
36
|
+
class SendBufferState:
|
|
37
|
+
WAITING_CTS = 0 # waiting for CTS
|
|
38
|
+
SENDING_IN_CTS = 1 # sending packages (temporary state)
|
|
39
|
+
SENDING_BM = 2 # sending broadcast packages
|
|
40
|
+
TRANSMISSION_FINISHED = 3 # finished, remove buffer
|
|
41
|
+
|
|
42
|
+
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):
|
|
43
|
+
# Receive buffers
|
|
44
|
+
self._rcv_buffer = {}
|
|
45
|
+
# Send buffers
|
|
46
|
+
self._snd_buffer = {}
|
|
47
|
+
|
|
48
|
+
# List of ControllerApplication
|
|
49
|
+
self._cas = []
|
|
50
|
+
|
|
51
|
+
# set minimum time between two tp-rts/cts messages
|
|
52
|
+
self._minimum_tp_rts_cts_dt_interval = minimum_tp_rts_cts_dt_interval
|
|
53
|
+
|
|
54
|
+
# set minimum time between two tp-bam messages
|
|
55
|
+
if minimum_tp_bam_dt_interval == None:
|
|
56
|
+
self._minimum_tp_bam_dt_interval = self.Timeout.Tb
|
|
57
|
+
else:
|
|
58
|
+
self._minimum_tp_bam_dt_interval = minimum_tp_bam_dt_interval
|
|
59
|
+
|
|
60
|
+
# number of packets that can be sent/received with CMDT (Connection Mode Data Transfer)
|
|
61
|
+
self._max_cmdt_packets = max_cmdt_packets
|
|
62
|
+
|
|
63
|
+
# Lock protecting _rcv_buffer and _snd_buffer — accessed from both the
|
|
64
|
+
# Notifier thread (notify/process_tp_*) and the protocol job thread (async_job_thread).
|
|
65
|
+
self._buffer_lock = threading.Lock()
|
|
66
|
+
|
|
67
|
+
self.__job_thread_wakeup = job_thread_wakeup
|
|
68
|
+
self.__send_message = send_message
|
|
69
|
+
self.__notify_subscribers = notify_subscribers
|
|
70
|
+
self.__ecu_is_message_acceptable = ecu_is_message_acceptable
|
|
71
|
+
|
|
72
|
+
def add_ca(self, ca):
|
|
73
|
+
self._cas.append(ca)
|
|
74
|
+
|
|
75
|
+
def remove_ca(self, device_address):
|
|
76
|
+
for ca in self._cas:
|
|
77
|
+
if device_address == ca._device_address_preferred:
|
|
78
|
+
self._cas.remove(ca)
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def _buffer_hash(self, src_address, dest_address):
|
|
83
|
+
"""Calcluates a hash value for the given address pair
|
|
84
|
+
|
|
85
|
+
:param src_address:
|
|
86
|
+
The Source-Address the connection should bound to.
|
|
87
|
+
:param dest_address:
|
|
88
|
+
The Destination-Address the connection should bound to.
|
|
89
|
+
|
|
90
|
+
:return:
|
|
91
|
+
The calculated hash value.
|
|
92
|
+
|
|
93
|
+
:rtype: int
|
|
94
|
+
"""
|
|
95
|
+
return ((src_address & 0xFF) << 8) | (dest_address & 0xFF)
|
|
96
|
+
|
|
97
|
+
def send_pgn(self, data_page, pdu_format, pdu_specific, priority, src_address, data, time_limit, frame_format):
|
|
98
|
+
pgn = ParameterGroupNumber(data_page, pdu_format, pdu_specific)
|
|
99
|
+
if len(data) <= 8:
|
|
100
|
+
# send normal message
|
|
101
|
+
mid = MessageId(priority=priority, parameter_group_number=pgn.value, source_address=src_address)
|
|
102
|
+
self.__send_message(mid.can_id, True, data)
|
|
103
|
+
else:
|
|
104
|
+
# if the PF is between 0 and 239, the message is destination dependent when pdu_specific != 255
|
|
105
|
+
# if the PF is between 240 and 255, the message can only be broadcast
|
|
106
|
+
if (pdu_specific == ParameterGroupNumber.Address.GLOBAL) or ParameterGroupNumber(0, pdu_format, pdu_specific).is_pdu2_format:
|
|
107
|
+
dest_address = ParameterGroupNumber.Address.GLOBAL
|
|
108
|
+
else:
|
|
109
|
+
dest_address = pdu_specific
|
|
110
|
+
|
|
111
|
+
# init sequence
|
|
112
|
+
# known limitation: only one BAM can be sent in parallel to a destination node
|
|
113
|
+
buffer_hash = self._buffer_hash(src_address, dest_address)
|
|
114
|
+
message_size = len(data)
|
|
115
|
+
num_packets = int(message_size / 7) if (message_size % 7 == 0) else int(message_size / 7) + 1
|
|
116
|
+
|
|
117
|
+
# if the PF is between 240 and 255, the message can only be broadcast
|
|
118
|
+
if dest_address == ParameterGroupNumber.Address.GLOBAL:
|
|
119
|
+
# send BAM before acquiring the lock — CAN I/O must not be
|
|
120
|
+
# held under _buffer_lock to avoid priority inversion with the
|
|
121
|
+
# protocol thread.
|
|
122
|
+
with self._buffer_lock:
|
|
123
|
+
if buffer_hash in self._snd_buffer:
|
|
124
|
+
# There is already a sequence active for this pair
|
|
125
|
+
return False
|
|
126
|
+
self.__send_tp_bam(src_address, priority, pgn.value, message_size, num_packets)
|
|
127
|
+
|
|
128
|
+
# init new buffer for this connection
|
|
129
|
+
with self._buffer_lock:
|
|
130
|
+
self._snd_buffer[buffer_hash] = {
|
|
131
|
+
"pgn": pgn.value,
|
|
132
|
+
"priority": priority,
|
|
133
|
+
"message_size": message_size,
|
|
134
|
+
"num_packages": num_packets,
|
|
135
|
+
"data": data,
|
|
136
|
+
"state": self.SendBufferState.SENDING_BM,
|
|
137
|
+
"deadline": time.monotonic() + self._minimum_tp_bam_dt_interval,
|
|
138
|
+
'src_address' : src_address,
|
|
139
|
+
'dest_address' : ParameterGroupNumber.Address.GLOBAL,
|
|
140
|
+
'next_packet_to_send' : 0,
|
|
141
|
+
}
|
|
142
|
+
else:
|
|
143
|
+
# send RTS/CTS
|
|
144
|
+
pgn.pdu_specific = 0 # this is 0 for peer-to-peer transfer
|
|
145
|
+
with self._buffer_lock:
|
|
146
|
+
if buffer_hash in self._snd_buffer:
|
|
147
|
+
# There is already a sequence active for this pair
|
|
148
|
+
return False
|
|
149
|
+
self.__send_tp_rts(src_address, pdu_specific, priority, pgn.value, message_size, num_packets, min(self._max_cmdt_packets, num_packets))
|
|
150
|
+
|
|
151
|
+
# init new buffer for this connection
|
|
152
|
+
with self._buffer_lock:
|
|
153
|
+
self._snd_buffer[buffer_hash] = {
|
|
154
|
+
"pgn": pgn.value,
|
|
155
|
+
"priority": priority,
|
|
156
|
+
"message_size": message_size,
|
|
157
|
+
"num_packages": num_packets,
|
|
158
|
+
"data": data,
|
|
159
|
+
"state": self.SendBufferState.WAITING_CTS,
|
|
160
|
+
"deadline": time.monotonic() + self.Timeout.T3,
|
|
161
|
+
'src_address' : src_address,
|
|
162
|
+
'dest_address' : pdu_specific,
|
|
163
|
+
'next_packet_to_send' : 0,
|
|
164
|
+
'next_wait_on_cts': 0,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
self.__job_thread_wakeup()
|
|
168
|
+
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def async_job_thread(self, now):
|
|
173
|
+
|
|
174
|
+
next_wakeup = now + 5.0 # wakeup in 5 seconds
|
|
175
|
+
|
|
176
|
+
with self._buffer_lock:
|
|
177
|
+
# check receive buffers for timeout
|
|
178
|
+
for bufid in list(self._rcv_buffer):
|
|
179
|
+
buf = self._rcv_buffer[bufid]
|
|
180
|
+
if buf['deadline'] != 0:
|
|
181
|
+
if buf['deadline'] > now:
|
|
182
|
+
if next_wakeup > buf['deadline']:
|
|
183
|
+
next_wakeup = buf['deadline']
|
|
184
|
+
else:
|
|
185
|
+
# deadline reached
|
|
186
|
+
logger.info("Deadline reached for rcv_buffer src 0x%02X dst 0x%02X", buf['src_address'], buf['dest_address'] )
|
|
187
|
+
if buf['dest_address'] != ParameterGroupNumber.Address.GLOBAL:
|
|
188
|
+
# TODO: should we handle retries?
|
|
189
|
+
self.__send_tp_abort(buf['dest_address'], buf['src_address'], self.ConnectionAbortReason.TIMEOUT, buf['pgn'])
|
|
190
|
+
# TODO: should we notify our CAs about the cancelled transfer?
|
|
191
|
+
del self._rcv_buffer[bufid]
|
|
192
|
+
|
|
193
|
+
# check send buffers
|
|
194
|
+
for bufid in list(self._snd_buffer):
|
|
195
|
+
buf = self._snd_buffer[bufid]
|
|
196
|
+
if buf['deadline'] != 0:
|
|
197
|
+
if buf['deadline'] > now:
|
|
198
|
+
if next_wakeup > buf['deadline']:
|
|
199
|
+
next_wakeup = buf['deadline']
|
|
200
|
+
else:
|
|
201
|
+
# deadline reached
|
|
202
|
+
if buf['state'] == self.SendBufferState.WAITING_CTS:
|
|
203
|
+
logger.info("Deadline WAITING_CTS reached for snd_buffer src 0x%02X dst 0x%02X", buf['src_address'], buf['dest_address'] )
|
|
204
|
+
self.__send_tp_abort(buf['src_address'], buf['dest_address'], self.ConnectionAbortReason.TIMEOUT, buf['pgn'])
|
|
205
|
+
# TODO: should we notify our CAs about the cancelled transfer?
|
|
206
|
+
del self._snd_buffer[bufid]
|
|
207
|
+
elif buf['state'] == self.SendBufferState.SENDING_IN_CTS:
|
|
208
|
+
while buf['next_packet_to_send'] < buf['num_packages']:
|
|
209
|
+
package = buf['next_packet_to_send']
|
|
210
|
+
offset = package * 7
|
|
211
|
+
data = buf['data'][offset:]
|
|
212
|
+
if len(data)>7:
|
|
213
|
+
data = data[:7]
|
|
214
|
+
else:
|
|
215
|
+
while len(data)<7:
|
|
216
|
+
data.append(255)
|
|
217
|
+
data.insert(0, package+1)
|
|
218
|
+
|
|
219
|
+
# modify the snd_buffer state in anticipation
|
|
220
|
+
# of the message we are about to transmit
|
|
221
|
+
|
|
222
|
+
buf['next_packet_to_send'] += 1
|
|
223
|
+
|
|
224
|
+
should_break = False
|
|
225
|
+
if package == buf['next_wait_on_cts']:
|
|
226
|
+
# wait on next cts
|
|
227
|
+
buf['state'] = self.SendBufferState.WAITING_CTS
|
|
228
|
+
buf['deadline'] = time.monotonic() + self.Timeout.T3
|
|
229
|
+
should_break = True
|
|
230
|
+
elif self._minimum_tp_rts_cts_dt_interval != None:
|
|
231
|
+
buf['deadline'] = time.monotonic() + self._minimum_tp_rts_cts_dt_interval
|
|
232
|
+
should_break = True
|
|
233
|
+
|
|
234
|
+
# state is ready for recv - Now send the message
|
|
235
|
+
self.__send_tp_dt(buf['src_address'], buf['dest_address'], data)
|
|
236
|
+
if should_break:
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
# recalc next wakeup
|
|
240
|
+
if next_wakeup > buf['deadline']:
|
|
241
|
+
next_wakeup = buf['deadline']
|
|
242
|
+
|
|
243
|
+
elif buf['state'] == self.SendBufferState.SENDING_BM:
|
|
244
|
+
# send next broadcast message...
|
|
245
|
+
offset = buf['next_packet_to_send'] * 7
|
|
246
|
+
data = buf['data'][offset:]
|
|
247
|
+
if len(data)>7:
|
|
248
|
+
data = data[:7]
|
|
249
|
+
else:
|
|
250
|
+
while len(data)<7:
|
|
251
|
+
data.append(255)
|
|
252
|
+
data.insert(0, buf['next_packet_to_send']+1)
|
|
253
|
+
|
|
254
|
+
# modify the snd_buffer state in anticipation
|
|
255
|
+
# of the message we are about to transmit
|
|
256
|
+
|
|
257
|
+
buf['next_packet_to_send'] += 1
|
|
258
|
+
|
|
259
|
+
if buf['next_packet_to_send'] < buf['num_packages']:
|
|
260
|
+
buf['deadline'] = time.monotonic() + self._minimum_tp_bam_dt_interval
|
|
261
|
+
# recalc next wakeup
|
|
262
|
+
if next_wakeup > buf['deadline']:
|
|
263
|
+
next_wakeup = buf['deadline']
|
|
264
|
+
else:
|
|
265
|
+
# done
|
|
266
|
+
del self._snd_buffer[bufid]
|
|
267
|
+
|
|
268
|
+
# state is updated and ready for recv - now send data
|
|
269
|
+
self.__send_tp_dt(buf['src_address'], buf['dest_address'], data)
|
|
270
|
+
elif buf['state'] == self.SendBufferState.TRANSMISSION_FINISHED:
|
|
271
|
+
del self._snd_buffer[bufid]
|
|
272
|
+
else:
|
|
273
|
+
logger.critical("unknown SendBufferState %d", buf['state'])
|
|
274
|
+
del self._snd_buffer[bufid]
|
|
275
|
+
|
|
276
|
+
return next_wakeup
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _process_tp_cm(self, mid, dest_address, data, timestamp):
|
|
280
|
+
"""Processes a Transport Protocol Connection Management (TP.CM) message
|
|
281
|
+
|
|
282
|
+
:param j1939.MessageId mid:
|
|
283
|
+
A MessageId object holding the information extracted from the can_id.
|
|
284
|
+
:param int dest_address:
|
|
285
|
+
The destination address of the message
|
|
286
|
+
:param bytearray data:
|
|
287
|
+
The data contained in the can-message.
|
|
288
|
+
:param float timestamp:
|
|
289
|
+
The timestamp the message was received (mostly) in fractions of Epoch-Seconds.
|
|
290
|
+
"""
|
|
291
|
+
control_byte = data[0]
|
|
292
|
+
pgn = data[5] | (data[6] << 8) | (data[7] << 16)
|
|
293
|
+
|
|
294
|
+
src_address = mid.source_address
|
|
295
|
+
|
|
296
|
+
with self._buffer_lock:
|
|
297
|
+
if control_byte == self.ConnectionMode.RTS:
|
|
298
|
+
message_size = data[1] | (data[2] << 8)
|
|
299
|
+
num_packages = data[3]
|
|
300
|
+
max_num_packages = data[4] # Maximum number of segments that can be sent in response to one CTS.
|
|
301
|
+
buffer_hash = self._buffer_hash(src_address, dest_address)
|
|
302
|
+
if buffer_hash in self._rcv_buffer:
|
|
303
|
+
# according SAE J1939-21 we have to send an ABORT if an active
|
|
304
|
+
# transmission is already established
|
|
305
|
+
self.__send_tp_abort(dest_address, src_address, self.ConnectionAbortReason.BUSY, pgn)
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
# limit max number segments
|
|
309
|
+
max_num_packages = min(max_num_packages, num_packages)
|
|
310
|
+
|
|
311
|
+
# open new buffer for this connection
|
|
312
|
+
self._rcv_buffer[buffer_hash] = {
|
|
313
|
+
'pgn': pgn,
|
|
314
|
+
'message_size': message_size,
|
|
315
|
+
'num_packages': num_packages,
|
|
316
|
+
'next_packet': min(self._max_cmdt_packets, max_num_packages),
|
|
317
|
+
'max_cmdt_packages': self._max_cmdt_packets,
|
|
318
|
+
'num_packages_max_rec': min(self._max_cmdt_packets, max_num_packages),
|
|
319
|
+
'data': [],
|
|
320
|
+
'deadline': time.monotonic() + self.Timeout.T2,
|
|
321
|
+
'src_address' : src_address,
|
|
322
|
+
'dest_address' : dest_address,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
self.__send_tp_cts(dest_address, src_address, self._rcv_buffer[buffer_hash]['num_packages_max_rec'], 1, pgn)
|
|
326
|
+
self.__job_thread_wakeup()
|
|
327
|
+
elif control_byte == self.ConnectionMode.CTS:
|
|
328
|
+
num_packages = data[1]
|
|
329
|
+
next_package_number = data[2] - 1
|
|
330
|
+
buffer_hash = self._buffer_hash(dest_address, src_address)
|
|
331
|
+
if buffer_hash not in self._snd_buffer:
|
|
332
|
+
self.__send_tp_abort(dest_address, src_address, self.ConnectionAbortReason.RESOURCES, pgn)
|
|
333
|
+
return
|
|
334
|
+
if num_packages == 0:
|
|
335
|
+
# SAE J1939/21
|
|
336
|
+
# receiver requests a pause
|
|
337
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.Th
|
|
338
|
+
self.__job_thread_wakeup()
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
num_packages_all = self._snd_buffer[buffer_hash]["num_packages"]
|
|
342
|
+
if num_packages > num_packages_all:
|
|
343
|
+
logger.debug("CTS: Allowed more packets %d than complete transmission %d", num_packages, num_packages_all)
|
|
344
|
+
num_packages = num_packages_all
|
|
345
|
+
if next_package_number + num_packages > num_packages_all:
|
|
346
|
+
logger.debug("CTS: Allowed more packets %d than needed to complete transmission %d", num_packages, num_packages_all - next_package_number)
|
|
347
|
+
num_packages = num_packages_all - next_package_number
|
|
348
|
+
|
|
349
|
+
self._snd_buffer[buffer_hash]['next_wait_on_cts'] = self._snd_buffer[buffer_hash]['next_packet_to_send'] + num_packages - 1
|
|
350
|
+
|
|
351
|
+
self._snd_buffer[buffer_hash]['state'] = self.SendBufferState.SENDING_IN_CTS
|
|
352
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic()
|
|
353
|
+
self.__job_thread_wakeup()
|
|
354
|
+
|
|
355
|
+
elif control_byte == self.ConnectionMode.EOM_ACK:
|
|
356
|
+
buffer_hash = self._buffer_hash(dest_address, src_address)
|
|
357
|
+
if buffer_hash not in self._snd_buffer:
|
|
358
|
+
self.__send_tp_abort(dest_address, src_address, self.ConnectionAbortReason.RESOURCES, pgn)
|
|
359
|
+
return
|
|
360
|
+
# TODO: should we inform the application about the successful transmission?
|
|
361
|
+
# Notify subscribers here to be used for the memory access server to know when to send operation complete
|
|
362
|
+
self.__notify_subscribers(mid.priority,pgn,mid.source_address,dest_address,timestamp,data)
|
|
363
|
+
|
|
364
|
+
self._snd_buffer[buffer_hash]['state'] = self.SendBufferState.TRANSMISSION_FINISHED
|
|
365
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic()
|
|
366
|
+
self.__job_thread_wakeup()
|
|
367
|
+
elif control_byte == self.ConnectionMode.BAM:
|
|
368
|
+
message_size = data[1] | (data[2] << 8)
|
|
369
|
+
num_packages = data[3]
|
|
370
|
+
buffer_hash = self._buffer_hash(src_address, dest_address)
|
|
371
|
+
if buffer_hash in self._rcv_buffer:
|
|
372
|
+
# TODO: should we deliver the partly received message to our CAs?
|
|
373
|
+
del self._rcv_buffer[buffer_hash]
|
|
374
|
+
self.__job_thread_wakeup()
|
|
375
|
+
|
|
376
|
+
# init new buffer for this connection
|
|
377
|
+
self._rcv_buffer[buffer_hash] = {
|
|
378
|
+
"pgn": pgn,
|
|
379
|
+
"message_size": message_size,
|
|
380
|
+
"num_packages": num_packages,
|
|
381
|
+
"next_packet": 1,
|
|
382
|
+
"max_cmdt_packages": self._max_cmdt_packets,
|
|
383
|
+
"data": [],
|
|
384
|
+
"deadline": time.monotonic() + self.Timeout.T1,
|
|
385
|
+
'src_address' : src_address,
|
|
386
|
+
'dest_address' : dest_address,
|
|
387
|
+
}
|
|
388
|
+
self.__job_thread_wakeup()
|
|
389
|
+
elif control_byte == self.ConnectionMode.ABORT:
|
|
390
|
+
# if abort received before transmission established -> cancel transmission
|
|
391
|
+
buffer_hash = self._buffer_hash(dest_address, src_address)
|
|
392
|
+
if buffer_hash in self._snd_buffer and self._snd_buffer[buffer_hash]['state'] == self.SendBufferState.WAITING_CTS:
|
|
393
|
+
self._snd_buffer[buffer_hash]['state'] = self.SendBufferState.TRANSMISSION_FINISHED
|
|
394
|
+
self._snd_buffer[buffer_hash]['deadline'] = time.monotonic()
|
|
395
|
+
# TODO: any more abort responses?
|
|
396
|
+
pass
|
|
397
|
+
else:
|
|
398
|
+
raise RuntimeError("Received TP.CM with unknown control_byte %d", control_byte)
|
|
399
|
+
|
|
400
|
+
def _process_tp_dt(self, mid, dest_address, data, timestamp):
|
|
401
|
+
sequence_number = data[0]
|
|
402
|
+
|
|
403
|
+
src_address = mid.source_address
|
|
404
|
+
|
|
405
|
+
buffer_hash = self._buffer_hash(src_address, dest_address)
|
|
406
|
+
|
|
407
|
+
with self._buffer_lock:
|
|
408
|
+
if buffer_hash not in self._rcv_buffer:
|
|
409
|
+
# TODO: LOG/TRACE/EXCEPTION?
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
# get data
|
|
413
|
+
self._rcv_buffer[buffer_hash]['data'].extend(data[1:])
|
|
414
|
+
|
|
415
|
+
# message is complete with sending an acknowledge
|
|
416
|
+
if len(self._rcv_buffer[buffer_hash]['data']) >= self._rcv_buffer[buffer_hash]['message_size']:
|
|
417
|
+
logger.info("finished RCV of PGN {} with size {}".format(self._rcv_buffer[buffer_hash]['pgn'], self._rcv_buffer[buffer_hash]['message_size']))
|
|
418
|
+
# shorten data to message_size
|
|
419
|
+
self._rcv_buffer[buffer_hash]['data'] = self._rcv_buffer[buffer_hash]['data'][:self._rcv_buffer[buffer_hash]['message_size']]
|
|
420
|
+
# finished reassembly
|
|
421
|
+
if dest_address != ParameterGroupNumber.Address.GLOBAL:
|
|
422
|
+
self.__send_tp_eom_ack(dest_address, src_address, self._rcv_buffer[buffer_hash]['message_size'], self._rcv_buffer[buffer_hash]['num_packages'], self._rcv_buffer[buffer_hash]['pgn'])
|
|
423
|
+
self.__notify_subscribers(mid.priority, self._rcv_buffer[buffer_hash]['pgn'], src_address, dest_address, timestamp, self._rcv_buffer[buffer_hash]['data'])
|
|
424
|
+
del self._rcv_buffer[buffer_hash]
|
|
425
|
+
self.__job_thread_wakeup()
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
# clear to send
|
|
429
|
+
if (dest_address != ParameterGroupNumber.Address.GLOBAL) and (sequence_number >= self._rcv_buffer[buffer_hash]['next_packet']):
|
|
430
|
+
|
|
431
|
+
# send cts
|
|
432
|
+
number_of_packets_that_can_be_sent = min( self._rcv_buffer[buffer_hash]['num_packages_max_rec'], self._rcv_buffer[buffer_hash]['num_packages'] - self._rcv_buffer[buffer_hash]['next_packet'] )
|
|
433
|
+
next_packet_to_be_sent = self._rcv_buffer[buffer_hash]['next_packet'] + 1
|
|
434
|
+
self.__send_tp_cts(dest_address, src_address, number_of_packets_that_can_be_sent, next_packet_to_be_sent, self._rcv_buffer[buffer_hash]['pgn'])
|
|
435
|
+
|
|
436
|
+
# calculate next packet number at which a CTS is to be sent
|
|
437
|
+
self._rcv_buffer[buffer_hash]['next_packet'] = min(self._rcv_buffer[buffer_hash]['next_packet'] + self._rcv_buffer[buffer_hash]['num_packages_max_rec'],
|
|
438
|
+
self._rcv_buffer[buffer_hash]['num_packages'])
|
|
439
|
+
|
|
440
|
+
self._rcv_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.T2
|
|
441
|
+
self.__job_thread_wakeup()
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
self._rcv_buffer[buffer_hash]['deadline'] = time.monotonic() + self.Timeout.T1
|
|
445
|
+
self.__job_thread_wakeup()
|
|
446
|
+
|
|
447
|
+
def __send_tp_dt(self, src_address, dest_address, data):
|
|
448
|
+
pgn = ParameterGroupNumber(0, 235, dest_address)
|
|
449
|
+
mid = MessageId(priority=7, parameter_group_number=pgn.value, source_address=src_address)
|
|
450
|
+
self.__send_message(mid.can_id, True, data)
|
|
451
|
+
|
|
452
|
+
def __send_tp_abort(self, src_address, dest_address, reason, pgn_value):
|
|
453
|
+
pgn = ParameterGroupNumber(0, 236, dest_address)
|
|
454
|
+
mid = MessageId(priority=7, parameter_group_number=pgn.value, source_address=src_address)
|
|
455
|
+
data = [self.ConnectionMode.ABORT, reason, 0xFF, 0xFF, 0xFF, pgn_value & 0xFF, (pgn_value >> 8) & 0xFF, (pgn_value >> 16) & 0xFF]
|
|
456
|
+
self.__send_message(mid.can_id, True, data)
|
|
457
|
+
|
|
458
|
+
def __send_tp_cts(self, src_address, dest_address, num_packets, next_packet, pgn_value):
|
|
459
|
+
pgn = ParameterGroupNumber(0, 236, dest_address)
|
|
460
|
+
mid = MessageId(priority=7, parameter_group_number=pgn.value, source_address=src_address)
|
|
461
|
+
data = [self.ConnectionMode.CTS, num_packets, next_packet, 0xFF, 0xFF, pgn_value & 0xFF, (pgn_value >> 8) & 0xFF, (pgn_value >> 16) & 0xFF]
|
|
462
|
+
self.__send_message(mid.can_id, True, data)
|
|
463
|
+
|
|
464
|
+
def __send_tp_eom_ack(self, src_address, dest_address, message_size, num_packets, pgn_value):
|
|
465
|
+
pgn = ParameterGroupNumber(0, 236, dest_address)
|
|
466
|
+
mid = MessageId(priority=7, parameter_group_number=pgn.value, source_address=src_address)
|
|
467
|
+
data = [self.ConnectionMode.EOM_ACK, message_size & 0xFF, (message_size >> 8) & 0xFF, num_packets, 0xFF, pgn_value & 0xFF, (pgn_value >> 8) & 0xFF, (pgn_value >> 16) & 0xFF]
|
|
468
|
+
self.__send_message(mid.can_id, True, data)
|
|
469
|
+
|
|
470
|
+
def __send_tp_rts(self, src_address, dest_address, priority, pgn_value, message_size, num_packets, max_cmdt_packets):
|
|
471
|
+
pgn = ParameterGroupNumber(0, 236, dest_address)
|
|
472
|
+
mid = MessageId(priority=priority, parameter_group_number=pgn.value, source_address=src_address)
|
|
473
|
+
data = [self.ConnectionMode.RTS, message_size & 0xFF, (message_size >> 8) & 0xFF, num_packets, max_cmdt_packets, pgn_value & 0xFF, (pgn_value >> 8) & 0xFF, (pgn_value >> 16) & 0xFF]
|
|
474
|
+
self.__send_message(mid.can_id, True, data)
|
|
475
|
+
|
|
476
|
+
def __send_acknowledgement(self, control_byte, group_function_value, address_acknowledged, pgn):
|
|
477
|
+
data = [control_byte, group_function_value, 0xFF, 0xFF, address_acknowledged, (pgn & 0xFF), ((pgn >> 8) & 0xFF), ((pgn >> 16) & 0xFF)]
|
|
478
|
+
mid = MessageId(priority=6, parameter_group_number=0x00E800, source_address=255)
|
|
479
|
+
self.__send_message(mid.can_id, True, data)
|
|
480
|
+
|
|
481
|
+
def __send_tp_bam(self, src_address, priority, pgn_value, message_size, num_packets):
|
|
482
|
+
pgn = ParameterGroupNumber(0, 236, ParameterGroupNumber.Address.GLOBAL)
|
|
483
|
+
mid = MessageId(priority=priority, parameter_group_number=pgn.value, source_address=src_address)
|
|
484
|
+
data = [self.ConnectionMode.BAM, message_size & 0xFF, (message_size >> 8) & 0xFF, num_packets, 0xFF, pgn_value & 0xFF, (pgn_value >> 8) & 0xFF, (pgn_value >> 16) & 0xFF]
|
|
485
|
+
self.__send_message(mid.can_id, True, data)
|
|
486
|
+
|
|
487
|
+
def notify(self, can_id, data, timestamp):
|
|
488
|
+
"""Feed incoming CAN message into this ecu.
|
|
489
|
+
|
|
490
|
+
If a custom interface is used, this function must be called for each
|
|
491
|
+
29-bit standard message read from the CAN bus.
|
|
492
|
+
|
|
493
|
+
:param int can_id:
|
|
494
|
+
CAN-ID of the message (always 29-bit)
|
|
495
|
+
:param bytearray data:
|
|
496
|
+
Data part of the message (0 - 8 bytes)
|
|
497
|
+
:param float timestamp:
|
|
498
|
+
The timestamp field in a CAN message is a floating point number
|
|
499
|
+
representing when the message was received since the epoch in
|
|
500
|
+
seconds.
|
|
501
|
+
Where possible this will be timestamped in hardware.
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
mid = MessageId(can_id=can_id)
|
|
505
|
+
pgn = ParameterGroupNumber()
|
|
506
|
+
pgn.from_message_id(mid)
|
|
507
|
+
|
|
508
|
+
if pgn.is_pdu2_format:
|
|
509
|
+
# direct broadcast
|
|
510
|
+
self.__notify_subscribers(mid.priority, pgn.value, mid.source_address, ParameterGroupNumber.Address.GLOBAL, timestamp, data)
|
|
511
|
+
return
|
|
512
|
+
|
|
513
|
+
# peer to peer
|
|
514
|
+
# pdu_specific is destination Address
|
|
515
|
+
pgn_value = pgn.value & 0x1FF00
|
|
516
|
+
dest_address = pgn.pdu_specific # may be Address.GLOBAL
|
|
517
|
+
|
|
518
|
+
# iterate all CAs to check if we have to handle this destination address
|
|
519
|
+
if dest_address != ParameterGroupNumber.Address.GLOBAL:
|
|
520
|
+
if not self.__ecu_is_message_acceptable(dest_address): # simple peer-to-peer reception without adding a controller-application
|
|
521
|
+
reject = True
|
|
522
|
+
for ca in self._cas:
|
|
523
|
+
if ca.message_acceptable(dest_address):
|
|
524
|
+
reject = False
|
|
525
|
+
break
|
|
526
|
+
if reject == True:
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
if pgn_value == ParameterGroupNumber.PGN.ADDRESSCLAIM:
|
|
530
|
+
for ca in self._cas:
|
|
531
|
+
ca._process_addressclaim(mid, data, timestamp)
|
|
532
|
+
elif pgn_value == ParameterGroupNumber.PGN.REQUEST:
|
|
533
|
+
for ca in self._cas:
|
|
534
|
+
if ca.message_acceptable(dest_address):
|
|
535
|
+
ca._process_request(mid, dest_address, data, timestamp)
|
|
536
|
+
elif pgn_value == ParameterGroupNumber.PGN.TP_CM:
|
|
537
|
+
self._process_tp_cm(mid, dest_address, data, timestamp)
|
|
538
|
+
elif pgn_value == ParameterGroupNumber.PGN.DATATRANSFER:
|
|
539
|
+
self._process_tp_dt(mid, dest_address, data, timestamp)
|
|
540
|
+
else:
|
|
541
|
+
self.__notify_subscribers(mid.priority, pgn_value, mid.source_address, dest_address, timestamp, data)
|
|
542
|
+
return
|
|
543
|
+
|