package com.mes.interaction.workstation.scanner.handler;
|
|
import com.mes.device.entity.DeviceConfig;
|
import com.mes.device.entity.GlassInfo;
|
import com.mes.device.service.DevicePlcOperationService;
|
import com.mes.device.service.GlassInfoService;
|
import com.mes.device.vo.DevicePlcVO;
|
import com.mes.interaction.workstation.base.WorkstationBaseHandler;
|
import com.mes.interaction.workstation.config.WorkstationLogicConfig;
|
import com.mes.s7.enhanced.EnhancedS7Serializer;
|
import com.mes.s7.provider.S7SerializerProvider;
|
import com.mes.service.PlcDynamicDataService;
|
import com.mes.task.model.TaskExecutionContext;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Component;
|
import org.springframework.util.StringUtils;
|
|
import java.time.LocalDateTime;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* 卧转立扫码设备逻辑处理器
|
* 负责从MES写区读取玻璃尺寸,并落库 glass_info
|
*/
|
@Slf4j
|
@Component
|
public class HorizontalScannerLogicHandler extends WorkstationBaseHandler {
|
|
private static final List<String> MES_FIELDS = Arrays.asList("mesSend", "mesGlassId", "mesWidth", "mesHeight", "workLine");
|
|
private final PlcDynamicDataService plcDynamicDataService;
|
private final GlassInfoService glassInfoService;
|
private final S7SerializerProvider s7SerializerProvider;
|
|
public HorizontalScannerLogicHandler(DevicePlcOperationService devicePlcOperationService,
|
PlcDynamicDataService plcDynamicDataService,
|
GlassInfoService glassInfoService,
|
S7SerializerProvider s7SerializerProvider) {
|
super(devicePlcOperationService);
|
this.plcDynamicDataService = plcDynamicDataService;
|
this.glassInfoService = glassInfoService;
|
this.s7SerializerProvider = s7SerializerProvider;
|
}
|
|
@Override
|
public String getDeviceType() {
|
return DeviceConfig.DeviceType.WORKSTATION_SCANNER;
|
}
|
|
@Override
|
protected DevicePlcVO.OperationResult doExecute(DeviceConfig deviceConfig,
|
String operation,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
|
if (serializer == null) {
|
return buildResult(deviceConfig, operation, false, "获取PLC序列化器失败", null);
|
}
|
|
if ("clearPlc".equalsIgnoreCase(operation) || "reset".equalsIgnoreCase(operation)) {
|
return clearPlc(deviceConfig, serializer);
|
}
|
|
WorkstationLogicConfig config = parseWorkstationConfig(logicParams);
|
return executeScan(deviceConfig, config, serializer, params);
|
}
|
|
private DevicePlcVO.OperationResult executeScan(DeviceConfig deviceConfig,
|
WorkstationLogicConfig config,
|
EnhancedS7Serializer serializer,
|
Map<String, Object> params) {
|
try {
|
// 从参数中获取玻璃ID(定时器每次只处理一个)
|
String inputGlassId = null;
|
if (params != null) {
|
Object glassIdObj = params.get("glassId");
|
if (glassIdObj != null) {
|
inputGlassId = String.valueOf(glassIdObj).trim();
|
}
|
}
|
|
// 执行单次扫描(定时器会循环调用此方法)
|
return executeSingleScan(deviceConfig, config, serializer, inputGlassId, params);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
log.warn("卧转立扫码等待MES数据被中断, deviceId={}", deviceConfig.getId(), e);
|
return buildResult(deviceConfig, "scanOnce", false, "等待MES数据被中断", null);
|
} catch (Exception e) {
|
log.error("卧转立扫码处理异常, deviceId={}", deviceConfig.getId(), e);
|
return buildResult(deviceConfig, "scanOnce", false, "处理异常: " + e.getMessage(), null);
|
}
|
}
|
|
/**
|
* 执行单次扫描
|
*/
|
private DevicePlcVO.OperationResult executeSingleScan(DeviceConfig deviceConfig,
|
WorkstationLogicConfig config,
|
EnhancedS7Serializer serializer,
|
String inputGlassId,
|
Map<String, Object> params) throws InterruptedException {
|
// 1. 写入plcRequest=1和plcGlassId(如果提供了玻璃ID)
|
triggerScanRequest(deviceConfig, serializer, inputGlassId);
|
|
// 2. 等待MES回写mesSend=1以及玻璃信息
|
Map<String, Object> mesData = waitForMesData(deviceConfig, serializer, config);
|
if (mesData == null || mesData.isEmpty()) {
|
log.error("等待MES写入玻璃信息超时: deviceId={}, timeout={}ms",
|
deviceConfig.getId(), config.getScanIntervalMs());
|
return buildResult(deviceConfig, "scanOnce", false,
|
String.format("等待MES写入玻璃信息超时(%dms)", config.getScanIntervalMs()), null);
|
}
|
|
// 3. 读取MES回写的玻璃信息
|
String glassId = parseString(mesData.get("mesGlassId"));
|
if (!StringUtils.hasText(glassId)) {
|
return buildResult(deviceConfig, "scanOnce", false, "MES写区未提供玻璃ID", null);
|
}
|
// 读取MES尺寸数据:mesWidth=表宽,mesHeight=长
|
Integer rawWidth = parseInteger(mesData.get("mesWidth"));
|
Integer rawHeight = parseInteger(mesData.get("mesHeight"));
|
Integer workLine = parseInteger(mesData.get("workLine"));
|
|
// 4. 清空plcRequest和plcGlassId(只清除PLC字段)
|
clearPlcRequestFields(deviceConfig, serializer);
|
|
|
// 5. 保存玻璃信息到数据库
|
GlassInfo glassInfo = buildGlassInfo(glassId, rawWidth, rawHeight, workLine);
|
boolean saved = glassInfoService.saveOrUpdateGlassInfo(glassInfo);
|
if (!saved) {
|
return buildResult(deviceConfig, "scanOnce", false, "保存玻璃信息失败: " + glassId, null);
|
}
|
|
// 6. 将扫描到的玻璃ID保存到共享数据中(供大车设备定时器读取)
|
saveScannedGlassId(params, glassId);
|
|
String msg = String.format("玻璃[%s] 尺寸[表宽:%s x 长:%s] 已接收并入库,workLine=%s",
|
glassId,
|
rawWidth != null ? rawWidth + "mm" : "-",
|
rawHeight != null ? rawHeight + "mm" : "-",
|
workLine != null ? workLine : "-");
|
Map<String, Object> resultData = new HashMap<>();
|
resultData.put("glassIds", Collections.singletonList(glassId));
|
if (workLine != null) {
|
resultData.put("workLine", workLine);
|
}
|
return buildResult(deviceConfig, "scanOnce", true, msg, resultData);
|
}
|
|
/**
|
* 设置暂停标志(供大车设备调用)
|
*/
|
public static void setPauseFlag(TaskExecutionContext context, boolean pause) {
|
if (context != null) {
|
context.getSharedData().put("scannerPause", pause);
|
}
|
}
|
|
/**
|
* 保存扫描到的玻璃ID到共享数据中
|
*/
|
@SuppressWarnings("unchecked")
|
private void saveScannedGlassId(Map<String, Object> params, String glassId) {
|
if (params == null || !StringUtils.hasText(glassId)) {
|
return;
|
}
|
|
Object contextObj = params.get("_taskContext");
|
if (contextObj instanceof TaskExecutionContext) {
|
TaskExecutionContext context = (TaskExecutionContext) contextObj;
|
List<String> scannedGlassIds = (List<String>) context.getSharedData()
|
.computeIfAbsent("scannedGlassIds", k -> new java.util.ArrayList<>());
|
if (!scannedGlassIds.contains(glassId)) {
|
scannedGlassIds.add(glassId);
|
log.debug("已保存扫描到的玻璃ID到共享数据: glassId={}", glassId);
|
}
|
}
|
}
|
|
private DevicePlcVO.OperationResult clearPlc(DeviceConfig deviceConfig,
|
EnhancedS7Serializer serializer) {
|
try {
|
// 只清空PLC操作区字段(plcRequest、plcGlassId),不清空MES写区字段
|
Map<String, Object> resetFields = new HashMap<>();
|
resetFields.put("plcRequest", 0);
|
resetFields.put("plcGlassId", "");
|
plcDynamicDataService.writePlcData(deviceConfig, resetFields, serializer);
|
return buildResult(deviceConfig, "clearPlc", true, "已清空PLC操作区字段(保留MES写区字段)", null);
|
} catch (Exception e) {
|
log.error("卧转立扫码清空PLC失败, deviceId={}", deviceConfig.getId(), e);
|
return buildResult(deviceConfig, "clearPlc", false, "清空PLC失败: " + e.getMessage(), null);
|
}
|
}
|
|
/**
|
* 触发MES请求:写入plcRequest=1和plcGlassId(如果提供了玻璃ID)
|
*/
|
private void triggerScanRequest(DeviceConfig deviceConfig, EnhancedS7Serializer serializer, String glassId) {
|
Map<String, Object> writeFields = new HashMap<>();
|
writeFields.put("plcRequest", 1);
|
|
if (StringUtils.hasText(glassId)) {
|
writeFields.put("plcGlassId", glassId);
|
}
|
|
plcDynamicDataService.writePlcData(deviceConfig, writeFields, serializer);
|
}
|
|
/**
|
* 清空PLC请求字段:只清除plcRequest和plcGlassId(不清除MES写区字段)
|
*/
|
private void clearPlcRequestFields(DeviceConfig deviceConfig, EnhancedS7Serializer serializer) {
|
try {
|
Map<String, Object> clearFields = new HashMap<>();
|
clearFields.put("plcRequest", 0);
|
clearFields.put("plcGlassId", "");
|
plcDynamicDataService.writePlcData(deviceConfig, clearFields, serializer);
|
} catch (Exception e) {
|
log.error("清空PLC请求字段失败: deviceId={}", deviceConfig.getId(), e);
|
// 不清空不影响主流程,只记录错误
|
}
|
}
|
|
private Map<String, Object> waitForMesData(DeviceConfig deviceConfig,
|
EnhancedS7Serializer serializer,
|
WorkstationLogicConfig config) throws InterruptedException {
|
long timeoutMs = Math.max(config.getScanIntervalMs(), 3_000);
|
long deadline = System.currentTimeMillis() + timeoutMs;
|
int pollInterval = Math.max(200, Math.min(config.getScanIntervalMs() / 5, 1_000));
|
|
while (System.currentTimeMillis() < deadline) {
|
Map<String, Object> mesData = plcDynamicDataService.readPlcData(deviceConfig, MES_FIELDS, serializer);
|
|
if (mesData != null && !mesData.isEmpty()) {
|
Integer mesSend = parseInteger(mesData.get("mesSend"));
|
if (mesSend != null && mesSend == 1) {
|
log.info("检测到MES已写入数据: deviceId={}, mesSend=1, mesGlassId={}, mesWidth={}, mesHeight={}, workLine={}",
|
deviceConfig.getId(),
|
mesData.get("mesGlassId"),
|
mesData.get("mesWidth"),
|
mesData.get("mesHeight"),
|
mesData.get("workLine"));
|
return mesData;
|
}
|
}
|
|
Thread.sleep(pollInterval);
|
}
|
|
// 超时前最后一次尝试读取
|
log.warn("等待MES数据超时: deviceId={}, timeout={}ms", deviceConfig.getId(), timeoutMs);
|
Map<String, Object> lastMesData = plcDynamicDataService.readPlcData(deviceConfig, MES_FIELDS, serializer);
|
if (lastMesData != null && !lastMesData.isEmpty()) {
|
log.warn("超时前最后一次读取到的数据: deviceId={}, mesData={}",
|
deviceConfig.getId(), lastMesData);
|
}
|
|
return Collections.emptyMap();
|
}
|
|
private GlassInfo buildGlassInfo(String glassId, Integer width, Integer height, Integer workLine) {
|
GlassInfo glassInfo = new GlassInfo();
|
glassInfo.setGlassId(glassId.trim());
|
// mesWidth=表宽 -> glassWidth, mesHeight=长 -> glassLength
|
if (width != null) {
|
glassInfo.setGlassWidth(width); // 表宽
|
}
|
if (height != null) {
|
glassInfo.setGlassLength(height); // 长
|
}
|
glassInfo.setStatus(GlassInfo.Status.ACTIVE);
|
if (workLine != null) {
|
glassInfo.setDescription("workLine=" + workLine);
|
}
|
return glassInfo;
|
}
|
|
private Integer parseInteger(Object value) {
|
if (value instanceof Number) {
|
return ((Number) value).intValue();
|
}
|
if (value == null) {
|
return null;
|
}
|
try {
|
return Integer.parseInt(String.valueOf(value));
|
} catch (NumberFormatException e) {
|
return null;
|
}
|
}
|
|
private String parseString(Object value) {
|
return value == null ? null : String.valueOf(value).trim();
|
}
|
|
private DevicePlcVO.OperationResult buildResult(DeviceConfig deviceConfig,
|
String operation,
|
boolean success,
|
String message,
|
Map<String, Object> data) {
|
return DevicePlcVO.OperationResult.builder()
|
.deviceId(deviceConfig.getId())
|
.deviceName(deviceConfig.getDeviceName())
|
.deviceCode(deviceConfig.getDeviceCode())
|
.projectId(deviceConfig.getProjectId() != null ? String.valueOf(deviceConfig.getProjectId()) : null)
|
.operation(operation)
|
.success(success)
|
.message(message)
|
.timestamp(LocalDateTime.now())
|
.data(data)
|
.build();
|
}
|
}
|