C#上位机与欧姆龙PLC的通信08----开发自己的通讯库读写数据

1、介绍

前面已经完成了7项工作:

C#上位机与欧姆龙PLC的通信01----项目背景-CSDN博客

C#上位机与欧姆龙PLC的通信02----搭建仿真环境-CSDN博客

C#上位机与欧姆龙PLC的通信03----创建项目工程-CSDN博客

C#上位机与欧姆龙PLC的通信04---- 欧姆龙plc的存储区

C#上位机与欧姆龙PLC的通信05---- HostLink协议(C-Mode版)

C#上位机与欧姆龙PLC的通信06---- HostLink协议(FINS版)

C#上位机与欧姆龙PLC的通信07----使用第3方通讯库读写数据

 这当中,06是重点的重点,需要非常熟悉才能自己写通讯库,封装自己的库需要掌握socket通讯,串口通讯,同步异步,集合数组,字节序列等技能点,这是走向武林高手的必经之路,这样才能强大自己,丰满的肌肉需要一步步啃。

2、开搞

1、创建VS解决方案项目

2、添加类库项目

类库项目创建目录Omron及Base及5个基础类

添加对类库的引用

3、编写基础类库文件

 1)AreaType.cs

存储区对应厂家手册规定的值,不能自己改。

(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0,H字:B2)

    /// <summary>/// 存储区枚举/// </summary>public enum AreaType{CIOBIT = 0x30,WBIT = 0x31,DMBIT = 0x02,ABIT = 0x33,HBIT = 0x32,CIOWORD = 0xB0,WWORD = 0xB1,DMWORD = 0x82,AWORD = 0xB3,HWORD = 0xB2 }

 2)DataAddress.cs

/// <summary>
/// 地址模型类
/// </summary>
public class DataAddress
{/// <summary>/// 区域类型/// </summary>public AreaType AreaType { get; set; }/// <summary>/// Word起始地址/// </summary>public ushort WordAddress { get; set; }/// <summary>/// Bit起始地址/// </summary>public byte BitAddress { get; set; }}

3)EndianType.cs

 这就是大小端的列举,涉及字节序列中的高位低位交换等处理

namespace Zhaoxi.Communication.Omron.Base
{/// <summary>/// 字节序列/// </summary>public enum EndianType{AB, BA,ABCD, CDAB, BADC, DCBA,ABCDEFGH, GHEFCDAB, BADCFEHG, HGFEDCBA}
}

4)Result.cs

通信结果类

namespace Omron.Communimcation.Fins.Omron
{/// <summary>/// 通讯结果类/// </summary>/// <typeparam name="T"></typeparam>public class Result<T>{/// <summary>/// 状态/// </summary>public bool IsSuccessed { get; set; }/// <summary>/// 对应的消息/// </summary>public string Message { get; set; }/// <summary>/// 数据列表/// </summary>public List<T> Datas { get; set; }public Result() : this(true, "OK") { }public Result(bool state, string msg) : this(state, msg, new List<T>()) { }public Result(bool state, string msg, List<T> datas){this.IsSuccessed = state; Message = msg; Datas = datas;}}public class Result : Result<bool> { }
}

4、编写核心的通信类Fins

这个类就是实现读取和写入数据的方法,封装了报文的每个组成部分,象请求的报文头,命令码,数据处理,发送和接收处理等,字节处理,寄存器的数据类型处理,辅助方法等很多。该类很强大,能很好的处理short,ushort,bool,float,OOP的思想在这个类中得到了很好的发挥,注释很详细,代码很规范,需要很深的功底才可以写得出来的,需要对fins的报文结构指令内容非常精通才能写好,各位高僧能人自行阅读,欢迎留言。

using Omron.Communimcation.Fins.Omron.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace Omron.Communimcation.Fins.Omron
{/// <summary>/// 欧姆龙FINS协议通讯库/// </summary>public class FinsTcp{/// <summary>/// tcp服务器地址/// </summary>string _ip;/// <summary>/// tcp端口号/// </summary>int _port;/// <summary>/// 目标节点/// </summary>byte _da;/// <summary>/// 源节点/// </summary>byte _sa;/// <summary>/// socket连接对象(全局变量)/// </summary>Socket socket = null;/// <summary>/// 超时对象/// </summary>ManualResetEvent TimeoutObject = new ManualResetEvent(false);/// <summary>/// 连接状态/// </summary>bool connectState = false;/// <summary>/// 头部字节/// </summary>byte[] finsTcpHeader = new byte[] { 0x46, 0x49, 0x4E, 0x53 };/// <summary>/// 构造方法/// </summary>/// <param name="ip">TCP服务器IP</param>/// <param name="port">TCP服务器端口</param>/// <param name="da">PLC节点</param>/// <param name="sa">PC端节点</param>public FinsTcp(string ip, int port, byte da = 0x00, byte sa = 0x00){_ip = ip;_port = port;_da = da;_sa = sa;// 初始化一个通信对象socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);}#region 操作PLC/// <summary>/// 连接PLC/// </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){// 断线重连:// 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);//异步连接PLCsocket.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){throw new Exception("网络连接异常");}finally{count++;}}if (socket == null || !socket.Connected || ((socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0)))){throw new Exception("网络连接失败");}else{// 2、建立连接result = this.SetupConnect();if (!result.IsSuccessed){return result;}}}catch (Exception ex){result.IsSuccessed = false;result.Message = ex.Message;}return result;}/// <summary>/// 启动PLC/// </summary>/// <returns></returns>public Result Run(){return PlcSate(0x01);}/// <summary>/// 停止PLC/// </summary>/// <returns></returns>public Result Stop(){return PlcSate(0x02);}/// <summary>/// PLC状态/// </summary>/// <param name="state">0401启动,0402停止</param>/// <returns></returns>public Result PlcSate(byte state){Result result = new Result();try{var connectState = this.Connect();if (!connectState.IsSuccessed){throw new Exception(connectState.Message);}List<byte> baseBytes = this.GetBaseCommand(0x04, state); //构建命令this.Send(baseBytes);//发送命令}catch (Exception ex){result.IsSuccessed = false;result.Message = ex.Message;}return result;}#endregion#region 读取数据/ <summary>/  读取命令,原有方法/ </summary>/ <typeparam name="T">返回的数据类型,如ushort,short,int32,float等</typeparam>/ <param name="areaCode">存储区代码</param>/ <param name="wStartAddr">开始地址(16进制格式,如ox64)</param>/ <param name="bStartAddr">Bit地址(16进制格式,如ox00)</param>/ <param name="count">读取个数(16进制格式,如读取12个,就是ox0b)</param>/ <returns></returns>//public Result<T> Read<T>(AreaType areaCode, ushort wStartAddr, byte bStartAddr, ushort count)//{//     //发送: 46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 80 00 02 00 02 00 00 0A 00 00 01 01 82 00 C8 00 00 01//     //接收: 46 49 4E 53 00 00 00 18 00 00 00 02 00 00 00 00 C0 00 02 00 02 00 00 0A 00 00 01 01 00 00 02 2E//    Result<T> result = new Result<T>();//    int typeLen = 1;//数据类型所占的字节宽度//    if (!typeof(T).Equals(typeof(bool)))//    {//        typeLen = Marshal.SizeOf<T>() / 2;   // 每一个数据需要多少个寄存器//    }//    try//    {//        byte[] bytes = new byte[] {//        0x00,0x00,0x00,0x02,   // 读写的时候固定传这个值 //        0x00,0x00,0x00,0x00, // 错误代码;//        0x80,// ICF //        0x00, // Rev  //        0x02, // GCT //        0x00,_da,0x00, // DNA DA1 DA2 ,即目标网络号,目标节点号,目标单元号//        0x00,_sa,0x00,// SNA SA1 SA2,即源网络号,源节点号,源单元号//        0x00,   // SID,固定值  //        0x01,0x01,// 命令码,读操作固定值为0101 //        (byte)areaCode,  // 存储区//        (byte)(wStartAddr/256%256),//Word起始地址,占2个字节//        (byte)( wStartAddr%256),//        bStartAddr,//起始位地址,占1个字节//        (byte)(count*typeLen/256%256),//读取个数,占2个字节//        (byte)(count*typeLen%256)//    };//        //命令字节//        List<byte> commandBytes = new List<byte>();//        // 计算字节长度,占4个字节//        List<byte> lengthbyte = new List<byte>();//        lengthbyte.Add((byte)(bytes.Length / 256 / 256 / 256 % 256));//        lengthbyte.Add((byte)(bytes.Length / 256 / 256 % 256));//        lengthbyte.Add((byte)(bytes.Length / 256 % 256));//        lengthbyte.Add((byte)(bytes.Length % 256));//        commandBytes.AddRange(finsTcpHeader);//加入头部//        commandBytes.AddRange(lengthbyte.ToArray());//加入长度 //        commandBytes.AddRange(bytes);//加入协议内容//        socket.Send(commandBytes.ToArray());//发送报文//        //1.1接收数据中的前8个字节,即fins头部 //        byte[] respHeader = new byte[8];//        socket.Receive(respHeader, 0, 8, SocketFlags.None);//        //1.2 判断前面4个字节是否是FINS//        for (int i = 0; i < 4; i++)//        {//            if (respHeader[i] != finsTcpHeader[i])//            {//                throw new Exception("响应报文无效");//            }//        } //        //1.3、获取剩余报文长度:  00 00 00 18//        byte[] lenBytes = new byte[4];//        lenBytes[0] = respHeader[7];//        lenBytes[1] = respHeader[6];//        lenBytes[2] = respHeader[5];//        lenBytes[3] = respHeader[4];//        int len = BitConverter.ToInt32(lenBytes);//        Console.WriteLine("剩余报文长度:" + len); //        //1.4 接收剩余字节//        byte[] dataBytes = new byte[len];//        socket.Receive(dataBytes, 0, len, SocketFlags.None);//        // 1.5 错误信息判断 ,确定没有ErrorCode //        var code = dataBytes[4] | dataBytes[5] | dataBytes[6] | dataBytes[7];//        if (code > 0)//        {//            Console.WriteLine($"有错误[{code}]");//        }   //        code = (dataBytes[20] << 8) | dataBytes[21];  // 01 01   0000 0001 0000 0001//        if (code > 0)//        {//            Console.WriteLine($"有错误[{code}]");  // 字典处理:{257:"当前网络环境中无法匹配此节点"}//        } //        // 1.6 开始解析数据部分//        List<byte> dataList = new List<byte>(dataBytes);//        dataList.RemoveRange(0, 22);//移除指定部分,保留数据内容//        //循环处理数据//        for (int i = 0; i < dataList.Count;)//        {//            if (typeof(T) == typeof(bool))//布尔类型//            { //                Type tConvert = typeof(Convert); //                //查找 convert这个类中的toboolean方法//                MethodInfo method = tConvert.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.Name == "ToBoolean") as MethodInfo;//                result.Datas.Add((T)method.Invoke(tConvert, new object[] { int.Parse(dataList[i++].ToString()) }));//            }//            else //short,ushort,float,double类型//            {//                List<byte> datas = new List<byte>();//                // Word -> Short,DWrod-> Float,DD-> Double//                for (int j = 0; j < typeLen * 2; j++)//                { //                    datas.Add(dataList[i++]); // 只能处理2个字节的情况,其他的处理不了,需要进行字节序的调整//                } //                if (typeLen == 1)//2个字节,适合ushort,short,int16//                {//                    datas = new List<byte>(this.SwitchEndian(datas.ToArray(), EndianType.AB));//                } //                else if (typeLen == 2)//4个字节,适合int32,float//                {//                    datas = new List<byte>(this.SwitchEndian(datas.ToArray(), EndianType.CDAB));//                } //                Type tBitConverter = typeof(BitConverter);//                MethodInfo method = tBitConverter.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.ReturnType == typeof(T)) as MethodInfo;//                if (method == null)//                {//                    Console.WriteLine("未找到匹配的数据类型转换方法");//                } //                result.Datas.Add((T)method?.Invoke(tBitConverter, new object[] { datas.ToArray(), 0 }));//            } //        }//    }//    catch (Exception ex)//    {//        return new Result<T>(false, ex.Message);//    }//    return result;//}/// <summary>/// 读取操作/// </summary>/// <typeparam name="T">返回的数据类型</typeparam>/// <param name="address">存储区地址</param>/// <param name="count">读取数量</param>/// <returns></returns>public Result<T> Read<T>(string address, ushort count){Result<T> result = new Result<T>();try{DataAddress dataAddress = this.AnalysisAddress(address);result = this.Read<T>(dataAddress, count);}catch (Exception ex){result.IsSuccessed = false;result.Message = ex.Message;}return result;}/// <summary>/// 读取操作/// </summary>/// <typeparam name="T">返回的数据类型</typeparam>/// <param name="dataAddress">地址模型</param>/// <param name="count">读取数量</param>/// <returns></returns>public Result<T> Read<T>(DataAddress dataAddress, ushort count){Result<T> result = new Result<T>();int typeLen = 1;if (!typeof(T).Equals(typeof(bool))){typeLen = Marshal.SizeOf<T>() / 2;// 每一个数据需要多少个寄存器}try{//1、判断连接状态var connectState = this.Connect();//没有连接成功时则返回if (!connectState.IsSuccessed){throw new Exception(connectState.Message);}//2、构建基本命令:0101表示读取,0102表示写入//命令头部List<byte> baseBytes = this.GetBaseCommand(0x01, 0x01);//存储区类型baseBytes.Add((byte)dataAddress.AreaType);//开始地址,占3个字节baseBytes.Add((byte)(dataAddress.WordAddress / 256 % 256));baseBytes.Add((byte)(dataAddress.WordAddress % 256));baseBytes.Add(dataAddress.BitAddress);//读取数量,占2个字节baseBytes.Add((byte)(count * typeLen / 256 % 256));baseBytes.Add((byte)(count * typeLen % 256));//3、发送命令this.Send(baseBytes);//4、获取响应的数据报文List<byte> dataList = this.CheckResponseBytes();//5、解析数据报文for (int i = 0; i < dataList.Count;){if (typeof(T) == typeof(bool))//bool类型的处理{dynamic boolValue = dataList[i++] == 0x01;result.Datas.Add(boolValue);}else  //short,ushort,float,double类型的处理{List<byte> datas = new List<byte>();for (int j = 0; j < typeLen * 2; j++){datas.Add(dataList[i++]); // 只能处理2个字节的情况,其他的处理不了,需要进行字节序的调整}datas = this.SwitchBytes(datas); //交换字节顺序Type tBitConverter = typeof(BitConverter);MethodInfo method = tBitConverter.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.ReturnType == typeof(T)) as MethodInfo;if (method == null){throw new Exception("未找到匹配的数据类型转换方法");}result.Datas.Add((T)method?.Invoke(tBitConverter, new object[] { datas.ToArray(), 0 }));}}}catch (Exception ex){return new Result<T>(false, ex.Message);}return result;}#endregion#region 写入数据/// <summary>/// 写入操作/// </summary>/// <typeparam name="T">写入的数据类型</typeparam>/// <param name="values">写入的具体数据</param>/// <param name="address">写入的具体地址</param>/// <returns></returns>public Result Write<T>(List<T> values, string address){Result result = new Result();try{DataAddress dataAddress = this.AnalysisAddress(address);result = this.Write<T>(values, dataAddress);}catch (Exception ex){result.IsSuccessed = false;result.Message = ex.Message;}return result;}/// <summary>/// 写入操作/// </summary>/// <typeparam name="T">写入的数据类型</typeparam>/// <param name="values">写入的具体数据</param>/// <param name="dataAddress">地址模型</param>/// <returns></returns>public Result Write<T>(List<T> values, DataAddress dataAddress){Result result = new Result();int typeLen = 1;if (!typeof(T).Equals(typeof(bool))){typeLen = Marshal.SizeOf<T>() / 2; // 每一个数据需要多少个寄存器}try{//1、建立连接,如果连接失败则抛出异常var connectState = this.Connect();if (!connectState.IsSuccessed){throw new Exception(connectState.Message);}//2、加入基本命令,0102表示写,0101表示读List<byte> baseBytes = this.GetBaseCommand(0x01, 0x02);//2.1加入存储区baseBytes.Add((byte)dataAddress.AreaType);//2.2加入地址,占3个字节baseBytes.Add((byte)(dataAddress.WordAddress / 256 % 256));baseBytes.Add((byte)(dataAddress.WordAddress % 256));baseBytes.Add(dataAddress.BitAddress);//2.3 加入长度,  short 是1字占1个长度 , float是2字占2个长度baseBytes.Add((byte)(values.Count * typeLen / 256 % 256));baseBytes.Add((byte)(values.Count * typeLen % 256));//处理数值foreach (dynamic item in values){if (typeof(T) == typeof(bool))//bool类型处理{baseBytes.Add((byte)(bool.Parse(item.ToString()) ? 0x01 : 0x00));}else // short,ushort,int32,float类型处理{List<byte> vBytes = new List<byte>(BitConverter.GetBytes(item));vBytes = this.SwitchBytes(vBytes);baseBytes.AddRange(vBytes);}}//3、发送命令this.Send(baseBytes);//4、检查报文,写入时不需要处理返回数据this.CheckResponseBytes();}catch (Exception ex){result.IsSuccessed = false;result.Message = ex.Message;}return result;}#endregion#region 基本方法/// <summary>/// 初始化内容/// </summary>/// <returns></returns>private Result SetupConnect(){byte[] connectBytes = new byte[] {// Header     FINS对应的Ascii编码(16进制)0x46,0x49,0x4E,0x53,// Length0x00,0x00,0x00,0x0C,// Command0x00,0x00,0x00,0x00,// Error code0x00,0x00,0x00,0x00,// Client node addr0x00,0x00,0x00,0x04};try{socket.Send(connectBytes);//发送握手报文byte[] respBytes = new byte[24];int count = socket.Receive(respBytes, 0, 24, SocketFlags.None);// 判断是否FINS开头的报文for (int i = 0; i < 4; i++){if (respBytes[i] != finsTcpHeader[i])throw new Exception("连接请求响应报文无效");}// 确定没有ErrorCodevar state = respBytes[12] | respBytes[13] | respBytes[14] | respBytes[15];if (state > 0)throw new Exception($"有错误[{state}]");}catch (Exception ex){return new Result() { IsSuccessed = false, Message = ex.Message };}return new Result();}/// <summary>/// 调整字节序/// </summary>/// <param name="value"></param>/// <param name="endianType"></param>/// <returns></returns>byte[] SwitchEndian(byte[] value, EndianType endianType){List<byte> result = new List<byte>(value);switch (endianType){case EndianType.AB:case EndianType.ABCD:case EndianType.ABCDEFGH:result.Reverse();return result.ToArray();case EndianType.CDAB: // 4字节处理if (value.Length == 4){result[3] = value[2];result[2] = value[3];result[1] = value[0];result[0] = value[1];}return result.ToArray();case EndianType.BADC: // 4字节处理if (value.Length == 4){result[3] = value[1];result[2] = value[0];result[1] = value[3];result[0] = value[2];}return result.ToArray();case EndianType.GHEFCDAB:  // 8字节处理if (value.Length == 8){result[7] = value[6];result[6] = value[7];result[5] = value[4];result[4] = value[5];result[3] = value[2];result[2] = value[3];result[1] = value[0];result[0] = value[1];}return result.ToArray();case EndianType.BADCFEHG: // 8字节处理if (value.Length == 8){result[7] = value[1];result[6] = value[0];result[5] = value[3];result[4] = value[2];result[3] = value[5];result[2] = value[4];result[1] = value[7];result[0] = value[6];}return result.ToArray();case EndianType.BA:case EndianType.DCBA:case EndianType.HGFEDCBA:return value;default:break;}return null;}/// <summary>/// 字节交换/// </summary>/// <param name="bytes"></param>/// <returns></returns>private List<byte> SwitchBytes(List<byte> bytes){byte temp;for (int i = 0; i < bytes.Count; i += 2){temp = bytes[i];bytes[i] = bytes[i + 1];bytes[i + 1] = temp;}return bytes;}/// <summary>/// 地址解析/// </summary>/// <param name="addr">地址</param>/// <returns></returns>/// <exception cref="Exception"></exception>private DataAddress AnalysisAddress(string addr){DataAddress dataAddress = new DataAddress();addr = addr.ToUpper();AreaType wordCode = 0x00, bitCode = 0x00;if (addr.Substring(0, 3) == "CIO"){wordCode = AreaType.CIOWORD;bitCode = AreaType.CIOBIT;addr = addr.Substring(3);}else if (addr.Substring(0, 2) == "DM"){wordCode = AreaType.DMWORD;bitCode = AreaType.DMBIT;addr = addr.Substring(2);}else{switch (addr[0]){case 'W':bitCode = AreaType.WBIT;wordCode = AreaType.WWORD;break;case 'A':bitCode = AreaType.ABIT;wordCode = AreaType.AWORD;break;case 'H':bitCode = AreaType.HBIT;wordCode = AreaType.HWORD;break;}addr = addr.Substring(1);}if (bitCode == 0x00){throw new Exception("地址类型暂不支持!");}string[] tempAddr = addr.Split('.');dataAddress.AreaType = wordCode;if (string.IsNullOrEmpty(tempAddr[0])){throw new Exception("地址标记错误");}ushort ws = 0;if (ushort.TryParse(tempAddr[0], out ws)){dataAddress.WordAddress = ws;}// 如果有小数点 if (tempAddr.Length > 1){dataAddress.AreaType = bitCode;if (string.IsNullOrEmpty(tempAddr[1])){throw new Exception("地址标记错误");}byte bs = 0;if (byte.TryParse(tempAddr[1], out bs)){dataAddress.BitAddress = bs;}}return dataAddress;}/// <summary>/// 创建基本命令/// </summary>/// <param name="mCommand">主命令</param>/// <param name="sCommand">次命令</param>/// <returns></returns>private List<byte> GetBaseCommand(byte mCommand, byte sCommand){return new List<byte> {// Command   读写的时候固定传这个值0x00,0x00,0x00,0x02,// Error code 错误代码;0x00,0x00,0x00,0x00,  // ICF0x80,// Rev 0x00,// GCT0x02,// DNA DA1 DA2,即目标网络号,目标节点号,目标单元号,也就是指PLC网络0x00,_da,0x00,// SNA SA1 SA2,即源网络号,源节点号,源单元号,也就是指PC网络0x00,_sa,0x00,// SID  ,固定值  0x00,//读写命令mCommand,sCommand};}/// <summary>/// 检查响应报文,返回数据内容/// </summary>/// <returns></returns>/// <exception cref="Exception"></exception>private List<byte> CheckResponseBytes(){byte[] respHeader = new byte[8];//头部数据,占8个字节socket.Receive(respHeader, 0, 8, SocketFlags.None); //接收fins头部//1、判断前面4个字节是否是FINSfor (int i = 0; i < 4; i++){if (respHeader[i] != finsTcpHeader[i]){throw new Exception("响应报文无效");}}//2、获取剩余报文长度 00 00 00 18byte[] lenBytes = new byte[4];lenBytes[0] = respHeader[7];lenBytes[1] = respHeader[6];lenBytes[2] = respHeader[5];lenBytes[3] = respHeader[4];int len = BitConverter.ToInt32(lenBytes, 0);//字节数组转换成int32//Console.WriteLine("剩余报文长度:" + len); //3、接收剩余字节byte[] dataBytes = new byte[len];socket.Receive(dataBytes, 0, len, SocketFlags.None);//4、错误信息解读 确定没有ErrorCode ResponseCodevar code = dataBytes[4] | dataBytes[5] | dataBytes[6] | dataBytes[7];if (code > 0){throw new Exception($"有错误[{code}]");}code = (dataBytes[20] << 8) | dataBytes[21];  /// 判断End Code 01 01   0000 0001 0000 0001   0x10  0x03 "1003"if (code > 0){throw new Exception($"有错误[{code}]");  // 字典处理:{257:"当前网络环境中无法匹配此节点"}}//5、获取数据部分List<byte> dataList = new List<byte>(dataBytes);dataList.RemoveRange(0, 22);//移除前面22个字节,剩下的就是数据部分//6、返回数据部分return dataList;}/// <summary>/// 发送TCP数据/// </summary>/// <param name="baseBytes">命令字节集合</param>private void Send(List<byte> baseBytes){//命令字节List<byte> commandBytes = new List<byte>();//  计算字节长度,占4个字节List<byte> lengthbyte = new List<byte>();lengthbyte.Add((byte)(baseBytes.Count / 256 / 256 / 256 % 256));lengthbyte.Add((byte)(baseBytes.Count / 256 / 256 % 256));lengthbyte.Add((byte)(baseBytes.Count / 256 % 256));lengthbyte.Add((byte)(baseBytes.Count % 256));commandBytes.AddRange(finsTcpHeader);//加入头部commandBytes.AddRange(lengthbyte.ToArray());//加入长度 commandBytes.AddRange(baseBytes);//加入协议内容 socket.Send(commandBytes.ToArray());//发送命令}#endregion}
}

