Skip to content

slcan doesn't receive corresponding response after sending can message. #2060

@earthPerson-001

Description

@earthPerson-001

Describe the bug

While calling slcanBus.send, the corresponding response isn't read. This makes it report false success even on failed messages.

To Reproduce

Setup two can devices, both configured to send ack on successful can message receive. Disconnect the can wires after some time.

Expected behavior

Expected to receive ack when wire is connected (slcan should report successful transmission)
Expected to receive nack(bel) when wire is disconnected (slcan should report failed transmission)

Additional context

OS and version: Ubuntu 24.04.3 LTS
Python version: Python 3.11.14
python-can version: 4.6.1
python-can interface/s: slcan with custom slcan device (esp32s3)

Details The following implementation solves that problem (introduces other ones)
import can
from can.interfaces.slcan import slcanBus
import threading


class SLCANBusWithErrorCheck(slcanBus):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._tx_lock = threading.Lock()

    def send(self, msg, timeout=None):
        if timeout != self.serialPortOrig.write_timeout:
            self.serialPortOrig.write_timeout = timeout

        if msg.is_remote_frame:
            if msg.is_extended_id:
                sendStr = f"R{msg.arbitration_id:08X}{msg.dlc:d}"
            else:
                sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}"
        else:
            if msg.is_extended_id:
                sendStr = f"T{msg.arbitration_id:08X}{msg.dlc:d}"
            else:
                sendStr = f"t{msg.arbitration_id:03X}{msg.dlc:d}"
            sendStr += msg.data.hex().upper()

        with self._tx_lock:
            # Drain any leftover bytes from a previous error before sending
            self.serialPortOrig.reset_input_buffer()
            self._buffer.clear()

            self._write(sendStr)
            response = self._read(timeout=max(timeout or 0, 2.0))

        if response is None:
            raise can.CanError("TX timeout - no response from adapter")
        if self._ERROR.decode() in response:
            raise can.CanError("TX failed - BEL received (no ACK on bus)")

    def recv(self, timeout=None):
        if self._tx_lock.locked():
            return None
        return super().recv(timeout=timeout)

I know following are the errors in the provided solution:

  • If we are mid receive, then any call to send can create race condition (clear the serial buffers mid receive)
  • the timeout logic is also incorrect (additional 2.0 second for timeout < 2.0 second, patch for some other problem)
  • Receive fails immediately if transmission is going on

Any solution i came up for these problems seemed patchy as best. So, expecting it to solve inside of python-can itself or provide a better solution than locking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions