Skip to content

CAN 总线驱动 ​

dc3-driver-can 把 CAN 总线设备接入 IoT DC3:作为总线上的一个节点,监听 SocketCAN 接口上匹配指定 CAN ID 的帧并按位号配置解析为采集值,必要时先发"请求帧"再读"应答帧",并通过命令帧写值。读完本页,你能在一台 CAN 设备上配好驱动属性与位号属性,并判断哪些行为已就位、哪些仍是骨架。

协议背景 ​

CAN(Controller Area Network,控制器局域网)是汽车与工业自动化里使用极广的现场总线,在物联网四层架构里属于网络层 ——它定义了设备之间怎么在一根总线上可靠地交换帧。

CAN 与 Modbus 这类主从协议最大的不同在于它是广播 + 按 ID 过滤的发布-订阅模型:

  • 节点不靠地址点对点通信,而是把带 CAN ID(标识符)的帧广播到总线上,所有节点都能收到,由接收方按自己关心的 CAN ID 过滤。
  • 一帧最多携带 8 字节载荷;要表达的物理量按字节偏移、长度、字节序从这 8 字节里切分出来。
  • 标准帧用 11 位 CAN ID,扩展帧用 29 位 CAN ID,二者由帧格式标志区分。
  • 没有中心轮询器,天然适合多接收方、事件驱动的场景:值变就发,谁关心谁收。

典型用途包括车载 ECU、电池管理系统(BMS)、伺服驱动器、各类传感器节点,以及越来越多的工业嵌入式控制器。在 Linux 上,CAN 设备通常以 SocketCAN 网络接口(如 can0)的形式呈现,应用通过它收发帧——本驱动正是接到这层接口上工作。

CAN 在物联网网络层的位置

CAN 解决的是"同一根总线上的节点如何交换帧",属于现场总线(网络层)范畴。它和 Modbus、Profibus、BACnet 等并列;和这些协议如何在四层架构里取舍,见工业总线与协议。

属性配置 ​

CAN 设备的接入配置分三层填写:设备级 driver-attribute(接口、波特率、帧格式),位号级 point-attribute(CAN ID、字节切分、可选请求帧),以及可写位号的 command-attribute(写目标与帧数据模板)。下表均来自驱动 application.yml ,先读散文理解每项作用,再照表填值。

驱动属性(设备级 driver-attribute) ​

接入一台 CAN 设备时,在设备上填这些属性。 interfaceName 指向驱动进程所在 Linux 主机上的 SocketCAN 接口名,是必填项(其它属性都建立在它之上);bitrate 与 frameFormat 描述总线本身的物理与帧格式特征,必须与设备实际一致。

属性code类型默认值说明
InterfaceinterfaceNameSTRINGcan0SocketCAN 接口名,驱动据此收发帧
BitratebitrateINT500000CAN 总线波特率(bps),需与总线一致
Frame FormatframeFormatSTRINGSTANDARD帧格式:STANDARD(11bit) 或 EXTENDED(29bit)

位号属性(point-attribute) ​

每个采集位号上填以下属性。前五项决定" 匹配哪一帧、从载荷哪几个字节取值";其中 dataOffset/dataLength 已参与切片,dataFormat/byteOrder 的解析尚未接通(见下方告警)。后两项 requestCanId/requestData 是可选的"先请求后应答" 机制,留空则纯被动监听。

属性code类型默认值说明
CAN IDcanIdSTRING(空)要匹配的 CAN 标识符(十六进制,不带 0x)
Data OffsetdataOffsetINT0帧载荷内的起始字节偏移
Data LengthdataLengthINT1从偏移处读取的字节数
Data FormatdataFormatSTRINGINT解析格式:INT/UINT/HEX
Byte OrderbyteOrderSTRINGLITTLE多字节的字节序(如 LITTLE)
Request CAN IDrequestCanIdSTRING(空)可选请求帧的 CAN ID
Request DatarequestDataSTRING(空)可选请求帧的载荷(十六进制)

主动请求型读取

