# 工业自动化生产线管控系统 详细设计方案 ## 文档信息 - **版本**:V2.4(最终生产版·固定槽位与批量补料) - **日期**:2026-05-01 - **状态**:可交付开发 --- ## 目录 1. [项目背景与目标](#1-项目背景与目标) 2. [核心设计理念](#2-核心设计理念) 3. [系统总体架构](#3-系统总体架构) 4. [事件总线与一致性模型](#4-事件总线与一致性模型) 5. [工艺路线定义](#5-工艺路线定义) 6. [数据库设计](#6-数据库设计) 7. [工件生命周期与状态管理](#7-工件生命周期与状态管理) 8. [调度器三层架构设计](#8-调度器三层架构设计) 9. [任务(Task)状态机与生命周期](#9-任务task状态机与生命周期) 10. [工单管理详细设计](#10-工单管理详细设计) 11. [工站接口设计](#11-工站接口设计) 12. [机器人子调度器与手持工具](#12-机器人子调度器与手持工具) 13. [超时与心跳机制](#13-超时与心跳机制) 14. [运行期状态校验](#14-运行期状态校验) 15. [断电恢复与事件重放](#15-断电恢复与事件重放) 16. [Redis数据结构设计](#16-redis数据结构设计) 17. [暂存台槽位管理与批量补料](#17-暂存台槽位管理与批量补料) 18. [与外部系统交互](#18-与外部系统交互) 19. [关键技术选型](#19-关键技术选型) 20. [部署与扩展性](#20-部署与扩展性) 21. [附录A:PLC信号与事件映射表](#附录a-plc信号与事件映射表) --- ## 1. 项目背景与目标 ### 1.1 项目背景 本系统面向一条多品种小批量柔性生产线,核心设备包括: - 4台CNC加工中心(CNC_1~4) - 高压清洗机(双槽位,关门批处理,清洗时间2~3分钟/件) - 内窥镜检测滚筒线(自动检测分流) - 热能去毛刺机 - 除锈清洗线(5位FIFO流水线) - 激光打标机(无上下料位,手持作业) - 地轨机器人(唯一搬运设备) - 10个接驳台(各9槽位) - 随行暂存台(8个固定槽位) - 抽检台(1号槽位用于异常工件暂存) 可加工的工件类型及机床绑定关系: | 基本类型 | 子型号 | 绑定机床 | |---------|--------|---------| | A6VM107 | B99414, B99487 | CNC_3, CNC_4 | | A6VM160 | B99464, B99488 | CNC_1, CNC_2 | | A6VM200 | B99259 | CNC_1 | ### 1.2 建设目标 - 工件全生命周期实时追踪与可视化 - 事件驱动 + 状态机自动调度,无需预编排任务图 - 多品种混流生产,灵活应对异常(插单、故障、返工) - 新增工件类型或设备仅需配置,不改核心代码 - 工业级健壮性:防重复派单、防状态回退、超时心跳、分级断电恢复 --- ## 2. 核心设计理念 ### 2.1 工艺状态机驱动 每种工件定义静态工艺路线(状态机),工件实例仅保留当前工序指针、状态、上下文及物理位置。**绝不预生成任务实体**,所有前进步骤均由事件触发实时决策。 ### 2.2 事件驱动 + Redis 单一真相源 - **Redis 是运行时唯一状态源**,所有调度决策仅查询 Redis。 - PostgreSQL 仅作持久化日志和审计存储,不参与在线决策。 - PLC 信号是输入,不是状态源。 - 每个状态变更均产生不可变事件,全局唯一 ID,实体版本号防乱序,所有处理器幂等。 ### 2.3 调度三层解耦 调度引擎拆分为 **任务生成 → 约束过滤 → 策略选择**,各层独立可测,易于扩展。 ### 2.4 机器人子调度器 调度引擎只下发抽象运输任务(取、放工件),机器人控制器自行完成路径规划、手持工具调用、重试。 ### 2.5 工单与调度过滤 工单暂停仅修改工单自身状态,调度时通过约束过滤层排除,不污染工件状态。 ### 2.6 暂存台固定槽位策略 暂存台提供 8 个固定槽位,工件在生产周期内**独占一个槽位直到下料**。补料策略:首次尽量装满(8件),后续当空位数超过 4 个时,一次性批量补满。 ### 2.7 工业级健壮性 - 实体版本号防并发和状态回退 - 原子抢占防重复派单 - Task 状态机跟踪生命周期,支持超时与重试 - 设备心跳监控在线状态 - 分级断电恢复策略 --- ## 3. 系统总体架构 本系统为 **单体应用**,采用分层模块化设计,打包为单个可执行文件,内部按职责划分包(package),通过接口通信。 ``` ┌──────────────────────────────────────────────────────┐ │ 人工终端 (React) │ └──────────────────────┬───────────────────────────────┘ │ WebSocket / HTTP ┌──────────────────────▼───────────────────────────────┐ │ go-zero HTTP Server (API层) │ │ ┌─────────┐ ┌──────────┐ ┌────────────────────┐ │ │ │工单管理 │ │看板/监控 │ │人工交互接口 │ │ │ └────┬────┘ └────┬─────┘ └────────┬───────────┘ │ │ │ │ │ │ │ ┌────▼────────────▼────────────────▼─────────────┐ │ │ │ 业务逻辑层 (Service) │ │ │ │ ┌────────────┐ ┌───────────┐ ┌──────────┐ │ │ │ │ │工单服务 │ │调度引擎 │ │状态管理 │ │ │ │ │ └────────────┘ └───────────┘ └──────────┘ │ │ │ └───────────────────────┬───────────────────────┘ │ │ │ │ │ ┌───────────────────────▼───────────────────────┐ │ │ │ 基础设施层 (Infra) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ │ │ │事件总线 │ │PLC适配器 │ │机器人通信 │ │ │ │ │ │(Redis │ │(OPC-UA/ │ │(TCP/寄存器)│ │ │ │ │ │ Streams) │ │ Modbus) │ │ │ │ │ │ │ └──────────┘ └──────────┘ └────────────┘ │ │ │ └───────────────────────┬───────────────────────┘ │ │ │ │ │ Redis (SSOT) PostgreSQL (持久化) │ └──────────────────────────────────────────────────────┘ ``` --- ## 4. 事件总线与一致性模型 ### 4.1 事件结构 ```go type Event struct { ID string // UUIDv7 EntityID string // 如 "job:J123" EntityVersion int // 事件基于的实体版本 Type string // 事件类型 Source string // 来源模块/设备 Timestamp time.Time Payload map[string]interface{} } ``` ### 4.2 原子更新与版本控制 - 每个实体在 Redis Hash 中存储 `version`(整数)。 - 应用事件时,使用 Lua 脚本原子校验:当前版本必须等于事件携带版本,否则拒绝(过期事件)。 - 校验通过后更新状态并递增版本。 ### 4.3 写入路径 1. 生成事件对象(携带当前版本)。 2. 发布到 Redis Stream (XADD)。 3. 消费协程调用 `StateManager.ApplyEvent` 执行 Lua 脚本更新 Redis。 4. 异步写入 PostgreSQL。 ### 4.4 幂等与重放 - 实体记录 `last_event_id`,重复事件直接跳过。 - 断电恢复时可从 PG `event_log` 按顺序重放事件,安全重建 Redis 状态。 --- ## 5. 工艺路线定义 ### 5.1 工序类型 | 类型 | 说明 | 资源需求 | |------|------|---------| | LOAD | 上料并首次扫码 | 机器人+接驳台 | | BUFFER_STAGE | 暂存台积聚等待 | **暂存台特定槽位**(分配后固定) | | MACHINING | CNC加工 | CNC_1~4(可限定) | | WASH | 高压清洗(成对关门) | 高压清洗机 | | INSPECTION | 内窥镜检测 | 检测滚筒线 | | JUDGE | 检测结果判定 | 无 | | RUST_WASH | 除锈清洗 | 除锈清洗线(FIFO) | | FINAL_SCAN | 最终扫码(手持) | 扫码器 | | LASER_MARK | 激光打标(手持) | 激光打标机 | | DECISION | 抽检/下料分支 | 机器人 | ### 5.2 首次扫码特殊规则 机器人从接驳台抓取工件后直接手持扫码: - **成功** → 分配暂存台空闲槽位,放入,工件进入 `BUFFER_STAGE`(状态 `ON_BUFFER`)。 - **失败** → 工件放回原接驳台槽位,状态 `SUSPENDED`,原因 `SCAN_FAILED`,人工介入。 ### 5.3 示例工艺路线(A6VM107) ```json { "recipe_id": "R-A6VM107", "steps": [ {"step_id":"10","type":"LOAD","tool":"SCANNER","next":"20","branches":{"FAIL":"_RETURN_BAY"}}, {"step_id":"20","type":"BUFFER_STAGE","next":"30"}, {"step_id":"30","type":"MACHINING","allowed":["CNC_3","CNC_4"],"next":"40","step_timeout":600}, {"step_id":"40","type":"UNLOAD","next":"50"}, {"step_id":"50","type":"WASH","next":"60","step_timeout":300}, {"step_id":"60","type":"UNLOAD","next":"70"}, {"step_id":"70","type":"INSPECTION","next":"80"}, {"step_id":"80","type":"JUDGE","branches":{"PASS":"90","FAIL":"900"}}, {"step_id":"90","type":"UNLOAD","next":"100"}, {"step_id":"100","type":"DEBURR","next":"110","step_timeout":300}, {"step_id":"110","type":"UNLOAD","next":"120"}, {"step_id":"120","type":"RUST_WASH","next":"130"}, {"step_id":"130","type":"UNLOAD","next":"140"}, {"step_id":"140","type":"FINAL_SCAN","tool":"SCANNER","next":"150","branches":{"FAIL":"145"}}, {"step_id":"145","type":"LASER_MARK","tool":"LASER","next":"150"}, {"step_id":"150","type":"DECISION","branches":{"SAMPLE":"200","NORMAL":"210"}}, {"step_id":"200","type":"PLACE_SAMPLING"}, {"step_id":"210","type":"PLACE_AGV"} ] } ``` > `_RETURN_BAY` 表示放回原接驳台槽位并挂起。 --- ## 6. 数据库设计 ### 6.1 基础主数据表 **product_type** | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(20) PK | A6VM107, A6VM160, A6VM200 | | name | varchar(50) | | | recipe_id | varchar(20) FK | | | is_active | boolean | | **recipe** | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(20) PK | | | name | varchar(50) | | | version | int | | | create_time | timestamptz | | **recipe_step** | 字段 | 类型 | 说明 | |------|------|------| | step_id | varchar(10) PK | | | recipe_id | varchar(20) FK | | | step_name | varchar(50) | | | step_type | varchar(20) | 工序类型 | | resource_type | varchar(50) | 所需工站类型 | | tool_type | varchar(20) | 手持工具类型 | | allowed_resources | jsonb | 允许的设备列表 | | processing_params | jsonb | 工时等 | | next_step_default | varchar(10) | | | next_step_branches | jsonb | 分支映射 | | step_timeout | int | 超时秒数 | | description | text | | **equipment** | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(20) PK | | | type_code | varchar(20) | | | name | varchar(50) | | | status | varchar(20) | 从Redis推导,此字段仅作离线参考 | | slot_count | int | | | ip_address | varchar(45) | | | location | varchar(50) | | ### 6.2 业务数据表 **work_order** | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(30) PK | | | product_type_id | varchar(20) FK | | | quantity | int | | | source_bay_slot_id | varchar(30) FK | | | status | varchar(20) | CREATED/IN_PROGRESS/PAUSED/COMPLETED/CANCELLED | | context | jsonb | | | created_time | timestamptz | | | updated_time | timestamptz | | | completed_time | timestamptz | | **job** | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(30) PK | | | work_order_id | varchar(30) FK | | | product_type_id | varchar(20) FK | | | recipe_id | varchar(20) FK | | | current_step_id | varchar(10) FK | | | status | varchar(20) | 见7.1 | | position_type | varchar(20) | ON_EQUIPMENT/ON_BUFFER/IN_HAND | | position_ref_id | varchar(50) | 位置引用(含暂存台槽位ID) | | context | jsonb | 子型号、扫码数据等 | | priority | int | | | suspended_reason | text | | | version | int | 事件版本控制 | | last_event_id | varchar(36) | | | created_time | timestamptz | | | last_updated | timestamptz | | **job_step_instance** | 字段 | 类型 | 说明 | |------|------|------| | id | bigserial PK | | | job_id | varchar(30) FK | | | step_id | varchar(10) FK | | | attempt_no | int | | | status | varchar(20) | IN_PROGRESS/COMPLETED/FAILED | | equipment_id | varchar(20) | | | start_time | timestamptz | | | end_time | timestamptz | | | result | jsonb | | **task** | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(36) PK | UUID | | job_id | varchar(30) FK | | | type | varchar(30) | TRANSPORT/HANDHELD_SCAN… | | status | varchar(20) | CREATED/DISPATCHED/RUNNING/SUCCESS/FAILED/TIMEOUT | | from_pos | jsonb | | | to_pos | jsonb | | | assigned_robot | varchar(20) | | | started_time | timestamptz | | | completed_time | timestamptz | | | result | jsonb | | **task_log / event_log** 略。 --- ## 7. 工件生命周期与状态管理 ### 7.1 Job 状态 | 状态 | 含义 | 转换示例 | |------|------|---------| | CREATED | 已创建,在接驳台 | → IN_HANDLING | | IN_HANDLING | 机器人操作中 | → ON_BUFFER(扫码成功) / SUSPENDED(扫码失败) | | PROCESSING | 设备加工中 | → WAITING_UNLOAD | | WAITING_UNLOAD | 加工完成待取走 | → IN_HANDLING | | ON_BUFFER | 固定在暂存台槽位上 | → IN_HANDLING | | WAITING_DECISION | 等待判定(检测后) | → COMPLETED / SCRAPPED / ON_BUFFER | | COMPLETED | 正常下线,**释放暂存台槽位** | 终态 | | SCRAPPED | 报废,**释放暂存台槽位** | 终态 | | SUSPENDED | 异常暂停(扫码失败等) | → 恢复 | ### 7.2 暂存台槽位生命周期 - 扫码成功后分配一个空闲槽位(编号1~8),写入 `position_ref_id`。 - 工件全程独占该槽位,直到进入 COMPLETED 或 SCRAPPED 状态后释放。 --- ## 8. 调度器三层架构设计 ### 8.0 调度触发机制(事件驱动) 1. 状态管理器在原子更新 Redis 后,将新变为 `ON_BUFFER` 或 `WAITING_UNLOAD` 的工件 ID 推入 Redis Sorted Set `sched:ready_jobs`。 2. 调度引擎阻塞在 `ZPOPMAX` 上,仅处理变动工件,无全局扫描。 3. 处理完若无可行任务,工件放回 `sched:ready_jobs` 等待下一次事件。 ### 8.1 任务生成层 根据 `job.current_step` 和 `recipe_step` 生成候选动作列表。 ### 8.2 约束过滤层 过滤设备不可用、工单暂停、工件暂停、工具锁冲突、批处理未满、超时挂起等。 ### 8.3 策略选择层 优先级排序: 1. 紧急转移(WAITING_UNLOAD) 2. 成组装卸(高压清洗机) 3. 设备上料 4. **暂存台批量补料**(空位 > 4 触发) 5. 手持作业 6. 普通搬运 ### 8.4 原子抢占 Lua 脚本原子地将工件状态从 `ON_BUFFER` 改为 `IN_HANDLING`,成功则创建 Task。 --- ## 9. 任务(Task)状态机与生命周期 ``` CREATED → DISPATCHED → RUNNING → SUCCESS ↓ ↓ FAILED TIMEOUT ``` - CREATED:调度生成 - DISPATCHED:已下发机器人 - RUNNING:机器人开始执行 - SUCCESS/FAILED/TIMEOUT:终态 - FAILED 可重试,TIMEOUT 则工件回退。 --- ## 10. 工单管理详细设计 - **创建**:选择接驳台槽位、工件类型(可指定子型号),生成工单和 N 个 Job。 - **暂停/恢复**:工单状态改为 PAUSED,过滤层排除;恢复改回 IN_PROGRESS。 - **单工件暂存**:直接设置 Job 状态为 SUSPENDED。 - **返工**:修改 `current_step`,新增 `job_step_instance`。 --- ## 11. 工站接口设计 ```go type Station interface { ID() string Type() string GetStatus() StationStatus // IDLE, BUSY, WAITING, FAULT CanAccept(jobType string) bool Execute(cmd StationCommand) (AssignedSlot string, error) OnEvent(event StationEvent) } ``` 设备占用通过 Redis 推导,不冗余存储。 --- ## 12. 机器人子调度器与手持工具 调度引擎下发抽象任务,机器人控制器自行完成运动控制、工具调用(通过 `HandheldTool` 接口),工具用 Redis 锁互斥。 --- ## 13. 超时与心跳机制 - **工序超时**:`step_timeout` 超时自动挂起。 - **任务超时**:Task 创建后超时回退。 - **设备心跳**:`equipment:{id}:heartbeat` TTL 5s,丢失则过滤。 --- ## 14. 运行期状态校验 周期性抽查 PLC 实际工件 vs Redis 逻辑,不一致则挂起报警。 --- ## 15. 断电恢复与事件重放 ### 15.1 恢复分级 | 级别 | 条件 | 措施 | |------|------|------| | L1 自动 | CREATED/ON_BUFFER/WAITING_RESOURCE | 直接恢复 | | L2 半自动 | PROCESSING 且 PLC 确认完成 | 转 WAITING_UNLOAD 自动恢复 | | L3 人工 | IN_HANDLING/关门中/位置不明 | 人工确认处置 | ### 15.2 重放 从 PG 事件流按顺序重放,版本号与幂等保证正确性。 --- ## 16. Redis 数据结构设计 - `job:{id}` Hash:状态、位置、版本、last_event_id - `equipment:{id}` Hash:设备状态 - `equipment:{id}:heartbeat` String TTL - `task:{id}` Hash:任务状态 - `sched:ready_jobs` Sorted Set:按优先级排队的就绪工件 - `buffer:slots:bitmap` String:8位 Bitmap 表示暂存台槽位占用 - `tool:lock:{type}` String TTL:手持工具锁 --- ## 17. 暂存台槽位管理与批量补料 ### 17.1 槽位分配与释放 - 暂存台共 8 个槽位,编号 1~8,通过 Redis Bitmap 管理。 - **分配**:首次扫码成功后,寻找第一个空闲槽位(位值为0),置1,记录槽位号到 job 的 `position_ref_id`。 - **占用期**:工件在生产全程独占该槽位,即使被取走去加工,槽位依然保持占用。 - **释放**:工件状态变为 COMPLETED 或 SCRAPPED 时,对应 Bitmap 位置 0。 ### 17.2 批量补料规则 - **冷启动**:系统启动时若暂存台空位 = 8,触发连续上料,尽可能装满 8 件。 - **运行中触发**:每次槽位释放(工件下线)后,检查 Bitmap 中 0 的个数。若 **空位数 > 4**,立即触发批量补料。 - **补料数量**:N = min(空位数, 接驳台上待上料工件数),目标是将暂存台补满至 8。 - **实现**:状态管理器在处理工件终态事件后调用 `BufferManager.CheckAndRefill()`,若满足条件则向调度引擎提交高优先级批量上料命令(优先级 4),生成一串“抓取→扫码→入暂存台”任务,连续执行直至补满或无料可上。 --- ## 18. 与外部系统交互 - **AGV**:托盘到位/成品送出通过事件通知。 - **MES**(可选):接收工单,上报履历。 - **人工终端**:工单、抽检、暂停/恢复、断电恢复看板。 --- ## 19. 关键技术选型 | 组件 | 技术 | |------|------| | 后端语言 | Go 1.23.4 | | 框架 | go-zero v1.3.8(单体) | | ORM | ent v0.14.5 | | 数据库 | PostgreSQL | | 缓存/状态/事件 | Redis (SSOT, Streams, Lua) | | PLC 通信 | OPC-UA / Modbus TCP | | 前端 | React 19 + Semi Design + Zustand + Vite 7 | --- ## 20. 部署与扩展性 - 单体二进制部署,依赖 Redis + PostgreSQL。 - 模块接口解耦,未来可拆分微服务。 - 新增工件/设备仅需数据库配置。 --- ## 附录A:PLC信号与事件映射表 ### A.1 状态信号(PLC → 系统) | PLC信号 | 地址 | 类型 | 系统事件 | |---------|------|------|---------| | CNCDone1~4 | M1140.0~3 | bool | MACHINE_TASK_COMPLETE | | WasherDone | M1144.0 | bool | MACHINE_TASK_COMPLETE | | DeburrDone | M1145.0 | bool | MACHINE_TASK_COMPLETE | | CleaningDone | M1146.0 | bool | MACHINE_TASK_COMPLETE | | MeasureOK/NG | M1148.0/1 | bool | INSPECTION_PASS/FAIL | | SamplingOK/NG | M1151.0/1 | bool | SAMPLING_PASS/FAIL | ### A.2 指令信号(系统 → PLC) | 指令 | 地址 | 参数 | 任务类型 | |------|------|------|---------| | FetchWorkpieceFromDock | M1000 | 类型,接驳台号,槽号 | PICK | | PlaceWorkpieceToDock | M1010 | 类型,接驳台号,槽号 | PLACE | | FetchWorkpieceFromTempStation | M1020 | 类型,暂存位 | PICK | | PlaceWorkpieceToTempStation | M1030 | 类型,暂存位 | PLACE | | PlaceWorkpieceToMachine | M1040 | 类型,设备ID,槽位 | LOAD | | FetchWorkpieceFromMachine | M1050 | 类型,设备ID,槽位 | UNLOAD | | ExchangeWorkpieceToMachine | M1060 | 类型,设备ID,槽位 | EXCHANGE | | PlaceWorkpieceToRollingLine | M1070 | 类型 | LOAD | | FetchWorkpieceFromRollingLine | M1080 | 类型 | UNLOAD | | PlaceWorkpieceToSamplingStation | M1090 | 类型 | PLACE | | FetchWorkpieceFromSamplingStation | M1100 | 类型 | PICK | | MoveWorkpieceToMarkingStation | M1110 | 类型 | HANDHELD_MARK | | StartScanCode | M1130 | 类型,轮次 | HANDHELD_SCAN | --- *本方案覆盖了从事件驱动、状态机、调度引擎到现场PLC信号的全部细节,可直接指导开发团队进行实现。*