Files
MES/yawei-mes/.tasks/2025-11-29_v1.0.30_8multi接入优化.md
2026-04-02 10:39:03 +08:00

20 KiB
Raw Permalink Blame History

YJH-MES 8Multi协议接入优化文档

版本: v1.0.30
更新日期: 2025-12-01
项目: YJH-MES (yjh-mes + mes-ui)
说明: 本文档基于项目实际代码编写,准确反映当前实现


一、项目概述

1.1 技术栈

层级 技术 说明
后端 Spring Boot + MyBatis-Plus yjh-mes模块
前端 Vue 2 + Element UI mes-ui模块
数据库 MySQL 8.0 device / device_data 表

1.2 协议类型

协议 数据格式 判断条件 设备号来源
8ADPRO 帧格式 +开头且以EEFF结尾 parts[0]
8MULTI JSON格式 {开头且以}结尾 id字段后4位

二、数据上报接口

2.1 接口信息

POST /equipment/info/ingest/raw
Content-Type: text/plain

2.2 8ADPRO帧格式

+YAV:设备号,温度,电流,计数1,计数2,模拟1,模拟2,模拟3,模拟4,模拟5,模拟6,模拟7,数字1,数字2,数字3,数字4,数字5,数字6,EEFF

示例:

+YAV:1,25.5,3.2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,EEFF

2.3 8MULTI JSON格式

{
  "header": "+YAV",
  "Card": "8multi",
  "id": 101126010002,
  "dt": 300,
  "a1": 0.106,
  "a2": 0.106,
  "q1": 0.098,
  "q2": 0.098,
  "t1": 25.5,
  "t2": 26.0,
  "dht1": 65.0,
  "dht2": 60.0,
  "cn_reg": 1,
  "f1": 50.00,
  "f2": 50.00,
  "c1": 5,
  "c2": 3,
  "do_reg": 0,
  "reg1": 0.000,
  "reg2": 0.000,
  "tv": 0,
  "save": 0,
  "oee": 100,
  "end": "EEFF"
}

三、8MULTI卡号ID格式

卡号ID共12位: 品牌1位 + 年份1位 + 月日4位 + 用户号2位 + 序号4位

示例: 101126010002
  1    - 品牌前缀
  0    - 年份(0=2025, 1=2026, 2=2027...)
  1126 - 月日(11月26日)
  01   - 用户号
  0002 - 设备序号后端使用后4位作为设备号

四、8MULTI JSON字段映射

4.1 字段对照表

JSON字段 数据库字段 含义 说明
id device_no 卡号 取后4位作为设备号
a1 current1_value 电流1 已换算实际值(A)
a2 current2_value 电流2 已换算实际值(A)
q1 quality1_value 质量1 已换算实际值
q2 quality2_value 质量2 已换算实际值
t1 temperature_c 温度1 °C
t2 analog4 温度2 °C(预留)
dht1 humidity 湿度1 %
dht2 analog5 湿度2 %(预留)
c1 counter1 计数1 发送完清零
c2 counter2 计数2 发送完清零
f1 f1 测频1 Hz
f2 f2 测频2 Hz
cn_reg cn_reg 屏幕状态 0-7
do_reg do_reg 状态输出 位编码
reg1 reg1 从机数据1
reg2 reg2 从机数据2
tv tv 显示 1有0无
save save_flag 边缘存储 1有0无
oee oee OEE计算 0或100
dt dt 采样间隔

4.2 cn_reg屏幕状态定义

含义 前端颜色
0 计划停机 灰色(info)
1 正常工作 绿色(success)
2 待机 黄色(warning)
3 故障 红色(danger)
4 在修 黄色(warning)
5 缺人 黄色(warning)
6 缺料 黄色(warning)
7 清零 灰色(info)

4.3 cn_reg自动判断逻辑下位机执行

cn_reg=0计划停机: 3次都是 [(a1'<20%) & (a2'<20%)] || [(f1=0) & (f2=0)]
cn_reg=1正常工作: 3次都是 (a1'>40%) || (a2'>40%) || (f1>0.1) || (f2>0.1)
cn_reg=2待机    : 3次都是 (a1介于20-40%) || (a2介于20-40%)
cn_reg=3-7         : 人工设置(触摸屏点击)

4.4 OEE计算规则

下位机逻辑: if(cn_reg=1) oee=100; else oee=0;
上位机: 计算时间段内oee均值

4.5 上报频率

1. 开机上发一次dt=0
2. 开机60s内10s发一次
3. 开机超过60s按设置间隔发送默认300s
4. 计数值改变时上发间隔至少60sdt=60
5. 不接计数器时300s发一次dt=300