不少 CAN 设备需要先收到一帧"请求"才会应答数据。源码中,只有 requestCanId 与 requestData 同时非空时,驱动才会在采集前用 cansend 发一帧请求(形如 cansend can0 <requestCanId>#<requestData>),再监听 canId 匹配的应答帧;两者留空则纯被动监听总线上周期广播的帧。

dataFormat / byteOrder 当前未参与解析(dataOffset / dataLength 已参与切片)

按源码,read() 用 interfaceName、canId、requestCanId/requestData 抓帧后,会按 dataOffset / dataLength 从载荷里切出一段字节、以连续十六进制串作为采集值返回;但 dataFormat、byteOrder 尚未在读取路径里做类型转换/字节序处理——不管填 INT/UINT 还是 HEX,拿到的都是该切片的十六进制原文(属骨架待补部分)。配置项已就位,行为以后续实现为准。

写命令属性(command-attribute) ​

application.yml 在 command-attribute 下声明了 canId 与 data(data 默认 ${value}),原意是让写命令携带帧数据模板。

data 模板当前不会生效(骨架待补)

按源码,write() 只从位号属性(pointConfig)读取 canId 与 data: canId = getConfigValue(pointConfig, "canId", "")、data = getConfigValue(pointConfig, "data", "")。但 point-attribute 里没有 data(data 只声明在 command-attribute 下),而 command-attribute 仅经 DriverCommand.execute(commandConfig, …) 这条路传入——CAN 驱动并未覆写 execute()(用默认空实现),所以写路径根本读不到 command-attribute。结果是 data 恒取默认空串、frameData 恒为空,${value} 模板不会被渲染,cansend 发出的载荷为空(形如 cansend can0 <canId>#)。这与 dataFormat/byteOrder 同属"骨架待补"部分,下发的写值当前不会真正落到帧载荷里。

属性code类型默认值说明
CAN IDcanIdSTRING(空)写入目标的 CAN 标识符(十六进制)
DatadataSTRING${value}帧数据模板(设计上用命令参数渲染 ${value},当前未接通,见上方告警)

采集与健康 ​

  • 采集周期:read 调度默认 cron 0/30 * * * * ?(每 30 秒抓一轮帧);驱动另有 custom 自定义调度默认 cron 0/5 * * * * ?,但 CAN 驱动的 schedule() 为空实现,未挂自定义任务。
  • 健康/在线:设备健康检查默认 cron 0/15 * * * * ?,租约超时 45 秒——驱动用 ip link show <interfaceName> 判断接口是否存在(退出码 0 即在线),在线状态机制见设备。
最小接入示例

把 can0 上一个周期广播温度、CAN ID 为 123 的节点接进来:

  1. 选 CAN Bus Driver 创建设备,driver 属性填 interfaceName=can0、bitrate=500000、 frameFormat=STANDARD。
  2. 给设备绑定的模板加一个温度位号(READ_ONLY ),point 属性填 canId=123、dataOffset=0、dataLength=2、dataFormat=INT、byteOrder=LITTLE,requestCanId/ requestData 留空(被动监听)。
  3. 启动驱动,30 秒内就能在位号值里看到采集值。

故障排查 ​

  • 驱动必须跑在装了 can-utils 的 Linux 上。底层读写依赖 candump/cansend,健康检查依赖 ip link show,且要求一个可用的 SocketCAN 接口。命令通过 sh -c 执行:在 macOS/Windows 或缺少 can-utils 时,candump 无输出,read() 会因 output.isEmpty() 抛 No CAN frame received 读异常(ReadPointException);写侧 cansend 缺失则因 executeCommand 的退出码/超时进入 WritePointException,设备一直离线。
  • 设备一直离线。健康检查实质是 ip link show <interfaceName> 的退出码:接口名写错、接口未 up、或进程无权限访问该接口,都会让退出码非 0 而判离线。先在主机上手动跑 ip link show can0 确认接口存在且 UP。
  • 采到 No CAN frame received。candump 用 timeout 3 抓单帧,3 秒内没等到匹配 canId 的帧就抛读异常。排查方向: canId 写错(大小写/进制)、frameFormat 与设备实际帧格式(11/29 位)不一致、设备本就需要先收到请求帧——后者要配上 requestCanId/requestData。
  • canId 写法要对。canId/requestCanId 按 can-utils 的写法填十六进制、不带 0x 前缀(标准帧如 123,扩展帧按其 29 位十六进制原文填)。带前缀或写成十进制会匹配不到帧。
  • 波特率/帧格式不匹配。bitrate 与 frameFormat 必须与总线和设备一致;总线波特率配错会导致整条总线收不到任何帧,表现为持续 No CAN frame received。
  • 写值没生效。当前写路径未真正接通:write() 从位号属性读取 data,而 data 仅声明在 command-attribute、且 execute() 未实现,因此 data 恒为空、${value} 不被渲染,cansend 发出的是空载荷帧(cansend can0 <canId># ),设备收不到预期数据——这属于"骨架待补",不是配置问题。即便如此仍可先确认目标 canId 是否正确、该位号是否为可写( rwFlag)。

在 IoT DC3 中如何落地 ​

  • dc3.driver.code:CanDriver(驱动名 CAN Bus Driver,类型 DRIVER_CLIENT,主动在总线上收发帧)。这是稳定的路由标识,不可随意更改。
  • 读能力:✓ 支持。read() 通过 candump 抓取匹配 canId 的单帧,按 dataOffset/dataLength 切出载荷切片(十六进制串) 返回,可选先用 cansend 发请求帧。
  • 写能力:桩/部分实现。write() 已能调 cansend 把帧发上总线,但 data 帧数据模板当前未接通(data 取自 pointConfig 却只声明于 command-attribute、execute() 未实现),故 ${value} 不被渲染、当前发出的是空载荷帧(详见上方" 写命令属性"告警)。
  • 订阅能力:— 不支持。CAN 在本驱动里是请求-响应式的定时主动读,并非把订阅推送接进 DC3。以上与驱动能力矩阵中 CAN 行(✓ 读 / — 写 / — 订阅)一致。

实现状态:骨架(读已可用、写未接通),底层走 can-utils

该驱动是一个起步模板。read()/write() 通过 ProcessBuilder 调用 Linux can-utils(candump/cansend)完成收发, health() 用 ip link show 检查接口——这些调用路径在装了 can-utils 的 Linux + 真实 SocketCAN 接口上能真正执行,而非抛" 未实现"。但仍有未接通处:

  • 读路径:dataOffset/dataLength 已参与字节切片(返回连续十六进制串);dataFormat/byteOrder 尚未参与类型转换与字节序处理,读到的始终是切片的十六进制原文。
  • 写路径:write() 的 data 帧数据模板渲染当前未真正接通——data 取自 pointConfig 但仅声明于 command-attribute ,且 execute() 未实现,故 ${value} 不被渲染、当前发出的是空载荷帧。与 dataFormat 等同属待补骨架,不要把 write 当作已可写值。
  • 原生 SocketCAN JNI 曾被评估但有意不采用:每次起进程的开销相对本驱动低频工业采样场景可接受,引入 JNI 交叉编译不划算(源码类注释为据)。生产前仍需补齐格式/字节序解析与写值模板接通。

延伸阅读 ​

基于 AGPL-3.0 协议发布