# 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格式 ```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. 计数值改变时上发(间隔至少60s,dt=60) 5. 不接计数器时300s发一次(dt=300) ``` --- ## 五、数据库设计 ### 5.1 device表扩展字段 ```sql -- 协议类型 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表扩展字段 ```sql -- 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使用) ```sql 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) ```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) ```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) ```java @Select({ "" }) 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接口 ```javascript // 获取设备OEE均值 export function getOeeAvg(deviceId, from, to) { return request({ url: '/equipment/info/oee/avg', method: 'get', params: { deviceId, from, to } }) } ``` ### 7.3 协议类型判断显示 ```vue