手摸手教你撕碎西门子S7通讯协议14--开发自己的通讯库读数据

1、S7通讯回顾


    - (1)建立TCP连接      Socket.Connect- 

     - (2)发送访问请求     COTP- 

     - (3)交换通信信息     Setup Communication- 

     - (4)执行相关操作     读、写、PLC启停、时间、上传下载-  
 

2、s7报文回顾 

1、CTOP请求连接

2、SetupCommunication通讯数据交换

3、S7COMM-Read报文

4、S7COMM-Write报文

3、第3方通讯库回顾 

S7.NET是一个广泛应用于.NET平台的西门子PLC通信库,在使用西门子PLC进行工业自动化控制的过程中,经常利用这个工具实现与PLC进行数据交换,它的方法通过上节操作应用可以看到格式是这样的,读取使用的命令是:plc.Read(varaddr),写入使用的命令是: plc.Write(varaddr, ts3),而我们的方法是一个个拼装字节数组,然后发送,然后解析报文,这对于应用者来说再痛苦了,能不能象第3方库那样,给个地址,调用命令就可以实现读取了,当然可以,这就是我们的目的,必须地强大武装起来,封装后的通讯库那是硬核,黄金。

 4、开始封装

1、创建项目

 

2、添加类库项目

 

  

3、创建基础类

 

 

 

4、封装连接的方法

 

 

 /// <summary>
 /// 建立连接:包括3步:1》tcp连接,2》COTP连接,3》通信连接
 /// </summary>
 /// <param name="timeout">超时时间</param>
 /// <returns></returns>
 public Result Connect(int timeout = 50)
 {
     TimeoutObject.Reset();
     Result result = new Result();
     try
     {
         if (socket == null)
             throw new Exception("通信对象未初始化");
         int count = 0;
         while (count < timeout)
         {
             // 第一步:建立tcp连接,即socket.Connected
             // 断线重连:
             // 1、被服务端主动踢掉(服务连接列表里已经没有当前客户端信息了)
             // 2、断网(拔网线)   客户端(不知道-》新的端口进行连接)、服务端(可以知道、检查客户端的心跳)
             if (!(!socket.Connected || (socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0))))
             {
                 return result;
             }
             try
             {
                 socket?.Close();
                 socket.Dispose();
                 socket = null;
                 socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                 socket.BeginConnect(_ip, _port, callback =>
                 {
                     connectState = false;
                     var cbSocket = callback.AsyncState as Socket;
                     if (cbSocket != null)
                     {
                         connectState = cbSocket.Connected;
                         if (cbSocket.Connected)
                             cbSocket.EndConnect(callback);
                     }
                     TimeoutObject.Set();
                 }, socket);
                 TimeoutObject.WaitOne(2000, false);
                 if (!connectState) throw new Exception();
                 else break;
             }
             catch (SocketException ex)
             {
                 if (ex.ErrorCode == 10060)
                     throw new Exception(ex.Message);
             }
             catch (Exception) { }
             finally
             {
                 count++;
             }
         }
         if (socket == null || !socket.Connected || ((socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0))))
         {
             throw new Exception("网络连接失败");
         }
         else
         {
             // 第二步:建立COTP连接请求
             result = COTPConnection();
             if (!result.IsSuccessed) return result;
             // 第三步:建立Setup通信
             result = SetupCommunication();
             if (!result.IsSuccessed) return result;
         }
     }
     catch (Exception ex)
     {
         result.IsSuccessed = false;
         result.Message = ex.Message;
     }
     return result;
 }

 /// <summary>
 /// 建立cotp连接 
 /// </summary>
 /// <returns></returns>
 private Result COTPConnection()
 {
     //COTP连接包括2个部分,共22个字节),22=4+18
     Result result = new Result();
     byte[] cotpBytes = new byte[] {
         //1)TPKT包括4个字节
         0x03,//版本号,版本默认3
         0x00,//默认保留为0
         0x00,//整个请求字节高位
         0x16,//整个请求字节低位(0x16转换成为10进制就是22)
         //2)COTP包括18个字节 
         0x11,//当前字节以后的字节数(不包括自已,0x11转换成10进制就是17)
         0xe0,//PDU type,0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输
         0x00,//DST reference(2个字节)
         0x00,//
         0x00,//SRC reference(2个字节)
         0x00,//
         0x00,//class(固定的) 
         0xc1, //Parameter-code  src-tsap 上位机
         0x02,  //Parameter-Len   
         0x10 ,   //Source TSAP:01->PG;02->OP;03->S7单边(服务器模式);0x10->S7双边通 
         0x00,   //机架与插槽号为0   
         0xc2,//Parameter-code  dst-tsap PLC 
         0x02,//Parameter len  
         0x03,//Destination TSAP  
         (byte)(_rack*32+_slot),//机架与插槽号: 
         0xc0,  //    Parameter code:tpdu-size 
         0x01,   //   Parameter length
         0x0a  //    TPDU size 
     };
     try
     {
         //发送报文
         socket.Send(cotpBytes);
         //响应报文的长度是固定的22个字节
         byte[] respBytes = new byte[22];
         int count = socket.Receive(respBytes, 0, 22, SocketFlags.None);
         //第5个字节是pdu type,具体是:0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输
         if (respBytes[5] != 0xd0)
         {
             throw new Exception("COTP连接响应异常");
         }
     }
     catch (Exception ex)
     {
         result.IsSuccessed = false;
         result.Message = "COTP连接未建立!" + ex.Message;
     }
     return result;
 }

 /// <summary>
 /// 建立通讯连接
 /// </summary>
 /// <returns></returns>
 private Result SetupCommunication()
 {
     //s7comm连接包括4个部分,共25个字节,即25=4+3+10+8
     Result result = new Result();
     byte[] setupBytes = new byte[] {
         // 1)TPKT包括4个字节
         0x03,//版本默认3
         0x00,//保留默认0
         0x00,//整个请求字节数高位
         0x19,//整个请求字节数低位,0x19转换成10进制就是25
             //2)COTP包括3个字节
         0x02,//当前字节以后的字节数(不包括自已,0x02转换成10进制就是2),注意这个“当前字节以后的字节数”是指COTP这部分,而不是整个字节部分
         0xf0,//PDU Type,0xe0 连接请求,0xd0 连接确认,0x08 断开请求,0x0c 断开确认,0x05 拒绝访问,0x01 加急数据,0x02 加急数据确认,0x04 用户数据,0x07 TPDU错误,0xf0 数据传输
         0x80,//TPDU number,固定值
         // 3)Header包括10个字节
         0x32,//默认值,协议id
         0x01,//ROSCTR,0x01 Job request。主站发送请求,0x02 Ack。从站响应请求不带数据,0x03 Ack_Data。从站响应请求并带有数据,0x07 Userdata。原始协议的扩展。读取编程/调试、SZL读取、安全功能、时间设置等
         0x00,//Redundancy Identification (Reserved)固定值,占2个字节
         0x00,
         0x00,//Protocol Data Unit Reference固定值,占2个字节
         0x00,
         0x00,//Parameter length参数长度,占2个字节
         0x08,
         0x00,//Data length数据长度,占2个字节
         0x00,
         // 4)Parameter包括8个字节
         0xf0,//Function功能码,具体是:0x00 CPU服务,0xF0 设置通信,0x04 读取变量,0x05 写变量,0x1A 请求下载,0x1B 下载块,0x1C 下载结束,0x1D 开始上传,0x1E 上传,0x1F 结束上传,0x28 PLC 控制,0x29 PLC 停止
         0x00,//保留默认值
         0x00,//Max AmQ(parallel jobs with ack) calling,占2个字节
         0x03,
         0x00,//Max AmQ(parallel jobs with ack) called,占2个字节
         0x03,
         0x03,//PDU length,占2个字节,0x03co转换成10进制就是960
         0xc0
     };
     try
     {
         socket.Send(setupBytes);
         //响应报文的长度就是固定的27个字节
         byte[] respBytes = new byte[27];
         int count = socket.Receive(respBytes);
         // 拿到PDU长度   后续进行报文组装和接收的时候可以参考
         byte[] pdu_size = new byte[2];
         pdu_size[0] = respBytes[26];
         pdu_size[1] = respBytes[25];
         this._pduSize = BitConverter.ToInt16(pdu_size,0);
     }
     catch (Exception ex)
     {
         result.IsSuccessed = false;
         result.Message = "Setup通信未建立!" + ex.Message;
     }
     return result;
 }

