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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+