Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions can/interfaces/pcan/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@

import logging

if platform.system() == "Windows":
import winreg
PLATFORM = platform.system()

if PLATFORM == "Windows":
import winreg

logger = logging.getLogger("can.pcan")

Expand Down
88 changes: 64 additions & 24 deletions can/interfaces/pcan/pcan.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
Enable basic CAN over a PCAN USB device.
"""

import logging
import time
from datetime import datetime
Expand All @@ -18,6 +17,7 @@


from .basic import (
PLATFORM,
PCAN_BITRATES,
PCAN_FD_PARAMETER_LIST,
PCAN_CHANNEL_NAMES,
Expand Down Expand Up @@ -64,7 +64,8 @@
log = logging.getLogger("can.pcan")

MIN_PCAN_API_VERSION = version.parse("4.2.0")

IS_WINDOWS = PLATFORM == "Windows"
IS_LINUX = PLATFORM == "Linux"

try:
# use the "uptime" library if available
Expand All @@ -82,22 +83,34 @@
)
boottimeEpoch = 0

try:
# Try builtin Python 3 Windows API
from _overlapped import CreateEvent
from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE

HAS_EVENTS = True
except ImportError:
if IS_WINDOWS:
try:
# Try pywin32 package
from win32event import CreateEvent
from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
# Try builtin Python 3 Windows API
from _overlapped import CreateEvent
from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE

HAS_EVENTS = True
except ImportError:
# Use polling instead
try:
# Try pywin32 package
from win32event import CreateEvent
from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE

HAS_EVENTS = True
except ImportError:
# Use polling instead
HAS_EVENTS = False
elif IS_LINUX:
try:
import errno
import os
import select

HAS_EVENTS = True
except Exception:
HAS_EVENTS = False
else:
HAS_EVENTS = False


class PcanBus(BusABC):
Expand Down Expand Up @@ -290,10 +303,16 @@ def __init__(
raise PcanCanInitializationError(self._get_formatted_error(result))

if HAS_EVENTS:
self._recv_event = CreateEvent(None, 0, 0, None)
result = self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event
)
if IS_WINDOWS:
self._recv_event = CreateEvent(None, 0, 0, None)
result = self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event
)
elif IS_LINUX:
result, self._recv_event = self.m_objPCANBasic.GetValue(
self.m_PcanHandle, PCAN_RECEIVE_EVENT
)

if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))

Expand Down Expand Up @@ -437,11 +456,26 @@ def set_device_number(self, device_number):
return False
return True

def _read_message(self):
"""Read a message using Basic API.

:return: a tuple containing the current CAN status, the received
message and the timestamp
:rtype: tuple
"""
if self.fd:
return self.m_objPCANBasic.ReadFD(self.m_PcanHandle)

return self.m_objPCANBasic.Read(self.m_PcanHandle)

def _recv_internal(self, timeout):

if HAS_EVENTS:
if HAS_EVENTS and IS_WINDOWS:
# We will utilize events for the timeout handling
timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE
elif HAS_EVENTS and IS_LINUX:
# We will utilize events for the timeout handling
timeout_s = timeout if timeout != None else None
elif timeout is not None:
# Calculate max time
end_time = time.perf_counter() + timeout
Expand All @@ -450,16 +484,19 @@ def _recv_internal(self, timeout):

result = None
while result is None:
if self.fd:
result = self.m_objPCANBasic.ReadFD(self.m_PcanHandle)
else:
result = self.m_objPCANBasic.Read(self.m_PcanHandle)

result = self._read_message()

if result[0] == PCAN_ERROR_QRCVEMPTY:
if HAS_EVENTS:
result = None
if HAS_EVENTS and IS_WINDOWS:
val = WaitForSingleObject(self._recv_event, timeout_ms)
if val != WAIT_OBJECT_0:
return None, False
elif HAS_EVENTS and IS_LINUX:
recv, _, _ = select.select([self._recv_event], [], [], timeout_s)
if self._recv_event not in recv:
return None, False
result = self._read_message()
elif timeout is not None and time.perf_counter() >= end_time:
return None, False
else:
Expand Down Expand Up @@ -593,6 +630,9 @@ def flash(self, flash):

def shutdown(self):
super().shutdown()
if HAS_EVENTS and IS_LINUX:
self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_RECEIVE_EVENT, 0)

self.m_objPCANBasic.Uninitialize(self.m_PcanHandle)

@property
Expand Down
9 changes: 6 additions & 3 deletions test/test_pcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import ctypes
import unittest
from unittest import mock
from unittest.mock import Mock
from unittest.mock import Mock, patch


import pytest
from parameterized import parameterized
Expand All @@ -29,7 +30,6 @@ def setUp(self) -> None:
self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK)
self.mock_pcan.GetValue = self._mockGetValue
self.PCAN_API_VERSION_SIM = "4.2"

self.bus = None

def tearDown(self) -> None:
Expand All @@ -44,6 +44,8 @@ def _mockGetValue(self, channel, parameter):
"""
if parameter == PCAN_API_VERSION:
return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii")
elif parameter == PCAN_RECEIVE_EVENT:
return PCAN_ERROR_OK, int.from_bytes(PCAN_RECEIVE_EVENT, "big")
raise NotImplementedError(
f"No mock return value specified for parameter {parameter}"
)
Expand Down Expand Up @@ -204,7 +206,8 @@ def test_recv_fd(self):
self.assertEqual(recv_msg.timestamp, 0)

@pytest.mark.timeout(3.0)
def test_recv_no_message(self):
@patch("select.select", return_value=([], [], []))
def test_recv_no_message(self, mock_select):
self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_QRCVEMPTY, None, None))
self.bus = can.Bus(bustype="pcan")
self.assertEqual(self.bus.recv(timeout=0.5), None)
Expand Down