五、数据库设计

5.1 device表扩展字段

-- 协议类型
ALTER TABLE `device` ADD COLUMN `protocol_type` VARCHAR(16) DEFAULT '8ADPRO' 
  COMMENT '协议类型(8ADPRO/8MULTI)';

-- 量程配置(用于超量程警告)
ALTER TABLE `device` ADD COLUMN `current1_range_start` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `current1_range_end` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `current2_range_start` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `current2_range_end` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `quality1_range_start` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `quality1_range_end` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `quality2_range_start` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `quality2_range_end` DECIMAL(10,3) DEFAULT NULL;
ALTER TABLE `device` ADD COLUMN `quality1_unit` VARCHAR(10) DEFAULT 'kg';
ALTER TABLE `device` ADD COLUMN `quality2_unit` VARCHAR(10) DEFAULT 'kg';
ALTER TABLE `device` ADD COLUMN `current_unit` VARCHAR(10) DEFAULT 'A';
ALTER TABLE `device` ADD COLUMN `voltage1` DECIMAL(10,3) DEFAULT NULL COMMENT '电压1(用于功率计算)';
ALTER TABLE `device` ADD COLUMN `voltage2` DECIMAL(10,3) DEFAULT NULL COMMENT '电压2(用于功率计算)';
ALTER TABLE `device` ADD COLUMN `counter1_baseline` INT DEFAULT 0 COMMENT '计数1清零基准';
ALTER TABLE `device` ADD COLUMN `counter2_baseline` INT DEFAULT 0 COMMENT '计数2清零基准';

-- 设备扩展信息
ALTER TABLE `device` ADD COLUMN `location` VARCHAR(255) DEFAULT NULL COMMENT '设备位置';
ALTER TABLE `device` ADD COLUMN `brand` VARCHAR(128) DEFAULT NULL COMMENT '设备品牌';
ALTER TABLE `device` ADD COLUMN `model` VARCHAR(128) DEFAULT NULL COMMENT '设备型号';
ALTER TABLE `device` ADD COLUMN `workshop_id` BIGINT DEFAULT NULL COMMENT '车间ID(关联md_workshop)';
ALTER TABLE `device` ADD COLUMN `section` VARCHAR(64) DEFAULT NULL COMMENT '工序';

5.2 device_data表扩展字段

-- 8Multi V2.8 专用字段
ALTER TABLE `device_data` ADD COLUMN `current1_value` DECIMAL(10,3) DEFAULT NULL COMMENT '电流1实际值';
ALTER TABLE `device_data` ADD COLUMN `current2_value` DECIMAL(10,3) DEFAULT NULL COMMENT '电流2实际值';
ALTER TABLE `device_data` ADD COLUMN `quality1_value` DECIMAL(10,3) DEFAULT NULL COMMENT '质量1实际值';
ALTER TABLE `device_data` ADD COLUMN `quality2_value` DECIMAL(10,3) DEFAULT NULL COMMENT '质量2实际值';
ALTER TABLE `device_data` ADD COLUMN `humidity` DECIMAL(10,3) DEFAULT NULL COMMENT '湿度';
ALTER TABLE `device_data` ADD COLUMN `f1` DECIMAL(10,2) DEFAULT NULL COMMENT '测频1(Hz)';
ALTER TABLE `device_data` ADD COLUMN `f2` DECIMAL(10,2) DEFAULT NULL COMMENT '测频2(Hz)';
ALTER TABLE `device_data` ADD COLUMN `cn_reg` INT DEFAULT NULL COMMENT '屏幕状态(0-7)';
ALTER TABLE `device_data` ADD COLUMN `do_reg` VARCHAR(16) DEFAULT NULL COMMENT '状态输出';
ALTER TABLE `device_data` ADD COLUMN `reg1` DECIMAL(12,3) DEFAULT NULL COMMENT '从机数据1';
ALTER TABLE `device_data` ADD COLUMN `reg2` DECIMAL(12,3) DEFAULT NULL COMMENT '从机数据2';
ALTER TABLE `device_data` ADD COLUMN `tv` INT DEFAULT NULL COMMENT '显示';
ALTER TABLE `device_data` ADD COLUMN `save_flag` INT DEFAULT NULL COMMENT '边缘存储';
ALTER TABLE `device_data` ADD COLUMN `oee` INT DEFAULT NULL COMMENT 'OEE计算值';
ALTER TABLE `device_data` ADD COLUMN `dt` INT DEFAULT NULL COMMENT '采样间隔(秒)';
ALTER TABLE `device_data` ADD COLUMN `power1` DECIMAL(12,3) DEFAULT NULL COMMENT '功率1';
ALTER TABLE `device_data` ADD COLUMN `power2` DECIMAL(12,3) DEFAULT NULL COMMENT '功率2';

