MES PLC Send 是一个基于 Spring Boot 的多设备联合测试系统,支持 PLC 设备管理、设备组配置、多设备任务编排和执行。系统实现了"模板 + 实例"的设计模式,支持一个设备类型模板对应多个设备实例,实现了设备间的协调和数据传递。
技术栈:
- Spring Boot 2.x
- MyBatis-Plus
- S7NetPlus(PLC通信)
- MySQL
- Server-Sent Events (SSE) 实时推送
服务端口:10018
LOAD_VEHICLE):支持多实例协调,自动状态管理,MES任务处理LARGE_GLASS):格子范围配置,逻辑判断WORKSTATION_SCANNER):定时扫描,MES数据读取WORKSTATION_TRANSFER):30s缓冲判定,批量处理GLASS_STORAGE):玻璃存储管理(已实现,但当前不使用)max_concurrent_devices 控制并发数mes-plcSend/
├── device/ # 设备管理层
│ ├── entity/ # 实体类
│ │ ├── DeviceConfig.java # 设备配置
│ │ ├── DeviceGroupConfig.java # 设备组配置
│ │ ├── DeviceGroupRelation.java # 设备组关系
│ │ ├── DeviceStatus.java # 设备状态
│ │ └── GlassInfo.java # 玻璃信息
│ ├── service/ # 服务层
│ │ ├── DeviceConfigService.java
│ │ ├── DeviceGroupConfigService.java
│ │ ├── DeviceCoordinationService.java # 设备协调服务
│ │ └── GlassInfoService.java
│ └── controller/ # 控制器
│ ├── DeviceConfigController.java
│ ├── DeviceGroupController.java
│ └── DevicePlcController.java
│
├── interaction/ # 交互逻辑层
│ ├── base/ # 基础接口
│ │ ├── BaseDeviceLogicHandler.java
│ │ ├── DeviceInteraction.java
│ │ └── InteractionContext.java
│ │
│ ├── vehicle/ # 大车设备专用包
│ │ ├── handler/
│ │ │ └── LoadVehicleLogicHandler.java # 共享逻辑处理器
│ │ ├── flow/
│ │ │ └── LoadVehicleInteraction.java
│ │ ├── coordination/
│ │ │ ├── VehicleStatusManager.java # 状态管理器
│ │ │ └── VehicleCoordinationService.java # 协调服务
│ │ └── model/
│ │ ├── VehicleStatus.java
│ │ ├── VehiclePosition.java
│ │ ├── VehicleState.java
│ │ ├── VehiclePath.java
│ │ └── VehicleTask.java
│ │
│ ├── workstation/ # 卧转立设备包
│ │ ├── base/
│ │ │ └── WorkstationBaseHandler.java
│ │ ├── config/
│ │ │ └── WorkstationLogicConfig.java
│ │ ├── scanner/
│ │ │ └── handler/
│ │ │ └── HorizontalScannerLogicHandler.java
│ │ └── transfer/
│ │ └── handler/
│ │ └── HorizontalTransferLogicHandler.java
│ │
│ ├── largeglass/ # 大理片笼设备包
│ │ ├── handler/
│ │ │ └── LargeGlassLogicHandler.java
│ │ ├── config/
│ │ │ └── LargeGlassConfig.java
│ │ └── model/
│ │ └── GridRange.java
│ │
│ ├── flow/ # 交互流程(通用)
│ │ ├── GlassStorageInteraction.java
│ │ └── LargeGlassInteraction.java
│ │
│ └── impl/ # 简单设备实现
│ └── GlassStorageLogicHandler.java
│
├── task/ # 任务管理层
│ ├── entity/
│ │ ├── MultiDeviceTask.java # 多设备任务
│ │ └── TaskStepDetail.java # 任务步骤详情
│ ├── service/
│ │ ├── MultiDeviceTaskService.java
│ │ ├── TaskExecutionEngine.java # 任务执行引擎
│ │ └── TaskStatusNotificationService.java # SSE推送服务
│ ├── controller/
│ │ ├── MultiDeviceTaskController.java
│ │ └── TaskStatusNotificationController.java
│ └── model/
│ ├── RetryPolicy.java # 重试策略
│ ├── TaskExecutionContext.java
│ └── TaskExecutionResult.java
│
├── service/ # PLC服务层
│ ├── PlcDynamicDataService.java # PLC动态数据服务
│ └── PlcTestWriteService.java
│
└── s7/ # S7通信
└── provider/
└── S7SerializerProvider.java
设计理念:一个设备类型模板(共享逻辑)+ 多个设备实例(独立状态)
LoadVehicleLogicHandler (共享逻辑处理器)
↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 大车实例1│ │ 大车实例2│ │ 大车实例3│
│ 状态独立 │ │ 状态独立 │ │ 状态独立 │
└─────────┘ └─────────┘ └─────────┘
优势:
- 共享逻辑:所有实例使用同一个逻辑处理器
- 独立状态:每个实例有独立的运行时状态
- 灵活配置:设备组可以包含任意数量的实例
- 易于扩展:新增实例只需在数据库添加记录
plcRequest 保持为 1mesSend=1 时,读取 MES 参数(玻璃ID、起始位置、目标位置等)gotime 和 cartimestate1~6 状态流转(0→1→2),自动触发 MES 汇报state=1(上车完成)时,自动将"卧转立"设备的 plcRequest 设置为 0startSlot 和 outboundSlotRanges 自动判断任务类型{
"vehicleCapacity": 6,
"vehicleSpeed": 1.0,
"minRange": 1,
"maxRange": 100,
"homePosition": 50,
"idleMonitorIntervalMs": 1000,
"taskMonitorIntervalMs": 1000,
"mesConfirmTimeoutMs": 30000,
"positionMapping": {
"900": 100,
"901": 500
},
"outboundSlotRanges": [
{"start": 1000, "end": 2000}
],
"gridPositionMapping": {
"1000": 80
}
}
IDLE (空闲) → EXECUTING (执行中) → IDLE (空闲)
mesSend=1 时,读取玻璃信息(mesGlassId、mesWidth、mesHeight、workLine)glass_info 表{
"scanIntervalMs": 10000,
"workLine": 1
}
plcGlassId1~6、plcGlassCount、inPosition、plcRequest{
"scanIntervalMs": 10000,
"bufferTimeoutMs": 30000,
"vehicleCapacity": 2,
"monitorIntervalMs": 1000,
"workLine": "LINE_001",
"positionValue": 100
}
{
"gridRanges": [
{"row": 1, "start": 1, "end": 52},
{"row": 2, "start": 53, "end": 101}
],
"gridLength": 2000,
"gridWidth": 1500,
"gridThickness": 5
}
DeviceConfig device = new DeviceConfig();
device.setDeviceId("DEVICE_001");
device.setDeviceCode("DEV_001");
device.setDeviceName("大车设备1");
device.setDeviceType(DeviceConfig.DeviceType.LOAD_VEHICLE);
device.setPlcIp("192.168.1.101");
device.setPlcPort(102);
device.setPlcType(DeviceConfig.PlcType.S7_1200);
device.setModuleName("DB1");
device.setProjectId(1L);
device.setEnabled(true);
// 设置逻辑参数
device.setExtraParams(extraParams);
deviceConfigService.createDevice(device);
在 extraParams.deviceLogic 中配置设备特定的逻辑参数,如车辆速度、位置映射等。
DeviceGroupConfig group = new DeviceGroupConfig();
group.setGroupCode("GROUP_001");
group.setGroupName("生产线A");
group.setGroupType(1); // 1-生产线,2-测试线,3-辅助设备组
group.setProjectId(1L);
group.setStatus(1); // 0-停用,1-启用,3-维护中
group.setMaxConcurrentDevices(3); // 最大并发设备数
group.setHeartbeatInterval(30);
group.setCommunicationTimeout(5000);
deviceGroupService.createGroup(group);
// 添加设备,设置优先级、角色、连接顺序
deviceGroupService.addDevicesToGroup(groupId, deviceIds, priorities, roles, connectionOrders);
MultiDeviceTaskRequest request = new MultiDeviceTaskRequest();
request.setGroupId(groupId);
request.setTaskName("测试任务");
request.setProjectId("PROJECT_001");
request.setParameters(taskParameters);
MultiDeviceTask task = multiDeviceTaskService.startTask(request);
POST /device/task/start - 启动任务POST /device/task/list - 查询任务列表GET /device/task/{taskId} - 查询任务详情GET /device/task/{taskId}/steps - 查询任务步骤详情POST /device/task/{taskId}/cancel - 取消任务前端通过 SSE 连接实时接收任务状态更新:
```javascript
// 监听指定任务
const eventSource = new EventSource('/task/notification/sse?taskId=xxx');
eventSource.addEventListener('taskStatus', (event) => {
const data = JSON.parse(event.data);
// 处理任务状态更新
});
eventSource.addEventListener('stepUpdate', (event) => {
const data = JSON.parse(event.data);
// 处理步骤更新
});
// 监听所有任务
const eventSourceAll = new EventSource('/task/notification/sse/all');
```
SSE 端点:
- GET /task/notification/sse?taskId=xxx - 监听指定任务
- GET /task/notification/sse/all - 监听所有任务
- POST /task/notification/close/{taskId} - 关闭指定任务的SSE连接
- POST /task/notification/close/all - 关闭所有SSE连接
id:主键(BIGINT)device_id:设备唯一标识(VARCHAR(50),唯一)device_code:设备编码(VARCHAR(50),唯一)device_name:设备名称(VARCHAR(100))device_type:设备类型(VARCHAR(50))project_id:所属项目ID(BIGINT)plc_ip:PLC IP地址(VARCHAR(15))plc_port:PLC端口(INT)plc_type:PLC类型(VARCHAR(20))module_name:模块名称(VARCHAR(50))status:设备状态(VARCHAR(20))is_primary:是否主控设备(BOOLEAN)enabled:是否启用(BOOLEAN)config_json:设备特定配置(TEXT,JSON格式)extra_params:扩展参数(JSON)description:设备描述(VARCHAR(200))is_deleted:是否删除(INT,0-否,1-是)created_time、updated_time:创建/更新时间created_by、updated_by:创建/更新人id:主键(BIGINT)group_code:设备组编码(VARCHAR(50),唯一)group_name:设备组名称(VARCHAR(100))group_type:设备组类型(INT,1-生产线,2-测试线,3-辅助设备组)project_id:所属项目ID(BIGINT)status:设备组状态(INT,0-停用,1-启用,3-维护中)max_concurrent_devices:最大并发设备数(INT)heartbeat_interval:心跳检测间隔(INT,秒)communication_timeout:通信超时时间(INT,毫秒)description:设备组描述(VARCHAR(200))extra_config:扩展配置(JSON)is_deleted:是否删除(INT)created_time、updated_time:创建/更新时间created_by、updated_by:创建/更新人id:主键(BIGINT)group_id:设备组ID(BIGINT)device_id:设备ID(BIGINT)priority:设备在组内的优先级(INT,1-最高,10-最低)role:设备在组内的角色(INT,1-主控,2-协作,3-监控)status:设备在该组中的状态(INT,0-未配置,1-正常,2-故障,3-维护)connection_order:连接顺序(INT,数值越小越先连接)relation_desc:关联描述(VARCHAR(200))extra_params:扩展参数(JSON)is_deleted:是否删除(INT)created_time、updated_time:创建/更新时间created_by、updated_by:创建/更新人id:主键(BIGINT)task_id:任务唯一标识(VARCHAR(50),唯一)group_id:设备组ID(VARCHAR(50))project_id:项目ID(VARCHAR(50))status:任务状态(ENUM:PENDING, RUNNING, COMPLETED, FAILED, CANCELLED)current_step:当前执行步骤(INT)total_steps:总步骤数(INT)start_time:开始时间(DATETIME)end_time:结束时间(DATETIME)error_message:错误信息(TEXT)result_data:结果数据(JSON)created_time、updated_time:创建/更新时间id:主键(BIGINT)task_id:任务ID(VARCHAR(50))step_order:步骤顺序(INT)device_id:设备ID(VARCHAR(50))step_name:步骤名称(VARCHAR(100))status:步骤状态(ENUM:PENDING, RUNNING, COMPLETED, FAILED, SKIPPED)start_time:步骤开始时间(DATETIME)end_time:步骤结束时间(DATETIME)duration_ms:执行耗时(BIGINT,毫秒)input_data:输入数据(JSON)output_data:输出数据(JSON)error_message:错误信息(TEXT)retry_count:重试次数(INT)created_time:创建时间(DATETIME)id:主键(BIGINT)glass_id:玻璃ID(VARCHAR(50))width:宽度(DECIMAL)height:高度(DECIMAL)work_line:产线编号(VARCHAR(50))scan_time:扫码时间(DATETIME)status:状态(VARCHAR(20))created_time、updated_time:创建/更新时间id:主键(BIGINT)device_id:设备ID(VARCHAR(50))task_id:关联任务ID(VARCHAR(50),可选)status:设备状态(ENUM:ONLINE, OFFLINE, BUSY, ERROR, MAINTENANCE)last_heartbeat:最后心跳时间(DATETIME)cpu_usage:CPU使用率(DECIMAL(5,2))memory_usage:内存使用率(DECIMAL(5,2))plc_connection_status:PLC连接状态(ENUM:CONNECTED, DISCONNECTED, ERROR)current_operation:当前操作(VARCHAR(100))operation_progress:操作进度(DECIMAL(5,2),0-100)alert_message:告警信息(TEXT)created_time:记录时间(DATETIME)1. 创建任务记录(status = PENDING)
2. 获取设备组中的设备列表(按 connection_order 排序)
3. 依次执行每个设备:
a. 检查前置条件
b. 更新步骤状态为 RUNNING
c. 执行设备交互逻辑
d. 传递数据到下一个设备
e. 更新步骤状态为 COMPLETED
4. 所有设备执行完成后,更新任务状态为 COMPLETED
1. 创建任务记录(status = PENDING)
2. 获取设备组中的设备列表
3. 使用线程池并行执行设备:
a. 使用 max_concurrent_devices 控制并发数
b. 每个设备独立执行
c. 等待所有设备完成
4. 所有设备执行完成后,更新任务状态为 COMPLETED
server:
port: 10018
spring:
profiles:
active: dev
application:
name: plcSend
liquibase:
enabled: true
change-log: classpath:changelog/changelogBase.xml
# PLC配置
s7:
load:
dbArea: DB1
beginIndex: 0
raw:
dbArea: DB2
beginIndex: 0
# PLC模拟配置
plc:
simulate:
enabled: false
interval: 5000
failure-rate: 0
task-count: 10
task-type: normal
# MES配置
mes:
width: 2800
height: 5000
每个设备类型的逻辑参数在 extraParams.deviceLogic 中配置,具体参数见各设备类型的说明。
在 DeviceConfig.DeviceType 中添加常量java public static final class DeviceType { public static final String NEW_DEVICE = "新设备类型"; }
创建设备处理器
```java
@Component
public class NewDeviceLogicHandler extends BaseDeviceLogicHandler {
@Override
public String getDeviceType() {
return DeviceConfig.DeviceType.NEW_DEVICE;
}
@Override
protected DevicePlcVO.OperationResult doExecute(
DeviceConfig deviceConfig,
String operation,
Map<String, Object> params,
Map<String, Object> logicParams) {
// 实现设备逻辑
}
}
```
创建交互流程(可选)java @Component public class NewDeviceInteraction implements DeviceInteraction { @Override public InteractionResult execute(InteractionContext context) { // 实现交互流程 } }
对于需要多实例协调的复杂设备,可以创建专用包: interaction/ └── newdevice/ ├── handler/ ├── coordination/ └── model/
VehicleStatusManager),服务重启后会丢失ConcurrentHashMap,支持并发访问max_concurrent_devices 控制并发GLASS_STORAGE):代码中已实现,但当前业务场景不使用,保留用于未来扩展/device/*)/device/group/*)/device/task/*)/task/notification/*)src/main/resources/db/migration//swagger-ui.htmlmes-web 项目如有问题或建议,请联系开发团队。