如何解决socketcan J1939过滤器在python中的使用
在 Python 中,我尝试使用 linux 内核文档中提到的 J1939 过滤:https://www.kernel.org/doc/html/latest/networking/j1939.html
以下代码在 setsockopt() 行失败(设置过滤器):
import socket
import struct
def pack_J1939_filters(can_filters):
can_filter_fmt = "=" + "2Q2B2I" * len(can_filters)
filter_data = []
for can_filter in can_filters:
name = can_filter['name']
name_mask = can_filter['name_mask']
addr = can_filter['addr']
addr_mask = can_filter['addr_mask']
pgn = can_filter['pgn']
pgn_mask = can_filter['pgn_mask']
filter_data.append(name)
filter_data.append(name_mask)
filter_data.append(addr)
filter_data.append(addr_mask)
filter_data.append(pgn)
filter_data.append(pgn_mask)
return struct.pack(can_filter_fmt,*filter_data)
s = socket.socket(socket.PF_CAN,socket.SOCK_DGRAM,socket.CAN_J1939)
interface = "vcan0"
src_name = socket.J1939_NO_NAME
src_pgn = socket.J1939_NO_PGN
src_addr = 0x81
src_sck_addr = (interface,src_name,src_pgn,src_addr)
s.bind(src_sck_addr)
filters = [{"name": 0,"name_mask":0,"addr":0,"addr_mask":0,"pgn": 0,"pgn_mask": 0}]
packed_filters = pack_J1939_filters(filters)
# socket.SOL_CAN_J1939 does not seem to exist
SOL_CAN_BASE = 100
CAN_J1939 = 7
SOL_CAN_J1939 = SOL_CAN_BASE + CAN_J1939
s.setsockopt(SOL_CAN_J1939,socket.SO_J1939_FILTER,packed_filters)
s.recvfrom(128)
s.close()
首先,内核文档提到使用 SOL_CAN_J1939 作为第一个参数。但是 socket.SOL_CAN_J1939 在 socket 包中不存在。所以查看这个位置的代码,我能够理解这个 int 值应该是 107:http://socket-can.996257.n3.nabble.com/RFC-v3-0-6-CAN-add-SAE-J1939-protocol-td7571.html
至于 setsockopt() 第三个参数,我打包过滤器以匹配 j1939_filter 结构(26 字节,如上一个链接的代码中所述)。这类似于在 can.interfaces.socketcan.utils 中为原始 CAN 所做的。
我做错了什么导致setsockopt()失败?
解决方法
第一个问题是 struct.pack 格式 (can_filter_fmt) 错误。我首先假设内核 j1939_filter 结构大小是成员的总和。这是错误的,因为编译器添加了填充。这可以作为 x 添加到 struct.pack 格式中,例如 2Q2I2B6x。请参阅Why isn't sizeof for a struct equal to the sum of sizeof of each member?
第二个问题是 can_filter_fmt 不是打包为 2Q2B2I 而是打包为 2Q2I2B6x(addr 成员在中间)。
至于 SOL_CAN_J1939 我是正确的,需要在文件中创建,因为它还没有在包中。
最终代码如下:
#!/usr/bin/env python3
import socket
import struct
def pack_J1939_filters(can_filters=None):
if can_filters is None:
# Pass all messages
can_filters = [{}]
can_filter_fmt = "=" + "2Q2I2B6x" * len(can_filters)
filter_data = []
for can_filter in can_filters:
if 'name' in can_filter:
name = can_filter['name']
else:
name = 0
if 'name_mask' in can_filter:
name_mask = can_filter['name_mask']
else:
name_mask = 0
if 'pgn' in can_filter:
pgn = can_filter['pgn']
else:
pgn = 0
if 'pgn_mask' in can_filter:
pgn_mask = can_filter['pgn_mask']
else:
pgn_mask = 0
if 'addr' in can_filter:
addr = can_filter['addr']
else:
addr = 0
if 'addr_mask' in can_filter:
addr_mask = can_filter['addr_mask']
else:
addr_mask = 0
filter_data.append(name)
filter_data.append(name_mask)
filter_data.append(pgn)
filter_data.append(pgn_mask)
filter_data.append(addr)
filter_data.append(addr_mask)
return struct.pack(can_filter_fmt,*filter_data)
def print_msg(data,sck_addr):
print(f"SA:{hex(sck_addr[3])} PGN:{hex(sck_addr[2])}")
for j in range(len(data)):
if j % 8 == 0 and j != 0:
print()
if j % 8 == 0:
print(f"bytes {j} to {j+7}: ",end="")
print(f"{hex(data[j])} ",end="")
print()
print()
def main():
s = socket.socket(socket.PF_CAN,socket.SOCK_DGRAM,socket.CAN_J1939)
# allows to receive broadcast messages
s.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
interface = "vcan0"
src_name = socket.J1939_NO_NAME
src_pgn = socket.J1939_NO_PGN # always no PGN for source,unless filtering is needed
src_addr = 0x81 # recvfrom() will not return destination specific messages for other addresses
src_sck_addr = (interface,src_name,src_pgn,src_addr)
s.bind(src_sck_addr)
packed_filters = pack_J1939_filters()
SOL_CAN_BASE = 100
CAN_J1939 = 7
SOL_CAN_J1939 = SOL_CAN_BASE + CAN_J1939
s.setsockopt(SOL_CAN_J1939,socket.SO_J1939_FILTER,packed_filters)
(recv_data,recv_sck_addr) = s.recvfrom(128)
print_msg(recv_data,recv_sck_addr)
s.close()
if __name__ == "__main__":
main()
谢谢。
,要使 J1939 与 SocketCAN 一起使用,您需要做两件事:
- 内核 5.4+
- can-j1939 内核模块已启用
测试 can-1939:
如果你安装了 can-utils 并且在 sudo modprobe can-j1939 之后你得到的是致命错误,或者如果你从 can-utils 启动 testj1939 并且你得到不支持协议的错误,那么这意味着 can-j1939 是未在您的内核中启用,您需要手动编译它。
以下是我在 Debian 10 内核中启用 can-j1939 的说明:
https://github.com/linux-can/can-utils/blob/master/can-j1939-install-kernel-module.md
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。