From 7a9ef00103533969b2b75807e93d92ff30d4fa20 Mon Sep 17 00:00:00 2001 From: spencer Date: Mon, 17 Feb 2025 20:42:45 -0700 Subject: [PATCH 1/7] add FD support to slcan according to CANable 2.0 impementation --- can/interfaces/slcan.py | 68 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f0a04e305..aae7f538b 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -16,7 +16,7 @@ CanOperationError, error_check, ) -from can.util import check_or_adjust_timing_clock, deprecated_args_alias +from can.util import CAN_FD_DLC, check_or_adjust_timing_clock, deprecated_args_alias logger = logging.getLogger(__name__) @@ -48,6 +48,10 @@ class slcanBus(BusABC): 1000000: "S8", 83300: "S9", } + _DATA_BITRATES = { + 2000000: "Y2", + 5000000: "Y5", + } _SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds @@ -86,7 +90,8 @@ def __init__( If this argument is set then it overrides the bitrate and btr arguments. The `f_clock` value of the timing instance must be set to 8_000_000 (8MHz) for standard CAN. - CAN FD and the :class:`~can.BitTimingFd` class are not supported. + CAN FD and the :class:`~can.BitTimingFd` class have partial support according to the non-standard + slcan protocol implementation in the CANABLE 2.0 firmware: currently only data rates of 2M and 5M. :param poll_interval: Poll interval in seconds when reading messages :param sleep_after_open: @@ -143,9 +148,7 @@ def __init__( timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) self.set_bitrate_reg(f"{timing.btr0:02X}{timing.btr1:02X}") elif isinstance(timing, BitTimingFd): - raise NotImplementedError( - f"CAN FD is not supported by {self.__class__.__name__}." - ) + self.set_bitrate(timing.nom_bitrate, timing.data_bitrate) else: if bitrate is not None and btr is not None: raise ValueError("Bitrate and btr mutually exclusive.") @@ -157,10 +160,12 @@ def __init__( super().__init__(channel, **kwargs) - def set_bitrate(self, bitrate: int) -> None: + def set_bitrate(self, bitrate: int, dbitrate: int = 0) -> None: """ :param bitrate: Bitrate in bit/s + :param dbitrate: + Data Bitrate in bit/s for FD frames :raise ValueError: if ``bitrate`` is not among the possible values """ @@ -169,9 +174,15 @@ def set_bitrate(self, bitrate: int) -> None: else: bitrates = ", ".join(str(k) for k in self._BITRATES.keys()) raise ValueError(f"Invalid bitrate, choose one of {bitrates}.") + if dbitrate in self._DATA_BITRATES: + dbitrate_code = self._DATA_BITRATES[dbitrate] + else: + dbitrates = ", ".join(str(k) for k in self._DATA_BITRATES.keys()) + raise ValueError(f"Invalid data bitrate, choose one of {dbitrates}.") self.close() self._write(bitrate_code) + self._write(dbitrate_code) self.open() def set_bitrate_reg(self, btr: str) -> None: @@ -235,6 +246,8 @@ def _recv_internal( remote = False extended = False data = None + isFd = False + fdBrs = False if self._queue.qsize(): string: Optional[str] = self._queue.get_nowait() @@ -268,6 +281,34 @@ def _recv_internal( dlc = int(string[9]) extended = True remote = True + elif string[0] == "d": + # FD standard frame + canId = int(string[1:4], 16) + dlc = int(string[4], 16) + isFd = True + data = bytearray.fromhex(string[5 : 5 + CAN_FD_DLC[dlc] * 2]) + elif string[0] == "D": + # FD extended frame + canId = int(string[1:9], 16) + dlc = int(string[9], 16) + extended = True + isFd = True + data = bytearray.fromhex(string[10 : 10 + CAN_FD_DLC[dlc] * 2]) + elif string[0] == "b": + # FD with bitrate switch + canId = int(string[1:4], 16) + dlc = int(string[4], 16) + isFd = True + fdBrs = True + data = bytearray.fromhex(string[5 : 5 + CAN_FD_DLC[dlc] * 2]) + elif string[0] == "B": + # FD extended with bitrate switch + canId = int(string[1:9], 16) + dlc = int(string[9], 16) + extended = True + isFd = True + fdBrs = True + data = bytearray.fromhex(string[10 : 10 + CAN_FD_DLC[dlc] * 2]) if canId is not None: msg = Message( @@ -275,6 +316,8 @@ def _recv_internal( is_extended_id=extended, timestamp=time.time(), # Better than nothing... is_remote_frame=remote, + is_fd=isFd, + bitrate_switch=fdBrs, dlc=dlc, data=data, ) @@ -289,6 +332,19 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: sendStr = f"R{msg.arbitration_id:08X}{msg.dlc:d}" else: sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}" + elif msg.is_fd: + if msg.bitrate_switch: + if msg.is_extended_id: + sendStr = f"B{msg.arbitration_id:08X}{msg.dlc:d}" + else: + sendStr = f"b{msg.arbitration_id:03X}{msg.dlc:d}" + sendStr += msg.data.hex().upper() + else: + if msg.is_extended_id: + sendStr = f"D{msg.arbitration_id:08X}{msg.dlc:d}" + else: + sendStr = f"d{msg.arbitration_id:03X}{msg.dlc:d}" + sendStr += msg.data.hex().upper() else: if msg.is_extended_id: sendStr = f"T{msg.arbitration_id:08X}{msg.dlc:d}" From c2a7dfc02c9acbd4dfdb3922ec072b6dcb3095f0 Mon Sep 17 00:00:00 2001 From: spencer Date: Tue, 22 Apr 2025 14:34:03 -0600 Subject: [PATCH 2/7] allow 0 data bitrate to allow non-FD settings --- can/interfaces/slcan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index aae7f538b..64564c4cb 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -49,6 +49,7 @@ class slcanBus(BusABC): 83300: "S9", } _DATA_BITRATES = { + 0: "", 2000000: "Y2", 5000000: "Y5", } From f9e8a3c295766554d59a85f8fdea22266ad5f031 Mon Sep 17 00:00:00 2001 From: ssj71 Date: Thu, 17 Jul 2025 22:16:36 -0700 Subject: [PATCH 3/7] make interface more consistent with other HW --- can/interfaces/slcan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 64564c4cb..1af63d07a 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -161,11 +161,11 @@ def __init__( super().__init__(channel, **kwargs) - def set_bitrate(self, bitrate: int, dbitrate: int = 0) -> None: + def set_bitrate(self, bitrate: int, data_bitrate: int = None) -> None: """ :param bitrate: Bitrate in bit/s - :param dbitrate: + :param data_bitrate: Data Bitrate in bit/s for FD frames :raise ValueError: if ``bitrate`` is not among the possible values @@ -175,8 +175,8 @@ def set_bitrate(self, bitrate: int, dbitrate: int = 0) -> None: else: bitrates = ", ".join(str(k) for k in self._BITRATES.keys()) raise ValueError(f"Invalid bitrate, choose one of {bitrates}.") - if dbitrate in self._DATA_BITRATES: - dbitrate_code = self._DATA_BITRATES[dbitrate] + if data_bitrate in self._DATA_BITRATES: + dbitrate_code = self._DATA_BITRATES[data_bitrate] else: dbitrates = ", ".join(str(k) for k in self._DATA_BITRATES.keys()) raise ValueError(f"Invalid data bitrate, choose one of {dbitrates}.") From 0246b7027c1954b6abde97c90025015e670af424 Mon Sep 17 00:00:00 2001 From: ssj71 Date: Fri, 18 Jul 2025 21:18:03 -0700 Subject: [PATCH 4/7] proper DLC handling for FD frames in slcan --- can/interfaces/slcan.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 1af63d07a..fbeaa5339 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -16,7 +16,7 @@ CanOperationError, error_check, ) -from can.util import CAN_FD_DLC, check_or_adjust_timing_clock, deprecated_args_alias +from can.util import CAN_FD_DLC, check_or_adjust_timing_clock, deprecated_args_alias, len2dlc logger = logging.getLogger(__name__) @@ -319,7 +319,7 @@ def _recv_internal( is_remote_frame=remote, is_fd=isFd, bitrate_switch=fdBrs, - dlc=dlc, + dlc=CAN_FD_DLC[dlc], data=data, ) return msg, False @@ -334,17 +334,18 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: else: sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}" elif msg.is_fd: + fd_dlc = len2dlc(msg.dlc) if msg.bitrate_switch: if msg.is_extended_id: - sendStr = f"B{msg.arbitration_id:08X}{msg.dlc:d}" + sendStr = f"B{msg.arbitration_id:08X}{fd_dlc:X}" else: - sendStr = f"b{msg.arbitration_id:03X}{msg.dlc:d}" + sendStr = f"b{msg.arbitration_id:03X}{fd_dlc:X}" sendStr += msg.data.hex().upper() else: if msg.is_extended_id: - sendStr = f"D{msg.arbitration_id:08X}{msg.dlc:d}" + sendStr = f"D{msg.arbitration_id:08X}{fd_dlc:X}" else: - sendStr = f"d{msg.arbitration_id:03X}{msg.dlc:d}" + sendStr = f"d{msg.arbitration_id:03X}{fd_dlc:X}" sendStr += msg.data.hex().upper() else: if msg.is_extended_id: From 460d74b81f8bc745ccdc0d1656935ff5e3c4791d Mon Sep 17 00:00:00 2001 From: ssj71 Date: Fri, 18 Jul 2025 21:18:27 -0700 Subject: [PATCH 5/7] adding tests for slcan FD support --- test/test_slcan.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/test/test_slcan.py b/test/test_slcan.py index 220a6d7e0..4dc859dac 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -178,6 +178,102 @@ def test_send_extended_remote(self): rx_msg = self.bus.recv(TIMEOUT) self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + def test_recv_fd(self): + self.serial.set_input_buffer(b"d123A303132333435363738393a3b3c3d3e3f\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.is_fd, True) + self.assertEqual(msg.bitrate_switch, False) + self.assertEqual(msg.dlc, 16) + self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + + def test_send_fd(self): + payload = b"d123A303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x123, is_extended_id=False, is_fd=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + + def test_recv_fd_extended(self): + self.serial.set_input_buffer(b"D12ABCDEFA303132333435363738393A3B3C3D3E3F\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 16) + self.assertEqual(msg.bitrate_switch, False) + self.assertTrue(msg.is_fd) + self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + + def test_send_fd_extended(self): + payload = b"D12ABCDEFA303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x12ABCDEF, is_extended_id=True, is_fd=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + + def test_recv_fd_brs(self): + self.serial.set_input_buffer(b"b123A303132333435363738393a3b3c3d3e3f\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.is_fd, True) + self.assertEqual(msg.bitrate_switch, True) + self.assertEqual(msg.dlc, 16) + self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + + def test_send_fd_brs(self): + payload = b"b123A303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x123, is_extended_id=False, is_fd=True, bitrate_switch=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + + def test_recv_fd_brs_extended(self): + self.serial.set_input_buffer(b"B12ABCDEFA303132333435363738393A3B3C3D3E3F\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 16) + self.assertEqual(msg.bitrate_switch, True) + self.assertTrue(msg.is_fd) + self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + + def test_send_fd_brs_extended(self): + payload = b"B12ABCDEFA303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x12ABCDEF, is_extended_id=True, is_fd=True, bitrate_switch=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + def test_partial_recv(self): self.serial.set_input_buffer(b"T12ABCDEF") msg = self.bus.recv(TIMEOUT) From f2f8cb1685fca5325e49e661e99aab8a3ab71919 Mon Sep 17 00:00:00 2001 From: ssj71 Date: Sat, 19 Jul 2025 22:55:42 -0700 Subject: [PATCH 6/7] black formatting --- can/interfaces/slcan.py | 7 +- test/test_slcan.py | 178 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 176 insertions(+), 9 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 1d220c016..491f2d6d9 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -16,7 +16,12 @@ CanOperationError, error_check, ) -from can.util import CAN_FD_DLC, check_or_adjust_timing_clock, deprecated_args_alias, len2dlc +from can.util import ( + CAN_FD_DLC, + check_or_adjust_timing_clock, + deprecated_args_alias, + len2dlc, +) logger = logging.getLogger(__name__) diff --git a/test/test_slcan.py b/test/test_slcan.py index 4dc859dac..491800e24 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -188,12 +188,52 @@ def test_recv_fd(self): self.assertEqual(msg.is_fd, True) self.assertEqual(msg.bitrate_switch, False) self.assertEqual(msg.dlc, 16) - self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) def test_send_fd(self): payload = b"d123A303132333435363738393A3B3C3D3E3F\r" msg = can.Message( - arbitration_id=0x123, is_extended_id=False, is_fd=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + arbitration_id=0x123, + is_extended_id=False, + is_fd=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], ) self.bus.send(msg) self.assertEqual(payload, self.serial.get_output_buffer()) @@ -212,12 +252,52 @@ def test_recv_fd_extended(self): self.assertEqual(msg.dlc, 16) self.assertEqual(msg.bitrate_switch, False) self.assertTrue(msg.is_fd) - self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) def test_send_fd_extended(self): payload = b"D12ABCDEFA303132333435363738393A3B3C3D3E3F\r" msg = can.Message( - arbitration_id=0x12ABCDEF, is_extended_id=True, is_fd=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + arbitration_id=0x12ABCDEF, + is_extended_id=True, + is_fd=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], ) self.bus.send(msg) self.assertEqual(payload, self.serial.get_output_buffer()) @@ -236,12 +316,53 @@ def test_recv_fd_brs(self): self.assertEqual(msg.is_fd, True) self.assertEqual(msg.bitrate_switch, True) self.assertEqual(msg.dlc, 16) - self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) def test_send_fd_brs(self): payload = b"b123A303132333435363738393A3B3C3D3E3F\r" msg = can.Message( - arbitration_id=0x123, is_extended_id=False, is_fd=True, bitrate_switch=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + arbitration_id=0x123, + is_extended_id=False, + is_fd=True, + bitrate_switch=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], ) self.bus.send(msg) self.assertEqual(payload, self.serial.get_output_buffer()) @@ -260,12 +381,53 @@ def test_recv_fd_brs_extended(self): self.assertEqual(msg.dlc, 16) self.assertEqual(msg.bitrate_switch, True) self.assertTrue(msg.is_fd) - self.assertSequenceEqual(msg.data, [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F]) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) def test_send_fd_brs_extended(self): payload = b"B12ABCDEFA303132333435363738393A3B3C3D3E3F\r" msg = can.Message( - arbitration_id=0x12ABCDEF, is_extended_id=True, is_fd=True, bitrate_switch=True, data=[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] + arbitration_id=0x12ABCDEF, + is_extended_id=True, + is_fd=True, + bitrate_switch=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], ) self.bus.send(msg) self.assertEqual(payload, self.serial.get_output_buffer()) From 3bb4ce5c802526a98b9430204ae947586ab7afff Mon Sep 17 00:00:00 2001 From: ssj71 Date: Mon, 21 Jul 2025 07:40:05 -0700 Subject: [PATCH 7/7] adding optional keyword to optional arg --- can/interfaces/slcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 491f2d6d9..01ba9c995 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -166,7 +166,7 @@ def __init__( super().__init__(channel, **kwargs) - def set_bitrate(self, bitrate: int, data_bitrate: int = None) -> None: + def set_bitrate(self, bitrate: int, data_bitrate: Optional[int] = None) -> None: """ :param bitrate: Bitrate in bit/s