Skip to content

Commit b9d9d01

Browse files
lumagizariiii9003
andauthored
Add device_id parameter to PcanBus constructor (#1346)
* Add device_id parameter to PcanBus constructor The new device_id parameter can be used to select a PCAN channel based on the freely programmable device ID of a PCAN USB device. This change allows for a more deterministic channel selection since the device ID does not change between restarts. * Change wording of PCAN _find_channel_by_dev_id docstring Co-authored-by: zariiii9003 <[email protected]> * Apply black code formatting Co-authored-by: zariiii9003 <[email protected]>
1 parent 3ae0879 commit b9d9d01

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

can/interfaces/pcan/pcan.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class PcanBus(BusABC):
103103
def __init__(
104104
self,
105105
channel="PCAN_USBBUS1",
106+
device_id=None,
106107
state=BusState.ACTIVE,
107108
bitrate=500000,
108109
*args,
@@ -119,6 +120,14 @@ def __init__(
119120
Alternatively the value can be an int with the numerical value.
120121
Default is 'PCAN_USBBUS1'
121122
123+
:param int device_id:
124+
Select the PCAN interface based on its ID. The device ID is a 8/32bit
125+
value that can be configured for each PCAN device. If you set the
126+
device_id parameter, it takes precedence over the channel parameter.
127+
The constructor searches all connected interfaces and initializes the
128+
first one that matches the parameter value. If no device is found,
129+
an exception is raised.
130+
122131
:param can.bus.BusState state:
123132
BusState of the channel.
124133
Default is ACTIVE
@@ -198,6 +207,15 @@ def __init__(
198207
Ignored if not using CAN-FD.
199208
200209
"""
210+
self.m_objPCANBasic = PCANBasic()
211+
212+
if device_id is not None:
213+
channel = self._find_channel_by_dev_id(device_id)
214+
215+
if channel is None:
216+
err_msg = "Cannot find a channel with ID {:08x}".format(device_id)
217+
raise ValueError(err_msg)
218+
201219
self.channel_info = str(channel)
202220
self.fd = kwargs.get("fd", False)
203221
pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K)
@@ -209,7 +227,6 @@ def __init__(
209227
if not isinstance(channel, int):
210228
channel = PCAN_CHANNEL_NAMES[channel]
211229

212-
self.m_objPCANBasic = PCANBasic()
213230
self.m_PcanHandle = channel
214231

215232
self.check_api_version()
@@ -269,6 +286,28 @@ def __init__(
269286

270287
super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs)
271288

289+
def _find_channel_by_dev_id(self, device_id):
290+
"""
291+
Iterate over all possible channels to find a channel that matches the device
292+
ID. This method is somewhat brute force, but the Basic API only offers a
293+
suitable API call since V4.4.0.
294+
295+
:param device_id: The device_id for which to search for
296+
:return: The name of a PCAN channel that matches the device ID, or None if
297+
no channel can be found.
298+
"""
299+
for ch_name, ch_handle in PCAN_CHANNEL_NAMES.items():
300+
err, cur_dev_id = self.m_objPCANBasic.GetValue(
301+
ch_handle, PCAN_DEVICE_NUMBER
302+
)
303+
if err != PCAN_ERROR_OK:
304+
continue
305+
306+
if cur_dev_id == device_id:
307+
return ch_name
308+
309+
return None
310+
272311
def _get_formatted_error(self, error):
273312
"""
274313
Gets the text using the GetErrorText API function.

test/test_pcan.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,25 @@ def test_status_string(self, name, status, expected_result) -> None:
344344
self.assertEqual(self.bus.status_string(), expected_result)
345345
self.mock_pcan.GetStatus.assert_called()
346346

347+
@parameterized.expand([(0x0, "error"), (0x42, "PCAN_USBBUS8")])
348+
def test_constructor_with_device_id(self, dev_id, expected_result):
349+
def get_value_side_effect(handle, param):
350+
if param == PCAN_API_VERSION:
351+
return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii")
352+
353+
if handle in (PCAN_USBBUS8, PCAN_USBBUS14):
354+
return 0, 0x42
355+
else:
356+
return PCAN_ERROR_ILLHW, 0x0
357+
358+
self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect)
359+
360+
if expected_result == "error":
361+
self.assertRaises(ValueError, can.Bus, bustype="pcan", device_id=dev_id)
362+
else:
363+
self.bus = can.Bus(bustype="pcan", device_id=dev_id)
364+
self.assertEqual(expected_result, self.bus.channel_info)
365+
347366

348367
if __name__ == "__main__":
349368
unittest.main()

0 commit comments

Comments
 (0)