5.3 计数器触发器仅8ADPRO使用

DROP TRIGGER IF EXISTS `update_counter_totals_on_insert`;

DELIMITER $$
CREATE TRIGGER `update_counter_totals_on_insert` 
BEFORE INSERT ON `device_data` 
FOR EACH ROW 
BEGIN
    DECLARE last_c1 BIGINT UNSIGNED DEFAULT 0;
    DECLARE last_c2 BIGINT UNSIGNED DEFAULT 0;
    DECLARE proto VARCHAR(16) DEFAULT '8ADPRO';
    
    SELECT IFNULL(protocol_type, '8ADPRO') INTO proto FROM device WHERE id = NEW.device_id LIMIT 1;
    
    -- 仅8ADPRO使用触发器累计
    IF proto = '8ADPRO' OR proto IS NULL THEN
        SELECT IFNULL(counter1_total, 0), IFNULL(counter2_total, 0) INTO last_c1, last_c2
        FROM device_data WHERE device_id = NEW.device_id ORDER BY collected_at DESC LIMIT 1;
        
        SET NEW.counter1_total = IF(NEW.counter1 > 0, last_c1 + NEW.counter1, last_c1);
        SET NEW.counter2_total = IF(NEW.counter2 > 0, last_c2 + NEW.counter2, last_c2);
    END IF;
END$$
DELIMITER ;

六、后端核心代码

6.1 文件清单

yjh-mes/src/main/java/cn/sourceplan/equipment/
├── controller/
│   └── EquipmentInfoController.java    # 设备信息控制器
├── service/
│   ├── IEquipmentInfoService.java      # 服务接口
│   ├── Multi8ProtocolService.java      # 8Multi协议解析服务 ⭐
│   └── impl/
│       └── EquipmentInfoServiceImpl.java # 服务实现
├── domain/
│   ├── MesDevice.java                  # 设备实体
│   └── DeviceData.java                 # 设备数据实体
└── mapper/
    ├── MesDeviceMapper.java            # 设备Mapper
    └── DeviceDataMapper.java           # 数据Mapper

6.2 协议判断入口 (EquipmentInfoServiceImpl.java)

@Override
@Transactional(rollbackFor = Exception.class)
public void ingestFrame(String frameRaw) {
    if (StringUtils.isBlank(frameRaw)) throw new IllegalArgumentException("frame is empty");
    String raw = frameRaw.trim();
    
    // 8Multi: 以{开头且以}结尾(JSON格式)
    if (raw.startsWith("{") && raw.endsWith("}")) {
        log.info("检测到8Multi协议(JSON格式)");
        multi8ProtocolService.parse8MultiJson(raw);
        return;
    }

    // 8AdPro: 以+开头且以EEFF结尾(帧格式)
    if (!raw.startsWith("+") || !raw.endsWith("EEFF")) {
        throw new IllegalArgumentException("协议格式无效");
    }
    
    // ... 8ADPRO处理逻辑
}

6.3 8Multi JSON解析 (Multi8ProtocolService.java)

public void parse8MultiJson(String rawJson) {
    JSONObject json = JSONUtil.parseObj(rawJson);
    
    // 1. 提取设备号ID后4位
    String idStr = json.getStr("id");
    String lastFour = idStr.substring(idStr.length() - 4);
    Integer deviceNo = Integer.valueOf(lastFour);
    
    // 2. 查找或创建设备
    MesDevice device = findOrCreateDevice(deviceNo);
    
    // 3. 构建DeviceData
    DeviceData data = new DeviceData();
    data.setDeviceId(device.getId());
    data.setCollectedAt(new Timestamp(System.currentTimeMillis()));
    data.setFrameRaw(rawJson);
    
    // 4. 字段映射(下位机已换算,直接存储)
    data.setCurrent1Value(parseDecimal(json.getStr("a1")));
    data.setCurrent2Value(parseDecimal(json.getStr("a2")));
    data.setQuality1Value(parseDecimal(json.getStr("q1")));
    data.setQuality2Value(parseDecimal(json.getStr("q2")));
    data.setTemperatureC(parseDecimal(json.getStr("t1")));
    data.setHumidity(parseDecimal(json.getStr("dht1")));
    data.setCounter1(parseInt(json.getStr("c1")));
    data.setCounter2(parseInt(json.getStr("c2")));
    data.setF1(parseDecimal(json.getStr("f1")));
    data.setF2(parseDecimal(json.getStr("f2")));
    data.setCnReg(parseInt(json.getStr("cn_reg")));
    data.setDoReg(json.getStr("do_reg"));
    data.setReg1(parseDecimal(json.getStr("reg1")));
    data.setReg2(parseDecimal(json.getStr("reg2")));
    data.setTv(parseInt(json.getStr("tv")));
    data.setSaveFlag(parseInt(json.getStr("save")));
    data.setOee(parseInt(json.getStr("oee")));
    data.setDt(parseInt(json.getStr("dt")));
    
    // 5. 计数器累计
    DeviceData lastData = deviceDataMapper.selectLastByDeviceId(device.getId());
    Long counter1Total = (lastData != null ? lastData.getCounter1Total() : 0L);
    Long counter2Total = (lastData != null ? lastData.getCounter2Total() : 0L);
    if (data.getCounter1() != null && data.getCounter1() > 0) {
        counter1Total += data.getCounter1();
    }
    if (data.getCounter2() != null && data.getCounter2() > 0) {
        counter2Total += data.getCounter2();
    }
    data.setCounter1Total(counter1Total);
    data.setCounter2Total(counter2Total);
    
    // 6. 保存
    deviceDataMapper.insert(data);
}

