IXXAT: does not recover / no exceptions after CAN bus disconneted for a few seconds
Describe the bug
When I unplug the CAN bus (physically pull the connector), after a few seconds, the underlying Ixxat driver is raising an exception raise VCIError("Error warning limit exceeded") that I see in the output but cannot catch in any way.
After that, the bus seems dead (no receive, no transmit). It does not recover after the physical bus is reconnected.
The bus object is a can.TreadSafeBus().
Strangely, the bus's property .state is still can.BusState.ACTIVE, and I can happily call send() (without effect, of course). Unfortunately, it does not return a status code (the error?), nor is an exception thrown.
I tried to catch: can.CanError, can.CanOperationError, can.interfaces.ixxat.exceptions.VCIError
I tried to add a custom Listener on the Notifier with method on_error(), but this is not called (in contrast to on_message_received(), so the Listener basically works).
To Reproduce
some code lines from the CanBus class:
def __init__ ...
can.rc = can.util.load_file_config(config_file)
self._listener = MyListener(receive_handler)
self._bus = can.ThreadSafeBus()
...
loop = asyncio.get_event_loop()
self._notifier = can.Notifier(self._bus, [self._listener], loop=loop)
async def send_can( self, message: can.Message ) -> bool:
print( "Bus state: " + str(self._bus.state))
if self._bus.state == can.BusState.ACTIVE:
self._bus.send( message )
return True
else:
return False
the Listener:
class MyListener(can.Listener):
def __init__(self, receive_handler):
self._receive_handler = receive_handler
def __call__(self, msg: can.Message):
return self.on_message_received(msg)
async def on_message_received(self, msg):
await self._receive_handler(msg)
async def on_error(exc):
print( "!!!!!!!!! Listener exception" )
Expected behavior
My assumptions were:
- nice if the driver would recover on its own once the CAN is reconnected
- nice if I could somehow retrieve the error state in any way (return value, exception, status call). Then I could -- somehow -- actively reset the interface. Don't know how, though...
- is that an Ixxat related issue or a general one?
Additional context
OS and version: Windows 10 Python version: 3.12.4 python-can version: 4.4.2 python-can interface/s (if applicable): IXXAT
Traceback and logs
This is what I get:Other (unknown) CAN error
CAN message flags bAddFlags/bFlags2 0x00 bflags 0x03
Other (unknown) CAN error
CAN message flags bAddFlags/bFlags2 0x00 bflags 0x03
Other (unknown) CAN error
... (lots of these messages)
CAN message flags bAddFlags/bFlags2 0x00 bflags 0x03
Exception in thread can.notifier for bus "unknown":
Traceback (most recent call last):
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1073, in _bootstrap_inner
self.run()
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1010, in run
Exception in callback Notifier._on_error(VCIError('Err...mit exceeded'))
handle: <Handle Notifier._on_error(VCIError('Err...mit exceeded'))>
Traceback (most recent call last):
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\asyncio\events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\site-packages\can\notifier.py", line 163, in _on_error
listener.on_error(exc)
TypeError: MyListener.on_error() takes 1 positional argument but 2 were given
self._target(*self._args, **self._kwargs)
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\site-packages\can\notifier.py", line 126, in _rx_thread
if msg := bus.recv(self.timeout):
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\site-packages\can\thread_safe_bus.py", line 53, in recv
return self.__wrapped__.recv(timeout=timeout, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\site-packages\can\bus.py", line 127, in recv
msg, already_filtered = self._recv_internal(timeout=time_left)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\site-packages\can\interfaces\ixxat\canlib.py", line 148, in _recv_internal
return self.bus._recv_internal(timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\Lib\site-packages\can\interfaces\ixxat\canlib_vcinpl.py", line 731, in _recv_internal
raise VCIError("Error warning limit exceeded")
can.interfaces.ixxat.exceptions.VCIError: Error warning limit exceeded
... (next bus.send() call:)
Bus state: BusState.ACTIVE
on_error was indeed called -- midway down your log, you can see the line
TypeError: MyListener.on_error() takes 1 positional argument but 2 were given
Upon reviewing your code, the on_error method is missing the class instance arg (self). Provide this and you should be able to successfully handle the exception.
Also, since you're using asyncio, you may just want to set the method loop.set_exception_handler(handle_exception)
Thank you very much! I obviously did not see the forrest due to all these trees...
Yes, on_error() in now called. First I tried to reset the bus from there via a callback, which lead to another exception but did not solve the problem.
So it turned out that I also need loop's exception handler which catches that second exception from where I can gracefully shut down the application and let upper levels restart.
In short: Listener's on_error() is called upon the driver error, then throws another exception (without the callback needed) to trigger loop's exception handler.