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

Implement _detect_available_configs for the Ixxat bus. #1607

Merged
merged 9 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion can/interfaces/ixxat/canlib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable, Optional, Sequence, Union
from typing import Callable, List, Optional, Sequence, Union

import can.interfaces.ixxat.canlib_vcinpl as vcinpl
import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2
Expand All @@ -8,6 +8,7 @@
CyclicSendTaskABC,
Message,
)
from can.typechecking import AutoDetectedConfig


class IXXATBus(BusABC):
Expand Down Expand Up @@ -170,3 +171,7 @@ def state(self) -> BusState:
Return the current state of the hardware
"""
return self.bus.state

@staticmethod
def _detect_available_configs() -> List[AutoDetectedConfig]:
return vcinpl._detect_available_configs()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Without any IXXAT-knowledge: does this detect FD-devices, too? What about vcinpl2?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a good question. I believe that vcinpl.dll and vcinpl2.dll are just the CAN LS/HS driver and CAN FD driver respectively, and that these are both components of the VCI4 package (so I think that both APIs should work for the CAN FD capable devices, but I am not 100% certain).

Based on the implementation of get_ixxat_hwids() in the __init__.py, I would expect that vcinpl.dll does work for the FD devices, and also has the advantage that it is compatible with the older VCI3 drivers (pre-FD) - I think this is why #1119 was chosen in favour of #1126. Unfortunately I dont have the FD capable hardware to test the theory.

If you would prefer, I can implement the function in the vcinpl2 file too and default to that implementation, falling back to to the vcinpl definition if vcinpl2.dll is not available?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@fjburgos can you take a look?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@MattWoodhead Regarding #1119 and #1126, with hindsight i think that was the wrong choice. The current solution with two IXXATBus implementations and that proxy class has lead to many bug reports.

Isn't vcinpl2.dll backwards compatible to older devices and classical CAN 2.0? The docs say
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The bug canopen #296 in the canopen library apears to be related, and is one that is currently causing me a headache :)

Yes, I am 90% sure that the vcinpl2.dll driver is backwards compatible with all of the older ixxat devices, it just wont be present on any systems that have the VCI3 or earlier drivers installed. I can modify my local branch and test with a non-FD USB-to-CAN Compact V2 to confirm tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good morning @zariiii9003.
I have tested this modified branch with the non-FD ixxat interface onto a bus with another non-ixxat interface and it all worked as expected.
Tests:

  • receive
  • can.viewer
  • send
  • send periodic (using bus.bus.send_periodic workaround for proxy class)

Copy link
Collaborator

Choose a reason for hiding this comment

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

That sounds promising. Would you like to reimplement the IXXATBus based on vcinpl2 without a proxy class? Maybe we could build upon #1126

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @zariiii9003. Yes I am happy to give that a go. Do you want me to do it in this PR?

I have compared canlib.py from #1119 and canlib_vcinpl2.py #1126 from the time when 1119 was merged, and the only significant differences between them are:

  1. The way in which the predefined bitrates are implemented (1126 stores them as class variables, 1119 stores them in the constants.py file.
  2. 1119 implements a check of the hardware interfaces capabilities before adding features such as CAN FD to the open method of the VCI driver, whereas 1126 just adds them if the parameter provided to the class demands it. In my opinion the check of the interfaces available functionality is valuable if we are converging on a single implementation.

What are your thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd suggest to create a new PR. If doesn't go anywhere, we can still merge this.

  1. I'd prefer the constants file. Regarding the bitrates, it would be nice if support for the BitTiming classes was added, too.
  2. I agree. Checking the capabilities doesn't hurt.

Copy link

Choose a reason for hiding this comment

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

Just for information Ixxat PCI_CAN interface is not compatible with VCI4. I know this is an old devicebut please dont forget user that use legacy board :)

55 changes: 54 additions & 1 deletion can/interfaces/ixxat/canlib_vcinpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import logging
import sys
import warnings
from typing import Callable, Optional, Sequence, Tuple, Union
from typing import Callable, List, Optional, Sequence, Tuple, Union

from can import (
BusABC,
Expand All @@ -28,6 +28,7 @@
from can.ctypesutil import HANDLE, PHANDLE, CLibrary
from can.ctypesutil import HRESULT as ctypes_HRESULT
from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError
from can.typechecking import AutoDetectedConfig
from can.util import deprecated_args_alias

from . import constants, structures
Expand Down Expand Up @@ -943,3 +944,55 @@ def get_ixxat_hwids():
_canlib.vciEnumDeviceClose(device_handle)

return hwids


def _detect_available_configs() -> List[AutoDetectedConfig]:
config_list = [] # list in wich to store the resulting bus kwargs

# used to detect HWID
device_handle = HANDLE()
device_info = structures.VCIDEVICEINFO()

# used to attempt to open channels
channel_handle = HANDLE()
device_handle2 = HANDLE()

try:
_canlib.vciEnumDeviceOpen(ctypes.byref(device_handle))
while True:
try:
_canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info))
except StopIteration:
break
else:
hwid = device_info.UniqueHardwareId.AsChar.decode("ascii")
_canlib.vciDeviceOpen(
ctypes.byref(device_info.VciObjectId),
ctypes.byref(device_handle2),
)
for channel in range(4):
try:
_canlib.canChannelOpen(
device_handle2,
channel,
constants.FALSE,
ctypes.byref(channel_handle),
)
except Exception:
# Array outside of bounds error == accessing a channel not in the hardware
break
else:
_canlib.canChannelClose(channel_handle)
config_list.append(
{
"interface": "ixxat",
"channel": channel,
"unique_hardware_id": hwid,
}
)
_canlib.vciDeviceClose(device_handle2)
_canlib.vciEnumDeviceClose(device_handle)
except AttributeError:
pass # _canlib is None in the CI tests -> return a blank list

return config_list
10 changes: 4 additions & 6 deletions can/interfaces/ixxat/canlib_vcinpl2.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,17 +509,15 @@ def __init__(
tseg1_abr is None or tseg2_abr is None or sjw_abr is None
):
raise ValueError(
"To use bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_abr, tseg2_abr and swj_abr".format(
bitrate
)
f"To use bitrate {bitrate} (that has not predefined preset) is mandatory "
f"to use also parameters tseg1_abr, tseg2_abr and swj_abr"
)
if data_bitrate not in constants.CAN_DATABITRATE_PRESETS and (
tseg1_dbr is None or tseg2_dbr is None or sjw_dbr is None
):
raise ValueError(
"To use data_bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr".format(
data_bitrate
)
f"To use data_bitrate {data_bitrate} (that has not predefined preset) is mandatory "
f"to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr"
)

if rx_fifo_size <= 0:
Expand Down
4 changes: 1 addition & 3 deletions can/interfaces/pcan/pcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,7 @@ def bits(n):
for b in bits(error):
stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9)
if stsReturn[0] != PCAN_ERROR_OK:
text = "An error occurred. Error-code's text ({:X}h) couldn't be retrieved".format(
error
)
text = f"An error occurred. Error-code's text ({error:X}h) couldn't be retrieved"
else:
text = stsReturn[1].decode("utf-8", errors="replace")

Expand Down
31 changes: 28 additions & 3 deletions doc/interfaces/ixxat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,42 @@ VCI documentation, section "Message filters" for more info.

List available devices
----------------------
In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id.
To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below:

In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id.
The function :meth:`~can.detect_available_configs` can be used to generate a list of :class:`~can.BusABC` constructors
(including the channel number and unique hardware ID number for the connected devices).

.. testsetup:: ixxat

from unittest.mock import Mock
import can
assert hasattr(can, "detect_available_configs")
can.detect_available_configs = Mock(
"interface",
return_value=[{'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW441489'}, {'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW107422'}, {'interface': 'ixxat', 'channel': 1, 'unique_hardware_id': 'HW107422'}],
)

.. doctest:: ixxat

>>> import can
>>> configs = can.detect_available_configs("ixxat")
>>> for config in configs:
... print(config)
{'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW441489'}
{'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW107422'}
{'interface': 'ixxat', 'channel': 1, 'unique_hardware_id': 'HW107422'}


You may also get a list of all connected IXXAT devices using the function ``get_ixxat_hwids()`` as demonstrated below:

.. testsetup:: ixxat2

from unittest.mock import Mock
import can.interfaces.ixxat
assert hasattr(can.interfaces.ixxat, "get_ixxat_hwids")
can.interfaces.ixxat.get_ixxat_hwids = Mock(side_effect=lambda: ['HW441489', 'HW107422'])

.. doctest:: ixxat
.. doctest:: ixxat2

>>> from can.interfaces.ixxat import get_ixxat_hwids
>>> for hwid in get_ixxat_hwids():
Expand Down
12 changes: 12 additions & 0 deletions test/test_interface_ixxat.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ def setUp(self):
raise unittest.SkipTest("not available on this platform")

def test_bus_creation(self):
try:
configs = can.detect_available_configs("ixxat")
if configs:
for interface_kwargs in configs:
bus = can.Bus(**interface_kwargs)
bus.shutdown()
else:
raise unittest.SkipTest("No adapters were detected")
except can.CanInterfaceNotImplementedError:
raise unittest.SkipTest("not available on this platform")

def test_bus_creation_incorrect_channel(self):
# non-existent channel -> use arbitrary high value
with self.assertRaises(can.CanInitializationError):
can.Bus(interface="ixxat", channel=0xFFFF)
Expand Down