using HslCommunication.Core; using HslCommunication.Serial; using System; using System.Collections.Generic; using System.Linq; using System.Text; using HslCommunication.BasicFramework; namespace HslCommunication.Profinet.Melsec { /// /// 三菱的串口通信的对象,适用于读取FX系列的串口数据,支持的类型参考文档说明 /// /// /// 字读写地址支持的列表如下: /// /// /// 地址名称 /// 地址代号 /// 示例 /// 地址范围 /// 地址进制 /// 备注 /// /// /// 数据寄存器 /// D /// D100,D200 /// D0-D511,D8000-D8255 /// 10 /// /// /// /// 定时器的值 /// TN /// TN10,TN20 /// TN0-TN255 /// 10 /// /// /// /// 计数器的值 /// CN /// CN10,CN20 /// CN0-CN199,CN200-CN255 /// 10 /// /// /// /// 位地址支持的列表如下: /// /// /// 地址名称 /// 地址代号 /// 示例 /// 地址范围 /// 地址进制 /// 备注 /// /// /// 内部继电器 /// M /// M100,M200 /// M0-M1023,M8000-M8255 /// 10 /// /// /// /// 输入继电器 /// X /// X1,X20 /// X0-X177 /// 8 /// /// /// /// 输出继电器 /// Y /// Y10,Y20 /// Y0-Y177 /// 8 /// /// /// /// 步进继电器 /// S /// S100,S200 /// S0-S999 /// 10 /// /// /// /// 定时器触点 /// TS /// TS10,TS20 /// TS0-TS255 /// 10 /// /// /// /// 定时器线圈 /// TC /// TC10,TC20 /// TC0-TC255 /// 10 /// /// /// /// 计数器触点 /// CS /// CS10,CS20 /// CS0-CS255 /// 10 /// /// /// /// 计数器线圈 /// CC /// CC10,CC20 /// CC0-CC255 /// 10 /// /// /// /// /// /// /// public class MelsecFxSerial : SerialDeviceBase { #region Constructor /// /// 实例化三菱的串口协议的通讯对象 /// public MelsecFxSerial( ) { WordLength = 1; } #endregion #region Check Response private OperateResult CheckPlcReadResponse( byte[] ack ) { if (ack.Length == 0) return new OperateResult( StringResources.Language.MelsecFxReceiveZore ); if (ack[0] == 0x15) return new OperateResult( StringResources.Language.MelsecFxAckNagative + " Actual: " + SoftBasic.ByteToHexString( ack, ' ' ) ); if (ack[0] != 0x02) return new OperateResult( StringResources.Language.MelsecFxAckWrong + ack[0] + " Actual: " + SoftBasic.ByteToHexString( ack, ' ' ) ); if (!MelsecHelper.CheckCRC( ack )) return new OperateResult( StringResources.Language.MelsecFxCrcCheckFailed ); return OperateResult.CreateSuccessResult( ); } private OperateResult CheckPlcWriteResponse( byte[] ack ) { if (ack.Length == 0) return new OperateResult( StringResources.Language.MelsecFxReceiveZore ); if (ack[0] == 0x15) return new OperateResult( StringResources.Language.MelsecFxAckNagative + " Actual: " + SoftBasic.ByteToHexString( ack, ' ' ) ); if (ack[0] != 0x06) return new OperateResult( StringResources.Language.MelsecFxAckWrong + ack[0] + " Actual: " + SoftBasic.ByteToHexString( ack, ' ' ) ); return OperateResult.CreateSuccessResult( ); } #endregion #region Read Support /// /// 从三菱PLC中读取想要的数据,返回读取结果 /// /// 读取地址,,支持的类型参考文档说明 /// 读取的数据长度 /// 带成功标志的结果数据对象 /// /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下: /// /// 以下是读取不同类型数据的示例 /// /// public override OperateResult Read( string address, ushort length ) { // 获取指令 OperateResult command = BuildReadWordCommand( address, length ); if (!command.IsSuccess) return OperateResult.CreateFailedResult( command ); // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return OperateResult.CreateFailedResult( read ); // 反馈检查 OperateResult ackResult = CheckPlcReadResponse( read.Content ); if (!ackResult.IsSuccess) return OperateResult.CreateFailedResult( ackResult ); // 数据提炼 return ExtractActualData( read.Content ); } /// /// 从三菱PLC中批量读取位软元件,返回读取结果,该读取地址最好从0,16,32...等开始读取,这样可以读取比较长得数据数组 /// /// 起始地址 /// 读取的长度 /// 带成功标志的结果数据对象 /// /// /// public OperateResult ReadBool( string address, ushort length ) { //获取指令 OperateResult command = BuildReadBoolCommand( address, length ); if (!command.IsSuccess) return OperateResult.CreateFailedResult( command ); // 核心交互 OperateResult read = ReadBase( command.Content1 ); if (!read.IsSuccess) return OperateResult.CreateFailedResult( read ); // 反馈检查 OperateResult ackResult = CheckPlcReadResponse( read.Content ); if (!ackResult.IsSuccess) return OperateResult.CreateFailedResult( ackResult ); // 提取真实的数据 return ExtractActualBoolData( read.Content, command.Content2, length ); } /// /// 从三菱PLC中批量读取位软元件,返回读取结果 /// /// 起始地址 /// 带成功标志的结果数据对象 /// 参照 方法 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 Override /// /// 向PLC写入数据,数据格式为原始的字节类型 /// /// 初始地址,支持的类型参考文档说明 /// 原始的字节数据 /// /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,写入如下: /// /// 以下是读取不同类型数据的示例 /// /// /// 是否写入成功的结果对象 public override OperateResult Write( string address, byte[] value ) { // 获取写入 OperateResult command = BuildWriteWordCommand( address, value ); if (!command.IsSuccess) return command; // 核心交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 结果验证 OperateResult checkResult = CheckPlcWriteResponse( read.Content ); if (!checkResult.IsSuccess) return checkResult; return OperateResult.CreateSuccessResult( ); } #endregion #region Write Bool /// /// 强制写入位数据的通断,支持的类型参考文档说明 /// /// 地址信息 /// 是否为通 /// 是否写入成功的结果对象 public OperateResult Write( string address, bool value ) { // 先获取指令 OperateResult command = BuildWriteBoolPacket( address, value ); if (!command.IsSuccess) return command; // 和串口进行核心的数据交互 OperateResult read = ReadBase( command.Content ); if (!read.IsSuccess) return read; // 检查结果是否正确 OperateResult checkResult = CheckPlcWriteResponse( read.Content ); if (!checkResult.IsSuccess) return checkResult; return OperateResult.CreateSuccessResult( ); } #endregion #region Object Override /// /// 获取当前对象的字符串标识形式 /// /// 字符串信息 public override string ToString( ) { return "MelsecFxSerial"; } #endregion #region Static Method Helper /// /// 生成位写入的数据报文信息,该报文可直接用于发送串口给PLC /// /// 地址信息,每个地址存在一定的范围,需要谨慎传入数据。举例:M10,S10,X5,Y10,C10,T10 /// True或是False /// 带报文信息的结果对象 public static OperateResult BuildWriteBoolPacket( string address, bool value ) { var analysis = FxAnalysisAddress( address ); if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis ); // 二次运算起始地址偏移量,根据类型的不同,地址的计算方式不同 ushort startAddress = analysis.Content2; if (analysis.Content1 == MelsecMcDataType.M) { if (startAddress >= 8000) { startAddress = (ushort)(startAddress - 8000 + 0x0F00); } else { startAddress = (ushort)(startAddress + 0x0800); } } else if (analysis.Content1 == MelsecMcDataType.S) { startAddress = (ushort)(startAddress + 0x0000); } else if (analysis.Content1 == MelsecMcDataType.X) { startAddress = (ushort)(startAddress + 0x0400); } else if (analysis.Content1 == MelsecMcDataType.Y) { startAddress = (ushort)(startAddress + 0x0500); } else if (analysis.Content1 == MelsecMcDataType.CS) { startAddress += (ushort)(startAddress + 0x01C0); } else if (analysis.Content1 == MelsecMcDataType.CC) { startAddress += (ushort)(startAddress + 0x03C0); } else if (analysis.Content1 == MelsecMcDataType.CN) { startAddress += (ushort)(startAddress + 0x0E00); } else if (analysis.Content1 == MelsecMcDataType.TS) { startAddress += (ushort)(startAddress + 0x00C0); } else if (analysis.Content1 == MelsecMcDataType.TC) { startAddress += (ushort)(startAddress + 0x02C0); } else if (analysis.Content1 == MelsecMcDataType.TN) { startAddress += (ushort)(startAddress + 0x0600); } else { return new OperateResult( StringResources.Language.MelsecCurrentTypeNotSupportedBitOperate ); } byte[] _PLCCommand = new byte[9]; _PLCCommand[0] = 0x02; // STX _PLCCommand[1] = value ? (byte)0x37 : (byte)0x38; // Read _PLCCommand[2] = SoftBasic.BuildAsciiBytesFrom( startAddress )[2]; // 偏移地址 _PLCCommand[3] = SoftBasic.BuildAsciiBytesFrom( startAddress )[3]; _PLCCommand[4] = SoftBasic.BuildAsciiBytesFrom( startAddress )[0]; _PLCCommand[5] = SoftBasic.BuildAsciiBytesFrom( startAddress )[1]; _PLCCommand[6] = 0x03; // ETX MelsecHelper.FxCalculateCRC( _PLCCommand ).CopyTo( _PLCCommand, 7 ); // CRC return OperateResult.CreateSuccessResult( _PLCCommand ); } /// /// 根据类型地址长度确认需要读取的指令头 /// /// 起始地址 /// 长度 /// 带有成功标志的指令数据 public static OperateResult BuildReadWordCommand( string address, ushort length ) { var addressResult = FxCalculateWordStartAddress( address ); if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult( addressResult ); length = (ushort)(length * 2); ushort startAddress = addressResult.Content; byte[] _PLCCommand = new byte[11]; _PLCCommand[0] = 0x02; // STX _PLCCommand[1] = 0x30; // Read _PLCCommand[2] = SoftBasic.BuildAsciiBytesFrom( startAddress )[0]; // 偏移地址 _PLCCommand[3] = SoftBasic.BuildAsciiBytesFrom( startAddress )[1]; _PLCCommand[4] = SoftBasic.BuildAsciiBytesFrom( startAddress )[2]; _PLCCommand[5] = SoftBasic.BuildAsciiBytesFrom( startAddress )[3]; _PLCCommand[6] = SoftBasic.BuildAsciiBytesFrom( (byte)length )[0]; // 读取长度 _PLCCommand[7] = SoftBasic.BuildAsciiBytesFrom( (byte)length )[1]; _PLCCommand[8] = 0x03; // ETX MelsecHelper.FxCalculateCRC( _PLCCommand ).CopyTo( _PLCCommand, 9 ); // CRC return OperateResult.CreateSuccessResult( _PLCCommand ); // Return } /// /// 根据类型地址长度确认需要读取的指令头 /// /// 起始地址 /// bool数组长度 /// 带有成功标志的指令数据 public static OperateResult BuildReadBoolCommand( string address, ushort length ) { var addressResult = FxCalculateBoolStartAddress( address ); if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult( addressResult ); // 计算下实际需要读取的数据长度 ushort length2 = (ushort)((addressResult.Content2 + length - 1) / 8 - (addressResult.Content2 / 8) + 1); ushort startAddress = addressResult.Content1; byte[] _PLCCommand = new byte[11]; _PLCCommand[0] = 0x02; // STX _PLCCommand[1] = 0x30; // Read _PLCCommand[2] = SoftBasic.BuildAsciiBytesFrom( startAddress )[0]; // 偏移地址 _PLCCommand[3] = SoftBasic.BuildAsciiBytesFrom( startAddress )[1]; _PLCCommand[4] = SoftBasic.BuildAsciiBytesFrom( startAddress )[2]; _PLCCommand[5] = SoftBasic.BuildAsciiBytesFrom( startAddress )[3]; _PLCCommand[6] = SoftBasic.BuildAsciiBytesFrom( (byte)length2 )[0]; // 读取长度 _PLCCommand[7] = SoftBasic.BuildAsciiBytesFrom( (byte)length2 )[1]; _PLCCommand[8] = 0x03; // ETX MelsecHelper.FxCalculateCRC( _PLCCommand ).CopyTo( _PLCCommand, 9 ); // CRC return OperateResult.CreateSuccessResult( _PLCCommand, (int)addressResult.Content3 ); } /// /// 根据类型地址以及需要写入的数据来生成指令头 /// /// 起始地址 /// 实际的数据信息 /// 带有成功标志的指令数据 public static OperateResult BuildWriteWordCommand( string address, byte[] value ) { var addressResult = FxCalculateWordStartAddress( address ); if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult( addressResult ); // 字节数据转换成ASCII格式 if (value != null) value = SoftBasic.BuildAsciiBytesFrom( value ); ushort startAddress = addressResult.Content; byte[] _PLCCommand = new byte[11 + value.Length]; _PLCCommand[0] = 0x02; // STX _PLCCommand[1] = 0x31; // Read _PLCCommand[2] = SoftBasic.BuildAsciiBytesFrom( startAddress )[0]; // Offect Address _PLCCommand[3] = SoftBasic.BuildAsciiBytesFrom( startAddress )[1]; _PLCCommand[4] = SoftBasic.BuildAsciiBytesFrom( startAddress )[2]; _PLCCommand[5] = SoftBasic.BuildAsciiBytesFrom( startAddress )[3]; _PLCCommand[6] = SoftBasic.BuildAsciiBytesFrom( (byte)(value.Length / 2) )[0]; // Read Length _PLCCommand[7] = SoftBasic.BuildAsciiBytesFrom( (byte)(value.Length / 2) )[1]; Array.Copy( value, 0, _PLCCommand, 8, value.Length ); _PLCCommand[_PLCCommand.Length - 3] = 0x03; // ETX MelsecHelper.FxCalculateCRC( _PLCCommand ).CopyTo( _PLCCommand, _PLCCommand.Length - 2 ); // CRC return OperateResult.CreateSuccessResult( _PLCCommand ); } /// /// 从PLC反馈的数据进行提炼操作 /// /// PLC反馈的真实数据 /// 数据提炼后的真实数据 public static OperateResult ExtractActualData( byte[] response ) { try { byte[] data = new byte[(response.Length - 4) / 2]; for (int i = 0; i < data.Length; i++) { byte[] buffer = new byte[2]; buffer[0] = response[i * 2 + 1]; buffer[1] = response[i * 2 + 2]; data[i] = Convert.ToByte( Encoding.ASCII.GetString( buffer ), 16 ); } return OperateResult.CreateSuccessResult( data ); } catch (Exception ex) { return new OperateResult( ) { Message = "Extract Msg:" + ex.Message + Environment.NewLine + "Data: " + BasicFramework.SoftBasic.ByteToHexString( response ) }; } } /// /// 从PLC反馈的数据进行提炼bool数组操作 /// /// PLC反馈的真实数据 /// 起始提取的点信息 /// bool数组的长度 /// 数据提炼后的真实数据 public static OperateResult ExtractActualBoolData( byte[] response, int start, int length ) { OperateResult extraResult = ExtractActualData( response ); if (!extraResult.IsSuccess) return OperateResult.CreateFailedResult( extraResult ); // 转化bool数组 try { bool[] data = new bool[length]; bool[] array = BasicFramework.SoftBasic.ByteToBoolArray( extraResult.Content, extraResult.Content.Length * 8 ); for (int i = 0; i < length; i++) { data[i] = array[i + start]; } return OperateResult.CreateSuccessResult( data ); } catch (Exception ex) { return new OperateResult( ) { Message = "Extract Msg:" + ex.Message + Environment.NewLine + "Data: " + BasicFramework.SoftBasic.ByteToHexString( response ) }; } } /// /// 解析数据地址成不同的三菱地址类型 /// /// 数据地址 /// 地址结果对象 private static OperateResult FxAnalysisAddress( string address ) { var result = new OperateResult( ); try { switch (address[0]) { case 'M': case 'm': { result.Content1 = MelsecMcDataType.M; result.Content2 = Convert.ToUInt16( address.Substring( 1 ), MelsecMcDataType.M.FromBase ); break; } case 'X': case 'x': { result.Content1 = MelsecMcDataType.X; result.Content2 = Convert.ToUInt16( address.Substring( 1 ), 8 ); break; } case 'Y': case 'y': { result.Content1 = MelsecMcDataType.Y; result.Content2 = Convert.ToUInt16( address.Substring( 1 ), 8 ); break; } case 'D': case 'd': { result.Content1 = MelsecMcDataType.D; result.Content2 = Convert.ToUInt16( address.Substring( 1 ), MelsecMcDataType.D.FromBase ); break; } case 'S': case 's': { result.Content1 = MelsecMcDataType.S; result.Content2 = Convert.ToUInt16( address.Substring( 1 ), MelsecMcDataType.S.FromBase ); break; } case 'T': case 't': { if (address[1] == 'N' || address[1] == 'n') { result.Content1 = MelsecMcDataType.TN; result.Content2 = Convert.ToUInt16( address.Substring( 2 ), MelsecMcDataType.TN.FromBase ); break; } else if (address[1] == 'S' || address[1] == 's') { result.Content1 = MelsecMcDataType.TS; result.Content2 = Convert.ToUInt16( address.Substring( 2 ), MelsecMcDataType.TS.FromBase ); break; } else if (address[1] == 'C' || address[1] == 'c') { result.Content1 = MelsecMcDataType.TC; result.Content2 = Convert.ToUInt16( address.Substring( 2 ), MelsecMcDataType.TC.FromBase ); break; } else { throw new Exception( StringResources.Language.NotSupportedDataType ); } } case 'C': case 'c': { if (address[1] == 'N' || address[1] == 'n') { result.Content1 = MelsecMcDataType.CN; result.Content2 = Convert.ToUInt16( address.Substring( 2 ), MelsecMcDataType.CN.FromBase ); break; } else if (address[1] == 'S' || address[1] == 's') { result.Content1 = MelsecMcDataType.CS; result.Content2 = Convert.ToUInt16( address.Substring( 2 ), MelsecMcDataType.CS.FromBase ); break; } else if (address[1] == 'C' || address[1] == 'c') { result.Content1 = MelsecMcDataType.CC; result.Content2 = Convert.ToUInt16( address.Substring( 2 ), MelsecMcDataType.CC.FromBase ); break; } else { throw new Exception( StringResources.Language.NotSupportedDataType ); } } default: throw new Exception( StringResources.Language.NotSupportedDataType ); } } catch (Exception ex) { result.Message = ex.Message; return result; } result.IsSuccess = true; return result; } /// /// 返回读取的地址及长度信息 /// /// 读取的地址信息 /// 带起始地址的结果对象 private static OperateResult FxCalculateWordStartAddress( string address ) { // 初步解析,失败就返回 var analysis = FxAnalysisAddress( address ); if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis ); // 二次解析 ushort startAddress = analysis.Content2; if (analysis.Content1 == MelsecMcDataType.D) { if (startAddress >= 8000) { startAddress = (ushort)((startAddress - 8000) * 2 + 0x0E00); } else { startAddress = (ushort)(startAddress * 2 + 0x1000); } } else if (analysis.Content1 == MelsecMcDataType.CN) { if (startAddress >= 200) { startAddress = (ushort)((startAddress - 200) * 4 + 0x0C00); } else { startAddress = (ushort)(startAddress * 2 + 0x0A00); } } else if (analysis.Content1 == MelsecMcDataType.TN) { startAddress = (ushort)(startAddress * 2 + 0x0800); } else { return new OperateResult( StringResources.Language.MelsecCurrentTypeNotSupportedWordOperate ); } return OperateResult.CreateSuccessResult( startAddress ); } /// /// 返回读取的地址及长度信息,以及当前的偏置信息 /// 读取的地址信息 /// 带起始地址的结果对象 private static OperateResult FxCalculateBoolStartAddress( string address ) { // 初步解析 var analysis = FxAnalysisAddress( address ); if (!analysis.IsSuccess) return OperateResult.CreateFailedResult( analysis ); // 二次解析 ushort startAddress = analysis.Content2; if (analysis.Content1 == MelsecMcDataType.M) { if (startAddress >= 8000) { startAddress = (ushort)((startAddress - 8000) / 8 + 0x01E0); } else { startAddress = (ushort)(startAddress / 8 + 0x0100); } } else if (analysis.Content1 == MelsecMcDataType.X) { startAddress = (ushort)(startAddress / 8 + 0x0080); } else if (analysis.Content1 == MelsecMcDataType.Y) { startAddress = (ushort)(startAddress / 8 + 0x00A0); } else if (analysis.Content1 == MelsecMcDataType.S) { startAddress = (ushort)(startAddress / 8 + 0x0000); } else if (analysis.Content1 == MelsecMcDataType.CS) { startAddress += (ushort)(startAddress / 8 + 0x01C0); } else if (analysis.Content1 == MelsecMcDataType.CC) { startAddress += (ushort)(startAddress / 8 + 0x03C0); } else if (analysis.Content1 == MelsecMcDataType.TS) { startAddress += (ushort)(startAddress / 8 + 0x00C0); } else if (analysis.Content1 == MelsecMcDataType.TC) { startAddress += (ushort)(startAddress / 8 + 0x02C0); } else { return new OperateResult( StringResources.Language.MelsecCurrentTypeNotSupportedBitOperate ); } return OperateResult.CreateSuccessResult( startAddress, analysis.Content2, ( ushort)(analysis.Content2 % 8) ); } #endregion } }