6.4 OEE均值查询 (DeviceDataMapper.java)

@Select({
    "<script>",
    "SELECT AVG(oee) FROM device_data ",
    "WHERE device_id = #{deviceId} AND oee IS NOT NULL",
    "<if test='from != null and from != \"\"'>",
    "  AND collected_at &gt;= #{from}",
    "</if>",
    "<if test='to != null and to != \"\"'>",
    "  AND collected_at &lt;= #{to}",
    "</if>",
    "</script>"
})
Double selectOeeAvg(@Param("deviceId") Long deviceId, @Param("from") String from, @Param("to") String to);

七、前端实现

7.1 文件位置

mes-ui/src/views/mes/equipment/info/index.vue    # 设备监控页面
mes-ui/src/api/mes/equipment/info.js             # API接口

7.2 API接口

// 获取设备OEE均值
export function getOeeAvg(deviceId, from, to) {
  return request({
    url: '/equipment/info/oee/avg',
    method: 'get',
    params: { deviceId, from, to }
  })
}

7.3 协议类型判断显示

<!-- 8ADPRO设备显示 -->
<div v-if="!d.protocol_type || d.protocol_type === '8ADPRO'" class="metrics">
  <!-- 温度电流计数器等 -->
</div>

<!-- 8MULTI设备显示 -->
<div v-else-if="d.protocol_type === '8MULTI'" class="metrics">
  <!-- 电流1/2质量1/2温湿度OEE状态等 -->
</div>

7.4 cn_reg状态显示

// 状态文本
cnRegText(val) {
  const map = { 
    0: '计划停机', 1: '正常工作', 2: '待机', 3: '故障', 
    4: '在修', 5: '缺人', 6: '缺料', 7: '清零' 
  }
  return map[val] || '未知'
},

// 状态颜色
cnRegType(val) {
  const map = { 
    0: 'info', 1: 'success', 2: 'warning', 3: 'danger', 
    4: 'warning', 5: 'warning', 6: 'warning', 7: 'info' 
  }
  return map[val] || 'info'
}

7.5 超量程红色警告

// 判断是否超量程
isOutOfRange(value, rangeStart, rangeEnd) {
  if (value === null || value === undefined) return false
  if (rangeStart === null && rangeEnd === null) return false
  if (rangeStart !== null && value < rangeStart) return true
  if (rangeEnd !== null && value > rangeEnd) return true
  return false
}
<!-- 超量程显示红色 -->
<span :style="{color: isOutOfRange(d.current1_value, d.current1RangeStart, d.current1RangeEnd) ? '#F56C6C' : ''}">
  {{ fmt(d.current1_value, 'A', 2) }}
</span>

7.6 设备编辑表单扩展

新增字段:

  • 设备位置 (location)
  • 设备品牌 (brand)
  • 设备型号 (model)
  • 所属车间 (workshopId) - 从md_workshop表读取
  • 所属工序 (section) - 从pro_process表读取
  • 量程配置 (current1/2RangeStart/End, quality1/2RangeStart/End) - 用于超量程警告

八、废弃字段说明

8Multi V2.8协议中不再使用但保留以兼容8ADPRO

废弃字段 替代方案 说明
current1_raw, current2_raw current1_value 下位机已换算
quality1_raw, quality2_raw quality1_value 下位机已换算
status_work/stop/fault/reset cn_reg (0-7) 统一用屏幕状态
frequency1, frequency2 f1, f2 使用新测频字段
counter1_total_8multi counter1_total 统一使用
relay_power/alarm, do_soft_start/stop do_reg 统一用状态输出
slave_data1/2 reg1, reg2 使用新从机数据字段
touchscreen_data tv 使用新显示字段

