【问题标题】:How to send and receive CAN messages between 2 Linux machines without any CAN hardware?如何在没有任何 CAN 硬件的 2 台 Linux 机器之间发送和接收 CAN 消息?
【发布时间】:2020-07-30 00:18:24
【问题描述】:

我一直在尝试在没有任何 CAN 硬件的情况下在我的笔记本电脑 (Ubuntu 20 Virtualbox) 和 Raspberry Pi (Ubuntu 20) 之间建立 CAN 通信,因为在模拟环境中不会收到 CAN 消息。我想通过 wifi 或 USB 将 CAN 数据作为有效负载发送。然后我的python模拟环境应该能够将这些解释为CAN消息并适当地转发。

我从socketcan 尝试过vcan,但它只能在同一Linux 机器的2 个终端之间工作。有人建议我查看slcan。除了使用实际的 CAN 硬件之外,似乎没有其他选择。我在任何地方都找不到任何教程或任何其他帮助。

如果有人能建议如何在没有任何 CAN 硬件的情况下通过 wifi 或 USB 在 2 台 Linux 机器之间发送和接收 CAN 消息,我将不胜感激?

我试过的Python源代码:

import sys
import socket
import argparse
import struct
import errno


class CANSocket(object):
  FORMAT = "<IB3x8s"
  FD_FORMAT = "<IB3x64s"
  CAN_RAW_FD_FRAMES = 5

  def __init__(self, interface=None):
    self.sock = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
    if interface is not None:
      self.bind(interface)

  def bind(self, interface):
    self.sock.bind((interface,))
    self.sock.setsockopt(socket.SOL_CAN_RAW, self.CAN_RAW_FD_FRAMES, 1)

  def send(self, cob_id, data, flags=0):
    cob_id = cob_id | flags
    can_pkt = struct.pack(self.FORMAT, cob_id, len(data), data)
    self.sock.send(can_pkt)

  def recv(self, flags=0):
    can_pkt = self.sock.recv(72)

    if len(can_pkt) == 16:
      cob_id, length, data = struct.unpack(self.FORMAT, can_pkt)
    else:
      cob_id, length, data = struct.unpack(self.FD_FORMAT, can_pkt)

    cob_id &= socket.CAN_EFF_MASK
    return (cob_id, data[:length])


def format_data(data):
    return ''.join([hex(byte)[2:] for byte in data])


def generate_bytes(hex_string):
    if len(hex_string) % 2 != 0:
      hex_string = "0" + hex_string

    int_array = []
    for i in range(0, len(hex_string), 2):
        int_array.append(int(hex_string[i:i+2], 16))

    return bytes(int_array)


def send_cmd(args):
    try:
      s = CANSocket(args.interface)
    except OSError as e:
      sys.stderr.write('Could not send on interface {0}\n'.format(args.interface))
      sys.exit(e.errno)

    try:
      cob_id = int(args.cob_id, 16)
    except ValueError:
      sys.stderr.write('Invalid cob-id {0}\n'.format(args.cob_id))
      sys.exit(errno.EINVAL)

    s.send(cob_id, generate_bytes(args.body), socket.CAN_EFF_FLAG if args.extended_id else 0)


def listen_cmd(args):
    try:
      s = CANSocket(args.interface)
    except OSError as e:
      sys.stderr.write('Could not listen on interface {0}\n'.format(args.interface))
      sys.exit(e.errno)

    print('Listening on {0}'.format(args.interface))

    while True:
        cob_id, data = s.recv()
        print('%s %03x#%s' % (args.interface, cob_id, format_data(data)))


def parse_args():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    send_parser = subparsers.add_parser('send', help='send a CAN packet')
    send_parser.add_argument('interface', type=str, help='interface name (e.g. vcan0)')
    send_parser.add_argument('cob_id', type=str, help='hexadecimal COB-ID (e.g. 10a)')
    send_parser.add_argument('body', type=str, nargs='?', default='',
      help='hexadecimal msg body up to 8 bytes long (e.g. 00af0142fe)')
    send_parser.add_argument('-e', '--extended-id', action='store_true', default=False,
      help='use extended (29 bit) COB-ID')
    send_parser.set_defaults(func=send_cmd)

    listen_parser = subparsers.add_parser('listen', help='listen for and print CAN packets')
    listen_parser.add_argument('interface', type=str, help='interface name (e.g. vcan0)')
    listen_parser.set_defaults(func=listen_cmd)

    return parser.parse_args()


