# 硬件视觉调度时序图 ## 概述 本文档描述了硬件视觉调度系统的完整时序流程,展示了从调度车托料完成到最终调度至立锯的全过程。 ### 系统架构 | 项目 | 技术栈 | 角色 | 说明 | | ---- | ------ | ---- | ---- | | `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 上位机Go participant 下位机PLC box rgb(173, 216, 230) 硬件调度系统 participant 调度车 end box rgb(255, 218, 185) 控制系统 participant 上位机Go participant 下位机PLC end box rgb(152, 251, 152) 视觉算法系统 participant 视觉算法 end Note over 调度车,视觉算法: 系统初始化阶段 调度车->>上位机Go: 托料完成(M42上升沿) 上位机Go->>上位机Go: 生成WoodID、推送LED面板 Note over 调度车,视觉算法: 视觉定位阶段(对中拍照) 上位机Go->>下位机PLC: M809触发 + D2130=2630验证 上位机Go->>上位机Go: 打开红外测距仪、等待读数稳定 上位机Go->>下位机PLC: 写入D2152/D2154调整坐标、M119调整拍照位 上位机Go->>下位机PLC: M810就位 → 关闭红外、M130触发坐标调整 上位机Go->>上位机Go: M120就位 → 监控激光传感器读数 上位机Go->>视觉算法: center_photo_taking(wood_id+左右相机物理拍照坐标) 视觉算法-->>上位机Go: ACK: center_scanning 视觉算法->>视觉算法: 异步拍照+端面中心计算 视觉算法->>上位机Go: center_photo_result(顶针目标坐标+直径) 上位机Go->>上位机Go: 设置原木直径、触发托料装置升高 上位机Go->>下位机PLC: 写入D2024/D2026/D2028/D2030顶针坐标 上位机Go->>下位机PLC: 写入D2168/D2170轨道目标坐标 上位机Go->>下位机PLC: M49启动顶料 Note over 调度车,视觉算法: 视觉扫描阶段 上位机Go->>上位机Go: M50上升沿 + D2130<1600 + D2042>=2300 上位机Go->>视觉算法: start_vision_algorithm(wood_id) 视觉算法-->>上位机Go: ACK: vision_scanning 视觉算法->>视觉算法: 异步启动相机采集 视觉算法->>上位机Go: capture_ready(status=ready) 上位机Go->>下位机PLC: 写入M103启动扫描轨道 上位机Go->>上位机Go: 启动M104监控(等待工单推送触发) 下位机PLC->>下位机PLC: 扫描轨道运行、编码器触发采集 Note over 调度车,视觉算法: 工单推送与算法处理阶段 上位机Go->>上位机Go: M104上升沿 + M42=true 上位机Go->>上位机Go: 读取D2134计算原木长度 上位机Go->>视觉算法: order_info(工单信息+规格+长度) 视觉算法-->>上位机Go: ACK: algorithm_coordinates_run(status=processing) 视觉算法->>视觉算法: 等待帧就绪→停止采集 视觉算法->>视觉算法: 配对融合→体素降采样 视觉算法->>视觉算法: Y轴归一化→顶针裁剪 视觉算法->>视觉算法: 多规格切割算法优化 视觉算法->>上位机Go: algorithm_coordinates(切割坐标+旋转角度+材积) 视觉算法->>上位机Go: board_split_yield_result(板材拆分出材率) Note over 调度车,视觉算法: 旋转与调度执行阶段 上位机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) 调度车->>调度车: 取料并调度至立锯 ``` --- ## 流程说明 ### 1. 初始阶段 - **调度车托料完成**: M42上升沿检测,生成WoodID,推送当天原木数量至LED面板 ### 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. 视觉扫描阶段 - **M50上升沿检测**: 同时验证D2130<1600(升降车安全位)且D2042>=2300(调度车就位) - **D2138方向判断**: D2138>-2250为左扫描(left),<=-2250为右扫描(right) - **推送扫描标识**: Go发送`start_vision_algorithm`,C#异步启动4台相机持续采集 - **capture_ready信号**: C#相机就绪后推送就绪信号,Go收到后写入M103启动扫描轨道 ### 4. 工单推送与算法处理阶段 - **M104上升沿**: 扫描结束触发,同时验证M42=true - **读取原木长度**: 从D2134寄存器读取长度值,减去315mm得到实际原木长度 - **推送工单信息**: Go发送`order_info`,包含工单规格、副规格、锯路厚度等参数 - **异步算法处理**: C#端(`ProcessScanAndOptimizeAsync`)依次执行:等待所有相机帧就绪(最多10秒)→停止采集→时间窗口配对转同步帧→多相机融合建模(含体素降采样+Y轴归一化)→顶针裁剪→材积报告→多规格切割优化(含板材拆分择优) - **推送算法结果**: C#推送`algorithm_coordinates`(切割坐标+旋转角度+材积+出材率) - **推送板材拆分**: C#异步推送`board_split_yield_result`(板材拆分出材率) ### 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顶针坐标)写入后立即回读验证 - **体素降采样**: 融合后点云超过100万点时自动触发体素网格降采样,加速后续建模与优化计算 - **材积基准统一**: 出材率由`BoardSplitYieldService.ComputeEffectiveBoardYield`全局统一计算(旋转点云→构建板区间→Z方向木方规划→有效板材木方材积/原木总材积πR²L),结果覆盖`algorithm_coordinates`的AvailableVolume/TotalVolume/YieldRate ## 注意事项 1. 每个阶段的执行都依赖于前一阶段的成功完成 2. C#端TCP服务监听 `0.0.0.0:9000`,支持多客户端连接 3. 视觉算法的准确性直接影响整个调度流程的效果 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视觉算法数据接口 ### 接口概述 上位机(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`) ### 通用消息格式 #### 请求格式(Go → C#) ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "action": "order_info", "data": { ... }, "timestamp": 1762158534297 } ``` | 参数 | 类型 | 必填 | 说明 | | ---- | ---- | ---- | ---- | | id | string | 是 | 请求唯一标识符(UUID) | | action | string | 是 | 动作类型 | | data | object | 否 | 请求数据,根据不同action有不同结构 | | timestamp | number | 是 | 请求时间戳(毫秒) | #### 响应格式(C# → Go) ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "action": "algorithm_coordinates", "data": { ... }, "timestamp": 1762308230194, "error_id": "" } ``` | 参数 | 类型 | 必填 | 说明 | | ---- | ---- | ---- | ---- | | id | string | 是 | 对应请求的ID | | action | string | 是 | 动作类型 | | data | object | 否 | 响应数据 | | timestamp | number | 是 | 时间戳(毫秒) | | error_id | string | 否 | 错误码:空字符串=成功,非空=错误标识 | --- ### 支持的动作类型 #### 1. center_photo_taking - 视觉取中拍照命令(Go → C#) **功能说明:** 上位机下发视觉取中拍照命令,触发C#端图漾深度相机拍照并计算原木端面中心坐标。 **触发条件:** M120上升沿 && 激光传感器读数左/右均在(850,1000]mm **调用方法:** `visionClient.SendRequestWithRetry(request)` → C# `HandleCenterPhotoTaking()` **请求示例:** ```json { "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, "d": 910.5 }, "right": { "x": 55050, "y": 32849, "z": 0, "d": 912.3 } }, "timestamp": 1762158362326 } ``` **data参数说明:** | 参数 | 类型 | 必填 | 说明 | | ---- | ---- | ---- | ---- | | 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": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "action": "center_scanning", "data": {}, "timestamp": 1762158362400, "error_id": "" } ``` --- #### 2. center_photo_result - 视觉对中结果推送(C# → Go) **功能说明:** C#端异步完成拍照和端面中心计算后,主动推送视觉顶针机构目标坐标给上位机。 **调用方法:** C# `SendCenteringResult()` → Go `handleCenterPhotoResult()` **请求示例:** ```json { "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": { "wood_id": "WOOD_20251103_162602_004_2ace914e", "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" }, "timestamp": 1762158534297 } ``` **data参数说明:** | 参数 | 类型 | 必填 | 说明 | | ---- | ---- | ---- | ---- | | 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": "c3d4e5f6-a7b8-9012-cdef-123456789012", "action": "algorithm_coordinates_run", "data": { "status": "processing" }, "timestamp": 1762158534400, "error_id": "" } ``` > **说明:** `order_info`代表扫描结束,C#收到后立即响应ACK,然后异步执行(`ProcessScanAndOptimizeAsync`):等待所有相机帧就绪→停止采集→时间窗口配对转同步帧→多相机融合建模(体素降采样+Y轴归一化)→顶针裁剪→材积报告→多规格切割优化(含板材拆分择优评估)→推送algorithm_coordinates→异步推送board_split_yield_result。 --- #### 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", "action": "algorithm_coordinates", "data": {}, "timestamp": 1705123456789, "error_id": "err_algorithm" } ``` #### 常见错误码 | 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行协议,每条消息以`\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"