Add USBCANFD transport and visualize 0x391 door feedback
This commit is contained in:
+9
-1
@@ -13,11 +13,19 @@ find_path(TENSORRT_INCLUDE_DIR NvInfer.h PATH_SUFFIXES aarch64-linux-gnu x86_64-
|
|||||||
find_library(TENSORRT_LIBRARY nvinfer REQUIRED)
|
find_library(TENSORRT_LIBRARY nvinfer REQUIRED)
|
||||||
add_library(camera_risk_core
|
add_library(camera_risk_core
|
||||||
src/trt_engine.cpp src/pose_engine.cu src/gpu_preprocess.cu src/uyvy_converter.cu
|
src/trt_engine.cpp src/pose_engine.cu src/gpu_preprocess.cu src/uyvy_converter.cu
|
||||||
src/detector.cpp src/door_risk.cpp src/camera.cpp src/camera_pipeline.cpp src/inference_session.cpp src/serialization.cpp src/output_writer.cpp src/result_renderer.cpp src/config.cpp)
|
src/can_bus.cpp src/detector.cpp src/door_risk.cpp src/camera.cpp src/camera_pipeline.cpp src/inference_session.cpp src/serialization.cpp src/output_writer.cpp src/result_renderer.cpp src/config.cpp)
|
||||||
target_include_directories(camera_risk_core PUBLIC include third_party ${TENSORRT_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS})
|
target_include_directories(camera_risk_core PUBLIC include third_party ${TENSORRT_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS})
|
||||||
target_link_libraries(camera_risk_core PUBLIC ${TENSORRT_LIBRARY} ${OpenCV_LIBS} Threads::Threads CUDA::cudart CUDA::nppc CUDA::nppig CUDA::nppidei CUDA::nppicc)
|
target_link_libraries(camera_risk_core PUBLIC ${TENSORRT_LIBRARY} ${OpenCV_LIBS} Threads::Threads CUDA::cudart CUDA::nppc CUDA::nppig CUDA::nppidei CUDA::nppicc)
|
||||||
|
target_include_directories(camera_risk_core SYSTEM PRIVATE third_party/zlg)
|
||||||
target_compile_options(camera_risk_core PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-Wall;-Wextra;-Wpedantic>)
|
target_compile_options(camera_risk_core PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-Wall;-Wextra;-Wpedantic>)
|
||||||
add_library(camera_risk_visualizer src/visualizer.cpp)
|
add_library(camera_risk_visualizer src/visualizer.cpp)
|
||||||
target_link_libraries(camera_risk_visualizer PUBLIC camera_risk_core)
|
target_link_libraries(camera_risk_visualizer PUBLIC camera_risk_core)
|
||||||
add_executable(camera_risk src/main.cpp)
|
add_executable(camera_risk src/main.cpp)
|
||||||
target_link_libraries(camera_risk PRIVATE camera_risk_visualizer)
|
target_link_libraries(camera_risk PRIVATE camera_risk_visualizer)
|
||||||
|
|
||||||
|
# Reference USBCANFD SDK is shipped with the standalone Orin package.
|
||||||
|
target_link_libraries(camera_risk_core PUBLIC "${CMAKE_SOURCE_DIR}/orin/lib/libusbcanfd.so"
|
||||||
|
"-Wl,--no-as-needed" "${CMAKE_SOURCE_DIR}/orin/lib/libusb-1.0.so" "-Wl,--as-needed")
|
||||||
|
set_target_properties(camera_risk PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN/../orin/lib;/usr/local/cuda/lib64")
|
||||||
|
# Keep the bundled SDK/libusb pair together even with a desktop LD_LIBRARY_PATH.
|
||||||
|
target_link_options(camera_risk PRIVATE "-Wl,--disable-new-dtags")
|
||||||
|
|||||||
@@ -135,3 +135,44 @@ Ctrl+C / SIGTERM 会封装已写入的视频,`CameraPipeline::join()` 返回
|
|||||||
编解码及会话回归:`./tests/run_session.sh`,无需摄像头或 TensorRT。真实摄像头与 TensorRT 验证脚本:`python3 tests/orin_session_test.py --binary build/camera_risk --config config/camera.json --output /tmp/new-session-test`;输出目录必须不存在,脚本不会停止已有部署进程。
|
编解码及会话回归:`./tests/run_session.sh`,无需摄像头或 TensorRT。真实摄像头与 TensorRT 验证脚本:`python3 tests/orin_session_test.py --binary build/camera_risk --config config/camera.json --output /tmp/new-session-test`;输出目录必须不存在,脚本不会停止已有部署进程。
|
||||||
|
|
||||||
本次实机结果与未覆盖范围见 [H.264 会话测试报告](tests/ORIN_SESSION_REPORT.md)。
|
本次实机结果与未覆盖范围见 [H.264 会话测试报告](tests/ORIN_SESSION_REPORT.md)。
|
||||||
|
|
||||||
|
## 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,需检查设备后重启程序。
|
||||||
|
|
||||||
|
首次使用 USB-CAN 时,若节点为 root:root 且普通用户不可写,可运行 `./scripts/enable_can_access.sh`(需要 sudo)。它只为 `3068:0009` 适配器安装 `plugdev` 组读写规则并刷新设备权限;运行用户须属于 `plugdev`,新加组后需重新登录。无需以 root 运行整个推理程序。
|
||||||
|
|
||||||
|
`0x391` 四个信号按参考工程注释中的四个连续字节解析:
|
||||||
|
|
||||||
|
| 字节(从 0 开始) | 预览标签 | 信号 |
|
||||||
|
|---|---|---|
|
||||||
|
| 0 | LF | 左前门实际位置反馈 |
|
||||||
|
| 1 | RF | 右前门实际位置反馈 |
|
||||||
|
| 2 | LR | 左后门实际位置反馈 |
|
||||||
|
| 3 | RR | 右后门实际位置反馈 |
|
||||||
|
|
||||||
|
显示值是无符号原始字节 0–255,尚无物理单位、缩放或无效值定义,因此不标为百分比、不把 255 擅自视为无效。接收 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 只提供状态显示和下游数据,本次没有新增基于门位置的推理启停或风险门控。
|
||||||
|
|
||||||
|
下游通过 `pipeline.canStatus()` 读取当前状态;`pipeline.latest()->can` 是该推理结果生成时的快照。按需发送使用线程安全的 `pipeline.sendCan()`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cms::CanFrame frame;
|
||||||
|
frame.channel = 0; // CH0 或 CH1
|
||||||
|
frame.id = 0x123; // 按实际协议设置
|
||||||
|
frame.data = {0x01, 0x02}; // 示例数据,应替换为实际报文
|
||||||
|
bool sent = pipeline.sendCan(frame);
|
||||||
|
|
||||||
|
// CANFD:frame.fd = true;可按协议设置 extended、bitrate_switch。
|
||||||
|
// CAN 数据长度 0–8;CANFD 长度 0–8、12、16、20、24、32、48、64。
|
||||||
|
// 标准 ID 最大 0x7FF,扩展 ID 最大 0x1FFFFFFF。
|
||||||
|
```
|
||||||
|
|
||||||
|
非法通道/ID/长度/标志抛出 `std::invalid_argument`;设备未就绪或 SDK 发送失败返回 false,发送计数以 SDK 返回为准。接口不自动填充/截断数据,不自动发送周期或风险控制报文。发送调用与设备关闭互斥;双通道接收保持独立线程运行。
|
||||||
|
|
||||||
|
结果 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 接收。
|
||||||
|
|||||||
@@ -18,5 +18,11 @@
|
|||||||
"warning_relative": 0.3,
|
"warning_relative": 0.3,
|
||||||
"release_relative": 0.45,
|
"release_relative": 0.45,
|
||||||
"calibration_verified": false
|
"calibration_verified": false
|
||||||
|
},
|
||||||
|
"can": {
|
||||||
|
"enabled": true,
|
||||||
|
"device_index": 0,
|
||||||
|
"timeout_ms": 1000,
|
||||||
|
"termination": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace cms {
|
|||||||
struct PipelineConfig {
|
struct PipelineConfig {
|
||||||
static constexpr char const* session_variant="inference-only";
|
static constexpr char const* session_variant="inference-only";
|
||||||
CameraConfig camera;
|
CameraConfig camera;
|
||||||
|
CanConfig can;
|
||||||
OutputOptions output;
|
OutputOptions output;
|
||||||
std::filesystem::path record_session,replay_session;
|
std::filesystem::path record_session,replay_session;
|
||||||
std::filesystem::path engines,door_mask;
|
std::filesystem::path engines,door_mask;
|
||||||
@@ -36,6 +37,8 @@ public:
|
|||||||
CameraPipeline& operator=(CameraPipeline const&)=delete;
|
CameraPipeline& operator=(CameraPipeline const&)=delete;
|
||||||
void start();
|
void start();
|
||||||
FrameResultPtr latest() const;
|
FrameResultPtr latest() const;
|
||||||
|
CanSnapshot canStatus() const;
|
||||||
|
bool sendCan(CanFrame const& frame);
|
||||||
bool running() const noexcept;
|
bool running() const noexcept;
|
||||||
void requestStop() noexcept;
|
void requestStop() noexcept;
|
||||||
void join();
|
void join();
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "can_types.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace cms {
|
||||||
|
// One lifecycle per instance; only one owner per device index in a process.
|
||||||
|
// start() must run after StopSignals so the receiver inherits its signal mask.
|
||||||
|
// Missing hardware is reported by snapshot(), and does not stop inference.
|
||||||
|
class CanBus {
|
||||||
|
public:
|
||||||
|
explicit CanBus(CanConfig config);
|
||||||
|
~CanBus();
|
||||||
|
CanBus(CanBus const&)=delete;
|
||||||
|
CanBus& operator=(CanBus const&)=delete;
|
||||||
|
void start();
|
||||||
|
void requestStop() noexcept;
|
||||||
|
void join() noexcept;
|
||||||
|
CanSnapshot snapshot() const;
|
||||||
|
// 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);
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cms {
|
||||||
|
struct CanConfig {
|
||||||
|
bool enabled{true};
|
||||||
|
int device_index{0};
|
||||||
|
int timeout_ms{1000};
|
||||||
|
bool termination{true};
|
||||||
|
};
|
||||||
|
struct CanDoorSignals {
|
||||||
|
// Raw unsigned bytes: left front, right front, left rear, right rear.
|
||||||
|
// No scale/unit is specified in the reference protocol.
|
||||||
|
std::array<int,4> positions{{-1,-1,-1,-1}};
|
||||||
|
bool valid{},fd{},extended{};
|
||||||
|
std::uint64_t received{},received_monotonic_ns{};
|
||||||
|
std::uint32_t device_timestamp_us{};
|
||||||
|
double age_ms{-1};
|
||||||
|
};
|
||||||
|
struct CanSnapshot {
|
||||||
|
std::string source{"live"},status{"DISABLED"};
|
||||||
|
std::array<CanDoorSignals,2> channels;
|
||||||
|
std::uint64_t received{},transmitted{},transmit_errors{},receive_errors{};
|
||||||
|
};
|
||||||
|
struct CanFrame {
|
||||||
|
int channel{};
|
||||||
|
std::uint32_t id{};
|
||||||
|
bool fd{},extended{},bitrate_switch{};
|
||||||
|
std::vector<std::uint8_t> data;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// Public downstream contract. Owns its data; no GPU, JSON or network handles.
|
// Public downstream contract. Owns its data; no GPU, JSON or network handles.
|
||||||
#include "image.hpp"
|
#include "image.hpp"
|
||||||
|
#include "can_types.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -67,6 +68,7 @@ struct FrameResult {
|
|||||||
std::vector<int> track_ids;
|
std::vector<int> track_ids;
|
||||||
std::vector<std::array<double,4>> boxes;
|
std::vector<std::array<double,4>> boxes;
|
||||||
RiskResult risk;
|
RiskResult risk;
|
||||||
|
CanSnapshot can;
|
||||||
CaptureMetadata capture;
|
CaptureMetadata capture;
|
||||||
StageTiming timing;
|
StageTiming timing;
|
||||||
std::vector<PersonResult> persons;
|
std::vector<PersonResult> persons;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "frame_result.hpp"
|
#include "frame_result.hpp"
|
||||||
#include "door_risk.hpp"
|
#include "door_risk.hpp"
|
||||||
namespace cms { Json serializeResult(FrameResult const& result); }
|
namespace cms {
|
||||||
|
Json serializeCan(CanSnapshot const& can);
|
||||||
|
CanSnapshot deserializeCan(Json const& value);
|
||||||
|
Json serializeResult(FrameResult const& result);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
namespace cms {
|
namespace cms {
|
||||||
class CameraPipeline;
|
class CameraPipeline;
|
||||||
// Pure drawing from the immutable result. Never re-runs inference.
|
// Pure drawing from the immutable result. Never re-runs inference.
|
||||||
cv::Mat renderResult(FrameResult const& result);
|
cv::Mat renderResult(FrameResult const& result,CanSnapshot const* liveCan=nullptr);
|
||||||
|
void renderCanOverlay(cv::Mat& image,CanSnapshot const& can);
|
||||||
// Optional observer: closing the window returns without stopping the pipeline.
|
// Optional observer: closing the window returns without stopping the pipeline.
|
||||||
void runLocalVisualization(CameraPipeline const& pipeline);
|
void runLocalVisualization(CameraPipeline const& pipeline);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,3 +51,11 @@ bin/drivers 中 GMSL 模块对应内核 5.10.120-tegra。初始化脚本检查
|
|||||||
```
|
```
|
||||||
|
|
||||||
录制目录必须不存在。会话保存一份 H.264 视频、逐帧过程 JSON、推理配置与门区副本;回灌解码视频并重新执行 TensorRT 推理,不打开摄像头,结束后自动退出。使用当前配置指向的引擎;有损压缩会引起推理结果差异。需要 OpenCV FFmpeg 的 H.264 编码支持。强制退出或断电导致未封装完成的会话不可回灌;正常 Ctrl+C / SIGTERM 会完成封装。详细格式与限制见工程 README。
|
录制目录必须不存在。会话保存一份 H.264 视频、逐帧过程 JSON、推理配置与门区副本;回灌解码视频并重新执行 TensorRT 推理,不打开摄像头,结束后自动退出。使用当前配置指向的引擎;有损压缩会引起推理结果差异。需要 OpenCV FFmpeg 的 H.264 编码支持。强制退出或断电导致未封装完成的会话不可回灌;正常 Ctrl+C / SIGTERM 会完成封装。详细格式与限制见工程 README。
|
||||||
|
|
||||||
|
## CAN 状态与发送接口
|
||||||
|
|
||||||
|
实时运行默认接收 USBCANFD 双通道(500 kbps / 2 Mbps)。预览新增 `0x391` 的 LF/RF/LR/RR 四门原始位置值,每通道分别显示,并标记新鲜度和收发计数。`--no-can` 可关闭;设备缺失不停止推理。`config/camera.json` 的 `can.termination` 控制终端电阻,默认沿用参考工程的开启设置。
|
||||||
|
|
||||||
|
`lib/` 包含 SDK 与 libusb 及其 SONAME 链接。回灌使用录制的 CAN 快照,不访问总线;旧会话显示 NOT_RECORDED。按需发送通过 C++ `CameraPipeline::sendCan()`,参数与完整示例见工程 README。
|
||||||
|
|
||||||
|
USB-CAN 首次接入如出现权限不足,运行 `./scripts/enable_can_access.sh`(需要 sudo,运行用户属于 plugdev),仅为 `3068:0009` 适配器安装设备访问规则。
|
||||||
|
|||||||
+9
-3
@@ -1,11 +1,17 @@
|
|||||||
6d5e874fd27fcfd75d303d7e35d9672028af71041f52dcc82a1e83676faefa65 ./bin/drivers/max9295.ko
|
6d5e874fd27fcfd75d303d7e35d9672028af71041f52dcc82a1e83676faefa65 ./bin/drivers/max9295.ko
|
||||||
8a78d04ef8be44e9b62a5e4ee315d3d6d679e34aedff02a234bbf4d40e222933 ./bin/drivers/max9296.ko
|
8a78d04ef8be44e9b62a5e4ee315d3d6d679e34aedff02a234bbf4d40e222933 ./bin/drivers/max9296.ko
|
||||||
3da628587477cc35d5e3f8aee86e718e2ddf16808e893e00ba6e164b2307e843 ./bin/drivers/sgx-yuv-gmsl2.ko
|
3da628587477cc35d5e3f8aee86e718e2ddf16808e893e00ba6e164b2307e843 ./bin/drivers/sgx-yuv-gmsl2.ko
|
||||||
cd9711d71c6d04f40adf9434f893abe1744e9e2013f1459ddf736a80dcbbfd64 ./build/camera_risk
|
e8788d22fd6db0e3b9edc95a02322a51ca7951e1df81fea10f34db7da3143461 ./build/camera_risk
|
||||||
8a572f661e056e3916758a442e73212842bfe89361dd1b07b87de8f6ae523cf8 ./config/camera.json
|
d44dcf10df43c7bcd73b7f2b64e4117d207435a5218c2146bfb659566cb320ba ./config/camera.json
|
||||||
9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 ./config/door_mask.json
|
9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 ./config/door_mask.json
|
||||||
ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 ./engines/rtmw_l_384.engine
|
ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 ./engines/rtmw_l_384.engine
|
||||||
f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 ./engines/yolo11s.engine
|
f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 ./engines/yolo11s.engine
|
||||||
011e8d1e9b735536630a26cb0ee9930f4f6ae52052e26427156fb4d9a6f96e83 ./README.md
|
8dae8c557deadbe6bd7241c0dcd7025cb007077ee39a79b115e953a2b22cd9c1 ./lib/libusb-1.0.so
|
||||||
|
8dae8c557deadbe6bd7241c0dcd7025cb007077ee39a79b115e953a2b22cd9c1 ./lib/libusb-1.0.so.0
|
||||||
|
ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 ./lib/libusbcanfd.so
|
||||||
|
ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 ./lib/libusbcanfd.so.1.0.12
|
||||||
|
bb61f669d8b471c5ca42045b31a0e6fd300235da37a80893ae229f37deb1b7b9 ./README.md
|
||||||
7f42b0e7f9396e0d82313a892fc62c153261dab1206a206a466bce6376f58a36 ./run.sh
|
7f42b0e7f9396e0d82313a892fc62c153261dab1206a206a466bce6376f58a36 ./run.sh
|
||||||
|
41ebe2dcf57674232ca137d024e99447b467049cb1c63a284daae274296cb78c ./scripts/99-usbcanfd.rules
|
||||||
|
920102fc5e91bde9b78f6f7c5cb2ff8761a0c39b63c7a28b3347d5de5fe7c60a ./scripts/enable_can_access.sh
|
||||||
54e1d63e3aa72ad9b056a224d55968d4947ca6703a8d1c405570de5707a7e7ef ./scripts/initialize_camera.sh
|
54e1d63e3aa72ad9b056a224d55968d4947ca6703a8d1c405570de5707a7e7ef ./scripts/initialize_camera.sh
|
||||||
|
|||||||
Binary file not shown.
@@ -18,5 +18,11 @@
|
|||||||
"warning_relative": 0.3,
|
"warning_relative": 0.3,
|
||||||
"release_relative": 0.45,
|
"release_relative": 0.45,
|
||||||
"calibration_verified": false
|
"calibration_verified": false
|
||||||
|
},
|
||||||
|
"can": {
|
||||||
|
"enabled": true,
|
||||||
|
"device_index": 0,
|
||||||
|
"timeout_ms": 1000,
|
||||||
|
"termination": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
libusb-1.0.so
|
||||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
libusbcanfd.so
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# USBCANFD adapter used by the reference project. Grant the plugdev group access.
|
||||||
|
SUBSYSTEM=="usb", ATTR{idVendor}=="3068", ATTR{idProduct}=="0009", MODE="0660", GROUP="plugdev", TAG+="uaccess"
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
root_command() { if [[ $EUID -eq 0 ]]; then "$@"; else sudo -n "$@"; fi; }
|
||||||
|
getent group plugdev >/dev/null || { echo 'Create a plugdev group and add the runtime user before running this script.' >&2; exit 1; }
|
||||||
|
root_command install -m 0644 "$root/99-usbcanfd.rules" /etc/udev/rules.d/99-usbcanfd.rules
|
||||||
|
root_command udevadm control --reload-rules
|
||||||
|
root_command udevadm trigger --subsystem-match=usb --attr-match=idVendor=3068 --attr-match=idProduct=0009 --action=add
|
||||||
|
root_command udevadm settle
|
||||||
|
printf '%s\n' 'USBCANFD 3068:0009 access enabled for plugdev. Newly added group members must log in again.'
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$root/orin/scripts/enable_can_access.sh" "$@"
|
||||||
@@ -7,6 +7,6 @@ cp -p "$root/config/door_mask.json" "$root/orin/config/"
|
|||||||
sed 's|../orin/engines|../engines|' "$root/config/camera.json" > "$root/orin/config/camera.json"
|
sed 's|../orin/engines|../engines|' "$root/config/camera.json" > "$root/orin/config/camera.json"
|
||||||
(
|
(
|
||||||
cd "$root/orin"
|
cd "$root/orin"
|
||||||
find . -type f ! -path './output/*' ! -path './outputs/*' ! -path './logs/*' ! -name SHA256SUMS -print0 |
|
find . \( -type f -o -type l \) ! -path './output/*' ! -path './outputs/*' ! -path './logs/*' ! -name SHA256SUMS -print0 |
|
||||||
sort -z | xargs -0 sha256sum > SHA256SUMS
|
sort -z | xargs -0 sha256sum > SHA256SUMS
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "door_risk.hpp"
|
#include "door_risk.hpp"
|
||||||
#include "records.hpp"
|
#include "records.hpp"
|
||||||
#include "inference_session.hpp"
|
#include "inference_session.hpp"
|
||||||
|
#include "can_bus.hpp"
|
||||||
|
#include "serialization.hpp"
|
||||||
#include "result_conversion.hpp"
|
#include "result_conversion.hpp"
|
||||||
#include <opencv2/core.hpp>
|
#include <opencv2/core.hpp>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -60,6 +62,7 @@ struct CameraPipeline::Impl {
|
|||||||
std::unique_ptr<DoorRisk> risk;
|
std::unique_ptr<DoorRisk> risk;
|
||||||
std::shared_ptr<const BinaryMask> doorPixels;
|
std::shared_ptr<const BinaryMask> doorPixels;
|
||||||
std::unique_ptr<Camera> camera;
|
std::unique_ptr<Camera> camera;
|
||||||
|
std::unique_ptr<CanBus> can;
|
||||||
std::thread captureThread,detectorThread,inferenceThread;
|
std::thread captureThread,detectorThread,inferenceThread;
|
||||||
mutable std::mutex stateMutex;
|
mutable std::mutex stateMutex;
|
||||||
std::exception_ptr failure;
|
std::exception_ptr failure;
|
||||||
@@ -70,6 +73,8 @@ struct CameraPipeline::Impl {
|
|||||||
|
|
||||||
explicit Impl(PipelineConfig c):config(std::move(c)),captures(config.queue_capacity),detections(config.queue_capacity) {
|
explicit Impl(PipelineConfig c):config(std::move(c)),captures(config.queue_capacity),detections(config.queue_capacity) {
|
||||||
if(!config.replay_session.empty())sessionReader=std::make_unique<SessionReader>(config);
|
if(!config.replay_session.empty())sessionReader=std::make_unique<SessionReader>(config);
|
||||||
|
auto canConfig=config.can;if(sessionReader)canConfig.enabled=false;
|
||||||
|
can=std::make_unique<CanBus>(canConfig);
|
||||||
if(config.queue_capacity>8)throw std::invalid_argument("Queue capacity must be between 1 and 8");
|
if(config.queue_capacity>8)throw std::invalid_argument("Queue capacity must be between 1 and 8");
|
||||||
if(config.max_people<1||config.max_people>16||!std::isfinite(config.detector_confidence)||config.detector_confidence<=0||config.detector_confidence>=1)
|
if(config.max_people<1||config.max_people>16||!std::isfinite(config.detector_confidence)||config.detector_confidence<=0||config.detector_confidence>=1)
|
||||||
throw std::invalid_argument("Invalid detector configuration");
|
throw std::invalid_argument("Invalid detector configuration");
|
||||||
@@ -80,9 +85,11 @@ struct CameraPipeline::Impl {
|
|||||||
if(captureThread.joinable())captureThread.join();
|
if(captureThread.joinable())captureThread.join();
|
||||||
if(detectorThread.joinable())detectorThread.join();
|
if(detectorThread.joinable())detectorThread.join();
|
||||||
if(inferenceThread.joinable())inferenceThread.join();
|
if(inferenceThread.joinable())inferenceThread.join();
|
||||||
|
can->join();
|
||||||
}
|
}
|
||||||
void stop() noexcept {
|
void stop() noexcept {
|
||||||
stopped=true;active=false;
|
stopped=true;active=false;
|
||||||
|
can->requestStop();
|
||||||
// Camera stays alive until all threads have joined. start()/destruction
|
// Camera stays alive until all threads have joined. start()/destruction
|
||||||
// are owner-thread operations; requestStop() only interrupts existing work.
|
// are owner-thread operations; requestStop() only interrupts existing work.
|
||||||
{std::lock_guard<std::mutex> lock(stateMutex);if(camera)camera->requestStop();}
|
{std::lock_guard<std::mutex> lock(stateMutex);if(camera)camera->requestStop();}
|
||||||
@@ -98,6 +105,7 @@ struct CameraPipeline::Impl {
|
|||||||
void start() {
|
void start() {
|
||||||
{std::lock_guard<std::mutex> lock(stateMutex);if(started||stopped)throw std::logic_error("CameraPipeline can only be started once");started=true;}
|
{std::lock_guard<std::mutex> lock(stateMutex);if(started||stopped)throw std::logic_error("CameraPipeline can only be started once");started=true;}
|
||||||
try {
|
try {
|
||||||
|
can->start();
|
||||||
outputWriter=std::make_unique<OutputWriter>(config.output);
|
outputWriter=std::make_unique<OutputWriter>(config.output);
|
||||||
sessionWriter=std::make_unique<SessionWriter>(config);
|
sessionWriter=std::make_unique<SessionWriter>(config);
|
||||||
detector=std::make_unique<Detector>(config.engines/"yolo11s.engine",logger,config.detector_confidence,config.max_people);
|
detector=std::make_unique<Detector>(config.engines/"yolo11s.engine",logger,config.detector_confidence,config.max_people);
|
||||||
@@ -160,6 +168,12 @@ struct CameraPipeline::Impl {
|
|||||||
auto record=makeRecord(index,raw,isolated,fused,!config.record_session.empty(),false);
|
auto record=makeRecord(index,raw,isolated,fused,!config.record_session.empty(),false);
|
||||||
auto riskData=risk->evaluate(index,record);
|
auto riskData=risk->evaluate(index,record);
|
||||||
auto result=std::make_shared<FrameResult>(makeFrameResult(record,riskData,detected->frame.metadata));
|
auto result=std::make_shared<FrameResult>(makeFrameResult(record,riskData,detected->frame.metadata));
|
||||||
|
if(detected->frame.replay_record) {
|
||||||
|
auto const& recorded=detected->frame.replay_record->at("result");
|
||||||
|
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();
|
||||||
attachPoses(*result,fused);
|
attachPoses(*result,fused);
|
||||||
result->image=std::move(detected->frame.image);result->door_mask_pixels=doorPixels;
|
result->image=std::move(detected->frame.image);result->door_mask_pixels=doorPixels;
|
||||||
result->door_mask_calibration_verified=config.door_mask_calibration_verified;
|
result->door_mask_calibration_verified=config.door_mask_calibration_verified;
|
||||||
@@ -178,6 +192,17 @@ CameraPipeline::CameraPipeline(PipelineConfig config):impl_(std::make_unique<Imp
|
|||||||
CameraPipeline::~CameraPipeline()=default;
|
CameraPipeline::~CameraPipeline()=default;
|
||||||
void CameraPipeline::start(){impl_->start();}
|
void CameraPipeline::start(){impl_->start();}
|
||||||
FrameResultPtr CameraPipeline::latest()const{return impl_->results.load();}
|
FrameResultPtr CameraPipeline::latest()const{return impl_->results.load();}
|
||||||
|
CanSnapshot CameraPipeline::canStatus()const {
|
||||||
|
if(impl_->sessionReader) {
|
||||||
|
auto result=impl_->results.load();if(result)return result->can;
|
||||||
|
CanSnapshot s;s.source="replay";s.status="WAITING";return s;
|
||||||
|
}
|
||||||
|
return impl_->can->snapshot();
|
||||||
|
}
|
||||||
|
bool CameraPipeline::sendCan(CanFrame const& frame) {
|
||||||
|
if(impl_->sessionReader)throw std::logic_error("CAN transmission is disabled during replay");
|
||||||
|
return impl_->can->send(frame);
|
||||||
|
}
|
||||||
bool CameraPipeline::running()const noexcept{return impl_->active.load();}
|
bool CameraPipeline::running()const noexcept{return impl_->active.load();}
|
||||||
void CameraPipeline::requestStop()noexcept{impl_->stop();}
|
void CameraPipeline::requestStop()noexcept{impl_->stop();}
|
||||||
void CameraPipeline::join(){impl_->joinWorkers();{std::lock_guard<std::mutex> lock(impl_->stateMutex);impl_->camera.reset();}impl_->rethrowFailure();}
|
void CameraPipeline::join(){impl_->joinWorkers();{std::lock_guard<std::mutex> lock(impl_->stateMutex);impl_->camera.reset();}impl_->rethrowFailure();}
|
||||||
|
|||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
#include "can_bus.hpp"
|
||||||
|
#include "zcan.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace cms {
|
||||||
|
namespace {
|
||||||
|
constexpr unsigned deviceType=33,receiveCapacity=128;
|
||||||
|
std::mutex ownersMutex;
|
||||||
|
std::set<int> owners;
|
||||||
|
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))
|
||||||
|
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)))
|
||||||
|
throw std::invalid_argument("Invalid CAN/CANFD payload length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct CanBus::Impl {
|
||||||
|
CanConfig config;
|
||||||
|
mutable std::mutex stateMutex;
|
||||||
|
std::mutex lifecycleMutex,waitMutex;
|
||||||
|
std::condition_variable wake;
|
||||||
|
CanSnapshot state;
|
||||||
|
std::atomic<bool> stopped{false};
|
||||||
|
bool started{},opened{},owned{};
|
||||||
|
std::array<bool,2> initialized{};
|
||||||
|
std::thread receiver;
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
~Impl(){requestStop();join();}
|
||||||
|
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);
|
||||||
|
VCI_CloseDevice(deviceType,config.device_index);
|
||||||
|
opened=false;initialized.fill(false);
|
||||||
|
}
|
||||||
|
if(owned){std::lock_guard<std::mutex> lock(ownersMutex);owners.erase(config.device_index);owned=false;}
|
||||||
|
}
|
||||||
|
void start() {
|
||||||
|
std::lock_guard<std::mutex> lock(lifecycleMutex);
|
||||||
|
if(started)throw std::logic_error("CAN bus can only be started once");
|
||||||
|
started=true;
|
||||||
|
if(!config.enabled||stopped)return;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> ownerLock(ownersMutex);
|
||||||
|
if(!owners.insert(config.device_index).second)throw std::logic_error("CAN device already owned by another pipeline");
|
||||||
|
owned=true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
if(VCI_InitCAN(deviceType,config.device_index,i,&init)!=1)throw std::runtime_error("VCI_InitCAN failed");
|
||||||
|
initialized[i]=true;
|
||||||
|
U32 termination=config.termination?1:0;
|
||||||
|
if(VCI_SetReference(deviceType,config.device_index,i,CMD_CAN_TRES,&termination)!=1)
|
||||||
|
throw std::runtime_error("CAN termination configuration failed");
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
throw std::runtime_error("VCI_StartCAN failed");
|
||||||
|
setStatus("OPEN");
|
||||||
|
// Exactly one merged reader for both channels; no competing readers.
|
||||||
|
receiver=std::thread([this]{receiveLoop();});
|
||||||
|
std::cout<<"CAN device "<<config.device_index<<": CH0/CH1 enabled, 500 kbps / 2 Mbps"<<std::endl;
|
||||||
|
}catch(std::exception const& e) {
|
||||||
|
releaseDevice();setStatus("UNAVAILABLE");
|
||||||
|
std::cerr<<"CAN unavailable; inference continues: "<<e.what()<<std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void requestStop() noexcept {stopped=true;wake.notify_all();}
|
||||||
|
void join() noexcept {
|
||||||
|
requestStop();
|
||||||
|
std::lock_guard<std::mutex> lock(lifecycleMutex);
|
||||||
|
if(receiver.joinable())receiver.join();
|
||||||
|
releaseDevice();
|
||||||
|
if(config.enabled&&started)setStatus("STOPPED");
|
||||||
|
}
|
||||||
|
void receiveFrame(ZCAN_MSG_HDR const& header,std::uint8_t const* data,bool fd,unsigned channel) {
|
||||||
|
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>1||header.len>(fd?64:8)||header.id>(header.inf.sef?0x1fffffffu:0x7ffu)) {
|
||||||
|
++state.receive_errors;return;
|
||||||
|
}
|
||||||
|
++state.received;
|
||||||
|
if(header.id!=0x391)return;
|
||||||
|
if(header.len<4){++state.receive_errors;return;}
|
||||||
|
auto& s=state.channels[channel];
|
||||||
|
for(unsigned i=0;i<4;++i)s.positions[i]=data[i];
|
||||||
|
++s.received;s.received_monotonic_ns=nowNs();s.device_timestamp_us=header.ts;
|
||||||
|
s.fd=fd;s.extended=header.inf.sef;
|
||||||
|
}
|
||||||
|
void receiveLoop() noexcept {
|
||||||
|
try {
|
||||||
|
std::array<ZCANDataObj,receiveCapacity> frames{};
|
||||||
|
while(!stopped) {
|
||||||
|
auto count=VCI_ReceiveData(deviceType,config.device_index,0,frames.data(),receiveCapacity,100);
|
||||||
|
if(count>receiveCapacity) {
|
||||||
|
{std::lock_guard<std::mutex> lock(stateMutex);++state.receive_errors;state.status="ERROR";}
|
||||||
|
std::cerr<<"CAN receive failed; inference continues without live CAN"<<std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for(unsigned i=0;i<count&&!stopped;++i) {
|
||||||
|
auto const& item=frames[i];
|
||||||
|
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;}
|
||||||
|
}
|
||||||
|
if(!count){std::unique_lock<std::mutex> lock(waitMutex);wake.wait_for(lock,std::chrono::milliseconds(5),[this]{return stopped.load();});}
|
||||||
|
}
|
||||||
|
}catch(...) {setStatus("ERROR");}
|
||||||
|
}
|
||||||
|
CanSnapshot snapshot() const {
|
||||||
|
std::lock_guard<std::mutex> lock(stateMutex);auto result=state;auto now=nowNs();
|
||||||
|
for(auto& c:result.channels) {
|
||||||
|
c.age_ms=c.received?(now-c.received_monotonic_ns)*1e-6:-1;
|
||||||
|
c.valid=state.status=="OPEN"&&!stopped&&c.received&&c.age_ms<=config.timeout_ms;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
bool send(CanFrame const& f) {
|
||||||
|
validate(f);
|
||||||
|
std::lock_guard<std::mutex> lock(lifecycleMutex);
|
||||||
|
bool ready;
|
||||||
|
{std::lock_guard<std::mutex> stateLock(stateMutex);ready=state.status=="OPEN";}
|
||||||
|
unsigned sent=0;
|
||||||
|
if(opened&&!stopped&&ready) {
|
||||||
|
ZCAN_MSG_HDR header{};header.id=f.id;header.chn=f.channel;header.len=f.data.size();
|
||||||
|
header.inf.fmt=f.fd?1:0;header.inf.sef=f.extended;header.inf.brs=f.bitrate_switch;
|
||||||
|
if(f.fd) {
|
||||||
|
ZCAN_FD_MSG message{};message.hdr=header;std::copy(f.data.begin(),f.data.end(),message.dat);
|
||||||
|
sent=VCI_TransmitFD(deviceType,config.device_index,f.channel,&message,1);
|
||||||
|
}else {
|
||||||
|
ZCAN_20_MSG message{};message.hdr=header;std::copy(f.data.begin(),f.data.end(),message.dat);
|
||||||
|
sent=VCI_Transmit(deviceType,config.device_index,f.channel,&message,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> stateLock(stateMutex);
|
||||||
|
if(sent==1)++state.transmitted;else ++state.transmit_errors;
|
||||||
|
return sent==1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CanBus::CanBus(CanConfig c):impl_(std::make_unique<Impl>(c)){}
|
||||||
|
CanBus::~CanBus()=default;
|
||||||
|
void CanBus::start(){impl_->start();}
|
||||||
|
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);}
|
||||||
|
}
|
||||||
@@ -19,6 +19,11 @@ PipelineConfig loadConfig(std::filesystem::path const& path) {
|
|||||||
auto const& door=j.at("door");config.door_mask=resolve(door.at("mask"));
|
auto const& door=j.at("door");config.door_mask=resolve(door.at("mask"));
|
||||||
config.warning_relative=door.at("warning_relative");config.release_relative=door.at("release_relative");
|
config.warning_relative=door.at("warning_relative");config.release_relative=door.at("release_relative");
|
||||||
config.door_mask_calibration_verified=door.at("calibration_verified");
|
config.door_mask_calibration_verified=door.at("calibration_verified");
|
||||||
|
if(j.contains("can")) {
|
||||||
|
auto const& can=j.at("can");config.can.enabled=can.value("enabled",true);
|
||||||
|
config.can.device_index=can.value("device_index",0);config.can.timeout_ms=can.value("timeout_ms",1000);
|
||||||
|
config.can.termination=can.value("termination",true);
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ SessionWriter::SessionWriter(PipelineConfig const& c):directory_(c.record_sessio
|
|||||||
Json config={{"width",c.camera.width},{"height",c.camera.height},{"fps",c.camera.fps},
|
Json config={{"width",c.camera.width},{"height",c.camera.height},{"fps",c.camera.fps},
|
||||||
{"detector_confidence",c.detector_confidence},{"max_people",c.max_people},
|
{"detector_confidence",c.detector_confidence},{"max_people",c.max_people},
|
||||||
{"warning_relative",c.warning_relative},{"release_relative",c.release_relative},
|
{"warning_relative",c.warning_relative},{"release_relative",c.release_relative},
|
||||||
{"calibration_verified",c.door_mask_calibration_verified}};
|
{"calibration_verified",c.door_mask_calibration_verified},
|
||||||
|
{"can",{{"enabled",c.can.enabled},{"device_index",c.can.device_index},{"timeout_ms",c.can.timeout_ms},{"termination",c.can.termination}}}};
|
||||||
Json models=Json::object();
|
Json models=Json::object();
|
||||||
for(auto name:{"yolo11s.engine","rtmw_l_384.engine"})
|
for(auto name:{"yolo11s.engine","rtmw_l_384.engine"})
|
||||||
models[name]={{"path",(c.engines/name).string()},{"bytes",std::filesystem::file_size(c.engines/name)}};
|
models[name]={{"path",(c.engines/name).string()},{"bytes",std::filesystem::file_size(c.engines/name)}};
|
||||||
|
|||||||
+4
-2
@@ -11,7 +11,7 @@ int main(int argc,char** argv)try {
|
|||||||
auto root=std::filesystem::canonical("/proc/self/exe").parent_path().parent_path();
|
auto root=std::filesystem::canonical("/proc/self/exe").parent_path().parent_path();
|
||||||
auto configPath=root/"config/camera.json";
|
auto configPath=root/"config/camera.json";
|
||||||
cms::OutputOptions output;output.directory=root/"output";
|
cms::OutputOptions output;output.directory=root/"output";
|
||||||
bool visualize=true;
|
bool visualize=true,disableCan=false;
|
||||||
std::filesystem::path recordSession,replaySession;
|
std::filesystem::path recordSession,replaySession;
|
||||||
std::map<std::string,std::function<void(int&)>> arguments{
|
std::map<std::string,std::function<void(int&)>> arguments{
|
||||||
{"--record-session",[&](int&i){if(++i>=argc)throw std::invalid_argument("--record-session needs a directory");recordSession=argv[i];}},
|
{"--record-session",[&](int&i){if(++i>=argc)throw std::invalid_argument("--record-session needs a directory");recordSession=argv[i];}},
|
||||||
@@ -19,14 +19,16 @@ int main(int argc,char** argv)try {
|
|||||||
{"--save-original",[&](int&){output.original=true;}},
|
{"--save-original",[&](int&){output.original=true;}},
|
||||||
{"--save-annotated",[&](int&){output.annotated=true;}},
|
{"--save-annotated",[&](int&){output.annotated=true;}},
|
||||||
{"--save-results",[&](int&){output.results=true;}},
|
{"--save-results",[&](int&){output.results=true;}},
|
||||||
|
{"--no-can",[&](int&){disableCan=true;}},
|
||||||
{"--visualize",[&](int&){visualize=true;}},
|
{"--visualize",[&](int&){visualize=true;}},
|
||||||
{"--no-visualize",[&](int&){visualize=false;}},
|
{"--no-visualize",[&](int&){visualize=false;}},
|
||||||
{"--config",[&](int&i){if(++i>=argc)throw std::invalid_argument("--config needs a file");configPath=argv[i];}},
|
{"--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] [--visualize|--no-visualize]\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 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);}}
|
||||||
};
|
};
|
||||||
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);}
|
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);}
|
||||||
|
|
||||||
auto config=cms::loadConfig(configPath);config.output=output;config.record_session=recordSession;config.replay_session=replaySession;
|
auto config=cms::loadConfig(configPath);config.output=output;config.record_session=recordSession;config.replay_session=replaySession;
|
||||||
|
if(disableCan)config.can.enabled=false;
|
||||||
cms::CameraPipeline pipeline(config);
|
cms::CameraPipeline pipeline(config);
|
||||||
cms::StopSignals signals(pipeline);
|
cms::StopSignals signals(pipeline);
|
||||||
pipeline.start();
|
pipeline.start();
|
||||||
|
|||||||
+20
-1
@@ -22,7 +22,25 @@ void segment(cv::Mat& image,cv::Point a,cv::Point b,cv::Scalar c,int thickness,b
|
|||||||
for(double x=0;x<length;x+=14)cv::line(image,cv::Point2d(a)+d*x,cv::Point2d(a)+d*std::min(x+8,length),c,thickness,cv::LINE_AA);
|
for(double x=0;x<length;x+=14)cv::line(image,cv::Point2d(a)+d*x,cv::Point2d(a)+d*std::min(x+8,length),c,thickness,cv::LINE_AA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cv::Mat renderResult(FrameResult const& r) {
|
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
|
||||||
|
<<" 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 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) {
|
||||||
|
line<<labels[i]<<": ";if(s.positions[i]<0)line<<"--";else line<<s.positions[i];line<<" ";
|
||||||
|
}
|
||||||
|
if(s.received)line<<"age "<<std::fixed<<std::setprecision(0)<<s.age_ms<<" ms "<<(s.fd?"CANFD":"CAN")<<" #"<<s.received;
|
||||||
|
auto color=s.valid?cv::Scalar(80,220,90):cv::Scalar(0,190,255);
|
||||||
|
cv::putText(canvas,line.str(),{24,210+int(channel)*34},cv::FONT_HERSHEY_SIMPLEX,.72,color,2,cv::LINE_AA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cv::Mat renderResult(FrameResult const& r,CanSnapshot const* liveCan) {
|
||||||
if(!r.image)throw std::invalid_argument("Result has no frame image");
|
if(!r.image)throw std::invalid_argument("Result has no frame image");
|
||||||
auto const& image=*r.image;
|
auto const& image=*r.image;
|
||||||
if(image.width<=0||image.height<=0||image.stride<image.width*3||image.pixels.size()<std::size_t(image.stride)*image.height)
|
if(image.width<=0||image.height<=0||image.stride<image.width*3||image.pixels.size()<std::size_t(image.stride)*image.height)
|
||||||
@@ -75,6 +93,7 @@ cv::Mat renderResult(FrameResult const& r) {
|
|||||||
cv::putText(canvas,status.str(),{24,82},cv::FONT_HERSHEY_SIMPLEX,.72,{235,235,235},2,cv::LINE_AA);
|
cv::putText(canvas,status.str(),{24,82},cv::FONT_HERSHEY_SIMPLEX,.72,{235,235,235},2,cv::LINE_AA);
|
||||||
std::string calibration=r.door_mask_calibration_verified?"door calibration verified":"door calibration NOT verified (bring-up)";
|
std::string calibration=r.door_mask_calibration_verified?"door calibration verified":"door calibration NOT verified (bring-up)";
|
||||||
cv::putText(canvas,calibration+" | q/Esc: close preview",{24,123},cv::FONT_HERSHEY_SIMPLEX,.70,{0,190,255},2,cv::LINE_AA);
|
cv::putText(canvas,calibration+" | q/Esc: close preview",{24,123},cv::FONT_HERSHEY_SIMPLEX,.70,{0,190,255},2,cv::LINE_AA);
|
||||||
|
renderCanOverlay(canvas,liveCan?*liveCan:r.can);
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,30 @@ Json riskJson(RiskResult const& r) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Json serializeCan(CanSnapshot const& s) {
|
||||||
|
Json out={{"source",s.source},{"status",s.status},{"received",s.received},{"transmitted",s.transmitted},
|
||||||
|
{"transmit_errors",s.transmit_errors},{"receive_errors",s.receive_errors},{"channels",Json::array()}};
|
||||||
|
for(auto const& c:s.channels)out["channels"].push_back({{"positions",c.positions},{"valid",c.valid},
|
||||||
|
{"received",c.received},{"received_monotonic_ns",c.received_monotonic_ns},{"device_timestamp_us",c.device_timestamp_us},
|
||||||
|
{"age_ms",c.age_ms},{"fd",c.fd},{"extended",c.extended}});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
CanSnapshot deserializeCan(Json const& j) {
|
||||||
|
CanSnapshot s;s.source=j.at("source");s.status=j.at("status");
|
||||||
|
s.received=j.at("received");s.transmitted=j.at("transmitted");
|
||||||
|
s.transmit_errors=j.at("transmit_errors");s.receive_errors=j.at("receive_errors");
|
||||||
|
auto const& channels=j.at("channels");
|
||||||
|
if(!channels.is_array()||channels.size()!=2)throw std::runtime_error("Invalid recorded CAN channels");
|
||||||
|
for(std::size_t i=0;i<2;++i) {
|
||||||
|
auto const& v=channels[i];auto& c=s.channels[i];
|
||||||
|
if(!v.at("positions").is_array()||v.at("positions").size()!=4)throw std::runtime_error("Invalid recorded CAN positions");
|
||||||
|
c.positions=v.at("positions").get<std::array<int,4>>();
|
||||||
|
for(auto raw:c.positions)if(raw < -1||raw>255)throw std::runtime_error("Recorded CAN position out of range");
|
||||||
|
c.valid=v.at("valid");c.received=v.at("received");c.received_monotonic_ns=v.at("received_monotonic_ns");
|
||||||
|
c.device_timestamp_us=v.at("device_timestamp_us");c.age_ms=v.at("age_ms");c.fd=v.at("fd");c.extended=v.at("extended");
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
Json serializeResult(FrameResult const& r) {
|
Json serializeResult(FrameResult const& r) {
|
||||||
Json out={{"frame_index",r.frame_index},{"track_ids",r.track_ids},{"boxes",r.boxes},{"skeletons",Json::array()},{"risk",riskJson(r.risk)}};
|
Json out={{"frame_index",r.frame_index},{"track_ids",r.track_ids},{"boxes",r.boxes},{"skeletons",Json::array()},{"risk",riskJson(r.risk)}};
|
||||||
for(std::size_t i=0;i<r.persons.size();++i) {
|
for(std::size_t i=0;i<r.persons.size();++i) {
|
||||||
@@ -38,6 +62,7 @@ Json serializeResult(FrameResult const& r) {
|
|||||||
skeleton["temporal_keypoints"].push_back({b.x,b.y,b.score});
|
skeleton["temporal_keypoints"].push_back({b.x,b.y,b.score});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
out["can"]=serializeCan(r.can);
|
||||||
out["timing"]={{"detector_ms",r.timing.detector_ms},{"pose_ms",r.timing.pose_ms},{"fusion_risk_ms",r.timing.fusion_risk_ms},{"capture_to_result_ms",r.timing.capture_to_result_ms}};
|
out["timing"]={{"detector_ms",r.timing.detector_ms},{"pose_ms",r.timing.pose_ms},{"fusion_risk_ms",r.timing.fusion_risk_ms},{"capture_to_result_ms",r.timing.capture_to_result_ms}};
|
||||||
out["capture"]={{"sequence",r.capture.driver_sequence},{"capture_monotonic_ms",r.capture.capture_monotonic_ns*1e-6},{"driver_timestamp_ms",r.capture.driver_timestamp_ns*1e-6}};
|
out["capture"]={{"sequence",r.capture.driver_sequence},{"capture_monotonic_ms",r.capture.capture_monotonic_ns*1e-6},{"driver_timestamp_ms",r.capture.driver_timestamp_ns*1e-6}};
|
||||||
out["capture_to_result_ms"]=rounded(r.timing.capture_to_result_ms,3);out["calibration_verified"]=r.door_mask_calibration_verified;return out;
|
out["capture_to_result_ms"]=rounded(r.timing.capture_to_result_ms,3);out["calibration_verified"]=r.door_mask_calibration_verified;return out;
|
||||||
|
|||||||
+14
-1
@@ -3,6 +3,7 @@
|
|||||||
#include <opencv2/highgui.hpp>
|
#include <opencv2/highgui.hpp>
|
||||||
#include <opencv2/highgui/highgui_c.h>
|
#include <opencv2/highgui/highgui_c.h>
|
||||||
#include <opencv2/imgproc.hpp>
|
#include <opencv2/imgproc.hpp>
|
||||||
|
#include <chrono>
|
||||||
namespace cms {
|
namespace cms {
|
||||||
namespace { constexpr char windowName[]="CMS Camera Risk"; }
|
namespace { constexpr char windowName[]="CMS Camera Risk"; }
|
||||||
void runLocalVisualization(CameraPipeline const& pipeline) {
|
void runLocalVisualization(CameraPipeline const& pipeline) {
|
||||||
@@ -11,9 +12,21 @@ void runLocalVisualization(CameraPipeline const& pipeline) {
|
|||||||
~Window(){try{if(cvGetWindowHandle(windowName))cv::destroyWindow(windowName);}catch(...) {}}
|
~Window(){try{if(cvGetWindowHandle(windowName))cv::destroyWindow(windowName);}catch(...) {}}
|
||||||
} window;
|
} window;
|
||||||
FrameResultPtr shown;
|
FrameResultPtr shown;
|
||||||
|
auto nextRefresh=std::chrono::steady_clock::now();
|
||||||
while(pipeline.running()) {
|
while(pipeline.running()) {
|
||||||
auto result=pipeline.latest();
|
auto result=pipeline.latest();
|
||||||
if(result&&result!=shown){cv::imshow(windowName,renderResult(*result));shown=std::move(result);}
|
auto now=std::chrono::steady_clock::now();
|
||||||
|
if(result!=shown||now>=nextRefresh) {
|
||||||
|
auto can=pipeline.canStatus();cv::Mat image;
|
||||||
|
if(result)image=renderResult(*result,&can);
|
||||||
|
else {
|
||||||
|
image=cv::Mat(800,1000,CV_8UC3,cv::Scalar(20,24,30));
|
||||||
|
cv::putText(image,"Waiting for inference...",{24,64},cv::FONT_HERSHEY_SIMPLEX,.8,{235,235,235},2);
|
||||||
|
renderCanOverlay(image,can);
|
||||||
|
}
|
||||||
|
cv::imshow(windowName,image);shown=std::move(result);
|
||||||
|
nextRefresh=now+std::chrono::milliseconds(100);
|
||||||
|
}
|
||||||
int key=cv::waitKey(16)&0xff;
|
int key=cv::waitKey(16)&0xff;
|
||||||
if(key==27||key=='q'||key=='Q'||cvGetWindowHandle(windowName)==nullptr)return;
|
if(key==27||key=='q'||key=='Q'||cvGetWindowHandle(windowName)==nullptr)return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# CAN 收发与 0x391 显示实现记录(2026-09-24)
|
||||||
|
|
||||||
|
范围:仅当前 `inference-only` 分支。用户明确当前并非真实 CAN 环境,接收实测暂不验证;真实 CAN 接收和发送均未验收。
|
||||||
|
|
||||||
|
## 已完成的模拟和离线检查
|
||||||
|
|
||||||
|
- `tests/run_can.sh`:UBSan + 模拟 SDK,无硬件访问。覆盖四个连续字节独立解析、双通道隔离、CAN/CANFD 数据帧、过期与恢复、无关 ID / 短报文不刷新状态、按需发送编码、非法 ID/通道/长度/BRS、发送失败、接收错误、缺失适配器、部分初始化失败清理、设备单一所有权和停止后拒绝发送。
|
||||||
|
- `tests/run_session.sh`:人工图像 H.264 往返与过程记录,新增 CAN 快照保存/读取检查,四门值、有效状态与年龄保持不变。
|
||||||
|
- 静态可视化检查:人工构造 CH0 LIVE、CH1 STALE 快照,四门不同值均可见,显示收发计数和报文年龄,未读取任何 CAN 设备。临时图 `/tmp/vap_can_overlay.png`。
|
||||||
|
- 源码构建通过,SDK 和 libusb 从独立包加载,配置和 `--no-can` 接入完成。
|
||||||
|
|
||||||
|
以上均不能代替真实 CAN 总线验证。
|
||||||
|
|
||||||
|
## 已执行的 Orin 摄像头 / TensorRT 检查
|
||||||
|
|
||||||
|
在用户要求暂停 CAN 实测前执行过 `tests/orin_session_test.py`,测试目录 `/tmp/vap-can-hardware-20260924/`。当时 USB 节点为 `root:root 0664`,SDK 打开失败,状态为 UNAVAILABLE,实际接收数为 0。
|
||||||
|
|
||||||
|
在这一缺失 CAN 的回退条件下,真实 `/dev/video5` 与 TensorRT 完成 13 帧 H.264 录制、13 帧完整回灌,CAN UNAVAILABLE 快照在回灌中保持一致并标记 `source=replay`。13 帧检测到人员;跟踪 ID 一致 13/13,风险决策一致 8/13。有损视频产生姿态/风险差异,不能把视频回灌解释为算法输出逐位相同。SIGTERM 退出 1.317 秒,回灌 SIGINT 退出 0.916 秒。完整原始结果在 `orin_can_results.json`。
|
||||||
|
|
||||||
|
此项仅证明真实摄像头/TensorRT、无 CAN 回退、状态录制/回灌及停止,不证明 CAN 收发。
|
||||||
|
|
||||||
|
## 设备访问准备与延期项目
|
||||||
|
|
||||||
|
已读取 USB 描述确认 `3068:0009` 为 ZLG USBCANFD-200U。经批准安装仅针对该 VID/PID 的 udev 规则,将设备设为 `root:plugdev 0660`;orin 原已属于 plugdev。规则及安装脚本均纳入独立包。权限修复后的接收测试被用户取消,未启动;后续没有再打开适配器接收或发送报文。
|
||||||
|
|
||||||
|
未验证实际总线 `0x391` 布局/物理单位、CAN/CANFD 真实发送或 ACK、双通道接线、波特率/终端电阻与实车匹配、硬件断开重连、长时间运行。没有新增板端故障注入或物理拔插操作。发送仅提供按需 C++ API,未定义或自动发送任何业务控制报文。
|
||||||
|
|
||||||
|
## 部署校验
|
||||||
|
|
||||||
|
本次程序 SHA256:`e8788d22fd6db0e3b9edc95a02322a51ca7951e1df81fea10f34db7da3143461`。
|
||||||
|
|
||||||
|
权限修复前的板端测试核对过 `/proc/<pid>/exe` 与源码构建文件一致。更新独立包时核对源码构建与 `orin/build/camera_risk` 哈希一致,并刷新 `orin/SHA256SUMS`(包含 SDK 库及 SONAME 链接)。用户要求暂不验证接收后,仅做文件和依赖校验,不再启动 CAN 测试进程。测试前没有常驻推理进程,没有修改既有启动参数。
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include "can_bus.hpp"
|
||||||
|
#include "zcan.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <thread>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable changed;
|
||||||
|
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{};
|
||||||
|
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;
|
||||||
|
while(!f()){check(std::chrono::steady_clock::now()<deadline,"Asynchronous CAN condition timed out");std::this_thread::sleep_for(1ms);}
|
||||||
|
}
|
||||||
|
template<class F> void rejects(F f){bool failed=false;try{f();}catch(std::exception const&){failed=true;}check(failed,"Expected rejection");}
|
||||||
|
void reset() {
|
||||||
|
opens=closes=resets=mergeCalls=txCalls=rxCalls=0;
|
||||||
|
unavailable=initFailure=receiveFailure=txFailure=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) {
|
||||||
|
ZCANDataObj obj{};obj.chnl=channel;obj.dataType=fd?ZCAN_DT_ZCAN_CANFD_DATA:ZCAN_DT_ZCAN_CAN_DATA;
|
||||||
|
auto& hdr=fd?obj.data.zcanCANFDData.hdr:obj.data.zcanCANData.hdr;
|
||||||
|
hdr.id=id;hdr.chn=channel;hdr.len=bytes.size();hdr.ts=stamp;
|
||||||
|
auto* data=fd?obj.data.zcanCANFDData.dat:obj.data.zcanCANData.dat;
|
||||||
|
std::copy(bytes.begin(),bytes.end(),data);
|
||||||
|
{std::lock_guard<std::mutex> lock(mutex);incoming.push_back(obj);}changed.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(init->clk==60000000&&init->mode==0&&init->aset.brp==5&&init->dset.brp==1,"Reference timing not retained");
|
||||||
|
return initFailure&&port==1?0:1;
|
||||||
|
}
|
||||||
|
U32 VCI_SetReference(U32,U32,U32 port,U32 ref,void* data){
|
||||||
|
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_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();
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
changed.wait_for(lock,10ms,[]{return !incoming.empty();});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int main()try {
|
||||||
|
using namespace cms;
|
||||||
|
reset();{
|
||||||
|
CanConfig c;c.enabled=false;CanBus bus(c);bus.start();
|
||||||
|
check(bus.snapshot().status=="DISABLED"&&!bus.send({0,0x123,false,false,false,{1,2}}),"Disabled CAN used hardware");
|
||||||
|
}check(opens==0,"Disabled CAN opened device");
|
||||||
|
reset();unavailable=true;{
|
||||||
|
CanBus bus({});bus.start();check(bus.snapshot().status=="UNAVAILABLE","Missing adapter not reported");
|
||||||
|
}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");
|
||||||
|
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==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");
|
||||||
|
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;});
|
||||||
|
s=bus.snapshot();check(s.channels[0].received_monotonic_ns==timestamp&&s.receive_errors==1,"Invalid input refreshed signal");
|
||||||
|
waitFor([&]{return !bus.snapshot().channels[0].valid;});
|
||||||
|
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)};
|
||||||
|
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({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");
|
||||||
|
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";
|
||||||
|
}catch(std::exception const& e){std::cerr<<e.what()<<'\n';return 1;}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"platform": "orin-desktop",
|
||||||
|
"binary": "/home/orin/vision-anti-pinch-0923/build/camera_risk",
|
||||||
|
"binary_sha256": "e8788d22fd6db0e3b9edc95a02322a51ca7951e1df81fea10f34db7da3143461",
|
||||||
|
"running_binary_sha256": "e8788d22fd6db0e3b9edc95a02322a51ca7951e1df81fea10f34db7da3143461",
|
||||||
|
"recorded_frames": 13,
|
||||||
|
"replayed_frames": 13,
|
||||||
|
"frames_with_people": 13,
|
||||||
|
"max_raw_pose_error_px": 938.180541992,
|
||||||
|
"matching_track_frames": 13,
|
||||||
|
"matching_decision_frames": 8,
|
||||||
|
"video_bytes": 322966,
|
||||||
|
"uncompressed_bgr_bytes": 115015680,
|
||||||
|
"sigterm_seconds": 1.3167834819996642,
|
||||||
|
"replay_sigint_seconds": 0.9158224279999558,
|
||||||
|
"interrupted_frames": 2,
|
||||||
|
"can_statuses": [
|
||||||
|
"UNAVAILABLE"
|
||||||
|
],
|
||||||
|
"can_received": 0,
|
||||||
|
"can_0x391_by_channel": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -106,6 +106,9 @@ def main():
|
|||||||
for before, after, stem in zip(original, restored, rows(recorded)):
|
for before, after, stem in zip(original, restored, rows(recorded)):
|
||||||
assert before['frame_index'] == after['frame_index']
|
assert before['frame_index'] == after['frame_index']
|
||||||
assert before['capture'] == after['source_capture']
|
assert before['capture'] == after['source_capture']
|
||||||
|
if 'can' in before['result']:
|
||||||
|
expected_can = dict(before['result']['can'], source='replay')
|
||||||
|
assert expected_can == after['result']['can'], 'Recorded CAN state changed during replay'
|
||||||
assert before['video_frame_index'] == after['video_frame_index']
|
assert before['video_frame_index'] == after['video_frame_index']
|
||||||
matching_tracks += before['result']['track_ids'] == after['result']['track_ids']
|
matching_tracks += before['result']['track_ids'] == after['result']['track_ids']
|
||||||
matching_decisions += before['result']['risk']['decision'] == after['result']['risk']['decision']
|
matching_decisions += before['result']['risk']['decision'] == after['result']['risk']['decision']
|
||||||
@@ -124,7 +127,10 @@ def main():
|
|||||||
'running_binary_sha256': running_hash, 'recorded_frames': len(original), 'replayed_frames': len(restored),
|
'running_binary_sha256': running_hash, 'recorded_frames': len(original), 'replayed_frames': len(restored),
|
||||||
'frames_with_people': sum(bool(row['result']['track_ids']) for row in original),
|
'frames_with_people': sum(bool(row['result']['track_ids']) for row in original),
|
||||||
'max_raw_pose_error_px': max_pose_error, 'matching_track_frames': matching_tracks, 'matching_decision_frames': matching_decisions,
|
'max_raw_pose_error_px': max_pose_error, 'matching_track_frames': matching_tracks, 'matching_decision_frames': matching_decisions,
|
||||||
'video_bytes': (recorded/'frames.mp4').stat().st_size, 'uncompressed_bgr_bytes': len(original)*int(probe['width'])*int(probe['height'])*3, 'sigterm_seconds': term_seconds, 'replay_sigint_seconds': interrupt_seconds, 'interrupted_frames': interrupted_count}
|
'video_bytes': (recorded/'frames.mp4').stat().st_size, 'uncompressed_bgr_bytes': len(original)*int(probe['width'])*int(probe['height'])*3, 'sigterm_seconds': term_seconds, 'replay_sigint_seconds': interrupt_seconds, 'interrupted_frames': interrupted_count,
|
||||||
|
'can_statuses': sorted({r['result'].get('can', {}).get('status', 'NOT_RECORDED') for r in original}),
|
||||||
|
'can_received': max(r['result'].get('can', {}).get('received', 0) for r in original),
|
||||||
|
'can_0x391_by_channel': [max((r['result'].get('can', {}).get('channels', [{}, {}])[ch].get('received', 0) for r in original)) for ch in range(2)]}
|
||||||
assert result['binary_sha256'] == running_hash
|
assert result['binary_sha256'] == running_hash
|
||||||
(args.output/'report.json').write_text(json.dumps(result, indent=2)+'\n')
|
(args.output/'report.json').write_text(json.dumps(result, indent=2)+'\n')
|
||||||
print(json.dumps(result, indent=2))
|
print(json.dumps(result, indent=2))
|
||||||
|
|||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
test_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$test_dir"' EXIT
|
||||||
|
"${CXX:-g++}" -std=c++17 -O1 -g -Wall -Wextra -Wpedantic -pthread \
|
||||||
|
-fsanitize=undefined -fno-sanitize-recover=all -I"$root/include" -isystem "$root/third_party/zlg" \
|
||||||
|
"$root/src/can_bus.cpp" "$root/tests/can_bus_test.cpp" -o "$test_dir/can_bus_test"
|
||||||
|
"$test_dir/can_bus_test"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "inference_session.hpp"
|
#include "inference_session.hpp"
|
||||||
#include "camera_pipeline.hpp"
|
#include "camera_pipeline.hpp"
|
||||||
|
#include "serialization.hpp"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -24,6 +25,8 @@ int main(int argc,char** argv)try {
|
|||||||
im->pixels.resize(9600);
|
im->pixels.resize(9600);
|
||||||
for(int y=0;y<48;++y)for(int x=0;x<192;++x)im->pixels[y*200+x]=32+x/3+y%4;
|
for(int y=0;y<48;++y)for(int x=0;x<192;++x)im->pixels[y*200+x]=32+x/3+y%4;
|
||||||
FrameResult r;r.image=im;r.frame_index=3;r.capture.frame_index=3;r.capture.driver_sequence=42;
|
FrameResult r;r.image=im;r.frame_index=3;r.capture.frame_index=3;r.capture.driver_sequence=42;
|
||||||
|
r.can.status="OPEN";r.can.received=7;r.can.channels[0].positions={0,25,100,255};
|
||||||
|
r.can.channels[0].valid=true;r.can.channels[0].received=7;r.can.channels[0].age_ms=12.5;
|
||||||
r.capture.capture_monotonic_ns=123;r.capture.received_monotonic_ns=124;
|
r.capture.capture_monotonic_ns=123;r.capture.received_monotonic_ns=124;
|
||||||
{
|
{
|
||||||
SessionWriter writer(c);writer.write(r,{{"raw_keypoints",{1,2,3}}});
|
SessionWriter writer(c);writer.write(r,{{"raw_keypoints",{1,2,3}}});
|
||||||
@@ -37,6 +40,8 @@ int main(int argc,char** argv)try {
|
|||||||
PipelineConfig replay;replay.replay_session=c.record_session;
|
PipelineConfig replay;replay.replay_session=c.record_session;
|
||||||
SessionReader reader(replay);check(replay.camera.width==64&&replay.detector_confidence==.42,"Settings not restored");
|
SessionReader reader(replay);check(replay.camera.width==64&&replay.detector_confidence==.42,"Settings not restored");
|
||||||
auto first=reader.read();check(first&&first->metadata.frame_index==3,"First index mismatch");
|
auto first=reader.read();check(first&&first->metadata.frame_index==3,"First index mismatch");
|
||||||
|
auto can=deserializeCan(first->replay_record->at("result").at("can"));
|
||||||
|
check(can.channels[0].positions==r.can.channels[0].positions&&can.channels[0].valid&&can.channels[0].age_ms==12.5,"Recorded CAN snapshot changed");
|
||||||
check(first->metadata.driver_sequence==42&&first->metadata.capture_monotonic_ns!=123,"Capture metadata mismatch");
|
check(first->metadata.driver_sequence==42&&first->metadata.capture_monotonic_ns!=123,"Capture metadata mismatch");
|
||||||
check(first->replay_record->at("capture").at("capture_monotonic_ns")==123,"Original clock lost");
|
check(first->replay_record->at("capture").at("capture_monotonic_ns")==123,"Original clock lost");
|
||||||
for(int y=0;y<48;++y)for(int x=0;x<192;++x)check(std::abs(int(first->image->pixels[y*192+x])-(32+x/3+y%4))<12,"Decoded first frame mismatch");
|
for(int y=0;y<48;++y)for(int x=0;x<192;++x)check(std::abs(int(first->image->pixels[y*192+x])-(32+x/3+y%4))<12,"Decoded first frame mismatch");
|
||||||
|
|||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# USBCANFD SDK
|
||||||
|
|
||||||
|
`zcan.h` and the runtime libraries in `orin/lib/` were copied unchanged from the user-specified reference project `/home/orin/vision-anti-pinch` on 2026-09-24. The runtime libraries are aarch64 `libusbcanfd.so` (SONAME `libusbcanfd.so.1.0.12`) and `libusb-1.0.so` (SONAME `libusb-1.0.so.0`). Symlinks retain these SONAMEs in the standalone package.
|
||||||
|
|
||||||
|
`src/can_bus.cpp` uses the reference device type and 500 kbps / 2 Mbps initialization settings. It implements one merged receive worker, per-channel state, explicit data-frame transmission, and lifecycle cleanup instead of importing the reference global callback/receive-thread implementation.
|
||||||
Vendored
+443
@@ -0,0 +1,443 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#define EXTERN_C extern "C"
|
||||||
|
#else
|
||||||
|
#define EXTERN_C
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ZCAN_API
|
||||||
|
#define INVALID_HANDLE_VALUE ((HANDLE)(-1))
|
||||||
|
|
||||||
|
#define CMD_CAN_FILTER 0x14 // 滤波
|
||||||
|
#define CMD_CAN_TTX 0x16 // 定时发送
|
||||||
|
#define CMD_CAN_TTX_CTL 0x17 // 使能定时发送
|
||||||
|
#define CMD_CAN_TRES 0x18 // CAN终端电阻
|
||||||
|
|
||||||
|
#define ZCAN_CMD_SET_CHNL_RECV_MERGE 0x32 // 设置合并接收 0:不合并接收;1:合并接收
|
||||||
|
#define ZCAN_CMD_GET_CHNL_RECV_MERGE 0x33 // 获取是否开启合并接收 0:不合并接收;1:合并接收
|
||||||
|
|
||||||
|
#define CMD_SET_SN 0x42 // 获取SN号
|
||||||
|
#define CMD_GET_SN 0x43 // 设置SN号
|
||||||
|
#define CMD_CAN_TX_TIMEOUT 0x44 // 发送超时
|
||||||
|
|
||||||
|
#define ZCAN_CMD_GET_SEND_QUEUE_SIZE 0x100 // 获取队列大小,uint32_t
|
||||||
|
#define ZCAN_CMD_GET_SEND_QUEUE_SPACE 0x101 // 获取队列剩余空间, uint32_t
|
||||||
|
#define ZCAN_CMD_SET_SEND_QUEUE_CLR 0x102 // 清空发送队列,1:清空
|
||||||
|
#define ZCAN_CMD_SET_SEND_QUEUE_EN 0x103 // 开启发送队列,1:使能
|
||||||
|
|
||||||
|
typedef unsigned char U8;
|
||||||
|
typedef unsigned short U16;
|
||||||
|
typedef unsigned int U32;
|
||||||
|
typedef unsigned long long U64;
|
||||||
|
|
||||||
|
enum eZCANDataDEF
|
||||||
|
{
|
||||||
|
//数据类型
|
||||||
|
ZCAN_DT_ZCAN_CAN_DATA = 1, // CAN/CANFD数据
|
||||||
|
ZCAN_DT_ZCAN_CANFD_DATA = 2, // CAN/CANFD数据
|
||||||
|
ZCAN_DT_ZCAN_ERROR_DATA = 3, // CAN/CANFD错误数据
|
||||||
|
ZCAN_DT_ZCAN_LIN_DATA = 4, // LIN数据
|
||||||
|
ZCAN_DT_ZCAN_LIN_ERROR_DATA = 5, // LIN错误数据
|
||||||
|
};
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ZCAN_TX_NORM = 0, /**< normal transmission */
|
||||||
|
ZCAN_TX_ONCE = 1, /**< single-shot transmission */
|
||||||
|
ZCAN_SR_NORM = 2, /**< self reception */
|
||||||
|
ZCAN_SR_ONCE = 3, /**< single-shot transmission & self reception */
|
||||||
|
} ZCAN_TX_MODE;
|
||||||
|
|
||||||
|
/** CAN filter configuration */
|
||||||
|
typedef struct {
|
||||||
|
U8 type; /**< 0-std_frame, 1-ext_frame */
|
||||||
|
U8 pad[3];
|
||||||
|
U32 sid; /**< start-id */
|
||||||
|
U32 eid; /**< end-id */
|
||||||
|
} ZCAN_FILTER;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
U32 size;
|
||||||
|
ZCAN_FILTER table[64];
|
||||||
|
} ZCAN_FILTER_TABLE;
|
||||||
|
|
||||||
|
/** controller initialization */
|
||||||
|
typedef struct {
|
||||||
|
U32 clk; // 时钟频率
|
||||||
|
U32 mode; /**< bit0-normal/listen_only, bit1-ISO/BOSCH */
|
||||||
|
struct {
|
||||||
|
U8 tseg1;
|
||||||
|
U8 tseg2;
|
||||||
|
U8 sjw;
|
||||||
|
U8 smp;
|
||||||
|
U16 brp;
|
||||||
|
} aset;
|
||||||
|
struct {
|
||||||
|
U8 tseg1;
|
||||||
|
U8 tseg2;
|
||||||
|
U8 sjw;
|
||||||
|
U8 smp;
|
||||||
|
U16 brp;
|
||||||
|
} dset;
|
||||||
|
} ZCAN_INIT;
|
||||||
|
|
||||||
|
/** CAN message info */
|
||||||
|
typedef struct {
|
||||||
|
U32 txm : 4; /**< TX-mode, @see ZCAN_TX_MODE */
|
||||||
|
U32 fmt : 4; /**< 0-CAN2.0, 1-CANFD */
|
||||||
|
U32 sdf : 1; /**< 0-data_frame, 1-remote_frame */
|
||||||
|
U32 sef : 1; /**< 0-std_frame, 1-ext_frame */
|
||||||
|
U32 err : 1; /**< error flag */
|
||||||
|
U32 brs : 1; /**< bit-rate switch */
|
||||||
|
U32 est : 1; /**< error state */
|
||||||
|
U32 tx : 1; /**< received valid, tx frame */
|
||||||
|
U32 echo : 1; /**< tx valid, echo frame */
|
||||||
|
U32 qsend_100us:1; /**< queue send delay unit, 1-100us, 0-ms */
|
||||||
|
U32 qsend:1; /**< send valid, queue send frame */
|
||||||
|
U32 pad : 15;
|
||||||
|
} ZCAN_MSG_INF;
|
||||||
|
|
||||||
|
/** CAN message header */
|
||||||
|
typedef struct {
|
||||||
|
U32 ts; /**< timestamp */
|
||||||
|
U32 id; /**< CAN-ID */
|
||||||
|
ZCAN_MSG_INF inf; /**< @see ZCAN_MSG_INF */
|
||||||
|
U16 pad; /**< send queue delay */
|
||||||
|
U8 chn; /**< channel */
|
||||||
|
U8 len; /**< data length */
|
||||||
|
} ZCAN_MSG_HDR;
|
||||||
|
|
||||||
|
/** CAN2.0-frame */
|
||||||
|
typedef struct {
|
||||||
|
ZCAN_MSG_HDR hdr;
|
||||||
|
U8 dat[8];
|
||||||
|
} ZCAN_20_MSG;
|
||||||
|
|
||||||
|
/** CANFD-frame */
|
||||||
|
typedef struct {
|
||||||
|
ZCAN_MSG_HDR hdr;
|
||||||
|
U8 dat[64];
|
||||||
|
} ZCAN_FD_MSG;
|
||||||
|
|
||||||
|
// 错误帧
|
||||||
|
typedef struct {
|
||||||
|
ZCAN_MSG_HDR hdr;
|
||||||
|
U8 dat[8];
|
||||||
|
} ZCAN_ERR_MSG;
|
||||||
|
|
||||||
|
// 定时发送结构体
|
||||||
|
typedef struct {
|
||||||
|
U32 interval; // 定时发送周期,单位 100us
|
||||||
|
U16 repeat; // 发送次数,0等于循环发
|
||||||
|
U8 index; // 定时发送列表的帧索引号,也就是第几条定时发送报文
|
||||||
|
U8 flags; // 0-此帧禁用定时发送,1-此帧使能定时发送
|
||||||
|
ZCAN_FD_MSG msg; // CANFD帧结构体
|
||||||
|
} ZCAN_TTX;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
U32 size; // 实际生效的数组的长度(用来控制实际生效多少条定时发送帧结构体)
|
||||||
|
ZCAN_TTX table[8]; // 最大设置8条
|
||||||
|
} ZCAN_TTX_CFG;
|
||||||
|
|
||||||
|
// 设备信息
|
||||||
|
typedef struct {
|
||||||
|
U16 hwv; /**< hardware version */
|
||||||
|
U16 fwv; /**< firmware version */
|
||||||
|
U16 drv; /**< driver version */
|
||||||
|
U16 api; /**< API version */
|
||||||
|
U16 irq; /**< IRQ */
|
||||||
|
U8 chn; /**< channels */
|
||||||
|
U8 sn[20]; /**< serial number */
|
||||||
|
U8 id[40]; /**< card id */
|
||||||
|
U16 pad[4];
|
||||||
|
} ZCAN_DEV_INF;
|
||||||
|
|
||||||
|
/** controller status */
|
||||||
|
typedef struct {
|
||||||
|
U8 IR; /**< not used(for backward compatibility) */
|
||||||
|
U8 MOD; /**< not used */
|
||||||
|
U8 SR; /**< not used */
|
||||||
|
U8 ALC; /**< not used */
|
||||||
|
U8 ECC; /**< not used */
|
||||||
|
U8 EWL; /**< not used */
|
||||||
|
U8 RXE; /**< RX errors */
|
||||||
|
U8 TXE; /**< TX errors */
|
||||||
|
U32 PAD;
|
||||||
|
} ZCAN_STAT;
|
||||||
|
|
||||||
|
//lin begin
|
||||||
|
typedef struct _VCI_LIN_INIT_CONFIG
|
||||||
|
{
|
||||||
|
U8 linMode; // 是否作为主机,0-从机,1-主机
|
||||||
|
U8 chkSumMode; // 校验方式,1-经典校验 2-增强校验 3-自动(对应eZLINChkSumMode的模式)
|
||||||
|
U16 reserved; // 保留
|
||||||
|
U32 linBaud; // 波特率,取值1000~20000
|
||||||
|
}ZCAN_LIN_INIT_CONFIG, *PZCAN_LIN_INIT_CONFIG;
|
||||||
|
|
||||||
|
typedef struct tagZCANLINData
|
||||||
|
{
|
||||||
|
union{
|
||||||
|
struct {
|
||||||
|
U8 ID : 6; // 帧ID
|
||||||
|
U8 Parity : 2; // 帧ID校验
|
||||||
|
}unionVal;
|
||||||
|
U8 rawVal; // 受保护的ID原始值
|
||||||
|
} PID; // 受保护的ID
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
U64 timeStamp; // 时间戳,单位微秒(us)
|
||||||
|
U8 dataLen; // 数据长度
|
||||||
|
U8 dir; // 传输方向,0-接收 1-发送
|
||||||
|
U8 chkSum; // 数据校验, 部分设备不支持校验数据的获取
|
||||||
|
U8 reserved[13]; // 保留
|
||||||
|
U8 data[8]; // 数据
|
||||||
|
}RxData; // 仅接收数据时有效
|
||||||
|
U8 reserved[7]; // 保留
|
||||||
|
}ZCANLINData;
|
||||||
|
|
||||||
|
typedef struct tagZCANLINErrData
|
||||||
|
{
|
||||||
|
U64 timeStamp; // 时间戳, 单位微秒(us)
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
U8 ID : 6; // 帧ID
|
||||||
|
U8 Parity : 2; // 帧ID校验
|
||||||
|
}unionVal;
|
||||||
|
U8 rawVal; // 受保护的ID原始值
|
||||||
|
} PID; // 受保护的ID
|
||||||
|
U8 dataLen;
|
||||||
|
U8 data[8];
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
U16 errStage : 4; // 错误阶段
|
||||||
|
U16 errReason : 4; // 错误原因
|
||||||
|
U16 reserved : 8; // 保留
|
||||||
|
};
|
||||||
|
U16 unionErrData;
|
||||||
|
}errData;
|
||||||
|
U8 dir; // 传输方向
|
||||||
|
U8 chkSum; // 数据校验, 部分设备不支持校验数据的获取
|
||||||
|
U8 reserved[10]; // 保留
|
||||||
|
}ZCANLINErrData;
|
||||||
|
|
||||||
|
typedef struct _VCI_LIN_MSG{
|
||||||
|
U8 chnl; // 数据通道
|
||||||
|
U8 dataType; // 数据类型, 0-LIN数据 1-LIN错误数据
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ZCANLINData zcanLINData; // LIN数据
|
||||||
|
ZCANLINErrData zcanLINErrData; // LIN错误数据
|
||||||
|
U8 raw[46]; // RAW数据
|
||||||
|
} data; // 实际数据, 联合体,有效成员根据 dataType 字段而定
|
||||||
|
}ZCAN_LIN_MSG, *PZCAN_LIN_MSG;
|
||||||
|
|
||||||
|
enum eZLINChkSumMode
|
||||||
|
{
|
||||||
|
DEFAULT = 0, // 默认,启动时配置
|
||||||
|
CLASSIC_CHKSUM, // 经典校验
|
||||||
|
ENHANCE_CHKSUM, // 增强校验
|
||||||
|
AUTOMATIC, // 自动,设备自动识别校验方式(仅ZCAN_SetLINSubscribe时有效)
|
||||||
|
};
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
MAX_LIN_ID_COUNT = 63,
|
||||||
|
MIN_LIN_DLC = 1,
|
||||||
|
MAX_LIN_DLC = 8,
|
||||||
|
AUTO_LIN_DLC = 255,
|
||||||
|
};
|
||||||
|
typedef struct _VCI_LIN_PUBLISH_CFG
|
||||||
|
{
|
||||||
|
U8 ID; // 受保护的ID(ID取值范围为0-63)
|
||||||
|
U8 dataLen; // dataLen范围为1-8
|
||||||
|
U8 data[8];
|
||||||
|
U8 chkSumMode; // 校验方式,0-默认,启动时配置 1-经典校验 2-增强校验(对应eZLINChkSumMode的模式)
|
||||||
|
U8 reserved[5]; // 保留
|
||||||
|
}ZCAN_LIN_PUBLISH_CFG, *PZCAN_LIN_PUBLISH_CFG;
|
||||||
|
|
||||||
|
typedef struct _VCI_LIN_SUBSCIBE_CFG
|
||||||
|
{
|
||||||
|
U8 ID; // 受保护的ID(ID取值范围为0-63)
|
||||||
|
U8 dataLen; // dataLen范围为1-8 当为255(0xff)则表示设备自动识别报文长度
|
||||||
|
U8 chkSumMode; // 校验方式,0-默认,启动时配置 1-经典校验 2-增强校验 3-自动(对应eZLINChkSumMode的模式)
|
||||||
|
U8 reserved[5]; // 保留
|
||||||
|
}ZCAN_LIN_SUBSCIBE_CFG, *PZCAN_LIN_SUBSCIBE_CFG;
|
||||||
|
|
||||||
|
// 合并接收数据数据结构,支持CAN/CANFD/LIN/错误等不同类型数据
|
||||||
|
typedef struct tagZCANDataObj
|
||||||
|
{
|
||||||
|
U8 dataType; // 数据类型, 参考eZCANDataDEF中 数据类型 部分定义
|
||||||
|
U8 chnl; // 数据通道
|
||||||
|
union{
|
||||||
|
struct{
|
||||||
|
U16 reserved : 16; // 保留
|
||||||
|
}unionVal;
|
||||||
|
U16 rawVal;
|
||||||
|
}flag; // 标志信息, 暂未使用
|
||||||
|
U8 extraData[4]; // 额外数据, 暂未使用
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ZCAN_20_MSG zcanCANData; // CAN数据
|
||||||
|
ZCAN_FD_MSG zcanCANFDData; // CANFD数据
|
||||||
|
ZCAN_ERR_MSG zcanErrData; // 错误数据
|
||||||
|
ZCANLINData zcanLINData; // LIN数据
|
||||||
|
ZCANLINErrData zcanLINErrData; // LIN错误数据
|
||||||
|
U8 raw[92]; // RAW数据
|
||||||
|
} data; // 实际数据, 联合体,有效成员根据 dataType 字段而定
|
||||||
|
}ZCANDataObj;
|
||||||
|
|
||||||
|
|
||||||
|
// UDS传输协议版本
|
||||||
|
typedef U8 ZCAN_UDS_TRANS_VER;
|
||||||
|
#define ZCAN_UDS_TRANS_VER_0 0 // ISO15765-2(2004版本)
|
||||||
|
#define ZCAN_UDS_TRANS_VER_1 1 // ISO15765-2(2016版本)
|
||||||
|
|
||||||
|
// 帧类型
|
||||||
|
typedef U8 ZCAN_UDS_FRAME_TYPE;
|
||||||
|
#define ZCAN_UDS_FRAME_CAN 0 // CAN帧
|
||||||
|
#define ZCAN_UDS_FRAME_CANFD 1 // CANFD帧
|
||||||
|
#define ZCAN_UDS_FRAME_CANFD_BRS 2 // CANFD加速帧
|
||||||
|
|
||||||
|
// UDS填充模式
|
||||||
|
#define FILL_MODE_SHORT 0 // 小于8字节填充至8字节,大于8字节时按DLC就近填充
|
||||||
|
#define FILL_MODE_NONE 1 // 不填充
|
||||||
|
#define FILL_MODE_MAX 2 // 填充至最大数据长度 (不建议)
|
||||||
|
|
||||||
|
// CAN UDS请求数据
|
||||||
|
typedef struct _ZCAN_UDS_REQUEST
|
||||||
|
{
|
||||||
|
U32 req_id; // 请求事务ID,范围0~65535,本次请求的唯一标识
|
||||||
|
U8 channel; // 设备通道索引 0~255
|
||||||
|
ZCAN_UDS_FRAME_TYPE frame_type; // 帧类型
|
||||||
|
U8 reserved0[2]; // 保留
|
||||||
|
U32 src_addr; // 请求地址
|
||||||
|
U32 dst_addr; // 响应地址
|
||||||
|
U8 suppress_response; // 1:抑制响应
|
||||||
|
U8 sid; // 请求服务id
|
||||||
|
U8 reserved1[6]; // 保留
|
||||||
|
struct {
|
||||||
|
U32 timeout; // 响应超时时间(ms)。因PC定时器误差,建议设置不小于200ms
|
||||||
|
U32 enhanced_timeout; // 收到消极响应错误码为0x78后的超时时间(ms)。因PC定时器误差,建议设置不小于200ms
|
||||||
|
U8 check_any_negative_response:1; // 接收到非本次请求服务的消极响应时是否需要判定为响应错误
|
||||||
|
U8 wait_if_suppress_response:1; // 抑制响应时是否需要等待消极响应,等待时长为响应超时时间
|
||||||
|
U8 flag:6; // 保留
|
||||||
|
U8 reserved0[7]; // 保留
|
||||||
|
} session_param; // 会话层参数
|
||||||
|
struct {
|
||||||
|
ZCAN_UDS_TRANS_VER version; // 传输协议版本, VERSION_0, VERSION_1
|
||||||
|
U8 max_data_len; // 单帧最大数据长度, can:8, canfd:64
|
||||||
|
U8 local_st_min; // 本程序发送流控时用,连续帧之间的最小间隔, 0x00-0x7F(0ms~127ms), 0xF1-0xF9(100us~900us)
|
||||||
|
U8 block_size; // 流控帧的块大小
|
||||||
|
U8 fill_byte; // 无效字节的填充数据
|
||||||
|
U8 ext_frame; // 0:标准帧 1:扩展帧
|
||||||
|
U8 is_modify_ecu_st_min; // 是否忽略ECU返回流控的STmin,强制使用本程序设置的 remote_st_min
|
||||||
|
U8 remote_st_min; // 发送多帧时用, is_ignore_ecu_st_min = 1 时有效, 0x00-0x7F(0ms~127ms), 0xF1-0xF9(100us~900us)
|
||||||
|
U32 fc_timeout; // 接收流控超时时间(ms), 如发送首帧后需要等待回应流控帧
|
||||||
|
U8 fill_mode; // 数据长度填充模式, FILL_MODE_SHORT, FILL_MODE_NONE, FILL_MODE_MAX
|
||||||
|
U8 reserved0[3]; // 保留
|
||||||
|
} trans_param; // 传输层参数
|
||||||
|
U8 *data; // 数据数组(不包含SID)
|
||||||
|
U32 data_len; // 数据数组的长度
|
||||||
|
U32 reserved2; // 保留
|
||||||
|
} ZCAN_UDS_REQUEST;
|
||||||
|
|
||||||
|
// UDS错误码
|
||||||
|
typedef U8 ZCAN_UDS_ERROR;
|
||||||
|
#define ZCAN_UDS_ERROR_OK 0 // 没错误
|
||||||
|
#define ZCAN_UDS_ERROR_TIMEOUT 1 // 响应超时
|
||||||
|
#define ZCAN_UDS_ERROR_TRANSPORT 2 // 发送数据失败
|
||||||
|
#define ZCAN_UDS_ERROR_CANCEL 3 // 取消请求
|
||||||
|
#define ZCAN_UDS_ERROR_SUPPRESS_RESPONSE 4 // 抑制响应
|
||||||
|
#define ZCAN_UDS_ERROR_BUSY 5 // 忙碌中
|
||||||
|
#define ZCAN_UDS_ERROR_REQ_PARAM 6 // 请求参数错误
|
||||||
|
#define ZCAN_UDS_ERROR_OTHTER 100
|
||||||
|
|
||||||
|
typedef U8 ZCAN_UDS_RESPONSE_TYPE;
|
||||||
|
#define ZCAN_UDS_RT_NEGATIVE 0 // 消极响应
|
||||||
|
#define ZCAN_UDS_RT_POSITIVE 1 // 积极响应
|
||||||
|
|
||||||
|
// UDS响应数据
|
||||||
|
typedef struct _ZCAN_UDS_RESPONSE
|
||||||
|
{
|
||||||
|
ZCAN_UDS_ERROR status; // 响应状态
|
||||||
|
U8 reserved[6]; // 保留
|
||||||
|
ZCAN_UDS_RESPONSE_TYPE type; // 响应类型
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
U8 sid; // 响应服务id
|
||||||
|
U32 data_len; // 数据长度(不包含SID), 数据存放在接口传入的dataBuf中
|
||||||
|
} positive;
|
||||||
|
struct {
|
||||||
|
U8 neg_code; // 固定为0x7F
|
||||||
|
U8 sid; // 请求服务id
|
||||||
|
U8 error_code; // 错误码
|
||||||
|
} negative;
|
||||||
|
U8 raw[8];
|
||||||
|
};
|
||||||
|
} ZCAN_UDS_RESPONSE;
|
||||||
|
|
||||||
|
// UDS控制类型
|
||||||
|
typedef U32 ZCAN_UDS_CTRL_CODE;
|
||||||
|
#define ZCAN_UDS_CTRL_STOP_REQ 0 // 停止UDS请求
|
||||||
|
|
||||||
|
// UDS控制请求
|
||||||
|
typedef struct _ZCAN_UDS_CTRL_REQ
|
||||||
|
{
|
||||||
|
U32 reqID; // 请求事务ID,指明要操作哪一条请求
|
||||||
|
ZCAN_UDS_CTRL_CODE cmd; // 控制类型
|
||||||
|
U8 reserved[8]; // 保留
|
||||||
|
} ZCAN_UDS_CTRL_REQ;
|
||||||
|
|
||||||
|
// UDS控制结果
|
||||||
|
typedef U32 ZCAN_UDS_CTRL_RESULT;
|
||||||
|
#define ZCAN_UDS_CTRL_RESULT_OK 0 // 成功
|
||||||
|
#define ZCAN_UDS_CTRL_RESULT_ERR 1 // 失败
|
||||||
|
|
||||||
|
// UDS控制响应数据
|
||||||
|
typedef struct _ZCAN_UDS_CTRL_RESP
|
||||||
|
{
|
||||||
|
ZCAN_UDS_CTRL_RESULT result; // 操作结果
|
||||||
|
U8 reserved[8]; // 保留
|
||||||
|
} ZCAN_UDS_CTRL_RESP;
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
//
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_OpenDevice(U32 Type, U32 Card, U32 Reserved);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_CloseDevice(U32 Type, U32 Card);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_InitCAN(U32 Type, U32 Card, U32 Port, ZCAN_INIT *pInit);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_ReadBoardInfo(U32 Type, U32 Card, ZCAN_DEV_INF *pInfo);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_ReadErrInfo(U32 Type, U32 Card, U32 Port, ZCAN_ERR_MSG *pErr);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_ReadCANStatus(U32 Type, U32 Card, U32 Port, ZCAN_STAT *pStat);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_GetReference(U32 Type, U32 Card, U32 Port, U32 Ref, void *pData);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_SetReference(U32 Type, U32 Card, U32 Port, U32 Ref, void *pData);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_GetReceiveNum(U32 Type, U32 Card, U32 Port);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_ClearBuffer(U32 Type, U32 Card, U32 Port);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_StartCAN(U32 Type, U32 Card, U32 Port);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_ResetCAN(U32 Type, U32 Card, U32 Port);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_Transmit(U32 Type, U32 Card, U32 Port, ZCAN_20_MSG *pData, U32 Count);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_TransmitFD(U32 Type, U32 Card, U32 Port, ZCAN_FD_MSG *pData, U32 Count);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_Receive(U32 Type, U32 Card, U32 Port, ZCAN_20_MSG *pData, U32 Count, U32 Time);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_ReceiveFD(U32 Type, U32 Card, U32 Port, ZCAN_FD_MSG *pData, U32 Count, U32 Time);
|
||||||
|
EXTERN_C U32 ZCAN_API VCI_Debug(U32 Debug);
|
||||||
|
|
||||||
|
// LIN
|
||||||
|
EXTERN_C U32 VCI_InitLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_INIT_CONFIG pLINInitConfig);
|
||||||
|
EXTERN_C U32 VCI_StartLIN(U32 Type, U32 Card, U32 LinChn);
|
||||||
|
EXTERN_C U32 VCI_ResetLIN(U32 Type, U32 Card, U32 LinChn);
|
||||||
|
EXTERN_C U32 VCI_TransmitLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_MSG pSend, U32 Len);
|
||||||
|
EXTERN_C U32 VCI_GetLINReceiveNum(U32 Type, U32 Card, U32 LinChn);
|
||||||
|
EXTERN_C U32 VCI_ClearLINBuffer(U32 Type, U32 Card, U32 LinChn);
|
||||||
|
EXTERN_C U32 VCI_ReceiveLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_MSG pReceive, U32 Len,int WaitTime);
|
||||||
|
EXTERN_C U32 VCI_SetLINSubscribe(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_SUBSCIBE_CFG pSend, U32 nSubscribeCount);
|
||||||
|
EXTERN_C U32 VCI_SetLINPublish(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_PUBLISH_CFG pSend, U32 nPublishCount);
|
||||||
|
|
||||||
|
// 合并接收
|
||||||
|
EXTERN_C U32 VCI_TransmitData(unsigned Type, unsigned Card, unsigned Port, ZCANDataObj *pData, unsigned Count);
|
||||||
|
EXTERN_C U32 VCI_ReceiveData(unsigned Type, unsigned Card, unsigned Port, ZCANDataObj *pData, unsigned Count, unsigned Time);
|
||||||
|
|
||||||
|
// UDS
|
||||||
|
EXTERN_C U32 VCI_UDS_Request(unsigned Type, unsigned Card, const ZCAN_UDS_REQUEST *req, ZCAN_UDS_RESPONSE *resp, U8 *dataBuf, U32 dataBufSize);
|
||||||
|
EXTERN_C U32 VCI_UDS_Control(unsigned Type, unsigned Card, const ZCAN_UDS_CTRL_REQ *ctrl, ZCAN_UDS_CTRL_RESP *resp);
|
||||||
Reference in New Issue
Block a user