From 877b95bb341b637862ad680740d9610f3b9060e9 Mon Sep 17 00:00:00 2001 From: ykxiao Date: Sun, 28 Jun 2026 03:26:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E8=A7=89=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 18 + zh-cn/hardware_vision_scheduling.md | 891 ++++++++++++++++------------ 2 files changed, 516 insertions(+), 393 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fbf0b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +### macOS +# Finder metadata +.DS_Store + +# Thumbnails +._* + +# Custom folder icons +Icon + +# Volume root files +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent \ No newline at end of file diff --git a/zh-cn/hardware_vision_scheduling.md b/zh-cn/hardware_vision_scheduling.md index c73d99e..18aff23 100644 --- a/zh-cn/hardware_vision_scheduling.md +++ b/zh-cn/hardware_vision_scheduling.md @@ -4,94 +4,89 @@ 本文档描述了硬件视觉调度系统的完整时序流程,展示了从调度车托料完成到最终调度至立锯的全过程。 +### 系统架构 + +| 项目 | 技术栈 | 角色 | 说明 | +| ---- | ------ | ---- | ---- | +| `go-auto` | Go | TCP客户端(上位机) | 硬件控制中枢,通过PLC控制下位机,通过TCP与视觉算法通信 | +| `LogMax3D` | C# | TCP服务端(视觉算法) | 视觉算法服务,监听9000端口,处理3D扫描、建模、切割优化 | + +### 通信协议 + +- **协议**: TCP长连接,JSON行协议(`\n`分隔) +- **连接方向**: Go客户端 → C#服务端(上位机主动连接视觉算法) +- **消息格式**: 每行一个完整JSON对象,支持单行和多行格式化JSON自动重组 +- **连接确认**: 连接后C#立即推送 `connection_ack` 确认 +- **错误标识**: 使用 `error_id` 字段标识错误,空字符串表示成功 + ## 上位机控制时序图-视觉部分 ```mermaid sequenceDiagram autonumber participant 调度车 - participant 上位机 - participant 下位机 - participant 侧面相机 - participant 视觉相机 - participant 视觉算法 + participant 上位机Go + participant 下位机PLC + participant 视觉算法C# - %% 为每个参与者设置不同颜色 box rgb(173, 216, 230) 硬件调度系统 participant 调度车 end box rgb(255, 218, 185) 控制系统 - participant 上位机 - participant 下位机 - end - box rgb(221, 160, 221) 视觉系统 - participant 侧面相机 - participant 视觉相机 + participant 上位机Go + participant 下位机PLC end box rgb(152, 251, 152) 算法系统 - participant 视觉算法 + participant 视觉算法C# end - Note over 调度车,视觉算法: 系统初始化阶段 - 调度车->>上位机: 托料完成 + Note over 调度车,视觉算法C#: 系统初始化阶段 + 调度车->>上位机Go: 托料完成(M42上升沿) + 上位机Go->>上位机Go: 生成WoodID、推送LED面板 - Note over 调度车,视觉算法: 视觉定位阶段 - 上位机->>侧面相机: 发送取中拍照指令 - 侧面相机->>侧面相机: 计算对中坐标 - 侧面相机->>上位机: 返回中心坐标结果 - alt 超时重试 - 上位机-->>侧面相机: 超时重试请求 - 侧面相机-->>上位机: 返回坐标 - end - 上位机->>下位机: 写入顶中坐标 - 下位机->>下位机: 顶针对中顶料 + Note over 调度车,视觉算法C#: 视觉定位阶段(对中拍照) + 上位机Go->>下位机PLC: M809触发 + D2130=2630验证 + 上位机Go->>上位机Go: 打开红外测距仪、等待读数稳定 + 上位机Go->>下位机PLC: 写入D2152/D2154调整坐标、M119调整拍照位 + 上位机Go->>下位机PLC: M810就位 → 关闭红外、M130触发坐标调整 + 上位机Go->>上位机Go: M120就位 → 监控激光传感器读数 + 上位机Go->>视觉算法C#: center_photo_taking(wood_id+左右相机坐标) + 视觉算法C#-->>上位机Go: ACK: center_scanning + 视觉算法C#->>视觉算法C#: 异步拍照+端面中心计算 + 视觉算法C#->>上位机Go: center_photo_result(顶针目标坐标+直径) + 上位机Go->>上位机Go: 设置原木直径、触发托料装置升高 + 上位机Go->>下位机PLC: 写入D2024/D2026/D2028/D2030顶针坐标 + 上位机Go->>下位机PLC: 写入D2168/D2170轨道目标坐标 + 上位机Go->>下位机PLC: M49启动顶料 - Note over 调度车,视觉算法: 物理定位与扫描准备阶段 - 下位机->>下位机: 顶料至预定位置 - 上位机->>下位机: 发送扫描指令 - alt 超时重试 - 上位机-->>下位机: 超时重试扫描指令 - end + Note over 调度车,视觉算法C#: 视觉扫描阶段 + 上位机Go->>上位机Go: M50上升沿 + D2130<1600 + D2042>=2300 + 上位机Go->>视觉算法C#: start_vision_algorithm(wood_id) + 视觉算法C#-->>上位机Go: ACK: vision_scanning + 视觉算法C#->>视觉算法C#: 异步启动相机采集 + 视觉算法C#->>上位机Go: capture_ready(status=ready) + 上位机Go->>下位机PLC: 写入M103启动扫描轨道 + 上位机Go->>上位机Go: 启动M104监控(等待工单推送触发) + 下位机PLC->>下位机PLC: 扫描轨道运行、编码器触发采集 - Note over 调度车,视觉算法: 并行扫描处理阶段(三方并行) - par 上位机等待扫描完成 - 上位机->>下位机: 等待扫描完成指令 - 下位机->>上位机: 扫描完成通知 - alt 超时重试 - 上位机-->>下位机: 超时重试等待 - end - and 下位机硬触发相机 - 下位机->>视觉相机: 硬触发扫描动作 - and 发送算法参数 - 上位机->>视觉算法: 发送原木长度与顶针预留长度 - 上位机->>视觉算法: 推送工单信息(规格/等级等) - end + Note over 调度车,视觉算法C#: 工单推送与算法处理阶段 + 上位机Go->>上位机Go: M104上升沿 + M42=true + 上位机Go->>上位机Go: 读取D2134计算原木长度 + 上位机Go->>视觉算法C#: order_info(工单信息+规格+长度) + 视觉算法C#-->>上位机Go: ACK: algorithm_coordinates_run(status=processing) + 视觉算法C#->>视觉算法C#: 停止采集→建模→顶针裁剪→多规格切割优化 + 视觉算法C#->>上位机Go: algorithm_coordinates(切割坐标+旋转角度+材积) + 视觉算法C#->>上位机Go: board_split_yield_result(板材拆分出材率) - Note over 调度车,视觉算法: 视觉算法处理阶段 - 上位机->>视觉算法: 触发视觉算法处理 - 视觉相机->>视觉算法: 提供扫描数据 - 视觉算法->>视觉算法: 算法处理中 - 视觉算法->>上位机: 等待算法结果 - alt 超时重试 - 上位机-->>视觉算法: 超时重试请求 - end - - par 算法结果输出 - 视觉算法->>上位机: 建模完成 - and 算法完成 - 视觉算法->>上位机: 算法完成 - end - - Note over 调度车,视觉算法: 数据汇聚与处理阶段 - 上位机->>上位机: 返回视觉算法数据 - 上位机->>下位机: 写入算法旋转角度 - - Note over 调度车,视觉算法: 旋转与调度执行阶段 - 下位机->>下位机: 执行旋转完成切割角度旋转 - 调度车->>调度车: 取料 - 上位机->>调度车: 写入算法规划图 - 上位机->>调度车: 下发调度指令 - 调度车->>调度车: 调度至立锯 + Note over 调度车,视觉算法C#: 旋转与调度执行阶段 + 上位机Go->>上位机Go: 保存视觉数据到数据库+推送LED面板 + 上位机Go->>下位机PLC: 写入D2038旋转角度 + 上位机Go->>下位机PLC: M45执行旋转 + 上位机Go->>下位机PLC: 写入D2136原木直径 + 上位机Go->>上位机Go: M46上升沿检测(旋转完成) + 上位机Go->>上位机Go: 异步计算A/B组夹臂坐标(D2106-D2110/D2162-D2166) + 上位机Go->>上位机Go: 触发立锯调度(M807=true) + 调度车->>调度车: 取料并调度至立锯 ``` --- @@ -99,322 +94,141 @@ sequenceDiagram ## 流程说明 ### 1. 初始阶段 -- **调度车托料完成**: 系统开始进入视觉调度流程 +- **调度车托料完成**: M42上升沿检测,生成WoodID,推送当天原木数量至LED面板 -### 2. 视觉定位阶段 -- **上位机发送取中拍照指令**: 启动视觉系统进行中心定位 -- **等待中心坐标结果**: 等待视觉算法处理结果,支持超时重试机制 +### 2. 视觉定位阶段(对中拍照) +- **M809触发**: 升降车D2130在2630±15mm拍照位时,启动红外测距仪 +- **红外测距调整**: 等待红外读数稳定,计算调整坐标写入D2152/D2154,M119调整拍照位 +- **M810就位信号**: 关闭红外测距仪,写入M130触发坐标调整 +- **M120就位信号**: 监控激光传感器读数,左右均在(850,1000]mm范围内时,推送`center_photo_taking` +- **异步对中计算**: C#端异步执行图漾深度相机拍照和端面中心计算 +- **推送对中结果**: C#推送`center_photo_result`,包含视觉顶针机构目标坐标(mm)和直径(mm) +- **写入顶针坐标**: Go端将坐标写入D2024/D2026/D2028/D2030,轨道坐标写入D2168/D2170,M49启动顶料 -### 3. 物理定位阶段 -- **写入下位机顶中坐标并顶料至线扫位置**: 将获取的中心坐标传递给下位机,同时执行物理定位 +### 3. 视觉扫描阶段 +- **M50上升沿检测**: 同时验证D2130<1600(升降车安全位)且D2042>=2300(调度车就位) +- **D2138方向判断**: D2138>-2250为左扫描(left),<=-2250为右扫描(right) +- **推送扫描标识**: Go发送`start_vision_algorithm`,C#异步启动4台相机持续采集 +- **capture_ready信号**: C#相机就绪后推送就绪信号,Go收到后写入M103启动扫描轨道 -### 4. 视觉扫描阶段 -- **视觉扫描到达线扫速度并发送扫描指令**: 系统准备进行线扫描并启动扫描流程,支持超时重试 -- **上位机等待扫描完成指令并写入下位机**: 等待扫描完成信号并将完成指令传递给下位机,支持超时重试(三方并行执行) -- **视觉相机硬触发扫描动作**: 视觉相机执行硬件触发扫描操作,采集视觉数据(与上位机等待和算法处理并行执行) -- **等待视觉算法处理**: 视觉算法处理扫描数据,支持超时重试(与上位机等待和相机触发并行执行) +### 4. 工单推送与算法处理阶段 +- **M104上升沿**: 扫描结束触发,同时验证M42=true +- **读取原木长度**: 从D2134寄存器读取长度值,减去315mm得到实际原木长度 +- **推送工单信息**: Go发送`order_info`,包含工单规格、副规格、锯路厚度等参数 +- **异步算法处理**: C#端依次执行:停止采集→等待帧就绪→点云融合建模→Y轴归一化→顶针裁剪→多规格切割优化 +- **推送算法结果**: C#推送`algorithm_coordinates`(切割坐标+旋转角度+材积+出材率) +- **推送板材拆分**: C#异步推送`board_split_yield_result`(板材拆分出材率) -### 5. 数据处理与旋转阶段 -- **返回视觉数据并写入算法旋转角度**: 算法处理完成,返回结果并将旋转角度传递给下位机 -- **下位机执行旋转完成旋转**: 执行物理旋转操作直至完成 - -### 6. 调度执行阶段 -- **调度车取料并写入算法规划图**: 调度车取走已处理的物料,同时将算法规划结果写入系统 -- **下发调度指令并调度至立锭**: 发送调度执行指令并完成最终的调度至立锯操作 +### 5. 旋转与调度执行阶段 +- **数据持久化**: Go保存视觉算法数据到数据库(坐标JSON、旋转角度、材积、径级),推送出材率到LED面板 +- **写入旋转角度**: D2038写入算法角度值(回读验证,最多重试5次) +- **执行旋转**: M45写入true执行物理旋转 +- **写入原木直径**: D2136写入原木直径值 +- **旋转完成检测**: M46上升沿检测(旋转完成),并行计算A/B组夹臂伺服坐标 +- **触发立锯调度**: M807=true时触发立锯调度逻辑 ## 关键特性 -- **超时重试机制**: 在关键步骤(中心坐标获取、视觉扫描、扫描完成、视觉算法)设置了超时重试机制,确保系统稳定性 -- **三方并行执行**: “上位机等待扫描完成指令”、“视觉相机硬触发扫描动作”和“等待视觉算法”为三方并行执行,提高执行效率 -- **分阶段执行**: 整个流程分为视觉定位、物理定位、视觉扫描、旋转调整和调度执行五个主要阶段 -- **算法集成**: 深度集成视觉算法,实现自动化的坐标定位和旋转角度计算 +- **异步推送机制**: C#端收到请求后立即返回ACK,算法计算完成后异步推送结果(`center_photo_result`/`algorithm_coordinates`) +- **capture_ready握手**: C#端相机就绪后推送就绪信号,Go端收到后才写入M103启动扫描轨道,避免时序错误 +- **多规格对比优化**: 算法对比主规格与副规格,选出材率最高的切割方案 +- **顶针裁剪**: 根据扫描方向(left/right)从点云两端裁剪固定长度,移除机械顶针和过渡区点云 +- **Y轴归一化**: scan_start="right"时翻转Y轴,确保Y=0始终对应同一物理端 +- **寄存器回读验证**: 关键寄存器(D2038角度值、D2024-D2030顶针坐标)写入后立即回读验证 +- **材积基准统一**: TotalVolume使用middle Y截面R计算,与AvailableVolume同源,消除出材率偏高 ## 注意事项 1. 每个阶段的执行都依赖于前一阶段的成功完成 -2. 超时重试机制需要合理设置重试次数和超时时间 +2. C#端TCP服务监听 `0.0.0.0:9000`,支持多客户端连接 3. 视觉算法的准确性直接影响整个调度流程的效果 -4. 上位机与下位机之间的通信稳定性至关重要 -5. "上位机等待扫描完成指令"、"视觉相机硬触发扫描动作"和"等待视觉算法"是三方并行执行的,需要做好同步协调 +4. Go与C#之间的TCP长连接支持自动重连(默认10秒间隔) +5. 红外测距仪读数稳定判断:连续10次相邻读数差值<0.5mm +6. 激光传感器有效范围:左/右均在(850, 1000]mm +7. 原木长度 = D2134寄存器值 - 315mm(固定偏移) +8. 顶针裁剪量根据扫描方向不同:left扫描(低Y端215mm+高Y端172mm),right扫描(低Y端170mm+高Y端209mm) -## TCP请求上位机数据接口 +## TCP视觉算法数据接口 ### 接口概述 -硬件视觉调度系统通过TCP协议与上位机进行数据交互,支持多种动作类型的请求处理。 +上位机(go-auto)作为TCP客户端,连接视觉算法服务端(LogMax3D, 监听9000端口),通过JSON行协议(`\n`分隔)进行双向通信。 -### 通用请求格式 +**通信模型**: +- **Go → C#**: 上位机发送请求(`center_photo_taking`、`start_vision_algorithm`、`order_info`) +- **C# → Go**: 算法推送异步结果(`center_photo_result`、`capture_ready`、`algorithm_coordinates`、`board_split_yield_result`) -```json -{ - "id": "req001", - "action": "test", - "data": { - "message": "hello server", - "param1": "value1" - }, - "timestamp": 1705123456789 -} -``` +### 通用消息格式 -#### 请求参数说明 +#### 请求格式(Go → C#) -| 参数 | 类型 | 必填 | 说明 | -| ---- | ---- | ---- | ---- | -| id | string | 是 | 请求唯一标识符 | -| action | string | 是 | 动作类型,决定具体的处理逻辑 | -| data | object | 否 | 请求数据,根据不同action有不同结构 | -| timestamp | number | 否 | 请求时间戳(毫秒) | - -### 支持的动作类型 - -#### 1. center_photo_result - 视觉写入侧面对中坐标 - -**功能说明:** 用于将视觉系统获取的原木端面对中坐标写入下位机,为后续的顶针定位操作提供准确的坐标参考。 - -**调用方法:** `s.hardwareController.WriteVisionPinCoordinate()` - -**请求示例:** -```json -{ - "id": "req001", - "action": "center_photo_result", - "data": { - "left": { - "x": 100.5, - "y": 200.3, - "z": 300.7, - "diameter": 34.5 - }, - "right": { - "x": 100.5, - "y": 200.3, - "z": 300.7, - "diameter": 34.5 - }, - "x_diameter": 34.5, - "y_diameter": 34.5, - "error_id": "err001", - "wood id": "WooD _20251103 162602 004 2ace914e", - "reg_timestamp": 1762158362326, - "recv_timestamp": 1762308226907, - "send _timestamp": 1762308230194 - }, - "timestamp": 1705123456789 -} -``` - -**data参数说明:** - -| 参数 | 类型 | 必填 | 说明 | -| ---- | ---- | ---- | ---- | -| left | object | 是 | 左侧坐标 | -| left.x | number | 是 | X坐标 | -| left.y | number | 是 | Y坐标 | -| left.z | number | 是 | Z坐标 | -| left.diameter | number | 否 | 原木直径 | -| right | object | 是 | 右侧坐标 | -| right.x | number | 是 | X坐标 | -| right.y | number | 是 | Y坐标 | -| right.z | number | 是 | Z坐标 | -| right.diameter | number | 否 | 原木直径 | -| x_diameter | number | 否 | X方向原木直径 | -| y_diameter | number | 否 | Y方向原木直径 | -| error_id | string | 否 | 错误标识 | -| wood_id | string | 否 | 原木标识 | -| reg_timestamp | number | 否 | 注册时间戳 | -| recv_timestamp | number | 否 | 接收时间戳 | -| send_timestamp | number | 否 | 发送时间戳 | - -**响应示例:** -```json -{ - "id": "req001", - "success": true, - "data": { - "coordinates": { - "left": { - "x": 100.5, - "y": 200.3, - "z": 300.7 - }, - "right": { - "x": 100.5, - "y": 200.3, - "z": 300.7 - } - }, - "error_id": "err001", - "message": "视觉顶针坐标写入成功", - "wood_id": "" - }, - "error": "", - "timestamp": 1762561787764 -} -``` - -#### 2. scan_completed - 视觉扫描完成推送图像流 - -**功能说明:** 用于通知系统视觉扫描已完成并推送建模图像,触发硬件控制器将M106寄存器设置为true,标记扫描流程的完成。 - -**调用方法:** `s.hardwareController.ProcessVisionScanComplete()` - -**寄存器操作:** 将M106寄存器设置为true - -**请求示例:** ```json { "id": "550e8400-e29b-41d4-a716-446655440000", - "action": "scan_completed", - "data": { - "wood_id": "WD0001", - "image_size": 314572800, - "image_format": "jpg" - }, - "timestamp": 1705300000000 + "action": "order_info", + "data": { ... }, + "timestamp": 1762158534297 } ``` -**传输格式** -协议设计(三段式) +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| id | string | 是 | 请求唯一标识符(UUID) | +| action | string | 是 | 动作类型 | +| data | object | 否 | 请求数据,根据不同action有不同结构 | +| timestamp | number | 是 | 请求时间戳(毫秒) | -1. JSON元数据:{"id": "...", "action": "scan_completed", "data": {"wood_id": "WD0001", "image_size": 314572800, "image_format": "jpg"}} -2. 8字节小端序整数:图像大小 -3. 原始二进制图像数据流 +#### 响应格式(C# → Go) -**支持的图像格式:** -- 普通图像:jpg, jpeg, png, bmp, tiff -- 点云数据:pcd, ply, las, xyz -- 3D模型:obj - -| 阶段 | 描述 | 示例 | -| ---- | ---- | ---- | -| 1 | JSON元数据(以'}'结束,触发JSON解析) | {"id":"xxx","action":"scan_completed","data":{"wood_id":"WD0001","image_size":314572800,"image_format":"jpg"},"timestamp":1705300000000} | -| 2 | 8字节小端序整数(图像大小) | [0x00][0x1C][0xC1][0x12][0x00][0x00][0x00][0x00] = 314572800 bytes (约300MB) | -| 3 | 原始二进制图像数据流 | [图像二进制数据... 共314572800字节] | - -**说明:** 用于通知系统视觉扫描已完成并推送建模图像。系统接收到此请求后会自动将M106寄存器设置为true。 - -**参数说明:** - -| 参数 | 类型 | 说明 | -| ---- | ---- | ---- | -| wood_id | string | 原木唯一标识ID | -| image_size | number | 图像大小 | -| image_format | string | 图像格式 | -| timestamp | number | 请求时间戳(毫秒) | - -```c -// 第一阶段:发送JSON元数据 -json request = { - {"id", "req_123"}, - {"action", "scan_completed"}, - {"data", { - {"wood_id", "WD0001"}, - {"image_size", 314572800}, // 300MB - {"image_format", "jpg"} - }} -}; -send(sock, request.dump().c_str(), ...); - -// 第二阶段:发送8字节小端序图像大小 -int64_t imageSize = 314572800; -int64_t networkSize = htole64(imageSize); // 转换为小端序 -send(sock, &networkSize, 8, 0); - -// 第三阶段:发送原始二进制图像流 -FILE* fp = fopen("image.jpg", "rb"); -char buffer[32768]; -while ((n = fread(buffer, 1, sizeof(buffer), fp)) > 0) { - send(sock, buffer, n, 0); -} -fclose(fp); -``` - -**响应示例:** ```json { - "id": "req002", - "success": true, - "data": { - "message": "视觉扫描完成处理成功", - "action": "scan_completed", - "register": "M106=true" - }, - "timestamp": 1705123456789 -} -``` - -#### 3. algorithm_coordinates - 算法坐标和旋转角度 - -**功能说明:** 用于处理视觉算法计算得出的最终坐标和旋转角度,将这些数据传递给下位机进行物理旋转操作。 - -**调用方法:** `s.hardwareController.ProcessVisionScanCoordinate()` - -**参数处理:** 将坐标数据构建为[4]float32数组,旋转角度转换为float32 - -**请求示例:** -```json -{ - "id": "req003", + "id": "550e8400-e29b-41d4-a716-446655440000", "action": "algorithm_coordinates", - "data": { - "left": [120.5, 120.5, 86.12], - "right": [160.8, 124.5, 17.2], - "rotation_angle": 15.5, - "available_volume": 0.85 - }, - "timestamp": 1705123456789 + "data": { ... }, + "timestamp": 1762308230194, + "error_id": "" } ``` -**data参数说明:** +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| id | string | 是 | 对应请求的ID | +| action | string | 是 | 动作类型 | +| data | object | 否 | 响应数据 | +| timestamp | number | 是 | 时间戳(毫秒) | +| error_id | string | 否 | 错误码:空字符串=成功,非空=错误标识 | -| 参数 | 类型 | 必填 | 说明 | -| ---- | ---- | ---- |---------| -| left | float64 | 是 | 左侧锯路坐标 | -| right | float64 | 是 | 右侧锯路坐标 | -| rotation_angle | float64 | 是 | 旋转角度(度) | +--- -**响应示例:** -```json -{ - "id": "req003", - "success": true, - "data": { - "message": "视觉算法坐标处理成功", - "coordinates": { - "left": [120.5,32.4], - "right": [160.8,12] - }, - "rotation_angle": 15.5, - "available_volume": 0.85 - }, - "timestamp": 1705123456789 -} -``` +### 支持的动作类型 -#### 4. center_photo_taking - 视觉取中拍照命令下发 +#### 1. center_photo_taking - 视觉取中拍照命令(Go → C#) -**功能说明:** 用于下发视觉取中拍照命令,指定需要拍照的原木ID,触发视觉系统进行中心定位拍照操作。 +**功能说明:** 上位机下发视觉取中拍照命令,触发C#端图漾深度相机拍照并计算原木端面中心坐标。 + +**触发条件:** M120上升沿 && 激光传感器读数左/右均在(850,1000]mm + +**调用方法:** `visionClient.SendRequestWithRetry(request)` → C# `HandleCenterPhotoTaking()` **请求示例:** ```json { - "id": "", + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "action": "center_photo_taking", "data": { + "wood_id": "WOOD_20251103_162602_004_2ace914e", "left": { "x": 55050, "y": 32849, - "z": 0 + "z": 0, + "d": 910.5 }, "right": { "x": 55050, "y": 32849, - "z": 0 - }, - "wood_id": "WOOD_20251103_162602_004_2ace914e" + "z": 0, + "d": 912.3 + } }, "timestamp": 1762158362326 } @@ -425,39 +239,182 @@ fclose(fp); | 参数 | 类型 | 必填 | 说明 | | ---- | ---- | ---- | ---- | | wood_id | string | 是 | 原木唯一标识ID | +| left | object | 是 | 左相机在机械坐标系下的绝对位置 | +| left.x | number | 是 | 左相机X坐标(PLC D2016) | +| left.y | number | 是 | 左相机Y坐标(PLC D2018) | +| left.z | number | 是 | 左相机Z坐标(PLC D2126) | +| left.d | number | 否 | 左侧激光传感器读数(mm) | +| right | object | 是 | 右相机在机械坐标系下的绝对位置 | +| right.x | number | 是 | 右相机X坐标(PLC D2020) | +| right.y | number | 是 | 右相机Y坐标(PLC D2022) | +| right.z | number | 是 | 右相机Z坐标(PLC D2128) | +| right.d | number | 否 | 右侧激光传感器读数(mm) | -**响应示例:** +**即时响应(ACK):** ```json { - "id": "", - "success": true, - "data": { - "message": "视觉取中拍照命令下发成功", - "wood_id": "WOOD_20251102_094718_004_8b369a99" - }, - "timestamp": 1762048038500 + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "action": "center_scanning", + "data": {}, + "timestamp": 1762158362400, + "error_id": "" } ``` -#### 5. order_info - 工单信息下发 +--- -**功能说明:** 用于下发工单信息至视觉系统,包含工单编号、规格和原木ID等关键信息,为后续的加工流程提供数据支持。 +#### 2. center_photo_result - 视觉对中结果推送(C# → Go) + +**功能说明:** C#端异步完成拍照和端面中心计算后,主动推送视觉顶针机构目标坐标给上位机。 + +**调用方法:** C# `SendCenteringResult()` → Go `handleCenterPhotoResult()` **请求示例:** ```json { - "id": "", + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "action": "center_photo_result", + "data": { + "left": { + "x": 509.34, + "y": 1330.28, + "z": 450.0, + "diameter": 345.6 + }, + "right": { + "x": 512.15, + "y": 1325.40, + "z": 455.0, + "diameter": 340.2 + }, + "x_diameter": 348.5, + "y_diameter": 342.1, + "wood_id": "WOOD_20251103_162602_004_2ace914e", + "reg_timestamp": 1762158362326, + "recv_timestamp": 1762308226907, + "send_timestamp": 1762308230194 + }, + "timestamp": 1762308230194, + "error_id": "" +} +``` + +**data参数说明:** + +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| left | object | 是 | 左端面中心点结果 | +| left.x | number | 是 | 左视觉顶针机构目标X坐标(mm) = 相机位置X + RT变换偏移X | +| left.y | number | 是 | 左视觉顶针机构目标Y坐标(mm) = 相机位置Y + RT变换偏移Y | +| left.z | number | 是 | 相机到原木端面距离(mm) | +| left.diameter | number | 否 | 左端面直径(mm) = (X径+Y径)/2 | +| right | object | 是 | 右端面中心点结果 | +| right.x | number | 是 | 右视觉顶针机构目标X坐标(mm) | +| right.y | number | 是 | 右视觉顶针机构目标Y坐标(mm) | +| right.z | number | 是 | 相机到原木端面距离(mm) | +| right.diameter | number | 否 | 右端面直径(mm) | +| x_diameter | number | 否 | X方向直径(mm) | +| y_diameter | number | 否 | Y方向直径(mm) | +| wood_id | string | 是 | 原木标识 | +| reg_timestamp | number | 否 | 请求时间戳(来自上位机原始请求) | +| recv_timestamp | number | 否 | 视觉系统接收时间戳 | +| send_timestamp | number | 否 | 视觉系统发送结果时间戳 | + +**Go端处理逻辑:** +1. 直径单位转换:`diameter / 10`(mm → cm) +2. 设置全局原木直径、X轴直径、Y轴直径 +3. 触发托料装置升高 +4. 调用`WriteVisionPinCoordinate()`写入顶针坐标至D2024/D2026/D2028/D2030 +5. 计算轨道目标坐标写入D2168/D2170 +6. 写入M49启动顶料 + +--- + +#### 3. start_vision_algorithm - 开始视觉扫描(Go → C#) + +**功能说明:** 上位机通知视觉算法开始3D扫描,C#端异步启动4台相机进入持续采集模式。 + +**触发条件:** M50上升沿 && D2130<1600 && D2042>=2300 + +**调用方法:** `visionClient.SendRequestWithRetry(request)` → C# `HandleStartVision()` + +**请求示例:** +```json +{ + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "action": "start_vision_algorithm", + "data": { + "wood_id": "WOOD_20251103_162602_004_2ace914e" + }, + "timestamp": 1762158400000 +} +``` + +**data参数说明:** + +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| wood_id | string | 是 | 原木唯一标识ID | + +**即时响应(ACK):** +```json +{ + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "action": "vision_scanning", + "data": { + "status": "scanning" + }, + "timestamp": 1762158400100, + "error_id": "" +} +``` + +**异步响应(相机就绪后推送):** +```json +{ + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "action": "capture_ready", + "data": { + "status": "ready" + }, + "timestamp": 1762158401500, + "error_id": "" +} +``` + +> **说明:** `capture_ready`的`status`字段可能为`ready`(成功)或`error`(失败)。Go端收到`ready`后写入M103启动扫描轨道,收到`error`则不写入M103。 + +**Go端capture_ready处理逻辑:** +1. 检查`error_id`和`status`字段 +2. 读取`pendingScanStart`(D2138方向判断结果) +3. 写入M103=true启动扫描轨道 +4. 启动M104监控goroutine,等待工单推送触发 + +--- + +#### 4. order_info - 工单信息下发(Go → C#) + +**功能说明:** 上位机下发工单信息,代表扫描结束。C#端停止采集并执行建模+切割优化算法。 + +**触发条件:** M104上升沿 && M42=true + +**调用方法:** `visionClient.SendRequestWithRetry(request)` → C# `HandleOrderInfo()` + +**请求示例:** +```json +{ + "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "action": "order_info", "data": { - "grade": "等级A", - "has_bark": true, - "order_sn": "SO20251101000002", - "order_spec": "11*12*12", - "sub_spec": ["38*89*3.98", "37*66*4.1"], "wood_id": "WOOD_20251103_162602_004_2ace914e", - "start_distance": 10.5, - "end_distance": 10.5, - "length": 4100.0, + "order_sn": "SO20251101000002", + "order_spec": "38*89*3.98", + "sub_spec": ["38*89*3.98", "37*66*4.1"], + "has_bark": true, + "grade": "等级A", + "start_distance": 115, + "end_distance": 115, + "length": 3785.0, "saw_thickness": 1.8, "scan_start": "left" }, @@ -467,63 +424,211 @@ fclose(fp); **data参数说明:** -| 参数 | 类型 | 必填 | 说明 | -| ---- | ---- | ---- |----------------| -| order_sn | string | 是 | 工单编号 | -| order_spec | string | 是 | 工单主规格(格式:厚*宽*长) | -| wood_id | string | 是 | 原木唯一标识ID | -| grade | string | 否 | 原木等级 | -| has_bark | bool | 否 | 是否有 bark (带树皮) | -| sub_spec | []string | 否 | 原木副规格(格式:厚*宽*长) | -| left_distance | number | 否 | 原木左端距离 | -| right_distance | number | 否 | 原木右端距离 | -| length | number | 否 | 原木长度 | -| saw_thickness | number | 否 | 锯片厚度 (锯路) | -| scan_start | string | 否 | 初始扫描方向(left或right) | +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| wood_id | string | 是 | 原木唯一标识ID | +| order_sn | string | 是 | 工单编号 | +| order_spec | string | 是 | 工单主规格(格式:厚*宽*长),如"38*89*3.98" | +| sub_spec | []string | 否 | 副规格列表,算法对比主规格与各副规格选出材率最高方案 | +| has_bark | bool | 否 | 是否带树皮(默认true) | +| grade | string | 否 | 原木等级(如"等级A") | +| start_distance | number | 否 | 起始距离(mm):侧面相机至原木端面距离(常量115mm) | +| end_distance | number | 否 | 结束距离(mm):侧面相机至原木端面距离(常量115mm) | +| length | number | 否 | 原木长度(mm):D2134寄存器值 - 315 | +| saw_thickness | number | 否 | 锯片厚度(mm),默认1.8 | +| scan_start | string | 否 | 初始扫描方向:"left"或"right"(基于D2138判断) | -**响应示例:** +**即时响应(ACK):** ```json { - "id": "", - "success": true, + "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", + "action": "algorithm_coordinates_run", "data": { - "message": "工单信息下发成功", - "order_sn": "SO20251101000002", - "order_spec": "11*12*12", - "wood_id": "WOOD_20251102_094814_007_e5337aa2" + "status": "processing" }, - "timestamp": 1762048153709 + "timestamp": 1762158534400, + "error_id": "" } ``` -### 错误响应格式 +> **说明:** `order_info`代表扫描结束,C#收到后立即响应ACK,然后异步执行:停止采集→等待帧就绪→点云融合→建模→顶针裁剪→多规格切割优化→推送结果。 -当请求处理失败时,系统会返回错误响应: +--- + +#### 5. algorithm_coordinates - 算法切割坐标结果(C# → Go) + +**功能说明:** C#端切割优化算法计算完成后,主动推送最优切割方案给上位机。 + +**调用方法:** C# `TcpServer.SendToClientAsync()` → Go `handleAlgorithmCoordinates()` + +**请求示例:** +```json +{ + "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", + "action": "algorithm_coordinates", + "data": { + "left": [120.5, 80.3, 40.1], + "right": [15.2, 55.8, 96.4, 137.0], + "rotation_angle": 15.5, + "available_volume": 0.85, + "total_volume": 1.205, + "yield_rate": 70.5, + "wood_id": "WOOD_20251103_162602_004_2ace914e" + }, + "timestamp": 1762158540000, + "error_id": "" +} +``` + +**data参数说明:** + +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| left | []number | 是 | 左侧锯片坐标数组(mm),距中心距离,1-15个元素 | +| right | []number | 是 | 右侧锯片坐标数组(mm),距中心距离,1-15个元素 | +| rotation_angle | number | 是 | 旋转角度(度) | +| available_volume | number | 是 | 可用材积(m³) | +| total_volume | number | 否 | 整根原木材积(m³),基于middle Y截面R计算 | +| yield_rate | number | 否 | 出材率(%),如70.5表示70.5% | +| wood_id | string | 否 | 原木ID | + +**Go端处理逻辑:** +1. 验证`error_id`为空 +2. 解析`left`/`right`数组(长度1-15)并转换为float32 +3. 保存视觉数据到数据库(坐标JSON、旋转角度、材积、径级) +4. 推送出材率到LED面板 +5. 写入D2038旋转角度(回读验证,最多重试5次,容差0.01度) +6. 写入M45=true执行旋转 +7. 写入D2136原木直径 +8. 启动M46上升沿监控(旋转完成后触发立锯调度) +9. 异步计算A/B组夹臂伺服坐标 + +--- + +#### 6. board_split_yield_result - 板材拆分出材率(C# → Go) + +**功能说明:** C#端异步完成板材拆分计算后,推送出材率结果给上位机。此消息在`algorithm_coordinates`之后异步推送,不阻塞主流程。 + +**调用方法:** C# `BoardSplitYieldService.RunAsync()` → Go `handleBoardSplitYieldResult()` + +**请求示例:** +```json +{ + "id": "", + "action": "board_split_yield_result", + "data": { + "status": "completed", + "wood_id": "WOOD_20251103_162602_004_2ace914e", + "yield_rate": 68.5, + "n_boards": 6, + "n_blocks": 42, + "block_volume": 0.583, + "available_volume": 0.85, + "ply_file": "data/modeling/WOOD_20251103_162602_004_2ace914e/board_split.ply", + "png_file": "data/modeling/WOOD_20251103_162602_004_2ace914e/board_split.png" + }, + "timestamp": 1762158545000, + "error_id": "" +} +``` + +**data参数说明:** + +| 参数 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| status | string | 是 | 计算状态:"completed"=完成,"error"=错误 | +| wood_id | string | 是 | 原木唯一标识ID | +| yield_rate | number | 是 | 板材拆分出材率(%) | +| n_boards | number | 是 | 板材数量 | +| n_blocks | number | 是 | 木方数量 | +| block_volume | number | 是 | 木方总体积(m³) | +| available_volume | number | 是 | 可用材积(m³) | +| ply_file | string | 否 | PLY点云可视化文件路径 | +| png_file | string | 否 | PNG截面图文件路径 | + +--- + +#### 7. connection_ack - 连接确认(C# → Go) + +**功能说明:** TCP连接建立后,C#端立即推送连接确认消息。 + +**请求示例:** +```json +{ + "id": "", + "action": "connection_ack", + "data": null, + "timestamp": 1762158000000, + "error_id": "" +} +``` + +--- + +### 错误处理 + +#### 错误响应格式 + +当请求处理失败时,C#端通过`error_id`字段标识错误: ```json { "id": "req001", - "success": false, - "error": "错误描述信息", - "timestamp": 1705123456789 + "action": "algorithm_coordinates", + "data": {}, + "timestamp": 1705123456789, + "error_id": "err_algorithm" } ``` -#### 常见错误类型 +#### 常见错误码 -1. **请求解析失败**: `"请求解析失败: invalid JSON format"` -2. **硬件控制器未初始化**: `"硬件控制器未初始化"` -3. **缺少必要数据**: `"缺少坐标数据"` 或 `"缺少坐标和角度数据"` -4. **数据格式错误**: `"坐标数据格式错误,需要left_x, left_y, right_x, right_y字段"` -5. **硬件操作失败**: `"写入视觉顶针坐标失败: [specific error]"` +| error_id | 说明 | 触发场景 | +| -------- | ---- | -------- | +| `""` (空字符串) | 成功 | 正常处理 | +| `err_unknown_action` | 未知动作类型 | action不在支持列表中 | +| `err_empty_data` | 请求data为空 | order_info缺少data | +| `err_parse_order` | 解析OrderData失败 | JSON格式错误 | +| `err_algorithm` | 算法计算异常 | 切割优化过程异常 | +| `err_no_data` | 未采集到有效数据 | 相机未采集到帧数据 | +| `err_no_model` | 无原木3D模型 | 未收到start_vision_algorithm | +| `err_start_capture` | 启动采集失败 | capture_ready推送error状态 | +| `err003` | 对中计算异常 | CenteringService计算过程异常 | +| `err004` | 对中服务未初始化 | 图漾相机未连接或未启用 | + +### 完整调用链路时序 + +``` +Go(上位机) C#(视觉算法) + | | + | ── center_photo_taking ──────────────────► | 对中拍照请求 + | ◄── center_scanning (ACK) ─────────────── | 立即响应 + | | 异步: 拍照+计算 + | ◄── center_photo_result ────────────────── | 推送对中坐标 + | | + | ── start_vision_algorithm ───────────────► | 开始扫描请求 + | ◄── vision_scanning (ACK) ─────────────── | 立即响应 + | | 异步: 启动相机采集 + | ◄── capture_ready ──────────────────────── | 相机就绪信号 + | (写入M103启动扫描轨道) | + | | + | ── order_info ───────────────────────────► | 工单信息(扫描结束) + | ◄── algorithm_coordinates_run (ACK) ────── | 立即响应 + | | 异步: 停止采集→建模→切割优化 + | ◄── algorithm_coordinates ──────────────── | 推送切割坐标+材积 + | ◄── board_split_yield_result ───────────── | 推送板材拆分出材率 + | | +``` ### 接口使用注意事项 -1. **数据格式**:所有请求必须是有效的JSON格式,系统会自动清理前后空白字符和换行符 -2. **坐标精度**:坐标值支持float64精度,系统内部会转换为float32处理 -3. **硬件控制器**:所有涉及硬件操作的接口都需要硬件控制器已正确初始化 -4. **请求ID**:建议使用唯一的请求ID,便于追踪和调试,系统会在响应中返回相同ID -5. **超时处理**:建议客户端设置合理的超时时间,特别是涉及硬件操作的请求 -6. **错误处理**:客户端应当处理所有可能的错误响应,包括JSON解析错误、硬件操作失败等 -7. **日志记录**:系统会记录所有请求的处理过程,包括成功和失败情况,方便问题排查 -8. **并发处理**:系统支持并发处理多个请求,但建议按照时序发送相关请求 \ No newline at end of file +1. **数据格式**:所有请求为JSON行协议,每条消息以`\n`结尾 +2. **坐标精度**:坐标值支持float64精度,Go端内部转换为float32写入PLC +3. **TCP长连接**:Go端使用带自动重连的TCP客户端,断线自动重连(默认10秒间隔) +4. **异步推送**:C#端收到请求后立即返回ACK,计算完成后异步推送结果,Go端通过注册的`asyncHandler`处理 +5. **请求ID**:使用UUID生成唯一请求ID,便于追踪和调试 +6. **超时处理**:Go端`SendRequestWithRetry`支持重试机制(可配置重试次数和间隔) +7. **线程安全**:C#端使用SemaphoreSlim保证StreamWriter写入线程安全 +8. **连接清理**:C#端每60秒自动清理已断开的客户端连接,防止僵尸连接积累 +9. **直径单位**:C#推送的直径单位为mm,Go端接收后除以10转换为cm存储 +10. **扫描方向**:scan_start由D2138寄存器判断,>-2250为"left",<=-2250为"right"