Skip to content

Commit 7b353ca

Browse files
Implement _detect_available_configs for the Ixxat bus. (#1607)
* Add _detect_available_configs to ixxat bus * Add typing and cover CI test failure * Format code with black * Format code with black * re-order imports for ruff * Update ixxat docs * fix doctest * Update test_interface_ixxat.py * make ruff happy --------- Co-authored-by: MattWoodhead <[email protected]> Co-authored-by: zariiii9003 <[email protected]>
1 parent e869fb7 commit 7b353ca

File tree

6 files changed

+105
-14
lines changed

6 files changed

+105
-14
lines changed

can/interfaces/ixxat/canlib.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Callable, Optional, Sequence, Union
1+
from typing import Callable, List, Optional, Sequence, Union
22

33
import can.interfaces.ixxat.canlib_vcinpl as vcinpl
44
import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2
@@ -8,6 +8,7 @@
88
CyclicSendTaskABC,
99
Message,
1010
)
11+
from can.typechecking import AutoDetectedConfig
1112

1213

1314
class IXXATBus(BusABC):
@@ -170,3 +171,7 @@ def state(self) -> BusState:
170171
Return the current state of the hardware
171172
"""
172173
return self.bus.state
174+
175+
@staticmethod
176+
def _detect_available_configs() -> List[AutoDetectedConfig]:
177+
return vcinpl._detect_available_configs()

can/interfaces/ixxat/canlib_vcinpl.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import logging
1515
import sys
1616
import warnings
17-
from typing import Callable, Optional, Sequence, Tuple, Union
17+
from typing import Callable, List, Optional, Sequence, Tuple, Union
1818

1919
from can import (
2020
BusABC,
@@ -28,6 +28,7 @@
2828
from can.ctypesutil import HANDLE, PHANDLE, CLibrary
2929
from can.ctypesutil import HRESULT as ctypes_HRESULT
3030
from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError
31+
from can.typechecking import AutoDetectedConfig
3132
from can.util import deprecated_args_alias
3233

3334
from . import constants, structures
@@ -943,3 +944,55 @@ def get_ixxat_hwids():
943944
_canlib.vciEnumDeviceClose(device_handle)
944945

945946
return hwids
947+
948+
949+
def _detect_available_configs() -> List[AutoDetectedConfig]:
950+
config_list = [] # list in wich to store the resulting bus kwargs
951+
952+
# used to detect HWID
953+
device_handle = HANDLE()
954+
device_info = structures.VCIDEVICEINFO()
955+
956+
# used to attempt to open channels
957+
channel_handle = HANDLE()
958+
device_handle2 = HANDLE()
959+
960+
try:
961+
_canlib.vciEnumDeviceOpen(ctypes.byref(device_handle))
962+
while True:
963+
try:
964+
_canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info))
965+
except StopIteration:
966+
break
967+
else:
968+
hwid = device_info.UniqueHardwareId.AsChar.decode("ascii")
969+
_canlib.vciDeviceOpen(
970+
ctypes.byref(device_info.VciObjectId),
971+
ctypes.byref(device_handle2),
972+
)
973+
for channel in range(4):
974+
try:
975+
_canlib.canChannelOpen(
976+
device_handle2,
977+
channel,
978+
constants.FALSE,
979+
ctypes.byref(channel_handle),
980+
)
981+
except Exception:
982+
# Array outside of bounds error == accessing a channel not in the hardware
983+
break
984+
else:
985+
_canlib.canChannelClose(channel_handle)
986+
config_list.append(
987+
{
988+
"interface": "ixxat",
989+
"channel": channel,
990+
"unique_hardware_id": hwid,
991+
}
992+
)
993+
_canlib.vciDeviceClose(device_handle2)
994+
_canlib.vciEnumDeviceClose(device_handle)
995+
except AttributeError:
996+
pass # _canlib is None in the CI tests -> return a blank list
997+
998+
return config_list

can/interfaces/ixxat/canlib_vcinpl2.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -509,17 +509,15 @@ def __init__(
509509
tseg1_abr is None or tseg2_abr is None or sjw_abr is None
510510
):
511511
raise ValueError(
512-
"To use bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_abr, tseg2_abr and swj_abr".format(
513-
bitrate
514-
)
512+
f"To use bitrate {bitrate} (that has not predefined preset) is mandatory "
513+
f"to use also parameters tseg1_abr, tseg2_abr and swj_abr"
515514
)
516515
if data_bitrate not in constants.CAN_DATABITRATE_PRESETS and (
517516
tseg1_dbr is None or tseg2_dbr is None or sjw_dbr is None
518517
):
519518
raise ValueError(
520-
"To use data_bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr".format(
521-
data_bitrate
522-
)
519+
f"To use data_bitrate {data_bitrate} (that has not predefined preset) is mandatory "
520+
f"to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr"
523521
)
524522

525523
if rx_fifo_size <= 0:

can/interfaces/pcan/pcan.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,7 @@ def bits(n):
396396
for b in bits(error):
397397
stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9)
398398
if stsReturn[0] != PCAN_ERROR_OK:
399-
text = "An error occurred. Error-code's text ({:X}h) couldn't be retrieved".format(
400-
error
401-
)
399+
text = f"An error occurred. Error-code's text ({error:X}h) couldn't be retrieved"
402400
else:
403401
text = stsReturn[1].decode("utf-8", errors="replace")
404402

doc/interfaces/ixxat.rst

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,42 @@ VCI documentation, section "Message filters" for more info.
5656

5757
List available devices
5858
----------------------
59-
In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id.
60-
To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below:
59+
60+
In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id.
61+
The function :meth:`~can.detect_available_configs` can be used to generate a list of :class:`~can.BusABC` constructors
62+
(including the channel number and unique hardware ID number for the connected devices).
6163

6264
.. testsetup:: ixxat
6365

66+
from unittest.mock import Mock
67+
import can
68+
assert hasattr(can, "detect_available_configs")
69+
can.detect_available_configs = Mock(
70+
"interface",
71+
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'}],
72+
)
73+
74+
.. doctest:: ixxat
75+
76+
>>> import can
77+
>>> configs = can.detect_available_configs("ixxat")
78+
>>> for config in configs:
79+
... print(config)
80+
{'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW441489'}
81+
{'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW107422'}
82+
{'interface': 'ixxat', 'channel': 1, 'unique_hardware_id': 'HW107422'}
83+
84+
85+
You may also get a list of all connected IXXAT devices using the function ``get_ixxat_hwids()`` as demonstrated below:
86+
87+
.. testsetup:: ixxat2
88+
6489
from unittest.mock import Mock
6590
import can.interfaces.ixxat
6691
assert hasattr(can.interfaces.ixxat, "get_ixxat_hwids")
6792
can.interfaces.ixxat.get_ixxat_hwids = Mock(side_effect=lambda: ['HW441489', 'HW107422'])
6893

69-
.. doctest:: ixxat
94+
.. doctest:: ixxat2
7095

7196
>>> from can.interfaces.ixxat import get_ixxat_hwids
7297
>>> for hwid in get_ixxat_hwids():

test/test_interface_ixxat.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ def setUp(self):
5151
raise unittest.SkipTest("not available on this platform")
5252

5353
def test_bus_creation(self):
54+
try:
55+
configs = can.detect_available_configs("ixxat")
56+
if configs:
57+
for interface_kwargs in configs:
58+
bus = can.Bus(**interface_kwargs)
59+
bus.shutdown()
60+
else:
61+
raise unittest.SkipTest("No adapters were detected")
62+
except can.CanInterfaceNotImplementedError:
63+
raise unittest.SkipTest("not available on this platform")
64+
65+
def test_bus_creation_incorrect_channel(self):
5466
# non-existent channel -> use arbitrary high value
5567
with self.assertRaises(can.CanInitializationError):
5668
can.Bus(interface="ixxat", channel=0xFFFF)

0 commit comments

Comments
 (0)