|
1 | 1 | """
|
2 | 2 | Enable basic CAN over a PCAN USB device.
|
3 | 3 | """
|
4 |
| - |
5 | 4 | import logging
|
6 | 5 | import time
|
7 | 6 | from datetime import datetime
|
8 | 7 | import platform
|
9 |
| -from typing import Optional, List |
| 8 | +from typing import Optional, List, Tuple |
10 | 9 |
|
11 | 10 | from packaging import version
|
12 | 11 |
|
|
50 | 49 | PCAN_LISTEN_ONLY,
|
51 | 50 | PCAN_PARAMETER_OFF,
|
52 | 51 | TPCANHandle,
|
| 52 | + IS_LINUX, |
| 53 | + IS_WINDOWS, |
53 | 54 | PCAN_PCIBUS1,
|
54 | 55 | PCAN_USBBUS1,
|
55 | 56 | PCAN_PCCBUS1,
|
|
70 | 71 |
|
71 | 72 | MIN_PCAN_API_VERSION = version.parse("4.2.0")
|
72 | 73 |
|
73 |
| - |
74 | 74 | try:
|
75 | 75 | # use the "uptime" library if available
|
76 | 76 | import uptime
|
|
86 | 86 | )
|
87 | 87 | boottimeEpoch = 0
|
88 | 88 |
|
89 |
| -try: |
90 |
| - # Try builtin Python 3 Windows API |
91 |
| - from _overlapped import CreateEvent |
92 |
| - from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE |
| 89 | +HAS_EVENTS = False |
93 | 90 |
|
94 |
| - HAS_EVENTS = True |
95 |
| -except ImportError: |
| 91 | +if IS_WINDOWS: |
96 | 92 | try:
|
97 |
| - # Try pywin32 package |
98 |
| - from win32event import CreateEvent |
99 |
| - from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE |
| 93 | + # Try builtin Python 3 Windows API |
| 94 | + from _overlapped import CreateEvent |
| 95 | + from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE |
100 | 96 |
|
101 | 97 | HAS_EVENTS = True
|
102 | 98 | except ImportError:
|
103 |
| - # Use polling instead |
104 |
| - HAS_EVENTS = False |
| 99 | + pass |
| 100 | + |
| 101 | +elif IS_LINUX: |
| 102 | + try: |
| 103 | + import errno |
| 104 | + import os |
| 105 | + import select |
| 106 | + |
| 107 | + HAS_EVENTS = True |
| 108 | + except Exception: |
| 109 | + pass |
105 | 110 |
|
106 | 111 |
|
107 | 112 | class PcanBus(BusABC):
|
@@ -294,10 +299,16 @@ def __init__(
|
294 | 299 | raise PcanCanInitializationError(self._get_formatted_error(result))
|
295 | 300 |
|
296 | 301 | if HAS_EVENTS:
|
297 |
| - self._recv_event = CreateEvent(None, 0, 0, None) |
298 |
| - result = self.m_objPCANBasic.SetValue( |
299 |
| - self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event |
300 |
| - ) |
| 302 | + if IS_WINDOWS: |
| 303 | + self._recv_event = CreateEvent(None, 0, 0, None) |
| 304 | + result = self.m_objPCANBasic.SetValue( |
| 305 | + self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event |
| 306 | + ) |
| 307 | + elif IS_LINUX: |
| 308 | + result, self._recv_event = self.m_objPCANBasic.GetValue( |
| 309 | + self.m_PcanHandle, PCAN_RECEIVE_EVENT |
| 310 | + ) |
| 311 | + |
301 | 312 | if result != PCAN_ERROR_OK:
|
302 | 313 | raise PcanCanInitializationError(self._get_formatted_error(result))
|
303 | 314 |
|
@@ -441,84 +452,96 @@ def set_device_number(self, device_number):
|
441 | 452 | return False
|
442 | 453 | return True
|
443 | 454 |
|
444 |
| - def _recv_internal(self, timeout): |
| 455 | + def _recv_internal( |
| 456 | + self, timeout: Optional[float] |
| 457 | + ) -> Tuple[Optional[Message], bool]: |
| 458 | + end_time = time.time() + timeout if timeout is not None else None |
445 | 459 |
|
446 |
| - if HAS_EVENTS: |
447 |
| - # We will utilize events for the timeout handling |
448 |
| - timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE |
449 |
| - elif timeout is not None: |
450 |
| - # Calculate max time |
451 |
| - end_time = time.perf_counter() + timeout |
452 |
| - |
453 |
| - # log.debug("Trying to read a msg") |
454 |
| - |
455 |
| - result = None |
456 |
| - while result is None: |
| 460 | + while True: |
457 | 461 | if self.fd:
|
458 |
| - result = self.m_objPCANBasic.ReadFD(self.m_PcanHandle) |
| 462 | + result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.ReadFD( |
| 463 | + self.m_PcanHandle |
| 464 | + ) |
459 | 465 | else:
|
460 |
| - result = self.m_objPCANBasic.Read(self.m_PcanHandle) |
461 |
| - if result[0] == PCAN_ERROR_QRCVEMPTY: |
462 |
| - if HAS_EVENTS: |
463 |
| - result = None |
464 |
| - val = WaitForSingleObject(self._recv_event, timeout_ms) |
465 |
| - if val != WAIT_OBJECT_0: |
466 |
| - return None, False |
467 |
| - elif timeout is not None and time.perf_counter() >= end_time: |
468 |
| - return None, False |
| 466 | + result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.Read( |
| 467 | + self.m_PcanHandle |
| 468 | + ) |
| 469 | + |
| 470 | + if result == PCAN_ERROR_OK: |
| 471 | + # message received |
| 472 | + break |
| 473 | + |
| 474 | + if result == PCAN_ERROR_QRCVEMPTY: |
| 475 | + # receive queue is empty, wait or return on timeout |
| 476 | + |
| 477 | + if end_time is None: |
| 478 | + time_left: Optional[float] = None |
| 479 | + timed_out = False |
469 | 480 | else:
|
470 |
| - result = None |
| 481 | + time_left = max(0.0, end_time - time.time()) |
| 482 | + timed_out = time_left == 0.0 |
| 483 | + |
| 484 | + if timed_out: |
| 485 | + return None, False |
| 486 | + |
| 487 | + if not HAS_EVENTS: |
| 488 | + # polling mode |
471 | 489 | time.sleep(0.001)
|
472 |
| - elif result[0] & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY): |
473 |
| - log.warning(self._get_formatted_error(result[0])) |
474 |
| - return None, False |
475 |
| - elif result[0] != PCAN_ERROR_OK: |
476 |
| - raise PcanCanOperationError(self._get_formatted_error(result[0])) |
477 |
| - |
478 |
| - theMsg = result[1] |
479 |
| - itsTimeStamp = result[2] |
480 |
| - |
481 |
| - # log.debug("Received a message") |
482 |
| - |
483 |
| - is_extended_id = ( |
484 |
| - theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value |
485 |
| - ) == PCAN_MESSAGE_EXTENDED.value |
486 |
| - is_remote_frame = ( |
487 |
| - theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value |
488 |
| - ) == PCAN_MESSAGE_RTR.value |
489 |
| - is_fd = (theMsg.MSGTYPE & PCAN_MESSAGE_FD.value) == PCAN_MESSAGE_FD.value |
490 |
| - bitrate_switch = ( |
491 |
| - theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value |
492 |
| - ) == PCAN_MESSAGE_BRS.value |
493 |
| - error_state_indicator = ( |
494 |
| - theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value |
495 |
| - ) == PCAN_MESSAGE_ESI.value |
496 |
| - is_error_frame = ( |
497 |
| - theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value |
498 |
| - ) == PCAN_MESSAGE_ERRFRAME.value |
| 490 | + continue |
| 491 | + |
| 492 | + if IS_WINDOWS: |
| 493 | + # Windows with event |
| 494 | + if time_left is None: |
| 495 | + time_left_ms = INFINITE |
| 496 | + else: |
| 497 | + time_left_ms = int(time_left * 1000) |
| 498 | + _ret = WaitForSingleObject(self._recv_event, time_left_ms) |
| 499 | + if _ret == WAIT_OBJECT_0: |
| 500 | + continue |
| 501 | + |
| 502 | + elif IS_LINUX: |
| 503 | + # Linux with event |
| 504 | + recv, _, _ = select.select([self._recv_event], [], [], time_left) |
| 505 | + if self._recv_event in recv: |
| 506 | + continue |
| 507 | + |
| 508 | + elif result & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY): |
| 509 | + log.warning(self._get_formatted_error(result)) |
| 510 | + |
| 511 | + else: |
| 512 | + raise PcanCanOperationError(self._get_formatted_error(result)) |
| 513 | + |
| 514 | + return None, False |
| 515 | + |
| 516 | + is_extended_id = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) |
| 517 | + is_remote_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_RTR.value) |
| 518 | + is_fd = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_FD.value) |
| 519 | + bitrate_switch = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_BRS.value) |
| 520 | + error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value) |
| 521 | + is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) |
499 | 522 |
|
500 | 523 | if self.fd:
|
501 |
| - dlc = dlc2len(theMsg.DLC) |
502 |
| - timestamp = boottimeEpoch + (itsTimeStamp.value / (1000.0 * 1000.0)) |
| 524 | + dlc = dlc2len(pcan_msg.DLC) |
| 525 | + timestamp = boottimeEpoch + (pcan_timestamp.value / (1000.0 * 1000.0)) |
503 | 526 | else:
|
504 |
| - dlc = theMsg.LEN |
| 527 | + dlc = pcan_msg.LEN |
505 | 528 | timestamp = boottimeEpoch + (
|
506 | 529 | (
|
507 |
| - itsTimeStamp.micros |
508 |
| - + 1000 * itsTimeStamp.millis |
509 |
| - + 0x100000000 * 1000 * itsTimeStamp.millis_overflow |
| 530 | + pcan_timestamp.micros |
| 531 | + + 1000 * pcan_timestamp.millis |
| 532 | + + 0x100000000 * 1000 * pcan_timestamp.millis_overflow |
510 | 533 | )
|
511 | 534 | / (1000.0 * 1000.0)
|
512 | 535 | )
|
513 | 536 |
|
514 | 537 | rx_msg = Message(
|
515 | 538 | timestamp=timestamp,
|
516 |
| - arbitration_id=theMsg.ID, |
| 539 | + arbitration_id=pcan_msg.ID, |
517 | 540 | is_extended_id=is_extended_id,
|
518 | 541 | is_remote_frame=is_remote_frame,
|
519 | 542 | is_error_frame=is_error_frame,
|
520 | 543 | dlc=dlc,
|
521 |
| - data=theMsg.DATA[:dlc], |
| 544 | + data=pcan_msg.DATA[:dlc], |
522 | 545 | is_fd=is_fd,
|
523 | 546 | bitrate_switch=bitrate_switch,
|
524 | 547 | error_state_indicator=error_state_indicator,
|
@@ -597,6 +620,9 @@ def flash(self, flash):
|
597 | 620 |
|
598 | 621 | def shutdown(self):
|
599 | 622 | super().shutdown()
|
| 623 | + if HAS_EVENTS and IS_LINUX: |
| 624 | + self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_RECEIVE_EVENT, 0) |
| 625 | + |
600 | 626 | self.m_objPCANBasic.Uninitialize(self.m_PcanHandle)
|
601 | 627 |
|
602 | 628 | @property
|
|
0 commit comments