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/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
+