Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add protocol property to BusABC to determine active CAN Protocol #1532

Merged
merged 34 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b2ec42c
Implement is_fd property for BusABC and PCANBus
lumagi Mar 5, 2023
059f33a
Implement enum to represent CAN protocol
lumagi Mar 7, 2023
07defb0
Implement CANProtocol for VirtualBus
lumagi Mar 12, 2023
26fe9d4
Implement CANProtocol for UDPMulticastBus
lumagi Mar 12, 2023
091572b
Implement CANProtocol for the CANalystIIBus
lumagi Mar 12, 2023
56e40d2
Implement CANProtocol for the slcanBus
lumagi Mar 12, 2023
642e711
Rename CANProtocol to CanProtocol
lumagi Mar 12, 2023
c6da1c4
Reimplement PcanBus.fd attribute as read-only property
lumagi Mar 19, 2023
da89d35
Reimplement UdpMulticastBus.is_fd attribute as read-only property
lumagi Mar 19, 2023
51301fe
Implement CanProtocol for robotellBus
lumagi Mar 19, 2023
55e349a
Implement CanProtocol for NicanBus
lumagi Mar 19, 2023
76465e4
Implement CanProtocol for IscanBus
lumagi Mar 19, 2023
cdbfa13
Implement CanProtocol for CantactBus
lumagi Mar 19, 2023
9ad3dae
Fix sphinx reference to CanProtocol
lumagi Mar 19, 2023
652d5cc
Implement CanProtocol for GsUsbBus
lumagi Mar 19, 2023
a20af38
Implement CanProtocol for NiXNETcanBus
lumagi Mar 19, 2023
d6fabc6
Implement CanProtocol for EtasBus
lumagi Mar 19, 2023
b2d9f30
Implement CanProtocol for IXXATBus
lumagi Mar 19, 2023
9a39e2e
Implement CanProtocol for KvaserBus
lumagi Mar 19, 2023
2aa8ceb
Implement CanProtocol for the SerialBus
lumagi Mar 19, 2023
4365157
Implement CanProtocol for UcanBus
lumagi Mar 19, 2023
08e9446
Implement CanProtocol for VectorBus
lumagi Mar 25, 2023
5ae8e44
Implement CanProtocol for NeousysBus
lumagi Mar 25, 2023
41b1979
Implement CanProtocol for Usb2canBus
lumagi Mar 25, 2023
eadfa83
Implement CanProtocol for NeoViBus
lumagi Mar 25, 2023
6b59513
Merge branch 'develop' into feature/bus_fd_field
zariiii9003 Apr 1, 2023
6b53ef4
Implement CanProtocol for SocketcanBus
lumagi Apr 2, 2023
5ac9814
Permit passthrough of protocol field for SocketCanDaemonBus
lumagi Apr 4, 2023
6914cba
Implement CanProtocol for SeeedBus
lumagi Apr 4, 2023
03504b4
Merge branch 'develop' into feature/bus_fd_field
lumagi Apr 13, 2023
4fe9aa9
Remove CanProtocol attribute from BusABC constructor
lumagi May 9, 2023
7541cd5
Apply suggestions from code review
lumagi May 12, 2023
0500799
Fix syntax error
lumagi May 12, 2023
3047263
Fix more enum comparisons against BusABC.protocol
lumagi May 12, 2023
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
2 changes: 1 addition & 1 deletion can/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .util import set_logging_level

from .message import Message
from .bus import BusABC, BusState
from .bus import BusABC, BusState, CANProtocol
from .thread_safe_bus import ThreadSafeBus
from .notifier import Notifier
from .interfaces import VALID_INTERFACES
Expand Down
26 changes: 26 additions & 0 deletions can/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ class BusState(Enum):
ERROR = auto()


class CANProtocol(Enum):
"""The CAN protocol type supported by a :class:`can.BusABC` instance"""

CAN_20 = auto()
CAN_FD = auto()
CAN_XL = auto()


class BusABC(metaclass=ABCMeta):
"""The CAN Bus Abstract Base Class that serves as the basis
for all concrete interfaces.
Expand All @@ -48,6 +56,7 @@ class BusABC(metaclass=ABCMeta):
def __init__(
self,
channel: Any,
protocol: CANProtocol = CANProtocol.CAN_20,
can_filters: Optional[can.typechecking.CanFilters] = None,
**kwargs: object
):
Expand All @@ -59,6 +68,13 @@ def __init__(
:param channel:
The can interface identifier. Expected type is backend dependent.

:param protocol:
The CAN protocol currently used by this bus instance. This value
is determined at initialization time (based on the initialization
parameters or because the bus interface only supports a specific
protocol) and does not change during the lifetime of a bus
instance.

:param can_filters:
See :meth:`~can.BusABC.set_filters` for details.

Expand All @@ -71,6 +87,7 @@ def __init__(
:raises ~can.exceptions.CanInitializationError:
If the bus cannot be initialized
"""
self._can_protocol = protocol
self._periodic_tasks: List[_SelfRemovingCyclicTask] = []
self.set_filters(can_filters)