5、测试通讯库-读取数据

这里还是以前面的存储区数据进行测试:

读取CIO区0.0开始的6个bool数据

读取D区100开始的4个数据,ushort类型

读取H区100开始的4个short类型的数据

读取W区100开始的5个float浮点字

读取W区104开始的2个float数据

1)连接PLC

namespace Omron.Communimcation.Test
{internal class Program{static void Main(string[] args){#region 通讯库 FinsTcpLibTest();Console.WriteLine("执行完成!"); #endregion  Console.ReadKey();}private static void FinsTcpLibTest(){FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 var result = finsTcp.Connect();// 开始连接PLCif (!result.IsSuccessed){Console.WriteLine(result.Message);return;}}}
}

 

 通讯的TCP报文

2)读取CIO区0.0开始的6个bool数据

设置PLC内存区数据

  private static void FinsTcpLibTest(){FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 var result = finsTcp.Connect();// 开始连接PLCif (!result.IsSuccessed){Console.WriteLine(result.Message);return;}//1,读取CIO区0.0开始的6个bool数据, var datas5 = finsTcp.Read<bool>("CIO0.0", 6);if (!datas5.IsSuccessed){Console.WriteLine(datas5.Message);return;}Console.WriteLine("读取CIO区0.0开始的6个bool数据");datas5.Datas.ForEach(dd => Console.WriteLine(dd)); }

通信报文结果 

3)读取D区100开始的4个数据,ushort类型

