diff --git a/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java b/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java index bc4cc8a..1993ede 100644 --- a/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java +++ b/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java @@ -39,6 +39,10 @@ public class ConfigProperties { private Rfid rfid; + /** + * TCP服务端端口 + */ + private Integer serverPort; @Data public static class CameraConfig{ diff --git a/web/src/main/java/com/zhehekeji/web/controller/CheckLogController.java b/web/src/main/java/com/zhehekeji/web/controller/CheckLogController.java index 4c22b24..79bbb29 100644 --- a/web/src/main/java/com/zhehekeji/web/controller/CheckLogController.java +++ b/web/src/main/java/com/zhehekeji/web/controller/CheckLogController.java @@ -5,6 +5,7 @@ import com.zhehekeji.common.util.ValidatorUtil; import com.zhehekeji.core.pojo.Result; import com.zhehekeji.web.entity.CheckLog; import com.zhehekeji.web.entity.StockLog; +import com.zhehekeji.web.pojo.LocReq; import com.zhehekeji.web.pojo.stock.CheckLogSearch; import com.zhehekeji.web.pojo.stock.StockLogSearch; import com.zhehekeji.web.service.CheckLogService; @@ -17,6 +18,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import java.util.List; +import java.util.Map; @Api(tags = "盘点历史") @RequestMapping("/checkLog") @@ -34,5 +37,11 @@ public class CheckLogController { return Result.success(checkLogService.list(checkLogSearch)); } + @ApiOperation("盘点历史") + @PostMapping("/getOne") + public Map getOne(@RequestBody Result> checkLogSearch){ + + return null; + } } diff --git a/web/src/main/java/com/zhehekeji/web/pojo/LocReq.java b/web/src/main/java/com/zhehekeji/web/pojo/LocReq.java new file mode 100644 index 0000000..8a069e1 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/pojo/LocReq.java @@ -0,0 +1,16 @@ +package com.zhehekeji.web.pojo; + +import lombok.Data; + +@Data +public class LocReq { + Integer streetId; + //货位编码 + private String loc; + //时间 + private String date; + //托盘标签 + private String tagid; + //taskId + private String jobnum; +} diff --git a/web/src/main/java/com/zhehekeji/web/service/InitService.java b/web/src/main/java/com/zhehekeji/web/service/InitService.java index 307609d..dd03da0 100644 --- a/web/src/main/java/com/zhehekeji/web/service/InitService.java +++ b/web/src/main/java/com/zhehekeji/web/service/InitService.java @@ -13,6 +13,7 @@ import com.zhehekeji.web.lib.joyware.NetSDKLib; import com.zhehekeji.web.mapper.CameraMapper; import com.zhehekeji.web.mapper.SensorGunMapper; import com.zhehekeji.web.mapper.StreetMapper; +import com.zhehekeji.web.service.client.NettyServer; import com.zhehekeji.web.service.ksec.KsecNettyClient; import com.zhehekeji.web.service.robotic.NettyClient; import com.zhehekeji.web.service.sick.SickNettyClient; @@ -52,6 +53,9 @@ public class InitService implements ApplicationRunner { @Resource private CameraService cameraService; + @Resource + private NettyServer nettyServer; + @Bean public CameraControlLoginModule cameraControlLoginModule(){ CameraControlLoginModule cameraControlLoginModule = null; @@ -79,6 +83,8 @@ public class InitService implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { + nettyServer.CreateNettyServer(configProperties.getServerPort()); + //球机登录 List cameras = cameraMapper.selectByMap(new HashMap<>(0)); cameras.forEach(camera -> { diff --git a/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java b/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java index 74375a1..85f1e37 100644 --- a/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java +++ b/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java @@ -73,6 +73,12 @@ public class PlcCmdInfo { private Integer streetId; + private String typeNum; + + private Integer quantity; + + private Integer checkRlt; + public PlcCmdInfo(String plcId, String taskId, Integer side1, Integer leftRight1, Integer column1, Integer row1, Integer separation1, Integer side2, Integer leftRight2, Integer column2, Integer row2, Integer separation2,String lotnum) { this.taskId = taskId; this.side1 = side1; diff --git a/web/src/main/java/com/zhehekeji/web/service/PlcService.java b/web/src/main/java/com/zhehekeji/web/service/PlcService.java index 176e08b..186af94 100644 --- a/web/src/main/java/com/zhehekeji/web/service/PlcService.java +++ b/web/src/main/java/com/zhehekeji/web/service/PlcService.java @@ -14,6 +14,8 @@ import com.zhehekeji.web.pojo.OrderVO; import com.zhehekeji.web.service.RFID.RFIDMap; import com.zhehekeji.web.service.RFID.RFIDSocket; import com.zhehekeji.web.service.RFID.RFIDSocketFactory; +import com.zhehekeji.web.service.client.ClientChanel; +import com.zhehekeji.web.service.client.SCTransmission; import com.zhehekeji.web.service.damLightSource.JYDAMEquip; import com.zhehekeji.web.service.damLightSource.JYDamHelper; import com.zhehekeji.web.service.hikLightSource.HikControlSocket; @@ -37,6 +39,8 @@ import java.util.concurrent.BlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import static org.aspectj.weaver.tools.cache.SimpleCacheFactory.path; + /** * @Description plc信号指令处理类 * 对plcId做同步处理,防止内存中保存的正在运行的订单信息错乱 @@ -743,6 +747,7 @@ public class PlcService { } Boolean trayGoodCheck = Boolean.TRUE; + OrderInfo orderInfo = new OrderInfo(street, plcCmdInfo, 1, cmdCode); //扫货物 if(configProperties.getScanCodeMode().getGoods() == 2) { log.info("扫码类型:" + configProperties.getScanCodeMode().getTray()); @@ -800,10 +805,26 @@ public class PlcService { log.warn("rfid error:{}", trayCode); } + }else if (configProperties.getScanCodeMode().getGoods() == 4){ + //开始盘点 + String goodLocation = orderInfo.getStreetId().toString()+"_"+ plcCmdInfo.getLeftRight1().toString()+"_"+ orderInfo.getSeparation().toString()+"_"+ orderInfo.getRow().toString()+"_"+ orderInfo.getColumn(); + SCTransmission scTransmission = new SCTransmission(street.getPlcId(),plcCmdInfo.getTaskId(),goodLocation,plcCmdInfo.getTypeNum(), plcCmdInfo.getQuantity(), "N"); + + String scTransmissionStr = scTransmission.toString(); + ClientChanel.write(scTransmissionStr,street.getPlcId()); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + //滁州开始盘点即取货到位 + + SCTransmission scTransmission2 = new SCTransmission(street.getPlcId(),plcCmdInfo.getTaskId()); + String scTransmissionStr2 = scTransmission2.toSC02String(); + ClientChanel.write(scTransmissionStr2,street.getPlcId()); } - OrderInfo orderInfo = new OrderInfo(street, plcCmdInfo, 1, cmdCode); Stock stock = stockMapper.getByStreetAndDirectionAndSideAndRowColumn(orderInfo.getStreetId(), plcCmdInfo.getLeftRight1(), orderInfo.getSeparation(), orderInfo.getRow(), orderInfo.getColumn()); String scanCode = null; @@ -845,7 +866,6 @@ public class PlcService { stock.setWmsCode(wmsCode); stockMapper.updateById(stock); } - checkLog(stock); // StockCheckRunnable stockCheckRunnable = new StockCheckRunnable(street,plcCmdInfo,cmdCode,stockMapper,path,checkLogMapper,configProperties.getScanCodeMode().getGoods(),wmsCode,wmsTrayCode,trayCode,trayCheck,configProperties,sensorGun); // threadPoolExecutor.execute(stockCheckRunnable); //还原相机 @@ -935,4 +955,31 @@ public class PlcService { return configProperties; } + public void visualInventory(SCTransmission scTransmission) { + String[] location = scTransmission.getGoodsLocation().split("_"); + Stock stock = stockMapper.getByStreetAndDirectionAndSideAndRowColumn( + Integer.parseInt(location[0]),//巷道id + Integer.parseInt(location[1]),//左右 + Integer.parseInt(location[2]),//内外 + Integer.parseInt(location[3]),//行 + Integer.parseInt(location[4]));//列 + + //收到盘点结果 + if (stock == null) { + stock = Stock.builder() + .category(scTransmission.getRstCategory()) + .count(scTransmission.getRstCount()) + .status(scTransmission.getCheckRst().equals("1")?2:1) + .exportTime(LocalDateTime.now()).build(); + stockMapper.insert(stock); + } else { + stock.setCategory(scTransmission.getRstCategory()); + stock.setCount(scTransmission.getRstCount()); + if(stock.getStatus() != 1) + stock.setStatus(scTransmission.getCheckRst().equals("1")?2:1); + stock.setExportTime(LocalDateTime.now()); + stockMapper.updateById(stock); + } + checkLog(stock); + } } diff --git a/web/src/main/java/com/zhehekeji/web/service/client/CETransmission.java b/web/src/main/java/com/zhehekeji/web/service/client/CETransmission.java new file mode 100644 index 0000000..d06acf3 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/CETransmission.java @@ -0,0 +1,32 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; + +@Data +/** + * 与客户端建立连接的传输体 + */ +public class CETransmission { + + private static String HEADER = "CE"; + private static String Split = "&"; + + private String SRMNumber; + + public String toString(){ + return HEADER + Split + SRMNumber; + } + + public static String getHEADER(){ + return HEADER; + } + + public CETransmission(String body){ + String [] strings = body.split(Split); + if(strings != null && strings.length == 2 && HEADER.equals(strings[0])){ + SRMNumber = strings[1]; + } + + } + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/ClientChanel.java b/web/src/main/java/com/zhehekeji/web/service/client/ClientChanel.java new file mode 100644 index 0000000..4231f2d --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/ClientChanel.java @@ -0,0 +1,111 @@ +package com.zhehekeji.web.service.client; + + +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 所有的客户端的chanel + */ +@Slf4j +public class ClientChanel { + + + static final Logger tcpLogger = LoggerFactory.getLogger("tcp"); + + /** + * key : 巷道标识符 + */ + static Map channelMap = new ConcurrentHashMap<>(); + + + /** + * key : 巷道标识符 + */ + static Map channelStringTime = new ConcurrentHashMap<>(); + + + /** + * key :IP + * value: 巷道标识符 + */ + static Map IP_SRMNumberMap = new ConcurrentHashMap<>(); + + /** + * key :巷道标识符 + * value: IP + */ + static Map SRMNumber_IPMap = new ConcurrentHashMap<>(); + + public static void putIp(String ip,String ID){ + IP_SRMNumberMap.put(ip,ID); + } + + public static void putSRMNUmber_Ip(String ID,String ip){ + SRMNumber_IPMap.put(ID,ip); + IP_SRMNumberMap.put(ip,ID); + } + + public static String getIpFromId(String ID){ + return SRMNumber_IPMap.get(ID); + } + + public static void deleteIp(String ip){ + IP_SRMNumberMap.remove(ip); + } + + public static String getIDFromIp(String ip){ + return IP_SRMNumberMap.get(ip); + } + + public static void connect(String SRMNumber, Channel channel){ + channelMap.put(SRMNumber,channel); + InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress(); + String clientIp = socketAddress.getAddress().getHostAddress(); + putSRMNUmber_Ip(SRMNumber, clientIp); + channelStringTime.put(SRMNumber,LocalDateTime.now()); + log.info("connect:{}巷道 ", SRMNumber); + } +// static { +// Timer timer = new Timer(); +// timer.scheduleAtFixedRate(new TimerTask() { +// @Override +// public void run() { +// for (String key :channelStringTime.keySet()){ +// if(LocalDateTime.now().equals(channelStringTime.get(key).plusMinutes(5))) { +// channelStringTime.remove(key); +// disConnect(key); +// } +// } +// } +// },0,60000); +// } + public static void disConnect(String key){ + channelMap.remove(key); + } + + public static Set keys(){ + return channelMap.keySet(); + } + + public static Channel get(String key){ + return channelMap.get(key); + } + + public static void write(String data,String key){ + if(channelMap.get(key) != null){ + channelMap.get(key).writeAndFlush(data); + }else { + tcpLogger.info("no connect client:{}",key); + } + } + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/ClientCodeMap.java b/web/src/main/java/com/zhehekeji/web/service/client/ClientCodeMap.java new file mode 100644 index 0000000..bb3a427 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/ClientCodeMap.java @@ -0,0 +1,24 @@ +package com.zhehekeji.web.service.client; + +import java.util.Hashtable; +import java.util.Map; + +public class ClientCodeMap { + + /** + * 线程安全的map + */ + protected static Map codeMap = new Hashtable<>(); + + public static void putCode(String SRMNumber,String code){ + codeMap.put(SRMNumber,code); + } + + public static String getCode(String SRMNumber){ + return codeMap.get(SRMNumber); + } + + public static void removeCode(String SRMNumber){ + codeMap.remove(SRMNumber); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/Decoder.java b/web/src/main/java/com/zhehekeji/web/service/client/Decoder.java new file mode 100644 index 0000000..7b1e478 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/Decoder.java @@ -0,0 +1,145 @@ +package com.zhehekeji.web.service.client; + +import com.zhehekeji.web.entity.Stock; +import com.zhehekeji.web.service.PlcService; +import com.zhehekeji.web.service.ksec.*; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.zhehekeji.web.service.ksec.KsecDecoder.ksecInfoMap; +import static org.aspectj.weaver.tools.cache.SimpleCacheFactory.path; + +/** + * 客户端解码器 连接用 + */ +@Slf4j +public class Decoder extends DelimiterBasedFrameDecoder { + + private static final Logger tcpLogger = LoggerFactory.getLogger("tcp"); + + public static String PT_CLIENT = "PT"; + private static String WCS_CLIENT = "WCS"; + private static String EMPTY_CLIENT = "EMPTY"; + private static String END_STRING = "$"; + + private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,15,30, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(20000)); + + private PlcService plcService; + + public Decoder(PlcService plcService) { + + super(20000,true,false, Unpooled.copiedBuffer(">".getBytes()), + Unpooled.copiedBuffer("$".getBytes())); + this.plcService = plcService; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + + in = (ByteBuf) super.decode(ctx, in); + if(in == null){ + log.debug("no data"); + return null; + } + ClientRunnable clientRunnable = new ClientRunnable(in,ctx,plcService); + threadPoolExecutor.execute(clientRunnable); + return null; + } + + public static class ClientRunnable implements Runnable { + + private ByteBuf in; + + private ChannelHandlerContext ctx; + + private PlcService plcService; + + + public ClientRunnable(ByteBuf in,ChannelHandlerContext ctx,PlcService plcService){ + this.ctx = ctx; + this.in = in; + this.plcService = plcService; + } + + @Override + public void run() { + String body = in.toString(Charset.forName("UTF-8")); + tcpLogger.info("receive client:{}, data length:{}",body, body.length()); + //视觉服务 + { + if(body.startsWith(HBTransmission.getHEADER())) { + //心跳 + HBTransmission hbTransmission = new HBTransmission(body); + //回复客户端心跳 + ctx.channel().writeAndFlush(hbTransmission.toString()); + ClientChanel.connect(hbTransmission.getSRMNumber(), ctx.channel()); + //tcpLogger.info("client:{} heart", hbTransmission.getSRMNumber()); + in.release(); + } else if(body.startsWith(SCTransmission.getHeader())){ + //盘点指令 + SCTransmission scTransmission = new SCTransmission(body); + if(scTransmission.isCollectOver()){ + //给普天发送数据采集完毕指令 + KsecDataInfo ksecDataInfo = ksecInfoMap.get(scTransmission.getTaskNo()); + ksecDataInfo.setCmdName("E2"); + KsecInfo ksecInfo = new KsecInfo("KC","E",ksecDataInfo); + KsecNettyClient.write(ksecInfo); + }else { + plcService.visualInventory(scTransmission); + + KsecDataInfo ksecDataInfo = ksecInfoMap.get(scTransmission.getTaskNo()); + ksecDataInfo.setCmdName("E3"); + ksecDataInfo.setQuantity(scTransmission.getRstCount()); + ksecDataInfo.setTypeNum(scTransmission.getRstCategory()); + ksecDataInfo.setCheckRlt(Integer.parseInt(scTransmission.getCheckRst())); + KsecInfo ksecInfo = new KsecInfo("KC","E",ksecDataInfo); + KsecNettyClient.write(ksecInfo); + ksecInfoMap.remove(scTransmission.getTaskNo()); + //添加到实时信息里 + //RealtimeCheckMap.put(scTransmission.getSRMNumber(),scTransmission.checkInfo()); + //更新盤點統計 +// emptyCheckService.updateCheckLastTime(tmTransmission.getTaskNo(),tmTransmission.getSRMNumber(),stock.getCode()); + } + in.release(); + }else if(body.startsWith(CETransmission.getHEADER())){ + //客户端建立连接 + CETransmission ceTransmission = new CETransmission(body); + //回复客户端,建立连接完成 + + ctx.channel().writeAndFlush(ceTransmission.toString()); + ClientChanel.connect(ceTransmission.getSRMNumber(), ctx.channel()); + tcpLogger.info("client:{} connect", ceTransmission.getSRMNumber()); + in.release(); + } + else if(body.startsWith("DC")){ + //客户端断开连接 + String [] strings = body.split("&"); + if(strings != null && strings.length == 2){ + tcpLogger.info("client:{} disConnect", strings[1]); + ClientChanel.disConnect(strings[1]); + } + in.release(); + } + +// else if (body.contains("EMPTY_CLIENT")){ +// ClientChanel.connect(EMPTY_CLIENT_NAME, ctx.channel()); +// tcpLogger.info("client:{} connect", EMPTY_CLIENT_NAME); +// in.release(); +// } + } + } + } + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/ECResultMessage.java b/web/src/main/java/com/zhehekeji/web/service/client/ECResultMessage.java new file mode 100644 index 0000000..68eaae9 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/ECResultMessage.java @@ -0,0 +1,51 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j + +@Data +/** + * 空货位盘点 + * + * EC&6&17455&UL061001050111,1;UL061002050111,0;UL061003050111,1;>(算法客户端发送) + * UL061001050111,1 货位号,是否有货(0:无货,1:有货) + */ + +public class ECResultMessage { + private static String HEADER = "EC"; + private static String SPLIT = "&"; + private static String SPLIT_EMP = ";"; + private String SRMNumber; + private String taskId; + private String emptyCheckMsg; + private List ecTransMissionList; + + public ECResultMessage(String body){ + ecTransMissionList = new ArrayList<>(); + log.info("body: {}", body); + String [] strings = body.split(SPLIT); + if(strings != null && strings.length == 4 && HEADER.equals(strings[0])){ + SRMNumber = strings[1]; + taskId = strings[2]; + emptyCheckMsg = strings[3]; + log.info("srmNumber:{}, taskId:{},emptyCheckMsg:{}", SRMNumber, taskId, emptyCheckMsg); + String[] checkMsgStrs = emptyCheckMsg.split(SPLIT_EMP); + for (String checkMsgStr : checkMsgStrs) { + if (!checkMsgStr.isEmpty() && checkMsgStr.contains(",")){ + log.info("checkMsgStr: {}", checkMsgStr); + String[] msgStrs = checkMsgStr.split(","); + String goodsLocation = msgStrs[0]; + String empty = msgStrs[1]; + ECTransmission ecTransmission = new ECTransmission(SRMNumber, taskId, goodsLocation, empty); + ecTransMissionList.add(ecTransmission); + } + } + } + } + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/ECTransmission.java b/web/src/main/java/com/zhehekeji/web/service/client/ECTransmission.java new file mode 100644 index 0000000..7b0b07d --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/ECTransmission.java @@ -0,0 +1,125 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; + +@Data +/** + * 空货位盘点 + * + * ■ EC,SRM001,BY201710250940368150001,START;(服务端发送) + * ■ EC,SRM001,BY201710250940368150001,R010002001(货位号), Y/N;(客户端对每个货位回复有无,对于双伸的货位需要回复两次(浅深货位)) + * ■ EC,SRM001,BY201710250940368150001,END ;(服务端发送)。 + * + */ +public class ECTransmission { + + private static String HEADER = "EC"; + private static String Split = "&"; + +// private static String Split = ","; + + private String SRMNumber; + + private String taskId; + + private String type; + + private String goodsLocation; + + private String isEmpty; + + private String emptyMsg; + + /** + * 左右 1:左 2:右 + */ + private Integer direction; + + /** + * 深浅 1:浅 2:深 + */ + private Integer side; + + private Integer row; + + private Integer column; + + private Integer startColumn; + + private Integer endColumn; + + /** + * 货位条码 + */ + private String originCode; + +// public String toString(){ +// return HEADER + Split + SRMNumber; +// } + + public static String getHEADER(){ + return HEADER; + } + + public ECTransmission(String body){ + String [] strings = body.split(Split); + if(strings != null && strings.length == 4 && HEADER.equals(strings[0])){ + SRMNumber = strings[1]; + taskId = strings[2]; +// goodsLocation = strings[3]; +// isEmpty = strings[4]; + emptyMsg = strings[3]; +// direction = goodsLocation.substring(0).equals("L") ? 1:2; +// side = Integer.valueOf(goodsLocation.substring(2,3)); +// row = Integer.valueOf(goodsLocation.substring(3,6)); +// column = Integer.valueOf(goodsLocation.substring(6,9)); + }else if(strings != null && strings.length == 8 && HEADER.equals(strings[0])){ + //todo length == 9 + SRMNumber = strings[1]; + taskId = strings[2]; + startColumn = Integer.valueOf(strings[4]); + endColumn = Integer.valueOf(strings[5]); + goodsLocation = strings[6]; + direction = goodsLocation.substring(0).equals("L") ? 1:2; + side = Integer.valueOf(goodsLocation.substring(2,3)); + row = Integer.valueOf(goodsLocation.substring(3,6)); + column = Integer.valueOf(goodsLocation.substring(6,9)); + //todo 7 8 + originCode = "ddddd"; + isEmpty = strings[7]; + } + } + + public ECTransmission(String srmNumber, String id, String location, String empty){ + SRMNumber = srmNumber; + taskId = id; + goodsLocation = location; + isEmpty = empty; + + // UL061001050111 + row = Integer.valueOf(goodsLocation.substring(8,10)); + column = Integer.valueOf(goodsLocation.substring(5,8)); + direction = Integer.valueOf(goodsLocation.substring(4,5)); + side = Integer.valueOf(goodsLocation.substring(11,12)); + } + + /** + * 空托盘检测开始信息 + * @return + */ + public static String toEmptyCheckStartString(String SRMNumber, String taskId){ + StringBuffer sb = new StringBuffer(HEADER); + sb.append(Split).append(SRMNumber).append(Split).append(taskId).append(Split).append("START"); + return sb.toString(); + } + + /** + * 空托盘检测 结束信号 + * @return + */ + public static String toEmptyCheckEndString(String SRMNumber, String taskId){ + StringBuffer sb = new StringBuffer(HEADER); + sb.append(Split).append(SRMNumber).append(Split).append(taskId).append(Split).append("END"); + return sb.toString(); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/EmptyCheckCodeInfo.java b/web/src/main/java/com/zhehekeji/web/service/client/EmptyCheckCodeInfo.java new file mode 100644 index 0000000..69dcebf --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/EmptyCheckCodeInfo.java @@ -0,0 +1,41 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 空货位扫描到的全部条码 + */ +@Data +public class EmptyCheckCodeInfo { + + private static Map> allCode = new HashMap<>(); + + public static void start(String SRMNumber, String taskNo){ + String key = String.format("{}-{}",SRMNumber, taskNo); + allCode.remove(key); + } + + public static void addCode(String SRMNumber,Integer row,Integer startColumn,Integer endColumn,String code){ + String key = String.format("{}-{}-{}-{}",SRMNumber,row,startColumn,endColumn); + Set codes = allCode.get(key); + if(codes == null){ + codes = new HashSet<>(); + } + codes.add(code); + } + + public static Set getAllCode(String SRMNumber, String taskNo){ + String key = String.format("{}-{}",SRMNumber, taskNo); + return allCode.get(key); + } + + public static void stop(String SRMNumber,Integer row,Integer startColumn,Integer endColumn){ + String key = String.format("{}-{}-{}-{}",SRMNumber,row,startColumn,endColumn); + allCode.remove(key); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/Encoder.java b/web/src/main/java/com/zhehekeji/web/service/client/Encoder.java new file mode 100644 index 0000000..a523252 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/Encoder.java @@ -0,0 +1,24 @@ +package com.zhehekeji.web.service.client; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; + +/** + * 客户端传输协议 以;结尾 + */ +public class Encoder extends MessageToByteEncoder { + private static String END_STRING = ">"; + + private static final Logger tcpLogger = LoggerFactory.getLogger("tcp"); + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, String data, ByteBuf byteBuf) throws Exception { + data = data + END_STRING; + tcpLogger.info("send to client:{}, length:{}",data, data.length()); + byteBuf.writeBytes(data.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/HBTransmission.java b/web/src/main/java/com/zhehekeji/web/service/client/HBTransmission.java new file mode 100644 index 0000000..779812b --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/HBTransmission.java @@ -0,0 +1,36 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; + +@Data +/** + * 与客户端的心跳 + */ +public class HBTransmission { + + private static String HEADER = "HB"; + private static String Split = "&"; + + private String SRMNumber; + + public String toString(){ + return HEADER + Split + SRMNumber; + } + + public static String getHEADER(){ + return HEADER; + } + + public HBTransmission(String body){ + String [] strings = body.split(Split); + if(strings != null && strings.length == 2 && HEADER.equals(strings[0])){ + SRMNumber = strings[1]; + } + + } + + public static void main(String[] args) { + HBTransmission ceTransmission= new HBTransmission("HB&1"); + System.out.println(ceTransmission); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/ISTransmission.java b/web/src/main/java/com/zhehekeji/web/service/client/ISTransmission.java new file mode 100644 index 0000000..26d0e93 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/ISTransmission.java @@ -0,0 +1,44 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; + +@Data +public class ISTransmission { + + private static String HEADER = "IS"; + + private static String Split = ","; + + private String SRMNumber; + + private String taskId; + + private String goodsLocation; + + private String code; + + public String toString(){ + StringBuffer sb = new StringBuffer(); + sb.append(HEADER).append(Split).append(SRMNumber).append(Split).append(taskId).append(Split).append(goodsLocation); + if(code != null){ + sb.append(Split).append(code); + } + return sb.toString(); + } + + public ISTransmission(String SRMNumber,String taskId,String goodsLocation){ + this.SRMNumber = SRMNumber; + this.goodsLocation = goodsLocation; + this.taskId = taskId; + } + + public ISTransmission(String msg){ + String [] strings = msg.split(Split); + HEADER = strings[0]; + SRMNumber = strings[1]; + goodsLocation = strings[2]; + if(strings.length == 4){ + code = strings[3]; + } + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/LCTransmission.java b/web/src/main/java/com/zhehekeji/web/service/client/LCTransmission.java new file mode 100644 index 0000000..d2329c9 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/LCTransmission.java @@ -0,0 +1,34 @@ +package com.zhehekeji.web.service.client; + + +import lombok.Data; + +@Data +/** + * 开关灯 + */ +public class LCTransmission { + + private static String HEADER = "LC"; + + private String SRMNumber; + + /** + * 1:开 0:关 + */ + private Integer status; + + public String toString(){ + return HEADER + "&" + SRMNumber+"&"+status; + } + + public static String getHEADER(){ + return HEADER; + } + + public LCTransmission(String SRMNumber,Integer status){ + this.SRMNumber = SRMNumber; + this.status = status; + } + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/MessageConverter.java b/web/src/main/java/com/zhehekeji/web/service/client/MessageConverter.java new file mode 100644 index 0000000..5f767f0 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/MessageConverter.java @@ -0,0 +1,13 @@ +package com.zhehekeji.web.service.client; + +import org.springframework.stereotype.Component; + +/** + * 普天的消息 转给盘点客户端 + */ +@Component +public class MessageConverter { + + + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/NettyConnectHandler.java b/web/src/main/java/com/zhehekeji/web/service/client/NettyConnectHandler.java new file mode 100644 index 0000000..fe55324 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/NettyConnectHandler.java @@ -0,0 +1,44 @@ +package com.zhehekeji.web.service.client; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import lombok.extern.slf4j.Slf4j; + +import java.net.InetSocketAddress; + +/** + * 客户端的上下线 + * + * @author Administrator + * + */ +@Slf4j +public class NettyConnectHandler extends ChannelInboundHandlerAdapter { + + /** + * 建立连接时 + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + String clientIp = socketAddress.getAddress().getHostAddress(); + int clientPort = socketAddress.getPort(); + log.info("ip:{} port:{} connected",clientIp, clientPort); + ctx.fireChannelActive(); + } + + /** + * 关闭连接时 + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + String clientIp = socketAddress.getAddress().getHostAddress(); + int clientPort = socketAddress.getPort(); + log.info("ip:{} port:{} disconnected",clientIp, clientPort); + String ID = ClientChanel.getIDFromIp(clientIp); + //设置客户端下线 + ClientChanel.disConnect(ID); + } + +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/NettyServer.java b/web/src/main/java/com/zhehekeji/web/service/client/NettyServer.java new file mode 100644 index 0000000..6ea7758 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/NettyServer.java @@ -0,0 +1,60 @@ +package com.zhehekeji.web.service.client; + +import com.zhehekeji.web.service.PlcService; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import lombok.SneakyThrows; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +public class NettyServer { + + @Resource + private PlcService plcService; + + + public void CreateNettyServer(int port) throws InterruptedException { + Thread thread = new Thread(new Runnable() { + @SneakyThrows + @Override + public void run() { + EventLoopGroup bossGroup = new NioEventLoopGroup(); + // 从线程组, 老板线程组会把任务丢给他,让手下线程组去做任务 + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + // netty服务器的创建, 辅助工具类,用于服务器通道的一系列配置 + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup) //绑定两个线程组 + .channel(NioServerSocketChannel.class) //指定NIO的模式 + + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new NettyConnectHandler()); + ch.pipeline().addLast(new Decoder(plcService)); + ch.pipeline().addLast(new Encoder()); + }}); // 子处理器,用于处理workerGroup + + // 启动server,并且设置8088为启动的端口号,同时启动方式为同步 + ChannelFuture channelFuture = serverBootstrap.bind(port).sync(); + + // 监听关闭的channel,设置位同步方式 + channelFuture.channel().closeFuture().sync(); + } finally { + //退出线程组 + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + }); + thread.start(); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/RealtimeCheckMap.java b/web/src/main/java/com/zhehekeji/web/service/client/RealtimeCheckMap.java new file mode 100644 index 0000000..59289ab --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/RealtimeCheckMap.java @@ -0,0 +1,87 @@ +package com.zhehekeji.web.service.client; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@Data +public class RealtimeCheckMap { + + private static Map map = new HashMap<>(); + + public static void put(String SRMNumber,CheckInfo checkInfo){ + checkInfo.setTime(LocalDateTime.now()); + String ip ="http://"+ClientChanel.getIpFromId(SRMNumber)+":9009/pic/"+checkInfo.getCheckCode()+"/"+checkInfo.getTaskNo()+"/"; + checkInfo.setIP(ip); + checkInfo.setSidePic1(String.format("%s1.png",ip)); + checkInfo.setSidePic2(String.format("%s2.png",ip)); + checkInfo.setSidePic3(String.format("%s3.png",ip)); + checkInfo.setSidePic4(String.format("%s4.png",ip)); + checkInfo.setTopPic1(String.format("%s5.png",ip)); + checkInfo.setTopPic2(String.format("%s6.png",ip)); + map.put(SRMNumber,checkInfo); + } + + public static CheckInfo getRealtimeCheck(String SRMNumber){ + return map.get(SRMNumber); + } + + @Data + public static class CheckInfo{ + + private Integer row; + + private Integer column; + + private Integer side; + + private Integer direction; + + private String WMSCode; + + private String WMSCategory; + + private Integer WMSCount; + + private String checkCode; + + private String checkCategory; + + private Integer checkCount; + + private Integer status; + + private String topPic1; + + private String topPic2; + + private String sidePic1; + + private String sidePic2; + + private String sidePic3; + + private String sidePic4; + + private String IP; + + private String taskNo; + + @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss") + private LocalDateTime time; + + public Integer getStatus(){ + if(WMSCode.equals(checkCount) && checkCount.equals(WMSCount)){ + return 1; + } + return 0; + } + + + + + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/client/SCTransmission.java b/web/src/main/java/com/zhehekeji/web/service/client/SCTransmission.java new file mode 100644 index 0000000..591cad1 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/client/SCTransmission.java @@ -0,0 +1,181 @@ +package com.zhehekeji.web.service.client; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +/** + * 盘点指令 + * 与客户端的传输内容 + * + */ +@Data +public class SCTransmission { + + private static String header = "SC"; + + private String SRMNumber; + + /** + * 搬运任务号 + */ + private String taskNo; + + /** + * 货位号 + */ + private String goodsLocation; + + /** + * 托盘号 + */ + private String trayNo; + + + private String code; + /** + * 上位机下发品规 + */ + private String category; + private Integer count; + + /** + * 盘点结果 + */ + private String rstCategory; + private Integer rstCount; + + private String visualTest; + + private String isDisConnect; + + /** + * 盘点结果是否正确 + */ + private String checkRst; + + /** + * 左右 1:左 2:右 + */ + private Integer direction; + + /** + * 深浅 1:浅 2:深 + */ + private Integer side; + + private static String Split = "&"; + + private boolean collectOver; + + public String toString(){ + StringBuffer sb = new StringBuffer(header); + sb.append(Split).append(SRMNumber).append(Split).append(goodsLocation).append(Split) + .append(taskNo).append(Split).append(code) + .append(Split).append(count).append(Split).append(visualTest); + return sb.toString(); + } + + public String toSC02String(){ + return header+Split+SRMNumber+Split+taskNo; + } + + public static String getHeader(){ + return header; + } + + // public SCTransmission(String body){ +// String [] strings = body.split(Split); +// if(strings != null && strings.length >= 9 && strings[0].equals(header)){ +// if(strings.length == 9){ +// SRMNumber = strings[1]; +// taskNo = strings[2]; +// goodsLocation = strings[3]; +// trayNo = strings[4]; +// code = strings[5]; +// count = Integer.valueOf(strings[6]); +// visualTest = strings[7]; +// isDisConnect = strings[8]; +// direction = goodsLocation.substring(0).equals("L") ? 1:2; +// side = Integer.valueOf(goodsLocation.substring(2,3)); +// //checkType = strings[7]; +// collectOver = false; +// if("Y".equals(isDisConnect)){ +// //需要拆分之前的 +// } +// } +// +// }else if(strings.length == 3){ +// //是盘点数据采集完成的信号 +// collectOver = true; +// SRMNumber = strings[1]; +// taskNo = strings[2]; +// //goodsLocation = strings[3]; +// } +// } +//TM&6&UL062009050111&2121&11010019&30&NULL&NULL + public SCTransmission(String body){ + log.info("tmtrans body: {}", body); + String [] strings = body.split(Split); + if(strings != null && strings.length >= 9 && strings[0].equals(header)){ + if(strings.length == 9){ + SRMNumber = strings[1]; + goodsLocation = strings[2]; + taskNo = strings[3]; + category = strings[4]; + count = Integer.valueOf(strings[5]); + rstCategory = strings[6]; + rstCount = Integer.valueOf(strings[7]); + checkRst = strings[8]; + collectOver = false; + log.info("taskNo:{}, srmNumber:{}, goodsLocation:{}", taskNo, SRMNumber, goodsLocation); +// if("Y".equals(isDisConnect)){ +// //需要拆分之前的 +// } + } + + }else if(strings.length == 3){ + //是盘点数据采集完成的信号 + collectOver = true; + SRMNumber = strings[1]; + taskNo = strings[2]; + //goodsLocation = strings[3]; + } + } + + public SCTransmission(String SRMNumber,String taskId,String goodsLocation,String code,Integer count,String visual){ + this.SRMNumber = SRMNumber; + this.taskNo = taskId; + this.goodsLocation = goodsLocation; + this.code = code; + this.count = count; + this.trayNo = "Unknown"; + this.visualTest = visual; + } + + public SCTransmission(String SRMNumber,String taskId){ + this.SRMNumber = SRMNumber; + this.taskNo = taskId; + } + + public RealtimeCheckMap.CheckInfo checkInfo(){ + log.info("cheinfo start"); + RealtimeCheckMap.CheckInfo checkInfo = new RealtimeCheckMap.CheckInfo(); + Integer row = Integer.valueOf(goodsLocation.substring(8,10)); + Integer column = Integer.valueOf(goodsLocation.substring(5,8)); + Integer leftRight = Integer.valueOf(goodsLocation.substring(4,5)); + Integer inout = Integer.valueOf(goodsLocation.substring(11,12)); + checkInfo.setDirection(leftRight); + checkInfo.setColumn(column); + checkInfo.setRow(row); + checkInfo.setSide(inout); + checkInfo.setTaskNo(this.taskNo); +// checkInfo.setWMSCategory(category); +// checkInfo.setWMSCount(count); +// checkInfo.setWMSCode(code); + checkInfo.setCheckCode(goodsLocation); + checkInfo.setCheckCount(rstCount); + //checkInfo.setCheckCategory(); + return checkInfo; + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDataInfo.java b/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDataInfo.java index 90ac787..0737b83 100644 --- a/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDataInfo.java +++ b/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDataInfo.java @@ -1,5 +1,6 @@ package com.zhehekeji.web.service.ksec; +import io.swagger.models.auth.In; import lombok.Data; @Data @@ -39,6 +40,12 @@ public class KsecDataInfo { private String trayCode; + private String typeNum; + + private Integer quantity; + + private Integer checkRlt; + /** * 盘点批次号 */ diff --git a/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java b/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java index d40c757..dba44aa 100644 --- a/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java +++ b/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java @@ -16,6 +16,8 @@ import org.springframework.util.StringUtils; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -34,6 +36,8 @@ public class KsecDecoder extends DelimiterBasedFrameDecoder { private static String lastLotnum; + public static Map ksecInfoMap = new HashMap<>(); + private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(7,21,30, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(20000)); private PlcService plcService; @@ -95,7 +99,8 @@ public class KsecDecoder extends DelimiterBasedFrameDecoder { if(dataInfo != null){ plcCmdInfo = new PlcCmdInfo(dataInfo.getSRMNumber(), dataInfo.getTaskId(), dataInfo.getFromSide(), dataInfo.getFromDirection(), dataInfo.getFromColumn(), dataInfo.getFromRow(), dataInfo.getFromSeparation(),dataInfo.getToSide(), dataInfo.getToDirection(), dataInfo.getToColumn(), dataInfo.getToRow(),dataInfo.getToSeparation(),lotnum); - + plcCmdInfo.setTypeNum(dataInfo.getTypeNum()); + plcCmdInfo.setQuantity(dataInfo.getQuantity()); srmNumber = dataInfo.getSRMNumber(); cmdName = dataInfo.getCmdName(); } @@ -182,18 +187,12 @@ public class KsecDecoder extends DelimiterBasedFrameDecoder { // log.info("other D code :{}",code); // } } else if (Cmd.E.name().equals(ksecInfo.getType())) { - //成都蜜雪冰城 没有盘点批次号,把这一块注释 lotnum -// if(!StringUtils.isEmpty(lotnum) && !lotnum.equals(lastLotnum)){ -// //需要把stock表truncate -// FileUtil.save(lotnum,"lastLotnum"); -// tcpLogger.info("truncate table ,last lotnum:{},new lotnum:{}",lastLotnum,lotnum); -// plcService.truncateStock(); -// lastLotnum = lotnum; -// } + //盘点 //转球机到盘点位 然后拍照 plcCmdInfo.setTimes(1); - //成都蜜雪冰城 将lotnum当成托盘条码描述 + ksecInfoMap.put(dataInfo.getTaskId(),dataInfo); + Boolean ok = plcService.check(plcCmdInfo,ksecInfo.getData().getCmdName(), dataInfo.getCode(), dataInfo.getTrayCode(), dataInfo.getLotnum()); // Boolean ok = true; if(ok){ diff --git a/web/src/main/resources/application-prod.yml b/web/src/main/resources/application-prod.yml index 0cc6d0e..9f60935 100644 --- a/web/src/main/resources/application-prod.yml +++ b/web/src/main/resources/application-prod.yml @@ -65,7 +65,7 @@ savePath: # ------------服务端类型 0:TCP(罗伯泰克) 1:KSEC(JSON)(昆船) serverMode: 1 ksec: - ip: 192.168.168.11 + ip: 127.0.0.1 port: 8001 #断点重连的次数:-1->不断重连 reconnectNum: -1 @@ -85,10 +85,10 @@ lightSource: index: 1 # -----tray 托盘 # -----goods 货物 -# 扫码模式 0:此处不盘点 1:球机扫码 2:sick扫码枪 3:南北达RFID +# 扫码模式 0:此处不盘点 1:球机扫码 2:sick扫码枪 3:南北达RFID 4.南京视觉客户端(只支持货物盘点) scanCodeMode: - tray: 1 - goods: 0 + tray: 3 + goods: 4 goodsCodeTypes: - 14 trayCodeTypes: @@ -98,4 +98,6 @@ deleteFileDays: 365 rfid: codeType: ISO18000_6C - scanTime: 2 \ No newline at end of file + scanTime: 2 +#视觉服务接收端口 +serverPort: 5554 \ No newline at end of file