add:新增光栅驱动以及科聪暂停和恢复车辆接口
This commit is contained in:
@@ -451,6 +451,16 @@
|
||||
<artifactId>xercesImpl</artifactId>
|
||||
<version>2.12.0</version>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>5.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.digitalpetri.modbus</groupId>
|
||||
<artifactId>modbus-master-tcp</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -34,10 +34,17 @@ public class KeCongAgvController {
|
||||
return new ResponseEntity<>(keCongAgvService.applyIn(requestParam), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/requestResource")
|
||||
@Log("请求资源")
|
||||
@SaIgnore
|
||||
public ResponseEntity<Object> requestResource(@RequestBody JSONObject requestParam) {
|
||||
return new ResponseEntity<>(keCongAgvService.requestResource(requestParam), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/releaseResource")
|
||||
@Log("释放资源")
|
||||
@SaIgnore
|
||||
public ResponseEntity<AgvResponse> releaseResource(@RequestBody JSONObject requestParam) {
|
||||
public ResponseEntity<Object> releaseResource(@RequestBody JSONObject requestParam) {
|
||||
return new ResponseEntity<>(keCongAgvService.releaseResource(requestParam), HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,10 +39,16 @@ public interface KeCongAgvService {
|
||||
|
||||
JSONObject applyIn(JSONObject requestParam);
|
||||
|
||||
AgvResponse releaseResource(JSONObject requestParam);
|
||||
JSONObject releaseResource(JSONObject requestParam);
|
||||
|
||||
JSONObject requestAction(JSONObject requestParam);
|
||||
|
||||
HttpResponse pause(String device_code) throws Exception;
|
||||
|
||||
HttpResponse resume(String device_code) throws Exception;
|
||||
|
||||
JSONObject requestResource(JSONObject requestParam);
|
||||
|
||||
// HttpResponse getRobotInfo(String robotCode);
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,11 +15,14 @@ import org.nl.acs.agv.server.KeCongAgvService;
|
||||
import org.nl.acs.agv.server.MagicAgvService;
|
||||
import org.nl.acs.agv.server.dao.AgvResponse;
|
||||
import org.nl.acs.common.base.CommonFinalParam;
|
||||
import org.nl.acs.device.domain.Device;
|
||||
import org.nl.acs.device.service.DeviceService;
|
||||
import org.nl.acs.device.service.impl.DeviceServiceImpl;
|
||||
import org.nl.acs.device_driver.raster.RasterDeviceDriver;
|
||||
import org.nl.acs.ext.wms.service.AcsToWmsService;
|
||||
import org.nl.acs.instruction.domain.Instruction;
|
||||
import org.nl.acs.instruction.service.InstructionService;
|
||||
import org.nl.acs.opc.DeviceAppService;
|
||||
import org.nl.acs.storage_cell.domain.StorageCell;
|
||||
import org.nl.acs.storage_cell.service.mapper.StorageCellMapper;
|
||||
import org.nl.acs.task.enums.ActionTypeEnum;
|
||||
@@ -53,6 +56,7 @@ public class KeCongAgvServiceImpl implements KeCongAgvService {
|
||||
private TaskService taskserver;
|
||||
@Autowired
|
||||
private StorageCellMapper storageCellMapper;
|
||||
private final DeviceAppService deviceAppService;
|
||||
|
||||
@Override
|
||||
public HttpResponse deleteTask(String instCode) throws Exception {
|
||||
@@ -386,11 +390,128 @@ public class KeCongAgvServiceImpl implements KeCongAgvService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgvResponse releaseResource(JSONObject requestParam) {
|
||||
AgvResponse agvResponse = new AgvResponse();
|
||||
agvResponse.setResult("true");
|
||||
agvResponse.setErrMsg("请求失败");
|
||||
return agvResponse;
|
||||
public JSONObject releaseResource(JSONObject requestParam) {
|
||||
log.info("agv申请离开,请求参数:" + requestParam);
|
||||
String resourceID = requestParam.getString("point_code");
|
||||
String vehicleNo = requestParam.getString("VehicleNo");
|
||||
LuceneLogDto logDto1 = LuceneLogDto.builder()
|
||||
.device_code(vehicleNo)
|
||||
.content("agv申请离开,请求参数:"+ requestParam)
|
||||
.build();
|
||||
logDto1.setLog_level(4);
|
||||
luceneExecuteLogService.deviceExecuteLog(logDto1);
|
||||
Device device = deviceAppService.findDeviceByCode(resourceID);
|
||||
RasterDeviceDriver rasterDeviceDriver = (RasterDeviceDriver) device.getDeviceDriver();
|
||||
rasterDeviceDriver.setOption(1);
|
||||
JSONObject jo = new JSONObject();
|
||||
jo.put("code", "200");
|
||||
jo.put("message", "请求成功");
|
||||
LuceneLogDto logDto2 = LuceneLogDto.builder()
|
||||
.device_code(vehicleNo)
|
||||
.content("agv申请离开成功,返回agv参数:" + jo)
|
||||
.build();
|
||||
logDto2.setLog_level(4);
|
||||
luceneExecuteLogService.deviceExecuteLog(logDto2);
|
||||
return jo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse pause(String device_code) throws Exception {
|
||||
|
||||
if (StrUtil.equals(paramService.findByCode(AcsConfig.FORKAGV).
|
||||
|
||||
getValue(), CommonFinalParam.ONE)) {
|
||||
|
||||
Device device = deviceAppService.findDeviceByCode(device_code);
|
||||
RasterDeviceDriver rasterDeviceDriver = (RasterDeviceDriver) device.getDeviceDriver();
|
||||
if (rasterDeviceDriver.getOption() == 1) {
|
||||
String car_no = device.getCar_no();
|
||||
String agvurl = paramService.findByCode(AcsConfig.AGVURL).getValue();
|
||||
String agvport = paramService.findByCode(AcsConfig.AGVPORT).getValue();
|
||||
|
||||
|
||||
agvurl = agvurl + ":" + agvport + "/api/fms/writeVariable";
|
||||
log.info("暂停agv请求:{}", agvurl);
|
||||
|
||||
JSONObject ja = new JSONObject();
|
||||
ja.put("VehicleNo", car_no);
|
||||
ja.put("VariableValue", "1");
|
||||
HttpResponse result = HttpRequest.post(agvurl)
|
||||
//表单内容
|
||||
.body(String.valueOf(ja))
|
||||
//超时,毫秒
|
||||
.timeout(20000)
|
||||
.execute();
|
||||
log.info("暂停agv请求反馈:{}", result);
|
||||
return result;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse resume(String device_code) throws Exception {
|
||||
|
||||
if (StrUtil.equals(paramService.findByCode(AcsConfig.FORKAGV).
|
||||
|
||||
getValue(), CommonFinalParam.ONE)) {
|
||||
Device device = deviceAppService.findDeviceByCode(device_code);
|
||||
RasterDeviceDriver rasterDeviceDriver = (RasterDeviceDriver) device.getDeviceDriver();
|
||||
if (rasterDeviceDriver.getOption() == 1) {
|
||||
String car_no = device.getCar_no();
|
||||
String agvurl = paramService.findByCode(AcsConfig.AGVURL).getValue();
|
||||
String agvport = paramService.findByCode(AcsConfig.AGVPORT).getValue();
|
||||
|
||||
agvurl = agvurl + ":" + agvport + "/api/fms/writeVariable";
|
||||
log.info("恢复所有agv请求:{}", agvurl);
|
||||
|
||||
JSONObject ja = new JSONObject();
|
||||
ja.put("VehicleNo", car_no);
|
||||
ja.put("VariableValue", "2");
|
||||
HttpResponse result = HttpRequest.post(agvurl)
|
||||
//表单内容
|
||||
.body(String.valueOf(ja))
|
||||
//超时,毫秒
|
||||
.timeout(20000)
|
||||
.execute();
|
||||
log.info("恢复所有agv请求反馈:{}", result);
|
||||
|
||||
return result;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject requestResource(JSONObject requestParam) {
|
||||
log.info("agv申请进入,请求参数:" + requestParam);
|
||||
String resourceID = requestParam.getString("point_code");
|
||||
String vehicleNo = requestParam.getString("VehicleNo");
|
||||
LuceneLogDto logDto1 = LuceneLogDto.builder()
|
||||
.device_code(vehicleNo)
|
||||
.content("agv申请进入,请求参数:"+ requestParam)
|
||||
.build();
|
||||
logDto1.setLog_level(4);
|
||||
luceneExecuteLogService.deviceExecuteLog(logDto1);
|
||||
Device device = deviceAppService.findDeviceByCode(resourceID);
|
||||
RasterDeviceDriver rasterDeviceDriver = (RasterDeviceDriver) device.getDeviceDriver();
|
||||
rasterDeviceDriver.setOption(1);
|
||||
JSONObject jo = new JSONObject();
|
||||
jo.put("code", "200");
|
||||
jo.put("message", "请求成功");
|
||||
LuceneLogDto logDto2 = LuceneLogDto.builder()
|
||||
.device_code(vehicleNo)
|
||||
.content("agv申请进入成功,返回agv参数:" + jo)
|
||||
.build();
|
||||
logDto2.setLog_level(4);
|
||||
luceneExecuteLogService.deviceExecuteLog(logDto2);
|
||||
return jo;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -113,6 +113,12 @@ public class Device implements Serializable {
|
||||
@TableField(exist = false)
|
||||
private String barrels_status;
|
||||
|
||||
/**
|
||||
* 进入当前区域的车号
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String car_no;
|
||||
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String device_id;
|
||||
|
||||
@@ -60,6 +60,8 @@ public interface OpcDeviceDriver extends DeviceDriver {
|
||||
|
||||
dto.setItem_code(json.getString("extra_code"));
|
||||
dto.setItem_value(json.getString("extra_value"));
|
||||
dto.setItem_value_type(json.getString("value_type"));
|
||||
dto.setItem_name(json.getString("extra_name"));
|
||||
returns.add(dto);
|
||||
}
|
||||
return returns;
|
||||
@@ -107,10 +109,15 @@ public interface OpcDeviceDriver extends DeviceDriver {
|
||||
* @return
|
||||
*/
|
||||
default Integer getIntegeregerValue(String protocol) {
|
||||
if (ObjectUtil.isEmpty(this.getValue(protocol)) && "heartbeat".equals(protocol)) {
|
||||
return null;
|
||||
Object value = this.getValue(protocol);
|
||||
if (value instanceof Integer) {
|
||||
return (Integer) value;
|
||||
} else if (value instanceof Short) {
|
||||
return ((Short) value).intValue();
|
||||
} else if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return (Integer) (ObjectUtil.isEmpty(this.getValue(protocol)) ? 0 : this.getValue(protocol));
|
||||
throw new ClassCastException("Cannot cast " + value.getClass().getName() + " to Integer");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.nl.acs.device_driver.raster;
|
||||
|
||||
import org.nl.acs.device.device_driver.standard_inspect.ItemDto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ItemProtocol {
|
||||
public static String item_raster = "raster";
|
||||
private RasterDeviceDriver driver;
|
||||
|
||||
public ItemProtocol(RasterDeviceDriver driver){
|
||||
this.driver=driver;
|
||||
}
|
||||
|
||||
public int getItem_raster(){
|
||||
return this.getOpcIntegerValue(item_raster);
|
||||
}
|
||||
|
||||
Boolean isonline;
|
||||
Boolean isError;
|
||||
|
||||
public int getOpcIntegerValue(String protocol) {
|
||||
Integer value = this.driver.getIntegeregerValue(protocol);
|
||||
if (value == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ItemDto> getReadableItemDtos() {
|
||||
ArrayList list = new ArrayList();
|
||||
list.add(new ItemDto(item_raster, "光栅信号", "31009"));
|
||||
return list;
|
||||
}
|
||||
|
||||
public static List<ItemDto> getWriteableItemDtos() {
|
||||
ArrayList<ItemDto> list = new ArrayList<>();
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.nl.acs.device_driver.raster;
|
||||
|
||||
import org.nl.acs.device.device_driver.standard_inspect.ItemDto;
|
||||
import org.nl.acs.device.domain.Device;
|
||||
import org.nl.acs.device.enums.DeviceType;
|
||||
import org.nl.acs.device_driver.DeviceDriver;
|
||||
import org.nl.acs.device_driver.defination.OpcDeviceDriverDefination;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class RasterDefination implements OpcDeviceDriverDefination {
|
||||
@Override
|
||||
public String getDriverCode() {
|
||||
return "raster";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDriverName() {
|
||||
return "光栅站点";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDriverDescription() {
|
||||
return "光栅站点";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceDriver getDriverInstance(Device device) {
|
||||
return (new RasterDeviceDriver()).setDevice(device).setDriverDefination(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends DeviceDriver> getDeviceDriverType() {
|
||||
return RasterDeviceDriver.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceType> getFitDeviceTypes() {
|
||||
List<DeviceType> types = new LinkedList();
|
||||
types.add(DeviceType.conveyor);
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemDto> getReadableItemDtos() {
|
||||
return ItemProtocol.getReadableItemDtos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemDto> getWriteableItemDtos() {
|
||||
return ItemProtocol.getWriteableItemDtos();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.nl.acs.device_driver.raster;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nl.acs.agv.server.KeCongAgvService;
|
||||
import org.nl.acs.agv.server.impl.KeCongAgvServiceImpl;
|
||||
import org.nl.acs.device.domain.Device;
|
||||
import org.nl.acs.device_driver.DeviceDriver;
|
||||
import org.nl.acs.device_driver.RouteableDeviceDriver;
|
||||
import org.nl.acs.device_driver.driver.AbstractOpcDeviceDriver;
|
||||
import org.nl.acs.device_driver.driver.ExecutableDeviceDriver;
|
||||
|
||||
import org.nl.acs.ext.wms.service.AcsToWmsService;
|
||||
import org.nl.acs.ext.wms.service.impl.AcsToWmsServiceImpl;
|
||||
import org.nl.acs.monitor.DeviceStageMonitor;
|
||||
import org.nl.config.SpringContextHolder;
|
||||
|
||||
import org.nl.config.lucene.service.LuceneExecuteLogService;
|
||||
import org.nl.config.lucene.service.dto.LuceneLogDto;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class RasterDeviceDriver extends AbstractOpcDeviceDriver implements DeviceDriver, ExecutableDeviceDriver, RouteableDeviceDriver, DeviceStageMonitor {
|
||||
protected ItemProtocol itemProtocol = new ItemProtocol(this);
|
||||
private final LuceneExecuteLogService logService = SpringContextHolder.getBean(LuceneExecuteLogService.class);
|
||||
@Autowired
|
||||
AcsToWmsService acsToWmsService = SpringContextHolder.getBean(AcsToWmsServiceImpl.class);
|
||||
@Autowired
|
||||
KeCongAgvService keCongAgvService = SpringContextHolder.getBean(KeCongAgvServiceImpl.class);
|
||||
|
||||
|
||||
/**
|
||||
* 当前设备号
|
||||
*/
|
||||
private String device_code;
|
||||
|
||||
// 1 上位系统允许进入 2 上位系统允许离开
|
||||
int option = 0;
|
||||
/**
|
||||
* 光栅信号
|
||||
*/
|
||||
private int raster = 0;
|
||||
private int last_raster = 0;
|
||||
|
||||
@Override
|
||||
public Device getDevice() {
|
||||
return this.device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
this.device_code = this.getDevice().getDevice_code();
|
||||
this.raster = this.itemProtocol.getItem_raster();
|
||||
|
||||
|
||||
if (this.raster != this.last_raster) {
|
||||
logService.deviceExecuteLog(new LuceneLogDto(this.device_code, "自动线程读取信号: 光栅信号,由" + this.last_raster + "->" + this.raster));
|
||||
}
|
||||
|
||||
if (this.raster == 1) {
|
||||
keCongAgvService.pause(device_code);
|
||||
}
|
||||
if (this.raster == 0) {
|
||||
keCongAgvService.resume(device_code);
|
||||
}
|
||||
last_raster = raster;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JSONObject getDeviceStatusName() {
|
||||
JSONObject jo = new JSONObject();
|
||||
String action = "";
|
||||
if (this.getRaster() == 0) {
|
||||
action = "光栅关闭";
|
||||
} else if (this.getRaster() == 1) {
|
||||
action = "光栅开启";
|
||||
}
|
||||
jo.put("action", action);
|
||||
return jo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceStatus(JSONObject data) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,941 @@
|
||||
package org.nl.acs.opc;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.digitalpetri.modbus.master.ModbusTcpMaster;
|
||||
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
|
||||
import com.digitalpetri.modbus.requests.*;
|
||||
import com.digitalpetri.modbus.responses.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nl.acs.opc.service.dto.OpcServerManageDto;
|
||||
import org.nl.acs.udw.UnifiedDataAccessor;
|
||||
import org.nl.acs.udw.UnifiedDataAccessorFactory;
|
||||
import org.nl.acs.udw.UnifiedDataAppService;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class DeviceModbusTcpProtocolRunnable implements Runnable {
|
||||
private List<OpcItemDto> protocols;
|
||||
private OpcServerManageDto opcServer;
|
||||
private ModbusTcpMaster modbusMaster;
|
||||
private Map<String, OpcItemDto> itemSearchCache;
|
||||
|
||||
/**
|
||||
* 失败地址跟踪:记录每个地址的连续失败次数和最后失败时间
|
||||
* key: itemCode, value: FailureInfo
|
||||
*/
|
||||
private Map<String, FailureInfo> failureTracker;
|
||||
|
||||
/**
|
||||
* 失败信息
|
||||
*/
|
||||
private static class FailureInfo {
|
||||
int consecutiveFailures; // 连续失败次数
|
||||
long lastFailureTime; // 最后失败时间
|
||||
long skipUntilTime; // 跳过直到此时间
|
||||
|
||||
FailureInfo() {
|
||||
this.consecutiveFailures = 0;
|
||||
this.lastFailureTime = 0;
|
||||
this.skipUntilTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus读取项配置
|
||||
*/
|
||||
private static class ModbusItemConfig {
|
||||
String itemCode;
|
||||
int address;
|
||||
String dataType;
|
||||
int registerType;
|
||||
|
||||
ModbusItemConfig(String itemCode, int address, String dataType, int registerType) {
|
||||
this.itemCode = itemCode;
|
||||
this.address = address;
|
||||
this.dataType = dataType;
|
||||
this.registerType = registerType;
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceModbusTcpProtocolRunnable() {
|
||||
this.itemSearchCache = new ConcurrentHashMap<>();
|
||||
this.failureTracker = new ConcurrentHashMap<>();
|
||||
this.modbusMaster = null;
|
||||
}
|
||||
|
||||
public void setProtocols(List<OpcItemDto> protocols) {
|
||||
this.protocols = protocols;
|
||||
}
|
||||
|
||||
public void setOpcServer(OpcServerManageDto opcServer) {
|
||||
this.opcServer = opcServer;
|
||||
}
|
||||
|
||||
private OpcItemDto getItem(String item) {
|
||||
OpcItemDto x = this.itemSearchCache.get(item);
|
||||
if (x == null) {
|
||||
for (OpcItemDto dto : this.protocols) {
|
||||
if (StrUtil.equals(item, dto.getItem_code())) {
|
||||
x = dto;
|
||||
this.itemSearchCache.put(item, dto);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.runModbusTcp();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 断开Modbus连接
|
||||
*/
|
||||
private void disconnectModbus(String reason) {
|
||||
if (this.modbusMaster != null) {
|
||||
try {
|
||||
modbusMaster.disconnect();
|
||||
log.info("ModbusTCP连接已断开: {}", reason);
|
||||
} catch (Exception e) {
|
||||
log.error("断开ModbusTCP连接失败", e);
|
||||
}
|
||||
this.modbusMaster = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void runModbusTcp() {
|
||||
while (true) {
|
||||
try {
|
||||
// 断开之前的连接
|
||||
disconnectModbus("准备重新连接");
|
||||
String[] host_port = this.opcServer.getOpc_host().split(":");
|
||||
|
||||
ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder(host_port[0])
|
||||
.setPort(Integer.parseInt(host_port[1]))
|
||||
.setTimeout(Duration.ofSeconds(OpcConfig.modbus_tcp_timeout_seconds))
|
||||
.build();
|
||||
|
||||
this.modbusMaster = new ModbusTcpMaster(config);
|
||||
this.modbusMaster.connect();
|
||||
|
||||
log.info("ModbusTCP连接成功: {}", this.opcServer.getOpc_host());
|
||||
|
||||
List<String> itemsString = new ArrayList<>();
|
||||
List<ModbusItemConfig> itemConfigs = new ArrayList<>();
|
||||
|
||||
for (OpcItemDto protocol : this.protocols) {
|
||||
String itemCode = protocol.getItem_code();
|
||||
itemsString.add(itemCode);
|
||||
|
||||
try {
|
||||
ModbusItemConfig itemConfig = parseSimpleConfig(protocol);
|
||||
if (itemConfig != null) {
|
||||
itemConfigs.add(itemConfig);
|
||||
log.trace("添加读取项: {} -> 地址:{}, 类型:{}", itemCode, itemConfig.address, itemConfig.dataType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析配置失败: {}, 错误: {}", itemCode, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!OpcStartTag.is_run) {
|
||||
OpcStartTag.is_run = true;
|
||||
}
|
||||
|
||||
UnifiedDataAccessor accessor_value = UnifiedDataAccessorFactory.getAccessor(OpcConfig.udw_opc_value_key);
|
||||
|
||||
long lastStatisticsTime = System.currentTimeMillis();
|
||||
while (DeviceModbusTcpSynchronizeAutoRun.isRun) {
|
||||
Map<String, Object> dataMap = batchReadModbus(itemConfigs);
|
||||
for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
|
||||
String itemId = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
Object his = accessor_value.getValue(itemId);
|
||||
// 检查数值是否变化
|
||||
if (!UnifiedDataAppService.isEquals(value, his)) {
|
||||
OpcItemDto itemDto = this.getItem(itemId);
|
||||
this.logItemChanged(itemId, accessor_value, value, itemDto);
|
||||
if (!ObjectUtil.isEmpty(value) || "".equals(value)) {
|
||||
accessor_value.setValue(itemId, value);
|
||||
}
|
||||
if (ObjectUtil.isEmpty(value) && !"".equals(value)) {
|
||||
accessor_value.removeValue(itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 每5分钟打印一次失败地址统计
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastStatisticsTime > 300000) { // 5分钟
|
||||
logFailureStatistics();
|
||||
lastStatisticsTime = currentTime;
|
||||
}
|
||||
|
||||
// 等待读取间隔时间,避免频繁读取
|
||||
ThreadUtl.sleep((long) OpcConfig.synchronized_millisecond);
|
||||
}
|
||||
|
||||
// 正常退出时断开连接
|
||||
disconnectModbus("正常退出");
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
log.error("ModbusTCP连接异常: {}", e.getMessage(), e);
|
||||
disconnectModbus("异常处理");
|
||||
|
||||
if (!DeviceModbusTcpSynchronizeAutoRun.isRun) {
|
||||
log.warn("ModbusTCP线程停止(收到停止信号)");
|
||||
return;
|
||||
}
|
||||
log.info("{}秒后尝试重新连接...", OpcConfig.synchronized_exception_wait_second);
|
||||
ThreadUtl.sleep((OpcConfig.synchronized_exception_wait_second * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModbusItemConfig parseSimpleConfig(OpcItemDto protocol) {
|
||||
try {
|
||||
String itemCode = protocol.getItem_code();
|
||||
String itemName = protocol.getItem_name();
|
||||
String itemValueType = protocol.getItem_value_type();
|
||||
|
||||
if (StrUtil.isEmpty(itemCode) || StrUtil.isEmpty(itemName)) {
|
||||
log.error("配置错误: item_code或item_name为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 解析Modbus地址,确定寄存器类型,转换为实际地址
|
||||
int address = Integer.parseInt(itemName.trim());
|
||||
int registerType = getRegisterTypeFromAddress(address);
|
||||
int actualAddress = convertToActualAddress(address, registerType);
|
||||
return new ModbusItemConfig(itemCode, actualAddress, itemValueType, registerType);
|
||||
} catch (Exception e) {
|
||||
log.error("解析配置失败: {}, 错误: {}", protocol.getItem_code(), e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modbus标准地址约定:
|
||||
* 0xxxx (00001-09999): 线圈 (Coils)
|
||||
* 1xxxx (10001-19999): 离散输入 (Discrete Inputs)
|
||||
* 3xxxx (30001-39999): 输入寄存器 (Input Registers)
|
||||
* 4xxxx (40001-49999): 保持寄存器 (Holding Registers)
|
||||
* 1、线圈
|
||||
* 2、离散输入
|
||||
* 3、保持寄存器
|
||||
* 4、输入寄存器
|
||||
*/
|
||||
private int getRegisterTypeFromAddress(int address) {
|
||||
if (address >= 40001 && address <= 49999) {
|
||||
return 3;
|
||||
} else if (address >= 30001 && address <= 39999) {
|
||||
return 4;
|
||||
} else if (address >= 10001 && address <= 19999) {
|
||||
return 2;
|
||||
} else if (address >= 1 && address <= 9999) {
|
||||
return 1;
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为实际地址
|
||||
* 如果是标准Modbus地址,减去偏移量
|
||||
* 1、线圈
|
||||
* 2、离散输入
|
||||
* 3、保持寄存器
|
||||
* 4、输入寄存器
|
||||
*/
|
||||
private int convertToActualAddress(int address, int registerType) {
|
||||
switch (registerType) {
|
||||
case 3:
|
||||
return address >= 40001 ? address - 40001 : address;
|
||||
case 4:
|
||||
return address >= 30001 ? address - 30001 : address;
|
||||
case 2:
|
||||
return address >= 10001 ? address - 10001 : address;
|
||||
case 1:
|
||||
return address >= 1 && address <= 9999 ? address - 1 : address;
|
||||
default:
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部地址转换回Modbus标准地址格式
|
||||
* 1、线圈: 0xxxx (1-9999)
|
||||
* 2、离散输入: 1xxxx (10001-19999)
|
||||
* 3、保持寄存器: 4xxxx (40001-49999)
|
||||
* 4、输入寄存器: 3xxxx (30001-39999)
|
||||
*/
|
||||
private int convertToModbusAddress(int actualAddress, int registerType) {
|
||||
switch (registerType) {
|
||||
case 3:
|
||||
return actualAddress + 40001;
|
||||
case 4:
|
||||
return actualAddress + 30001;
|
||||
case 2:
|
||||
return actualAddress + 10001;
|
||||
case 1:
|
||||
return actualAddress + 1;
|
||||
default:
|
||||
return actualAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取寄存器类型名称
|
||||
* 1、线圈 (Coils)
|
||||
* 2、离散输入 (Discrete Inputs)
|
||||
* 3、保持寄存器 (Holding Registers)
|
||||
* 4、输入寄存器 (Input Registers)
|
||||
*/
|
||||
private String getRegisterTypeName(int registerType) {
|
||||
switch (registerType) {
|
||||
case 1:
|
||||
return "线圈(Coils)";
|
||||
case 2:
|
||||
return "离散输入(Discrete Inputs)";
|
||||
case 3:
|
||||
return "保持寄存器(Holding Registers)";
|
||||
case 4:
|
||||
return "输入寄存器(Input Registers)";
|
||||
default:
|
||||
return "未知类型(" + registerType + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该跳过该地址(因为持续失败)
|
||||
*/
|
||||
private boolean shouldSkipAddress(String itemCode) {
|
||||
FailureInfo info = failureTracker.get(itemCode);
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime < info.skipUntilTime) {
|
||||
// 还在跳过时间内
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录读取成功,重置失败计数
|
||||
*/
|
||||
private void recordSuccess(String itemCode) {
|
||||
FailureInfo info = failureTracker.get(itemCode);
|
||||
if (info != null && info.consecutiveFailures > 0) {
|
||||
log.info("地址恢复正常: {}, 之前连续失败{}次", itemCode, info.consecutiveFailures);
|
||||
failureTracker.remove(itemCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录读取失败
|
||||
*/
|
||||
private void recordFailure(String itemCode, ModbusItemConfig config, Exception e) {
|
||||
FailureInfo info = failureTracker.computeIfAbsent(itemCode, k -> new FailureInfo());
|
||||
info.consecutiveFailures++;
|
||||
info.lastFailureTime = System.currentTimeMillis();
|
||||
|
||||
// 判断是否是超时异常
|
||||
boolean isTimeout = isTimeoutException(e);
|
||||
|
||||
// 如果连续失败次数达到阈值,设置跳过时间
|
||||
if (info.consecutiveFailures >= OpcConfig.modbus_tcp_failure_threshold && isTimeout) {
|
||||
// 根据失败次数计算跳过时间(递增策略)
|
||||
int skipSeconds = OpcConfig.modbus_tcp_skip_time_seconds *
|
||||
Math.min(info.consecutiveFailures - OpcConfig.modbus_tcp_failure_threshold + 1, 4);
|
||||
info.skipUntilTime = info.lastFailureTime + skipSeconds * 1000L;
|
||||
|
||||
int modbusAddr = convertToModbusAddress(config.address, config.registerType);
|
||||
log.warn("地址持续失败: {}, 地址:{} (Modbus:{}), 连续失败{}次, 将跳过{}秒",
|
||||
itemCode, config.address, modbusAddr, info.consecutiveFailures, skipSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是超时相关异常
|
||||
*/
|
||||
private boolean isTimeoutException(Exception e) {
|
||||
if (e instanceof TimeoutException) {
|
||||
return true;
|
||||
}
|
||||
if (e instanceof ExecutionException) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
String causeClass = cause.getClass().getName();
|
||||
return causeClass.contains("Timeout") || causeClass.contains("timeout");
|
||||
}
|
||||
}
|
||||
String exMsg = e.getMessage();
|
||||
return exMsg != null && (exMsg.contains("timeout") || exMsg.contains("timed out"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印失败地址统计信息
|
||||
*/
|
||||
private void logFailureStatistics() {
|
||||
if (failureTracker.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int totalFailed = 0;
|
||||
int skippedCount = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n========== Modbus失败地址统计 ==========\n");
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
for (Map.Entry<String, FailureInfo> entry : failureTracker.entrySet()) {
|
||||
String itemCode = entry.getKey();
|
||||
FailureInfo info = entry.getValue();
|
||||
totalFailed++;
|
||||
|
||||
boolean isSkipped = currentTime < info.skipUntilTime;
|
||||
if (isSkipped) {
|
||||
skippedCount++;
|
||||
long remainSeconds = (info.skipUntilTime - currentTime) / 1000;
|
||||
sb.append(String.format(" [跳过中] %s - 连续失败%d次, 剩余跳过时间:%d秒\n",
|
||||
itemCode, info.consecutiveFailures, remainSeconds));
|
||||
} else {
|
||||
sb.append(String.format(" [监控中] %s - 连续失败%d次\n",
|
||||
itemCode, info.consecutiveFailures));
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(String.format("总计: %d个失败地址, 其中%d个正在跳过\n", totalFailed, skippedCount));
|
||||
sb.append("========================================");
|
||||
|
||||
log.warn(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量读取Modbus数据
|
||||
*/
|
||||
private Map<String, Object> batchReadModbus(List<ModbusItemConfig> itemConfigs) {
|
||||
Map<String, Object> dataMap = new HashMap<>();
|
||||
// 按寄存器类型分组
|
||||
Map<Integer, List<ModbusItemConfig>> groupedByType = new HashMap<>();
|
||||
for (ModbusItemConfig config : itemConfigs) {
|
||||
groupedByType.computeIfAbsent(config.registerType, k -> new ArrayList<>()).add(config);
|
||||
}
|
||||
// 对每种类型的寄存器进行批量读取
|
||||
for (Map.Entry<Integer, List<ModbusItemConfig>> entry : groupedByType.entrySet()) {
|
||||
int registerType = entry.getKey();
|
||||
List<ModbusItemConfig> configs = entry.getValue();
|
||||
// 读取这一组寄存器
|
||||
Map<String, Object> typeData = readRegisterGroup(registerType, configs);
|
||||
dataMap.putAll(typeData);
|
||||
}
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 地址范围类:用于批量读取
|
||||
*/
|
||||
private static class AddressRange {
|
||||
int startAddress;
|
||||
int quantity;
|
||||
List<ModbusItemConfig> configs;
|
||||
|
||||
AddressRange(int startAddress, int quantity) {
|
||||
this.startAddress = startAddress;
|
||||
this.quantity = quantity;
|
||||
this.configs = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取一组相同类型的寄存器
|
||||
*/
|
||||
private Map<String, Object> readRegisterGroup(int registerType, List<ModbusItemConfig> configs) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 如果是线圈或离散输入,不进行合并(数据类型不同)
|
||||
if (registerType == 1 || registerType == 2) {
|
||||
for (ModbusItemConfig config : configs) {
|
||||
// 检查是否应该跳过
|
||||
if (shouldSkipAddress(config.itemCode)) {
|
||||
result.put(config.itemCode, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Object value = readSingleRegister(registerType, config);
|
||||
result.put(config.itemCode, value);
|
||||
recordSuccess(config.itemCode);
|
||||
} catch (Exception e) {
|
||||
int modbusAddr = convertToModbusAddress(config.address, config.registerType);
|
||||
String errorMsg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();
|
||||
log.error("读取失败: {}, 地址:{} (Modbus:{}), 寄存器类型:{}, 异常类型:{}, 错误:{}",
|
||||
config.itemCode, config.address, modbusAddr,
|
||||
getRegisterTypeName(registerType), e.getClass().getName(), errorMsg, e);
|
||||
recordFailure(config.itemCode, config, e);
|
||||
result.put(config.itemCode, null);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<ModbusItemConfig> sortedConfigs = configs.stream()
|
||||
.sorted(Comparator.comparingInt(c -> c.address))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<AddressRange> ranges = mergeAddressRanges(sortedConfigs);
|
||||
|
||||
for (AddressRange range : ranges) {
|
||||
try {
|
||||
if (range.configs.size() == 1) {
|
||||
// 单个读取
|
||||
ModbusItemConfig config = range.configs.get(0);
|
||||
|
||||
// 检查是否应该跳过
|
||||
if (shouldSkipAddress(config.itemCode)) {
|
||||
result.put(config.itemCode, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = readSingleRegister(registerType, config);
|
||||
result.put(config.itemCode, value);
|
||||
recordSuccess(config.itemCode);
|
||||
} else {
|
||||
// 批量读取
|
||||
Map<String, Object> batchResult = readBatchRegisters(registerType, range);
|
||||
result.putAll(batchResult);
|
||||
// 批量读取成功,重置所有涉及的地址的失败计数
|
||||
for (ModbusItemConfig config : range.configs) {
|
||||
recordSuccess(config.itemCode);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
int modbusAddress = convertToModbusAddress(range.startAddress, registerType);
|
||||
String errorMsg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();
|
||||
log.error("批量读取失败: 起始地址:{} (Modbus:{}), 数量:{}, 寄存器类型:{}, 异常类型:{}, 错误:{}",
|
||||
range.startAddress, modbusAddress, range.quantity,
|
||||
getRegisterTypeName(registerType), e.getClass().getName(), errorMsg, e);
|
||||
// 失败时逐个读取
|
||||
for (ModbusItemConfig config : range.configs) {
|
||||
// 检查是否应该跳过
|
||||
if (shouldSkipAddress(config.itemCode)) {
|
||||
result.put(config.itemCode, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Object value = readSingleRegister(registerType, config);
|
||||
result.put(config.itemCode, value);
|
||||
recordSuccess(config.itemCode);
|
||||
} catch (Exception ex) {
|
||||
int modbusAddr = convertToModbusAddress(config.address, config.registerType);
|
||||
String errMsg = ex.getMessage() != null ? ex.getMessage() : ex.getClass().getSimpleName();
|
||||
log.error("单独读取失败: {}, 地址:{} (Modbus:{}), 异常类型:{}, 错误:{}",
|
||||
config.itemCode, config.address, modbusAddr, ex.getClass().getName(), errMsg, ex);
|
||||
recordFailure(config.itemCode, config, ex);
|
||||
result.put(config.itemCode, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并连续地址范围
|
||||
*/
|
||||
private List<AddressRange> mergeAddressRanges(List<ModbusItemConfig> sortedConfigs) {
|
||||
List<AddressRange> ranges = new ArrayList<>();
|
||||
if (sortedConfigs.isEmpty()) {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
AddressRange currentRange = null;
|
||||
for (ModbusItemConfig config : sortedConfigs) {
|
||||
int quantity = getQuantityByDataType(config.dataType);
|
||||
|
||||
if (currentRange == null) {
|
||||
// 第一个范围
|
||||
currentRange = new AddressRange(config.address, quantity);
|
||||
currentRange.configs.add(config);
|
||||
} else {
|
||||
int expectedNextAddress = currentRange.startAddress + currentRange.quantity;
|
||||
int gap = config.address - expectedNextAddress;
|
||||
|
||||
// 如果地址连续或间隔较小,合并到当前范围
|
||||
if (gap >= 0 && gap <= OpcConfig.modbus_tcp_batch_address_gap) {
|
||||
currentRange.quantity = config.address - currentRange.startAddress + quantity;
|
||||
currentRange.configs.add(config);
|
||||
} else {
|
||||
// 地址不连续,开始新范围
|
||||
ranges.add(currentRange);
|
||||
currentRange = new AddressRange(config.address, quantity);
|
||||
currentRange.configs.add(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRange != null) {
|
||||
ranges.add(currentRange);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private Map<String, Object> readBatchRegisters(int registerType, AddressRange range)
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
int unitId = OpcConfig.modbus_tcp_default_unit_id;
|
||||
|
||||
// 创建批量读取请求
|
||||
ModbusRequest request;
|
||||
switch (registerType) {
|
||||
case 3:
|
||||
request = new ReadHoldingRegistersRequest(range.startAddress, range.quantity);
|
||||
break;
|
||||
case 4:
|
||||
request = new ReadInputRegistersRequest(range.startAddress, range.quantity);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("不支持批量读取的寄存器类型: " + registerType);
|
||||
}
|
||||
|
||||
CompletableFuture<? extends ModbusResponse> future = modbusMaster.sendRequest(request, unitId);
|
||||
ModbusResponse response = future.get(OpcConfig.modbus_tcp_timeout_seconds, TimeUnit.SECONDS);
|
||||
|
||||
// 解析响应
|
||||
ByteBuf registers = null;
|
||||
try {
|
||||
if (response instanceof ReadHoldingRegistersResponse) {
|
||||
registers = ((ReadHoldingRegistersResponse) response).getRegisters();
|
||||
} else if (response instanceof ReadInputRegistersResponse) {
|
||||
registers = ((ReadInputRegistersResponse) response).getRegisters();
|
||||
}
|
||||
|
||||
if (registers != null) {
|
||||
// 为每个配置项提取对应的数据
|
||||
for (ModbusItemConfig config : range.configs) {
|
||||
int offset = (config.address - range.startAddress) * 2;
|
||||
int quantity = getQuantityByDataType(config.dataType);
|
||||
|
||||
// 创建一个新的ByteBuf用于解析当前项
|
||||
ByteBuf itemBuf = registers.slice(offset, quantity * 2).retain();
|
||||
try {
|
||||
Object value = parseRegisterValue(itemBuf, config.dataType);
|
||||
result.put(config.itemCode, value);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(itemBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (registers != null) {
|
||||
ReferenceCountUtil.release(registers);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("批量读取成功: 起始地址:{}, 数量:{}, 读取项数:{}",
|
||||
range.startAddress, range.quantity, range.configs.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object readSingleRegister(int registerType, ModbusItemConfig config)
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
// 从站ID(使用配置)
|
||||
int unitId = OpcConfig.modbus_tcp_default_unit_id;
|
||||
int quantity = getQuantityByDataType(config.dataType);
|
||||
// 创建请求
|
||||
ModbusRequest request;
|
||||
switch (registerType) {
|
||||
case 3:
|
||||
request = new ReadHoldingRegistersRequest(config.address, quantity);
|
||||
break;
|
||||
case 4:
|
||||
request = new ReadInputRegistersRequest(config.address, quantity);
|
||||
break;
|
||||
case 1:
|
||||
request = new ReadCoilsRequest(config.address, quantity);
|
||||
break;
|
||||
case 2:
|
||||
request = new ReadDiscreteInputsRequest(config.address, quantity);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("不支持的寄存器类型: " + registerType);
|
||||
}
|
||||
|
||||
CompletableFuture<? extends ModbusResponse> future =
|
||||
modbusMaster.sendRequest(request, unitId);
|
||||
ModbusResponse response = future.get(OpcConfig.modbus_tcp_timeout_seconds, TimeUnit.SECONDS);
|
||||
return parseResponse(response, config.dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Modbus响应
|
||||
*/
|
||||
private Object parseResponse(ModbusResponse response, String dataType) {
|
||||
if (response instanceof ReadHoldingRegistersResponse) {
|
||||
ReadHoldingRegistersResponse hrResponse = (ReadHoldingRegistersResponse) response;
|
||||
ByteBuf registers = hrResponse.getRegisters();
|
||||
try {
|
||||
return parseRegisterValue(registers, dataType);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(registers);
|
||||
}
|
||||
} else if (response instanceof ReadInputRegistersResponse) {
|
||||
ReadInputRegistersResponse irResponse = (ReadInputRegistersResponse) response;
|
||||
ByteBuf registers = irResponse.getRegisters();
|
||||
try {
|
||||
return parseRegisterValue(registers, dataType);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(registers);
|
||||
}
|
||||
} else if (response instanceof ReadCoilsResponse) {
|
||||
ReadCoilsResponse coilResponse = (ReadCoilsResponse) response;
|
||||
ByteBuf coils = coilResponse.getCoilStatus();
|
||||
try {
|
||||
if (coils.readableBytes() > 0) {
|
||||
byte status = coils.readByte();
|
||||
return (status & 0x01) != 0;
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(coils);
|
||||
}
|
||||
} else if (response instanceof ReadDiscreteInputsResponse) {
|
||||
ReadDiscreteInputsResponse diResponse = (ReadDiscreteInputsResponse) response;
|
||||
ByteBuf inputs = diResponse.getInputStatus();
|
||||
try {
|
||||
if (inputs.readableBytes() > 0) {
|
||||
byte status = inputs.readByte();
|
||||
return (status & 0x01) != 0;
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(inputs);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 16位数据需要1个寄存器
|
||||
* 32位数据需要2个寄存器
|
||||
* 64位数据需要4个寄存器
|
||||
* String类型:STRING:长度,例如 STRING:10 表示10个字符(需要5个寄存器)
|
||||
*/
|
||||
private int getQuantityByDataType(String dataType) {
|
||||
if (StrUtil.isEmpty(dataType)) {
|
||||
return 1;
|
||||
}
|
||||
String type = dataType.toUpperCase().trim();
|
||||
|
||||
// 处理 STRING:长度 格式
|
||||
if (type.startsWith("STRING:")) {
|
||||
try {
|
||||
String lengthStr = type.substring(7);
|
||||
int charLength = Integer.parseInt(lengthStr);
|
||||
// 每个寄存器存储2个字符,向上取整
|
||||
return (charLength + 1) / 2;
|
||||
} catch (Exception e) {
|
||||
log.error("解析String长度失败: {}", dataType);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "FLOAT":
|
||||
case "INT32":
|
||||
case "UINT32":
|
||||
case "DWORD":
|
||||
return 2;
|
||||
case "INT64":
|
||||
case "UINT64":
|
||||
case "DOUBLE":
|
||||
return 4;
|
||||
case "STRING":
|
||||
// 默认String长度:10个字符 = 5个寄存器
|
||||
return 5;
|
||||
case "INT16":
|
||||
case "UINT16":
|
||||
case "WORD":
|
||||
case "BOOL":
|
||||
case "BOOLEAN":
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据数据类型解析寄存器值
|
||||
*/
|
||||
private Object parseRegisterValue(ByteBuf registers, String dataType) {
|
||||
if (StrUtil.isEmpty(dataType)) {
|
||||
if (registers.readableBytes() >= 2) {
|
||||
return registers.readShort();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String type = dataType.toUpperCase().trim();
|
||||
try {
|
||||
switch (type) {
|
||||
case "INT16":
|
||||
if (registers.readableBytes() >= 2) {
|
||||
return registers.readShort();
|
||||
}
|
||||
break;
|
||||
|
||||
case "UINT16":
|
||||
case "WORD":
|
||||
if (registers.readableBytes() >= 2) {
|
||||
return registers.readUnsignedShort();
|
||||
}
|
||||
break;
|
||||
|
||||
case "INT32":
|
||||
if (registers.readableBytes() >= 4) {
|
||||
return registers.readInt();
|
||||
}
|
||||
break;
|
||||
|
||||
case "UINT32":
|
||||
case "DWORD":
|
||||
if (registers.readableBytes() >= 4) {
|
||||
return registers.readUnsignedInt();
|
||||
}
|
||||
break;
|
||||
|
||||
case "FLOAT":
|
||||
if (registers.readableBytes() >= 4) {
|
||||
return registers.readFloat();
|
||||
}
|
||||
break;
|
||||
|
||||
case "INT64":
|
||||
if (registers.readableBytes() >= 8) {
|
||||
return registers.readLong();
|
||||
}
|
||||
break;
|
||||
|
||||
case "DOUBLE":
|
||||
if (registers.readableBytes() >= 8) {
|
||||
return registers.readDouble();
|
||||
}
|
||||
break;
|
||||
|
||||
case "BOOL":
|
||||
case "BOOLEAN":
|
||||
if (registers.readableBytes() >= 2) {
|
||||
return registers.readShort() != 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// 处理 STRING 或 STRING:长度 格式
|
||||
if (type.equals("STRING") || type.startsWith("STRING:")) {
|
||||
return parseStringFromRegisters(registers, type);
|
||||
}
|
||||
|
||||
// log.warn("未知的数据类型: {}, 使用INT16", dataType);
|
||||
if (registers.readableBytes() >= 2) {
|
||||
return registers.readShort();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析寄存器值失败,数据类型: {}, 错误: {}", dataType, e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从寄存器中解析字符串
|
||||
* Modbus 寄存器:每个寄存器2字节,可存储2个ASCII字符
|
||||
* 字节序:大端序(高字节在前)
|
||||
*/
|
||||
private String parseStringFromRegisters(ByteBuf registers, String dataType) {
|
||||
try {
|
||||
int availableBytes = registers.readableBytes();
|
||||
if (availableBytes == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 确定字符串长度
|
||||
int maxLength = availableBytes;
|
||||
if (dataType.startsWith("STRING:")) {
|
||||
try {
|
||||
String lengthStr = dataType.substring(7);
|
||||
maxLength = Math.min(Integer.parseInt(lengthStr), availableBytes);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析String长度失败,使用默认: {}", dataType);
|
||||
}
|
||||
}
|
||||
|
||||
// 读取字节并转换为字符串
|
||||
byte[] bytes = new byte[availableBytes];
|
||||
registers.readBytes(bytes);
|
||||
|
||||
// 转换为字符串,去除末尾的null字符和空格
|
||||
String result = new String(bytes, 0, Math.min(maxLength, bytes.length), "ASCII");
|
||||
// 去除null字符
|
||||
int nullIndex = result.indexOf('\0');
|
||||
if (nullIndex >= 0) {
|
||||
result = result.substring(0, nullIndex);
|
||||
}
|
||||
// 去除首尾空格
|
||||
result = result.trim();
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("解析String失败: {}", e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 记录数据变化日志
|
||||
*/
|
||||
private void logItemChanged(String itemId, UnifiedDataAccessor accessor_value, Object value, OpcItemDto itemDto) {
|
||||
// 过滤心跳、时间等频繁变化的数据
|
||||
if (itemDto.getItem_code().endsWith("heartbeat") ||
|
||||
itemDto.getItem_code().endsWith("time") ||
|
||||
itemDto.getItem_code().endsWith("consumption")) {
|
||||
return;
|
||||
}
|
||||
Object his = accessor_value.getValue(itemId);
|
||||
// 记录关联项的值
|
||||
List<String> relate_items = itemDto.getRelate_items();
|
||||
if (relate_items != null && !relate_items.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String relate : relate_items) {
|
||||
Object obj = accessor_value.getValue(relate);
|
||||
sb.append(relate).append("=").append(obj).append(";");
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("数据变化 - item:{}, 旧值:{}, 新值:{}, 关联:{}", itemId, his, value, sb.toString());
|
||||
}
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("数据变化 - item:{}, 旧值:{}, 新值:{}", itemId, his, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.nl.acs.opc;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.dromara.dynamictp.core.support.ThreadPoolBuilder;
|
||||
import org.nl.acs.auto.run.AbstractAutoRunnable;
|
||||
import org.nl.acs.opc.service.dto.OpcServerManageDto;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.dromara.dynamictp.common.em.QueueTypeEnum.MEMORY_SAFE_LINKED_BLOCKING_QUEUE;
|
||||
|
||||
/**
|
||||
* OPC设备同步启动
|
||||
*
|
||||
* @author 20220102CG\noblelift
|
||||
*/
|
||||
@Component
|
||||
public class DeviceModbusTcpSynchronizeAutoRun extends AbstractAutoRunnable {
|
||||
|
||||
public static volatile boolean isRun = false;
|
||||
ExecutorService executorService = ThreadPoolBuilder.newBuilder()
|
||||
.threadPoolName("deviceOpc_thread")
|
||||
.threadFactory("deviceOpc_thread")
|
||||
.corePoolSize(80)
|
||||
.maximumPoolSize(100)
|
||||
.keepAliveTime(40)
|
||||
.timeUnit(TimeUnit.SECONDS)
|
||||
.workQueue(MEMORY_SAFE_LINKED_BLOCKING_QUEUE.getName(), 2000)
|
||||
.buildDynamic();
|
||||
|
||||
@Autowired
|
||||
private DeviceAppService deviceAppService;
|
||||
@Autowired
|
||||
private OpcServerManageService opcServerManageService;
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return DeviceModbusTcpSynchronizeAutoRun.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ModbusTCP/IP设备同步器";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void autoRun() throws Exception {
|
||||
{
|
||||
isRun = true;
|
||||
Map<String, OpcServerManageDto> servers = this.opcServerManageService.queryAllServerMap();
|
||||
Map<String, List<List<OpcItemDto>>> pros;
|
||||
do {
|
||||
Thread.sleep(1000L);
|
||||
pros = this.deviceAppService.findAllFormatProtocolFromDriver();
|
||||
} while (ObjectUtil.isEmpty(pros));
|
||||
Set<String> keys = pros.keySet();
|
||||
Iterator var4 = keys.iterator();
|
||||
//代码执行一次
|
||||
while (var4.hasNext()) {
|
||||
String key = (String) var4.next();
|
||||
List<List<OpcItemDto>> list = pros.get(key);
|
||||
OpcServerManageDto opcServer = servers.get(key);
|
||||
Iterator var8 = list.iterator();
|
||||
while (var8.hasNext()) {
|
||||
List<OpcItemDto> groupProtols = (List) var8.next();
|
||||
DeviceModbusTcpProtocolRunnable runnable = new DeviceModbusTcpProtocolRunnable();
|
||||
runnable.setProtocols(groupProtols);
|
||||
runnable.setOpcServer(opcServer);
|
||||
this.executorService.submit(runnable);
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
Thread.sleep(3000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
isRun = false;
|
||||
this.executorService.shutdownNow();
|
||||
this.executorService = ThreadPoolBuilder.newBuilder()
|
||||
.threadPoolName("deviceOpc_thread")
|
||||
.threadFactory("deviceOpc_thread")
|
||||
.corePoolSize(80)
|
||||
.maximumPoolSize(100)
|
||||
.keepAliveTime(40)
|
||||
.timeUnit(TimeUnit.SECONDS)
|
||||
.workQueue(MEMORY_SAFE_LINKED_BLOCKING_QUEUE.getName(), 2000)
|
||||
.buildDynamic();
|
||||
}
|
||||
}
|
||||
@@ -84,9 +84,9 @@ public class DeviceOpcProtocolRunable implements Runnable, DataCallback, ServerC
|
||||
@Override
|
||||
public void run() {
|
||||
if (OpcConfig.opc_item_read_using_callback) {
|
||||
this.runNew();
|
||||
// this.runNew();
|
||||
} else {
|
||||
this.runOld();
|
||||
// this.runOld();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,26 +440,26 @@ public class DeviceOpcProtocolRunable implements Runnable, DataCallback, ServerC
|
||||
&& !itemDto.getItem_code().endsWith("x") && !itemDto.getItem_code().endsWith("y")) {
|
||||
// 存在上次点位值为null情况 则不记录日志
|
||||
if(!(his instanceof Float) && !(value instanceof Float)){
|
||||
LuceneLogDto luceneLogDto = new LuceneLogDto(itemDto.getOpc_server_code(), itemDto.getOpc_plc_code(),4, itemDto.getDevice_code(), itemDto.getItem_code().substring(itemDto.getItem_code().lastIndexOf(".") + 1),
|
||||
String.valueOf(his), String.valueOf(value));
|
||||
luceneLogDto.setLogType(LogTypeEnum.DEVICE_LOG.getDesc());
|
||||
String logLevel = paramService.findByCode(AcsConfig.LOGLEVEL).getValue();
|
||||
if(StrUtil.isNotEmpty(logLevel) && isNumeric(logLevel) && (luceneLogDto.getLog_level() >= Integer.parseInt(logLevel))){
|
||||
log.info("{}", JSON.toJSONString(luceneLogDto));
|
||||
}
|
||||
// LuceneLogDto luceneLogDto = new LuceneLogDto(itemDto.getOpc_server_code(), itemDto.getOpc_plc_code(),4, itemDto.getDevice_code(), itemDto.getItem_code().substring(itemDto.getItem_code().lastIndexOf(".") + 1),
|
||||
// String.valueOf(his), String.valueOf(value));
|
||||
// luceneLogDto.setLogType(LogTypeEnum.DEVICE_LOG.getDesc());
|
||||
// String logLevel = paramService.findByCode(AcsConfig.LOGLEVEL).getValue();
|
||||
// if(StrUtil.isNotEmpty(logLevel) && isNumeric(logLevel) && (luceneLogDto.getLog_level() >= Integer.parseInt(logLevel))){
|
||||
// log.info("{}", JSON.toJSONString(luceneLogDto));
|
||||
// }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!itemDto.getItem_code().endsWith("heartbeat") && !itemDto.getItem_code().endsWith("time") && !itemDto.getItem_code().endsWith("consumption")
|
||||
&& !itemDto.getItem_code().endsWith("x") && !itemDto.getItem_code().endsWith("y")) {
|
||||
if(!(his instanceof Float) && !(value instanceof Float)){
|
||||
LuceneLogDto luceneLogDto = new LuceneLogDto(itemDto.getOpc_server_code(), itemDto.getOpc_plc_code(),4, itemDto.getDevice_code(), itemDto.getItem_code().substring(itemDto.getItem_code().lastIndexOf(".") + 1),
|
||||
String.valueOf(his), String.valueOf(value));
|
||||
luceneLogDto.setLogType(LogTypeEnum.DEVICE_LOG.getDesc());
|
||||
String logLevel = paramService.findByCode(AcsConfig.LOGLEVEL).getValue();
|
||||
if(StrUtil.isNotEmpty(logLevel) && isNumeric(logLevel) && (luceneLogDto.getLog_level() >= Integer.parseInt(logLevel))){
|
||||
log.info("{}", JSON.toJSONString(luceneLogDto));
|
||||
}
|
||||
// LuceneLogDto luceneLogDto = new LuceneLogDto(itemDto.getOpc_server_code(), itemDto.getOpc_plc_code(),4, itemDto.getDevice_code(), itemDto.getItem_code().substring(itemDto.getItem_code().lastIndexOf(".") + 1),
|
||||
// String.valueOf(his), String.valueOf(value));
|
||||
// luceneLogDto.setLogType(LogTypeEnum.DEVICE_LOG.getDesc());
|
||||
// String logLevel = paramService.findByCode(AcsConfig.LOGLEVEL).getValue();
|
||||
// if(StrUtil.isNotEmpty(logLevel) && isNumeric(logLevel) && (luceneLogDto.getLog_level() >= Integer.parseInt(logLevel))){
|
||||
// log.info("{}", JSON.toJSONString(luceneLogDto));
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,32 @@ public class OpcConfig {
|
||||
* OPC 数据同步是否采用回调机制实现。之前是线程定期全部读,效率低。
|
||||
*/
|
||||
public static Boolean opc_item_read_using_callback = false;
|
||||
|
||||
/**
|
||||
* ModbusTCP连接超时时间(秒)
|
||||
*/
|
||||
public static Integer modbus_tcp_timeout_seconds = Integer.valueOf(3);
|
||||
|
||||
/**
|
||||
* ModbusTCP默认从站ID(Unit ID)
|
||||
*/
|
||||
public static Integer modbus_tcp_default_unit_id = Integer.valueOf(1);
|
||||
|
||||
/**
|
||||
* ModbusTCP批量读取:连续地址的最大间隔(超过此间隔则分开读取)
|
||||
*/
|
||||
public static Integer modbus_tcp_batch_address_gap = Integer.valueOf(100);
|
||||
|
||||
/**
|
||||
* ModbusTCP:连续失败多少次后开始跳过该地址
|
||||
*/
|
||||
public static Integer modbus_tcp_failure_threshold = Integer.valueOf(3);
|
||||
|
||||
/**
|
||||
* ModbusTCP:失败后跳过的时间(秒)
|
||||
* 第1次失败:跳过 30 秒
|
||||
* 第2次失败:跳过 60 秒
|
||||
* 第3次及以上:跳过 120 秒
|
||||
*/
|
||||
public static Integer modbus_tcp_skip_time_seconds = Integer.valueOf(30);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ public class OpcItemDto {
|
||||
private String driver_code;
|
||||
private String opc_server_code;
|
||||
private String opc_plc_code;
|
||||
private String item_name;
|
||||
private String item_code;
|
||||
private Object item_value;
|
||||
private String item_value_type;
|
||||
|
||||
private Object his_item_value;
|
||||
|
||||
|
||||
@@ -947,6 +947,7 @@ public class TaskServiceImpl extends CommonServiceImpl<TaskMapper, Task> impleme
|
||||
this.removeByCodeFromCache(entity.getTask_code());
|
||||
//反馈上位系统任务状态
|
||||
this.feedWmsTaskStatus(entity);
|
||||
|
||||
//关闭仙工运单序列
|
||||
if (StrUtil.equals(task.getTask_type(), TaskTypeEnum.Standard_AGV_Task.getCode()) && (StrUtil.equals(task.getAgv_system_type(), AgvSystemTypeEnum.One_NDC_System_Type.getCode()) || StrUtil.equals(task.getAgv_system_type(), AgvSystemTypeEnum.XG_System_Type.getCode()))) {
|
||||
this.markComplete(entity);
|
||||
|
||||
@@ -10,7 +10,7 @@ spring:
|
||||
driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
# url: jdbc:log4jdbc:mysql://${DB_HOST:192.168.81.252}:${DB_PORT:3306}/${DB_NAME:stand_acs}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&useOldAliasMetadataBehavior=true
|
||||
# url: jdbc:log4jdbc:mysql://${DB_HOST:47.111.78.178}:${DB_PORT:3306}/${DB_NAME:lzhl_two_acs}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&useOldAliasMetadataBehavior=true
|
||||
url: jdbc:log4jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:fengtiandl_acs}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&useOldAliasMetadataBehavior=true&allowPublicKeyRetrieval=true
|
||||
url: jdbc:log4jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:dty_acs}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&useOldAliasMetadataBehavior=true&allowPublicKeyRetrieval=true
|
||||
|
||||
username: ${DB_USER:root}
|
||||
# password: ${DB_PWD:Root.123456}
|
||||
|
||||
@@ -6,7 +6,7 @@ spring:
|
||||
freemarker:
|
||||
check-template-location: false
|
||||
profiles:
|
||||
active: prod
|
||||
active: dev
|
||||
jackson:
|
||||
time-zone: GMT+8
|
||||
data:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
ENV = 'development'
|
||||
|
||||
# 接口地址
|
||||
VUE_APP_BASE_API = 'http://192.168.217.2:8011'
|
||||
VUE_APP_WS_API = 'ws://192.168.217.2:8011'
|
||||
VUE_APP_BASE_API = 'http://localhost:8011'
|
||||
VUE_APP_WS_API = 'ws://localhost:8011'
|
||||
|
||||
# 是否启用 babel-plugin-dynamic-import-node插件
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||
|
||||
@@ -2,6 +2,6 @@ ENV = 'production'
|
||||
|
||||
# 如果使用 Nginx 代理后端接口,那么此处需要改为 '/',文件查看 Docker 部署篇,Nginx 配置
|
||||
# 接口地址,注意协议,如果你没有配置 ssl,需要将 https 改为 http
|
||||
VUE_APP_BASE_API = 'http://192.168.217.2:8011'
|
||||
VUE_APP_BASE_API = 'http://localhost:8011'
|
||||
# 如果接口是 http 形式, wss 需要改为 ws
|
||||
VUE_APP_WS_API = 'ws://192.168.217.2:8011'
|
||||
VUE_APP_WS_API = 'ws://localhost:8011'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
window.g = {
|
||||
dev: {
|
||||
VUE_APP_BASE_API: 'http://192.168.217.2:8011'
|
||||
VUE_APP_BASE_API: 'http://localhost:8011'
|
||||
},
|
||||
prod: {
|
||||
VUE_APP_BASE_API: 'http://192.168.217.2:8011'
|
||||
VUE_APP_BASE_API: 'http://localhost:8011'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ import standard_autodoor from '@/views/acs/device/driver/standard_autodoor'
|
||||
import standard_inspect_site from './driver/standard_inspect_site'
|
||||
import standard_ordinary_site from './driver/standard_ordinary_site'
|
||||
import belt_conveyor from '@/views/acs/device/driver/belt_conveyor'
|
||||
import raster from '@/views/acs/device/driver/raster'
|
||||
import agv_ndc_one from '@/views/acs/device/driver/agv/agv_ndc_one'
|
||||
import agv_ndc_two from '@/views/acs/device/driver/agv/agv_ndc_two'
|
||||
import xg_agv from '@/views/acs/device/driver/agv/xg_agv'
|
||||
@@ -97,7 +98,8 @@ export default {
|
||||
standard_manipulator,
|
||||
xg_agv_car,
|
||||
standard_storage,
|
||||
kc_agv
|
||||
kc_agv,
|
||||
raster
|
||||
},
|
||||
dicts: ['device_type'],
|
||||
mixins: [crud],
|
||||
|
||||
544
acs2/nladmin-ui/src/views/acs/device/driver/raster.vue
Normal file
544
acs2/nladmin-ui/src/views/acs/device/driver/raster.vue
Normal file
@@ -0,0 +1,544 @@
|
||||
<template>
|
||||
<!--标准版-输送机-控制点-->
|
||||
<div>
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span">设备协议:</span>
|
||||
</div>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
OpcServer:
|
||||
<el-select
|
||||
v-model="opc_id"
|
||||
placeholder="无"
|
||||
clearable
|
||||
filterable
|
||||
@change="changeOpc"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dataOpcservers"
|
||||
:key="item.opc_id"
|
||||
:label="item.opc_name"
|
||||
:value="item.opc_id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
PLC:
|
||||
<el-select
|
||||
v-model="plc_id"
|
||||
placeholder="无"
|
||||
clearable
|
||||
@change="changePlc"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dataOpcPlcs"
|
||||
:key="item.plc_id"
|
||||
:label="item.plc_name"
|
||||
:value="item.plc_id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span">输送系统:</span>
|
||||
</div>
|
||||
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="78px">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="电气调度号" label-width="150px">
|
||||
<el-input v-model="form.address" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span">指令相关:</span>
|
||||
</div>
|
||||
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="78px">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="检验有货">
|
||||
<el-switch v-model="form.inspect_in_stocck" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="取货校验" label-width="150px">
|
||||
<el-switch v-model="form.ignore_pickup_check" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="放货校验" label-width="150px">
|
||||
<el-switch v-model="form.ignore_release_check" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="取货完成反馈" label-width="78px">
|
||||
<el-switch v-model="form.feedbackToLms" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="呼叫">
|
||||
<el-switch v-model="form.apply_task" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="响应" label-width="150px">
|
||||
<el-switch v-model="form.manual_create_task" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="关联设备" prop="device_code">
|
||||
<el-select
|
||||
v-model="form.link_device_code"
|
||||
filterable
|
||||
multiple
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in deviceList"
|
||||
:key="item.device_code"
|
||||
:label="item.device_name"
|
||||
:value="item.device_code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="关联三色灯" prop="device_code" label-width="100px">
|
||||
<el-select
|
||||
v-model="form.link_three_lamp"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in deviceList"
|
||||
:key="item.device_code"
|
||||
:label="item.device_name"
|
||||
:value="item.device_code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否输入物料" label-width="150px">
|
||||
<el-switch v-model="form.input_material" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否需要反馈光电" label-width="150px">
|
||||
<el-switch v-model="form.ship_device_update" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span">AGV相关:</span>
|
||||
</div>
|
||||
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="78px">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="取货">
|
||||
<el-switch v-model="form.is_pickup" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="放货">
|
||||
<el-switch v-model="form.is_release" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span">PLC读取字段:</span>
|
||||
</div>
|
||||
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="78px">
|
||||
<el-table
|
||||
v-loading="false"
|
||||
:data="data1"
|
||||
:max-height="550"
|
||||
size="small"
|
||||
style="width: 100%;margin-bottom: 15px"
|
||||
>
|
||||
|
||||
<el-table-column prop="name" label="用途" />
|
||||
<el-table-column prop="code" label="别名要求" />
|
||||
<el-table-column prop="db" label="DB块">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="data1[scope.$index].db"
|
||||
size="mini"
|
||||
class="edit-input"
|
||||
@input="finishReadEdit(data1[scope.$index])"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dbr_value">
|
||||
<template slot="header">
|
||||
<el-link type="primary" :underline="false" @click.native="test_read1()">测试读</el-link>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="data1[scope.$index].dbr_value" size="mini" class="edit-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span">PLC写入字段:</span>
|
||||
</div>
|
||||
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="78px">
|
||||
<el-table
|
||||
v-loading="false"
|
||||
:data="data2"
|
||||
:max-height="550"
|
||||
size="small"
|
||||
style="width: 100%;margin-bottom: 15px"
|
||||
>
|
||||
|
||||
<el-table-column prop="name" label="用途" />
|
||||
<el-table-column prop="code" label="别名要求" />
|
||||
<el-table-column prop="db" label="DB块">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="data2[scope.$index].db"
|
||||
size="mini"
|
||||
class="edit-input"
|
||||
@input="finishWriteEdit(data2[scope.$index])"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dbr_value2">
|
||||
<template slot="header">
|
||||
<el-link type="primary" :underline="false" @click.native="test_read2()">测试读</el-link>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="data2[scope.$index].dbr_value" size="mini" class="edit-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dbw_value">
|
||||
<template slot="header">
|
||||
<el-link type="primary" :underline="false" @click.native="test_write1()">测试写</el-link>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="data2[scope.$index].dbw_value" size="mini" class="edit-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="role-span" />
|
||||
<el-button
|
||||
:loading="false"
|
||||
icon="el-icon-check"
|
||||
size="mini"
|
||||
style="float: right; padding: 6px 9px"
|
||||
type="primary"
|
||||
@click="doSubmit"
|
||||
>保存
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
queryDriverConfig,
|
||||
updateConfig,
|
||||
testRead,
|
||||
testwrite
|
||||
} from '@/api/acs/device/driverConfig'
|
||||
import { selectOpcList } from '@/api/acs/device/opc'
|
||||
import { selectPlcList } from '@/api/acs/device/opcPlc'
|
||||
import { selectListByOpcID } from '@/api/acs/device/opcPlc'
|
||||
|
||||
import crud from '@/mixins/crud'
|
||||
import deviceCrud from '@/api/acs/device/device'
|
||||
|
||||
export default {
|
||||
name: 'Raster',
|
||||
mixins: [crud],
|
||||
props: {
|
||||
parentForm: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
device_code: '',
|
||||
device_id: '',
|
||||
plc_id: '',
|
||||
plc_code: '',
|
||||
address: '',
|
||||
opc_id: '',
|
||||
opc_code: '',
|
||||
configLoading: false,
|
||||
dataOpcservers: [],
|
||||
dataOpcPlcs: [],
|
||||
deviceList: [],
|
||||
data1: [],
|
||||
data2: [],
|
||||
form: {
|
||||
inspect_in_stocck: true,
|
||||
ignore_pickup_check: true,
|
||||
ignore_release_check: true,
|
||||
apply_task: true,
|
||||
link_three_lamp: '',
|
||||
manual_create_task: true,
|
||||
is_pickup: true,
|
||||
is_release: true,
|
||||
link_device_code: [],
|
||||
ship_device_update: true
|
||||
},
|
||||
rules: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
// 从父表单获取设备编码
|
||||
this.device_id = this.$props.parentForm.device_id
|
||||
this.device_code = this.$props.parentForm.device_code
|
||||
queryDriverConfig(this.device_id, this.$props.parentForm.driver_code).then(data => {
|
||||
// 给表单赋值,并且属性不能为空
|
||||
if (data.form) {
|
||||
const arr = Object.keys(data.form)
|
||||
// 不为空
|
||||
if (arr.length > 0) {
|
||||
this.form = data.form
|
||||
}
|
||||
}
|
||||
|
||||
// 给表单赋值,并且属性不能为空
|
||||
if (data.parentForm) {
|
||||
const arr = Object.keys(data.parentForm)
|
||||
// 不为空
|
||||
if (arr.length > 0) {
|
||||
this.opc_code = data.parentForm.opc_code
|
||||
this.plc_code = data.parentForm.plc_code
|
||||
}
|
||||
}
|
||||
this.data1 = data.rs
|
||||
this.data2 = data.ws
|
||||
this.sliceItem()
|
||||
})
|
||||
selectPlcList().then(data => {
|
||||
this.dataOpcPlcs = data
|
||||
this.plc_id = this.$props.parentForm.opc_plc_id
|
||||
})
|
||||
selectOpcList().then(data => {
|
||||
this.dataOpcservers = data
|
||||
this.opc_id = this.$props.parentForm.opc_server_id
|
||||
})
|
||||
deviceCrud.selectDeviceList().then(data => {
|
||||
this.deviceList = data
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
finishReadEdit(data) {
|
||||
// 编辑的是code列,并且值包含mode
|
||||
if (data.code.indexOf('mode') !== -1) {
|
||||
const dbValue = data.db
|
||||
// .之前的字符串
|
||||
const beforeStr = dbValue.match(/(\S*)\./)[1]
|
||||
// .之后的字符串
|
||||
const afterStr = dbValue.match(/\.(\S*)/)[1]
|
||||
// 取最后数字
|
||||
const endNumber = afterStr.substring(1)
|
||||
// 最后为非数字
|
||||
if (isNaN(parseInt(endNumber))) {
|
||||
return
|
||||
}
|
||||
for (const val in this.data1) {
|
||||
if (this.data1[val].code.indexOf('mode') !== -1) {
|
||||
this.data1[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 0)
|
||||
}
|
||||
if (this.data1[val].code.indexOf('move') !== -1) {
|
||||
this.data1[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 1)
|
||||
}
|
||||
if (this.data1[val].code.indexOf('carrier_direction') !== -1) {
|
||||
this.data1[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 2)
|
||||
}
|
||||
if (this.data1[val].code.indexOf('error') !== -1) {
|
||||
this.data1[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 4)
|
||||
}
|
||||
if (this.data1[val].code.indexOf('task') !== -1) {
|
||||
this.data1[val].db = beforeStr + '.' + 'D' + (parseInt(endNumber) + 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
finishWriteEdit(data) {
|
||||
// 编辑的是code列,并且值包含mode
|
||||
if (data.code.indexOf('to_command') !== -1) {
|
||||
const dbValue = data.db
|
||||
// .之前的字符串
|
||||
const beforeStr = dbValue.match(/(\S*)\./)[1]
|
||||
// .之后的字符串
|
||||
const afterStr = dbValue.match(/\.(\S*)/)[1]
|
||||
// 取最后数字
|
||||
const endNumber = afterStr.substring(1)
|
||||
// 最后为非数字
|
||||
if (isNaN(parseInt(endNumber))) {
|
||||
return
|
||||
}
|
||||
console.log(endNumber)
|
||||
for (const val in this.data2) {
|
||||
if (this.data2[val].code.indexOf('to_command') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 0)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_target') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 2)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_container_type') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 4)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_task') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + 'D' + (parseInt(endNumber) + 6)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_strap_times') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 10)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_length') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 12)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_weight') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 14)
|
||||
}
|
||||
if (this.data2[val].code.indexOf('to_height') !== -1) {
|
||||
this.data2[val].db = beforeStr + '.' + afterStr.substring(0, 1) + (parseInt(endNumber) + 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
changeOpc(val) {
|
||||
this.dataOpcservers.forEach(item => {
|
||||
if (item.opc_id === val) {
|
||||
this.opc_code = item.opc_code
|
||||
}
|
||||
})
|
||||
|
||||
selectListByOpcID(val).then(data => {
|
||||
this.dataOpcPlcs = data
|
||||
this.plc_id = ''
|
||||
this.plc_code = ''
|
||||
if (this.dataOpcPlcs && this.dataOpcPlcs.length > 0) {
|
||||
this.plc_id = this.dataOpcPlcs[0].plc_id
|
||||
this.plc_code = this.dataOpcPlcs[0].plc_code
|
||||
}
|
||||
this.sliceItem()
|
||||
})
|
||||
},
|
||||
changePlc(val) {
|
||||
this.dataOpcPlcs.forEach(item => {
|
||||
if (item.plc_id === val) {
|
||||
this.plc_code = item.plc_code
|
||||
this.sliceItem()
|
||||
return
|
||||
}
|
||||
})
|
||||
},
|
||||
test_read1() {
|
||||
testRead(this.data1, this.opc_id).then(data => {
|
||||
this.data1 = data
|
||||
console.log(this.data1)
|
||||
this.notify('操作成功!', 'success')
|
||||
}).catch(err => {
|
||||
console.log(err.response.data.message)
|
||||
})
|
||||
},
|
||||
test_write1() {
|
||||
testwrite(this.data2, this.opc_id).then(data => {
|
||||
this.notify('操作成功!', 'success')
|
||||
}).catch(err => {
|
||||
console.log(err.response.data.message)
|
||||
})
|
||||
},
|
||||
test_read2() {
|
||||
testRead(this.data2, this.opc_id).then(data => {
|
||||
this.data2 = data
|
||||
console.log(this.data2)
|
||||
this.notify('操作成功!', 'success')
|
||||
}).catch(err => {
|
||||
console.log(err.response.data.message)
|
||||
})
|
||||
},
|
||||
doSubmit() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.configLoading = true
|
||||
// 根据驱动类型判断是否为路由设备
|
||||
const parentForm = this.parentForm
|
||||
parentForm.is_route = true
|
||||
parentForm.plc_id = this.plc_id
|
||||
parentForm.opc_id = this.opc_id
|
||||
updateConfig(parentForm, this.form, this.data1, this.data2).then(res => {
|
||||
this.notify('保存成功', 'success')
|
||||
this.configLoading = false
|
||||
}).catch(err => {
|
||||
this.configLoading = false
|
||||
console.log(err.response.data.message)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
sliceItem() { // 拼接DB的Item值
|
||||
this.data1.forEach(item => {
|
||||
const str = item.code
|
||||
// 是否包含.
|
||||
if (str.search('.') !== -1) {
|
||||
// 截取最后一位
|
||||
item.code = this.opc_code + '.' + this.plc_code + '.' + this.device_code + '.' + str.slice(str.lastIndexOf('.') + 1)
|
||||
} else {
|
||||
item.code = this.opc_code + '.' + this.plc_code + '.' + this.device_code + '.' + item.code
|
||||
}
|
||||
})
|
||||
this.data2.forEach(item => {
|
||||
const str = item.code
|
||||
// 是否包含.
|
||||
if (str.search('.') !== -1) {
|
||||
// 截取最后一位
|
||||
item.code = this.opc_code + '.' + this.plc_code + '.' + this.device_code + '.' + str.slice(str.lastIndexOf('.') + 1)
|
||||
} else {
|
||||
item.code = this.opc_code + '.' + this.plc_code + '.' + this.device_code + '.' + item.code
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user