 private static void FinsTcpLibTest(){FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 var result = finsTcp.Connect();// 开始连接PLCif (!result.IsSuccessed){Console.WriteLine(result.Message);return;} 1,读取CIO区0.0开始的6个bool数据, // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);//if (!datas5.IsSuccessed)//{//    Console.WriteLine(datas5.Message);//    return;//}//Console.WriteLine("读取CIO区0.0开始的6个bool数据");//datas5.Datas.ForEach(dd => Console.WriteLine(dd));//2、读取D区100开始的4个数据,ushort类型, var datas1 = finsTcp.Read<ushort>("DM100", 4);if (!datas1.IsSuccessed){Console.WriteLine(datas1.Message);return;}Console.WriteLine("读取D区100开始的4个ushort类型数据");datas1.Datas.ForEach(dd => Console.WriteLine(dd));}

通讯报文结构

4)读取H区100开始的4个short类型的数据

  private static void FinsTcpLibTest(){FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 var result = finsTcp.Connect();// 开始连接PLCif (!result.IsSuccessed){Console.WriteLine(result.Message);return;} 1,读取CIO区0.0开始的6个bool数据, // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);//if (!datas5.IsSuccessed)//{//    Console.WriteLine(datas5.Message);//    return;//}//Console.WriteLine("读取CIO区0.0开始的6个bool数据");//datas5.Datas.ForEach(dd => Console.WriteLine(dd));2、读取D区100开始的4个数据,ushort类型, // var datas1 = finsTcp.Read<ushort>("DM100", 4);//if (!datas1.IsSuccessed)//{//    Console.WriteLine(datas1.Message);//    return;//}//Console.WriteLine("读取D区100开始的4个ushort类型数据");//datas1.Datas.ForEach(dd => Console.WriteLine(dd));//3、 读取H区100开始的4个short类型的数据 var datas2 = finsTcp.Read<short>("H100", 4);if (!datas2.IsSuccessed){Console.WriteLine(datas2.Message);return;}Console.WriteLine("读取H区100开始的4个short类型数据");datas2.Datas.ForEach(dd => Console.WriteLine(dd));}

可以看到,通讯库对负数的处理,报文返回的数据需要对short类型的负数进行处理,因为short是有符号的10进制数,包括正负整数,如908,-85

5)读取W区100开始的5个float浮点字

