Files
MES/yawei-mes/.tasks/2025-11-29_v1.0.30_8multi接入优化.md

620 lines
20 KiB
Markdown
Raw Permalink Normal View History

2026-04-02 10:38:23 +08:00
# 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原有功能