# Linux ZLG 车辆状态接入 当前工程可以通过 ZLG Linux VCI SDK 使用 USBCANFD-200U 的 CAN0/CAN1,并把 车辆状态送入 `/api/hardware/vehicle-state` 和前端整车孪生。只读配置使用独立 `monitor-only` 桥,桥进程会拒绝所有发送请求;有线控制配置则在一次请求内由 同一桥进程共同持有 CAN0/CAN1,完成安全预检、控制和反馈后关闭。Linux 只读 状态模式的前端刷新周期为 500 ms;缓存只保留 3 秒内的新鲜反馈, 断线后会自动关闭旧桥并重试连接。监听通道只接受本文列出的 12 个标准帧 ID, 同时在 SDK 桥内保留同样的软件过滤,避免把无关总线流量转发给服务进程。 ## 硬件和总线基线 - USB:`3068:0009 ZLG USBCANFD-200U` - Linux SDK 设备类型:`33` - 通道:CH1 - 帧格式:11-bit ISO CAN FD,按报文保留 BRS 属性 - 控制器时钟:60 MHz - 仲裁段:500 kbit/s,`tseg1=14, tseg2=3, sjw=2, brp=5` - 数据段:2 Mbit/s,`tseg1=10, tseg2=2, sjw=2, brp=1` - 终端:默认不启用适配器内部 120 欧;只能按实际拓扑调整 有线真实执行采用一个设备 owner 同时初始化两个通道:CAN0 为最新副驾座椅 经典 CAN 500 kbit/s,CAN1 为车辆 ISO CAN FD 500 kbit/s / 2 Mbit/s。副驾控制 使用 `0x300/0x29E/0x3CE`,状态反馈覆盖 `0x301–0x306/0x4FA`。Linux CAN0 不依赖旧 IAR profile、自动烧录或 T2G combined 固件检查。 前端车辆概况依赖 `0x342`(档位)、`0x4EB/0x07`(车速)和 `0x407` (门锁)。主驾座椅依赖 `0x4FA`、`0x494`。收到其他帧不能替代这些已验证 反馈。车身设备还会按帧独立更新:`0x388` 四窗、`0x404` 空调、`0x41A` 遮阳帘、`0x42E/0x3F3/0x3FF` 车内灯光,以及 `0x38A` 前部灯光。 ## 前端有线 / 无线选择 前端“车辆连接方式”提供两个相互独立的传输选项: - 有线控制:副驾座椅状态和控制走后端 ZLG CAN0,其余车辆状态和控制走 CAN1。 - 无线控制:状态订阅 `n7/n7-headunit/can/state`,控制发布到 `n7/n7-headunit/can/command`;默认 Broker 为 `127.0.0.1:1883`。MQTT CAN0 专用于 副驾座椅,MQTT CAN1 专用于除副驾座椅外的车辆状态和控制。 选择保存在当前浏览器,并随车辆状态请求和控制请求一起传给后端。切换传输时 会建立新的会话上下文,上一条链路缓存的状态不会冒充当前链路状态。两种方式 共用同一套编译范围检查、P 挡/零车速预检、反馈目标匹配和硬件批准令牌;MQTT 发布成功只证明 Broker 接收了消息,不能替代 ECU 状态反馈。 MQTT 状态主题只承载原始 CAN 帧,不接受已经解析的车辆状态 JSON。接收侧继续 兼容下面的规范单帧格式: ```json { "schema": "n7.can.frame.v1", "arbitration_id": 834, "frame_id": "0x342", "channel": 1, "data_hex": "00 00 00 01 00 00 00 00", "data": [0, 0, 0, 1, 0, 0, 0, 0], "payload_size": 8, "is_fd": true, "bitrate_switch": true, "timestamp": 1787029200.0 } ``` 车机实际使用的 `n7-can-signals/v1` 状态包由顶层 `signals` 承载同样的原始帧。 `n7/n7-headunit/can/state` 可以发送单帧对象,也可以发送 `{"frames":[...]}` 批次。接收侧兼容 `can_id`、`canId`、`frame_id` 和 `dataHex` 等常见字段名,但仍强制 CAN0/CAN1、11 位 ID、1–64 字节长度和 逐通道反馈 ID 白名单。 自然语言指令仍先由后端编译、检查白名单和车辆安全条件。每个已批准的 CAN 帧 再转换为车机 App `CanCommand` 格式并发布到 `n7/n7-headunit/can/command`: ```json { "requestId": "cognitive-agent-0e77879b83fd47a9bf12efb3d20dfae7", "channel": "can1", "canId": "0x431", "frameType": "fd", "brs": true, "extended": false, "remote": false, "data": "10 00 00 00 00 00 00 00", "repeat": 1, "intervalMs": 0, "responseTimeoutMs": 1500 } ``` 每次发布生成不同的 `requestId`。MQTT 外层固定 `repeat=1`,原有执行器继续负责 矩阵要求的重复次数、周期和动作后的安全停止,不会在车机端再次叠加重复发送。 CAN0 输出 `classic` 且禁用 BRS;CAN1 按编译结果输出 Classic CAN 或合法长度的 FD+BRS。车机按通道、控制 ID 和帧类型推断预期反馈 ID。 无线通道边界为: - CAN0:经典 CAN 副驾座椅;直接状态接收 `0x301–0x306`、`0x4FA`。当前车端 还会以只读 `0x6A0` `SEAT_INFO(0x60)` 查询和 `0x6A2` 响应上报四轴 Hall 位置、范围和标志。后端只配对 `C7/7C`、相同序号、`status=0` 的查询响应, 再转换成统一的四轴 `0x302` 状态;查询帧不会被当作普通动作执行。 无线副驾控制直接发布与有线相同的固件原生帧:运动/停止/木马/摇篮使用 `0x300`,通风/加热使用 `0x29E/0x3CE`;运动等待 `0x301/0x302`,动态模式 等待 `0x301/0x306`,热舒适等待 `0x4FA`。`0x300` 只开放语义运行时子集 `0x01/0x02/0x03/0x08/0x09/0x0B/0x0E/0x0F`,并逐字段校验版本、轴、方向、 sequence、ParamA 和 ParamB。刷写、进入 Bootloader、测试、全量程校准、查询 和清故障全部拒绝;`0x6A0` 不再允许作为无线控制帧发布。 - CAN1:除副驾座椅以外的整车 CAN;状态和控制继续使用项目中已有的 CH1 车辆白名单。CAN0 控制 ID 不能经 CAN1 发送,CAN1 控制 ID 也不能经 CAN0 发送。 两个通道的协议来源严格分离: - CAN0 只以 `/home/orin/project511/passenger_seat_firmware/firmware` 与 `/home/orin/project511/passenger_seat_firmware/Passenger_Seat_Controller_demo` 中的副驾固件协议为准; 入站状态必须是 11 位标准帧、经典 CAN、无 BRS、DLC 8。 - CAN1 只以 `/home/orin/project511/project-code/canfd/CAN矩阵表_filled.xlsx` 为准。 例如 `0x42E` 颜色值只有 `0x01–0x1F` 有效,保留值不会显示为 真实颜色。`0x388` 的四窗关好位按矩阵解析;byte 3–6 的四窗百分比另有 `project-code/canfd/window_native.txt` 实车变化轨迹和车端原始帧证据, 因此只有数值落在 `0–100` 时才显示为开度。 MQTT 消息自带的 `can0Seat.parsed` 和 `can1Vehicle.parsed` 只可用于车端 诊断,后端不使用它们更新状态或通过安全门禁。 副驾位置换算严格使用固件 `OTA_SEAT_INFO_AXIS0..3_POSITION_RANGE`:响应值低 16 位为当前位置 Hall 计数,高 16 位为该轴 Hall 范围; `OTA_SEAT_INFO_AXIS0..3_FLAGS` 提供 `valid/moving/driver_fault/direction`。四轴 分别映射为水平、坐垫、靠背和腿托。只读缓存最多保留 3 秒,新建状态请求可 立即复用新鲜四轴快照,不需要等待下一轮车端查询。 每个 MQTT 总线视图只会收到本通道反馈。发布完成仅代表 Broker 接收消息; 座椅和整车执行器仍必须等待对应状态 ID 与目标字段匹配,才会报告动作成功。 副驾加热/通风使用固件已有的 `0x3CE/0x29E` 控制和 `0x4FA` 反馈;无线模式 与有线模式共享同一套原始帧、参数白名单和反馈闭环,仅传输载体不同。 车端现有 `n7-can-signals/v1` 状态封装也可直接发布到 `n7/n7-headunit/can/state`。后端优先从顶层 `signals` 读取,避免与嵌套副本重复; 顶层缺失时会合并 `can0Seat.signals`(默认 CAN0)和 `can1Vehicle.signals`(默认 CAN1)。接收兼容 `channel: "can0"/"can1"`、 `channelIndex`、`frameType`、`brs`、`data`/`bytes` 和毫秒时间戳。若同一帧的 `channel` 与 `channelIndex` 冲突,整批消息会被拒绝。封装中的 `can0Seat.parsed` 和 `can1Vehicle.parsed` 都不作为权威状态输入;安全预检和 前端状态仍由原始 CAN 帧通过逐通道白名单解码器生成。带 Unix 时间戳的过期 CAN1 非安全帧可短暂作为 `display_only` 缓存显示,但不会进入控制执行器的 反馈队列。过期的 `0x342` 挡位和 `0x4EB` 车速帧会直接拒绝,绝不能用 旧的 P 挡或零车速结果通过当前控制预检。 无线 CAN0 和 CAN1 状态读取互不持有真实执行独占锁。CAN0 状态缺失或等待时 不会再让 CAN1 接口返回 `hardware_busy`。前端按通道分别保持 single-flight, CAN1 以 500 ms 周期刷新;手动切换有线/无线后会立即按所选后端重新建立轮询 周期,上一传输方式的迟到响应会被丢弃。 无线副驾动作在向 CAN0 发布前,也必须通过同一条 MQTT 链路上的 CAN1 新鲜 P 挡与零车速预检。远端链路不会触发本机 IAR 自动烧录;副驾固件升级仍需走 独立、明确授权的维护流程。 ## USB 权限 设备节点必须允许运行服务的用户读写。推荐使用仅授予当前桌面登录用户权限的 udev 规则: ```bash sudo sh -c 'printf "%s\n" '\''SUBSYSTEM=="usb", ATTR{idVendor}=="3068", ATTR{idProduct}=="0009", TAG+="uaccess", MODE="0660"'\'' > /etc/udev/rules.d/50-zlg-usbcanfd.rules' sudo udevadm control --reload-rules sudo udevadm trigger --attr-match=idVendor=3068 --attr-match=idProduct=0009 ``` 若 ACL 没有立即出现,重新插拔适配器。使用以下命令核对,当前服务用户应具有 `rw`: ```bash getfacl -p /dev/bus/usb/001/006 ``` 设备编号每次插拔可能变化,应以 `lsusb` 的 Bus/Device 为准。 ## 服务配置 只读 CH1 状态监听使用 `config/environments/linux_vehicle_state.example.env`。 CAN0/CAN1 有线控制使用 `config/environments/linux_wired_dual_channel.example.env`,复制到仓库根目录 `.env` 后按实际 SDK 位置修改 `COGNITIVE_AGENT_ZLGCAN_LINUX_LIBRARY`。本地有人 值守台架可以显式关闭逐请求令牌;共享或远程环境应恢复令牌门禁。 启动: ```bash make dev ``` 核对运行时: ```bash curl -s http://127.0.0.1:8000/api/runtime ``` 车辆状态接口在有线模式应返回 `source=physical_can_ch1`,在无线模式应返回 `source=wireless_mqtt_ch1`,并包含实际收到的 `observed_frame_ids` 和缺失反馈 列表。前端“整车设备状态”会把这两个来源都作为权威实时状态,并明确显示 “有线 CAN”或“无线 CAN”。发送成功、CAN ACK 和状态反馈是不同证据层级; 只有接口实际收到并解码目标反馈才会更新前端。 ## SDK 自检 自检只加载动态库和核对 ABI,不打开 USB: ```bash .venv/bin/python src/cognitive_agent/infrastructure/can/zlgcan_linux_bridge.py \ --dll /home/orin/project511/project-code/canfd/libusbcanfd.so \ --self-test ```