Files
bj_power/bj_power_mes/系统设计方案.md
T

536 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工业自动化生产线管控系统 详细设计方案
## 文档信息
- **版本**: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. [附录APLC信号与事件映射表](#附录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` String8位 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。
- 模块接口解耦,未来可拆分微服务。
- 新增工件/设备仅需数据库配置。
---
## 附录APLC信号与事件映射表
### 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信号的全部细节,可直接指导开发团队进行实现。*