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

620 lines
20 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 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. 计数值改变时上发间隔至少60sdt=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({
"<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接口
```javascript
// 获取设备OEE均值
export function getOeeAvg(deviceId, from, to) {
return request({
url: '/equipment/info/oee/avg',
method: 'get',
params: { deviceId, from, to }
})
}
```
### 7.3 协议类型判断显示
```vue
<!-- 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状态显示
```javascript
// 状态文本
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 超量程红色警告
```javascript
// 判断是否超量程
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
}
```
```vue
<!-- 超量程显示红色 -->
<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页面筛选功能
```vue
<!-- 协议类型筛选 -->
<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
```java
// EquipmentController.java
@GetMapping("/allDevices")
public AjaxResult listAllDevices(String deviceType) {
return success(equipmentService.listAllDevices(deviceType));
}
```
```xml
<!-- 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数据查询
```xml
<!-- 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) |
```java
// 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原有功能