pythonraspberry-pilistenercan-buspython-can

Problem configuring a Python-Can listener


I'm relatively new to python and am attempting to make a new program to interface with a canbus. I've been working my way through the documentation and some examples for python-can using the SocketCAN interface and I'm running into some issues.

I've confirmed the hardware works appropriately. I have a separate device to transmit a test-pattern onto the CANbus and when using a default listener "can.Printer()" the expected messages being transmitted onto the CANbus are correctly printed in terminal.

My next step was to create a new listener function that will ultimately process the received messages from the CANbus (the msg_rx_routine function in my code). When using the code I have now, usually the first received CAN message gets printed, but then I a callback error of "non type" object is not callable.

My hardware: -rPi CM4 + I/O expension board -MCP2515 breakout board

The test code:

import can
import time
import os

#--------define support functions
"""
FUNCTION:       message receive routine
@ARG can_bus:   passed can object
RETURN:         none
"""
def msg_rx_routine(can_bus):
    rx_msg = can_bus.recv()
    print(rx_msg)

#--------CANBUS init
os.system("sudo /sbin/ip link set can0 up type can bitrate 500000")         #bring up can0 interface at 500kbps
time.sleep(0.05)                                                            #brief pause
CAN1 = can.interface.Bus(channel='can0', bustype='socketcan')               #instance CAN object
#TODO: configure CAN connection (masks, etc)
#CAN1_listener = can.Printer()                                               #assign default "printer" listener to print to terminal
CAN1_listener = msg_rx_routine(CAN1)                                        #assign message handler function as a listener
CAN1_notifier = can.Notifier(CAN1, [CAN1_listener])                         #assign listener to notifier


def main():
    while True:
        pass        #just dummy loop to wait on a break command

if __name__ == "__main__":
    main()

The resultant error: Console Error Output

I feel like I'm obviously misunderstanding something at the core concept of the "listener/notifier" functionality of this works and it's got me stumped. How I'm (trying) to understand this now would be like how an IRQ request in C-code for a micro-contoller would work. Given the interrupt (notifier) it will call some linked function (listener).

As previously stated, what I'm trying to achieve is: -wait for message -on message receive, call function -function does something with the message

I see there's a similar discussion here where someone is doing the same thing. I (think) they're passing the parse_data function just the can data type, correct? It doesn't make sense to me though why you'd pass just the generic can type to the function, when it would make more sense to say WHICH bus you're looking for the message on. I'm not sure if that's just me mis-understanding something or non-unique variable naming in many of the examples I've found.

EDIT-1:

After doing some more reading, I realize I mis-understood the original code in a pretty "doh" moment. The part CAN1_listener = can.Printer() in the code was just creating a new object, of the can.Printer class, which was then being assigned to the notifier. I've tried to make my own class to basically replicate that but I'm still having issues:

class can_msg_handler:
    def __init__(   self, **kwargs) -> None:
        pass
    def on_message_reveived(self, msg: can.Message) -> None:
        print(msg)

when using the new class with CAN1_listener = can_msg_handler() I now get a "object is not callable" error. Obviously more to read/learn with py so I'll keep at it.

EDIT-2: updating the function as @mkreiger1 and @MSpiller suggested to the below and "attempt 2" works as intended. I'm still a bit confused as to why, since in a C/C++ sense you'd still have to input the arguments but it works. I'll try the class update and post back as well.

import can
import time
import os

#--------define support functions
"""
FUNCTION:       message receive routine
@ARG can_bus:   passed can object
RETURN:         none
"""
def msg_rx_routine(rx_msg):
    print(rx_msg)

class can_msg_handler:
    def __init__(   self, **kwargs) -> None:
        pass
    def on_message_reveived(self, msg: can.message) -> None:
        print(msg)

#--------CANBUS init
os.system("sudo /sbin/ip link set can0 up type can bitrate 500000")         #bring up can0 interface at 500kbps
time.sleep(0.05)                                                            #brief pause
CAN1 = can.interface.Bus(channel='can0', bustype='socketcan')               #instance CAN object
#TODO: configure CAN connection (masks, etc)
#CAN1_listener = can.Printer()                                              #attempt 1: packaged listener to print to terminal
CAN1_listener = msg_rx_routine                                              #attempt 2: call function when new message
#CAN1_listener = can_msg_handler()                                          #attempt 3: call function in class when new message
CAN1_notifier = can.Notifier(CAN1, [CAN1_listener])                         #assign listener to notifier

def main():
    while True:
        pass        #just dummy loop to wait on a break command

if __name__ == "__main__":
    main()

Solution

  • As described in the documentation of can.Notifier each listener has to be either an instance of can.Listener or a callable which takes a can.Message as first argument.

    You could use your class can_msg_handler by making it inherit from can.Listener:

    class can_msg_handler(can.Listener):
        def __init__(   self, **kwargs) -> None:
            pass
        def on_message_received(self, msg: can.Message) -> None:
            print(msg)
    
    ...
    CAN1_listener = can_msg_handler
    

    or you pass the function msg_rx_routine as a listener, as @ mkrieger1 has pointed out:

    CAN1_listener = msg_rx_routine