Files
MES/yawei-mes/.tasks/2026-03-06_v2.0.004_8Multi报工、设备状态修改.md
2026-04-02 10:39:03 +08:00

372 lines
15 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.

# YJH-MES v2.0.004 — 8Multi di_reg 报工 & 设备状态接口
> **版本**: v2.0.004
> **更新日期**: 2026-03-06
> **项目**: YJH-MES (yjh-mes + mes-ui)
> **说明**: 在 8Multi JSON 协议中新增 `di_reg` 字段处理,实现硬件触发报工和设备状态修改
---
## 一、需求概述
### 1.1 新增字段
原 8Multi JSON 格式中新增一个字段:
```json
"di_reg": %d // 0000-0111 触发报工1000-1111 修改设备状态
```
### 1.2 di_reg 编码规则
| 十进制值 | 二进制 | 动作类型 | 含义 |
|---------|--------|---------|------|
| 0 | 0000 | 报工 | 对工序路线中**第1道工序**的全部在产工单执行报工 |
| 1 | 0001 | 报工 | 对工序路线中**第2道工序**的全部在产工单执行报工 |
| 2 | 0010 | 报工 | 对工序路线中**第3道工序**的全部在产工单执行报工 |
| 3 | 0011 | 报工 | 对工序路线中**第4道工序**的全部在产工单执行报工 |
| 4 | 0100 | 报工 | 对工序路线中**第5道工序**的全部在产工单执行报工 |
| 5 | 0101 | 报工 | 对工序路线中**第6道工序**的全部在产工单执行报工 |
| 6 | 0110 | 报工 | 对工序路线中**第7道工序**的全部在产工单执行报工 |
| 7 | 0111 | 报工 | 对工序路线中**第8道工序**的全部在产工单执行报工 |
| 8 | 1000 | 设备状态 | 计划停机cn_reg = 0 |
| 9 | 1001 | 设备状态 | 正常工作cn_reg = 1 |
| 10 | 1010 | 设备状态 | 待机cn_reg = 2 |
| 11 | 1011 | 设备状态 | 故障cn_reg = 3 |
| 12 | 1100 | 设备状态 | 在修cn_reg = 4 |
| 13 | 1101 | 设备状态 | 缺人cn_reg = 5 |
| 14 | 1110 | 设备状态 | 缺料cn_reg = 6 |
| 15 | 1111 | 设备状态 | 清零cn_reg = 7 |
| null/其他 | — | 忽略 | 不触发任何动作 |
**判断逻辑**
```
di_reg < 8 → 报工模式processSort = di_reg + 1
di_reg >= 8 → 设备状态模式cn_reg_new = di_reg - 8
```
---
## 二、数据库设计
### 2.1 表设计总览(报工与设备状态两个功能独立)
| 功能 | 表 | 说明 |
|------|----|------|
| 报工结果 | `pro_report`(已有)| 生成报工单,工单状态变更为 D |
| 设备状态 | `device_data.cn_reg`(已有)| 本次上报的 cn_reg 被 di_reg 覆盖 |
| di_reg 原始值 | `device_data.di_reg`**新增列**| 随每次上报落库 |
| **触发动作日志** | `device_di_reg_log`**新建表**| 记录两类触发事件,用于追溯和审计 |
### 2.2 DDL — device_data 新增列
```sql
ALTER TABLE `device_data`
ADD COLUMN `di_reg` INT DEFAULT NULL COMMENT '输入寄存器(0-7报工/8-15设备状态)';
```
### 2.3 DDL — device_di_reg_log 新建表
```sql
CREATE TABLE `device_di_reg_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`device_id` BIGINT NOT NULL COMMENT '设备ID(关联device.id)',
`device_no` INT NOT NULL COMMENT '设备号(device.device_no)',
`di_reg` INT NOT NULL COMMENT 'di_reg原始值(0-15)',
`action_type` VARCHAR(16) NOT NULL COMMENT '动作类型: REPORT=报工 / STATUS=设备状态',
`process_sort` INT DEFAULT NULL COMMENT '工序顺序(REPORT时有效, = di_reg + 1)',
`cn_reg_new` INT DEFAULT NULL COMMENT '新设备状态值(STATUS时有效, = di_reg - 8)',
`affected_count` INT DEFAULT 0 COMMENT '本次处理的工单数量(REPORT时)',
`work_order_ids` VARCHAR(512) DEFAULT NULL COMMENT '处理的工单ID列表,逗号分隔(REPORT时)',
`trigger_time` DATETIME NOT NULL COMMENT '触发时间',
`remark` VARCHAR(512) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0正常/1删除)',
PRIMARY KEY (`id`),
KEY `idx_device_id_time` (`device_id`, `trigger_time`),
KEY `idx_action_type_time` (`action_type`, `trigger_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='8Multi di_reg触发动作日志表';
```
> **字段说明**`action_type=REPORT` 时 `process_sort`、`affected_count`、`work_order_ids` 有效;`action_type=STATUS` 时 `cn_reg_new` 有效。
---
## 三、后端实现方案
### 3.1 涉及文件
```
yjh-mes/src/main/java/cn/sourceplan/equipment/service/
└── Multi8ProtocolService.java ← 主改动di_reg 解析分发、日志写入
yjh-mes/src/main/java/cn/sourceplan/equipment/domain/
├── DeviceData.java ← 新增 diReg 字段
└── DeviceDiRegLog.java ← 新建:日志实体
yjh-mes/src/main/java/cn/sourceplan/equipment/mapper/
└── DeviceDiRegLogMapper.java ← 新建:日志 Mapperextends BaseMapper
yjh-mes/src/main/java/cn/sourceplan/production/mapper/
└── WorkOrderEntryMapper.java ← 新增 selectActiveEntriesByProcessSort 方法
yjh-mes/src/main/resources/mapper/production/
└── WorkOrderEntryMapper.xml ← 新建:对应 SQL
.sql/
└── 2026-03-06_v2.0.004_周启威_8Multi报工、设备状态修改.sql ← DDL 脚本
```
### 3.2 Multi8ProtocolService — parse8MultiJson 改动
`deviceDataMapper.insert(data)` 之前,解析 `di_reg` 并路由:
```java
// 解析 di_reg
Integer diReg = parseInt(json.getStr("di_reg"));
data.setDiReg(diReg);
// di_reg 分发处理
if (diReg != null) {
if (diReg >= 0 && diReg <= 7) {
// 报工模式processSort = diReg + 1全局、全量、幂等
handleAutoReport(device, diReg + 1);
} else if (diReg >= 8 && diReg <= 15) {
// 设备状态模式:强制覆盖 cn_reg在 insert 之前覆盖,确保落库值正确)
int newCnReg = diReg - 8;
data.setCnReg(newCnReg);
log.info("di_reg={} → 设备状态覆盖 cn_reg={}", diReg, newCnReg);
// saveStatusLog 在 deviceDataMapper.insert(data) 之后调用见注意事项4
}
}
```
### 3.3 handleAutoReport — 报工逻辑
**三条核心原则**
1. **报工数量** = 工单计划数量(全量报工,一次完成)
2. **幂等性** = 报工后工单状态置为 D已完成下次查询自动过滤无需额外判断
3. **工单范围** = 全局所有工单,不按工位/车间过滤
```java
/**
* di_reg 触发报工
* 全局查找 sort=processSort 且 pro_status IN('A','B') 的工单分录,全量报工后置为已完成
* 幂等保证:已完成(D)的工单在查询时自动过滤,无需重复判断
*
* @param device 上报设备(用于写日志)
* @param processSort 工序顺序1-based= di_reg + 1
*/
@Transactional(rollbackFor = Exception.class) // 保证报工单创建与工单状态更新原子性
private void handleAutoReport(MesDevice device, int processSort) {
List<WorkOrderEntry> entries = workOrderEntryMapper.selectActiveEntriesByProcessSort(processSort);
if (entries == null || entries.isEmpty()) {
log.info("di_reg 报工:未找到第{}道工序的在产工单分录", processSort);
saveReportLog(device, processSort - 1, processSort, 0, "");
return;
}
List<Long> processedIds = new ArrayList<>();
for (WorkOrderEntry entry : entries) {
try {
WorkOrder workOrder = workOrderMapper.selectById(entry.getWorkorderId());
if (workOrder == null || "D".equals(workOrder.getProStatus())) {
continue;
}
// 报工数量 = 工单计划数量(全量)
BigDecimal reportQty = workOrder.getQuantity() != null
? workOrder.getQuantity() : BigDecimal.ONE;
// 构建报工单
Report report = new Report();
report.setWorkOrderEntryId(entry.getId());
report.setWorkOrderId(workOrder.getId());
report.setProcessName(entry.getProcessName());
report.setProcessId(entry.getProcessId());
report.setWorkshopId(entry.getWorkshopId());
report.setWorkshopName(entry.getWorkshopName());
report.setStationId(entry.getStationId());
report.setStationName(entry.getStationName());
report.setReportTime(new Date());
report.setReportChannel("8Multi");
report.setReportQuantity(reportQty);
report.setQualifiedQuantity(reportQty);
report.setUnqualifiedQuantity(BigDecimal.ZERO);
report.setQualityStatus("A"); // 免检
// 创建报工单insertReportSimple 不处理工单状态,需手动更新)
reportService.insertReportSimple(report);
// 手动将工单标记为已完成 → 下次同 di_reg 触发时自动幂等跳过
// 注意:两步操作在同一 @Transactional 内,任一失败均回滚
workOrder.setTotalReportedQuantity(reportQty);
workOrder.setProStatus("D");
workOrder.setActualEndTime(new Date());
workOrder.setRealFinishDate(new Date());
workOrderMapper.updateById(workOrder);
processedIds.add(workOrder.getId());
log.info("di_reg 报工成功workOrderId={}, processSort={}, qty={}",
workOrder.getId(), processSort, reportQty);
} catch (Exception e) {
log.error("di_reg 报工失败entryId={}", entry.getId(), e);
}
}
// 写触发日志
String woIds = processedIds.stream().map(String::valueOf)
.collect(java.util.stream.Collectors.joining(","));
saveReportLog(device, processSort - 1, processSort, processedIds.size(), woIds);
}
```
### 3.4 日志写入方法
```java
private void saveReportLog(MesDevice device, int diReg, int processSort, int count, String woIds) {
DeviceDiRegLog entry = new DeviceDiRegLog();
entry.setDeviceId(device.getId());
entry.setDeviceNo(device.getDeviceNo());
entry.setDiReg(diReg);
entry.setActionType("REPORT");
entry.setProcessSort(processSort);
entry.setAffectedCount(count);
entry.setWorkOrderIds(woIds);
entry.setTriggerTime(new Date());
deviceDiRegLogMapper.insert(entry);
}
private void saveStatusLog(MesDevice device, int diReg, int cnRegNew) {
DeviceDiRegLog entry = new DeviceDiRegLog();
entry.setDeviceId(device.getId());
entry.setDeviceNo(device.getDeviceNo());
entry.setDiReg(diReg);
entry.setActionType("STATUS");
entry.setCnRegNew(cnRegNew);
entry.setTriggerTime(new Date());
deviceDiRegLogMapper.insert(entry);
}
```
### 3.5 WorkOrderEntryMapper — 新增查询方法
**接口**WorkOrderEntryMapper.java
```java
/**
* 查询所有在产工单非D状态中 processSort = processSort 的工单分录
*/
List<WorkOrderEntry> selectActiveEntriesByProcessSort(@Param("processSort") int processSort);
```
**XML**WorkOrderEntryMapper.xml新建文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.sourceplan.production.mapper.WorkOrderEntryMapper">
<select id="selectActiveEntriesByProcessSort"
resultType="cn.sourceplan.production.domain.WorkOrderEntry">
SELECT e.*
FROM pro_workorder_entry e
INNER JOIN pro_workorder w ON w.id = e.workorder_id
WHERE e.sort = #{processSort}
AND w.pro_status IN ('A', 'B')
</select>
</mapper>
```
> **说明**`sort` 对应 `WorkOrderEntry.sort`工序顺序1-based只取状态为 A未开始或 B生产中的工单已完成D的工单自动排除实现天然幂等。
---
## 四、设备状态覆盖说明
`di_reg >= 8` 时,本次上报的 `cn_reg` 值将被 `di_reg - 8` **覆盖**(无论 JSON 中 `cn_reg` 是什么)。这样 OEE 时序图、设备状态页面均可立即反映该状态,无需额外接口。
| di_reg | 覆盖后 cn_reg | 含义 |
|--------|------------|------|
| 8 | 0 | 计划停机 |
| 9 | 1 | 正常工作 |
| 10 | 2 | 待机 |
| 11 | 3 | 故障 |
| 12 | 4 | 在修 |
| 13 | 5 | 缺人 |
| 14 | 6 | 缺料 |
| 15 | 7 | 清零 |
---
## 五、完整 JSON 示例(含 di_reg
### 5.1 报工示例第2道工序
```json
{
"header": "+YAV",
"Card": "8multi",
"id": 101126010002,
"dt": 300,
"a1": 5.2,
"a2": 0.0,
"q1": 0.0,
"q2": 0.0,
"t1": 26.5,
"t2": 25.0,
"dht1": 60.0,
"dht2": 58.0,
"cn_reg": 1,
"f1": 50.00,
"f2": 0.00,
"c1": 50,
"c2": 0,
"di_reg": 1,
"do_reg": 0,
"reg1": 0.000,
"reg2": 0.000,
"tv": 0,
"save": 0,
"oee": 100,
"end": "EEFF"
}
```
→ 全局查找 `sort=2``pro_status IN('A','B')` 的工单分录 → 报工数量 = 工单计划数量 → 工单状态置为 D
再次收到 `di_reg=1` → 同工单已为 D查询结果为空 → 跳过,**天然幂等**
### 5.2 设备状态示例设置为故障di_reg=11
```json
{
"cn_reg": 1,
"di_reg": 11,
...
}
```
`cn_reg` 强制覆盖为 3故障`device_di_reg_log` 写入 STATUS 记录OEE 状态显示红色
---
## 六、实施步骤(清单)
- [ ] 执行 DDL 脚本 `2026-03-06_v2.0.004_周启威_8Multi报工、设备状态修改.sql`ALTER + CREATE TABLE
- [ ] `DeviceData.java` 新增 `diReg` 字段(`@TableField("di_reg")`
- [ ] 新建 `DeviceDiRegLog.java` 实体(对应 `device_di_reg_log` 表)
- [ ] 新建 `DeviceDiRegLogMapper.java``extends BaseMapper<DeviceDiRegLog>`
- [ ] `WorkOrderEntryMapper.java` 新增 `selectActiveEntriesByProcessSort` 方法
- [ ] 新建 `WorkOrderEntryMapper.xml`,添加对应 SQL
- [ ] `Multi8ProtocolService.java` 注入 `WorkOrderMapper``WorkOrderEntryMapper``IReportService``DeviceDiRegLogMapper`
- [ ] `Multi8ProtocolService.java``parse8MultiJson` 中新增 `di_reg` 解析分发
- [ ] `Multi8ProtocolService.java` 新增 `handleAutoReport``saveReportLog``saveStatusLog` 方法
- [ ] 联调测试di_reg=0 触发报工di_reg=11 触发故障状态
---
## 七、注意事项
1. **幂等性**:第一次触发时全量报工并将工单置为 D已完成后续同 `di_reg` 触发时,`pro_status IN('A','B')` 查询自动排除 D 状态工单,**无需额外判断**。
2. **报工数量**:固定取 `workOrder.getQuantity()`(计划数量),全量一次性完成,与 c1 计数器无关。
3. **di_reg = null**:旧版固件不携带此字段时,`parseInt` 返回 null分发代码不执行**不影响原有解析逻辑**。
4. **`saveStatusLog` 调用时机**:应在 `deviceDataMapper.insert(data)` **之后**调用,避免 insert 失败时日志已落库;`parse8MultiJson` 中需将 `saveStatusLog` 调用挪至 insert 语句后。
5. **`DeviceDiRegLog` 不继承 BaseEntity**:该实体仅需 `@TableName``@TableId` 注解,手动设置 `triggerTime`,不依赖框架自动填充的 `create_time`/`create_by` 等字段DDL 也不必添加这些列。
6. **事务隔离**`handleAutoReport``@Transactional`,每个工单报工+状态更新原子提交;但各工单之间彼此独立(外层 try-catch单个工单失败不影响其他工单。