Fix live CAN feedback with CH0-only asynchronous risk transmission
Send 0x1F6 risk updates through a bounded latest-result worker on CH0 and disable CH1 initialization, reception, transmission and live display. Preserve the four actual-position bytes from 0x391 and recorded-session compatibility. Include regression coverage, Orin camera/TensorRT/CAN validation, and the verified standalone binary with refreshed checksums.
This commit is contained in:
@@ -138,7 +138,7 @@ Ctrl+C / SIGTERM 会封装已写入的视频,`CameraPipeline::join()` 返回
|
||||
|
||||
## CAN/CANFD 收发与四门位置显示
|
||||
|
||||
实时运行默认打开参考工程 `/home/orin/vision-anti-pinch` 使用的 USBCANFD(设备类型 33、设备索引 0),启用 CH0/CH1:60 MHz 时钟、仲裁域 500 kbps、数据域 2 Mbps。一个合并接收线程读取两个通道,默认启用终端电阻。可在 `config/camera.json` 的 `can` 中设置 `enabled`、`device_index`、`timeout_ms`(默认 1000 ms)和 `termination`;`--no-can` 显式关闭实时 CAN。设备不可用时显示 UNAVAILABLE,摄像头推理继续;接收 API 失败显示 ERROR,需检查设备后重启程序。
|
||||
实时运行默认打开参考工程 `/home/orin/vision-anti-pinch` 使用的 USBCANFD(设备类型 33、设备索引 0),仅启用 CH0:60 MHz 时钟、仲裁域 500 kbps、数据域 2 Mbps。一个接收线程读取 CH0,默认启用终端电阻。可在 `config/camera.json` 的 `can` 中设置 `enabled`、`device_index`、`timeout_ms`(默认 1000 ms)和 `termination`;`--no-can` 显式关闭实时 CAN。设备不可用时显示 UNAVAILABLE,摄像头推理继续;接收 API 失败显示 ERROR,需检查设备后重启程序。
|
||||
|
||||
首次使用 USB-CAN 时,若节点为 root:root 且普通用户不可写,可运行 `./scripts/enable_can_access.sh`(需要 sudo)。它只为 `3068:0009` 适配器安装 `plugdev` 组读写规则并刷新设备权限;运行用户须属于 `plugdev`,新加组后需重新登录。无需以 root 运行整个推理程序。
|
||||
|
||||
@@ -151,15 +151,15 @@ Ctrl+C / SIGTERM 会封装已写入的视频,`CameraPipeline::join()` 返回
|
||||
| 2 | LR | 左后门实际位置反馈 |
|
||||
| 3 | RR | 右后门实际位置反馈 |
|
||||
|
||||
显示值是无符号原始字节 0–255,尚无物理单位、缩放或无效值定义,因此不标为百分比、不把 255 擅自视为无效。接收 CAN/CANFD 数据帧,过滤远程帧、错误帧和发送回显;少于 4 字节的 `0x391` 不更新信号。修正了参考代码 `get_bit_value(0/1/2/3, 8)` 导致的位域重叠,字节起始位应为 0/8/16/24。
|
||||
显示值是无符号原始字节 0–255,尚无物理单位、缩放或无效值定义,因此不标为百分比、不把 255 擅自视为无效。CH0 接收 CAN/CANFD 数据帧,过滤远程帧、错误帧和发送回显;少于 4 字节的 `0x391` 不更新信号。修正了参考代码 `get_bit_value(0/1/2/3, 8)` 导致的位域重叠,字节起始位应为 0/8/16/24。
|
||||
|
||||
默认预览为两个通道分别显示四门位置、LIVE/STALE/WAIT、新鲜度及帧数,并显示总 RX/TX 和错误计数。尚未接收到值时显示 `--`,超时保留最后值但标记 STALE;两个通道互不覆盖。即使相机正在重试或暂无新推理帧,CAN 预览仍约每 100 ms 刷新。CAN 只提供状态显示和下游数据,本次没有新增基于门位置的推理启停或风险门控。
|
||||
默认预览显示 CH0 四门实际位置反馈、LIVE/STALE/WAIT、新鲜度及帧数,并显示总 RX/TX 和错误计数。尚未接收到值时显示 `--`,超时保留最后值但标记 STALE;CH1 不初始化、不收发,实时预览不显示 CH1。即使相机正在重试或暂无新推理帧,CAN 预览仍约每 100 ms 刷新。CAN 只提供状态显示和下游数据,本次没有新增基于门位置的推理启停或风险门控。
|
||||
|
||||
下游通过 `pipeline.canStatus()` 读取当前状态;`pipeline.latest()->can` 是该推理结果生成时的快照。按需发送使用线程安全的 `pipeline.sendCan()`:
|
||||
|
||||
```cpp
|
||||
cms::CanFrame frame;
|
||||
frame.channel = 0; // CH0 或 CH1
|
||||
frame.channel = 0; // 仅 CH0
|
||||
frame.id = 0x123; // 按实际协议设置
|
||||
frame.data = {0x01, 0x02}; // 示例数据,应替换为实际报文
|
||||
bool sent = pipeline.sendCan(frame);
|
||||
@@ -169,10 +169,16 @@ bool sent = pipeline.sendCan(frame);
|
||||
// 标准 ID 最大 0x7FF,扩展 ID 最大 0x1FFFFFFF。
|
||||
```
|
||||
|
||||
非法通道/ID/长度/标志抛出 `std::invalid_argument`;设备未就绪或 SDK 发送失败返回 false,发送计数以 SDK 返回为准。接口不自动填充/截断数据,不自动发送周期或风险控制报文。发送调用与设备关闭互斥;双通道接收保持独立线程运行。
|
||||
非法通道/ID/长度/标志抛出 `std::invalid_argument`;设备未就绪或 SDK 发送失败返回 false,发送计数以 SDK 返回为准。`sendCan()` 接口不自动填充/截断数据。发送调用与设备关闭互斥;CH0 接收保持独立线程运行。
|
||||
|
||||
实时推理每产生一个新结果,会将风险提交给后台 CAN 发送线程,由它通过 CH0 发送一帧标准 CAN 数据报文 `0x1F6`,DLC 固定为 8。`data[0]` 的低四位(bit0–3)和高四位(bit4–7)同时填写相同的二值风险:`risk_level == "clear"` 时各为 0,其余状态(包括 `watch`、`warning`、`danger`、`unavailable`)各为 1;后七字节全部为 0。即 `clear` 发送 `00 00 00 00 00 00 00 00`,其余发送 `11 00 00 00 00 00 00 00`(十六进制)。无人或证据不足导致的 `unavailable` 也在高、低四位各发送 1。
|
||||
|
||||
发送随推理结果触发,没有独立周期;无新推理结果时不重发。SDK 发送等待不占用推理线程;后台最多保留一个待发结果,发送变慢时用最新结果替换尚未发送的结果,避免积压旧风险,并非保证每个推理帧都上总线。CH0 发送失败通过现有 TX 错误计数呈现;结果中的 CAN 快照记录采样时已完成的发送计数,不保证包含当前帧的发送。停止时丢弃待发结果,并等待已经进入 SDK 的调用返回后释放设备。`--no-can` 或 `can.enabled=false` 禁用自动发送,视频回灌始终不发送。按需 `sendCan()` 仍为同步 API。
|
||||
|
||||
结果 JSON 和录制会话自动包含每帧 `can` 快照。视频回灌不打开 USB-CAN,不发送报文,直接恢复录制时的状态并标记 `source=replay`;旧会话没有 CAN 字段时显示 NOT_RECORDED。回灌调用 `sendCan()` 会明确拒绝。
|
||||
|
||||
依赖 `third_party/zlg/zcan.h` 和 `orin/lib/` 中的参考工程 SDK 库(含 SONAME 链接),独立包已包含它们。纯模拟收发回归:`./tests/run_can.sh`;会话回归同时覆盖 CAN 快照保存。模拟测试不代表物理总线收发验证。
|
||||
|
||||
本次检查范围与延期项目见 [CAN 实现记录](tests/ORIN_CAN_REPORT.md);按当前要求暂不验证真实 CAN 接收。
|
||||
本次检查范围与延期项目见 [CAN 实现记录](tests/ORIN_CAN_REPORT.md);CH0 实机排查与单通道修复见 [0x391 实际位置反馈检查](tests/ORIN_391_REPORT.md)。
|
||||
|
||||
新增 `0x1F6` 二值风险发送的检查范围及部署状态见 [风险发送检查记录](tests/RISK_CAN_REPORT.md)。
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "can_types.hpp"
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace cms {
|
||||
// One lifecycle per instance; only one owner per device index in a process.
|
||||
@@ -19,6 +20,15 @@ public:
|
||||
// Thread-safe on-demand data-frame send; invalid frames throw, SDK failure
|
||||
// returns false. Never generates periodic or inference-triggered messages.
|
||||
bool send(CanFrame const& frame);
|
||||
// Send one standard 8-byte 0x1F6 frame on CH0. Both nibbles of
|
||||
// data[0] contain 0 for "clear", 1 for every other level (including
|
||||
// unavailable): 0x00 or 0x11. Bytes 1-7 are zero. Returns {CH0 result, false};
|
||||
// the second slot is retained for API compatibility, CH1 is never sent.
|
||||
std::array<bool,2> sendRisk(std::string_view riskLevel);
|
||||
// Nonblocking live-inference path. Keeps at most one pending risk result;
|
||||
// a newer result replaces one waiting behind a slow SDK send. Returns
|
||||
// queue acceptance, not delivery. No timer or retransmission of old risk.
|
||||
bool queueRisk(std::string_view riskLevel);
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
namespace cms {
|
||||
inline constexpr unsigned liveCanChannelCount=1; // CH0 only; CH1 is not used.
|
||||
struct CanConfig {
|
||||
bool enabled{true};
|
||||
int device_index{0};
|
||||
@@ -22,6 +23,7 @@ struct CanDoorSignals {
|
||||
};
|
||||
struct CanSnapshot {
|
||||
std::string source{"live"},status{"DISABLED"};
|
||||
// Keep two slots for existing recorded sessions; live CH1 stays unset.
|
||||
std::array<CanDoorSignals,2> channels;
|
||||
std::uint64_t received{},transmitted{},transmit_errors{},receive_errors{};
|
||||
};
|
||||
|
||||
+3
-1
@@ -54,8 +54,10 @@ bin/drivers 中 GMSL 模块对应内核 5.10.120-tegra。初始化脚本检查
|
||||
|
||||
## CAN 状态与发送接口
|
||||
|
||||
实时运行默认接收 USBCANFD 双通道(500 kbps / 2 Mbps)。预览新增 `0x391` 的 LF/RF/LR/RR 四门原始位置值,每通道分别显示,并标记新鲜度和收发计数。`--no-can` 可关闭;设备缺失不停止推理。`config/camera.json` 的 `can.termination` 控制终端电阻,默认沿用参考工程的开启设置。
|
||||
实时运行默认仅接收 USBCANFD CH0(500 kbps / 2 Mbps)。预览新增 `0x391` 的 LF/RF/LR/RR 四门实际位置反馈原始值,仅显示 CH0,并标记新鲜度和收发计数。`--no-can` 可关闭;设备缺失不停止推理。`config/camera.json` 的 `can.termination` 控制终端电阻,默认沿用参考工程的开启设置。
|
||||
|
||||
`lib/` 包含 SDK 与 libusb 及其 SONAME 链接。回灌使用录制的 CAN 快照,不访问总线;旧会话显示 NOT_RECORDED。按需发送通过 C++ `CameraPipeline::sendCan()`,参数与完整示例见工程 README。
|
||||
|
||||
每个新的实时推理结果提交到后台 CAN 发送线程,仅在 CH0 发送一帧标准 CAN `0x1F6`,长度 8 字节:首字节高、低四位同时填写相同风险值,`clear` 时各为 0,其余状态(包括 `unavailable`)各为 1,即首字节为 `0x00` 或 `0x11`,后七字节清零。CH1 不初始化、不收发。发送等待不阻塞推理,最多保留一个待发结果,忙时由最新结果替换;发送失败计入 TX 错误。无新结果时不重发,`--no-can` 和视频回灌禁用发送。停止时清空待发结果,等待正在执行的 SDK 调用结束后释放设备。
|
||||
|
||||
USB-CAN 首次接入如出现权限不足,运行 `./scripts/enable_can_access.sh`(需要 sudo,运行用户属于 plugdev),仅为 `3068:0009` 适配器安装设备访问规则。
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
6d5e874fd27fcfd75d303d7e35d9672028af71041f52dcc82a1e83676faefa65 ./bin/drivers/max9295.ko
|
||||
8a78d04ef8be44e9b62a5e4ee315d3d6d679e34aedff02a234bbf4d40e222933 ./bin/drivers/max9296.ko
|
||||
3da628587477cc35d5e3f8aee86e718e2ddf16808e893e00ba6e164b2307e843 ./bin/drivers/sgx-yuv-gmsl2.ko
|
||||
e8788d22fd6db0e3b9edc95a02322a51ca7951e1df81fea10f34db7da3143461 ./build/camera_risk
|
||||
418637a35f6ad05441578db1333e15714262a9c0ee088f10c2dd84923471cd0f ./build/camera_risk
|
||||
d44dcf10df43c7bcd73b7f2b64e4117d207435a5218c2146bfb659566cb320ba ./config/camera.json
|
||||
9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 ./config/door_mask.json
|
||||
ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 ./engines/rtmw_l_384.engine
|
||||
@@ -10,7 +10,7 @@ f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 ./engines/yolo
|
||||
8dae8c557deadbe6bd7241c0dcd7025cb007077ee39a79b115e953a2b22cd9c1 ./lib/libusb-1.0.so.0
|
||||
ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 ./lib/libusbcanfd.so
|
||||
ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 ./lib/libusbcanfd.so.1.0.12
|
||||
bb61f669d8b471c5ca42045b31a0e6fd300235da37a80893ae229f37deb1b7b9 ./README.md
|
||||
584875c5a52f969da7ae50514fd627749123f3db7140058052055982a6c01f2f ./README.md
|
||||
7f42b0e7f9396e0d82313a892fc62c153261dab1206a206a466bce6376f58a36 ./run.sh
|
||||
41ebe2dcf57674232ca137d024e99447b467049cb1c63a284daae274296cb78c ./scripts/99-usbcanfd.rules
|
||||
920102fc5e91bde9b78f6f7c5cb2ff8761a0c39b63c7a28b3347d5de5fe7c60a ./scripts/enable_can_access.sh
|
||||
|
||||
Binary file not shown.
@@ -173,7 +173,10 @@ struct CameraPipeline::Impl {
|
||||
if(recorded.contains("can"))result->can=deserializeCan(recorded.at("can"));
|
||||
else result->can.status="NOT_RECORDED";
|
||||
result->can.source="replay";
|
||||
}else result->can=can->snapshot();
|
||||
}else {
|
||||
if(!sessionReader&&config.can.enabled)can->queueRisk(result->risk.risk_level);
|
||||
result->can=can->snapshot();
|
||||
}
|
||||
attachPoses(*result,fused);
|
||||
result->image=std::move(detected->frame.image);result->door_mask_pixels=doorPixels;
|
||||
result->door_mask_calibration_verified=config.door_mask_calibration_verified;
|
||||
|
||||
+57
-10
@@ -6,6 +6,7 @@
|
||||
#include <condition_variable>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
@@ -19,7 +20,7 @@ std::uint64_t nowNs() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
void validate(CanFrame const& f) {
|
||||
if(f.channel<0||f.channel>1||f.id>(f.extended?0x1fffffffu:0x7ffu)||(!f.fd&&f.bitrate_switch))
|
||||
if(f.channel<0||unsigned(f.channel)>=liveCanChannelCount||f.id>(f.extended?0x1fffffffu:0x7ffu)||(!f.fd&&f.bitrate_switch))
|
||||
throw std::invalid_argument("Invalid CAN channel, identifier or BRS flag");
|
||||
auto size=f.data.size();
|
||||
if((!f.fd&&size>8)||(f.fd&&!(size<=8||size==12||size==16||size==20||size==24||size==32||size==48||size==64)))
|
||||
@@ -29,13 +30,14 @@ void validate(CanFrame const& f) {
|
||||
struct CanBus::Impl {
|
||||
CanConfig config;
|
||||
mutable std::mutex stateMutex;
|
||||
std::mutex lifecycleMutex,waitMutex;
|
||||
std::condition_variable wake;
|
||||
std::mutex lifecycleMutex,waitMutex,riskMutex;
|
||||
std::condition_variable wake,riskWake;
|
||||
std::optional<bool> pendingRisk;
|
||||
CanSnapshot state;
|
||||
std::atomic<bool> stopped{false};
|
||||
bool started{},opened{},owned{};
|
||||
std::array<bool,2> initialized{};
|
||||
std::thread receiver;
|
||||
std::thread receiver,riskSender;
|
||||
explicit Impl(CanConfig c):config(c) {
|
||||
if(c.device_index<0||c.timeout_ms<=0)throw std::invalid_argument("Invalid CAN configuration");
|
||||
state.status=c.enabled?"STARTING":"DISABLED";
|
||||
@@ -44,7 +46,7 @@ struct CanBus::Impl {
|
||||
void setStatus(char const* value) {std::lock_guard<std::mutex> lock(stateMutex);state.status=value;}
|
||||
void releaseDevice() noexcept {
|
||||
if(opened) {
|
||||
for(unsigned i=0;i<2;++i)if(initialized[i])VCI_ResetCAN(deviceType,config.device_index,i);
|
||||
for(unsigned i=0;i<liveCanChannelCount;++i)if(initialized[i])VCI_ResetCAN(deviceType,config.device_index,i);
|
||||
VCI_CloseDevice(deviceType,config.device_index);
|
||||
opened=false;initialized.fill(false);
|
||||
}
|
||||
@@ -64,7 +66,7 @@ struct CanBus::Impl {
|
||||
if(VCI_OpenDevice(deviceType,config.device_index,0)!=1)throw std::runtime_error("VCI_OpenDevice failed");
|
||||
opened=true;
|
||||
// Reference USBCANFD: 60 MHz clock, arbitration 500 kbps, data 2 Mbps.
|
||||
for(unsigned i=0;i<2;++i) {
|
||||
for(unsigned i=0;i<liveCanChannelCount;++i) {
|
||||
ZCAN_INIT init{};init.clk=60000000;init.mode=0;
|
||||
init.aset.tseg1=14;init.aset.tseg2=3;init.aset.sjw=2;init.aset.brp=5;
|
||||
init.dset.tseg1=10;init.dset.tseg2=2;init.dset.sjw=2;init.dset.brp=1;
|
||||
@@ -77,20 +79,30 @@ struct CanBus::Impl {
|
||||
U32 merge=1;
|
||||
if(VCI_SetReference(deviceType,config.device_index,0,ZCAN_CMD_SET_CHNL_RECV_MERGE,&merge)!=1)
|
||||
throw std::runtime_error("CAN merged receive configuration failed");
|
||||
for(unsigned i=0;i<2;++i)if(VCI_StartCAN(deviceType,config.device_index,i)!=1)
|
||||
for(unsigned i=0;i<liveCanChannelCount;++i)if(VCI_StartCAN(deviceType,config.device_index,i)!=1)
|
||||
throw std::runtime_error("VCI_StartCAN failed");
|
||||
setStatus("OPEN");
|
||||
// Exactly one merged reader for both channels; no competing readers.
|
||||
// Exactly one reader; only CH0 is initialized and accepted.
|
||||
receiver=std::thread([this]{receiveLoop();});
|
||||
std::cout<<"CAN device "<<config.device_index<<": CH0/CH1 enabled, 500 kbps / 2 Mbps"<<std::endl;
|
||||
riskSender=std::thread([this]{riskLoop();});
|
||||
std::cout<<"CAN device "<<config.device_index<<": CH0 enabled, 500 kbps / 2 Mbps"<<std::endl;
|
||||
}catch(std::exception const& e) {
|
||||
requestStop();
|
||||
if(receiver.joinable())receiver.join();
|
||||
releaseDevice();setStatus("UNAVAILABLE");
|
||||
std::cerr<<"CAN unavailable; inference continues: "<<e.what()<<std::endl;
|
||||
}
|
||||
}
|
||||
void requestStop() noexcept {stopped=true;wake.notify_all();}
|
||||
void requestStop() noexcept {
|
||||
stopped=true;wake.notify_all();
|
||||
{std::lock_guard<std::mutex> lock(riskMutex);pendingRisk.reset();}
|
||||
riskWake.notify_all();
|
||||
}
|
||||
void join() noexcept {
|
||||
requestStop();
|
||||
// A sender may be inside send() holding lifecycleMutex. Join it before
|
||||
// taking that mutex or closing the device.
|
||||
if(riskSender.joinable())riskSender.join();
|
||||
std::lock_guard<std::mutex> lock(lifecycleMutex);
|
||||
if(receiver.joinable())receiver.join();
|
||||
releaseDevice();
|
||||
@@ -100,6 +112,7 @@ struct CanBus::Impl {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
// Ignore remote, error and transmit-echo frames; they are not input.
|
||||
if(header.inf.sdf||header.inf.err||header.inf.tx)return;
|
||||
if(channel>=liveCanChannelCount)return;
|
||||
if(channel>1||header.len>(fd?64:8)||header.id>(header.inf.sef?0x1fffffffu:0x7ffu)) {
|
||||
++state.receive_errors;return;
|
||||
}
|
||||
@@ -123,6 +136,7 @@ struct CanBus::Impl {
|
||||
}
|
||||
for(unsigned i=0;i<count&&!stopped;++i) {
|
||||
auto const& item=frames[i];
|
||||
if(item.chnl>=liveCanChannelCount)continue;
|
||||
if(item.dataType==ZCAN_DT_ZCAN_CAN_DATA)receiveFrame(item.data.zcanCANData.hdr,item.data.zcanCANData.dat,false,item.chnl);
|
||||
else if(item.dataType==ZCAN_DT_ZCAN_CANFD_DATA)receiveFrame(item.data.zcanCANFDData.hdr,item.data.zcanCANFDData.dat,true,item.chnl);
|
||||
else if(item.dataType==ZCAN_DT_ZCAN_ERROR_DATA){std::lock_guard<std::mutex> lock(stateMutex);++state.receive_errors;}
|
||||
@@ -139,6 +153,29 @@ struct CanBus::Impl {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool queueRisk(std::string_view level) {
|
||||
std::lock_guard<std::mutex> lock(riskMutex);
|
||||
{std::lock_guard<std::mutex> stateLock(stateMutex);
|
||||
if(stopped||state.status!="OPEN")return false;
|
||||
}
|
||||
pendingRisk=level!="clear";riskWake.notify_one();return true;
|
||||
}
|
||||
void riskLoop() noexcept {
|
||||
try {
|
||||
CanFrame frame;frame.id=0x1f6;frame.data.assign(8,0);
|
||||
while(!stopped) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(riskMutex);
|
||||
riskWake.wait(lock,[this]{return stopped||pendingRisk.has_value();});
|
||||
if(stopped)break;
|
||||
frame.data[0]=*pendingRisk?0x11:0x00;pendingRisk.reset();
|
||||
}
|
||||
for(unsigned channel=0;channel<liveCanChannelCount&&!stopped;++channel) {
|
||||
frame.channel=channel;send(frame);
|
||||
}
|
||||
}
|
||||
}catch(...) {setStatus("ERROR");}
|
||||
}
|
||||
bool send(CanFrame const& f) {
|
||||
validate(f);
|
||||
std::lock_guard<std::mutex> lock(lifecycleMutex);
|
||||
@@ -168,4 +205,14 @@ void CanBus::requestStop()noexcept{impl_->requestStop();}
|
||||
void CanBus::join()noexcept{impl_->join();}
|
||||
CanSnapshot CanBus::snapshot()const{return impl_->snapshot();}
|
||||
bool CanBus::send(CanFrame const& f){return impl_->send(f);}
|
||||
bool CanBus::queueRisk(std::string_view level){return impl_->queueRisk(level);}
|
||||
std::array<bool,2> CanBus::sendRisk(std::string_view riskLevel) {
|
||||
CanFrame frame;frame.id=0x1f6;frame.data.assign(8,0);
|
||||
frame.data[0]=riskLevel=="clear"?0x00:0x11;
|
||||
std::array<bool,2> sent{};
|
||||
for(unsigned channel=0;channel<liveCanChannelCount;++channel) {
|
||||
frame.channel=channel;sent[channel]=send(frame);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ int main(int argc,char** argv)try {
|
||||
{"--visualize",[&](int&){visualize=true;}},
|
||||
{"--no-visualize",[&](int&){visualize=false;}},
|
||||
{"--config",[&](int&i){if(++i>=argc)throw std::invalid_argument("--config needs a file");configPath=argv[i];}},
|
||||
{"--help",[](int&){std::cout<<"camera_risk [--record-session NEW_DIR] [--replay-session DIR] [--save-original] [--save-annotated] [--save-results] [--config FILE] [--no-can] [--visualize|--no-visualize]\nLive CAN/CANFD reception is enabled by default; replay uses recorded CAN only.\nVisualization is enabled by default for live inference, recording and replay.\nContinuous camera inference; replay runs to EOF without a camera.\nH.264 sessions require a new recording directory. Stop with Ctrl+C or SIGTERM to finalize video.\n";std::exit(0);}}
|
||||
{"--help",[](int&){std::cout<<"camera_risk [--record-session NEW_DIR] [--replay-session DIR] [--save-original] [--save-annotated] [--save-results] [--config FILE] [--no-can] [--visualize|--no-visualize]\nLive CAN/CANFD reception and per-result 0x1F6 binary risk transmission on CH0 only are enabled by default.\nRisk: clear=0, all other levels=1; --no-can disables both reception and transmission.\nReplay uses recorded CAN only and never transmits.\nVisualization is enabled by default for live inference, recording and replay.\nContinuous camera inference; replay runs to EOF without a camera.\nH.264 sessions require a new recording directory. Stop with Ctrl+C or SIGTERM to finalize video.\n";std::exit(0);}}
|
||||
};
|
||||
for(int i=1;i<argc;++i){auto handler=arguments.find(argv[i]);if(handler==arguments.end())throw std::invalid_argument("Unknown option: "+std::string(argv[i]));handler->second(i);}
|
||||
|
||||
|
||||
@@ -25,11 +25,12 @@ void segment(cv::Mat& image,cv::Point a,cv::Point b,cv::Scalar c,int thickness,b
|
||||
void renderCanOverlay(cv::Mat& canvas,CanSnapshot const& can) {
|
||||
cv::rectangle(canvas,{0,148},{canvas.cols,262},{20,24,30},-1);
|
||||
std::ostringstream header;
|
||||
header<<"CAN 0x391 | "<<can.source<<" "<<can.status<<" | RX "<<can.received
|
||||
header<<"CAN 0x391 ACTUAL POSITION | "<<can.source<<" "<<can.status<<" | RX "<<can.received
|
||||
<<" TX "<<can.transmitted<<" | errors RX "<<can.receive_errors<<" TX "<<can.transmit_errors;
|
||||
cv::putText(canvas,header.str(),{24,174},cv::FONT_HERSHEY_SIMPLEX,.65,{235,235,235},2,cv::LINE_AA);
|
||||
constexpr char const* labels[]={"LF","RF","LR","RR"};
|
||||
for(std::size_t channel=0;channel<can.channels.size();++channel) {
|
||||
auto channels=can.source=="replay"?can.channels.size():liveCanChannelCount;
|
||||
for(std::size_t channel=0;channel<channels;++channel) {
|
||||
auto const& s=can.channels[channel];std::ostringstream line;
|
||||
line<<"CH"<<channel<<" "<<(s.valid?"LIVE":s.received?"STALE":"WAIT")<<" ";
|
||||
for(std::size_t i=0;i<4;++i) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# 0x391 实际位置反馈与 CH0 单通道修复(2026-09-28)
|
||||
|
||||
用户要求显示四门实际位置反馈,并明确不使用 CH1。本次保留 `0x391` 第 0/1/2/3 字节到 LF/RF/LR/RR 的无符号原始值映射,不改为门状态、不引入未经确认的百分比或角度换算。
|
||||
|
||||
实时模式仅初始化、启动、接收和发送 CH0;拒绝向 CH1 调用发送 API,忽略合并接收中的 CH1 数据,画面仅显示 CH0,并标明 ACTUAL POSITION。历史会话的双通道快照结构与回灌显示保持兼容,`sendRisk()` 返回值第二槽固定为 false。
|
||||
|
||||
## 实机现象与修复结果
|
||||
|
||||
用户第一次确认左前门打开后,原始接收工具取得 CH0、CANFD、64 字节的 `0x391`,前四字节为 `1F 00 00 00`,即左前门反馈 31。由此确认实际位置字段映射有真实报文依据。
|
||||
|
||||
修复前,真实摄像头/TensorRT 与原双通道风险发送同时运行时,接收计数长时间停顿;反馈年龄最高观察到约 5.84 秒。CH0 发送成功、CH1 发送失败,接收在发送等待期间停顿。仅将发送移到后台线程不能消除适配器发送等待对接收的影响。
|
||||
|
||||
最终仅 CH0 版本在同一硬件上运行:10 秒采样内 RX 持续增长,TX 到 299、TX 错误为 0,各采样点保持 LIVE。用户再次确认左前门打开后,最终版同一进程显示实际位置 `26,0,0,0`,末次采样更新为 `25,0,0,0`,0x391 计数从 4 增至 33。显示层由相同 `renderResult()` 绘制,已检查 [CAN 画面裁剪](orin_391_overlay.png),可见 LF 26,CH1 未显示。
|
||||
|
||||
详细结果与运行二进制校验见 [实机结果](orin_391_results.json)。原始日志为 `/tmp/vap_391_open.log`,对照采样为 `/tmp/vap_391_after.log`,采样源码为 `/tmp/vap_391_pipeline.cpp`。
|
||||
|
||||
## 本机模拟、板端故障注入、硬件覆盖
|
||||
|
||||
- 本机模拟:`tests/run_can.sh`(UBSan、模拟 SDK)通过。检查 CH1 不初始化/配置/启动/重置/发送、CH1 输入不覆盖 CH0、实际位置更新、CAN/CANFD、过期恢复、单通道风险报文、发送失败与恢复、后台最新值覆盖、停止清理。
|
||||
- 本机模拟:`tests/run_session.sh` 通过,保持已有会话 CAN 快照兼容;构建与 `git diff --check` 通过。
|
||||
- 板端故障注入:未执行。本次未修改共享摄像头采集、停止或恢复逻辑。
|
||||
- 真实硬件:Orin 上使用既有 `/dev/video5`、TensorRT 引擎、USBCANFD-200U 与配置;用户操作左前门,验证实际位置进入推理期间的实时显示。采样程序调用画面绘制,未运行 GUI 事件循环;正式程序另按原命令 `./run_camera.sh` 启动验证。
|
||||
- 未覆盖:其余三扇门逐一动作、长期运行、CH0 断开/缺失 ACK、独立总线分析仪验证风险报文。接收错误计数仍增长(画面记录 RX errors 5801),本次未确定该总线错误的原因,不能宣称总线无错误。
|
||||
|
||||
## 部署与版本
|
||||
|
||||
最终运行 `/proc/<pid>/exe`、`build/camera_risk` 和 `orin/build/camera_risk` SHA256 一致:
|
||||
|
||||
`418637a35f6ad05441578db1333e15714262a9c0ee088f10c2dd84923471cd0f`
|
||||
|
||||
独立包可执行文件已更新,`orin/SHA256SUMS` 已刷新并完整校验。正式程序使用原命令 `./run_camera.sh`,SIGTERM 后约 0.868 秒退出、退出码 0。测试前未发现常驻 camera_risk,测试后没有保留临时采样进程;没有修改原启动参数。
|
||||
|
||||
硬件验证时,本机即 Orin 工作区,检出 `inference-only`,HEAD 为 `515a9f32d8c0ebf83503d4d240ffe917dc09950c`;本次联网读取 Git 远端 `inference-only` 为 `0192fccbf06cb928eac098b3dfad32abf0fc370d`。上述硬件验证在未提交工作区进行。经用户要求,最终修复、后台风险发送、部署产物及验证记录一并纳入本报告所在提交;Git 远端尚未推送同步。以上二进制哈希标识已验证的本地部署产物。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 实时摄像头 FPS 修复(2026-09-28)
|
||||
|
||||
> 以下为后台双通道发送与 FPS 修复阶段的历史验证记录,通道、二进制哈希与 Git 状态均指当时版本。最终 CH0 单通道实现和实机结果见 [0x391 修复记录](ORIN_391_REPORT.md)。
|
||||
|
||||
用户通过默认 `./run_camera.sh` 运行实时摄像头时卡顿。原因已在宿主 Orin 复现:`inferenceLoop()` 同步发送 CH0、CH1 的 `0x1F6`,SDK 发送失败路径两通道合计等待约 3 秒,使结果发布和后续推理一起停顿。
|
||||
|
||||
## 真实硬件对照
|
||||
|
||||
设备:Jetson AGX Orin、`/dev/video5` 1920×1536 30 FPS、现有 YOLO/RTMW TensorRT 引擎、ZLG USBCANFD-200U。保留配置、模型、CAN 波特率和终端电阻设置;未调整功耗或时钟。
|
||||
|
||||
使用链接相应版本 `camera_risk_core` 的临时采样程序,各运行约 15 秒,按实际 published 结果增量计算 FPS,并调用 `renderResult()` 绘制。该指标为真实推理输出吞吐,非相机配置 FPS;采样程序不调用 GUI 的 imshow/waitKey,不录制、不保存图片。
|
||||
|
||||
| 路径 | 稳定输出 | 风险融合及发送阶段 | 结果延迟 | CAN 状态 |
|
||||
|---|---|---|---|---|
|
||||
| 修复前,同步双通道 CAN | 约 0.33 FPS | 3016–3019 ms/帧 | 随积压升至约 12 秒 | OPEN,TX=0,发送错误递增 |
|
||||
| 修复前,仅对照关闭 CAN | 29.93–30.00 FPS | 约 5–6.6 ms/帧 | 约 42–46 ms | DISABLED |
|
||||
| 修复后,后台双通道 CAN | 29.97–30.00 FPS | 约 6–6.9 ms/帧 | 约 46–50 ms | OPEN,TX=0,发送错误递增 |
|
||||
|
||||
首秒包含启动过渡,后两组约 27 FPS,不纳入稳定区间。画面绘制约 19 ms/帧。另做真实 TensorRT 视频回灌参考采样约 43–48 FPS,不能代替实时摄像头验证。
|
||||
|
||||
## 修改与模拟回归
|
||||
|
||||
实时推理改用 `queueRisk()`,不等待 SDK。独立发送线程处理一对双通道帧,最多保留一个待发风险,新的覆盖尚未发送的旧结果;没有无限队列或定时旧值重发。`0x1F6`、DLC 8、`clear→0x00`/其他→`0x11` 和后七字节清零的协议保持不变。按需 `sendCan()` 仍是同步接口。
|
||||
|
||||
停止时清空待发风险,唤醒后台线程,等待正在执行的 SDK 调用返回,再释放设备;不持有 SDK 生命周期锁等待发送线程,避免死锁。CAN 快照反映已完成的发送,不保证包括当前推理帧。
|
||||
|
||||
`tests/run_can.sh`(模拟 SDK + UBSan)通过,新增阻塞发送期间 1000 次提交、最新结果替换、双通道、失败和停止清理覆盖;构建及 `git diff --check` 通过。未进行板端故障注入。
|
||||
|
||||
## 正式程序和文件核对
|
||||
|
||||
经宿主进程检查确认测试前没有 `camera_risk`,最后按原命令 `./run_camera.sh` 启动正式程序,运行约 12 秒后 SIGTERM,退出码 0、停止约 1.267 秒。未改启动参数;测试后确认没有遗留进程。记录位于 `/tmp/vap-fps-default-verification.json` 和 `/tmp/vap-fps-default-run.log`;临时采样源码为 `/tmp/vap_fps_probe.cpp`。
|
||||
|
||||
实际 `/proc/<pid>/exe`、`build/camera_risk`、`orin/build/camera_risk` SHA256 均为:
|
||||
|
||||
`a84680513aa61fb2af353f97e6369bbef80c9f12521ecd88454e72fcecab447f`
|
||||
|
||||
独立包 SHA256SUMS 已刷新并完整校验。修改留在本地工作区,未提交/推送;版本引用详情见 [风险发送记录](RISK_CAN_REPORT.md)。
|
||||
|
||||
## 未覆盖项
|
||||
|
||||
物理 CAN 发送依然失败,本次只确认该等待已与推理解耦,未确认 ACK、线缆、接收端或总线配置的故障原因。未使用独立总线分析仪验收线上载荷,也未测试物理断连重连或长期耐久。当前场景约 30 FPS 不代表任意人数/场景都能达到同样吞吐。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 0x1F6 二值风险发送检查(2026-09-28)
|
||||
|
||||
> 以下为双通道风险发送阶段的历史验证记录,通道、二进制哈希与 Git 状态均指当时版本。最终 CH0 单通道实现和实机结果见 [0x391 修复记录](ORIN_391_REPORT.md)。
|
||||
|
||||
实现位于当前 `inference-only` 工作区:每个新的实时推理结果提交给后台 CAN 发送线程,在 CH0、CH1 各尝试发送一帧标准 CAN 数据报文,ID `0x1F6`、DLC 8。首字节 bit0–3 和 bit4–7 同时填写相同风险值,`clear` 时各为 0,其他状态(含无人或证据不足时的 `unavailable`)各为 1,即首字节为 `0x00` 或 `0x11`,后七字节清零。最多保留一个待发结果,忙时由最新结果替换,不保证每个推理帧都上总线。回灌和禁用 CAN 时不自动发送,没有独立周期或旧结果重发。
|
||||
|
||||
## 本机模拟与构建
|
||||
|
||||
- `./tests/run_can.sh` 通过(UBSan、模拟 ZLG SDK):覆盖 clear/watch/warning/danger/unavailable/未知/空状态、高低四位相同赋值、恢复 clear 后两者同时清零、标准帧标志、ID、DLC、完整载荷、每结果双通道各一帧、任一通道失败不影响另一通道尝试、收发计数、停止和禁用后不访问发送 API。
|
||||
- 后台发送补充回归通过:模拟 SDK 阻塞时可连续提交 1000 个新风险并读取快照;释放阻塞后仅发送正在处理及最新待发的两对报文,没有旧结果积压;停止时丢弃待发结果,等待 SDK 返回后释放设备。单通道失败不抑制另一通道发送。
|
||||
- `./tests/run_session.sh` 通过:人工图像的 H.264 编解码、录制/回灌顺序、CAN 快照保存、停止收尾与损坏数据检查。这不是摄像头/TensorRT 验证,也不证明实际推理循环的总线发送。
|
||||
- `cmake --build build -j2` 通过;`git diff --check` 通过。
|
||||
- `--help` 显示新的发送规则并返回 0;SDK 初始化同时报告 NvRmMem/Nvmap 设备不可用,不能据此声称 GPU 推理可运行。
|
||||
|
||||
## 板端故障注入与真实硬件操作
|
||||
|
||||
后续经授权在宿主 Orin 上完成真实摄像头、TensorRT 和 USB-CAN 性能对照。原同步发送路径约 0.33 FPS,SDK 双通道等待约 3 秒且均失败;改为后台发送后约 30 FPS,CAN 仍报告发送失败。详见 [FPS 修复记录](ORIN_FPS_REPORT.md)。未进行板端故障注入或物理拔插。未修改启动配置和参数,测试前后均无常驻程序。
|
||||
|
||||
未覆盖:真实总线成功发送与 ACK、双通道线上载荷、回灌时物理总线无发送、断连重连、长期运行。模拟通过及 FPS 恢复不代表这些项目已验收。
|
||||
|
||||
## 文件同步与版本状态
|
||||
|
||||
已更新工作区独立包 `orin/build/camera_risk`,刷新 `orin/SHA256SUMS`,完整清单校验通过;源码构建文件与独立包二进制 SHA256 相同:
|
||||
|
||||
`a84680513aa61fb2af353f97e6369bbef80c9f12521ecd88454e72fcecab447f`
|
||||
|
||||
本次改动未提交、未推送,未进行 Git 发布。当前本地 HEAD 为 `515a9f32d8c0ebf83503d4d240ffe917dc09950c`;本地保存的 `origin/inference-only` 引用为 `0192fccbf06cb928eac098b3dfad32abf0fc370d`,未联网核对远端最新值。未修改共享采集、相机停止或恢复逻辑;其他两分支没有本次修改的 `src/can_bus.cpp`,未同步。已按原命令 `./run_camera.sh` 启动正式程序,核对 `/proc/<pid>/exe`、构建文件和独立包哈希一致,SIGTERM 约 1.267 秒正常退出,恢复测试前无常驻进程状态。
|
||||
+93
-14
@@ -18,6 +18,11 @@ std::deque<ZCANDataObj> incoming;
|
||||
std::atomic<int> opens{0},closes{0},resets{0},mergeCalls{0},txCalls{0},rxCalls{0};
|
||||
std::atomic<bool> unavailable{false},initFailure{false},receiveFailure{false},txFailure{false};
|
||||
ZCAN_20_MSG sentCan{};ZCAN_FD_MSG sentFd{};
|
||||
std::vector<ZCAN_20_MSG> sentCanFrames;
|
||||
int failedTxChannel=-1;
|
||||
std::mutex txMutex;
|
||||
std::condition_variable txWake;
|
||||
std::atomic<bool> blockTx{false},txBlocked{false};
|
||||
void check(bool ok,char const* message){if(!ok)throw std::runtime_error(message);}
|
||||
template<class F> void waitFor(F f) {
|
||||
auto deadline=std::chrono::steady_clock::now()+2s;
|
||||
@@ -27,6 +32,8 @@ template<class F> void rejects(F f){bool failed=false;try{f();}catch(std::except
|
||||
void reset() {
|
||||
opens=closes=resets=mergeCalls=txCalls=rxCalls=0;
|
||||
unavailable=initFailure=receiveFailure=txFailure=false;
|
||||
sentCanFrames.clear();failedTxChannel=-1;
|
||||
blockTx=false;txBlocked=false;
|
||||
std::lock_guard<std::mutex> lock(mutex);incoming.clear();
|
||||
}
|
||||
void inject(unsigned channel,bool fd,std::uint32_t id,std::initializer_list<std::uint8_t> bytes,unsigned stamp=123) {
|
||||
@@ -42,14 +49,16 @@ extern "C" {
|
||||
U32 VCI_OpenDevice(U32 type,U32,U32){check(type==33,"Device type changed");++opens;return unavailable?0:1;}
|
||||
U32 VCI_CloseDevice(U32,U32){++closes;return 1;}
|
||||
U32 VCI_InitCAN(U32,U32,U32 port,ZCAN_INIT* init){
|
||||
check(port==0,"CH1 initialized");
|
||||
check(init->clk==60000000&&init->mode==0&&init->aset.brp==5&&init->dset.brp==1,"Reference timing not retained");
|
||||
return initFailure&&port==1?0:1;
|
||||
return initFailure?0:1;
|
||||
}
|
||||
U32 VCI_SetReference(U32,U32,U32 port,U32 ref,void* data){
|
||||
check(port==0,"CH1 configured");
|
||||
if(ref==ZCAN_CMD_SET_CHNL_RECV_MERGE){check(port==0&&*static_cast<U32*>(data)==1,"Invalid merged receive configuration");++mergeCalls;}return 1;
|
||||
}
|
||||
U32 VCI_StartCAN(U32,U32,U32){return 1;}
|
||||
U32 VCI_ResetCAN(U32,U32,U32){++resets;return 1;}
|
||||
U32 VCI_StartCAN(U32,U32,U32 port){check(port==0,"CH1 started");return 1;}
|
||||
U32 VCI_ResetCAN(U32,U32,U32 port){check(port==0,"CH1 reset");++resets;return 1;}
|
||||
U32 VCI_ReceiveData(U32,U32,U32 port,ZCANDataObj* out,U32 capacity,U32){
|
||||
check(port==0,"Multiple merged receive readers");++rxCalls;
|
||||
if(receiveFailure)return std::numeric_limits<U32>::max();
|
||||
@@ -58,10 +67,18 @@ U32 VCI_ReceiveData(U32,U32,U32 port,ZCANDataObj* out,U32 capacity,U32){
|
||||
U32 count=0;while(!incoming.empty()&&count<capacity){out[count++]=incoming.front();incoming.pop_front();}return count;
|
||||
}
|
||||
U32 VCI_Transmit(U32,U32,U32 port,ZCAN_20_MSG* message,U32 count){
|
||||
check(count==1&&port==message->hdr.chn,"Invalid CAN send call");++txCalls;sentCan=*message;return txFailure?0:1;
|
||||
check(port==0&&count==1&&port==message->hdr.chn,"Invalid CAN send call");++txCalls;sentCan=*message;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(txMutex);sentCanFrames.push_back(*message);
|
||||
if(blockTx) {
|
||||
txBlocked=true;txWake.notify_all();
|
||||
if(!txWake.wait_for(lock,2s,[]{return !blockTx.load();}))throw std::runtime_error("Test send gate timed out");
|
||||
}
|
||||
}
|
||||
return txFailure||int(port)==failedTxChannel?0:1;
|
||||
}
|
||||
U32 VCI_TransmitFD(U32,U32,U32 port,ZCAN_FD_MSG* message,U32 count){
|
||||
check(count==1&&port==message->hdr.chn,"Invalid CANFD send call");++txCalls;sentFd=*message;return txFailure?0:1;
|
||||
check(port==0&&count==1&&port==message->hdr.chn,"Invalid CANFD send call");++txCalls;sentFd=*message;return txFailure?0:1;
|
||||
}
|
||||
}
|
||||
int main()try {
|
||||
@@ -75,18 +92,21 @@ int main()try {
|
||||
}check(closes==0,"Closed unopened device");
|
||||
reset();initFailure=true;{
|
||||
CanBus bus({});bus.start();check(bus.snapshot().status=="UNAVAILABLE","Partial failure not reported");
|
||||
}check(closes==1&&resets==1,"Partial initialization leaked device");
|
||||
}check(closes==1&&resets==0,"Partial initialization leaked device");
|
||||
reset();{
|
||||
CanConfig c;c.timeout_ms=150;CanBus bus(c);bus.start();
|
||||
check(mergeCalls==1,"Merged receive configured more than once");
|
||||
CanBus duplicate(c);rejects([&]{duplicate.start();});
|
||||
inject(0,false,0x391,{1,37,128,255},123);
|
||||
inject(1,true,0x391,{4,25,76,100},456);
|
||||
waitFor([&]{return bus.snapshot().received==1;});
|
||||
check(bus.snapshot().channels[0].positions==std::array<int,4>{1,37,128,255},"Four byte positions overlap");
|
||||
inject(1,true,0x391,{99,99,99,99},789);
|
||||
inject(0,true,0x391,{31,25,76,100},456);
|
||||
waitFor([&]{return bus.snapshot().received==2;});
|
||||
auto s=bus.snapshot();
|
||||
check(s.channels[0].positions==std::array<int,4>{1,37,128,255},"Four byte positions overlap");
|
||||
check(s.channels[1].positions==std::array<int,4>{4,25,76,100},"Channels mixed");
|
||||
check(s.channels[0].valid&&s.channels[1].valid&&s.channels[1].fd&&s.channels[1].device_timestamp_us==456,"Freshness/frame metadata wrong");
|
||||
check(s.channels[0].positions==std::array<int,4>{31,25,76,100},"Actual positions not refreshed");
|
||||
check(s.channels[1].received==0&&!s.channels[1].valid&&s.channels[1].positions==std::array<int,4>{-1,-1,-1,-1},"CH1 input accepted");
|
||||
check(s.channels[0].valid&&s.channels[0].fd&&s.channels[0].device_timestamp_us==456,"Freshness/frame metadata wrong");
|
||||
auto timestamp=s.channels[0].received_monotonic_ns;
|
||||
inject(0,false,0x390,{9,9,9,9});inject(0,false,0x391,{99,99,99});
|
||||
waitFor([&]{return bus.snapshot().received==4;});
|
||||
@@ -95,18 +115,77 @@ int main()try {
|
||||
inject(0,false,0x391,{0,1,2,3});waitFor([&]{return bus.snapshot().channels[0].valid;});
|
||||
check(bus.send({0,0x321,false,false,false,{1,2,3}}),"CAN send failed");
|
||||
check(sentCan.hdr.len==3&&!sentCan.hdr.inf.sef&&sentCan.dat[2]==3,"CAN encoding incorrect");
|
||||
CanFrame fd{1,0x12345,true,true,true,std::vector<std::uint8_t>(64,0xa5)};
|
||||
CanFrame fd{0,0x12345,true,true,true,std::vector<std::uint8_t>(64,0xa5)};
|
||||
check(bus.send(fd)&&sentFd.hdr.len==64&&sentFd.hdr.inf.brs&&sentFd.hdr.inf.sef&&sentFd.dat[63]==0xa5,"CANFD encoding incorrect");
|
||||
fd.data.resize(9);rejects([&]{bus.send(fd);});
|
||||
rejects([&]{bus.send({2,0x391,false,false,false,{}});});
|
||||
rejects([&]{bus.send({1,0x391,false,false,false,{}});});
|
||||
rejects([&]{bus.send({0,0x800,false,false,false,{}});});
|
||||
rejects([&]{bus.send({0,0x391,false,false,true,{}});});
|
||||
txFailure=true;check(!bus.send({}),"SDK send failure ignored");
|
||||
check(bus.snapshot().transmitted==2&&bus.snapshot().transmit_errors==1,"TX counters wrong");
|
||||
receiveFailure=true;waitFor([&]{return bus.snapshot().status=="ERROR";});
|
||||
check(!bus.snapshot().channels[0].valid&&!bus.send({}),"Receive error left bus live");
|
||||
bus.requestStop();bus.join();check(closes==1&&resets==2,"Shutdown did not release both channels");
|
||||
bus.requestStop();bus.join();check(closes==1&&resets==1,"Shutdown did not release CH0");
|
||||
check(!bus.send({}),"Transmission after stop");
|
||||
}check(closes==1,"Device closed twice");
|
||||
std::cout<<"PASS CAN/CANFD RX decoding, independent channels, expiry/recovery, TX validation, errors and lifecycle cleanup\n";
|
||||
reset();{
|
||||
CanBus bus({});bus.start();
|
||||
for(auto level:{"clear","watch","warning","danger","unavailable","unknown","","clear"}) {
|
||||
auto sent=bus.sendRisk(level);
|
||||
check(sent==std::array<bool,2>{true,false},"Risk must use CH0 only");
|
||||
}
|
||||
check(sentCanFrames.size()==8,"Risk must send exactly once per channel per result");
|
||||
for(std::size_t i=0;i<sentCanFrames.size();++i) {
|
||||
auto const& message=sentCanFrames[i];
|
||||
check(message.hdr.id==0x1f6&&message.hdr.len==8&&message.hdr.chn==0,
|
||||
"Risk ID, DLC or channel incorrect");
|
||||
check(!message.hdr.inf.fmt&&!message.hdr.inf.sef&&!message.hdr.inf.brs&&!message.hdr.inf.sdf,
|
||||
"Risk must use a standard classic CAN data frame");
|
||||
check(message.dat[0]==(i==0||i==7?0x00:0x11),"Risk must fill both nibbles and reset both for clear");
|
||||
for(int byte=1;byte<8;++byte)check(message.dat[byte]==0,"Risk reserved bits not zero");
|
||||
}
|
||||
failedTxChannel=0;
|
||||
check(bus.sendRisk("danger")==std::array<bool,2>{false,false},"CH0 failure reported success");
|
||||
failedTxChannel=-1;
|
||||
check(bus.sendRisk("clear")==std::array<bool,2>{true,false},"CH0 did not recover after send failure");
|
||||
check(bus.snapshot().transmitted==9&&bus.snapshot().transmit_errors==1,"Risk TX counters wrong");
|
||||
auto calls=txCalls.load();bus.requestStop();
|
||||
check(bus.sendRisk("danger")==std::array<bool,2>{false,false}&&txCalls==calls,"Risk sent after stop");
|
||||
}
|
||||
reset();{
|
||||
CanConfig c;c.enabled=false;CanBus bus(c);bus.start();
|
||||
check(bus.sendRisk("danger")==std::array<bool,2>{false,false}&&txCalls==0&&opens==0,
|
||||
"Disabled risk send accessed hardware");
|
||||
check(!bus.queueRisk("danger"),"Disabled bus accepted asynchronous risk");
|
||||
}
|
||||
reset();{
|
||||
CanBus bus({});check(!bus.queueRisk("danger"),"Unstarted bus accepted risk");bus.start();
|
||||
// A blocked hardware SDK call must not stall inference submission or snapshots.
|
||||
blockTx=true;check(bus.queueRisk("danger"),"Risk queue rejected ready bus");
|
||||
waitFor([]{return txBlocked.load();});
|
||||
auto start=std::chrono::steady_clock::now();
|
||||
for(int i=0;i<1000;++i)check(bus.queueRisk(i%2?"clear":"warning"),"Risk queue stalled/rejected update");
|
||||
check(bus.snapshot().transmitted==0,"Blocked SDK falsely reported delivery");
|
||||
check(std::chrono::steady_clock::now()-start<250ms,"Risk submission waited for the SDK");
|
||||
{std::lock_guard<std::mutex> lock(txMutex);blockTx=false;}txWake.notify_all();
|
||||
waitFor([&]{return bus.snapshot().transmitted==2;});
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(txMutex);
|
||||
check(sentCanFrames.size()==2,"Stale pending results accumulated");
|
||||
for(int i=0;i<2;++i)check(sentCanFrames[i].hdr.chn==0&&
|
||||
sentCanFrames[i].dat[0]==(i==0?0x11:0x00),"Async risk or latest result incorrect");
|
||||
}
|
||||
failedTxChannel=0;
|
||||
check(bus.queueRisk("unavailable"),"Ready queue rejected unavailable risk");
|
||||
waitFor([&]{auto s=bus.snapshot();return s.transmitted==2&&s.transmit_errors==1;});
|
||||
failedTxChannel=-1;txBlocked=false;blockTx=true;
|
||||
check(bus.queueRisk("clear"),"Ready queue rejected clear risk");waitFor([]{return txBlocked.load();});
|
||||
check(bus.queueRisk("danger"),"Queue rejected pending stop fixture");
|
||||
auto calls=txCalls.load();bus.requestStop();
|
||||
check(!bus.queueRisk("danger"),"Stopped bus accepted risk");
|
||||
{std::lock_guard<std::mutex> lock(txMutex);blockTx=false;}txWake.notify_all();
|
||||
bus.join();check(txCalls==calls,"Stop sent a pending result or second channel");
|
||||
check(closes==1&&resets==1,"Async shutdown leaked hardware");
|
||||
}
|
||||
std::cout<<"PASS CAN/CANFD RX decoding, CH0-only reception, expiry/recovery, TX validation, binary risk 0x1F6 on CH0 only, errors and lifecycle cleanup\n";
|
||||
}catch(std::exception const& e){std::cerr<<e.what()<<'\n';return 1;}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"running_sha256": "418637a35f6ad05441578db1333e15714262a9c0ee088f10c2dd84923471cd0f",
|
||||
"build_sha256": "418637a35f6ad05441578db1333e15714262a9c0ee088f10c2dd84923471cd0f",
|
||||
"stop_seconds": 0.8675570619998325,
|
||||
"exit_code": 0,
|
||||
"command": "./run_camera.sh",
|
||||
"first_sample": {
|
||||
"seconds": 0.25,
|
||||
"rx": 8833,
|
||||
"tx": 5,
|
||||
"tx_errors": 0,
|
||||
"valid": true,
|
||||
"received_391": 4,
|
||||
"age_ms": 147.083,
|
||||
"positions": [
|
||||
26,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"last_sample": {
|
||||
"seconds": 10.0,
|
||||
"rx": 35669,
|
||||
"tx": 299,
|
||||
"tx_errors": 0,
|
||||
"valid": true,
|
||||
"received_391": 33,
|
||||
"age_ms": 84.5888,
|
||||
"positions": [
|
||||
25,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"sample_count": 40,
|
||||
"max_observed_age_ms": 913.11,
|
||||
"observed_positions": [
|
||||
[
|
||||
25,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
26,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
],
|
||||
"checks": "real camera/TensorRT/CH0/renderer; not CAN fault injection",
|
||||
"local_head": "515a9f32d8c0ebf83503d4d240ffe917dc09950c",
|
||||
"remote_inference_only": "0192fccbf06cb928eac098b3dfad32abf0fc370d",
|
||||
"source_state": "uncommitted workspace at hardware verification",
|
||||
"package_sha256": "418637a35f6ad05441578db1333e15714262a9c0ee088f10c2dd84923471cd0f"
|
||||
}
|
||||
Reference in New Issue
Block a user