# visoscanSdk **Repository Path**: playboy_plus/visoscan-sdk ## Basic Information - **Project Name**: visoscanSdk - **Description**: VISIOSCAN SDK(C++) 本仓库提供 VISIOSCAN NAV 与 VISIOSCAN RD 两套设备的 C++ 封装,目标是让上层业务不必直接处理协议帧拼接、校验和 MDI 数据解析,只需要调用对外接口即可完成常见配置与数据接收。 当前仓库主要提供: NAV 设备 SDK RD 设备 SDK - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-16 - **Last Updated**: 2026-04-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # VISIOSCAN SDK(C++) 本仓库提供 `VISIOSCAN NAV` 与 `VISIOSCAN RD` 两套设备的 C++ 封装,目标是让上层业务不必直接处理协议帧拼接、校验和 MDI 数据解析,只需要调用对外接口即可完成常见配置与数据接收。 当前仓库主要提供: - `NAV` 设备 SDK:`include/visioscan_nav/visioscan_nav.hpp` - `RD` 设备 SDK:`include/visioscan_rd/visioscan_rd.hpp` - 两个命令行示例程序 - 一个网页配置工具 `visioscan_web_config` ## 1. 特性 - 使用 `C++17` - 无第三方依赖 - 命令通道使用 TCP - MDI 数据支持 UDP / TCP - 已封装常用读取、写入、状态、污染检测、网口配置、LED、设备名等接口 ## 2. 目录结构 ```text include/ visioscan_nav/visioscan_nav.hpp NAV 对外头文件 visioscan_rd/visioscan_rd.hpp RD 对外头文件 src/ visioscan_nav.cpp NAV 实现 visioscan_rd.cpp RD 实现 visioscan_web_config.cpp 网页配置工具 sample/ nav/nav_sample.cpp NAV 示例 rd/rd_sample.cpp RD 示例 ``` ## 3. 构建方法 ```bash cmake -S . -B build cmake --build build -j ``` 构建完成后通常会生成: - `build/libvisioscan_nav.so` - `build/libvisioscan_rd.so` - `build/visioscan_nav_sample` - `build/visioscan_rd_sample` - `build/visioscan_web_config` ## 4. 运行示例 ### 4.1 NAV 示例 ```bash ./build/visioscan_nav_sample [command_port] [local_mdi_port] ``` 例如: ```bash ./build/visioscan_nav_sample 192.168.1.2 3050 2368 ``` 带污染阈值设置: ```bash ./build/visioscan_nav_sample 192.168.1.2 3050 2368 --set-cont 20 40 ``` ### 4.2 RD 示例 ```bash ./build/visioscan_rd_sample [command_port] [local_mdi_port] ``` 例如: ```bash ./build/visioscan_rd_sample 192.168.1.2 3050 2368 ``` 带参数设置示例: ```bash ./build/visioscan_rd_sample 192.168.1.2 3050 2368 --set-cont 20 40 ./build/visioscan_rd_sample 192.168.1.2 3050 2368 --set-proto tcp ./build/visioscan_rd_sample 192.168.1.2 3050 2368 --set-name RD_Device_01 ./build/visioscan_rd_sample 192.168.1.2 3050 2368 --set-filter on ./build/visioscan_rd_sample 192.168.1.2 3050 2368 --set-netled on ./build/visioscan_rd_sample 192.168.1.2 3050 2368 --set-led 1 0 ``` ## 5. 快速上手流程 建议客户按下面顺序使用: 1. 先确认设备 IP、命令端口、MDI 端口 2. 调用 `connect()` 3. 用 `getProto()`、`getPType()`、`getResol()`、`getStatus()` 等读取当前配置 4. 根据需求调用 `setMode()` 或单独配置接口 5. 注册回调 `setScanCallback()` 6. 调用 `startMdi()` 7. 调用 `startScanStream()` 接收 MDI 8. 结束时调用 `stopScanStream()`、`stopMdi()`、`disconnect()` ## 6. 连接参数说明 ### 6.1 通用参数 #### `sensor_ip` 设备 IP 地址。 ```cpp cfg.sensor_ip = "192.168.1.2"; ``` #### `command_port` 命令 TCP 端口,常见为 `3050`。 ```cpp cfg.command_port = 3050; ``` #### `command_timeout_ms` 命令发送和应答接收超时,单位毫秒。 ```cpp cfg.command_timeout_ms = 1000; ``` #### `mdi_receive_timeout_ms` MDI 接收超时,单位毫秒。 ```cpp cfg.mdi_receive_timeout_ms = 1000; ``` ### 6.2 MDI 端口相关参数 #### `local_mdi_port` 当设备通过 UDP 发送 MDI 时,本机接收端口。 ```cpp cfg.local_mdi_port = 2368; ``` 注意: - 只有设备真正把 UDP MDI 发往你电脑的该端口,才能收到数据 - 若端口填错,`startScanStream()` 虽然可能成功,但不会收到有效扫描数据 #### `sensor_mdi_tcp_port` 当设备通过 TCP 输出 MDI 时,SDK 去连接的设备侧端口。 ```cpp cfg.sensor_mdi_tcp_port = 3050; ``` 若设为 `0`,当前实现通常默认复用 `command_port`。 ## 7. 常用模式参数说明 下面这些参数主要通过 `ModeConfig` 配置,并使用 `setMode()` 一次性下发。 ```cpp ModeConfig mode; ``` ### 7.1 设置 MDI 传输协议 ```cpp mode.proto = TransportProto::Udp; // 或 TransportProto::Tcp ``` 适用场景: - `Udp`:局域网内常见,实时性高 - `Tcp`:部分环境下更容易穿透,适合不方便接收 UDP 的场景 ### 7.2 设置数据包类型 ```cpp mode.packet_type = PacketType::DistanceOnly; // 或 mode.packet_type = PacketType::DistanceAndIntensity; ``` 说明: - `DistanceOnly`:每点仅距离,带宽更低 - `DistanceAndIntensity`:每点距离 + 强度,信息更完整 ### 7.3 设置角分辨率 ```cpp mode.resolution = AngularResolution::Deg0_2_At80Hz; ``` 可选值: - `Deg0_2_At80Hz` - `Deg0_1_At40Hz` - `Deg0_05_At20Hz` - `Deg0_025_At10Hz` 一般来说: - 分辨率越高,单圈点数越多 - 频率越低,单圈数据通常更细 ### 7.4 设置扫描方向 ```cpp mode.direction = Direction::Clockwise; // 或 Direction::Counterclockwise ``` ### 7.5 设置角度范围 单位为 `0.01 度`。 ```cpp mode.angle_start_0p01deg = -13760; mode.angle_stop_0p01deg = 13760; ``` 全视场常见写法: ```cpp mode.angle_start_0p01deg = -13760; mode.angle_stop_0p01deg = 13760; ``` 如果只想看前方较小区域,可以缩小范围,例如: ```cpp mode.angle_start_0p01deg = -3000; // -30.00° mode.angle_stop_0p01deg = 3000; // 30.00° ``` ### 7.6 设置跳点数 ```cpp mode.skip_spots = 0; ``` 说明: - `0` 表示不跳点 - 值越大,输出点越稀疏 - 适合降低带宽或减轻上层处理负担 ### 7.7 设置污染阈值 ```cpp mode.cont_warn1 = 20; mode.cont_warn2 = 40; ``` 说明: - 单位为百分比 - 通常要求 `warn2 >= warn1` ## 8. 常用接口示例 ### 8.1 连接设备 #### NAV ```cpp #include using namespace visioscan_nav; VisioscanNav nav; VisioscanNav::Config cfg; cfg.sensor_ip = "192.168.1.2"; cfg.command_port = 3050; cfg.local_mdi_port = 2368; auto r = nav.connect(cfg); if (!r) { std::cerr << r.message << "\n"; } ``` #### RD ```cpp #include using namespace visioscan_rd; VisioscanRd rd; VisioscanRd::Config cfg; cfg.sensor_ip = "192.168.1.2"; cfg.command_port = 3050; cfg.local_mdi_port = 2368; auto r = rd.connect(cfg); if (!r) { std::cerr << r.message << "\n"; } ``` ### 8.2 一次性设置模式参数 #### NAV ```cpp ModeConfig mode; mode.proto = TransportProto::Udp; mode.packet_type = PacketType::DistanceOnly; mode.resolution = AngularResolution::Deg0_2_At80Hz; mode.direction = Direction::Clockwise; mode.angle_start_0p01deg = -13760; mode.angle_stop_0p01deg = 13760; mode.skip_spots = 0; mode.cont_warn1 = 20; mode.cont_warn2 = 40; auto r = nav.setMode(mode); ``` #### RD ```cpp ModeConfig mode; mode.proto = TransportProto::Tcp; mode.packet_type = PacketType::DistanceAndIntensity; mode.resolution = AngularResolution::Deg0_1_At40Hz; mode.direction = Direction::Counterclockwise; mode.angle_start_0p01deg = -6000; mode.angle_stop_0p01deg = 6000; mode.skip_spots = 1; mode.cont_warn1 = 15; mode.cont_warn2 = 35; auto r = rd.setMode(mode); ``` ### 8.3 读取污染阈值 #### NAV ```cpp uint8_t warn1 = 0; uint8_t warn2 = 0; auto r = nav.getContaminationThreshold(warn1, warn2); ``` #### RD ```cpp uint8_t warn1 = 0; uint8_t warn2 = 0; auto r = rd.getContaminationThreshold(warn1, warn2); ``` ### 8.4 设置污染阈值 ```cpp auto r = nav.setContaminationThreshold(20, 40); auto r2 = rd.setContaminationThreshold(20, 40); ``` ### 8.5 读取窗口污染状态 #### NAV:9 分区 ```cpp std::array zones{}; auto r = nav.getWindowContamination9Zones(zones); ``` #### RD:3 分区 ```cpp std::array zones{}; auto r = rd.getWindowContamination3Zones(zones); ``` ### 8.6 读取设备状态 #### NAV ```cpp Status st; auto r = nav.getStatus(st); if (r) { std::cout << "error_code=" << st.current_error_code << "\n"; std::cout << "mdi_tx_on=" << st.mdi_tx_on << "\n"; } ``` #### RD ```cpp uint16_t ecode = 0; auto r = rd.getErrorCode(ecode); std::optional t; auto r2 = rd.getInternalTemperature(t); ``` ### 8.7 读取 / 设置以太网参数 #### 读取 ```cpp EthernetConfig eth; auto r = nav.getEthernetConfig(eth); auto r2 = rd.getEthernetConfig(eth); ``` #### 设置 ```cpp EthernetConfig eth; eth.ip = {192, 168, 1, 10}; eth.subnet_mask = {255, 255, 255, 0}; eth.gateway = {192, 168, 1, 1}; eth.port = 3050; auto r = nav.setEthernetConfig(eth); auto r2 = rd.setEthernetConfig(eth); ``` ### 8.8 RD 专有配置示例 #### 设置设备名称 ```cpp auto r = rd.setDeviceName("RD_Device_01"); ``` #### 设置滤波器 ```cpp auto r = rd.setFilterEnabled(true); ``` #### 设置 LED ```cpp auto r = rd.setLedControl(true, false); ``` #### 设置网络指示灯 ```cpp auto r = rd.setNetworkIndicator(true); ``` ## 9. MDI 接收示例 ### 9.1 注册回调 #### NAV ```cpp nav.setScanCallback([](const ScanFrame& f) { std::cout << "points=" << f.points.size() << "\n"; }); ``` #### RD ```cpp rd.setScanCallback([](const ScanFrame& f) { std::cout << "points=" << f.points.size() << "\n"; }); ``` ### 9.2 回调函数什么时候会被调用 当满足下面两个条件后,SDK 每成功解析出一帧 MDI 数据,就会调用一次回调: 1. 已经注册 `setScanCallback(...)` 2. 已经调用 `startMdi()` 和 `startScanStream()` 也就是说,业务代码通常按下面顺序写: ```cpp nav.setScanCallback(...); nav.startMdi(); nav.startScanStream(); ``` 或: ```cpp rd.setScanCallback(...); rd.startMdi(); rd.startScanStream(); ``` ### 9.3 回调函数中的数据结构说明 回调函数签名: ```cpp void callback(const ScanFrame& f) ``` 其中 `ScanFrame` 表示“一帧扫描数据”,主要字段含义如下: ```cpp struct ScanFrame { uint16_t scan_freq_hz; // 当前帧对应的扫描频率,单位 Hz uint16_t timestamp_ms; // 时间戳,单位 ms std::vector points; // 当前帧包含的所有点 }; ``` `ScanPoint` 表示“单个点”的数据: ```cpp struct ScanPoint { float angle_deg; // 点的角度,单位度 uint16_t distance_mm; // 点的距离,单位毫米 std::optional intensity; // 强度值;若当前包不带强度,则为空 }; ``` 客户最常用的就是下面 3 个字段: - `f.scan_freq_hz`:这帧数据对应的频率 - `f.timestamp_ms`:这帧时间戳 - `f.points`:这帧里所有采样点 ### 9.4 如何在回调里遍历每个点 最常见的写法是直接遍历 `f.points`: ```cpp nav.setScanCallback([](const ScanFrame& f) { std::cout << "frame points=" << f.points.size() << "\n"; for (const auto& pt : f.points) { std::cout << "angle=" << pt.angle_deg << " deg, distance=" << pt.distance_mm << " mm\n"; } }); ``` 如果是 `RD`,写法完全一样: ```cpp rd.setScanCallback([](const ScanFrame& f) { for (const auto& pt : f.points) { std::cout << "angle=" << pt.angle_deg << ", distance=" << pt.distance_mm << "\n"; } }); ``` ### 9.5 如何读取强度数据 只有当设备工作在 `DistanceAndIntensity` 模式时,`pt.intensity` 才有值。 因此推荐这样写: ```cpp rd.setScanCallback([](const ScanFrame& f) { for (const auto& pt : f.points) { std::cout << "angle=" << pt.angle_deg << ", distance=" << pt.distance_mm; if (pt.intensity.has_value()) { std::cout << ", intensity=" << *pt.intensity; } else { std::cout << ", intensity=N/A"; } std::cout << "\n"; } }); ``` ### 9.6 如何在回调里做常见业务处理 #### 示例 1:只打印前 10 个点 ```cpp nav.setScanCallback([](const ScanFrame& f) { size_t count = std::min(10, f.points.size()); for (size_t i = 0; i < count; ++i) { const auto& pt = f.points[i]; std::cout << "[" << i << "] angle=" << pt.angle_deg << ", distance=" << pt.distance_mm << "\n"; } }); ``` #### 示例 2:过滤无效或超远距离点 ```cpp rd.setScanCallback([](const ScanFrame& f) { for (const auto& pt : f.points) { if (pt.distance_mm == 0) { continue; } if (pt.distance_mm > 30000) { continue; } std::cout << "valid point: angle=" << pt.angle_deg << ", distance=" << pt.distance_mm << "\n"; } }); ``` #### 示例 3:只关心某个角度范围 ```cpp nav.setScanCallback([](const ScanFrame& f) { for (const auto& pt : f.points) { if (pt.angle_deg < -30.0f || pt.angle_deg > 30.0f) { continue; } std::cout << "front area point: angle=" << pt.angle_deg << ", distance=" << pt.distance_mm << "\n"; } }); ``` #### 示例 4:把极坐标转成平面坐标 如果客户要做二维显示或障碍物处理,通常会把角度 + 距离转换成 `x/y`: ```cpp #include rd.setScanCallback([](const ScanFrame& f) { for (const auto& pt : f.points) { float angle_rad = pt.angle_deg * 3.1415926f / 180.0f; float dist_m = static_cast(pt.distance_mm) / 1000.0f; float x = dist_m * std::cos(angle_rad); float y = dist_m * std::sin(angle_rad); std::cout << "x=" << x << ", y=" << y << "\n"; } }); ``` ### 9.7 如何把回调数据交给自己的业务线程 如果客户不希望在回调里做太重的处理,建议: 1. 回调里只做轻量事情 2. 把 `ScanFrame` 复制或移动到自己的队列 3. 在业务线程中慢慢处理 例如: ```cpp #include #include std::mutex g_mtx; std::queue g_queue; rd.setScanCallback([](const ScanFrame& f) { std::lock_guard lk(g_mtx); g_queue.push(f); }); ``` 然后在你的主线程或工作线程里再取出: ```cpp { std::lock_guard lk(g_mtx); if (!g_queue.empty()) { ScanFrame frame = g_queue.front(); g_queue.pop(); // 在这里做业务处理 } } ``` ### 9.8 回调使用注意事项 - 不建议在回调里做阻塞太久的操作 - 不建议在回调里直接做大量磁盘写入 - 如果要保存大量历史帧,建议使用队列或环形缓存 - 若需要强度值,请确认已经把 `packet_type` 设为 `DistanceAndIntensity` - 如果没有进入回调,先检查设备是否已经 `startMdi()`、当前 `proto` 是否正确、以及 MDI 接收端口是否配置正确 ### 9.9 开始发送与接收 ```cpp auto r1 = nav.startMdi(); auto r2 = nav.startScanStream(); auto r3 = rd.startMdi(); auto r4 = rd.startScanStream(); ``` 说明: - 当前实现会先读取设备当前 `proto` - 若设备当前为 `UDP`,SDK 会按 UDP 方式接收 MDI - 若设备当前为 `TCP`,SDK 会按 TCP 方式接收 MDI - 因此用户不需要自己再写分支判断,统一注册回调并调用 `startMdi()`、`startScanStream()` 即可 ### 9.10 停止发送与接收 ```cpp nav.stopScanStream(); nav.stopMdi(); nav.disconnect(); rd.stopScanStream(); rd.stopMdi(); rd.disconnect(); ``` ## 10. UDP / TCP 使用建议 ### 10.1 UDP 模式 适用于: - 局域网环境 - 追求实时性 - 已明确设备会把 UDP MDI 发往本机指定端口 配置示例: ```cpp cfg.local_mdi_port = 2368; ModeConfig mode; mode.proto = TransportProto::Udp; ``` ### 10.2 TCP 模式 适用于: - 不方便接收 UDP 的环境 - 需要通过 TCP 方式接收 MDI 配置示例: ```cpp cfg.local_mdi_port = 0; cfg.sensor_mdi_tcp_port = 3050; ModeConfig mode; mode.proto = TransportProto::Tcp; ``` ### 10.3 如何确认当前到底走的是 UDP 还是 TCP 建议直接运行示例程序。示例会打印类似下面的信息: ```text Proto=UDP MDI receive path=UDP bind local port 2368 ``` 或: ```text Proto=TCP MDI receive path=TCP connect sensor port 3050 ``` 如果接收成功,回调会继续打印: ```text MDI frame: freq=80Hz, ts=123ms, points=700 ``` ### 10.4 UDP / TCP 两种模式的验证步骤 #### 验证 UDP 回调 1. 设置设备 `proto=UDP` 2. 确认本机 `local_mdi_port` 与设备实际发送目标端口一致 3. 运行示例程序 4. 观察是否持续打印 `MDI frame: ...` #### 验证 TCP 回调 1. 设置设备 `proto=TCP` 2. 确认 `sensor_mdi_tcp_port` 正确,若为 `0` 则默认使用 `command_port` 3. 运行示例程序 4. 观察是否持续打印 `MDI frame: ...` #### 如果没有打印回调数据 请依次检查: - 设备当前 `proto` 是否与预期一致 - UDP 模式下 `local_mdi_port` 是否正确 - TCP 模式下 `sensor_mdi_tcp_port` 是否正确 - 设备端是否已经执行 `SendMDI` - 设备网络和主机网络是否互通 ## 11. 网页配置工具 网页配置工具适合不写代码、直接通过浏览器读取和修改设备参数的场景。 ### 11.1 第一步:编译网页工具 如果你还没有编译项目,请先执行: ```bash cmake -S . -B build cmake --build build -j ``` 编译完成后会生成: ```text build/visioscan_web_config ``` ### 11.2 第二步:启动网页工具 在项目根目录执行: ```bash ./build/visioscan_web_config 8080 ``` 其中: - `8080` 是网页服务端口 - 你也可以换成别的端口,例如 `9090` 例如: ```bash ./build/visioscan_web_config 9090 ``` 启动成功后,终端通常会看到类似输出: ```text Visioscan Web Config listening on http://127.0.0.1:8080/ ``` ### 11.3 第三步:在浏览器中访问网页 如果网页工具和浏览器在同一台电脑上,直接打开: ```text http://127.0.0.1:8080/ ``` 或者: ```text http://localhost:8080/ ``` 如果网页工具运行在另一台电脑或工控机上,需要把 `127.0.0.1` 替换为那台机器的实际 IP,例如: ```text http://192.168.1.100:8080/ ``` ### 11.4 第四步:在网页中填写设备参数 打开网页后,可以看到 `RD` 和 `NAV` 两个区域。 需要填写或确认的主要参数包括: - `Sensor IP`:设备 IP 地址,例如 `192.168.1.2` - `Command Port`:命令端口,通常是 `3050` 然后可按下面方式使用: - 点击 `Read RD` 或 `Read NAV`:读取当前设备参数 - 点击 `Apply RD` 或 `Apply NAV`:把页面上的配置写回设备 - 点击 `Start MDI`:通知设备开始发送 MDI - 点击 `Stop MDI`:通知设备停止发送 MDI - 点击 `Reset`:恢复默认配置 - 点击 `Reboot`:重启设备 ### 11.5 第五步:查看终端日志 网页工具在运行时会把请求过程打印到终端,便于排查问题。 例如: ```text [2026-04-15 16:48:25] HTTP GET /api/rd/get?ip=192.168.1.2&port=3050 [2026-04-15 16:48:25] RD session connecting to 192.168.1.2:3050 ``` 如果网页点按钮没有反应,建议先看终端日志,再确认: - 设备 IP 是否填写正确 - `Command Port` 是否正确 - 电脑与设备网络是否互通 - 设备是否已经上电并处于可通信状态 ### 11.6 常见访问问题 #### 浏览器打不开网页 检查: - 程序是否已经启动 - 端口是否与浏览器访问端口一致 - 是否访问了正确地址,例如 `http://127.0.0.1:8080/` #### 局域网其他电脑无法访问 检查: - 启动网页工具的那台电脑 IP 是多少 - 浏览器访问地址是否改成该电脑的真实 IP - 防火墙是否拦截了对应端口 #### 网页能打开,但读取设备失败 检查: - `Sensor IP` 是否正确 - `Command Port` 是否为设备实际命令端口 - 电脑与设备是否同网段,或路由是否已打通 当前网页工具支持: - RD / NAV 配置读取 - RD / NAV 参数写入 - 污染阈值读取与设置 - 以太网参数读取与设置 - RD 设备名、滤波器、LED、网络指示灯设置 - RD / NAV 的 `Start MDI`、`Stop MDI` - RD / NAV 的 `Reset`、`Reboot` ## 12. 客户推荐上手示例 ### 场景 1:只想先连上设备,看看当前状态 建议先调用: ```cpp connect() getProto() getPType() getResol() getStatus() / getErrorCode() getContaminationThreshold() getEthernetConfig() ``` ### 场景 2:只想先收数据,不改设备太多参数 建议: ```cpp connect() getProto() setScanCallback() startMdi() startScanStream() ``` ### 场景 3:准备做正式部署 建议明确设置: - 设备 IP - 命令端口 - MDI 传输协议 - 本地 MDI 接收端口 - 数据包类型 - 分辨率 - 扫描方向 - 扫描角度范围 - 跳点数 - 污染阈值 ## 13. 注意事项 ### 13.1 关于 UDP MDI 如果设备通过 UDP 发送 MDI,除了 SDK 侧 `bind(local_mdi_port)` 外,还必须确认: - 设备侧确实把 UDP 数据发往你的电脑 IP - 设备侧使用的目标端口与你代码中的 `local_mdi_port` 一致 ### 13.2 关于角度单位 协议中的角度配置经常使用 `0.01 度` 或 `1/1000 度`: - `ModeConfig::angle_start_0p01deg` / `angle_stop_0p01deg` 是 `0.01 度` - MDI 包中的 `First angle` / `Delta angle` 是 `1/1000 度` ### 13.3 关于重启 `reboot()` 对应的协议命令通常没有响应,因此调用成功仅表示命令已成功发送,不代表设备已经完全重启完成。 ## 14. 参考文件 - `NAV_protocol.md` - `NAV_protocol_zh.md` - `RD_protocol.md` - `RD_protocol_zh.md` - `sample/nav/nav_sample.cpp` - `sample/rd/rd_sample.cpp` - `include/visioscan_nav/visioscan_nav.hpp` - `include/visioscan_rd/visioscan_rd.hpp`