 private static void FinsTcpLibTest(){FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 var result = finsTcp.Connect();// 开始连接PLCif (!result.IsSuccessed){Console.WriteLine(result.Message);return;}1,读取CIO区0.0开始的6个bool数据, // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);//if (!datas5.IsSuccessed)//{//    Console.WriteLine(datas5.Message);//    return;//}//Console.WriteLine("读取CIO区0.0开始的6个bool数据");//datas5.Datas.ForEach(dd => Console.WriteLine(dd));2、读取D区100开始的4个数据,ushort类型, // var datas1 = finsTcp.Read<ushort>("DM100", 4);//if (!datas1.IsSuccessed)//{//    Console.WriteLine(datas1.Message);//    return;//}//Console.WriteLine("读取D区100开始的4个ushort类型数据");//datas1.Datas.ForEach(dd => Console.WriteLine(dd));3、 读取H区100开始的4个short类型的数据 // var datas2 = finsTcp.Read<short>("H100", 4);//if (!datas2.IsSuccessed)//{//    Console.WriteLine(datas2.Message);//    return;//}//Console.WriteLine("读取H区100开始的4个short类型数据");//datas2.Datas.ForEach(dd => Console.WriteLine(dd));//4、读取W区100开始的5个float浮点字,包括正负整数,如223,-987和正负小数,如2.34,-87.65var datas3 = finsTcp.Read<float>("W100", 5);if (!datas3.IsSuccessed){Console.WriteLine(datas3.Message);return;}Console.WriteLine("读取W区100开始的5个float类型数据");datas3.Datas.ForEach(dd => Console.WriteLine(dd));}

fins报文 

6)读取W区104开始的2个float数据

 private static void FinsTcpLibTest(){FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 var result = finsTcp.Connect();// 开始连接PLCif (!result.IsSuccessed){Console.WriteLine(result.Message);return;}1,读取CIO区0.0开始的6个bool数据, // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);//if (!datas5.IsSuccessed)//{//    Console.WriteLine(datas5.Message);//    return;//}//Console.WriteLine("读取CIO区0.0开始的6个bool数据");//datas5.Datas.ForEach(dd => Console.WriteLine(dd));2、读取D区100开始的4个数据,ushort类型, // var datas1 = finsTcp.Read<ushort>("DM100", 4);//if (!datas1.IsSuccessed)//{//    Console.WriteLine(datas1.Message);//    return;//}//Console.WriteLine("读取D区100开始的4个ushort类型数据");//datas1.Datas.ForEach(dd => Console.WriteLine(dd));3、 读取H区100开始的4个short类型的数据 // var datas2 = finsTcp.Read<short>("H100", 4);//if (!datas2.IsSuccessed)//{//    Console.WriteLine(datas2.Message);//    return;//}//Console.WriteLine("读取H区100开始的4个short类型数据");//datas2.Datas.ForEach(dd => Console.WriteLine(dd));4、读取W区100开始的5个float浮点字,包括正负整数,如223,-987和正负小数,如2.34,-87.65// var datas3 = finsTcp.Read<float>("W100", 5);//if (!datas3.IsSuccessed)//{//    Console.WriteLine(datas3.Message);//    return;//}//Console.WriteLine("读取W区100开始的5个float类型数据");//datas3.Datas.ForEach(dd => Console.WriteLine(dd));//4,读取W区104开始的2个float数据var datas4 = finsTcp.Read<float>("W104", 2);if (!datas4.IsSuccessed){Console.WriteLine(datas4.Message);return;}Console.WriteLine("读取W区104开始的2个float类型数据");datas4.Datas.ForEach(dd => Console.WriteLine(dd));}

 通讯报文

 可以对照前面讲的C#上位机与欧姆龙PLC的通信05---- HostLink协议(C-Mode版)

 C#上位机与欧姆龙PLC的通信06---- HostLink协议(FINS版)

串口报文和TCP报文,熟悉每个报文的组成部分,Hostlink通讯协议有两种模式:C-mode和FINS

C-mode报文在串口上通信的,FINS报文在网络上通信的。

6、测试通讯库-写入数据

这里测试4种数据的写入。

写入CIO区1.0开始的6个bool数据

写入D区30开始的4个数据,ushort类型

写入H区30开始的4个short类型的数据

写入W区30开始的5个float浮点字

 1)写入CIO区1.0开始的6个bool数据true, true, false, , false,true , true

写入成功,看PLC内存数据

