diff --git a/README.md b/README.md index ae13e55c37b4e3927d9234f2a4320a912005fed8..7f3a837fef642e48f1f7f521d4096981073c958e 100644 --- a/README.md +++ b/README.md @@ -354,4 +354,68 @@ FF:SID; 0102:写指令; -0000:写入成功标识。 \ No newline at end of file +0000:写入成功标识。 + + + +# 三、错误代码 + +![img.png](img.png) + + + +# 四、测试 + +##### 注:DB 需要 x 256 在转16进制 + + + +#### 读取 + +```TXT +读指令:46494E530000001A000000020000000080000200CA00003D00FF0101821774000001 +46494E53 +0000001A +00000002 +00000000 +80 +00 +02 +00CA00 +003D00 +FF +0101:读指令 +82:D字 +177400:DB位 × 256 +0001:偏移量 +返回数据:46 49 4E 53 00 00 00 18 00 00 00 02 00 00 00 00 C0 00 02 00 3D 00 00 CA 00 FF 01 01 00 00 00 07 +说明:读取D6004,返回数据为7 +``` + + + +#### 写入 + +```TXT +写指令:46494E5300000023000000020000000080000200CA00003D00FF010202177000000101 +46494E53 +00000023 +00000002 +00000000 +80 +00 +02 +00CA00 +003D00 +FF +0102:写指令 +02:D字 +177000:DB位 × 256 +0001:偏移量 +01:写入值 +返回值:46 49 4E 53 00 00 00 16 00 00 00 02 00 00 00 00 C0 00 02 00 3D 00 00 CA 00 FF 01 02 00 00 +说明:写入D6000.1,值为true,长度不足四位需要补足 +``` + + + diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..adb186d1996cce43285b4a6305eb348ad0035be3 Binary files /dev/null and b/img.png differ diff --git a/pom.xml b/pom.xml index 33c93a183e1f98610b7b3279438f310d1b0a7dd0..a509c52500f8ee321cf2ff6f21668480039d4322 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ com.fjg omorn-tcp-fins 0.0.1-SNAPSHOT - omorn-tcp-fins - omorn-tcp-fins + omron-tcp-fins + omron-tcp-fins 1.8 UTF-8 @@ -21,12 +21,6 @@ spring-boot-starter-web - - org.springframework.boot - spring-boot-devtools - runtime - true - org.springframework.boot spring-boot-configuration-processor diff --git a/src/main/java/com/fjg/omorntcpfins/OmornTcpFinsApplication.java b/src/main/java/com/fjg/omorntcpfins/OmornTcpFinsApplication.java index 6018d8d753628881e95cff9bc0497a9877f08eb3..9d67e6f48189dd4153cb219497e43a9c0d517789 100644 --- a/src/main/java/com/fjg/omorntcpfins/OmornTcpFinsApplication.java +++ b/src/main/java/com/fjg/omorntcpfins/OmornTcpFinsApplication.java @@ -1,9 +1,11 @@ package com.fjg.omorntcpfins; +import cn.hutool.extra.spring.EnableSpringUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@EnableSpringUtil public class OmornTcpFinsApplication { public static void main(String[] args) { diff --git a/src/main/java/com/fjg/omorntcpfins/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index 0e7fbcfae613ef128542ee0fa0ad3c3ec3655df5..1b30a99e121749944aaf19eda044e476e30d0077 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -1,9 +1,16 @@ package com.fjg.omorntcpfins; -import com.fjg.omorntcpfins.config.FinsClientConfig; +import HslCommunication.Profinet.Omron.OmronFinsNet; +import cn.hutool.extra.spring.SpringUtil; +import com.fjg.omorntcpfins.handler.fins.FinsMessageHandler; +import com.fjg.omorntcpfins.config.fins.client.FinsClientConfig; +import com.fjg.omorntcpfins.util.FinsUtil; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + /** * @author fengjianguo * @date 2024/7/22 10:05 @@ -12,8 +19,50 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/send") public class TestController { @RequestMapping("/test") - public String test(String msg){ + public String test(String msg) { + FinsUtil bean = SpringUtil.getBean(FinsUtil.class); + String s1 = bean.readWord("D6004", 1); + System.out.println("readWord:" + s1); + CompletableFuture future = new CompletableFuture<>(); + FinsMessageHandler.setRequestFutures("1", future); FinsClientConfig.send(msg); + try { + String s = future.get(); + System.out.println("handler:" + s); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return "success"; + } + + /** + * 读取测试 + * + * @param db db块,D6000 + * @param offset 偏移量 1 + * @param bool 是否读取布尔值 + * @return {@link String } + */ + @RequestMapping("/send") + public String test(String db, Integer offset, Boolean bool) { + FinsUtil bean = SpringUtil.getBean(FinsUtil.class); + if (bool) { + System.out.println(bean.readBool(db, offset)); + } else { + System.out.println(bean.readWord(db, offset)); + } return "success"; } + + @RequestMapping("/write") + public String write(String db, Integer offset, String dbValue, Boolean bool) { + FinsUtil bean = SpringUtil.getBean(FinsUtil.class); + if (bool) { + System.out.println(bean.writeBool(db, offset, Boolean.valueOf(dbValue))); + } else { + System.out.println(bean.writeWord(db, offset, dbValue)); + } + return "success"; + } + } diff --git a/src/main/java/com/fjg/omorntcpfins/common/RequestMsg.java b/src/main/java/com/fjg/omorntcpfins/common/RequestMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..46e273222c8f701adc73edd469af5787e3a18e36 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/RequestMsg.java @@ -0,0 +1,148 @@ +package com.fjg.omorntcpfins.common; + + +/** + * @author fengjianguo + * @date 2024/7/29 9:47 + */ +public class RequestMsg { + + private static final String OK_CODE = "0"; + + private static final String ERROR_CODE = "-1"; + + /** + * 请求id + */ + private String requestId; + + /** + * 异常代码 + */ + private String errorCode; + + /** + * 内容 + */ + private String content; + + public RequestMsg(String requestId, String content) { + this.requestId = requestId; + this.content = content; + } + + public RequestMsg(String requestId, String content, String errorCode) { + this.requestId = requestId; + this.content = content; + this.errorCode = errorCode; + } + + public RequestMsg(String requestId) { + this.requestId = requestId; + } + + public RequestMsg() { + } + + /** + * 构建请求消息 + * + * @param requestId 请求id + * @param content 内容 + * @return {@link RequestMsg } + */ + public static RequestMsg build(String requestId, String content) { + return new RequestMsg(requestId, content); + } + + /** + * 构建请求消息 + * + * @param requestId 请求id + * @return {@link RequestMsg } + */ + public static RequestMsg build(String requestId) { + return new RequestMsg(requestId); + } + + /** + * 构建请求消息 + * + * @param requestId 请求id + * @param content 内容 + * @param errorCode 异常代码 + * @return {@link RequestMsg } + */ + public static RequestMsg build(String requestId, String content, String errorCode) { + return new RequestMsg(requestId, content, errorCode); + } + + /** + * 构建请求消息-成功 + * + * @param requestId 请求id + * @param content 内容 + * @return {@link RequestMsg } + */ + public static RequestMsg ok(String requestId, String content) { + return new RequestMsg(requestId, content, OK_CODE); + } + + /** + * 构建请求消息-失败 + * + * @param requestId 请求id + * @param content 内容 + * @return {@link RequestMsg } + */ + public static RequestMsg fail(String requestId, String content) { + return new RequestMsg(requestId, content, ERROR_CODE); + } + + /** + * 获取请求id + * + * @return {@link String } + */ + public String requestId() { + return requestId; + } + + /** + * 获取内容 + */ + public String content() { + return content; + } + + /** + * 获取异常代码 + */ + public String errorCode() { + return errorCode; + } + + /** + * 设置请求id + */ + public RequestMsg requestId(String requestId) { + this.requestId = requestId; + return this; + } + + /** + * 设置内容 + */ + public RequestMsg content(String content) { + this.content = content; + return this; + } + + /** + * 设置异常代码 + */ + public RequestMsg errorCode(String errorCode) { + this.errorCode = errorCode; + return this; + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/constant/FinsConstant.java b/src/main/java/com/fjg/omorntcpfins/common/constant/FinsConstant.java new file mode 100644 index 0000000000000000000000000000000000000000..e4a0ce982a002e2cdaa9db3a3add10d737b479d0 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/constant/FinsConstant.java @@ -0,0 +1,50 @@ +package com.fjg.omorntcpfins.common.constant; + +/** + * @author fengjianguo + * @date 2024/7/27 16:01 + */ +public class FinsConstant { + /** + * 46494E53:ASCII编码:FINS; + * + * 0000001A:指后面跟的字节长度; + * + * 00000002:固定命令; + * + * 00000000:错误代码; + * + * 80:ICF; + * + * 00:RSV; + * + * 02:GCT; + * + * 00:PLC网络地址; + * + * 17:PLC节点地址; + * + * 00:PLC单元地址; + * + * 00:PC网络地址; + * + * 18:PC节点地址; + * + * 00:PC单元地址; + * + * FF:SID; + * + * 0101:读指令; + * + * 82:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0); + * + * 006400:起始地址; + * + * 0002:读个数。 + */ + + /** + * FINS协议头 + */ + +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/AreaTypeEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/AreaTypeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..647d653ddebc5ac19d830dab5ff898aa7b25c7d5 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/AreaTypeEnum.java @@ -0,0 +1,25 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author fengjianguo + * @date 2024/7/29 11:14 + */ +@Getter +@AllArgsConstructor +public enum AreaTypeEnum { + + /** + * 0:位 + * 1:字 + */ + BIT(0, "位", "areaTypeBitStrategy"), + WORD(1, "字", "areaTypeWordStrategy"); + + private final int code; + private final String desc; + private final String strategy; + +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..3ece127df1de3443b6020910a5b38470a5adbfd9 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java @@ -0,0 +1,54 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * fins错误代码枚举 + * + * @author fengjianguo + * @date 2024/08/02 + */ +@Getter +@AllArgsConstructor +public enum FinsErrorCodeEnum { + /** + * 00000000 正常 + * 00000001 头不是‘FINS’(ASCII code) + * 00000002 数据太长 + * 00000003 不支持的命令 + * 00000020 所有的连接被占用 + * 00000021 制定的节点已经连接 + * 00000022 未指定的IP地址试图访问一个被保护的节点 + * 00000023 客户端FINS节点地址超范围 + * 00000024 相同的FINS节点地址已经被使用 + * 00000025 所有可用的节点地址都已使用 + */ + ERROR_CODE_000000000("00000000", "正常"), + ERROR_CODE_000000001("00000001", "头不是‘FINS’(ASCII code)"), + ERROR_CODE_000000002("00000002", "数据太长"), + ERROR_CODE_000000003("00000003", "不支持的命令"), + ERROR_CODE_000000020("00000020", "所有的连接被占用"), + ERROR_CODE_000000021("00000021", "指定的节点已经连接"), + ERROR_CODE_000000022("00000022", "未指定的IP地址试图访问一个被保护的节点"), + ERROR_CODE_000000023("00000023", "客户端FINS节点地址超范围"), + ERROR_CODE_000000024("00000024", "相同的FINS节点地址已经被使用"), + ERROR_CODE_000000025("00000025", "所有的可用的节点地址都已使用"), + ; + private final String code; + private final String msg; + + /** + * 校验错误码是否为正常 + */ + public static Boolean isNormal(String errorCode) { + return ERROR_CODE_000000000.getCode().equals(errorCode); + } + + /** + * 校验错误码是否为00000021 + */ + public static Boolean isConnectedAlready(String errorCode) { + return ERROR_CODE_000000021.getCode().equals(errorCode); + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/FinsFixedValueEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsFixedValueEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..e07e524b81addf8823dee2218d526bc1da6a52c8 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsFixedValueEnum.java @@ -0,0 +1,28 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author fengjianguo + * @date 2024/8/2 8:26 + */ +@Getter +@AllArgsConstructor +public enum FinsFixedValueEnum { + /** + * fins头:FINS:46494e53 + */ + FINS_HEAD("FINS", "46494e53", "FINS头"), + ; + private final String value; + private final String hexValue; + private final String desc; + + /** + * 是否是fins头 + */ + public static boolean isFinsHeadByHexValue(String finsHead) { + return FINS_HEAD.hexValue.equals(finsHead.toLowerCase()); + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/FinsReadWriteEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsReadWriteEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..66b7f460fff949f54ff1c2bac10a1a86f13f4f5e --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsReadWriteEnum.java @@ -0,0 +1,31 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author fengjianguo + * @date 2024/8/2 10:01 + */ +@Getter +@AllArgsConstructor +public enum FinsReadWriteEnum { + + /** + * FINS协议读指令:"0101"; + * FINS协议写指令: "0102" + */ + READ("0101", "读"), + WRITE("0102", "写"), + ; + + private final String code; + private final String desc; + + /** + * 是否写指令 + */ + public static boolean isWriteByCode(String code) { + return WRITE.getCode().equals(code); + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/FinsResponseFlagEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsResponseFlagEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..8935de409ce9ee6c7fb5093a8e24082b8b8e2f3e --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsResponseFlagEnum.java @@ -0,0 +1,25 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum FinsResponseFlagEnum { + /** + * 0000: 正常 + * 1103:命令错误 + */ + RESPONSE_FLAG_0000("0000", "正常"), + RESPONSE_FLAG_0103("0103", "命令错误") + ; + private final String code; + private final String msg; + + /** + * 校验响应是否为正常 + */ + public static Boolean isNormal(String responseFlag) { + return RESPONSE_FLAG_0000.getCode().equals(responseFlag); + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/MsgEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/MsgEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..ff9e021829a490f0a6f8c83b205ef6abb7588881 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/MsgEnum.java @@ -0,0 +1,34 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author Zhang GuiHong + * @date 2021/12/27 10:07 + **/ +@AllArgsConstructor +public enum MsgEnum { + + /** + * 成功 + */ + SUCCESS(0, "ok"), + + /** + * 失败 + */ + ERROR(-1, "error"), + + ; + private final Integer code; + private final String msg; + + public Integer code() { + return code; + } + + public String msg() { + return msg; + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/common/queue/HexQueue.java b/src/main/java/com/fjg/omorntcpfins/common/queue/HexQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..c923126d862641644fa5b6f2c3013425d956cf35 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/queue/HexQueue.java @@ -0,0 +1,52 @@ +package com.fjg.omorntcpfins.common.queue; + +/** + * @author fengjianguo + * @date 2024/7/30 16:12 + */ +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.IntStream; + +@Slf4j +public class HexQueue { + private static final ConcurrentLinkedQueue QUEUE = new ConcurrentLinkedQueue<>(); + + public synchronized static void init() { + IntStream + // 生成0-255的数字 + .rangeClosed(0, 255) + // 转换为两位的十六进制字符串 + .mapToObj(i -> String.format("%02x", i)) + // 添加到队列 + .forEach(QUEUE::add); + } + + /** + * 获取并删除数据的方法 + * + * @return {@link String } + */ + public synchronized static String fetchHex() { + if (QUEUE.isEmpty()){ + return null; + } + // 获取并移除队首元素 + return QUEUE.poll(); + } + + /** + * 归还数据到队列的方法 + * + * @param hexValue 十六进制值 + */ + public synchronized static void returnHex(String hexValue) { + if (hexValue != null && !hexValue.isEmpty()) { + // 将数据放回队列尾部 + QUEUE.offer(hexValue); + } + } + +} + diff --git a/src/main/java/com/fjg/omorntcpfins/config/FinsClientConfig.java b/src/main/java/com/fjg/omorntcpfins/config/FinsClientConfig.java deleted file mode 100644 index a314abce5efc95a9db771a44e8d1758d8b5c81ad..0000000000000000000000000000000000000000 --- a/src/main/java/com/fjg/omorntcpfins/config/FinsClientConfig.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.fjg.omorntcpfins.config; - -import cn.hutool.core.util.HexUtil; -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.timeout.IdleStateHandler; -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.TimeUnit; - - -/** - * Fins协议配置FinsClientConfig - * - * @author CLS - * @since 2023/08/04 - */ -@Slf4j -public class FinsClientConfig { - public static ChannelFuture channelFuture; - - public static void finsStart(String ip, int port) throws Exception { - NioEventLoopGroup eventExecutors = new NioEventLoopGroup(); - try { - //创建bootstrap对象,配置参数 - Bootstrap bootstrap = new Bootstrap(); - //设置线程组 - bootstrap.group(eventExecutors) - //设置客户端的通道实现类型 - .channel(NioSocketChannel.class) - .remoteAddress(ip, port) - .option(ChannelOption.SO_KEEPALIVE, true) - //使用匿名内部类初始化通道 - .handler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) throws Exception { - //添加客户端通道的处理器 - ch.pipeline().addLast(new MyClientHandler(bootstrap, eventExecutors)) - .addLast(new HeartBeatClientHandler()) - .addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS)); - } - }); - log.info("客户端准备就绪..."); - //连接服务端 - channelFuture = bootstrap.connect().sync(); - channelFuture.addListener(future -> { - if (future.isSuccess()) { - log.info("客户端连接成功"); - } else { - log.info("客户端连接失败"); - eventExecutors.schedule(() -> { - try { - finsStart(ip, port); - } catch (Exception e) { - log.info("客户端重连失败"); - } - }, 5, TimeUnit.SECONDS); - } - }); - //对通道关闭进行监听 - channelFuture.channel().closeFuture().sync(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - //关闭线程组 - eventExecutors.shutdownGracefully().sync(); - } - } - - /** - * 发送数据 - * - * @param msg 数据 - */ - public static void send(String msg) { - channelFuture.channel().writeAndFlush(Unpooled.wrappedBuffer(HexUtil.encodeHexStr(msg).getBytes())); - } - - public static void send(byte[] msg) { - channelFuture.channel().writeAndFlush(Unpooled.wrappedBuffer(HexUtil.encodeHexStr(msg).getBytes())); - } -} \ No newline at end of file diff --git a/src/main/java/com/fjg/omorntcpfins/config/HeartBeatClientHandler.java b/src/main/java/com/fjg/omorntcpfins/config/HeartBeatClientHandler.java deleted file mode 100644 index bb22c12364ea2cff5f90e2106c7cb7638a681b00..0000000000000000000000000000000000000000 --- a/src/main/java/com/fjg/omorntcpfins/config/HeartBeatClientHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fjg.omorntcpfins.config; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.timeout.IdleState; -import io.netty.handler.timeout.IdleStateEvent; - -import java.util.Date; - -/** - * @author fengjianguo - * @date 2024/7/22 14:28 - */ -public class HeartBeatClientHandler extends ChannelInboundHandlerAdapter { - private int curTime = 0; - private int beatTime = 3; - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - System.out.println("==channelRead==="); - System.out.println(msg.toString()); - } - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - System.out.println("客户端循环心跳监测发送: " + new Date()); - if (evt instanceof IdleStateEvent) { - IdleStateEvent event = (IdleStateEvent) evt; - if (event.state() == IdleState.WRITER_IDLE) { - if (curTime < beatTime) { - curTime++; - ctx.writeAndFlush("ping"); - } - } - } - } -} diff --git a/src/main/java/com/fjg/omorntcpfins/config/MyClientHandler.java b/src/main/java/com/fjg/omorntcpfins/config/MyClientHandler.java deleted file mode 100644 index 41e19590e901ed91bcff753838e0c406eb14db26..0000000000000000000000000000000000000000 --- a/src/main/java/com/fjg/omorntcpfins/config/MyClientHandler.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.fjg.omorntcpfins.config; - -import cn.hutool.core.util.HexUtil; -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.netty.channel.*; -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.TimeUnit; - -/** - * Handler - * - * @author CLS - * @since 2023/08/04 - */ -@Slf4j -public class MyClientHandler extends ChannelInboundHandlerAdapter { - - /** - * Fins协议第一次握手 - * - * @param ctx - * @throws Exception - */ - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - //握手 - ByteBuf byteBuf = Unpooled.wrappedBuffer(HexUtil.encodeHexStr("46494E530000000C000000000000000000000000").getBytes()); - ctx.writeAndFlush(byteBuf); - } - - /** - * 消息处理 - * - * @param ctx - * @param msg - * @throws Exception - */ - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - //接收服务端发送过来的消息 - ByteBuf byteBuf = (ByteBuf) msg; - byte[] bytes = ByteBufUtil.getBytes(byteBuf); - System.err.println(HexUtil.encodeHexStr(bytes)); - - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - cause.printStackTrace(); - log.error("异常信息:{}", cause.getMessage()); - ctx.close(); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - log.info("断开连接"); - // 关闭旧的channel - ctx.channel().close(); - // 使用新的EventLoop进行重连,避免死锁 - doReconnect(); - - } - - private final Bootstrap bootstrap; - private final EventLoopGroup group; - - private final int MAX_RECONNECT_ATTEMPTS = 10; - - private int reconnectAttempts = 0; - - public MyClientHandler(Bootstrap bootstrap, EventLoopGroup group) { - this.bootstrap = bootstrap; - this.group = group; - } - - private void doReconnect() { - log.info("断线重连..."); - ChannelFuture future = bootstrap.connect(); - future.addListener((ChannelFutureListener) future1 -> { - if (!future1.isSuccess() && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { - reconnectAttempts++; - // 如果重连不成功,可以再次安排重连,但要避免无限循环导致资源耗尽 - System.err.println("Reconnect attempt failed, will retry..."); - group.schedule(this::doReconnect, 3, TimeUnit.SECONDS); - } else { - System.err.println("Reconnect attempt success"); - reconnectAttempts = 0; - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java b/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java new file mode 100644 index 0000000000000000000000000000000000000000..fc881d15901d176538bcc7e06b33d035f8f5d5ea --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java @@ -0,0 +1,35 @@ +package com.fjg.omorntcpfins.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author fengjianguo + * @date 2024/7/29 12:51 + */ +@Data +@Configuration +@ConfigurationProperties("omron") +public class OmronConfigProps { + /** + * FINS TCP 端口 + */ + private String finsTcpIp; + /** + * FINS TCP 端口 + */ + private String finsTcpPort; + /** + * PLC IP 节点 + */ + private Integer plcIpNode; + /** + * PC ID 节点 + */ + private Integer pcIdNode; + /** + * 读写数据超时时间 + */ + private Integer readWriteTimeout; +} diff --git a/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientConfig.java b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..992156dcb689de06c80e65f3479508af4f8c2008 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientConfig.java @@ -0,0 +1,94 @@ +package com.fjg.omorntcpfins.config.fins.client; + +import com.fjg.omorntcpfins.util.HexStringUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.TimeUnit; + +/** + * fins客户端配置 + * + * @author fengjianguo + * @date 2024/07/25 + */ +@Slf4j +public class FinsClientConfig { + private final String host; + private final int port; + private Bootstrap bootstrap; + private static ChannelFuture channelFuture; + + public FinsClientConfig(String host, int port) { + this.host = host; + this.port = port; + init(); + } + + private void init() { + //客户端需要一个事件循环组 + EventLoopGroup group = new NioEventLoopGroup(); + //创建客户端启动对象 + // bootstrap 可重用, 只需在NettyClient实例化的时候初始化即可. + bootstrap = new Bootstrap(); + bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); + bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + //加入处理器 + ch.pipeline() + //设置指定时间没有响应数据抛出超时异常(秒) +// .addLast(new ReadTimeoutHandler(3)) + //设置处理器 + .addLast(new FinsClientHandler(FinsClientConfig.this)) + ; + } + }); + } + + /** + * 连接服务端 + * + * @throws Exception + */ + public void connect() throws Exception { + //启动客户端去连接服务器端 + channelFuture = bootstrap.connect(host, port); + channelFuture.addListener((ChannelFutureListener) future -> { + if (!future.isSuccess()) { + //重连交给后端线程执行 + future.channel().eventLoop().schedule(() -> { + log.info("重连服务端"); + try { + connect(); + } catch (Exception e) { + e.printStackTrace(); + } + }, 3000, TimeUnit.MILLISECONDS); + } else { + log.info("连接成功"); + } + }); + //对通道关闭进行监听 + channelFuture.channel().closeFuture().sync(); + } + + /** + * 发送数据 + * + * @param msg 数据 + */ + public static void send(String msg) { + if (channelFuture.channel().isActive()) { + log.info("发送的消息:{}", msg); + ByteBuf byteBuf = Unpooled.wrappedBuffer(HexStringUtil.hexToByteArray(msg)); + channelFuture.channel().writeAndFlush(byteBuf); + } + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d4b9a02b10ab34856996fc6c4ee5e62e170e49c6 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java @@ -0,0 +1,83 @@ +package com.fjg.omorntcpfins.config.fins.client; + +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.fjg.omorntcpfins.common.queue.HexQueue; +import com.fjg.omorntcpfins.config.OmronConfigProps; +import com.fjg.omorntcpfins.handler.fins.FinsMessageHandler; +import com.fjg.omorntcpfins.util.HexStringUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +/** + * fins客户端处理器 + * + * @author fengjianguo + * @date 2024/07/25 + */ +@Slf4j +public class FinsClientHandler extends ChannelInboundHandlerAdapter { + + private final FinsClientConfig finsClientConfig; + public static Object[] newsState = new Object[1]; + + public FinsClientHandler(FinsClientConfig finsClientConfig) { + this.finsClientConfig = finsClientConfig; + } + + /** + * 建立连接时 + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + String order = "46494E530000000C0000000000000000000000"; + OmronConfigProps bean = SpringUtil.getBean(OmronConfigProps.class); + Integer pcIdNode = bean.getPcIdNode(); + String hexString = Integer.toHexString(pcIdNode); + String handshakeMsg = order + String.format("%2s", hexString).replace(' ', '0'); + log.info("握手的消息:{}", handshakeMsg); + ByteBuf byteBuf = Unpooled.wrappedBuffer(HexStringUtil.hexToByteArray(handshakeMsg)); + ctx.writeAndFlush(byteBuf); + // 初始化绘话队列 + HexQueue.init(); + } + + /** + * 当通道有读取事件时会触发,即服务端发送数据给客户端 + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ByteBuf byteBuf = (ByteBuf) msg; + byte[] bytes = ByteBufUtil.getBytes(byteBuf); + String bf = HexUtil.encodeHexStr(bytes); + newsState[0] = bf; + log.info("接收的消息:{}\n", bf); + FinsMessageHandler.handle(bf); + } + + /** + * 关闭连接时 + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + log.error("运行中断开重连, isRegistered{},isActive:{}", ctx.channel().isRegistered(), ctx.channel().isActive()); + Thread.sleep(1000); + ctx.close(); + finsClientConfig.connect(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("连接异常:{}", StrUtil.isBlankIfStr(cause.getMessage()) ? cause.getClass() : cause.getMessage()); + ctx.close(); + } + +} diff --git a/src/main/java/com/fjg/omorntcpfins/exception/GlobalException.java b/src/main/java/com/fjg/omorntcpfins/exception/GlobalException.java new file mode 100644 index 0000000000000000000000000000000000000000..95d91a82e26102a33842c7304ac731500ee07c79 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/exception/GlobalException.java @@ -0,0 +1,29 @@ +package com.fjg.omorntcpfins.exception; + +import com.fjg.omorntcpfins.common.enums.MsgEnum; +import lombok.Getter; + +/** + * @author Zhang GuiHong + * @date 2021/12/27 10:25 + **/ +@Getter +public class GlobalException extends RuntimeException { + + private final Integer code; + + public GlobalException(Integer code, String msg) { + super(msg); + this.code = code; + } + + public GlobalException(MsgEnum msgEnum) { + super(msgEnum.msg()); + this.code = msgEnum.code(); + } + + public GlobalException(String msg) { + super(msg); + this.code = MsgEnum.ERROR.code(); + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..900c9fe6cd0294dfedd6dc96151d176b53e88d41 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -0,0 +1,98 @@ +package com.fjg.omorntcpfins.handler.fins; + +import com.fjg.omorntcpfins.common.enums.FinsErrorCodeEnum; +import com.fjg.omorntcpfins.common.enums.FinsFixedValueEnum; +import com.fjg.omorntcpfins.common.enums.FinsReadWriteEnum; +import com.fjg.omorntcpfins.common.queue.HexQueue; +import com.fjg.omorntcpfins.config.fins.client.FinsClientConfig; +import com.fjg.omorntcpfins.util.FinsMsgAnalysisUtil; +import com.fjg.omorntcpfins.util.HexStringUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author fengjianguo + * @date 2024/7/29 10:04 + */ +@Slf4j +public class FinsMessageHandler { + + private final static Map> REQUEST_FUTURES = new ConcurrentHashMap<>(); + + public static void handle(String msg) { + // 过滤握手信息 + if (FinsMsgAnalysisUtil.isHandshake(msg)){ + log.info("收到握手信息:" + msg); + try { + String s = FinsMsgAnalysisUtil.finsErrorCode(msg); + if (FinsErrorCodeEnum.isConnectedAlready(s)){ + log.error("连接被占用"); + return; + } + } catch (IllegalArgumentException e) { + log.error("解析错误:{}" , msg); + log.error("解析错误:{}" , e.getMessage()); + log.error("解析错误:{}" , Arrays.toString(e.getStackTrace())); + } + return; + } + try { + // 获取fins头 + String finsHead = FinsMsgAnalysisUtil.finsHead(msg); + // 十六进制解析 + boolean finsHeadByHexValue = FinsFixedValueEnum.isFinsHeadByHexValue(finsHead); + if (!finsHeadByHexValue){ + log.error("FINS头错误:" + finsHead); + return; + } + // 获取fins-SID + String finsSid = FinsMsgAnalysisUtil.finsSid(msg); + // 判断是读是写 + String finsReadWrite = FinsMsgAnalysisUtil.finsReadWrite(msg); + boolean writeByCode = FinsReadWriteEnum.isWriteByCode(finsReadWrite); + if (writeByCode){ + // 执行写操作 + if (REQUEST_FUTURES.containsKey(finsSid)){ + CompletableFuture future = REQUEST_FUTURES.get(finsSid); + // 解析,获取db信息,获取读取到的数据 + future.complete(FinsMsgAnalysisUtil.finsResponseFlag(msg)); + REQUEST_FUTURES.remove(finsSid); + }else{ + log.info("未找到对应的请求"); + } + return; + } + log.info("收到消息:" + msg); + if (REQUEST_FUTURES.containsKey(finsSid)){ + CompletableFuture future = REQUEST_FUTURES.get(finsSid); + // 解析,获取db信息,获取读取到的数据 + future.complete(FinsMsgAnalysisUtil.finsData(msg)); + REQUEST_FUTURES.remove(finsSid); + }else{ + log.info("未找到对应的请求"); + } + } catch (IllegalArgumentException e) { + log.error("解析错误:{}" , msg); + log.error("解析错误:{}" , e.getMessage()); + log.error("解析错误:{}" , Arrays.toString(e.getStackTrace())); + } + } + + public static void setRequestFutures(String key, CompletableFuture future) { + REQUEST_FUTURES.put(key, future); + } + + public static void removeRequestFutures(String key) { + REQUEST_FUTURES.remove(key); + log.info("删除请求:{}" , key); + log.info("归还sid:{}", key); + HexQueue.returnHex(key); + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeBitStrategy.java b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeBitStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..5c108a3ba52ca5dd42dd2690afb24f5d7439a9ca --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeBitStrategy.java @@ -0,0 +1,34 @@ +package com.fjg.omorntcpfins.handler.strategy.area; + +import com.fjg.omorntcpfins.model.DbInfoPO; +import org.springframework.stereotype.Component; + +/** + * @author fengjianguo + * @date 2024/7/29 11:24 + */ +@Component("areaTypeBitStrategy") +public class AreaTypeBitStrategy extends BaseAreaTypeStrategy { + @Override + public DbInfoPO parseDbInfo(String address, int offset) { + DbInfoPO dbInfo = buildDbInfo(address,offset); + String substring = address.substring(0, 1); + String dbArea; + // D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0 + switch (substring.toUpperCase()) { + case "D": + dbArea = "02"; + break; + case "W": + dbArea = "31"; + break; + case "C": + dbArea = "30"; + break; + default: + dbArea = "00"; + } + dbInfo.setDbArea(dbArea); + return dbInfo; + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeStrategy.java b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..7f72d8a44b35044c61510a42b85de6175c9522c8 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeStrategy.java @@ -0,0 +1,20 @@ +package com.fjg.omorntcpfins.handler.strategy.area; + +import com.fjg.omorntcpfins.model.DbInfoPO; + +/** + * @author fengjianguo + * @date 2024/7/29 11:22 + */ +public interface AreaTypeStrategy { + + /** + * 解析DB信息 + * + * @param address 地址 + * @param offset 偏移量 + * @return {@link DbInfoPO } + */ + DbInfoPO parseDbInfo(String address, int offset); + +} diff --git a/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeWordStrategy.java b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeWordStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..0fbf1983b41a6b979cca018871dc165d1f98136e --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeWordStrategy.java @@ -0,0 +1,35 @@ +package com.fjg.omorntcpfins.handler.strategy.area; + +import com.fjg.omorntcpfins.common.enums.AreaTypeEnum; +import com.fjg.omorntcpfins.model.DbInfoPO; +import org.springframework.stereotype.Component; + +/** + * @author fengjianguo + * @date 2024/7/29 11:24 + */ +@Component("areaTypeWordStrategy") +public class AreaTypeWordStrategy extends BaseAreaTypeStrategy { + @Override + public DbInfoPO parseDbInfo(String address, int offset) { + DbInfoPO dbInfo = buildDbInfo(address,offset); + String substring = address.substring(0, 1); + String dbArea; + // D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0 + switch (substring.toUpperCase()) { + case "D": + dbArea = "82"; + break; + case "W": + dbArea = "B1"; + break; + case "C": + dbArea = "B0"; + break; + default: + dbArea = "00"; + } + dbInfo.setDbArea(dbArea); + return dbInfo; + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/BaseAreaTypeStrategy.java b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/BaseAreaTypeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..f5280e2d24f05e9974807efaa3a4db00b9e9e89b --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/handler/strategy/area/BaseAreaTypeStrategy.java @@ -0,0 +1,29 @@ +package com.fjg.omorntcpfins.handler.strategy.area; + +import com.fjg.omorntcpfins.model.DbInfoPO; +import com.sun.org.apache.bcel.internal.generic.PUSH; +import lombok.extern.slf4j.Slf4j; + +/** + * @author fengjianguo + * @date 2024/7/29 11:24 + */ +@Slf4j +public abstract class BaseAreaTypeStrategy implements AreaTypeStrategy { + + @Override + public DbInfoPO parseDbInfo(String address, int offset) { + log.info("address:{}, offset:{}", address, offset); + log.error("默认策略-默认返回null"); + return null; + } + + public DbInfoPO buildDbInfo(String address, int offset) { + DbInfoPO dbInfo = new DbInfoPO(); + String dbBlock = Integer.toHexString(Integer.parseInt(address.substring(1)) * 256); + String dbOffset = Integer.toHexString(offset); + dbInfo.setDbBlock(dbBlock); + dbInfo.setDbOffset(String.format("%4s",dbOffset).replace(" ", "0")); + return dbInfo; + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java b/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java new file mode 100644 index 0000000000000000000000000000000000000000..0a8e7ab3579f9d3861c37f89ba6db4b414b16d1a --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java @@ -0,0 +1,32 @@ +package com.fjg.omorntcpfins.model; + +import lombok.Data; + +/** + * @author fengjianguo + * @date 2024/7/29 13:21 + */ +@Data +public class DbInfoPO { + + /** + * 区域 + */ + private String dbArea; + + /** + * db块 + */ + private String dbBlock; + + /** + * db偏移 + */ + private String dbOffset; + + /** + * db值 + */ + private String dbValue = ""; + +} diff --git a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java index 6d9a38e2331aad6e147c3b61af8b656e83bbce1f..9e5a56b8e4eda6ecf0dc1b48092f4e0d7963dc18 100644 --- a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java +++ b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java @@ -1,11 +1,14 @@ package com.fjg.omorntcpfins.run; -import com.fjg.omorntcpfins.config.FinsClientConfig; +import com.fjg.omorntcpfins.config.fins.client.FinsClientConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + /** * @author fengjianguo * @date 2024/7/22 9:28 @@ -14,19 +17,24 @@ import org.springframework.stereotype.Component; @Component public class AppRunning implements CommandLineRunner { - @Value("${omorn.fins-tcp-ip:(127.0.0.1)}") + @Value("${omron.fins-tcp-ip:(127.0.0.1)}") private String url; - @Value("${omorn.fins-tcp-port:(9600)}") + @Value("${omron.fins-tcp-port:(9600)}") private Integer port; @Override public void run(String... args) throws Exception { - log.info("初始化 ~ (●'网络◡通讯'●) ~ "); - try { - FinsClientConfig.finsStart(url, port); - } catch (Exception e) { - throw e; - } + log.info("PLC连接中······"); + FinsClientConfig finsClientConfig = new FinsClientConfig(url, port); + ExecutorService executorService = Executors.newFixedThreadPool(1); + //启动Fins通讯 + executorService.submit(() -> { + try { + finsClientConfig.connect(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } } diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..c7db9f68bca0f6dbe7bdfc8949ae24047a9908cc --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java @@ -0,0 +1,286 @@ +package com.fjg.omorntcpfins.util; + +import cn.hutool.core.util.HexUtil; + +/** + * fins-msg分析工具 + * + * @author fengjianguo + * @date 2024/07/29 + */ +public class FinsMsgAnalysisUtil { + + + /** + * FINS头 + */ + private static final int HEAD_START = 0; + /** + * 长度 + */ + private static final int LENGTH_START = 8; + /** + * 命令 + */ + private static final int COMMAND_START = 16; + /** + * 错误码 + */ + private static final int ERROR_CODE_START = 24; + /** + * ICF + */ + private static final int ICF_START = 32; + /** + * RSV + */ + private static final int RSV_START = 34; + /** + * GCT + */ + private static final int GCT_START = 36; + /** + * PC网络地址 + */ + private static final int PC_NETWORK_ADDRESS_START = 38; + /** + * PC节点地址 + */ + private static final int PC_NODE_ADDRESS_START = 40; + /** + * PLC网络地址 + */ + private static final int PC_UNIT_ADDRESS_START = 42; + /** + * PLC网络地址 + */ + private static final int PLC_NETWORK_ADDRESS_START = 44; + /** + * PLC节点地址 + */ + private static final int PLC_NODE_ADDRESS_START = 46; + /** + * PLC单元地址 + */ + private static final int PLC_UNIT_ADDRESS_START = 48; + /** + * 服务ID + */ + private static final int SID_START = 50; + /** + * 读写指令 + */ + private static final int READ_WRITE_START = 52; + /** + * 读写标识 + */ + private static final int READ_WRITE_FLAG_START = 56; + + /** + * 数据位 + */ + private static final int DATA_START = 60; + + /** + * 步长2 + */ + private static final int STEP_TWO = 2; + /** + * 步长4 + */ + private static final int STEP_FOUR = 4; + /** + * 步长8 + */ + private static final int STEP_EIGHT = 8; + + /** + * FINS协议体长度 + */ + private static final int FINS_BODY = 44; + + + /** + * 验证消息长度是否满足最小要求 + * + * @param msg 消息字符串 + * @param min_length 最小长度要求 + * @throws IllegalArgumentException 如果msg长度小于min_length + */ + private static void validateMsgLength(String msg, int min_length) throws IllegalArgumentException { + if (msg == null || msg.length() < min_length) { + throw new IllegalArgumentException("消息长度不足"); + } + } + + /** + * 提取字符串的子串 + * + * @param msg 源字符串 + * @param startIndex 子串起始索引 + * @param length 子串长度 + * @return 子串 + */ + private static String extractSubString(String msg, int startIndex, int length) { + return msg.substring(startIndex, startIndex + length); + } + + + + /** + * 获取FINS头 + * + * @param msg 消息字符串 + * @return FINS头字符串 + * @throws IllegalArgumentException 如果msg长度小于8 + */ + public static String finsHead(String msg) throws IllegalArgumentException { + validateMsgLength(msg, HEAD_START + STEP_EIGHT); + return extractSubString(msg, HEAD_START, STEP_EIGHT); + } + + + /** + * 获取FINS协议长度 + */ + public static String finsLength(String msg) { + validateMsgLength(msg, LENGTH_START + STEP_EIGHT); + return extractSubString(msg, LENGTH_START, STEP_EIGHT); + } + + /** + * 获取FINS协议命令 + */ + public static String finsCommand(String msg) { + validateMsgLength(msg, COMMAND_START + STEP_EIGHT); + return extractSubString(msg, COMMAND_START, STEP_EIGHT); + } + + /** + * 获取FINS错误码 + */ + public static String finsErrorCode(String msg) { + validateMsgLength(msg, ERROR_CODE_START + STEP_EIGHT); + return extractSubString(msg, ERROR_CODE_START, STEP_EIGHT); + } + + /** + * 获取FINS协议ICF + */ + public static String finsIcf(String msg) { + validateMsgLength(msg, ICF_START + STEP_TWO); + return extractSubString(msg, ICF_START, STEP_TWO); + } + + /** + * 获取FINS协议RSV + */ + public static String finsRsv(String msg) { + validateMsgLength(msg, RSV_START + STEP_TWO); + return extractSubString(msg, RSV_START, STEP_TWO); + } + + /** + * 获取FINS协议GCT + */ + public static String finsGct(String msg) { + validateMsgLength(msg, GCT_START + STEP_TWO); + return extractSubString(msg, GCT_START, STEP_TWO); + } + + /** + * 获取FINS协议PC网络地址 + */ + public static String finsPcNetworkAddress(String msg) { + validateMsgLength(msg, PC_NETWORK_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PC_NETWORK_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PC节点地址 + */ + public static String finsPcNodeAddress(String msg) { + validateMsgLength(msg, PC_NODE_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PC_NODE_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PC单元地址 + */ + public static String finsPcUnitAddress(String msg) { + validateMsgLength(msg, PC_UNIT_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PC_UNIT_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PLC网络地址 + */ + public static String finsPlcNetworkAddress(String msg) { + validateMsgLength(msg, PLC_NETWORK_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PLC_NETWORK_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PLC节点地址 + */ + public static String finsPlcNodeAddress(String msg) { + validateMsgLength(msg, PLC_NODE_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PLC_NODE_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PLC单元地址 + */ + public static String finsPlcUnitAddress(String msg) { + validateMsgLength(msg, PLC_UNIT_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PLC_UNIT_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议SID + * 服务ID + */ + public static String finsSid(String msg) { + validateMsgLength(msg, SID_START + STEP_TWO); + return extractSubString(msg, SID_START, STEP_TWO); + } + /** + * 获取FINS协议读写指令 + * 0101: 读指令 + * 0102: 写指令 + */ + public static String finsReadWrite(String msg) { + validateMsgLength(msg, READ_WRITE_START + STEP_FOUR); + return extractSubString(msg, READ_WRITE_START, STEP_FOUR); + } + + /** + * 获取FINS协议读取写入标识 + * 0000: 正常 + */ + public static String finsResponseFlag(String msg) { + validateMsgLength(msg, READ_WRITE_FLAG_START + STEP_FOUR); + return extractSubString(msg, READ_WRITE_FLAG_START, STEP_FOUR); + } + + /** + * 获取FINS协议数据 + */ + public static String finsData(String msg) { + String length = finsLength(msg); + int dataLength = Integer.parseInt(length,16) * 2; + dataLength -= FINS_BODY; + if (dataLength <= 0){ + return ""; + } + return msg.substring(DATA_START, dataLength + DATA_START); + } + + /** + * 判断本次回复是否为握手消息 + */ + public static boolean isHandshake(String msg) { + return "00000001".equals(finsCommand(msg)); + } + +} diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsRequestAnalysisUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsRequestAnalysisUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..c9758c6134823585fb5387b2740ee0b8b1626fe3 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsRequestAnalysisUtil.java @@ -0,0 +1,297 @@ +package com.fjg.omorntcpfins.util; + +/** + * fins-request分析工具 + * + * @author fengjianguo + * @date 2024/07/29 + */ +public class FinsRequestAnalysisUtil { + + + /** + * FINS头 + */ + private static final int HEAD_START = 0; + /** + * 长度 + */ + private static final int LENGTH_START = 8; + /** + * 命令 + */ + private static final int COMMAND_START = 16; + /** + * 错误码 + */ + private static final int ERROR_CODE_START = 24; + /** + * ICF + */ + private static final int ICF_START = 32; + /** + * RSV + */ + private static final int RSV_START = 34; + /** + * GCT + */ + private static final int GCT_START = 36; + /** + * PLC网络地址 + */ + private static final int PLC_NETWORK_ADDRESS_START = 38; + /** + * PLC节点地址 + */ + private static final int PLC_NODE_ADDRESS_START = 40; + /** + * PLC单元地址 + */ + private static final int PLC_UNIT_ADDRESS_START = 42; + /** + * PC网络地址 + */ + private static final int PC_NETWORK_ADDRESS_START = 44; + /** + * PC节点地址 + */ + private static final int PC_NODE_ADDRESS_START = 46; + /** + * PC单元地址 + */ + private static final int PC_UNIT_ADDRESS_START = 48; + /** + * 服务ID + */ + private static final int SID_START = 50; + /** + * 读写指令 + */ + private static final int READ_WRITE_START = 52; + + /** + * 读写地址区(2) + */ + private static final int READ_WRITE_AREA_START = 56; + + /** + * 读写起始地址(6) + */ + private static final int READ_WRITE_START_ADDRESS_START = 58; + + /** + * 读写长度(4) + */ + private static final int READ_WRITE_LENGTH_START = 64; + + /** + * 步长2 + */ + private static final int STEP_TWO = 2; + /** + * 步长4 + */ + private static final int STEP_FOUR = 4; + /** + * 步长6 + */ + private static final int STEP_SIX = 6; + /** + * 步长8 + */ + private static final int STEP_EIGHT = 8; + + + /** + * 验证消息长度是否满足最小要求 + * + * @param msg 消息字符串 + * @param min_length 最小长度要求 + * @throws IllegalArgumentException 如果msg长度小于min_length + */ + private static void validateMsgLength(String msg, int min_length) throws IllegalArgumentException { + if (msg == null || msg.length() < min_length) { + throw new IllegalArgumentException("消息长度不足"); + } + } + + /** + * 提取字符串的子串 + * + * @param msg 源字符串 + * @param startIndex 子串起始索引 + * @param length 子串长度 + * @return 子串 + */ + private static String extractSubString(String msg, int startIndex, int length) { + return msg.substring(startIndex, startIndex + length); + } + + + + /** + * 获取FINS头 + * + * @param msg 消息字符串 + * @return FINS头字符串 + * @throws IllegalArgumentException 如果msg长度小于8 + */ + public static String finsHead(String msg) throws IllegalArgumentException { + validateMsgLength(msg, HEAD_START + STEP_EIGHT); + return extractSubString(msg, HEAD_START, STEP_EIGHT); + } + + + /** + * 获取FINS协议长度 + */ + public static String finsLength(String msg) { + validateMsgLength(msg, LENGTH_START + STEP_EIGHT); + return extractSubString(msg, LENGTH_START, STEP_EIGHT); + } + + /** + * 获取FINS协议命令 + */ + public static String finsCommand(String msg) { + validateMsgLength(msg, COMMAND_START + STEP_EIGHT); + return extractSubString(msg, COMMAND_START, STEP_EIGHT); + } + + /** + * 获取FINS错误码 + */ + public static String finsErrorCode(String msg) { + validateMsgLength(msg, ERROR_CODE_START + STEP_EIGHT); + return extractSubString(msg, ERROR_CODE_START, STEP_EIGHT); + } + + /** + * 获取FINS协议ICF + */ + public static String finsIcf(String msg) { + validateMsgLength(msg, ICF_START + STEP_TWO); + return extractSubString(msg, ICF_START, STEP_TWO); + } + + /** + * 获取FINS协议RSV + */ + public static String finsRsv(String msg) { + validateMsgLength(msg, RSV_START + STEP_TWO); + return extractSubString(msg, RSV_START, STEP_TWO); + } + + /** + * 获取FINS协议GCT + */ + public static String finsGct(String msg) { + validateMsgLength(msg, GCT_START + STEP_TWO); + return extractSubString(msg, GCT_START, STEP_TWO); + } + + /** + * 获取FINS协议PC网络地址 + */ + public static String finsPcNetworkAddress(String msg) { + validateMsgLength(msg, PC_NETWORK_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PC_NETWORK_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PC节点地址 + */ + public static String finsPcNodeAddress(String msg) { + validateMsgLength(msg, PC_NODE_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PC_NODE_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PC单元地址 + */ + public static String finsPcUnitAddress(String msg) { + validateMsgLength(msg, PC_UNIT_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PC_UNIT_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PLC网络地址 + */ + public static String finsPlcNetworkAddress(String msg) { + validateMsgLength(msg, PLC_NETWORK_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PLC_NETWORK_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PLC节点地址 + */ + public static String finsPlcNodeAddress(String msg) { + validateMsgLength(msg, PLC_NODE_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PLC_NODE_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议PLC单元地址 + */ + public static String finsPlcUnitAddress(String msg) { + validateMsgLength(msg, PLC_UNIT_ADDRESS_START + STEP_TWO); + return extractSubString(msg, PLC_UNIT_ADDRESS_START, STEP_TWO); + } + + /** + * 获取FINS协议SID + * 服务ID + */ + public static String finsSid(String msg) { + validateMsgLength(msg, SID_START + STEP_TWO); + return extractSubString(msg, SID_START, STEP_TWO); + } + /** + * 获取FINS协议读写指令 + * 0101: 读指令 + * 0102: 写指令 + */ + public static String finsReadWrite(String msg) { + validateMsgLength(msg, READ_WRITE_START + STEP_FOUR); + return extractSubString(msg, READ_WRITE_START, STEP_FOUR); + } + + + /** + * 获取FINS协议读写地址区 + * 读写地址区(2) + */ + public static String finsReadWriteAddressArea(String msg) { + validateMsgLength(msg, READ_WRITE_AREA_START + STEP_TWO); + return extractSubString(msg, READ_WRITE_AREA_START, STEP_TWO); + } + + /** + * 获取FINS协议读写起始地址 + * 读写起始地址(6) + */ + public static String finsReadWriteStartAddress(String msg) { + validateMsgLength(msg, READ_WRITE_START_ADDRESS_START + STEP_SIX); + return extractSubString(msg, READ_WRITE_START_ADDRESS_START, STEP_SIX); + } + + /** + * 获取FINS协议读写长度 + * 读写长度(4) + */ + public static String finsReadWriteLength(String msg) { + validateMsgLength(msg, READ_WRITE_LENGTH_START + STEP_FOUR); + return extractSubString(msg, READ_WRITE_LENGTH_START, STEP_FOUR); + } + + /** + * 获取FINS协议数据 + */ + public static String finsData(String msg, int startIndex,int length) { + validateMsgLength(msg, startIndex + length); + String responseDate = extractSubString(msg, startIndex, length); + return responseDate; + } + +} diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..af873e48ea28b43032a79e3b2dd9f5ca7a7f1156 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java @@ -0,0 +1,301 @@ +package com.fjg.omorntcpfins.util; + +import com.fjg.omorntcpfins.common.enums.AreaTypeEnum; +import com.fjg.omorntcpfins.common.queue.HexQueue; +import com.fjg.omorntcpfins.config.OmronConfigProps; +import com.fjg.omorntcpfins.config.fins.client.FinsClientConfig; +import com.fjg.omorntcpfins.exception.GlobalException; +import com.fjg.omorntcpfins.handler.fins.FinsMessageHandler; +import com.fjg.omorntcpfins.handler.strategy.area.AreaTypeStrategy; +import com.fjg.omorntcpfins.model.DbInfoPO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * @author fengjianguo + * @date 2024/7/27 15:57 + */ +@Slf4j +@Component +public class FinsUtil { + + @Autowired + private OmronConfigProps omronConfigProps; + + @Autowired + private Map areaTypeStrategyMap; + + /** + * FINS协议头 + */ + private static final String FINS_HEAD = "46494E53"; + + /** + * FINS协议命令 + */ + private static final String FINS_COMMAND = "00000002"; + /** + * FINS协议错误码 + */ + private static final String FINS_ERROR_CODE = "00000000"; + /** + * FINS协议ICF + */ + private static final String FINS_ICF = "80"; + /** + * FINS协议RSV + */ + private static final String FINS_RSV = "00"; + /** + * FINS协议GCT + */ + private static final String FINS_GCT = "02"; + /** + * FINS协议PLC网络地址 + */ + private static final String FINS_PLC_NETWORK_ADDRESS = "00"; + /** + * FINS协议PLC单元地址 + */ + private static final String FINS_PLC_UNIT_ADDRESS = "00"; + /** + * FINS协议PC网络地址 + */ + private static final String FINS_PC_NETWORK_ADDRESS = "00"; + /** + * FINS协议PC单元地址 + */ + private static final String FINS_PC_UNIT_ADDRESS = "00"; + /** + * FINS协议SID + * 00 - FF + */ + private static final String FINS_SID = "FF"; + /** + * FINS协议读指令 + */ + private static final String FINS_READ_COMMAND = "0101"; + /** + * FINS协议写指令 + */ + private static final String FINS_WRITE_COMMAND = "0102"; + + /** + * 写入值固定长度 + */ + private static final int FINS_WRITE_VALUE_LENGTH = 4; + + /** + * 获取FINS读指令 + * + * @param address + * @param offset + * @param areaTypeEnum + * @return + */ + private String getReadMessage(String address, int offset, AreaTypeEnum areaTypeEnum) { + String strategy = areaTypeEnum.getStrategy(); + AreaTypeStrategy areaTypeStrategy = areaTypeStrategyMap.get(strategy); + // 获取db信息,区,块,位 + DbInfoPO dbInfo = areaTypeStrategy.parseDbInfo(address, offset); + // 获取plc地址 + return getSendMsg(dbInfo, false); + } + + /** + * 获取FINS写指令 + * + * @param address + * @param offset + * @param areaTypeEnum + * @param dbValue + * @return + */ + private String getWriteMessage(String address, int offset, AreaTypeEnum areaTypeEnum, String dbValue) { + String strategy = areaTypeEnum.getStrategy(); + AreaTypeStrategy areaTypeStrategy = areaTypeStrategyMap.get(strategy); + // 获取db信息,区,块,位 + DbInfoPO dbInfo = areaTypeStrategy.parseDbInfo(address, offset); + dbInfo.setDbValue(dbValue); + return getSendMsg(dbInfo, true); + } + + private String getSendMsg(DbInfoPO dbInfo, Boolean isWrite) { + // 获取plc地址 + String plcIpNode = String.format("%2s", Integer.toHexString(omronConfigProps.getPlcIpNode())).replace(" ", "0"); + // 获取pc地址 + String body = getFinsBody(plcIpNode, dbInfo, isWrite); + String length = String.format("%8s", Integer.toHexString(body.length() / 2)).replace(' ', '0'); + return FINS_HEAD + + length + + body; + } + + /** + * 获取FINS指令 + * + * @param plcIpNode plc ip节点 + * @param dbInfo db信息 + * @param isWrite + * @return {@link String } + */ + private String getFinsBody(String plcIpNode, DbInfoPO dbInfo, Boolean isWrite) { + String pcIdNode = String.format("%2s", Integer.toHexString(omronConfigProps.getPcIdNode())).replace(" ", "0"); + String sid = HexQueue.fetchHex(); + if (sid == null) { + throw new GlobalException("hex队列已空"); + } + return FINS_COMMAND + + FINS_ERROR_CODE + + FINS_ICF + + FINS_RSV + + FINS_GCT + + FINS_PLC_NETWORK_ADDRESS + + plcIpNode + + FINS_PLC_UNIT_ADDRESS + + FINS_PC_NETWORK_ADDRESS + + pcIdNode + + FINS_PC_UNIT_ADDRESS + + sid + + (isWrite ? FINS_WRITE_COMMAND : FINS_READ_COMMAND) + + dbInfo.getDbArea() + + dbInfo.getDbBlock() + + dbInfo.getDbOffset() + + dbInfo.getDbValue(); + } + + /** + * 发送FINS指令 + * + * @param msg + * @return + */ + private String send(String msg) { + log.info("发送的消息:{}", msg); + CompletableFuture future = new CompletableFuture<>(); + String sid = FinsRequestAnalysisUtil.finsSid(msg); + FinsMessageHandler.setRequestFutures(sid, future); + FinsClientConfig.send(msg); + try { + String s = future.get(omronConfigProps.getReadWriteTimeout(), TimeUnit.SECONDS); + FinsMessageHandler.removeRequestFutures(sid); + log.info("response: {}", s); + return s; + } catch (TimeoutException e) { + FinsMessageHandler.removeRequestFutures(sid); + throw new GlobalException("响应超时-[" + sid + "]"); + } catch (InterruptedException | ExecutionException e) { + throw new GlobalException("数据响应异常"); + } + } + + /** + * str到bool + * + * @param str str + * @return boolean + */ + private boolean strToBool(String str) { + try { + int i = Integer.parseInt(str, 16); + return 0 != i; + } catch (NumberFormatException e) { + log.error("数据读取异常: 数据转换失败-" + str); + e.printStackTrace(); + return false; + } + } + + /** + * 读取布尔值 + * + * @param address + * @param offset + * @return + */ + public Boolean readBool(String address, int offset) { + String readMessage = getReadMessage(address, offset, AreaTypeEnum.BIT); + // 发送读取指令 + return strToBool(send(readMessage)); + } + + /** + * 读取字 + * + * @param address + * @param offset + * @return + */ + public String readWord(String address, int offset) { + String readMessage = getReadMessage(address, offset, AreaTypeEnum.WORD); + // 发送读取指令 + String msg = send(readMessage); + // 十六进制转换为int + try { + return Integer.parseInt(msg, 16) + ""; + } catch (NumberFormatException e) { + e.printStackTrace(); + throw new GlobalException("数据读取异常: 数据转换失败-" + msg); + } + } + + /** + * 写入布尔值 + */ + public boolean writeBool(String address, int offset, Boolean bool) { + HexStringUtil.hexToByteArray(bool.toString()); + String dbValue = bool ? "01" : "00"; + String writeMessage = getWriteMessage(address, offset, AreaTypeEnum.BIT, dbValue); + // 发送读取指令 + String msg = send(writeMessage); + try { + // 十六进制转换为int + int i = Integer.parseInt(msg, 16); + return i == 0; + } catch (NumberFormatException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 写入字 + */ + public boolean writeWord(String address, int offset, String word) { + HexStringUtil.hexToByteArray(word); + int intValue = 0; + try { + intValue = Integer.parseInt(word); + } catch (NumberFormatException e) { + log.error("数据写入异常: 数据转换失败-" + word); + e.printStackTrace(); + throw new GlobalException("数据写入异常(目前只支持写入int类型): 数据转换失败-" + word); + } + String dbValue = Integer.toHexString(intValue); + int length = dbValue.length(); + if (length > FINS_WRITE_VALUE_LENGTH) { + throw new GlobalException("数据写入异常: 数据长度大于4位-" + word); + }else if (length < FINS_WRITE_VALUE_LENGTH) { + dbValue = String.format("%4s", dbValue).replace(' ', '0'); + } + String writeMessage = getWriteMessage(address, offset, AreaTypeEnum.WORD, dbValue); + // 发送读取指令 + String msg = send(writeMessage); + try { + // 十六进制转换为int + int i = Integer.parseInt(msg, 16); + return i == 0; + } catch (NumberFormatException e) { + e.printStackTrace(); + return false; + } + } + +} diff --git a/src/main/java/com/fjg/omorntcpfins/util/HexStringUtil.java b/src/main/java/com/fjg/omorntcpfins/util/HexStringUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d1aef21eaed9e59cb7d5a56c15c56abbf4bab5e2 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/HexStringUtil.java @@ -0,0 +1,37 @@ +package com.fjg.omorntcpfins.util; + +/** + * @author fengjianguo + * @date 2024/7/27 12:12 + */ +public class HexStringUtil { + /** + * 16进制字符串转化为byte数组 + * + * @param inHex 要转16进制字节流数组的16进制字符 + * @return 16进制字节流 + */ + public static byte[] hexToByteArray(String inHex) { + int hexlen = inHex.length(); + byte[] result; + if (hexlen % 2 == 1) { + // 奇数 + hexlen++; + result = new byte[(hexlen / 2)]; + inHex = "0" + inHex; + } else { + // 偶数 + result = new byte[(hexlen / 2)]; + } + int j = 0; + for (int i = 0; i < hexlen; i += 2) { + result[j] = hexToByte(inHex.substring(i, i + 2)); + j++; + } + return result; + } + + private static byte hexToByte(String inHex) { + return (byte) Integer.parseInt(inHex, 16); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f21b485d778ae19802aba206f64e6abcf7ab769c..ed8611e38d357adc639d056d9f941070a9d88be3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,7 +1,14 @@ # 应用服务 WEB 访问端口 server: port: 8080 -omorn: - fins-tcp-ip: 127.0.0.1 +omron: + fins-tcp-ip: 192.168.100.202 + # fins-tcp-ip: 192.168.201.61 fins-tcp-port: 9600 # FINS TCP 端口 默认为9600 + plc-ip-node: 202 + pc-id-node: 61 + # PLC 读取数据超时时间,单位秒 + read-write-timeout: 3 +logging: + config: classpath:logback-spring.xml diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000000000000000000000000000000000000..50edeef92e08322dd5423c1f4b0a1cbc3462a0ee --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ${log.pattern} + ${log.charset} + + + + + + + ${log.base}/info/${log.moduleName}_info.log + + + %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n + ${log.charset} + + + + + ${log.base}/info/archive/${log.moduleName}_info_%d{yyyy-MM-dd}.%i.log.zip + + 60 + + ${log.max.size} + + + + INFO + ACCEPT + DENY + + + + + + + ${log.base}/warn/${log.moduleName}_warn.log + + + %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n + + + + ${log.base}/warn/archive/${log.moduleName}_warn_%d{yyyy-MM-dd}.%i.log.zip + + + 60 + + ${log.max.size} + + + WARN + ACCEPT + DENY + + + + + + + ${log.base}/error/${log.moduleName}_error.log + + + %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n + + + + ${log.base}/error/archive/${log.moduleName}_error_%d{yyyy-MM-dd}.%i.log.zip + + + 60 + + ${log.max.size} + + + ERROR + ACCEPT + DENY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +