4

I've been using python-can and cantools to transmit and receive CAN bus messages without any issue. But when I begin transmitting periodic messages while also trying to receive messages there seems to be some error. I'm not sure if there is a problem with how I am doing it or if it is simply not possible to perform simultaneous transmit and receive. When I attempt to do so, receive() returns with NoneType and I know this is incorrect because I have another device logging and reporting that the signal I am looking for does exist on the bus.

Is it possible to send and transmit simultaneously on the same bus using python-can?

Traceback:

 line 51, in tc_1
    if sig_min <= sig <= sig_max:
TypeError: '<=' not supported between instances of 'float' and 'NoneType'

Code:

tc_1() calls on receive() to look for a specific message on the bus and also calls on periodic_send() to periodically send messages on the bus. The error occurs right after I call periodic_send() and while I am trying to look for a message on the bus. It seems like once I start sending messages receive() returns None. I know this isn't a timeout issue as the error occurs in a few seconds and my timeout is 15 seconds. And I don't get the issue if I comment out periodic_send().

def tc_1(bus, num):
    message = can.Message(arbitration_id=0x300)
    sig = receive(bus, message, 'sig')

    if sig is not None:
        sig = float(sig)
        logging.info('Test Case #{}.0 - PASS'.format(num))

        if sig_min <= sig <= sig_max:
            logging.info('Test Case #{}.1 - PASS'.format(num))

            close_relay = can.Message(arbitration_id=0x303, data=[1])
            periodic_send(bus, close_relay, 0.01)

            then = time.time()
            print('Entering while loop for sig to go low...')
            while type(sig) == float and sig > sig_max:
                # Refresh sig message instance

                ### THIS IS WHERE THE PROBLEM OCCURS ###

                sig = receive(bus, message, 'sig')
                if sig is not None:
                    sig = float(sig)
                    print('while loop, pwr_en = {:.3f}'.format(sig))

                now = time.time()
                # Timeout if sig does not transition low
                if now > (then + relay_switch_time):
                    break

                # Verify the active voltage state of power enable
                if sig_min <= sig <= sig_max:
                    print('PASS')
                else:
                    print('FAIL')
        else:
            print('FAIL')
    else:
        print('FAIL')

    # Final clean-up and restore
    bus.stop_all_periodic_tasks()

def receive(bus, message, signal):
    # include dbc file
    db = cantools.db.load_file(dbc_path)
    msg = bus.recv(timeout=15)
    if msg is None:
        logging.warning("Failed to detect bus activity. Timeout occurred")
    if msg is not None and msg.arbitration_id == message.arbitration_id:
        message_data = db.decode_message(msg.arbitration_id, msg.data)
        signal_data = message_data.get(signal)
        return signal_data


def periodic_send(bus, message, period):
    try:
        bus.send_periodic(message, period)
    except can.CanError:
        print("Message NOT sent")
    finally:
        return

Research

In the docs they mention the class can.ThreadSafeBus() and that it "assumes that both send() and _recv_internal() of the underlying bus instance can be called simultaneously" The leads me to think I must use this class of bus instance in order to tx/rx messages at the same time. But I see the word threading and get a little intimidated.

UPDATE 1:

After some testing, it looks like the can.Bus instance can simultaneously transmit and receive messages. Its not actually "simultaneous" but there is no issue if you attempt to transmit and receive at the same time.

The issue that I am running into seems to be in how I am implementing the transmit and receive. Sometimes receive() returns None (see code below) and this becomes problematic when using that return value as in in a compare statement which results with: TypeError: '<=' not supported between instances of 'float' and 'NoneType'

1593644420.403561  - sig: 4.7067000000000005
1593644420.4893048 - sig: 4.752400000000001
1593644420.5660996 - sig: None
1593644420.7276666 - sig: 4.752400000000001
1593644420.8034878 - sig: 4.752400000000001
1593644420.8912296 - sig: 4.7067000000000005
1593644420.9899697 - sig: 4.752400000000001
1593644421.0677896 - sig: None
1593644421.2223675 - sig: 1.0053
1593644421.3011339 - sig: 0.31980000000000003
1593644421.3919268 - sig: 0.0913
Tim51
  • 141
  • 3
  • 13
  • Depends on what you mean with "simultaneously". The CAN bus hardware only allows one message to be sent at a time, bus-wide. – Lundin Jul 02 '20 at 07:51
  • @Lundin I was referring to the can.Bus() instances ability to handle both receiving and transmitting messages without one having to complete before the other is started. I do understand that the hardware (a typical single CAN transceiver) can only do one or the other at any given time – Tim51 Jul 02 '20 at 15:49
  • Well, CAN controllers do have both tx and rx buffers in the hardware, so you can usually queue up a number of messages depending on hardware. More advanced controllers uses "mailbox" features and then you don't need to queue at all. – Lundin Jul 03 '20 at 07:30

1 Answers1

1

The core issue here is how I was implementing my receive(). The built in recv() was what I used to origionally poll data from the bus:

def receive(bus, message, signal):
    # include dbc file
    db = cantools.db.load_file(dbc_path)
    msg = bus.recv(timeout=15)
    if msg is None:
        logging.warning("Failed to detect bus activity. Timeout occurred")
    if msg is not None and msg.arbitration_id == message.arbitration_id:
        message_data = db.decode_message(msg.arbitration_id, msg.data)
        signal_data = message_data.get(signal)
        return signal_data

The problem with this approach is that recv() will return None if the condition is not satisfied. So instead of using the recv() I've just polled the bus until the the message is located. This works for now but I'll need to add some exception and timeout handling too it in the event there is a problem with the bus or the message never arrives.

def receive(bus, message, in_signal):
    # include dbc file
    db = cantools.db.load_file(dbc_path)
    for msg in bus:
        if msg.arbitration_id == message.arbitration_id:
            message_data = db.decode_message(msg.arbitration_id, msg.data)
            signal_data = message_data.get(in_signal)
            return signal_data
Tim51
  • 141
  • 3
  • 13