 通讯报文 

2)写入D区30开始的4个数据,ushort类型

写入成功

3)写入H区30开始的4个short类型的数据

通讯报文

4)写入W区30开始的5个float浮点字

 

 

 可以对照前面讲的C#上位机与欧姆龙PLC的通信05---- HostLink协议(C-Mode版)

 C#上位机与欧姆龙PLC的通信06---- HostLink协议(FINS版)

串口报文和TCP报文,熟悉每个报文的组成部分,Hostlink通讯协议有两种模式:C-mode和FINS

C-mode报文在串口上通信的,FINS报文在网络上通信的。

7、小结

自己写的通讯库很强大,能读取写入单个或多个数据值,可以是多种数据类型,可以是多个存储区的操作,通讯库最后就是一个dll文件,项目中直接引用就可以啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/228781.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux常用命令大全总结及讲解(超详细版)

前言&#xff1a; Linux 是一个基于Linux 内核的开源类Unix 操作系统&#xff0c;Linus Torvalds于 1991 年 9 月 17 日首次发布的操作系统内核。Linux 通常打包为Linux 发行版。 Linux 最初是为基于Intel x86架构的个人计算机开发的&#xff0c;但此后被移植到的平台比任何其…

前端实现websocket类封装

随着Web应用程序的发展&#xff0c;越来越多的人开始利用Websocket技术来构建实时应用程序。Websocket是一种在客户端和服务器之间建立持久连接的协议。这种协议可以在一个单独的连接上实现双向通信。与HTTP请求-响应模型不同&#xff0c;Websocket允许服务器自主地向客户端发送…

OpenCV-Python(21):OpenCV中的轮廓性质

3.轮廓的性质 本文我们将主要学习基于轮廓来提取一些经常使用的对象特征。 3.1 长宽比 边界矩形的宽高比&#xff1a; x,y,w,h cv2.boundingRect(cnt) aspect_ratio float(w)/h 3.2 Extent 轮廓面积与边界矩形面积的比。 area cv2.contourArea(cnt) x,y,w,h cv2.bounding…

基于JavaWeb实验室预约管理系统(源码+数据库+文档)

一、项目简介 本项目是一套基于JavaWeb实验室预约管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;e…

simulink代码生成(五)——ePWM模块初级应用

前面分别讲到了SCI及ADC的配置及使用&#xff0c;现在梳理一下ePWM的配置和使用&#xff1b; 先打一些基础的DSP28335的基础知识&#xff1b; F28335 关于ePWM中断与SOC采样信号的一些思考_socasel-CSDN博客 F28335 ePWM模块简介——TMS320F28335学习笔记&#xff08;四&…

MySQL数据库索引优化

一、引言 1. 索引的重要性 MySQL数据库索引的重要性主要体现在&#xff0c;一是查询速度优化&#xff0c;索引可以极大地提高查询速度。对于没有索引的表&#xff0c;MySQL必须进行全部扫描来找到所需的行&#xff0c;如果表中数据量很大&#xff0c;那么通常很慢。通过适当的…

iPhone 13 Pro 更换『移植电芯』和『超容电池』体验

文章目录 考虑换电池Ⅰ 方案一Ⅱ 方案二 总结危险 Note系列地址 简 述: 首发买的iPhone 13P &#xff08;2021.09&#xff09;&#xff0c;随性使用一年出头&#xff0c;容量就暴跌 85%&#xff0c;对比朋友一起买的同款&#xff0c;还是95%。这已经基本得一天两充 >_<&a…