九、设备OEE功能

9.1 OEE页面改造

将设备OEE页面与8ADPRO/8MULTI设备关联不再使用车间设备。

文件清单

mes-ui/src/views/mes/equipment/oeeGantt/index.vue    # OEE时序图页面
mes-ui/src/api/mes/equipment/equipment.js            # API接口
yjh-mes/.../controller/EquipmentController.java      # 控制器
yjh-mes/.../service/impl/EquipmentServiceImpl.java   # 服务实现
yjh-mes/.../mapper/EquipmentMapper.java              # Mapper接口
yjh-mes/.../resources/mapper/equipment/EquipmentMapper.xml  # SQL

9.2 OEE页面筛选功能

<!-- 协议类型筛选 -->
<el-select v-model="queryParams.deviceType" @change="onDeviceTypeChange">
  <el-option label="全部" value="" />
  <el-option label="8ADPRO" value="8ADPRO" />
  <el-option label="8MULTI" value="8MULTI" />
</el-select>

<!-- 设备筛选 -->
<el-select v-model="queryParams.deviceId" filterable>
  <el-option v-for="d in deviceList" :key="d.id" :label="d.name" :value="d.id" />
</el-select>

9.3 设备列表API

// EquipmentController.java
@GetMapping("/allDevices")
public AjaxResult listAllDevices(String deviceType) {
    return success(equipmentService.listAllDevices(deviceType));
}
<!-- EquipmentMapper.xml -->
<select id="selectMesDevices" resultType="Map">
    SELECT id, device_no, device_name, protocol_type
    FROM device WHERE is_active = 1
    <if test="deviceType != null and deviceType != ''">
        AND protocol_type = #{deviceType}
    </if>
    ORDER BY device_no
</select>

9.4 OEE数据查询

<!-- EquipmentMapper.xml -->
<select id="selectMesDeviceOee" parameterType="Map" resultType="Map">
    SELECT 
        d.id as equipment_id,
        d.device_name as equipment_name,
        d.device_no as equipment_number,
        dd.collected_at as record_time,
        dd.cn_reg as equipment_status,
        dd.oee as oee_value,
        d.protocol_type
    FROM device d
    LEFT JOIN device_data dd ON dd.device_id = d.id
    WHERE d.is_active = 1
      AND dd.collected_at IS NOT NULL
      AND dd.collected_at >= #{map.beginDate}
      AND dd.collected_at <= #{map.endDate}
      <if test="map.deviceType != null and map.deviceType != ''">
        AND d.protocol_type = #{map.deviceType}
      </if>
      <if test="map.deviceId != null and map.deviceId != ''">
        AND d.id = #{map.deviceId}
      </if>
    ORDER BY d.id DESC, dd.collected_at DESC
</select>

9.5 OEE状态映射

cn_reg值映射到OEE图表状态

cn_reg值 含义 OEE状态 颜色
1 正常工作 运行中 绿色(#67C23A)
3 故障 故障 红色(#F56C6C)
0,2,4,5,6,7 其他 停机 灰色(#909399)
// EquipmentServiceImpl.java - 状态转换
Integer cnReg = ((Number)cnRegObj).intValue();
Integer status;
switch (cnReg) {
    case 1: status = 0; break;  // 正常工作 -> 运行中
    case 3: status = 2; break;  // 故障
    default: status = 1; break; // 其他 -> 停机
}

9.6 前端调整

移除的功能

  • 设备卡片上的OEE显示
  • 设备详情中的OEE时段均值查询

保留的功能

  • OEE时序图页面独立页面
  • 设备类型/设备筛选
  • 时序图和饼状图显示
  • 设备状态统计卡片

十、计数器处理差异

协议 counter1/2值 累计方式 触发器
8ADPRO limitToBinary(>0→1) 数据库触发器累加 使用
8MULTI 原始增量值 Java代码累加 不使用

十一、注意事项

  1. 8MULTI协议识别: 根据数据格式判断JSON格式即为8MULTI
  2. 设备号提取: 8MULTI使用ID后4位作为设备号
  3. 量程配置: 8MULTI下位机已换算量程配置仅用于超量程警告显示
  4. 计数器清零: 通过设置baseline实现软清零
  5. OEE页面: 仅显示8ADPRO/8MULTI设备不显示车间设备
  6. OEE状态: 基于cn_reg字段判断1=运行中3=故障,其他=停机)
  7. 兼容性: 所有修改不影响8ADPRO原有功能