using System; using System.Collections.Generic; using System.Linq; using System.Text; using MECF.Framework.RT.Core.IoProviders.Common.IMessage; using System.Net.Sockets; using MECF.Framework.Common.PLC; using MECF.Framework.RT.Core.IoProviders.Common; using MECF.Framework.RT.Core.IoProviders.Common.Net.NetworkBase; using MECF.Framework.RT.Core.IoProviders.Common.Transfer; namespace MECF.Framework.RT.Core.IoProviders.Omron { /// /// 欧姆龙PLC通讯类,采用Fins-Tcp通信协议实现 /// /// /// 实例化之后,使用之前,需要初始化三个参数信息,具体见三个参数的说明: ///
/// 如果在测试的时候报错误码64,是因为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, string _localIp) { WordLength = 1; IpAddress = ipAddress; Port = port; SA1 = Convert.ToByte(_localIp.Substring(_localIp.LastIndexOf(".") + 1)); 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; #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(); } #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 } }