def main():
    args = parse_args()
    args.func(args)


if __name__ == '__main__':
    main()

【问题讨论】:

  • 不确定你想做什么。为什么不简单地在两台机器之间打开一个 TCP 套接字并发送日期呢? TCP 协议不关心负载是 CAN 消息还是其他。
  • 好的。我正在为 Pi 代表汽车 ECU 的特定汽车用例构建一个虚拟平台。这就是我必须使用 CAN 的原因,因为 ECU 之间通过 CAN 进行交互。
  • 仍然没有得到它。是的,ECU 交换数据;是的,他们可能在车上使用 CAN。你想模拟什么?交换的数据?时机? CAN 协议使用定义明确的物理层,如果没有适当的硬件,您将无法对其进行仿真。那么你将模拟什么?
  • 我将模拟防抱死制动系统用例。 1 RPI 应通过 CAN 将车速值发送到笔记本电脑。
  • 但为什么一定要“通过 CAN”。如果要模拟制动系统,为什么发送速度值还不够?

标签: python linux usb wifi can-bus


【解决方案1】:

CAN 是一个广播 协议,而不是像 TCP 那样的连接协议或像 UDP 这样的数据报协议。但是,如果您想要抽象硬件但仍然模拟 CAN 的更高层行为,我建议最好的方法是使用 UDP(但@M. Spiller 建议的 TCP 也可以工作,只是意味着处理接受连接您不必为 UDP 烦恼),并以与 CAN 帧相同的字节结构发送和接收数据包。您还需要考虑过滤器错误帧

如果你打算在 linux 上使用 socketcan,你的 UDP 数据包应该是这样的:

struct {
    uint16_t id;       /* CAN ID of the frame */
    uint8_t dlc;       /* frame payload length in bytes */
    uint8_t data[8];   /* CAN frame payload */
} simulated_udp_can_frame;

您应该阅读socketCAN kernel documentation 了解更多详情。

【讨论】:

  • 因此,如果没有 CAN 硬件,您将错过很多您需要做的事情,例如设置比特率等。但是您可以“假装”CAN 硬件在那里,并且您正在从一端向另一端发送。但实际上您将通过 UDP 发送它们。帧的重要之处在于设置过滤器、消息 ID 和位顺序,因为这对于真正的 CAN 软件至关重要。您可以从 socketCAN 文档中了解这一点。您可以编写代码,使这些东西:过滤器、帧和 id 与实际硬件用途相同。
  • 是的。 CAN 套接字是这样的: socket( PF_CAN, SOCK_RAW, CAN_RAW );一个 UDP 套接字是 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);但它们都是插座。使用 CAN 插座,您可以读写;使用 UDP 发送和接收;但如前所述,数据的布局可以相同。如果使用相同的 wifi,则 2 台 linux 机器应该在同一个子网中,或者至少每台机器上的 ip 地址应该可以访问到另一台。
  • 如果您没有 CAN 硬件,您将无法获得 CAN 插座。套接字由内核创建,需要一个驱动程序和一个 ip 接口,称为 can0 或类似 eth0 的那种接口。
  • 对于您所列出的情况,在两台没有 CAN 硬件的计算机之间;是的,完全。使用UDP并假装。如果您可以在一台计算机上演示,则可以使用 vcan。如果您在一台 PC 上插入了 CAN 硬件或 CAN 仿真器,那么您就可以使用 CAN 插座。但是两台计算机之间没有硬件?您必须假装,而 UDP 是最简单的方法。
  • 兄弟。我在这里猜测,但我认为你是 CAN 的新手。从应用软件的角度来看(我从事汽车硬件/软件设计工作),关于 CAN,你必须了解的是它有一个 BIT面向消息结构,即您必须根据位而不是字节来考虑数据。从关于 CAN 的 Wikipedia 文章开始,尤其是关于框架布局的部分。
猜你喜欢
  • 2015-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多