Expand Down Expand Up @@ -442,6 +459,15 @@ def state(self, new_state: BusState) -> None:
"""
raise NotImplementedError("Property is not implemented.")

@property
def protocol(self) -> CANProtocol:
"""Return the CAN protocol used by this bus instance.

This value is set at initialization time and does not change
during the lifetime of a bus instance.
"""
return self._can_protocol

@staticmethod
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
"""Detect all configurations/channels that this interface could
Expand Down
19 changes: 13 additions & 6 deletions can/interfaces/pcan/pcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from can import (
BusABC,
BusState,
CANProtocol,
BitTiming,
BitTimingFd,
Message,
Expand Down Expand Up @@ -247,7 +248,7 @@ def __init__(
raise ValueError(err_msg)

self.channel_info = str(channel)
self.fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False)
is_fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False)

hwtype = PCAN_TYPE_ISA
ioport = 0x02A0
Expand All @@ -271,7 +272,7 @@ def __init__(
result = self.m_objPCANBasic.Initialize(
self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt
)
elif self.fd:
elif is_fd:
if isinstance(timing, BitTimingFd):
timing = check_or_adjust_timing_clock(
timing, sorted(VALID_PCAN_FD_CLOCKS, reverse=True)
Expand Down Expand Up @@ -338,7 +339,13 @@ def __init__(
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))

super().__init__(channel=channel, state=state, bitrate=bitrate, **kwargs)
super().__init__(
channel=channel,
protocol=CANProtocol.CAN_FD if is_fd else CANProtocol.CAN_20,
state=state,
bitrate=bitrate,
**kwargs,
)

def _find_channel_by_dev_id(self, device_id):
"""
Expand Down Expand Up @@ -484,7 +491,7 @@ def _recv_internal(
end_time = time.time() + timeout if timeout is not None else None

while True:
if self.fd:
if self.protocol == CANProtocol.CAN_FD:
result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.ReadFD(
self.m_PcanHandle
)
Expand Down Expand Up @@ -546,7 +553,7 @@ def _recv_internal(
error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value)
is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value)

if self.fd:
if self.protocol == CANProtocol.CAN_FD:
dlc = dlc2len(pcan_msg.DLC)
timestamp = boottimeEpoch + (pcan_timestamp.value / (1000.0 * 1000.0))
else:
Expand Down Expand Up @@ -592,7 +599,7 @@ def send(self, msg, timeout=None):
if msg.error_state_indicator:
msgType |= PCAN_MESSAGE_ESI.value

if self.fd:
if self.protocol == CANProtocol.CAN_FD:
# create a TPCANMsg message structure
CANMsg = TPCANMsgFD()

Expand Down
17 changes: 9 additions & 8 deletions can/interfaces/udp_multicast/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@
import select
import socket
import struct
from typing import List, Optional, Tuple, Union

import can
from can import BusABC, CANProtocol
from can.typechecking import AutoDetectedConfig
from .utils import pack_message, unpack_message, check_msgpack_installed

try:
from fcntl import ioctl
except ModuleNotFoundError: # Missing on Windows
pass

from typing import List, Optional, Tuple, Union

log = logging.getLogger(__name__)

import can
from can import BusABC
from can.typechecking import AutoDetectedConfig

from .utils import pack_message, unpack_message, check_msgpack_installed


# see socket.getaddrinfo()
IPv4_ADDRESS_INFO = Tuple[str, int] # address, port
Expand Down Expand Up @@ -103,7 +102,9 @@ def __init__(
"receiving own messages is not yet implemented"
)

super().__init__(channel, **kwargs)
super().__init__(
channel, **kwargs, protocol=CANProtocol.CAN_FD if fd else CANProtocol.CAN_20
)

self.is_fd = fd
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zariiii9003 Would you consider this is_fd flag as a public attribute that would lead to a breaking API change if it were removed? With the protocol field it's technically redundant and could be removed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could replace it with a property and raise a DeprecationWarning with a removal scheduled for version 5.

self._multicast = GeneralPurposeUdpMulticastBus(channel, port, hop_limit)
Expand Down
46 changes: 40 additions & 6 deletions can/interfaces/virtual.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from random import randint

from can import CanOperationError
from can.bus import BusABC
from can.bus import BusABC, CANProtocol
from can.message import Message
from can.typechecking import AutoDetectedConfig

Expand All @@ -34,7 +34,8 @@

class VirtualBus(BusABC):
"""
A virtual CAN bus using an internal message queue. It can be used for example for testing.
A virtual CAN bus using an internal message queue. It can be used for
example for testing.

In this interface, a channel is an arbitrary object used as
an identifier for connected buses.
Expand All @@ -49,9 +50,11 @@ class VirtualBus(BusABC):
if a message is sent to 5 receivers with the timeout set to 1.0.

.. warning::
This interface guarantees reliable delivery and message ordering, but does *not* implement rate
limiting or ID arbitration/prioritization under high loads. Please refer to the section
:ref:`virtual_interfaces_doc` for more information on this and a comparison to alternatives.
This interface guarantees reliable delivery and message ordering, but
does *not* implement rate limiting or ID arbitration/prioritization
under high loads. Please refer to the section
:ref:`virtual_interfaces_doc` for more information on this and a
comparison to alternatives.
"""

def __init__(
Expand All @@ -60,10 +63,41 @@ def __init__(
receive_own_messages: bool = False,
rx_queue_size: int = 0,
preserve_timestamps: bool = False,
protocol: CANProtocol = CANProtocol.CAN_20,
**kwargs: Any,
) -> None:
"""
The constructed instance has access to the bus identified by the
channel parameter. It is able to see all messages transmitted on the
bus by virtual instances constructed with the same channel identifier.

:param channel: The channel identifier. This parameter can be an
arbitrary value. The bus instance will be able to see messages
from other virtual bus instances that were created with the same
value.
:param receive_own_messages: If set to True, sent messages will be
reflected back on the input queue.
:param rx_queue_size: The size of the reception queue. The reception
queue stores messages until they are read. If the queue reaches
its capacity, it will start dropping the oldest messages to make
room for new ones. If set to 0, the queue has an infinite capacity.
Be aware that this can cause memory leaks if messages are read
with a lower frequency than they arrive on the bus.
:param preserve_timestamps: If set to True, messages transmitted via
:func:`~can.BusABC.send` will keep the timestamp set in the
:class:`~can.Message` instance. Otherwise, the timestamp value
will be replaced with the current system time.
:param protocol: The protocol implemented by this bus instance. The
value does not affect the operation of the bus instance and can
be set to an arbitrary value for testing purposes.
:param kwargs: Additional keyword arguments passed to the parent
constructor.
"""
super().__init__(
channel=channel, receive_own_messages=receive_own_messages, **kwargs
channel=channel,
receive_own_messages=receive_own_messages,
protocol=protocol,
**kwargs,
)

# the channel identifier may be an arbitrary object
Expand Down
4 changes: 4 additions & 0 deletions doc/bus.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ Bus API
:members:
:undoc-members:

.. autoclass:: can.bus.CANProtocol
:members:
:undoc-members:


Thread safe bus
'''''''''''''''
Expand Down
13 changes: 9 additions & 4 deletions test/test_pcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from parameterized import parameterized

import can
from can.bus import BusState
from can import BusState, CANProtocol
from can.exceptions import CanInitializationError
from can.interfaces.pcan import PcanBus, PcanError
from can.interfaces.pcan.basic import *
Expand Down Expand Up @@ -53,8 +53,9 @@ def test_bus_creation(self) -> None:
self.bus = can.Bus(interface="pcan")

self.assertIsInstance(self.bus, PcanBus)
self.MockPCANBasic.assert_called_once()
self.assertEqual(self.bus.protocol, CANProtocol.CAN_20)

self.MockPCANBasic.assert_called_once()
self.mock_pcan.Initialize.assert_called_once()
self.mock_pcan.InitializeFD.assert_not_called()

Expand All @@ -80,6 +81,8 @@ def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None:
)

self.assertIsInstance(self.bus, PcanBus)
self.assertEqual(self.bus.protocol, CANProtocol.CAN_FD)

self.MockPCANBasic.assert_called_once()
self.mock_pcan.Initialize.assert_not_called()
self.mock_pcan.InitializeFD.assert_called_once()
Expand Down Expand Up @@ -452,10 +455,11 @@ def test_peak_fd_bus_constructor_regression(self):

def test_constructor_bit_timing(self):
timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x47, btr1=0x2F)
can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)
bus = can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)

bitrate_arg = self.mock_pcan.Initialize.call_args[0][1]
self.assertEqual(bitrate_arg.value, 0x472F)
self.assertEqual(bus.protocol, CANProtocol.CAN_20)

def test_constructor_bit_timing_fd(self):
timing = can.BitTimingFd(
Expand All @@ -469,7 +473,8 @@ def test_constructor_bit_timing_fd(self):
data_tseg2=6,
data_sjw=1,
)
can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)
bus = can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)
self.assertEqual(bus.protocol, CANProtocol.CAN_FD)

bitrate_arg = self.mock_pcan.InitializeFD.call_args[0][-1]

Expand Down