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
}
}