数据库进阶教学——读写分离(Mycat1.6+Ubuntu22.04主+Win10从)

目录 1、概述 2、环境准备 3、读写分离实验 3.1、安装jdk 3.2、安装Mycat 3.3、配置Mycat 3.3.1、配置schema.xml ​​​​3.3.2、配置server.xml 3.4、修改主从机远程登陆权限 3.4.1、主机 3.4.2、从机 3.5、启动Mycat 3.6、登录Mycat 3.7、验证 1、概述 读写分…

模型量化之AWQ和GPTQ

什么是模型量化 模型量化&#xff08;Model Quantization&#xff09;是一种通过减少模型参数表示的位数来降低模型计算和存储开销的技术。一般来说&#xff0c;模型参数在深度学习模型中以浮点数&#xff08;例如32位浮点数&#xff09;的形式存储&#xff0c;而模型量化可以…

OpenCV-Python(21):轮廓特征及周长、面积凸包检测和形状近似

2. 轮廓特征 轮廓特征是指由轮廓形状和结构衍生出来的一些特征参数。这些特征参数可以用于图像识别、目标检测和形状分析等应用中。常见的轮廓特征包括&#xff1a; 面积&#xff1a;轮廓所包围的区域的面积。周长&#xff1a;轮廓的周长&#xff0c;即轮廓线的长度。弧长&…

Docker自建文件快递柜系统

Docker自建文件快递柜系统。 软件特色&#xff1a; 轻量简洁&#xff1a;FastapiSqlite3Vue2ElementUI 轻松上传&#xff1a;复制粘贴&#xff0c;拖拽选择 多种类型&#xff1a;文本&#xff0c;文件 防止爆破&#xff1a;错误次数限制 防止滥用&#xff1a;IP限制上传次数…

GO语言基础笔记(七):网络编程

目录 Go语言网络协议基础 协议 实现 跨平台网络抽象 简单代码展示 服务端 客户端 服务端客户端通信实战 Go Linux服务端 Go Linux客户端 Windows C 客户端 总结 Go语言网络协议基础 在 Go 语言中&#xff0c;net/http 包提供了强大的工具来创建 HTTP 服务器。…

新产品推广选品牌外包广州迅腾文化传播多渠道传播能力

在当今激烈的市场竞争中&#xff0c;新产品推广已成为企业发展的关键。选择具备多渠道传播能力的品牌外包服务提供商&#xff0c;有助于快速提升品牌知名度和市场占有率。作为行业领先者&#xff0c;迅腾文化凭借卓越的多渠道传播能力&#xff0c;成为企业新产品推广的理想合作…

我的512天创作者纪念日总结:高效、高现

文章目录 512天创作者纪念日&#xff1a;2023年的12月31日CSDN的512天消息提醒第一篇文章&#xff0c;最后一篇文章总计847篇文章&#xff0c;每月发文分布512天&#xff0c;各专栏文章统计512天&#xff0c;互动总成绩 512天创作者纪念日&#xff1a;2023年的12月31日 2023年…

微服务(1)

目录 1.什么是微服务&#xff1f;谈谈你对微服务的理解&#xff1f; 2.什么是Spring Cloud&#xff1f; 3.Springcloud中的组件有哪些&#xff1f; 3.具体说说SpringCloud主要项目&#xff1f; 5.SpringCloud项目部署架构&#xff1f; 1.什么是微服务&#xff1f;谈谈你对微…

前端开发新趋势:Web3、区块链与虚拟现实

文章目录 Web3&#xff1a;下一代互联网区块链技术去中心化应用程序&#xff08;DApps&#xff09; 区块链&#xff1a;重塑数字世界数字钱包NFT&#xff08;非同质化代币&#xff09; 虚拟现实&#xff1a;沉浸式体验WebVR和WebXR三维图形 新挑战与机会性能与复杂性安全性创新…

【网络面试(2)】DNS原理-域名和IP地址的查询转换

从上一篇博客我们得知浏览器是如何生成了HTTP消息了&#xff0c;但是浏览器作为应用程序&#xff0c;是不具备向网络中发送请求的能力&#xff0c;而是需要委托给操作系统的内核协议栈来发送请求。在委托协议栈之前&#xff0c;浏览器还要做的一件事情就是将域名转换为IP地址。…

lambda表达式和包装器

正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 我们在使用库里的排序算法时如果排序的是自定义类型或者库里默认的排序不能满足我们则需求&…

Apache DolphinScheduler 3.1.9 版本发布:提升系统的稳定性和性能

&#x1f680;我们很高兴宣布&#xff0c;Apache DolphinScheduler 的最新版本 3.1.9 已正式发布&#xff01;此版本在 3.1.8 的基础上进行了关键的 bug 修复和文档更新&#xff0c;共计修复了 14 个 bug 和改进了 3 个文档。 主要更新亮点 本次更新重点解决了以下几个关键问题…

磁盘阵列raid

一、服务器硬件 cpu 、 主板 、内存、硬盘、网卡、电源、raid卡、风扇、远程管理卡 二、硬盘尺寸 目前生产环境中主流的两种类型硬盘 3.5寸 和 2.5寸 硬盘 2.5寸硬盘可以通过使用硬盘托架后适用于3.5寸硬盘的服务器&#xff0c;但是3.5寸没法转换成2.5寸 三、服务器常见故…