zoukankan      html  css  js  c++  java
  • java android 读写三菱PLC 使用TCP/IP 协议

    本文将使用一个Github开源的组件库技术来读写三菱PLC和西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作

    本项目目前支持C#语言和java语言,C#语言的功能比较齐全,java版本的库还在开发及完善中。

    C# 版本nuget地址:https://www.nuget.org/packages/HslCommunication/       nuget     下载

    C# 版本github地址:https://github.com/dathlin/HslCommunication      fork      star                     如果喜欢可以star或是fork,还可以打赏支持。

    联系作者及加群方式:http://www.hslcommunication.cn/Cooperation

    Java版本的Demo代码 https://github.com/dathlin/HslCommunicationJavaDemo

    在Maven里搜索 HslCommunication 即可。

    com.github.dathlin:HslCommunication
    

      

    本文将展示如何配置网络参数及怎样使用代码来访问PLC数据,希望给有需要的人解决一些实际问题。主要对三菱Q系列PLC的X,Y,M,L,B,V,F,S,D,W,R区域的数据读写,对西门子PLC的M,Q,I,DB块的数据读写,亲测有效。

    此处使用了网线直接的方式,如果PLC接进了局域网,就可以进行远程读写了^_^

    此处使用到了2个命名空间:

    import HslCommunication.Core.Types.OperateResultExOne;
    import HslCommunication.Profinet.Melsec.MelsecMcNet;

    访问测试项目


    下载JAVA版本的Demo源代码,然后运行即可,当然你也可以使用C#版本的来测试。

    https://github.com/dathlin/HslCommunicationJavaDemo

    随便聊聊


    当我们一个上位机需要读取100台西门子PLC设备(此处只是举个例子,凡是都是使用Modbus tcp的都是一样的)的时候,你采用服务器主动去请求100台设备的机制对性能来说是个极大的考验,如果开100个线程去轮询100台设备,那么性能损失将是非常大的,更不用说再增加设备,如果搭建Modbus tcp服务器,就可以完美的解决性能问题,因为连接的压力将会平均分摊给每一台PLC,服务器端只要新增一个时间戳就可以知道客户端有没有连接上。

    我们在100台PLC里都增加发送Modbus tcp方法,将数据发送到服务器的ip和端口上去,服务器根据站号来区分设备。这样就可以搭建一个高性能总站。 本组件支持快速搭建一个高性能的Modbus tcp总站。

    http://www.cnblogs.com/dathlin/p/7782315.html

    关于两种模式


    在PLC端,包括三菱,西门子,欧姆龙以及Modbus Tcp客户端的访问器上,都支持两种模式,短连接模式和长连接模式,现在就来解释下什么原理。

    短连接:每次读写都是一个单独的请求,请求完毕也就关闭了,如果服务器的端口仅仅支持单连接,那么关闭后这个端口可以被其他连接复用,但是在频繁的网络请求下,容易发生异常,会有其他的请求不成功,尤其是多线程的情况下。

    长连接:创建一个公用的连接通道,所有的读写请求都利用这个通道来完成,这样的话,读写性能更快速,即时多线程调用也不会影响,内部有同步机制。如果服务器的端口仅仅支持单连接,那么这个端口就被占用了,比如三菱的端口机制,西门子的Modbus tcp端口机制也是这样的。以下代码默认使用长连接,性能更高,还支持多线程同步。

    在短连接的模式下,每次请求都是单独的访问,所以没有重连的困扰,在长连接的模式下,如果本次请求失败了,在下次请求的时候,会自动重新连接服务器,直到请求成功为止。另外,尽量所有的读写都对结果的成功进行判断。

    关于日志记录


    暂时不支持

    PLC的配置


    环境1:此处以GX Works3为示例,fx5u的配置如下:(感谢 山楂 提供的图片)

    环境2:此处以GX Works2为示例,测试PLC为L02CPU,内置了以太网协议


    环境3:此处以GX Works2为示例,添加以太网模块,型号为QJ71E71-100,组态里添加完成后进行以太网的参数配置,此处需要注意的是:参数的配置对接下来的代码中配置参数要一一对应




    注意:在PLC的以太网模块的配置中,无法设置网络号为0,也无法设置站号为0, 所以此处均设置为1,在C#程序中也使用上述的配置,在代码中均配置为0,如果您自定义设置为网络2, 站号8,那么在代码中就要写对应的数据。如果仍然通信失败,重新测试0,0。

    打开设置:在上图中的打开设置选项,进行其他参数的配置,下图只是举了一个例子,开通了4个端口来支持读写操作:



    端口号设置规则:

    • 为了不与原先存在的系统发生冲突,您在添加自己的端口时尽量使用您自己的端口。
    • 如果读写都需要,尽可能的将读取端口和写入端口区分开来,这样做比较高性能。
    • 如果您的网络状态不是特别稳定,读取端口使用2个,一个受阻切换另一个读取可以提升系统的稳定性。


    本文档仅作组件的测试,所以只用了一个端口作为读写。如果你的程序也使用了一个端口,那么你在读取数据时候, 刚好也在写入(异步操作可能发生这样的情况),那么写入会失败!)(在长连接模式下没有这个问题)

    三菱PLC的数据主要由两类数据组成,位数据和字数据,在位数据中,例如X,Y,M,L都是位数据,字数据例如D,W。 两类的数据在读取解码上存在一点小差别。(事实上也可以先将16个M先赋值给一个D,读取D数据再进行解析, 在读取M的数量比较多的时候,这样操作效率更高)

    初始化访问PLC对象

    如果想使用本组件的数据读取功能,必须先初始化数据访问对象,根据实际情况进行数据的填入。 下面仅仅是测试中的数据:

    MelsecMcNet melsec_net = new MelsecMcNet("192.168.1.192",6001);
    

      

    然后你可以指定一些参数,网络号,网络站号之类的,通常的情况都是不需要指定的

            melsec_net.setNetworkNumber((byte) 0x00);
            melsec_net.setNetworkStationNumber((byte) 0x00);
            melsec_net.setConnectTimeOut(1000);
    

      

      

    打开连接

    melsec_net.ConnectServer();

    如果想知道有没有连接上去

            OperateResult connectResult = melsec_net.ConnectServer();
            if(connectResult.IsSuccess){
                System.out.print("连接成功");
            }
            else {
                System.out.print("连接失败:"+connectResult.Message);
            }
    

      

      

    关于地址的表示方式

    使用字符串表示,这个组件里所有的读写操作提供字符串表示的重载方法,所有的支持访问的类型对应如下,字符串的表示方式存在十进制和十六进制的区别:

    • 输入继电器:"X100","X1A0"            // 字符串为十六进制机制
    • 输出继电器:"Y100" ,"Y1A0"           // 字符串为十六进制机制
    • 内部继电器:"M100","M200"           // 字符串为十进制
    • 锁存继电器:"L100"  ,"L200"           // 字符串为十进制
    • 报警器:       "F100", "F200"            // 字符串为十进制
    • 边沿继电器:"V100" , "V200"          // 字符串为十进制
    • 链接继电器:"B100" , "B1A0"          // 字符串为十六进制
    • 步进继电器:"S100" , "S200"          // 字符串为十进制
    • 数据寄存器:"D100", "D200"           // 字符串为十进制
    • 链接寄存器:"W100" ,"W1A0"         // 字符串为十六进制
    • 文件寄存器:"R100","R200"            // 字符串为十进制

    关于数据分类

    以上地址的数据是分为位数据和字数据的,位数据只能调用ReadBool,字数据用Read及其扩展的方法

    简单读写的示例

            boolean[] M100 = melsec_net.ReadBool("M100",(short) 1).Content;            // 读取M100是否通,十进制地址
            boolean[] X1A0 = melsec_net.ReadBool("X1A0",(short) 1).Content;            // 读取X1A0是否通,十六进制地址
            boolean[] Y1A0 = melsec_net.ReadBool("Y1A0",(short) 1).Content;            // 读取Y1A0是否通,十六进制地址
            boolean[] B1A0 = melsec_net.ReadBool("B1A0",(short) 1).Content;            // 读取B1A0是否通,十六进制地址
            short short_D1000 = melsec_net.ReadInt16("D1000").Content;                 // 读取D1000的short值  ,W3C0,R3C0 效果是一样的
            int int_D1000 = melsec_net.ReadInt32("D1000").Content;                     // 读取D1000-D1001组成的int数据
            float float_D1000 = melsec_net.ReadFloat("D1000").Content;                 // 读取D1000-D1001组成的float数据
            long long_D1000 = melsec_net.ReadInt64("D1000").Content;                   // 读取D1000-D1003组成的long数据
            double double_D1000 = melsec_net.ReadDouble("D1000").Content;              // 读取D1000-D1003组成的double数据
            String str_D1000 = melsec_net.ReadString("D1000", (short) 10).Content;     // 读取D1000-D1009组成的条码数据
    
    
    
            melsec_net.Write("M100", new boolean[] { true} );                          // 写入M100为通
            melsec_net.Write( "Y1A0", new boolean[] { true } );                        // 写入Y1A0为通
            melsec_net.Write( "X1A0", new boolean[] { true } );                        // 写入X1A0为通
            melsec_net.Write( "B1A0", new boolean[] { true } );                        // 写入B1A0为通
            melsec_net.Write( "D1000", (short)1234);                                   // 写入D1000  short值  ,W3C0,R3C0 效果是一样的
            melsec_net.Write( "D1000", 1234566);                                // 写入D1000  int值
            melsec_net.Write( "D1000", 123.456f);                               // 写入D1000  float值
            melsec_net.Write( "D1000", 123.456d);                               // 写入D1000  double值
            melsec_net.Write( "D1000", 123456661235123534L);                    // 写入D1000  long值
            melsec_net.Write( "D1000", "K123456789");                           // 写入D1000  string值
    

      

    X,Y,M,L,F,V,B,S位数据的读写说明

    • X 输入继电器
    • Y 输出继电器
    • M 内部继电器
    • L 锁存继电器
    • F 报警器
    • V 边沿继电器
    • B 链接继电器
    • S 步进继电器

    本小节将展示八种位数据的读取,虽然更多的时候只是读取D数据即可,或者是将位数据批量挪到D数据中, 但是在此处仍然进行介绍单独的读取X,Y,M,L,F,V,B,S,由于这八种读取手法一致,故针对M数据进行介绍,其他的您可以自己测试。

    如下方法演示读取了M200-M209这10个M的值,注意:读取长度必须为偶数,即时写了奇数,也会补齐至偶数,读取和写入的最大长度为7168,否则报错。如需实际需求确实大于7168的,请分批次读取。
    返回值解析:如果读取正常则共返回10个字节的数据,以下示例数据进行批量化的读取

     OperateResultExOne<boolean[]> read = melsec_net.ReadBool("M100",(short)10);
            if(read.IsSuccess){
                boolean m100 = read.Content[0];
                boolean m101 = read.Content[1];
                boolean m102 = read.Content[2];
                boolean m103 = read.Content[3];
                boolean m104 = read.Content[4];
                boolean m105 = read.Content[5];
                boolean m106 = read.Content[6];
                boolean m107 = read.Content[7];
                boolean m108 = read.Content[8];
                boolean m109 = read.Content[9];
            }
            else {
                System.out.print("读取失败:"+read.Message);
            }
    

      

            // 写入测试,M100-M104 写入测试 此处写入后M100:通 M101:断 M102:断 M103:通 M104:通
            boolean[] values = new boolean[]{true,false,false,true,true};
            OperateResult write = melsec_net.Write("M100",values);
            if(write.IsSuccess){
                System.out.print("写入成功");
            }
            else {
                System.out.print("写入失败:"+write.Message);
            }
    

      

    D,W,R字数据的读写操作

    此处读取针对中间存在整数数据的情况,因为两者读取方式相同,故而只演示一种数据读取, 使用该组件读取数据,一次最多读取或写入960个字,超出则失败。 如果读取的长度确实超过限制,请考虑分批读取。

            OperateResultExOne<byte[]> read1 = melsec_net.Read("D100",(short)5);
            if(read1.IsSuccess){
                short D100 = melsec_net.getByteTransform().TransByte(read1.Content,0);
                short D101 = melsec_net.getByteTransform().TransByte(read1.Content,2);
                short D102 = melsec_net.getByteTransform().TransByte(read1.Content,4);
                short D103 = melsec_net.getByteTransform().TransByte(read1.Content,6);
                short D104 = melsec_net.getByteTransform().TransByte(read1.Content,8);
            }
            else {
                System.out.print("读取失败:"+read1.Message);
            }
    

      

            // D100为1234,D101为8765,D102为1234,D103为4567,D104为-2563
            short[] values2 = new short[]{1335, 8765, 1234, 4567, -2563 };
            OperateResult write = melsec_net.Write("M100",values2);
            if(write.IsSuccess){
                System.out.print("写入成功");
            }
            else {
                System.out.print("写入失败:"+write.Message);
            }
    

     

    一个实际中复杂的例子演示

    实际中可能碰到的情况会很复杂,一台设备中需要上传的数据包含了温度,压力,产量,规格等等信息,在一串数据中 会包含各种各样的不同的数据,上述的读取D,读取M,读取条码的方式不太好用,所以此处做一个完整示例的演示,假设我们需要读取 D4000-D4009的数据,假设D4000存放了温度数据,55.1℃在D中为551,D4001存放了压力数据,1.23MPa在D中存放为123,D4002存放了 设备状态,0为停止,1为运行,D4003存放了产量,1000就是指1000个,D4004备用,D4005-D4009存放了规格,以下代码演示如何去解析数据:

            //解析复杂数据
            OperateResultExOne<byte[]> read3 = melsec_net.Read("D4000", (short) 10);
            if (read3.IsSuccess)
            {
                double 温度 = melsec_net.getByteTransform().TransInt16(read3.Content, 0) / 10d;//索引很重要
                double 压力 = melsec_net.getByteTransform().TransInt16(read3.Content, 2) / 100d;
                boolean IsRun = melsec_net.getByteTransform().TransInt16(read3.Content, 4) == 1;
                int 产量 =melsec_net.getByteTransform().TransInt32(read3.Content, 6);
                String 规格 = melsec_net.getByteTransform().TransString(read3.Content, 10, 10,"ascii");
            }
            else
            {
                System.out.print("读取失败:"+read3.Message);
            }
    

      

    更详细的信息,可以参照源代码里面的测试项目。


  • 相关阅读:
    linux 命令——19 find (转)
    linux 命令——18 locate (转)
    linux 命令——17 whereis(转)
    linux 命令——16 which(转)
    linux 命令——15 tail (转)
    linux 命令——14 head (转)
    Java for LeetCode 038 Count and Say
    Java for LeetCode 037 Sudoku Solver
    Java for LeetCode 036 Valid Sudoku
    Java for LeetCode 035 Search Insert Position
  • 原文地址:https://www.cnblogs.com/dathlin/p/9176069.html
Copyright © 2011-2022 走看看