From 9b0273a430a065403fd471510d2817eee0d8bcdd Mon Sep 17 00:00:00 2001 From: fengjianguo <395987374@qq.com> Date: Thu, 25 Jul 2024 22:25:12 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=96=AD?= =?UTF-8?q?=E7=BA=BF=E9=87=8D=E8=BF=9E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fjg/omorntcpfins/TestController.java | 3 +- ...onfig.java => FinsClientBackupConfig.java} | 2 +- .../fins_client/FinsClientConfig.java | 95 +++++++++++++++++++ .../fins_client/FinsClientHandler.java | 69 ++++++++++++++ .../com/fjg/omorntcpfins/run/AppRunning.java | 38 ++++++-- 5 files changed, 199 insertions(+), 8 deletions(-) rename src/main/java/com/fjg/omorntcpfins/config/{FinsClientConfig.java => FinsClientBackupConfig.java} (98%) create mode 100644 src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java create mode 100644 src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java diff --git a/src/main/java/com/fjg/omorntcpfins/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index 0e7fbcf..33e9f33 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -1,6 +1,7 @@ package com.fjg.omorntcpfins; -import com.fjg.omorntcpfins.config.FinsClientConfig; +import com.fjg.omorntcpfins.config.FinsClientBackupConfig; +import com.fjg.omorntcpfins.fins_client.FinsClientConfig; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/com/fjg/omorntcpfins/config/FinsClientConfig.java b/src/main/java/com/fjg/omorntcpfins/config/FinsClientBackupConfig.java similarity index 98% rename from src/main/java/com/fjg/omorntcpfins/config/FinsClientConfig.java rename to src/main/java/com/fjg/omorntcpfins/config/FinsClientBackupConfig.java index a314abc..10bd1a3 100644 --- a/src/main/java/com/fjg/omorntcpfins/config/FinsClientConfig.java +++ b/src/main/java/com/fjg/omorntcpfins/config/FinsClientBackupConfig.java @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit; * @since 2023/08/04 */ @Slf4j -public class FinsClientConfig { +public class FinsClientBackupConfig { public static ChannelFuture channelFuture; public static void finsStart(String ip, int port) throws Exception { diff --git a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java new file mode 100644 index 0000000..dcac1b6 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java @@ -0,0 +1,95 @@ +package com.fjg.omorntcpfins.fins_client; + +import cn.hutool.core.util.HexUtil; +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 io.netty.handler.timeout.ReadTimeoutHandler; +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, 3000); + 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(HexUtil.encodeHexStr(msg).getBytes()); + channelFuture.channel().writeAndFlush(byteBuf); + } + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java new file mode 100644 index 0000000..f4ffed0 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java @@ -0,0 +1,69 @@ +package com.fjg.omorntcpfins.fins_client; + +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; +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; + +/** + * 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 = "46494E53 0000000C 00000000 00000000 00000000"; + order = order.replaceAll(" ", ""); + log.info("握手的消息:{}", order); + ByteBuf byteBuf = Unpooled.wrappedBuffer((HexUtil.encodeHexStr(order).getBytes())); + ctx.writeAndFlush(byteBuf); + } + + /** + * 当通道有读取事件时会触发,即服务端发送数据给客户端 + */ + @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); + } + + /** + * 关闭连接时 + */ + @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/run/AppRunning.java b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java index 6d9a38e..bbd7325 100644 --- a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java +++ b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java @@ -1,11 +1,15 @@ package com.fjg.omorntcpfins.run; -import com.fjg.omorntcpfins.config.FinsClientConfig; +import com.fjg.omorntcpfins.config.FinsClientBackupConfig; +import com.fjg.omorntcpfins.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 @@ -23,10 +27,32 @@ public class AppRunning implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("初始化 ~ (●'网络◡通讯'●) ~ "); - try { - FinsClientConfig.finsStart(url, port); - } catch (Exception e) { - throw e; - } + FinsClientConfig finsClientConfig = new FinsClientConfig(url, port); + finsClientConfig.connect(); +// ExecutorService executorService = Executors.newFixedThreadPool(2); +// //启动Fins通讯 +// executorService.submit(() -> { +// try { +// finsClientConfig.connect(); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// }); + //采集数据 +// executorService.submit(() -> { +// try { +// for (int i = 0; i < 60; i++) { +// Thread.sleep(1000); +// finsClientConfig.send( +// "46494e53 0000001a 00000002 00000000 80 00 02 00 06 00 00 00 00 00 01 01 824650000017" +// .replaceAll(" ", "") +// ); +// +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// }); } } -- Gitee From 3fdb98d546d685815e41c3974ce4a505435b7232 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Sat, 27 Jul 2024 12:56:24 +0800 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20=E6=95=B0=E6=8D=AE=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- img.png | Bin 0 -> 33348 bytes .../fins_client/FinsClientConfig.java | 5 ++- .../fins_client/FinsClientHandler.java | 8 +++- .../com/fjg/omorntcpfins/run/AppRunning.java | 1 - .../fjg/omorntcpfins/util/HexStringUtil.java | 37 ++++++++++++++++++ .../omorntcpfins/util/OmornDecodeUtil.java | 15 +++++++ src/main/resources/application.yml | 3 +- 8 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 img.png create mode 100644 src/main/java/com/fjg/omorntcpfins/util/HexStringUtil.java create mode 100644 src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java diff --git a/README.md b/README.md index ae13e55..e734d81 100644 --- a/README.md +++ b/README.md @@ -354,4 +354,6 @@ FF:SID; 0102:写指令; -0000:写入成功标识。 \ No newline at end of file +0000:写入成功标识。 + +![img.png](img.png) \ No newline at end of file diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..adb186d1996cce43285b4a6305eb348ad0035be3 GIT binary patch literal 33348 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 K@o!@KAO0Ia9YBZx literal 0 HcmV?d00001 diff --git a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java index dcac1b6..2978450 100644 --- a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java +++ b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java @@ -1,6 +1,7 @@ package com.fjg.omorntcpfins.fins_client; import cn.hutool.core.util.HexUtil; +import com.fjg.omorntcpfins.util.HexStringUtil; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -38,7 +39,7 @@ public class FinsClientConfig { //创建客户端启动对象 // bootstrap 可重用, 只需在NettyClient实例化的时候初始化即可. bootstrap = new Bootstrap(); - bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); + bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { @@ -88,7 +89,7 @@ public class FinsClientConfig { public static void send(String msg) { if (channelFuture.channel().isActive()) { log.info("发送的消息:{}", msg); - ByteBuf byteBuf = Unpooled.wrappedBuffer(HexUtil.encodeHexStr(msg).getBytes()); + ByteBuf byteBuf = Unpooled.wrappedBuffer(HexStringUtil.hexToByteArray(msg)); channelFuture.channel().writeAndFlush(byteBuf); } } diff --git a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java index f4ffed0..3800e65 100644 --- a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java @@ -2,6 +2,7 @@ package com.fjg.omorntcpfins.fins_client; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import com.fjg.omorntcpfins.util.HexStringUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; @@ -9,6 +10,8 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.extern.slf4j.Slf4j; +import java.nio.charset.StandardCharsets; + /** * fins客户端处理器 * @@ -30,10 +33,10 @@ public class FinsClientHandler extends ChannelInboundHandlerAdapter { */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - String order = "46494E53 0000000C 00000000 00000000 00000000"; + String order = "46494E530000000C00000000000000000000003D"; order = order.replaceAll(" ", ""); log.info("握手的消息:{}", order); - ByteBuf byteBuf = Unpooled.wrappedBuffer((HexUtil.encodeHexStr(order).getBytes())); + ByteBuf byteBuf = Unpooled.wrappedBuffer(HexStringUtil.hexToByteArray(order)); ctx.writeAndFlush(byteBuf); } @@ -47,6 +50,7 @@ public class FinsClientHandler extends ChannelInboundHandlerAdapter { String bf = HexUtil.encodeHexStr(bytes); newsState[0] = bf; log.info("接收的消息:{}\n", bf); + log.info("接收的消息:{}\n", HexUtil.decodeHexStr(bf)); } /** diff --git a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java index bbd7325..dbd76a4 100644 --- a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java +++ b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java @@ -33,7 +33,6 @@ public class AppRunning implements CommandLineRunner { // //启动Fins通讯 // executorService.submit(() -> { // try { -// finsClientConfig.connect(); // } catch (Exception e) { // throw new RuntimeException(e); // } 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 0000000..d1aef21 --- /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/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java b/src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java new file mode 100644 index 0000000..5168d56 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java @@ -0,0 +1,15 @@ +package com.fjg.omorntcpfins.util; + +import cn.hutool.core.util.HexUtil; + +/** + * @author fengjianguo + * @date 2024/7/26 13:18 + */ +public class OmornDecodeUtil { + + public static String decode(String hexStr) { + // bytes转string + return HexUtil.decodeHexStr(hexStr); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f21b485..013cc86 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,7 @@ server: port: 8080 omorn: - fins-tcp-ip: 127.0.0.1 + fins-tcp-ip: 192.168.100.202 +# fins-tcp-ip: 192.168.201.61 fins-tcp-port: 9600 # FINS TCP 端口 默认为9600 -- Gitee From 74768bd4dea2a81b5db06fba38c6be367a4d913e Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Sat, 27 Jul 2024 15:39:39 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat:=201.=E5=BC=95=E5=85=A5HslCommunicat?= =?UTF-8?q?ion=E6=B5=8B=E8=AF=95=E4=BD=BF=E7=94=A8=202.=E6=9B=B4=E6=96=B0r?= =?UTF-8?q?eadme=E6=96=87=E6=A1=A3=E5=A2=9E=E5=8A=A0=E8=AF=BB=E5=86=99?= =?UTF-8?q?=E5=80=BC=E6=B5=8B=E8=AF=95=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 64 ++++++++++++++++++- pom.xml | 5 ++ .../com/fjg/omorntcpfins/TestController.java | 14 ++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e734d81..86b7693 100644 --- a/README.md +++ b/README.md @@ -356,4 +356,66 @@ FF:SID; 0000:写入成功标识。 -![img.png](img.png) \ No newline at end of file + + +# 三、错误代码 + +![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/pom.xml b/pom.xml index 33c93a1..1a87d0a 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,11 @@ lombok true + + com.github.dathlin + HslCommunication + 3.3.1 + io.netty diff --git a/src/main/java/com/fjg/omorntcpfins/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index 33e9f33..810cf2b 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -1,5 +1,8 @@ package com.fjg.omorntcpfins; +import HslCommunication.Core.Types.OperateResult; +import HslCommunication.Profinet.Omron.OmronFinsNet; +import HslCommunication.Profinet.Omron.OmronFinsUdp; import com.fjg.omorntcpfins.config.FinsClientBackupConfig; import com.fjg.omorntcpfins.fins_client.FinsClientConfig; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,4 +20,15 @@ public class TestController { FinsClientConfig.send(msg); return "success"; } + + public static void main(String[] args) { + OmronFinsNet omronFinsNet = new OmronFinsNet("192.168.100.202", 9600); + System.out.println(omronFinsNet.ReadInt16("D6004").Content); + System.out.println(omronFinsNet.ReadInt16("D6005").Content); + System.out.println(omronFinsNet.ReadBool("D6000.0").Content); + System.out.println(omronFinsNet.ReadBool("D6000.1").Content); + System.out.println(omronFinsNet.ReadBool("D6000.2").Content); + System.out.println(omronFinsNet.ReadBool("D6000.3").Content); + System.out.println(omronFinsNet.ReadBool("D6000.4").Content); + } } -- Gitee From 1ed7be2611216fc79ecf2f89c888112be8721fc5 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Mon, 29 Jul 2024 15:42:38 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat:=201.=E6=80=8E=E5=8A=A0=E8=AF=BB?= =?UTF-8?q?=E5=80=BC=E8=A7=A3=E6=9E=90=202.=E5=A2=9E=E5=8A=A0fins=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../omorntcpfins/OmornTcpFinsApplication.java | 2 + .../com/fjg/omorntcpfins/TestController.java | 43 +++- .../fjg/omorntcpfins/common/RequestMsg.java | 148 ++++++++++++++ .../common/constant/FinsConstant.java | 50 +++++ .../common/enums/AreaTypeEnum.java | 25 +++ .../omorntcpfins/common/enums/MsgEnum.java | 34 ++++ .../config/FinsClientBackupConfig.java | 88 -------- .../config/HeartBeatClientHandler.java | 37 ---- .../omorntcpfins/config/MyClientHandler.java | 95 --------- .../omorntcpfins/config/OmronConfigProps.java | 31 +++ .../fins/client}/FinsClientConfig.java | 4 +- .../fins/client}/FinsClientHandler.java | 6 +- .../exception/GlobalException.java | 29 +++ .../handler/fins/FinsMessageHandler.java | 29 +++ .../strategy/area/AreaTypeBitStrategy.java | 34 ++++ .../strategy/area/AreaTypeStrategy.java | 20 ++ .../strategy/area/AreaTypeWordStrategy.java | 35 ++++ .../strategy/area/BaseAreaTypeStrategy.java | 29 +++ .../com/fjg/omorntcpfins/model/DbInfoPO.java | 27 +++ .../com/fjg/omorntcpfins/run/AppRunning.java | 10 +- .../com/fjg/omorntcpfins/util/FinsUtil.java | 188 ++++++++++++++++++ .../omorntcpfins/util/OmornDecodeUtil.java | 15 -- src/main/resources/application.yml | 4 +- 23 files changed, 729 insertions(+), 254 deletions(-) create mode 100644 src/main/java/com/fjg/omorntcpfins/common/RequestMsg.java create mode 100644 src/main/java/com/fjg/omorntcpfins/common/constant/FinsConstant.java create mode 100644 src/main/java/com/fjg/omorntcpfins/common/enums/AreaTypeEnum.java create mode 100644 src/main/java/com/fjg/omorntcpfins/common/enums/MsgEnum.java delete mode 100644 src/main/java/com/fjg/omorntcpfins/config/FinsClientBackupConfig.java delete mode 100644 src/main/java/com/fjg/omorntcpfins/config/HeartBeatClientHandler.java delete mode 100644 src/main/java/com/fjg/omorntcpfins/config/MyClientHandler.java create mode 100644 src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java rename src/main/java/com/fjg/omorntcpfins/{fins_client => config/fins/client}/FinsClientConfig.java (95%) rename src/main/java/com/fjg/omorntcpfins/{fins_client => config/fins/client}/FinsClientHandler.java (93%) create mode 100644 src/main/java/com/fjg/omorntcpfins/exception/GlobalException.java create mode 100644 src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java create mode 100644 src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeBitStrategy.java create mode 100644 src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeStrategy.java create mode 100644 src/main/java/com/fjg/omorntcpfins/handler/strategy/area/AreaTypeWordStrategy.java create mode 100644 src/main/java/com/fjg/omorntcpfins/handler/strategy/area/BaseAreaTypeStrategy.java create mode 100644 src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java create mode 100644 src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java delete mode 100644 src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java diff --git a/src/main/java/com/fjg/omorntcpfins/OmornTcpFinsApplication.java b/src/main/java/com/fjg/omorntcpfins/OmornTcpFinsApplication.java index 6018d8d..9d67e6f 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 810cf2b..20cf13d 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -1,13 +1,16 @@ package com.fjg.omorntcpfins; -import HslCommunication.Core.Types.OperateResult; import HslCommunication.Profinet.Omron.OmronFinsNet; -import HslCommunication.Profinet.Omron.OmronFinsUdp; -import com.fjg.omorntcpfins.config.FinsClientBackupConfig; -import com.fjg.omorntcpfins.fins_client.FinsClientConfig; +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 @@ -16,8 +19,38 @@ 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"; } 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 0000000..46e2732 --- /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 0000000..e4a0ce9 --- /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 0000000..647d653 --- /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/MsgEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/MsgEnum.java new file mode 100644 index 0000000..ff9e021 --- /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/config/FinsClientBackupConfig.java b/src/main/java/com/fjg/omorntcpfins/config/FinsClientBackupConfig.java deleted file mode 100644 index 10bd1a3..0000000 --- a/src/main/java/com/fjg/omorntcpfins/config/FinsClientBackupConfig.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 FinsClientBackupConfig { - 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 bb22c12..0000000 --- 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 41e1959..0000000 --- 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 0000000..6c7d10a --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java @@ -0,0 +1,31 @@ +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; +} diff --git a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientConfig.java similarity index 95% rename from src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java rename to src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientConfig.java index 2978450..992156d 100644 --- a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientConfig.java +++ b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientConfig.java @@ -1,6 +1,5 @@ -package com.fjg.omorntcpfins.fins_client; +package com.fjg.omorntcpfins.config.fins.client; -import cn.hutool.core.util.HexUtil; import com.fjg.omorntcpfins.util.HexStringUtil; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; @@ -9,7 +8,6 @@ import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.timeout.ReadTimeoutHandler; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java similarity index 93% rename from src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java rename to src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java index 3800e65..8e374df 100644 --- a/src/main/java/com/fjg/omorntcpfins/fins_client/FinsClientHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java @@ -1,7 +1,8 @@ -package com.fjg.omorntcpfins.fins_client; +package com.fjg.omorntcpfins.config.fins.client; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import com.fjg.omorntcpfins.handler.fins.FinsMessageHandler; import com.fjg.omorntcpfins.util.HexStringUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -10,8 +11,6 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.extern.slf4j.Slf4j; -import java.nio.charset.StandardCharsets; - /** * fins客户端处理器 * @@ -51,6 +50,7 @@ public class FinsClientHandler extends ChannelInboundHandlerAdapter { newsState[0] = bf; log.info("接收的消息:{}\n", bf); log.info("接收的消息:{}\n", HexUtil.decodeHexStr(bf)); + FinsMessageHandler.handle(bf); } /** 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 0000000..95d91a8 --- /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 0000000..657da1f --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -0,0 +1,29 @@ +package com.fjg.omorntcpfins.handler.fins; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author fengjianguo + * @date 2024/7/29 10:04 + */ +public class FinsMessageHandler { + + private final static Map> REQUEST_FUTURES = new ConcurrentHashMap<>(); + + public static void handle(String msg) { + System.out.println("收到消息:" + msg); + if (REQUEST_FUTURES.containsKey("1")){ + CompletableFuture future = REQUEST_FUTURES.get("1"); + // 解析,获取db信息,获取读取到的数据 + future.complete(msg.substring(msg.length()-4)); + }else{ + System.out.println("未找到对应的请求"); + } + } + + public static void setRequestFutures(String key, CompletableFuture future) { + REQUEST_FUTURES.put(key, future); + } +} 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 0000000..5c108a3 --- /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 0000000..7f72d8a --- /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 0000000..0fbf198 --- /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 0000000..f5280e2 --- /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 0000000..e9e6774 --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java @@ -0,0 +1,27 @@ +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; + +} diff --git a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java index dbd76a4..37965d2 100644 --- a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java +++ b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java @@ -1,15 +1,11 @@ package com.fjg.omorntcpfins.run; -import com.fjg.omorntcpfins.config.FinsClientBackupConfig; -import com.fjg.omorntcpfins.fins_client.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 @@ -18,10 +14,10 @@ import java.util.concurrent.Executors; @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 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 0000000..eab28dd --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java @@ -0,0 +1,188 @@ +package com.fjg.omorntcpfins.util; + +import com.fjg.omorntcpfins.common.RequestMsg; +import com.fjg.omorntcpfins.common.enums.AreaTypeEnum; +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; + +/** + * @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 + */ + 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"; + + /** + * 获取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地址 + String plcIpNode = String.format("%2s", Integer.toHexString(omronConfigProps.getPlcIpNode())).replace(" ", "0"); + // 获取pc地址 + String pcIdNode = String.format("%2s", Integer.toHexString(omronConfigProps.getPcIdNode())).replace(" ", "0"); + String body = 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 + + FINS_SID + + FINS_READ_COMMAND + + dbInfo.getDbArea() + + dbInfo.getDbBlock() + + dbInfo.getDbOffset(); + String length = String.format("%8s", Integer.toHexString(body.length() / 2)).replace(' ', '0'); + return FINS_HEAD + + length + + body; + } + + /** + * 发送FINS指令 + * + * @param readMessage + * @return + */ + private String send(String readMessage) { + CompletableFuture future = new CompletableFuture<>(); + FinsMessageHandler.setRequestFutures("1", future); + FinsClientConfig.send(readMessage); + try { + String s = future.get(); + log.info("read: {}", s); + return s; + } catch (InterruptedException | ExecutionException e) { + throw new GlobalException("数据读取异常"); + } + } + + private boolean strToBool(String str) { + return !"0000".equals(str); + } + + /** + * 读取布尔值 + * + * @param address + * @param offset + * @return + */ + public Boolean readBool(String address, int offset) { + String readMessage = getReadMessage(address, offset, AreaTypeEnum.BIT); + log.info("发送的消息:{}", readMessage); + // 发送读取指令 + return strToBool(send(readMessage)); + } + + /** + * 读取字 + * + * @param address + * @param offset + * @return + */ + public String readWord(String address, int offset) { + String readMessage = getReadMessage(address, offset, AreaTypeEnum.WORD); + log.info("发送的消息:{}", readMessage); + // 发送读取指令 + return send(readMessage); + } + + + private String read(RequestMsg requestMsg) { + CompletableFuture future = new CompletableFuture<>(); + FinsMessageHandler.setRequestFutures(requestMsg.requestId(), future); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + log.error("发送的消息:{}", e.getMessage()); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java b/src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java deleted file mode 100644 index 5168d56..0000000 --- a/src/main/java/com/fjg/omorntcpfins/util/OmornDecodeUtil.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fjg.omorntcpfins.util; - -import cn.hutool.core.util.HexUtil; - -/** - * @author fengjianguo - * @date 2024/7/26 13:18 - */ -public class OmornDecodeUtil { - - public static String decode(String hexStr) { - // bytes转string - return HexUtil.decodeHexStr(hexStr); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 013cc86..a3cebde 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,8 +1,10 @@ # 应用服务 WEB 访问端口 server: port: 8080 -omorn: +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 -- Gitee From 8c22c2f13229997e0c17eefc14f78482522c4e3f Mon Sep 17 00:00:00 2001 From: fengjianguo <395987374@qq.com> Date: Mon, 29 Jul 2024 21:42:50 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=93=8D=E5=BA=94msg=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/enums/FinsErrorCodeEnum.java | 41 +++ .../common/enums/FinsResponseFlagEnum.java | 25 ++ .../handler/fins/FinsMessageHandler.java | 8 + .../util/FinsMsgAnalysisUtil.java | 263 ++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100644 src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java create mode 100644 src/main/java/com/fjg/omorntcpfins/common/enums/FinsResponseFlagEnum.java create mode 100644 src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java 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 0000000..b7e7ffd --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java @@ -0,0 +1,41 @@ +package com.fjg.omorntcpfins.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@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); + } +} 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 0000000..8935de4 --- /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/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index 657da1f..fbaff0f 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -1,5 +1,7 @@ package com.fjg.omorntcpfins.handler.fins; +import com.fjg.omorntcpfins.util.FinsMsgAnalysisUtil; + import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -13,16 +15,22 @@ public class FinsMessageHandler { private final static Map> REQUEST_FUTURES = new ConcurrentHashMap<>(); public static void handle(String msg) { + // 获取fins头 + String finsHead = FinsMsgAnalysisUtil.finsHead(msg); + // 获取finsdb System.out.println("收到消息:" + msg); if (REQUEST_FUTURES.containsKey("1")){ CompletableFuture future = REQUEST_FUTURES.get("1"); // 解析,获取db信息,获取读取到的数据 future.complete(msg.substring(msg.length()-4)); + REQUEST_FUTURES.remove("1"); }else{ System.out.println("未找到对应的请求"); } } + + public static void setRequestFutures(String key, CompletableFuture future) { REQUEST_FUTURES.put(key, future); } 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 0000000..7f65a6f --- /dev/null +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java @@ -0,0 +1,263 @@ +package com.fjg.omorntcpfins.util; + +/** + * 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; + + /** + * 步长2 + */ + private static final int STEP_TWO = 2; + /** + * 步长4 + */ + private static final int STEP_FOUR = 4; + /** + * 步长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 finsData(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, int startIndex,int length) { + validateMsgLength(msg, startIndex + length); + String responseDate = extractSubString(msg, startIndex, length); + return responseDate; + } + +} -- Gitee From f9c093524152993ce4e984fcf03578a5ca36a588 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Tue, 30 Jul 2024 13:11:54 +0800 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9sid?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=96=B9=E6=B3=95=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../omorntcpfins/handler/fins/FinsMessageHandler.java | 9 +++++---- .../com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index fbaff0f..c62ad67 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -17,13 +17,14 @@ public class FinsMessageHandler { public static void handle(String msg) { // 获取fins头 String finsHead = FinsMsgAnalysisUtil.finsHead(msg); - // 获取finsdb + // 获取fins-SID + String finsSid = FinsMsgAnalysisUtil.finsSid(msg); System.out.println("收到消息:" + msg); - if (REQUEST_FUTURES.containsKey("1")){ - CompletableFuture future = REQUEST_FUTURES.get("1"); + if (REQUEST_FUTURES.containsKey(finsSid)){ + CompletableFuture future = REQUEST_FUTURES.get(finsSid); // 解析,获取db信息,获取读取到的数据 future.complete(msg.substring(msg.length()-4)); - REQUEST_FUTURES.remove("1"); + REQUEST_FUTURES.remove(finsSid); }else{ System.out.println("未找到对应的请求"); } diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java index 7f65a6f..9e2dee6 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java @@ -228,7 +228,7 @@ public class FinsMsgAnalysisUtil { * 获取FINS协议SID * 服务ID */ - public static String finsData(String msg) { + public static String finsSid(String msg) { validateMsgLength(msg, SID_START + STEP_TWO); return extractSubString(msg, SID_START, STEP_TWO); } -- Gitee From e023c2dff7525930c967f902dc9009c93d9b6ba0 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Tue, 30 Jul 2024 18:55:36 +0800 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20=E5=AE=8C=E5=96=84=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E8=AF=BB=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fjg/omorntcpfins/TestController.java | 1 + .../omorntcpfins/common/queue/HexQueue.java | 52 +++ .../config/fins/client/FinsClientHandler.java | 6 +- .../handler/fins/FinsMessageHandler.java | 21 +- .../util/FinsMsgAnalysisUtil.java | 18 +- .../util/FinsRequestAnalysisUtil.java | 297 ++++++++++++++++++ .../com/fjg/omorntcpfins/util/FinsUtil.java | 48 ++- 7 files changed, 426 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/fjg/omorntcpfins/common/queue/HexQueue.java create mode 100644 src/main/java/com/fjg/omorntcpfins/util/FinsRequestAnalysisUtil.java diff --git a/src/main/java/com/fjg/omorntcpfins/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index 20cf13d..b85966a 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -56,6 +56,7 @@ public class TestController { public static void main(String[] args) { OmronFinsNet omronFinsNet = new OmronFinsNet("192.168.100.202", 9600); + omronFinsNet.Write("D6004",30); System.out.println(omronFinsNet.ReadInt16("D6004").Content); System.out.println(omronFinsNet.ReadInt16("D6005").Content); System.out.println(omronFinsNet.ReadBool("D6000.0").Content); 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 0000000..c923126 --- /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/fins/client/FinsClientHandler.java b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java index 8e374df..a829ab3 100644 --- a/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java @@ -2,6 +2,7 @@ package com.fjg.omorntcpfins.config.fins.client; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import com.fjg.omorntcpfins.common.queue.HexQueue; import com.fjg.omorntcpfins.handler.fins.FinsMessageHandler; import com.fjg.omorntcpfins.util.HexStringUtil; import io.netty.buffer.ByteBuf; @@ -11,6 +12,8 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.extern.slf4j.Slf4j; +import java.util.Arrays; + /** * fins客户端处理器 * @@ -37,6 +40,8 @@ public class FinsClientHandler extends ChannelInboundHandlerAdapter { log.info("握手的消息:{}", order); ByteBuf byteBuf = Unpooled.wrappedBuffer(HexStringUtil.hexToByteArray(order)); ctx.writeAndFlush(byteBuf); + // 初始化绘话队列 + HexQueue.init(); } /** @@ -49,7 +54,6 @@ public class FinsClientHandler extends ChannelInboundHandlerAdapter { String bf = HexUtil.encodeHexStr(bytes); newsState[0] = bf; log.info("接收的消息:{}\n", bf); - log.info("接收的消息:{}\n", HexUtil.decodeHexStr(bf)); FinsMessageHandler.handle(bf); } diff --git a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index c62ad67..79c7658 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -1,6 +1,8 @@ package com.fjg.omorntcpfins.handler.fins; +import com.fjg.omorntcpfins.common.queue.HexQueue; import com.fjg.omorntcpfins.util.FinsMsgAnalysisUtil; +import lombok.extern.slf4j.Slf4j; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -10,23 +12,29 @@ 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); + return; + } // 获取fins头 String finsHead = FinsMsgAnalysisUtil.finsHead(msg); // 获取fins-SID String finsSid = FinsMsgAnalysisUtil.finsSid(msg); - System.out.println("收到消息:" + msg); + log.info("收到消息:" + msg); if (REQUEST_FUTURES.containsKey(finsSid)){ CompletableFuture future = REQUEST_FUTURES.get(finsSid); // 解析,获取db信息,获取读取到的数据 - future.complete(msg.substring(msg.length()-4)); + future.complete(FinsMsgAnalysisUtil.finsData(msg)); REQUEST_FUTURES.remove(finsSid); }else{ - System.out.println("未找到对应的请求"); + log.info("未找到对应的请求"); } } @@ -35,4 +43,11 @@ public class FinsMessageHandler { 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/util/FinsMsgAnalysisUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java index 9e2dee6..5ac6a12 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java @@ -74,6 +74,11 @@ public class FinsMsgAnalysisUtil { */ private static final int READ_WRITE_FLAG_START = 56; + /** + * 数据位 + */ + private static final int DATA_START = 60; + /** * 步长2 */ @@ -254,10 +259,15 @@ public class FinsMsgAnalysisUtil { /** * 获取FINS协议数据 */ - public static String finsData(String msg, int startIndex,int length) { - validateMsgLength(msg, startIndex + length); - String responseDate = extractSubString(msg, startIndex, length); - return responseDate; + public static String finsData(String msg) { + return msg.substring(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 0000000..c9758c6 --- /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 index eab28dd..f4faa27 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java @@ -2,6 +2,7 @@ package com.fjg.omorntcpfins.util; import com.fjg.omorntcpfins.common.RequestMsg; 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; @@ -15,6 +16,8 @@ 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 @@ -73,6 +76,7 @@ public class FinsUtil { private static final String FINS_PC_UNIT_ADDRESS = "00"; /** * FINS协议SID + * 00 - FF */ private static final String FINS_SID = "FF"; /** @@ -100,8 +104,27 @@ public class FinsUtil { // 获取plc地址 String plcIpNode = String.format("%2s", Integer.toHexString(omronConfigProps.getPlcIpNode())).replace(" ", "0"); // 获取pc地址 + String body = getFinsBody(plcIpNode, dbInfo); + 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信息 + * @return {@link String } + */ + private String getFinsBody(String plcIpNode, DbInfoPO dbInfo) { String pcIdNode = String.format("%2s", Integer.toHexString(omronConfigProps.getPcIdNode())).replace(" ", "0"); - String body = FINS_COMMAND + String sid = HexQueue.fetchHex(); + if (sid == null) { + throw new GlobalException("hex队列已空"); + } + return FINS_COMMAND + FINS_ERROR_CODE + FINS_ICF + FINS_RSV @@ -112,15 +135,11 @@ public class FinsUtil { + FINS_PC_NETWORK_ADDRESS + pcIdNode + FINS_PC_UNIT_ADDRESS - + FINS_SID + + sid + FINS_READ_COMMAND + dbInfo.getDbArea() + dbInfo.getDbBlock() + dbInfo.getDbOffset(); - String length = String.format("%8s", Integer.toHexString(body.length() / 2)).replace(' ', '0'); - return FINS_HEAD - + length - + body; } /** @@ -131,12 +150,17 @@ public class FinsUtil { */ private String send(String readMessage) { CompletableFuture future = new CompletableFuture<>(); - FinsMessageHandler.setRequestFutures("1", future); + String sid = FinsRequestAnalysisUtil.finsSid(readMessage); + FinsMessageHandler.setRequestFutures(sid, future); FinsClientConfig.send(readMessage); try { - String s = future.get(); + String s = future.get(3, TimeUnit.SECONDS); + FinsMessageHandler.removeRequestFutures(sid); log.info("read: {}", s); return s; + } catch (TimeoutException e) { + FinsMessageHandler.removeRequestFutures(sid); + throw new GlobalException("数据读取超时"); } catch (InterruptedException | ExecutionException e) { throw new GlobalException("数据读取异常"); } @@ -171,7 +195,13 @@ public class FinsUtil { String readMessage = getReadMessage(address, offset, AreaTypeEnum.WORD); log.info("发送的消息:{}", readMessage); // 发送读取指令 - return send(readMessage); + String msg = send(readMessage); + // 十六进制转换为int + try { + return Integer.parseInt(msg, 16) + ""; + } catch (NumberFormatException e) { + throw new GlobalException("数据读取异常: 数据转换失败-" + msg); + } } -- Gitee From 8bbe2ac6f716bd2ee7ba321db882ae15c0094b93 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Fri, 2 Aug 2024 09:34:22 +0800 Subject: [PATCH 08/12] =?UTF-8?q?refactor:=20=E5=AE=8C=E5=96=84=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E8=AF=BB=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fjg/omorntcpfins/TestController.java | 1 - .../common/enums/FinsFixedValueEnum.java | 28 ++++ .../omorntcpfins/config/OmronConfigProps.java | 4 + .../handler/fins/FinsMessageHandler.java | 38 +++-- .../util/FinsMsgAnalysisUtil.java | 15 +- .../com/fjg/omorntcpfins/util/FinsUtil.java | 21 +-- src/main/resources/application.yml | 6 +- src/main/resources/logback-spring.xml | 149 ++++++++++++++++++ 8 files changed, 234 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/fjg/omorntcpfins/common/enums/FinsFixedValueEnum.java create mode 100644 src/main/resources/logback-spring.xml diff --git a/src/main/java/com/fjg/omorntcpfins/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index b85966a..20cf13d 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -56,7 +56,6 @@ public class TestController { public static void main(String[] args) { OmronFinsNet omronFinsNet = new OmronFinsNet("192.168.100.202", 9600); - omronFinsNet.Write("D6004",30); System.out.println(omronFinsNet.ReadInt16("D6004").Content); System.out.println(omronFinsNet.ReadInt16("D6005").Content); System.out.println(omronFinsNet.ReadBool("D6000.0").Content); 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 0000000..e07e524 --- /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/config/OmronConfigProps.java b/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java index 6c7d10a..fc881d1 100644 --- a/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java +++ b/src/main/java/com/fjg/omorntcpfins/config/OmronConfigProps.java @@ -28,4 +28,8 @@ public class OmronConfigProps { * PC ID 节点 */ private Integer pcIdNode; + /** + * 读写数据超时时间 + */ + private Integer readWriteTimeout; } diff --git a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index 79c7658..be63579 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -1,5 +1,7 @@ package com.fjg.omorntcpfins.handler.fins; +import cn.hutool.core.util.HexUtil; +import com.fjg.omorntcpfins.common.enums.FinsFixedValueEnum; import com.fjg.omorntcpfins.common.queue.HexQueue; import com.fjg.omorntcpfins.util.FinsMsgAnalysisUtil; import lombok.extern.slf4j.Slf4j; @@ -23,18 +25,30 @@ public class FinsMessageHandler { log.info("收到握手信息:" + msg); return; } - // 获取fins头 - String finsHead = FinsMsgAnalysisUtil.finsHead(msg); - // 获取fins-SID - String finsSid = FinsMsgAnalysisUtil.finsSid(msg); - 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("未找到对应的请求"); + 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); + 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()); + e.printStackTrace(); } } diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java index 5ac6a12..c7db9f6 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsMsgAnalysisUtil.java @@ -1,5 +1,7 @@ package com.fjg.omorntcpfins.util; +import cn.hutool.core.util.HexUtil; + /** * fins-msg分析工具 * @@ -92,6 +94,11 @@ public class FinsMsgAnalysisUtil { */ private static final int STEP_EIGHT = 8; + /** + * FINS协议体长度 + */ + private static final int FINS_BODY = 44; + /** * 验证消息长度是否满足最小要求 @@ -260,7 +267,13 @@ public class FinsMsgAnalysisUtil { * 获取FINS协议数据 */ public static String finsData(String msg) { - return msg.substring(DATA_START); + 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); } /** diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java index f4faa27..f294e6e 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java @@ -154,18 +154,24 @@ public class FinsUtil { FinsMessageHandler.setRequestFutures(sid, future); FinsClientConfig.send(readMessage); try { - String s = future.get(3, TimeUnit.SECONDS); + String s = future.get(omronConfigProps.getReadWriteTimeout(), TimeUnit.SECONDS); FinsMessageHandler.removeRequestFutures(sid); log.info("read: {}", s); return s; } catch (TimeoutException e) { FinsMessageHandler.removeRequestFutures(sid); - throw new GlobalException("数据读取超时"); + throw new GlobalException("数据读取超时-[" + sid + "]"); } catch (InterruptedException | ExecutionException e) { throw new GlobalException("数据读取异常"); } } + /** + * TODO str到bool + * + * @param str str + * @return boolean + */ private boolean strToBool(String str) { return !"0000".equals(str); } @@ -204,15 +210,4 @@ public class FinsUtil { } } - - private String read(RequestMsg requestMsg) { - CompletableFuture future = new CompletableFuture<>(); - FinsMessageHandler.setRequestFutures(requestMsg.requestId(), future); - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - log.error("发送的消息:{}", e.getMessage()); - throw new RuntimeException(e); - } - } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a3cebde..ed8611e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,8 +3,12 @@ server: port: 8080 omron: fins-tcp-ip: 192.168.100.202 -# fins-tcp-ip: 192.168.201.61 + # 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 0000000..50edeef --- /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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- Gitee From dc9ab1b2545b46679f0704b9ea3e4e0a3a167c67 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Fri, 2 Aug 2024 09:36:56 +0800 Subject: [PATCH 09/12] =?UTF-8?q?refactor:=20=E5=AE=8C=E5=96=84=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E8=AF=BB=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fjg/omorntcpfins/util/FinsUtil.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java index f294e6e..deaea6c 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java @@ -167,13 +167,20 @@ public class FinsUtil { } /** - * TODO str到bool + * str到bool * * @param str str * @return boolean */ private boolean strToBool(String str) { - return !"0000".equals(str); + try { + int i = Integer.parseInt(str, 16); + return 0 != i; + } catch (NumberFormatException e) { + log.error("数据读取异常: 数据转换失败-" + str); + e.printStackTrace(); + return false; + } } /** @@ -206,6 +213,7 @@ public class FinsUtil { try { return Integer.parseInt(msg, 16) + ""; } catch (NumberFormatException e) { + e.printStackTrace(); throw new GlobalException("数据读取异常: 数据转换失败-" + msg); } } -- Gitee From b5c593be95c7e1e7be339393d3f0a008b0e65e3a Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Fri, 2 Aug 2024 11:25:24 +0800 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=86=99?= =?UTF-8?q?=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- pom.xml | 12 +- .../com/fjg/omorntcpfins/TestController.java | 12 ++ .../common/enums/FinsReadWriteEnum.java | 31 +++++ .../handler/fins/FinsMessageHandler.java | 21 +++- .../com/fjg/omorntcpfins/model/DbInfoPO.java | 5 + .../com/fjg/omorntcpfins/util/FinsUtil.java | 110 +++++++++++++++--- 7 files changed, 169 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/fjg/omorntcpfins/common/enums/FinsReadWriteEnum.java diff --git a/README.md b/README.md index 86b7693..7f3a837 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ FF 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 +说明:写入D6000.1,值为true,长度不足四位需要补足 ``` diff --git a/pom.xml b/pom.xml index 1a87d0a..5423bc4 100644 --- a/pom.xml +++ b/pom.xml @@ -21,12 +21,12 @@ 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/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index 20cf13d..6f484ab 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -54,8 +54,20 @@ public class TestController { 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"; + } + public static void main(String[] args) { OmronFinsNet omronFinsNet = new OmronFinsNet("192.168.100.202", 9600); + omronFinsNet.Write("D6004", 100); System.out.println(omronFinsNet.ReadInt16("D6004").Content); System.out.println(omronFinsNet.ReadInt16("D6005").Content); System.out.println(omronFinsNet.ReadBool("D6000.0").Content); 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 0000000..66b7f46 --- /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/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index be63579..4d5b9b0 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -2,10 +2,12 @@ package com.fjg.omorntcpfins.handler.fins; import cn.hutool.core.util.HexUtil; 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.util.FinsMsgAnalysisUtil; import lombok.extern.slf4j.Slf4j; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -31,11 +33,26 @@ public class FinsMessageHandler { // 十六进制解析 boolean finsHeadByHexValue = FinsFixedValueEnum.isFinsHeadByHexValue(finsHead); if (!finsHeadByHexValue){ - log.error("fins头错误:" + finsHead); + 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); @@ -48,7 +65,7 @@ public class FinsMessageHandler { } catch (IllegalArgumentException e) { log.error("解析错误:{}" , msg); log.error("解析错误:{}" , e.getMessage()); - e.printStackTrace(); + log.error("解析错误:{}" , Arrays.toString(e.getStackTrace())); } } diff --git a/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java b/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java index e9e6774..0a8e7ab 100644 --- a/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java +++ b/src/main/java/com/fjg/omorntcpfins/model/DbInfoPO.java @@ -24,4 +24,9 @@ public class DbInfoPO { */ private String dbOffset; + /** + * db值 + */ + private String dbValue = ""; + } diff --git a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java index deaea6c..af873e4 100644 --- a/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java +++ b/src/main/java/com/fjg/omorntcpfins/util/FinsUtil.java @@ -1,6 +1,5 @@ package com.fjg.omorntcpfins.util; -import com.fjg.omorntcpfins.common.RequestMsg; import com.fjg.omorntcpfins.common.enums.AreaTypeEnum; import com.fjg.omorntcpfins.common.queue.HexQueue; import com.fjg.omorntcpfins.config.OmronConfigProps; @@ -89,7 +88,12 @@ public class FinsUtil { private static final String FINS_WRITE_COMMAND = "0102"; /** - * 获取FINS指令 + * 写入值固定长度 + */ + private static final int FINS_WRITE_VALUE_LENGTH = 4; + + /** + * 获取FINS读指令 * * @param address * @param offset @@ -101,10 +105,33 @@ public class FinsUtil { 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); + String body = getFinsBody(plcIpNode, dbInfo, isWrite); String length = String.format("%8s", Integer.toHexString(body.length() / 2)).replace(' ', '0'); return FINS_HEAD + length @@ -116,9 +143,10 @@ public class FinsUtil { * * @param plcIpNode plc ip节点 * @param dbInfo db信息 + * @param isWrite * @return {@link String } */ - private String getFinsBody(String plcIpNode, DbInfoPO dbInfo) { + 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) { @@ -136,33 +164,35 @@ public class FinsUtil { + pcIdNode + FINS_PC_UNIT_ADDRESS + sid - + FINS_READ_COMMAND + + (isWrite ? FINS_WRITE_COMMAND : FINS_READ_COMMAND) + dbInfo.getDbArea() + dbInfo.getDbBlock() - + dbInfo.getDbOffset(); + + dbInfo.getDbOffset() + + dbInfo.getDbValue(); } /** * 发送FINS指令 * - * @param readMessage + * @param msg * @return */ - private String send(String readMessage) { + private String send(String msg) { + log.info("发送的消息:{}", msg); CompletableFuture future = new CompletableFuture<>(); - String sid = FinsRequestAnalysisUtil.finsSid(readMessage); + String sid = FinsRequestAnalysisUtil.finsSid(msg); FinsMessageHandler.setRequestFutures(sid, future); - FinsClientConfig.send(readMessage); + FinsClientConfig.send(msg); try { String s = future.get(omronConfigProps.getReadWriteTimeout(), TimeUnit.SECONDS); FinsMessageHandler.removeRequestFutures(sid); - log.info("read: {}", s); + log.info("response: {}", s); return s; } catch (TimeoutException e) { FinsMessageHandler.removeRequestFutures(sid); - throw new GlobalException("数据读取超时-[" + sid + "]"); + throw new GlobalException("响应超时-[" + sid + "]"); } catch (InterruptedException | ExecutionException e) { - throw new GlobalException("数据读取异常"); + throw new GlobalException("数据响应异常"); } } @@ -192,7 +222,6 @@ public class FinsUtil { */ public Boolean readBool(String address, int offset) { String readMessage = getReadMessage(address, offset, AreaTypeEnum.BIT); - log.info("发送的消息:{}", readMessage); // 发送读取指令 return strToBool(send(readMessage)); } @@ -206,7 +235,6 @@ public class FinsUtil { */ public String readWord(String address, int offset) { String readMessage = getReadMessage(address, offset, AreaTypeEnum.WORD); - log.info("发送的消息:{}", readMessage); // 发送读取指令 String msg = send(readMessage); // 十六进制转换为int @@ -218,4 +246,56 @@ public class FinsUtil { } } + /** + * 写入布尔值 + */ + 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; + } + } + } -- Gitee From 215136fbb7b2fe72f87353f65ad259d233ac9a0b Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Fri, 2 Aug 2024 13:07:23 +0800 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=86=99?= =?UTF-8?q?=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/enums/FinsErrorCodeEnum.java | 13 ++++++++++ .../handler/fins/FinsMessageHandler.java | 12 ++++++++++ .../com/fjg/omorntcpfins/run/AppRunning.java | 24 +++++++++++-------- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java index b7e7ffd..3ece127 100644 --- a/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java +++ b/src/main/java/com/fjg/omorntcpfins/common/enums/FinsErrorCodeEnum.java @@ -3,6 +3,12 @@ package com.fjg.omorntcpfins.common.enums; import lombok.AllArgsConstructor; import lombok.Getter; +/** + * fins错误代码枚举 + * + * @author fengjianguo + * @date 2024/08/02 + */ @Getter @AllArgsConstructor public enum FinsErrorCodeEnum { @@ -38,4 +44,11 @@ public enum FinsErrorCodeEnum { 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/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index 4d5b9b0..a3058c4 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -1,6 +1,7 @@ package com.fjg.omorntcpfins.handler.fins; import cn.hutool.core.util.HexUtil; +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; @@ -25,6 +26,17 @@ public class FinsMessageHandler { // 过滤握手信息 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 { diff --git a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java index 37965d2..876dc08 100644 --- a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java +++ b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java @@ -6,6 +6,9 @@ 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 @@ -22,17 +25,18 @@ public class AppRunning implements CommandLineRunner { @Override public void run(String... args) throws Exception { - log.info("初始化 ~ (●'网络◡通讯'●) ~ "); + log.info("PLC连接中······"); FinsClientConfig finsClientConfig = new FinsClientConfig(url, port); - finsClientConfig.connect(); -// ExecutorService executorService = Executors.newFixedThreadPool(2); -// //启动Fins通讯 -// executorService.submit(() -> { -// try { -// } catch (Exception e) { -// throw new RuntimeException(e); -// } -// }); +// finsClientConfig.connect(); + ExecutorService executorService = Executors.newFixedThreadPool(1); + //启动Fins通讯 + executorService.submit(() -> { + try { + finsClientConfig.connect(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); //采集数据 // executorService.submit(() -> { // try { -- Gitee From c793a1a0ce44fd0019ac9601d5fafcc97f3e26d8 Mon Sep 17 00:00:00 2001 From: fengjianguo Date: Fri, 2 Aug 2024 13:24:35 +0800 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 15 ++------------- .../com/fjg/omorntcpfins/TestController.java | 11 ----------- .../config/fins/client/FinsClientHandler.java | 14 ++++++++++---- .../handler/fins/FinsMessageHandler.java | 8 +++++--- .../com/fjg/omorntcpfins/run/AppRunning.java | 17 ----------------- 5 files changed, 17 insertions(+), 48 deletions(-) diff --git a/pom.xml b/pom.xml index 5423bc4..a509c52 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-configuration-processor @@ -37,11 +31,6 @@ lombok true - - com.github.dathlin - HslCommunication - 3.3.1 - io.netty diff --git a/src/main/java/com/fjg/omorntcpfins/TestController.java b/src/main/java/com/fjg/omorntcpfins/TestController.java index 6f484ab..1b30a99 100644 --- a/src/main/java/com/fjg/omorntcpfins/TestController.java +++ b/src/main/java/com/fjg/omorntcpfins/TestController.java @@ -65,15 +65,4 @@ public class TestController { return "success"; } - public static void main(String[] args) { - OmronFinsNet omronFinsNet = new OmronFinsNet("192.168.100.202", 9600); - omronFinsNet.Write("D6004", 100); - System.out.println(omronFinsNet.ReadInt16("D6004").Content); - System.out.println(omronFinsNet.ReadInt16("D6005").Content); - System.out.println(omronFinsNet.ReadBool("D6000.0").Content); - System.out.println(omronFinsNet.ReadBool("D6000.1").Content); - System.out.println(omronFinsNet.ReadBool("D6000.2").Content); - System.out.println(omronFinsNet.ReadBool("D6000.3").Content); - System.out.println(omronFinsNet.ReadBool("D6000.4").Content); - } } 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 index a829ab3..d4b9a02 100644 --- a/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/config/fins/client/FinsClientHandler.java @@ -2,7 +2,9 @@ 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; @@ -11,6 +13,7 @@ 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; @@ -35,10 +38,13 @@ public class FinsClientHandler extends ChannelInboundHandlerAdapter { */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - String order = "46494E530000000C00000000000000000000003D"; - order = order.replaceAll(" ", ""); - log.info("握手的消息:{}", order); - ByteBuf byteBuf = Unpooled.wrappedBuffer(HexStringUtil.hexToByteArray(order)); + 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(); diff --git a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java index a3058c4..900c9fe 100644 --- a/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java +++ b/src/main/java/com/fjg/omorntcpfins/handler/fins/FinsMessageHandler.java @@ -1,11 +1,15 @@ package com.fjg.omorntcpfins.handler.fins; -import cn.hutool.core.util.HexUtil; 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; @@ -81,8 +85,6 @@ public class FinsMessageHandler { } } - - public static void setRequestFutures(String key, CompletableFuture future) { REQUEST_FUTURES.put(key, future); } diff --git a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java index 876dc08..9e5a56b 100644 --- a/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java +++ b/src/main/java/com/fjg/omorntcpfins/run/AppRunning.java @@ -27,7 +27,6 @@ public class AppRunning implements CommandLineRunner { public void run(String... args) throws Exception { log.info("PLC连接中······"); FinsClientConfig finsClientConfig = new FinsClientConfig(url, port); -// finsClientConfig.connect(); ExecutorService executorService = Executors.newFixedThreadPool(1); //启动Fins通讯 executorService.submit(() -> { @@ -37,21 +36,5 @@ public class AppRunning implements CommandLineRunner { throw new RuntimeException(e); } }); - //采集数据 -// executorService.submit(() -> { -// try { -// for (int i = 0; i < 60; i++) { -// Thread.sleep(1000); -// finsClientConfig.send( -// "46494e53 0000001a 00000002 00000000 80 00 02 00 06 00 00 00 00 00 01 01 824650000017" -// .replaceAll(" ", "") -// ); -// -// } -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// }); } } -- Gitee