Port complete CAN-aware risk pipeline to C++

This commit is contained in:
zmv
2026-09-23 19:51:56 +08:00
parent 0f609612a6
commit e1e7fbe2fb
81 changed files with 27329 additions and 6381 deletions
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <condition_variable>
#include <deque>
#include <mutex>
#include <optional>
#include <stdexcept>
namespace cms {
// FIFO with bounded ownership. Normal close drains; cancellation may discard.
template<class T> class BoundedQueue {
public:
explicit BoundedQueue(std::size_t capacity):capacity_(capacity) {
if(!capacity)throw std::invalid_argument("Queue capacity must be positive");
}
bool push(T item) {
std::unique_lock<std::mutex> lock(mutex_);
changed_.wait(lock,[&]{return closed_||items_.size()<capacity_;});
if(closed_)return false;
items_.push_back(std::move(item));changed_.notify_all();return true;
}
bool pushLatest(T item) {
std::lock_guard<std::mutex> lock(mutex_);
if(closed_)return false;
if(items_.size()>=capacity_)items_.pop_front();
items_.push_back(std::move(item));changed_.notify_all();return true;
}
std::optional<T> pop() {
std::unique_lock<std::mutex> lock(mutex_);
changed_.wait(lock,[&]{return closed_||!items_.empty();});
if(items_.empty())return {};
T item=std::move(items_.front());items_.pop_front();changed_.notify_all();return item;
}
void close(bool discard=false) {
std::lock_guard<std::mutex> lock(mutex_);
closed_=true;if(discard)items_.clear();changed_.notify_all();
}
private:
const std::size_t capacity_;
std::mutex mutex_;std::condition_variable changed_;
std::deque<T> items_;bool closed_{};
};
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "image.hpp"
#include <atomic>
#include <memory>
#include <optional>
#include <string>
namespace cms {
struct CameraConfig {
std::string device{"/dev/video5"};
std::string video;
int width{1920},height{1536},fps{30},buffers{4},timeout_ms{2000};
};
struct CapturedFrame {
CaptureMetadata metadata;
std::shared_ptr<const BgrImage> image;
};
// The capture thread exclusively owns read(); requestStop() is thread safe.
class Camera {
public:
explicit Camera(CameraConfig config);
~Camera();
Camera(Camera const&)=delete;
Camera& operator=(Camera const&)=delete;
std::optional<CapturedFrame> read();
void requestStop() noexcept;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
std::uint64_t monotonicNs();
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "camera.hpp"
#include "frame_result.hpp"
#include <filesystem>
#include <memory>
namespace cms {
struct PipelineConfig {
CameraConfig camera;
std::filesystem::path engines,door_mask,door_zone_config;
bool can_enabled{true};
std::string can_side{"left"},active_door{"can"};
double can_timeout_ms{1000};
std::uint64_t max_frames{};
std::filesystem::path results_path,pose_records_path;
double detector_confidence{0.30};
int max_people{3};
double warning_relative{0.30},release_relative{0.45};
std::size_t queue_capacity{2};
bool door_mask_calibration_verified{false};
};
struct PipelineStats {
std::uint64_t captured{},published{},driver_sequence_gaps{},discarded_error_frames{};
double mean_capture_to_result_ms{};
};
// One lifecycle per instance. start() warms engines before starting capture.
// Runs continuously until requestStop() or a worker failure.
// latest() reads the immutable result variable without removing or acknowledging it.
// No result queue, consumer registration or frame-count termination.
// Stop is safe from any thread;
// destruction closes queues, interrupts capture and joins every worker.
class CameraPipeline {
public:
explicit CameraPipeline(PipelineConfig config);
~CameraPipeline();
CameraPipeline(CameraPipeline const&)=delete;
CameraPipeline& operator=(CameraPipeline const&)=delete;
void start();
FrameResultPtr latest() const;
bool running() const noexcept;
void requestStop() noexcept;
void join();
PipelineStats stats() const;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
}
+54
View File
@@ -0,0 +1,54 @@
// can_comm.h - USBCANFD CAN/CANFD 收发封装
//
// 整合自 vision-anti-pinch_bak/main.cpp 的 CAN 模块, 提供:
// can_start() - 打开设备, 初始化并启动双通道接收线程
// can_stop() - 停止接收并关闭设备
// can_send() - 发送一帧 CAN (8 字节)
// canfd_send() - 发送一帧 CANFD (64 字节)
//
// 依赖 zcan.h (include/) 和 libusbcanfd.so + libusb-1.0.so (lib/)
#pragma once
#include <cstdint>
#include <string>
namespace CanComm {
// 初始化并启动 CAN 收发(双通道, 仲裁域 500kbps / 数据域 2000kbps)。
// 返回 true 成功; false 失败(主流程可继续仅取图)。
bool can_start();
// 停止 CAN 接收线程并关闭设备。
void can_stop();
// 注册报文解析回调。收到每帧 CAN/CANFD 数据帧时调用。
// time: 时间戳(us)
// msg_id: CAN-ID
// data: 数据指针(回调内有效, 不要保存)
// len: 数据长度
// 传 nullptr 取消注册。
using MsgCallback = void (*)(int64_t time, uint32_t msg_id, const uint8_t *data, uint32_t len);
void set_msg_callback(MsgCallback cb);
// 发送一帧 CAN2.0 数据帧(8 字节)。
// chn_idx: 通道号 0/1
// id: CAN-ID
// data: 8 字节数据(不足补 0, 超出截断)
// 返回实际发送帧数(0 失败)。
uint32_t can_send(int chn_idx, uint32_t id, const uint8_t data[8]);
// 发送一帧 CANFD 数据帧(64 字节, 扩展帧+加速)。
// chn_idx: 通道号 0/1
// id: CAN-ID
// data: 64 字节数据(不足补 0, 超出截断)
// 返回实际发送帧数(0 失败)。
uint32_t canfd_send(int chn_idx, uint32_t id, const uint8_t data[64]);
// 是否已打开并运行
bool is_open();
// 累计接收帧数(线程安全)
int receive_count();
} // namespace CanComm
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <array>
#include <string>
namespace cms {
struct CanSnapshot {
std::array<int,4> positions{{-1,-1,-1,-1}}; // LF, RF, LR, RR
double age_ms{-1};
bool valid{false};
std::string source{"hardware"},side{"left"};
};
class CanInput {
public:
explicit CanInput(bool enabled);
~CanInput();
CanInput(CanInput const&)=delete;
CanInput& operator=(CanInput const&)=delete;
CanSnapshot snapshot(std::string const& side,double timeoutMs) const;
private:
bool opened_{};
};
std::string activeDoor(CanSnapshot const& state);
}
+122
View File
@@ -0,0 +1,122 @@
#pragma once
#include <NvInfer.h>
#include <NvInferVersion.h>
#include <cuda_runtime.h>
#include <cstring>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
namespace fangjia {
inline void checkCuda(cudaError_t status, char const* expression, char const* file, int line) {
if (status != cudaSuccess) {
throw std::runtime_error(
std::string("CUDA failure: ") + expression + " at " + file + ":" +
std::to_string(line) + ": " + cudaGetErrorString(status));
}
}
#define CUDA_CHECK(expr) ::fangjia::checkCuda((expr), #expr, __FILE__, __LINE__)
class TrtLogger final : public nvinfer1::ILogger {
public:
explicit TrtLogger(Severity minimum = Severity::kWARNING) : minimum_(minimum) {}
void log(Severity severity, char const* message) noexcept override {
if (severity <= minimum_) {
std::cerr << "[TensorRT] " << message << '\n';
}
}
private:
Severity minimum_;
};
template <typename T>
struct TrtDeleter {
void operator()(T* object) const noexcept { delete object; }
};
template <typename T>
using TrtUniquePtr = std::unique_ptr<T, TrtDeleter<T>>;
inline std::vector<std::uint8_t> readBinary(std::filesystem::path const& path) {
std::ifstream stream(path, std::ios::binary | std::ios::ate);
if (!stream) {
throw std::runtime_error("Cannot open " + path.string());
}
auto const size = stream.tellg();
if(size<=0)throw std::runtime_error("Empty or unreadable engine: "+path.string());
stream.seekg(0);
std::vector<std::uint8_t> bytes(static_cast<std::size_t>(size));
if(!stream.read(reinterpret_cast<char*>(bytes.data()), size))throw std::runtime_error("Truncated engine: "+path.string());
return bytes;
}
inline void writeBinary(std::filesystem::path const& path, void const* data, std::size_t size) {
std::filesystem::create_directories(path.parent_path());
std::ofstream stream(path, std::ios::binary);
if (!stream) {
throw std::runtime_error("Cannot write " + path.string());
}
stream.write(static_cast<char const*>(data), static_cast<std::streamsize>(size));
}
inline std::size_t volume(nvinfer1::Dims const& dims) {
std::size_t value = 1;
for (int index = 0; index < dims.nbDims; ++index) {
if (dims.d[index] < 0) {
throw std::runtime_error("Unresolved dynamic tensor dimension");
}
value *= static_cast<std::size_t>(dims.d[index]);
}
return value;
}
inline std::size_t dataTypeSize(nvinfer1::DataType type) {
switch (type) {
case nvinfer1::DataType::kFLOAT: return 4;
case nvinfer1::DataType::kHALF: return 2;
case nvinfer1::DataType::kINT8: return 1;
case nvinfer1::DataType::kINT32: return 4;
#if NV_TENSORRT_MAJOR >= 10
case nvinfer1::DataType::kINT64: return 8;
#endif
case nvinfer1::DataType::kBOOL: return 1;
default: throw std::runtime_error("Unsupported TensorRT data type");
}
}
class CudaEventTimer {
public:
CudaEventTimer() {
CUDA_CHECK(cudaEventCreate(&start_));
CUDA_CHECK(cudaEventCreate(&end_));
}
~CudaEventTimer() {
cudaEventDestroy(start_);
cudaEventDestroy(end_);
}
void start(cudaStream_t stream) { CUDA_CHECK(cudaEventRecord(start_, stream)); }
float stop(cudaStream_t stream) {
CUDA_CHECK(cudaEventRecord(end_, stream));
CUDA_CHECK(cudaEventSynchronize(end_));
float milliseconds = 0.0F;
CUDA_CHECK(cudaEventElapsedTime(&milliseconds, start_, end_));
return milliseconds;
}
private:
cudaEvent_t start_{};
cudaEvent_t end_{};
};
} // namespace fangjia
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "camera_pipeline.hpp"
namespace cms {
PipelineConfig loadConfig(std::filesystem::path const& path);
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "pose_engine.hpp"
#include "gpu_preprocess.hpp"
namespace cms {
class Detector {
public:
Detector(std::filesystem::path const& path, fangjia::TrtLogger& logger, float confidence, int maxPeople);
std::vector<fangjia::Box> infer(cv::Mat const& frame,cudaStream_t stream,int filterHeight=0);
private:
fangjia::TrtEngine engine_;
float confidence_;
int maxPeople_, width_, height_;
std::string input_,output_;
std::unique_ptr<YoloPreprocess> preprocess_;
std::vector<float> outputHost_;
};
cv::Mat maskOtherPeople(cv::Mat const& frame,std::vector<fangjia::Box> const& boxes,size_t target);
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <nlohmann/json.hpp>
#include <opencv2/core.hpp>
#include <filesystem>
#include <map>
#include <set>
using Json = nlohmann::ordered_json;
namespace cms {
struct DoorOptions {
std::filesystem::path constant, directory;
std::string format{"auto"};
std::set<std::string> labels{"door", "车门"};
int offset{};
bool require{};
double warning{0.30}, release{0.45};
};
class DoorRisk {
public:
DoorRisk(DoorOptions options, cv::Size size);
void initialize() { load(0); }
Json evaluate(int frame, Json& record);
cv::Mat const& mask() const { return mask_; }
private:
void load(int frame);
cv::Mat rasterize(std::filesystem::path const& path) const;
DoorOptions options_;
cv::Size size_;
std::string format_, status_;
std::filesystem::path loadedPath_;
std::map<int, std::filesystem::path> paths_;
cv::Mat mask_, distance_;
bool cached_{};
};
double rounded(double value, int digits);
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "door_risk.hpp"
#include <deque>
#include <unordered_map>
namespace cms {
class DoorZoneAssigner {
public:
explicit DoorZoneAssigner(std::filesystem::path const& configuration);
Json update(int frame, Json const& record, cv::Size size);
private:
std::string classify(double probability) const;
Json features_;
double frontMin_{},rearMax_{},minimumHeight_{};
int minimumPoseFeatures_{},historyFrames_{},staleFrames_{};
std::unordered_map<int,std::deque<double>> history_;
std::unordered_map<int,int> lastSeen_;
};
struct DoorDecisions { Json doors; Json selected; };
DoorDecisions buildDoorRisks(Json const& raw, Json const& assignments,
std::string const& activeDoor, bool canValid);
}
+96
View File
@@ -0,0 +1,96 @@
#pragma once
// Public downstream contract. Owns its data; no GPU, JSON or network handles.
#include "image.hpp"
#include "can_state.hpp"
#include <map>
#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace cms {
struct DoorMaskResult {
std::string status;
int annotation_index{};
std::optional<std::string> path;
std::string format;
int area_px{};
};
struct RegionRisk {
std::string status;
std::optional<double> risk_score;
std::string risk_level;
std::string decision;
std::string source;
std::string door_zone;
int track_id{};
std::string side;
std::optional<std::string> state;
// Unavailable evidence has no geometric measurements, matching the reference.
std::optional<int> region_area_px, door_overlap_px;
std::optional<double> region_overlap_ratio, min_distance_px;
std::optional<double> region_scale_px, distance_relative;
};
struct RiskResult {
std::string risk_type;
std::optional<std::string> door_id;
std::string status;
std::optional<double> risk_score;
std::string risk_level;
std::string decision;
std::optional<std::string> source;
std::optional<int> track_id;
std::optional<std::string> side;
DoorMaskResult door_mask;
double warning_distance_relative{}, release_distance_relative{};
std::vector<RegionRisk> regions;
};
struct PosePoint { double x{},y{},score{}; };
struct HandState {
std::string state, source;
bool limb_consensus{}, distance_eligible{}, alarm_eligible{};
};
struct PersonResult {
int track_id{};
double confidence{};
std::array<double,4> box{};
std::array<PosePoint,133> selected{}, smoothed{}, temporal{};
std::array<HandState,2> hands; // left, right
};
struct HandRegion {
int person_id{},track_id{};
std::string side;
std::array<PosePoint,21> keypoints{};
};
struct StageTiming {
double detector_ms{}, pose_ms{}, fusion_risk_ms{}, capture_to_result_ms{};
};
struct DoorAssignment {
int track_id{}, person_index{}, pose_feature_count{}, history_frames{};
std::string door_zone, raw_door_zone, evidence_source;
double front_probability{}, smoothed_front_probability{}, confidence{};
std::map<std::string,double> features, feature_frontness;
};
struct FrameResult {
int frame_index{};
std::vector<int> track_ids;
std::vector<std::array<double,4>> boxes;
RiskResult risk;
RiskResult ungated_risk;
std::map<std::string,RiskResult> door_risks;
std::vector<DoorAssignment> door_assignments;
std::string active_door;
CanSnapshot can;
CaptureMetadata capture;
StageTiming timing;
std::vector<PersonResult> persons;
std::vector<HandRegion> hand_regions;
// Both images belong to this result. The constant mask is shared across frames.
std::shared_ptr<const BgrImage> image;
std::shared_ptr<const BinaryMask> door_mask_pixels;
bool door_mask_calibration_verified{};
};
using FrameResultPtr = std::shared_ptr<const FrameResult>;
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "common.hpp"
#include <npp.h>
#include <opencv2/core.hpp>
namespace cms {
inline void nppCheck(NppStatus status) {if(status<0)throw std::runtime_error("NPP error "+std::to_string(int(status)));}
inline NppStreamContext nppContext(cudaStream_t stream) {
NppStreamContext c{};c.hStream=stream;CUDA_CHECK(cudaGetDevice(&c.nCudaDeviceId));cudaDeviceProp p{};CUDA_CHECK(cudaGetDeviceProperties(&p,c.nCudaDeviceId));
c.nMultiProcessorCount=p.multiProcessorCount;c.nMaxThreadsPerMultiProcessor=p.maxThreadsPerMultiProcessor;c.nMaxThreadsPerBlock=p.maxThreadsPerBlock;c.nSharedMemPerBlock=p.sharedMemPerBlock;c.nCudaDevAttrComputeCapabilityMajor=p.major;c.nCudaDevAttrComputeCapabilityMinor=p.minor;CUDA_CHECK(cudaStreamGetFlags(stream,&c.nStreamFlags));return c;
}
class YoloPreprocess {
public:
YoloPreprocess(int width,int height):width_(width),height_(height) {CUDA_CHECK(cudaMalloc(&letter_,size_t(width)*height*3));}
~YoloPreprocess(){cudaFree(source_);cudaFree(letter_);}
void enqueue(cv::Mat const& frame,float* destination,cudaStream_t stream);
private:
int width_,height_;size_t capacity_{};unsigned char *source_{},*letter_{};
};
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
#include <vector>
namespace cms {
// Immutable when published. Stride is explicit; no borrowed camera/GPU memory.
struct BgrImage {
int width{}, height{}, stride{};
std::vector<std::uint8_t> pixels;
};
struct BinaryMask {
int width{}, height{}, stride{};
std::vector<std::uint8_t> pixels;
};
struct CaptureMetadata {
std::uint64_t frame_index{};
std::uint32_t driver_sequence{};
std::uint64_t driver_timestamp_ns{};
std::uint64_t received_monotonic_ns{};
std::uint64_t capture_monotonic_ns{};
bool driver_timestamp_monotonic{};
std::uint32_t skipped_driver_frames{};
std::uint32_t discarded_error_frames{};
};
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "frame_result.hpp"
#include <atomic>
#include <stdexcept>
namespace cms {
// One immutable result variable. Readers neither remove nor acknowledge results.
class LatestResult {
public:
void publish(FrameResultPtr result) {
if(!result)throw std::invalid_argument("Cannot publish a null result");
std::atomic_store_explicit(&latest_,std::move(result),std::memory_order_release);
}
FrameResultPtr load() const {
return std::atomic_load_explicit(&latest_,std::memory_order_acquire);
}
private:
FrameResultPtr latest_;
};
}
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include <array>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
namespace fangjia {
struct Box {
float x1{};
float y1{};
float x2{};
float y2{};
float score{};
int trackId{};
};
struct Keypoint {
float x{};
float y{};
float score{};
};
struct PersonPose {
Box box;
std::array<Keypoint, 133> keypoints{};
};
enum class LimbState {
Observed,
Provisional,
Recovered,
Reassigned,
Interpolated,
PredictedShort,
ReachableMedium,
ReachableLong,
};
inline char const* limbStateName(LimbState state) {
switch (state) {
case LimbState::Observed: return "observed";
case LimbState::Provisional: return "provisional";
case LimbState::Recovered: return "recovered";
case LimbState::Reassigned: return "reassigned";
case LimbState::Interpolated: return "interpolated";
case LimbState::PredictedShort: return "predicted_short";
case LimbState::ReachableMedium: return "reachable_medium";
case LimbState::ReachableLong: return "reachable_long";
}
return "unknown";
}
struct LimbObservation {
int trackId{};
bool left{};
LimbState state{LimbState::ReachableLong};
Keypoint elbow{};
Keypoint wrist{};
std::array<Keypoint, 21> hand{};
Box personBox{};
bool handValid{};
bool alarmEligible{};
float handQuality{};
};
// Exact pre-V2 hand evidence consumed by the Python risk stage. These points
// come from raw/isolated fusion before temporal reconstruction.
struct GlobalHand {
int personId{};
int trackId{};
bool left{};
std::string source;
std::string state;
bool distanceEligible{};
bool alarmEligible{};
float score{};
float spanPx{};
std::array<Keypoint, 21> keypoints{};
std::vector<std::pair<int, bool>> aliases;
};
struct FusionMeta {
bool limbConsensus{}, handValid{}, distanceEligible{}, alarmEligible{};
double handScore{};
std::string handSource, handState;
};
struct PoseFrameResult {
std::vector<std::array<FusionMeta, 2>> metadata;
std::vector<PersonPose> selectedPeople;
std::vector<PersonPose> smoothedPeople;
std::vector<PersonPose> v2People;
std::vector<LimbObservation> limbs;
std::vector<GlobalHand> globalHands;
};
} // namespace fangjia
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include "pipeline_types.hpp"
#include "trt_engine.hpp"
#include <opencv2/core.hpp>
#include <deque>
#include <array>
#include <optional>
#include <string>
#include <unordered_map>
namespace fangjia {
class BoxTracker {
public:
std::vector<int> update(std::vector<Box>& boxes, int frameIndex, int width, int height);
private:
struct Track { Box box; int lastFrame{}; };
int nextId_{1};
std::unordered_map<int, Track> tracks_;
};
class RtmwEngine {
public:
RtmwEngine(
std::filesystem::path const& enginePath,
TrtLogger& logger,
int maxSourceWidth,
int maxSourceHeight,
int maxPeople = 3);
~RtmwEngine();
RtmwEngine(RtmwEngine const&) = delete;
RtmwEngine& operator=(RtmwEngine const&) = delete;
std::vector<PersonPose> infer(
cv::Mat const& frame,
std::vector<Box> const& boxes,
cudaStream_t stream,
float* preprocessMs = nullptr,
float* inferenceMs = nullptr,
float* postprocessMs = nullptr);
void warmup(cudaStream_t stream);
private:
TrtEngine engine_;
int maxSourceWidth_{};
int maxSourceHeight_{};
int maxPeople_{};
std::uint8_t* sourceDevice_{};
std::uint8_t* cropsDevice_{};
float* networkDevice_{};
std::vector<double> centersHost_;
std::vector<double> scalesHost_;
std::vector<float> simccXHost_;
std::vector<float> simccYHost_;
};
class PoseFusionTracker {
public:
PoseFrameResult update(
std::vector<PersonPose> const& raw,
std::vector<PersonPose> const& isolated,
int frameIndex);
private:
struct Vec2 { double x{}; double y{}; };
struct SideFusion {
std::array<Vec2, 3> limbPrevious{};
std::array<Vec2, 3> limbVelocity{};
std::array<Vec2, 21> handPrevious{};
std::array<Vec2, 21> handVelocity{};
bool hasLimbPrevious{};
bool hasLimbVelocity{};
bool hasHandPrevious{};
bool hasHandVelocity{};
std::string limbSource;
std::string handSource;
};
struct Endpoint {
bool hasWrist{};
Vec2 wrist{};
Vec2 velocity{};
std::array<Vec2, 21> handShape{};
bool hasHandShape{};
int lastObservedFrame{-1};
int missedFrames{};
bool hasLastElbow{};
Vec2 lastElbow{};
bool hasLastDirection{};
Vec2 lastDirection{};
};
struct PersonState {
std::array<SideFusion, 2> fusion;
std::array<Endpoint, 2> endpoints;
std::array<std::deque<std::array<double, 2>>, 2> boneSamples;
std::array<Vec2, 133> smoothed{};
bool hasSmoothed{};
int lastFrame{-1};
};
std::unordered_map<int, PersonState> states_;
};
} // namespace fangjia
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "door_risk.hpp"
#include "pipeline_types.hpp"
namespace cms {
inline Json xy(std::vector<fangjia::PersonPose> const& people, int digits=3) {
Json a=Json::array(); for(auto const& p:people) { Json k=Json::array();for(auto const& v:p.keypoints) k.push_back({rounded(v.x,digits),rounded(v.y,digits)});a.push_back(k); } return a;
}
inline Json scores(std::vector<fangjia::PersonPose> const& people) {
Json a=Json::array();for(auto const& p:people) {Json k=Json::array();for(auto const& v:p.keypoints) k.push_back(v.score);a.push_back(k);}return a;
}
inline Json makeRecord(int frame,std::vector<fangjia::PersonPose> const& raw,std::vector<fangjia::PersonPose> const& isolated,fangjia::PoseFrameResult const& fused, bool diagnostics=true, bool visual=true) {
Json r={{"frame_index",frame},{"track_ids",Json::array()},{"boxes",Json::array()},{"det_confidences",Json::array()}};
for(auto const&p:raw) {auto const&b=p.box;r["track_ids"].push_back(b.trackId);r["boxes"].push_back({rounded(b.x1,3),rounded(b.y1,3),rounded(b.x2,3),rounded(b.y2,3)});r["det_confidences"].push_back(rounded(b.score,4));}
if(diagnostics) {
r["raw_keypoints"]=xy(raw,9);r["raw_scores"]=scores(raw);r["isolated_keypoints"]=xy(isolated,9);r["isolated_scores"]=scores(isolated);
r["inference_boxes"]=Json::array();for(auto const&p:raw)r["inference_boxes"].push_back({p.box.x1,p.box.y1,p.box.x2,p.box.y2});
}
if(visual||diagnostics) {
r["temporal_selected_keypoints"]=xy(fused.selectedPeople);r["temporal_keypoints"]=xy(fused.smoothedPeople);r["v2_keypoints"]=xy(fused.v2People);
r["temporal_metadata"]=Json::array();r["v2_metadata"]=Json::array();
for(size_t p=0;p<fused.metadata.size();++p) {
Json m=Json::object(),v=Json::object();
for(int s=0;s<2;++s) {auto side=s==0?"left":"right";auto const&t=fused.metadata[p][s];
m[side]={{"limb_state",t.limbConsensus?"consensus":"inferred"},{"hand_source",t.handSource},{"hand_state",t.handState},{"distance_eligible",t.distanceEligible},{"alarm_eligible",t.alarmEligible},{"selected_hand_valid",t.handValid},{"selected_hand_score",t.handScore}};
v[side]={{"state",fangjia::limbStateName(fused.limbs[p*2+s].state)}};
} r["temporal_metadata"].push_back(m);r["v2_metadata"].push_back(v);
}
}
r["global_hands"]=Json::array();
for(auto const& h:fused.globalHands) {
Json points=Json::array(),aliases=Json::array();double cx=0,cy=0;
for(auto const& k:h.keypoints) {points.push_back({rounded(k.x,3),rounded(k.y,3)});cx+=k.x;cy+=k.y;}
for(auto const&a:h.aliases) aliases.push_back({{"person_id",a.first},{"side",a.second?"left":"right"}});
r["global_hands"].push_back({{"person_id",h.personId},{"side",h.left?"left":"right"},{"source",h.source},{"state",h.state},{"distance_eligible",h.distanceEligible},{"alarm_eligible",h.alarmEligible},{"score",rounded(h.score,4)},{"center",{rounded(cx/21,3),rounded(cy/21,3)}},{"span_px",rounded(h.spanPx,3)},{"keypoints",points},{"aliases",aliases}});
}
return r;
}
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "frame_result.hpp"
#include "door_risk.hpp"
namespace cms {
template<class T> inline std::optional<T> optionalField(Json const& j,char const* key) {
auto it=j.find(key);return it==j.end()||it->is_null()?std::nullopt:std::optional<T>(it->get<T>());
}
inline RiskResult parseRisk(Json const& j) {
RiskResult r;r.status=j.at("status");r.risk_score=optionalField<double>(j,"risk_score");r.risk_level=j.at("risk_level");r.decision=j.at("decision");
r.risk_type=j.value("risk_type",std::string());r.door_id=optionalField<std::string>(j,"door_id");
r.source=optionalField<std::string>(j,"source");r.track_id=optionalField<int>(j,"track_id");r.side=optionalField<std::string>(j,"side");
if(j.contains("door_mask")) {auto const& d=j.at("door_mask");r.door_mask={d.at("status"),d.at("annotation_index"),optionalField<std::string>(d,"path"),d.at("format"),d.at("area_px")};}
r.warning_distance_relative=j.value("warning_distance_relative",.30);r.release_distance_relative=j.value("release_distance_relative",.45);
for(auto const& x:j.at("regions")) {
RegionRisk v;v.status=x.at("status");v.risk_score=optionalField<double>(x,"risk_score");v.risk_level=x.at("risk_level");v.decision=x.at("decision");v.source=x.at("source");
v.track_id=x.at("track_id");v.side=x.at("side");v.state=optionalField<std::string>(x,"state");v.door_zone=x.value("door_zone",std::string());
v.region_area_px=optionalField<int>(x,"region_area_px");v.door_overlap_px=optionalField<int>(x,"door_overlap_px");
v.region_overlap_ratio=optionalField<double>(x,"region_overlap_ratio");v.min_distance_px=optionalField<double>(x,"min_distance_px");
v.region_scale_px=optionalField<double>(x,"region_scale_px");v.distance_relative=optionalField<double>(x,"distance_relative");r.regions.push_back(std::move(v));
}
return r;
}
inline DoorAssignment parseAssignment(Json const& j) {
DoorAssignment a;a.track_id=j.at("track_id");a.person_index=j.at("person_index");a.door_zone=j.at("door_zone");a.raw_door_zone=j.at("raw_door_zone");
a.front_probability=j.at("front_probability");a.smoothed_front_probability=j.at("smoothed_front_probability");a.confidence=j.at("confidence");a.evidence_source=j.at("evidence_source");
a.pose_feature_count=j.at("pose_feature_count");a.history_frames=j.at("history_frames");a.features=j.at("features").get<std::map<std::string,double>>();a.feature_frontness=j.at("feature_frontness").get<std::map<std::string,double>>();return a;
}
inline FrameResult makeFrameResult(Json const& record,Json const& raw,CaptureMetadata capture) {
FrameResult r;r.frame_index=record.at("frame_index");r.track_ids=record.at("track_ids").get<std::vector<int>>();r.boxes=record.at("boxes").get<std::vector<std::array<double,4>>>();r.capture=capture;r.ungated_risk=parseRisk(raw);return r;
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "frame_result.hpp"
#include "door_risk.hpp"
namespace cms {
Json serializeResult(FrameResult const& result);
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "camera_pipeline.hpp"
#include <atomic>
#include <csignal>
#include <pthread.h>
#include <stdexcept>
#include <thread>
namespace cms {
// Construct before starting workers so all threads inherit the signal mask.
// Stop requests run in an ordinary thread, never an asynchronous signal handler.
class StopSignals {
public:
explicit StopSignals(CameraPipeline& pipeline):pipeline_(pipeline) {
sigemptyset(&signals_);sigaddset(&signals_,SIGINT);sigaddset(&signals_,SIGTERM);
if(pthread_sigmask(SIG_BLOCK,&signals_,&old_)!=0)throw std::runtime_error("Cannot block termination signals");
try {
thread_=std::thread([this]{
while(!done_) {
timespec timeout{0,100000000};
int signal=sigtimedwait(&signals_,nullptr,&timeout);
if(signal==SIGINT||signal==SIGTERM){pipeline_.requestStop();break;}
}
});
}catch(...){pthread_sigmask(SIG_SETMASK,&old_,nullptr);throw;}
}
~StopSignals(){done_=true;if(thread_.joinable())thread_.join();pthread_sigmask(SIG_SETMASK,&old_,nullptr);}
StopSignals(StopSignals const&)=delete;
StopSignals& operator=(StopSignals const&)=delete;
private:
CameraPipeline& pipeline_;
sigset_t signals_{},old_{};
std::atomic<bool> done_{false};
std::thread thread_;
};
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include "common.hpp"
#include <unordered_map>
namespace fangjia {
class TrtEngine {
public:
TrtEngine(std::filesystem::path const& enginePath, TrtLogger& logger);
~TrtEngine();
TrtEngine(TrtEngine const&) = delete;
TrtEngine& operator=(TrtEngine const&) = delete;
void setInputShape(std::string const& name, nvinfer1::Dims const& shape);
void prepare();
void bindExternal(std::string const& name, void* pointer, std::size_t bytes);
void useOwned(std::string const& name);
bool enqueue(cudaStream_t stream);
void* device(std::string const& name);
std::size_t bytes(std::string const& name) const;
nvinfer1::Dims shape(std::string const& name) const;
nvinfer1::DataType dataType(std::string const& name) const;
std::vector<std::string> inputs() const;
std::vector<std::string> outputs() const;
private:
struct Buffer {
void* owned{};
void* active{};
std::size_t capacity{};
std::size_t required{};
bool external{};
};
Buffer& buffer(std::string const& name);
Buffer const& buffer(std::string const& name) const;
TrtUniquePtr<nvinfer1::IRuntime> runtime_;
TrtUniquePtr<nvinfer1::ICudaEngine> engine_;
TrtUniquePtr<nvinfer1::IExecutionContext> context_;
std::filesystem::path enginePath_;
std::unordered_map<std::string, Buffer> buffers_;
};
} // namespace fangjia
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "image.hpp"
#include <cstddef>
#include <memory>
namespace cms {
// GPU NPP BT.601 UYVY -> RGB followed by a CUDA BGR channel swap.
class UyvyConverter {
public:
UyvyConverter(int width,int height);
~UyvyConverter();
UyvyConverter(UyvyConverter const&)=delete;
UyvyConverter& operator=(UyvyConverter const&)=delete;
std::shared_ptr<const BgrImage> convert(void const* data,std::size_t stride);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
}
+91
View File
@@ -0,0 +1,91 @@
#ifndef V4L2_CAPTURE_H
#define V4L2_CAPTURE_H
#include <cstdint>
#include <stddef.h>
#include <string>
#include <vector>
// V4L2 流式(MMAP)取图封装
// 参考 quick_bring_up_64g.sh 中 v4l2src 的行为:
// - 打开 /dev/videoN
// - 协商 UYVY 像素格式与分辨率
// - 设置相机控制(sensor_mode / trig_pin 等, 等价于 v4l2-ctl -c ...)
// - MMAP 方式申请内核缓冲区并流式采集
class V4l2Capture {
public:
// 单帧采集结果
struct Frame {
void *data = nullptr; // 指向 MMAP 缓冲区, 释放前有效
size_t size = 0; // 实际帧字节数(bytesused)
uint32_t width = 0;
uint32_t height = 0;
uint32_t pixelformat = 0; // V4L2_PIX_FMT_* (如 V4L2_PIX_FMT_UYVY)
uint32_t bytes_per_line = 0;
uint32_t sequence = 0; // 驱动序号
uint64_t timestamp_us = 0; // 单调时钟时间戳(us)
int index = -1; // 内部缓冲区索引
};
V4l2Capture() = default;
~V4l2Capture();
V4l2Capture(const V4l2Capture &) = delete;
V4l2Capture &operator=(const V4l2Capture &) = delete;
// 打开设备节点, 例如 /dev/video5
bool open(const std::string &device);
// 关闭设备并释放所有资源
void close();
bool isOpen() const { return fd_ >= 0; }
// 协商像素格式与分辨率. pixelformat 使用 V4L2_PIX_FMT_* 四字符码
bool setFormat(uint32_t width, uint32_t height, uint32_t pixelformat);
// 设置帧率(通过 VIDIOC_S_PARM). fps 分子/分母均为 fps/1
bool setFps(uint32_t fps);
// 设置 V4L2 控制, 等价 v4l2-ctl -c <name>=<value>. id 为 V4L2_CID_* 之四字符码
// 例如 sensor_mode=0x009a2008, trig_pin=0x009a2084
bool setControl(uint32_t id, int32_t value);
// 申请 MMAP 缓冲区, 默认 4 个
bool initMmap(uint32_t buffer_count = 4);
// 开始 / 停止采集
bool startStreaming();
bool stopStreaming();
// 阻塞取一帧, 超时 timeout_ms 毫秒. 成功返回 true, 需配对调用 releaseFrame()
bool getFrame(Frame &frame, int timeout_ms = 1000);
// 归还缓冲区给驱动队列, 必须在下次 getFrame 前调用
void releaseFrame(const Frame &frame);
uint32_t width() const { return width_; }
uint32_t height() const { return height_; }
uint32_t pixelformat() const { return pixelformat_; }
private:
struct MmapBuffer {
void *start = nullptr;
size_t length = 0;
bool queued = false; // 是否在驱动队列中
};
// 归还指定索引的缓冲区 (内部使用)
void releaseFrameByIndex(int idx);
int fd_ = -1;
uint32_t width_ = 0;
uint32_t height_ = 0;
uint32_t pixelformat_ = 0;
uint32_t bytes_per_line_ = 0;
bool streaming_ = false;
std::vector<MmapBuffer> buffers_;
int last_dequeued_ = -1; // 当前已出队但未归还的缓冲区索引
};
#endif // V4L2_CAPTURE_H
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "frame_result.hpp"
#include <opencv2/core.hpp>
namespace cms {
class CameraPipeline;
// Pure drawing from the immutable result. Never re-runs inference.
cv::Mat renderResult(FrameResult const& result);
// Optional observer: closing the window returns without stopping the pipeline.
void runLocalVisualization(CameraPipeline const& pipeline);
}
+443
View File
@@ -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);