using System; using System.Collections.Generic; using System.Linq; using System.Text; using HslCommunication.Core.IMessage; using HslCommunication.Core.Net; using HslCommunication.Core; using System.Net.Sockets; using HslCommunication.BasicFramework; namespace HslCommunication.Profinet.Omron { /// /// 欧姆龙PLC通讯类,采用Fins-Tcp通信协议实现 /// /// /// 实例化之后,使用之前,需要初始化三个参数信息,具体见三个参数的说明: ///
/// 如果在测试的时候报错误码64,经网友 上海-Lex 指点,是因为PLC中产生了报警,如伺服报警,模块错误等产生的,但是数据还是能正常读到的,屏蔽64报警或清除plc错误可解决 /// 地址支持的列表如下: /// /// /// 地址名称 /// 地址代号 /// 示例 /// 地址进制 /// 字操作 /// 位操作 /// 备注 /// /// /// DM Area /// D /// D100,D200 /// 10 /// /// /// /// /// /// CIO Area /// C /// C100,C200 /// 10 /// /// /// /// /// /// Work Area /// W /// W100,W200 /// 10 /// /// /// /// /// /// Holding Bit Area /// H /// H100,H200 /// 10 /// /// /// /// /// /// Auxiliary Bit Area /// A /// A100,A200 /// 10 /// /// /// /// /// ///
/// /// /// /// public class OmronFinsNet : NetworkDeviceBase { #region Constructor /// /// 实例化一个欧姆龙PLC Fins帧协议的通讯对象 /// public OmronFinsNet( ) { WordLength = 1; ByteTransform.DataFormat = DataFormat.CDAB; } /// /// 实例化一个欧姆龙PLC Fins帧协议的通讯对象 /// /// PLCd的Ip地址 /// PLC的端口 public OmronFinsNet( string ipAddress, int port ) { WordLength = 1; IpAddress = ipAddress; Port = port; ByteTransform.DataFormat = DataFormat.CDAB; } #endregion #region IpAddress Override /// /// 设备的Ip地址信息 /// public override string IpAddress { get => base.IpAddress; set { DA1 = Convert.ToByte( value.Substring( value.LastIndexOf( "." ) + 1 ) ); base.IpAddress = value; } } #endregion #region Public Member /// /// 信息控制字段,默认0x80 /// public byte ICF { get; set; } = 0x80; /// /// 系统使用的内部信息 /// public byte RSV { get; private set; } = 0x00; /// /// 网络层信息,默认0x02,如果有八层消息,就设置为0x07 /// public byte GCT { get; set; } = 0x02; /// /// PLC的网络号地址,默认0x00 /// public byte DNA { get; set; } = 0x00; /// /// PLC的节点地址,这个值在配置了ip地址之后是默认赋值的,默认为Ip地址的最后一位 /// /// /// 假如你的PLC的Ip地址为192.168.0.10,那么这个值就是10 /// public byte DA1 { get; set; } = 0x13; /// /// PLC的单元号地址 /// /// /// 通常都为0 /// public byte DA2 { get; set; } = 0x00; /// /// 上位机的网络号地址 /// public byte SNA { get; set; } = 0x00; private byte computerSA1 = 0x0B; /// /// 上位机的节点地址,假如你的电脑的Ip地址为192.168.0.13,那么这个值就是13 /// /// /// 假如你的电脑的Ip地址为192.168.0.13,那么这个值就是13 /// public byte SA1 { get { return computerSA1; } set { computerSA1 = value; handSingle[19] = value; } } /// /// 上位机的单元号地址 /// public byte SA2 { get; set; } /// /// 设备的标识号 /// public byte SID { get; set; } = 0x00; /// /// 如果设置为True,当数据读取失败的时候,会自动变更当前的SA1值,会选择自动增加,但不会和DA1一致 /// public bool IsChangeSA1AfterReadFailed { get; set; } #endregion #region Build Command /// /// 将普通的指令打包成完整的指令 /// /// /// private byte[] PackCommand(byte[] cmd) { byte[] buffer = new byte[26 + cmd.Length]; Array.Copy( handSingle, 0, buffer, 0, 4 ); byte[] tmp = BitConverter.GetBytes( buffer.Length - 8 ); Array.Reverse( tmp ); tmp.CopyTo( buffer, 4 ); buffer[11] = 0x02; buffer[16] = ICF; buffer[17] = RSV; buffer[18] = GCT; buffer[19] = DNA; buffer[20] = DA1; buffer[21] = DA2; buffer[22] = SNA; buffer[23] = SA1; buffer[24] = SA2; buffer[25] = SID; cmd.CopyTo( buffer, 26 ); return buffer; } /// /// 根据类型地址长度确认需要读取的指令头 /// /// 起始地址 /// 长度 /// 是否是位读取 /// 带有成功标志的报文数据 public OperateResult BuildReadCommand( string address, ushort length ,bool isBit) { var command = OmronFinsNetHelper.BuildReadCommand( address, length, isBit ); if (!command.IsSuccess) return command; return OperateResult.CreateSuccessResult( PackCommand( command.Content ) ); } /// /// 根据类型地址以及需要写入的数据来生成指令头 /// /// 起始地址 /// 真实的数据值信息 /// 是否是位操作 /// 带有成功标志的报文数据 public OperateResult BuildWriteCommand( string address, byte[] value, bool isBit ) { var command = OmronFinsNetHelper.BuildWriteWordCommand( address, value, isBit ); if (!command.IsSuccess) return command; return OperateResult.CreateSuccessResult( PackCommand( command.Content ) ); } #endregion #region Double Mode Override /// /// 在连接上欧姆龙PLC后,需要进行一步握手协议 /// /// 连接的套接字 /// 初始化成功与否 protected override OperateResult InitializationOnConnect( Socket socket ) { // 握手信号 OperateResult read = ReadFromCoreServer( socket, handSingle ); if (!read.IsSuccess) return read; // 检查返回的状态 byte[] buffer = new byte[4]; buffer[0] = read.Content[15]; buffer[1] = read.Content[14]; buffer[2] = read.Content[13]; buffer[3] = read.Content[12]; int status = BitConverter.ToInt32( buffer, 0 ); if(status != 0) return new OperateResult( status, OmronFinsNetHelper.GetStatusDescription( status ) ); // 提取PLC的节点地址 if (read.Content.Length >= 24) DA1 = read.Content[23]; return OperateResult.CreateSuccessResult( ) ; } /// /// 和服务器交互完成的时候调用的方法,无论是成功或是失败,都将会调用,具体的操作需要重写实现 /// /// 读取结果 protected override void ExtraAfterReadFromCoreServer( OperateResult read ) { base.ExtraAfterReadFromCoreServer( read ); if (!read.IsSuccess) { if (IsChangeSA1AfterReadFailed) { // when read failed, changed the SA1 value SA1++; if (SA1 == 0) SA1++; if (SA1 == DA1) SA1++; } } } #endregion #region Read Support /// /// 从欧姆龙PLC中读取想要的数据,返回读取结果,读取单位为字 /// /// 读取地址,格式为"D100","C100","W100","H100","A100" /// 读取的数据长度 /// 带成功标志的结果数据对象 /// /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下: /// /// 以下是读取不同类型数据的示例 /// /// public override OperateResult Read( string address, ushort length ) { // 获取指令 var command = BuildReadCommand( address, length, false ); if (!command.IsSuccess) return OperateResult.CreateFailedResult( command ); // 核心数据交互 OperateResult read = ReadFromCoreServer( command.Content ); if(!read.IsSuccess) return OperateResult.CreateFailedResult( read ); // 数据有效性分析 OperateResult valid = OmronFinsNetHelper.ResponseValidAnalysis( read.Content, true ); if(!valid.IsSuccess) return OperateResult.CreateFailedResult( valid ); // 读取到了正确的数据 return OperateResult.CreateSuccessResult( valid.Content ); } /// /// 从欧姆龙PLC中批量读取位软元件,返回读取结果 /// /// 读取地址,格式为"D100","C100","W100","H100","A100" /// 读取的长度 /// 带成功标志的结果数据对象 /// /// /// public OperateResult ReadBool( string address, ushort length ) { // 获取指令 var command = BuildReadCommand( address, length, true ); if (!command.IsSuccess) return OperateResult.CreateFailedResult( command ); // 核心数据交互 OperateResult read = ReadFromCoreServer( command.Content ); if (!read.IsSuccess) return OperateResult.CreateFailedResult( read ); // 数据有效性分析 OperateResult valid = OmronFinsNetHelper.ResponseValidAnalysis( read.Content, true ); if (!valid.IsSuccess) return OperateResult.CreateFailedResult( valid ); // 返回正确的数据信息 return OperateResult.CreateSuccessResult( valid.Content.Select( m => m != 0x00 ? true : false ).ToArray( ) ); } /// /// 从欧姆龙PLC中批量读取位软元件,返回读取结果 /// /// 读取地址,格式为"D100.0","C100.15","W100.7","H100.4","A100.9" /// 带成功标志的结果数据对象 /// /// 地址的格式请参照方法 /// /// /// /// public OperateResult ReadBool( string address ) { OperateResult read = ReadBool( address, 1 ); if (!read.IsSuccess) return OperateResult.CreateFailedResult( read ); return OperateResult.CreateSuccessResult( read.Content[0] ); } #endregion #region Write Base /// /// 向PLC写入数据,数据格式为原始的字节类型 /// /// 初始地址 /// 原始的字节数据 /// 结果 /// /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下: /// /// 以下是写入不同类型数据的示例 /// /// public override OperateResult Write( string address, byte[] value ) { // 获取指令 var command = BuildWriteCommand( address, value, false ); if (!command.IsSuccess) return command; // 核心数据交互 OperateResult read = ReadFromCoreServer( command.Content ); if (!read.IsSuccess) return read; // 数据有效性分析 OperateResult valid = OmronFinsNetHelper.ResponseValidAnalysis( read.Content, false ); if (!valid.IsSuccess) return valid; // 成功 return OperateResult.CreateSuccessResult( ) ; } #endregion #region Write bool[] /// /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入D100,values[0]对应D100.0 /// /// 要写入的数据地址 /// 要写入的实际数据,长度为8的倍数 /// 返回写入结果 /// /// /// public OperateResult Write( string address, bool value ) { return Write( address, new bool[] { value } ); } /// /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入D100,values[0]对应D100.0 /// /// 要写入的数据地址 /// 要写入的实际数据,可以指定任意的长度 /// 返回写入结果 /// /// /// public OperateResult Write( string address, bool[] values ) { // 获取指令 var command = BuildWriteCommand( address, values.Select( m => m ? (byte)0x01 : (byte)0x00 ).ToArray( ), true ); if (!command.IsSuccess) return command; // 核心数据交互 OperateResult read = ReadFromCoreServer( command.Content ); if (!read.IsSuccess) return read; // 数据有效性分析 OperateResult valid = OmronFinsNetHelper.ResponseValidAnalysis( read.Content, false ); if (!valid.IsSuccess) return valid; // 写入成功 return OperateResult.CreateSuccessResult( ); } #endregion #region Hand Single // 握手信号 // 46494E530000000C0000000000000000000000D6 private readonly byte[] handSingle = new byte[] { 0x46, 0x49, 0x4E, 0x53, // FINS 0x00, 0x00, 0x00, 0x0C, // 后面的命令长度 0x00, 0x00, 0x00, 0x00, // 命令码 0x00, 0x00, 0x00, 0x00, // 错误码 0x00, 0x00, 0x00, 0x01 // 节点号 }; #endregion #region Object Override /// /// 返回表示当前对象的字符串 /// /// 字符串 public override string ToString( ) { return $"OmronFinsNet[{IpAddress}:{Port}]"; } #endregion } }