5、封装读取数据的方法


        /// <summary>
        /// 读取数据
        /// </summary>
        /// <typeparam name="T">返回值类型</typeparam>
        /// <param name="variable">变量地址</param>
        /// <param name="count">变量个数</param>
        /// <returns></returns>
        public Result<T> Read<T>(string variable, int count = 1)
        {
            DataParameter dataParameter = new DataParameter();
            Type mtype = typeof(T);

            try
            {
                dataParameter.GetFromVariable(variable);// 解析地址,最终是需要一个DataParameter
                if (mtype.Name == "Boolean")
                {
                    //dataParameter.ParamItemType = ParameterItemType.BIT;
                    dataParameter.DataItemType = DataItemType.BIT;
                }
                dataParameter.Count = count;

                // 获取数据
                return this.Read<T>(
                    dataParameter.ParamItemType,
                    dataParameter.DataItemType,
                    dataParameter.Count,
                    dataParameter.AreaType,
                    dataParameter.DBNumber,
                    dataParameter.ByteAddress,
                    dataParameter.BitAddress);
            }
            catch (Exception ex)
            {
                return new Result<T>(false, ex.Message);
            }
        }

6、添加项目引用

5、测试效果

先看下PLC的数据

1、读取浮点数

2、读取short数

3、读取bool数

 4、读取string

 

6、小结

 自己的通讯库是够甘甜的,和新疆的葡萄一样的香甜,有人可能要问,前面不是有第3方通讯库写好的,拉来用不就行了吗?是的,没有错,别人的东西可以拉来用,但是我们的目标不仅是使用,而是形成自己的产品,实力,别人的库也是写的代码,只是我们看不到而已。同样的,我们自己也可以,强国工程,从小做起,不得不来个大大的鸡腿,真香啊。

原创不易,打字截图不易,走过路过,不要错过,欢迎点赞,收藏,转载,复制,抄袭,留言,动动你的金手指,早日实现财务自由

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqwest

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值