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

15 KiB
Raw Blame History

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 格式中新增一个字段:

"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 新增列

ALTER TABLE `device_data`
    ADD COLUMN `di_reg` INT DEFAULT NULL COMMENT '输入寄存器(0-7报工/8-15设备状态)';

2.3 DDL — device_di_reg_log 新建表

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=REPORTprocess_sortaffected_countwork_order_ids 有效;action_type=STATUScn_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 并路由:

// 解析 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. 工单范围 = 全局所有工单,不按工位/车间过滤
/**
 * 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 日志写入方法

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

/**
 * 查询所有在产工单非D状态中 processSort = processSort 的工单分录
 */
List<WorkOrderEntry> selectActiveEntriesByProcessSort(@Param("processSort") int processSort);

XMLWorkOrderEntryMapper.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道工序

{
  "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=2pro_status IN('A','B') 的工单分录 → 报工数量 = 工单计划数量 → 工单状态置为 D
再次收到 di_reg=1 → 同工单已为 D查询结果为空 → 跳过,天然幂等

5.2 设备状态示例设置为故障di_reg=11

{
  "cn_reg": 1,
  "di_reg": 11,
  ...
}

cn_reg 强制覆盖为 3故障device_di_reg_log 写入 STATUS 记录OEE 状态显示红色


六、实施步骤(清单)

  • 执行 DDL 脚本 2026-03-06_v2.0.004_周启威_8Multi报工、设备状态修改.sqlALTER + CREATE TABLE
  • DeviceData.java 新增 diReg 字段(@TableField("di_reg")
  • 新建 DeviceDiRegLog.java 实体(对应 device_di_reg_log 表)
  • 新建 DeviceDiRegLogMapper.javaextends BaseMapper<DeviceDiRegLog>
  • WorkOrderEntryMapper.java 新增 selectActiveEntriesByProcessSort 方法
  • 新建 WorkOrderEntryMapper.xml,添加对应 SQL
  • Multi8ProtocolService.java 注入 WorkOrderMapperWorkOrderEntryMapperIReportServiceDeviceDiRegLogMapper
  • Multi8ProtocolService.javaparse8MultiJson 中新增 di_reg 解析分发
  • Multi8ProtocolService.java 新增 handleAutoReportsaveReportLogsaveStatusLog 方法
  • 联调测试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单个工单失败不影响其他工单。