|
11 | 11 | import logging |
12 | 12 | import os |
13 | 13 | import tempfile |
14 | | -from collections import deque |
| 14 | +from collections import deque, defaultdict |
| 15 | +from itertools import cycle |
| 16 | +from threading import Event |
15 | 17 |
|
16 | 18 | from can import Message, CanError, BusABC |
17 | 19 |
|
@@ -55,6 +57,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): |
55 | 57 | # Use inter-process mutex to prevent concurrent device open. |
56 | 58 | # When neoVI server is enabled, there is an issue with concurrent device open. |
57 | 59 | open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) |
| 60 | +description_id = cycle(range(1, 0x8000)) |
58 | 61 |
|
59 | 62 |
|
60 | 63 | class ICSApiError(CanError): |
@@ -176,6 +179,7 @@ def __init__(self, channel, can_filters=None, **kwargs): |
176 | 179 | logger.info("Using device: {}".format(self.channel_info)) |
177 | 180 |
|
178 | 181 | self.rx_buffer = deque() |
| 182 | + self.message_receipts = defaultdict(Event) |
179 | 183 |
|
180 | 184 | @staticmethod |
181 | 185 | def channel_to_netid(channel_name_or_id): |
@@ -261,9 +265,18 @@ def _process_msg_queue(self, timeout=0.1): |
261 | 265 | for ics_msg in messages: |
262 | 266 | if ics_msg.NetworkID not in self.channels: |
263 | 267 | continue |
| 268 | + |
264 | 269 | is_tx = bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG) |
265 | | - if not self._receive_own_messages and is_tx: |
266 | | - continue |
| 270 | + |
| 271 | + if is_tx: |
| 272 | + if bool(ics_msg.StatusBitField & ics.SPY_STATUS_GLOBAL_ERR): |
| 273 | + continue |
| 274 | + if ics_msg.DescriptionID: |
| 275 | + receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID) |
| 276 | + self.message_receipts[receipt_key].set() |
| 277 | + if not self._receive_own_messages: |
| 278 | + continue |
| 279 | + |
267 | 280 | self.rx_buffer.append(ics_msg) |
268 | 281 | if errors: |
269 | 282 | logger.warning("%d error(s) found", errors) |
@@ -343,7 +356,19 @@ def _recv_internal(self, timeout=0.1): |
343 | 356 | return None, False |
344 | 357 | return msg, False |
345 | 358 |
|
346 | | - def send(self, msg, timeout=None): |
| 359 | + def send(self, msg, timeout=0): |
| 360 | + """Transmit a message to the CAN bus. |
| 361 | +
|
| 362 | + :param Message msg: A message object. |
| 363 | +
|
| 364 | + :param float timeout: |
| 365 | + If > 0, wait up to this many seconds for message to be ACK'ed. |
| 366 | + If timeout is exceeded, an exception will be raised. |
| 367 | + None blocks indefinitely. |
| 368 | +
|
| 369 | + :raises can.CanError: |
| 370 | + if the message could not be sent |
| 371 | + """ |
347 | 372 | if not ics.validate_hobject(self.dev): |
348 | 373 | raise CanError("bus not open") |
349 | 374 | message = ics.SpyMessage() |
@@ -379,7 +404,20 @@ def send(self, msg, timeout=None): |
379 | 404 | else: |
380 | 405 | raise ValueError("msg.channel must be set when using multiple channels.") |
381 | 406 |
|
| 407 | + msg_desc_id = next(description_id) |
| 408 | + message.DescriptionID = msg_desc_id |
| 409 | + receipt_key = (msg.arbitration_id, msg_desc_id) |
| 410 | + |
| 411 | + if timeout != 0: |
| 412 | + self.message_receipts[receipt_key].clear() |
| 413 | + |
382 | 414 | try: |
383 | 415 | ics.transmit_messages(self.dev, message) |
384 | 416 | except ics.RuntimeError: |
385 | 417 | raise ICSApiError(*ics.get_last_api_error(self.dev)) |
| 418 | + |
| 419 | + # If timeout is set, wait for ACK |
| 420 | + # This requires a notifier for the bus or |
| 421 | + # some other thread calling recv periodically |
| 422 | + if timeout != 0 and not self.message_receipts[receipt_key].wait(timeout): |
| 423 | + raise CanError("Transmit timeout") |
0 commit comments