You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

657 lines
25 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using HslCommunication.Core;
using HslCommunication.Serial;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HslCommunication.Profinet.Panasonic
{
/// <summary>
/// 松下PLC的数据交互协议采用Mewtocol协议通讯
/// </summary>
/// <remarks>
/// 触点地址的输入的格式说明如下:
/// <list type="table">
/// <listheader>
/// <term>地址名称</term>
/// <term>地址代号</term>
/// <term>示例</term>
/// <term>地址进制</term>
/// <term>字操作</term>
/// <term>位操作</term>
/// <term>备注</term>
/// </listheader>
/// <item>
/// <term>外部输入继电器</term>
/// <term>X</term>
/// <term>X0,X100</term>
/// <term>10</term>
/// <term>×</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>外部输出继电器</term>
/// <term>Y</term>
/// <term>Y0,Y100</term>
/// <term>10</term>
/// <term>×</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>内部继电器</term>
/// <term>R</term>
/// <term>R0,R100</term>
/// <term>10</term>
/// <term>×</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>定时器</term>
/// <term>T</term>
/// <term>T0,T100</term>
/// <term>10</term>
/// <term>×</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>计数器</term>
/// <term>C</term>
/// <term>C0,C100</term>
/// <term>10</term>
/// <term>×</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// <item>
/// <term>链接继电器</term>
/// <term>L</term>
/// <term>L0,L100</term>
/// <term>10</term>
/// <term>×</term>
/// <term>√</term>
/// <term></term>
/// </item>
/// </list>
/// 数据地址的输入的格式说明如下:
/// <list type="table">
/// <listheader>
/// <term>地址名称</term>
/// <term>地址代号</term>
/// <term>示例</term>
/// <term>地址进制</term>
/// <term>字操作</term>
/// <term>位操作</term>
/// <term>备注</term>
/// </listheader>
/// <item>
/// <term>数据寄存器 DT</term>
/// <term>D</term>
/// <term>D0,D100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>链接寄存器 LT</term>
/// <term>L</term>
/// <term>L0,L100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>文件寄存器 FL</term>
/// <term>F</term>
/// <term>F0,F100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>目标值 SV</term>
/// <term>S</term>
/// <term>S0,S100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>经过值 EV</term>
/// <term>K</term>
/// <term>K0,K100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>索引寄存器 IX</term>
/// <term>IX</term>
/// <term>IX0,IX100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// <item>
/// <term>索引寄存器 IY</term>
/// <term>IY</term>
/// <term>IY0,IY100</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term></term>
/// </item>
/// </list>
/// </remarks>
public class PanasonicMewtocol : SerialDeviceBase<RegularByteTransform>
{
#region Constructor
/// <summary>
/// 实例化一个默认的松下PLC通信对象默认站号为1
/// </summary>
/// <param name="station">站号信息默认为0xEE</param>
public PanasonicMewtocol( byte station = 238 )
{
this.Station = station;
this.ByteTransform.DataFormat = DataFormat.DCBA;
}
#endregion
#region Public Properties
/// <summary>
/// 设备的目标站号
/// </summary>
public byte Station { get; set; }
#endregion
#region Read Write Override
/// <summary>
/// 从松下PLC中读取数据
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="length">长度</param>
/// <returns>返回数据信息</returns>
public override OperateResult<byte[]> Read( string address, ushort length )
{
// 创建指令
OperateResult<byte[]> command = BuildReadCommand( Station, address, length );
if (!command.IsSuccess) return command;
// 数据交互
OperateResult<byte[]> read = ReadBase( command.Content );
if (!read.IsSuccess) return read;
// 提取数据
return ExtraActualData( read.Content );
}
/// <summary>
/// 将数据写入到松下PLC中
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="value">真实数据</param>
/// <returns>是否写入成功</returns>
public override OperateResult Write( string address, byte[] value )
{
// 创建指令
OperateResult<byte[]> command = BuildWriteCommand( Station, address, value );
if (!command.IsSuccess) return command;
// 数据交互
OperateResult<byte[]> read = ReadBase( command.Content );
if (!read.IsSuccess) return read;
// 提取结果
return ExtraActualData( read.Content );
}
#endregion
#region Read Write Bool
/// <summary>
/// 批量读取松下PLC的位地址
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="length">数据长度</param>
/// <returns>读取结果对象</returns>
public OperateResult<bool[]> ReadBool(string address, ushort length )
{
// 读取数据
OperateResult<byte[]> read = Read( address, length );
if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool[]>( read );
// 提取bool
byte[] buffer = BasicFramework.SoftBasic.BytesReverseByWord( read.Content );
return OperateResult.CreateSuccessResult( BasicFramework.SoftBasic.ByteToBoolArray( read.Content, length ) );
}
/// <summary>
/// 读取单个的Bool数据
/// </summary>
/// <param name="address">起始地址</param>
/// <returns>读取结果对象</returns>
public OperateResult<bool> ReadBool(string address )
{
// 读取数据
OperateResult<bool[]> read = ReadBool( address, 1 );
if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool>( read );
return OperateResult.CreateSuccessResult( read.Content[0] );
}
/// <summary>
/// 写入bool数据信息存在一定的风险谨慎操作
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="values">数据值信息</param>
/// <returns>返回是否成功的结果对象</returns>
public OperateResult Write(string address, bool[] values )
{
// 计算字节数据
byte[] buffer = BasicFramework.SoftBasic.BoolArrayToByte( values );
// 创建指令
OperateResult<byte[]> command = BuildWriteCommand(Station, address, BasicFramework.SoftBasic.BytesReverseByWord( buffer ), (short)values.Length );
if (!command.IsSuccess) return command;
// 数据交互
OperateResult<byte[]> read = ReadBase( command.Content );
if (!read.IsSuccess) return read;
// 提取结果
return ExtraActualData( read.Content );
}
/// <summary>
/// 写入bool数据数据
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="value">True还是False</param>
/// <returns>返回是否成功的结果对象</returns>
public OperateResult Write( string address, bool value )
{
return Write( address, new bool[] { value } );
}
#endregion
#region Object Override
/// <summary>
/// 返回表示当前对象的字符串
/// </summary>
/// <returns>字符串信息</returns>
public override string ToString( )
{
return $"Panasonic Mewtocol[{PortName}:{BaudRate}]";
}
#endregion
#region Bulid Read Command
private static string CalculateCrc(StringBuilder sb )
{
byte tmp = 0;
tmp = (byte)sb[0];
for (int i = 1; i < sb.Length; i++)
{
tmp ^= (byte)sb[i];
}
return BasicFramework.SoftBasic.ByteToHexString( new byte[] { tmp } );
}
/// <summary>
/// 解析数据地址解析出地址类型起始地址DB块的地址
/// </summary>
/// <param name="address">数据地址</param>
/// <returns>解析出地址类型,起始地址,是否位读取</returns>
private static OperateResult<string, int> AnalysisAddress( string address )
{
var result = new OperateResult<string, int>( );
try
{
result.Content2 = 0;
if(address.StartsWith("IX") || address.StartsWith( "ix" ))
{
result.Content1 = "IX";
result.Content2 = int.Parse( address.Substring( 2 ) );
}
else if (address.StartsWith( "IY" ) || address.StartsWith( "iy" ))
{
result.Content1 = "IY";
result.Content2 = int.Parse( address.Substring( 2 ) );
}
else if (address.StartsWith( "ID" ) || address.StartsWith( "id" ))
{
result.Content1 = "ID";
result.Content2 = int.Parse( address.Substring( 2 ) );
}
else if (address[0] == 'X' || address[0] == 'x')
{
result.Content1 = "X";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if(address[0] == 'Y' || address[0] == 'y')
{
result.Content1 = "Y";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'R' || address[0] == 'r')
{
result.Content1 = "R";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'T' || address[0] == 't')
{
result.Content1 = "T";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'C' || address[0] == 'c')
{
result.Content1 = "C";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'L' || address[0] == 'l')
{
result.Content1 = "L";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'D' || address[0] == 'd')
{
result.Content1 = "D";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'F' || address[0] == 'f')
{
result.Content1 = "F";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if (address[0] == 'S' || address[0] == 's')
{
result.Content1 = "S";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else if(address[0] == 'K' || address[0] == 'k')
{
result.Content1 = "K";
result.Content2 = ushort.Parse( address.Substring( 1 ) );
}
else
{
throw new Exception( StringResources.Language.NotSupportedDataType );
}
}
catch (Exception ex)
{
result.Message = ex.Message;
return result;
}
result.IsSuccess = true;
return result;
}
/// <summary>
/// 创建读取离散触点的报文指令
/// </summary>
/// <param name="address">地址信息</param>
/// <returns>包含是否成功的结果对象</returns>
public static OperateResult<byte[]> BuildReadMultiCoil( string[] address )
{
// 参数检查
if (address == null) return new OperateResult<byte[]>( "address is not allowed null" );
if (address.Length < 1 || address.Length > 8) return new OperateResult<byte[]>( "length must be 1-8" );
StringBuilder sb = new StringBuilder( "%EE#RCP" );
sb.Append( address.Length.ToString( ) );
for (int i = 0; i < address.Length; i++)
{
// 解析地址
OperateResult<string, int> analysis = AnalysisAddress( address[i] );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2 );
}
sb.Append( CalculateCrc( sb ) );
sb.Append( '\u000D' );
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( sb.ToString( ) ) );
}
/// <summary>
/// 创建写入离散触点的报文指令
/// </summary>
/// <param name="address">地址信息</param>
/// <param name="values">bool值数组</param>
/// <returns>包含是否成功的结果对象</returns>
public static OperateResult<byte[]> BuildWriteMultiCoil( string[] address, bool[] values )
{
// 参数检查
if (address == null || values == null) return new OperateResult<byte[]>( "address and values is not allowed null" );
if (address.Length < 1 || address.Length > 8) return new OperateResult<byte[]>( "address length must be 1-8" );
if (address.Length != values.Length) return new OperateResult<byte[]>( "address and values length must be same" );
StringBuilder sb = new StringBuilder( "%EE#WCP" );
sb.Append( address.Length.ToString( ) );
for (int i = 0; i < address.Length; i++)
{
// 解析地址
OperateResult<string, int> analysis = AnalysisAddress( address[i] );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2 );
sb.Append( values[i] ? '1' : '0' );
}
sb.Append( CalculateCrc( sb ) );
sb.Append( '\u000D' );
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( sb.ToString( ) ) );
}
/// <summary>
/// 创建批量读取触点的报文指令
/// </summary>
/// <param name="station">站号信息</param>
/// <param name="address">地址信息</param>
/// <param name="length">数据长度</param>
/// <returns>包含是否成功的结果对象</returns>
public static OperateResult<byte[]> BuildReadCommand(byte station, string address, ushort length )
{
// 参数检查
if (address == null) return new OperateResult<byte[]>( StringResources.Language.PanasonicAddressParameterCannotBeNull );
// 解析地址
OperateResult<string, int> analysis = AnalysisAddress( address );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
StringBuilder sb = new StringBuilder( "%" );
sb.Append( station.ToString( "X2" ) );
sb.Append( "#" );
if(analysis.Content1 == "X" || analysis.Content1 == "Y" || analysis.Content1 == "R" || analysis.Content1 == "L")
{
sb.Append( "RCC" );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2.ToString( "D4" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D4" ) );
}
else if(analysis.Content1 == "D" || analysis.Content1 == "L" || analysis.Content1 == "F")
{
sb.Append( "RD" );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2.ToString( "D5" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D5" ) );
}
else if(analysis.Content1 == "IX" || analysis.Content1 == "IY" || analysis.Content1 == "ID")
{
sb.Append( "RD" );
sb.Append( analysis.Content1 );
sb.Append( "000000000" );
}
else if (analysis.Content1 == "C" || analysis.Content1 == "T" )
{
sb.Append( "RS" );
sb.Append( analysis.Content2.ToString( "D4" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D4" ) );
}
else
{
return new OperateResult<byte[]>( StringResources.Language.NotSupportedDataType );
}
sb.Append( CalculateCrc( sb ) );
sb.Append( '\u000D' );
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( sb.ToString( ) ) );
}
/// <summary>
/// 创建批量读取触点的报文指令
/// </summary>
/// <param name="station">设备站号</param>
/// <param name="address">地址信息</param>
/// <param name="values">数据值</param>
/// <param name="length">数据长度</param>
/// <returns>包含是否成功的结果对象</returns>
public static OperateResult<byte[]> BuildWriteCommand( byte station, string address, byte[] values, short length = -1 )
{
// 参数检查
if (address == null) return new OperateResult<byte[]>( StringResources.Language.PanasonicAddressParameterCannotBeNull );
// 解析地址
OperateResult<string, int> analysis = AnalysisAddress( address );
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
// 确保偶数长度
values = BasicFramework.SoftBasic.ArrayExpandToLengthEven( values );
if (length == -1) length = (short)(values.Length / 2);
StringBuilder sb = new StringBuilder( "%" );
sb.Append( station.ToString( "X2" ) );
sb.Append( "#" );
if (analysis.Content1 == "X" || analysis.Content1 == "Y" || analysis.Content1 == "R" || analysis.Content1 == "L")
{
sb.Append( "WCC" );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2.ToString( "D4" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D4" ) );
}
else if (analysis.Content1 == "D" || analysis.Content1 == "L" || analysis.Content1 == "F")
{
sb.Append( "WD" );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2.ToString( "D5" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D5" ) );
}
else if (analysis.Content1 == "IX" || analysis.Content1 == "IY" || analysis.Content1 == "ID")
{
sb.Append( "WD" );
sb.Append( analysis.Content1 );
sb.Append( analysis.Content2.ToString( "D9" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D9" ) );
}
else if (analysis.Content1 == "C" || analysis.Content1 == "T")
{
sb.Append( "WS" );
sb.Append( analysis.Content2.ToString( "D4" ) );
sb.Append( (analysis.Content2 + length - 1).ToString( "D4" ) );
}
sb.Append( BasicFramework.SoftBasic.ByteToHexString( values ) );
sb.Append( CalculateCrc( sb ) );
sb.Append( '\u000D' );
return OperateResult.CreateSuccessResult( Encoding.ASCII.GetBytes( sb.ToString( ) ) );
}
/// <summary>
/// 检查从PLC反馈的数据并返回正确的数据内容
/// </summary>
/// <param name="response">反馈信号</param>
/// <returns>是否成功的结果信息</returns>
public static OperateResult<byte[]> ExtraActualData( byte[] response )
{
if (response.Length < 9) return new OperateResult<byte[]>( StringResources.Language.PanasonicReceiveLengthMustLargerThan9 );
if(response[3] == '$')
{
byte[] data = new byte[response.Length - 9];
if (data.Length > 0)
{
Array.Copy( response, 6, data, 0, data.Length );
data = BasicFramework.SoftBasic.HexStringToBytes( Encoding.ASCII.GetString( data ) );
}
return OperateResult.CreateSuccessResult( data );
}
else if(response[3] == '!')
{
int err = int.Parse(Encoding.ASCII.GetString( response, 4, 2 ));
string msg = string.Empty;
switch (err)
{
case 20: msg = StringResources.Language.PanasonicMewStatus20; break;
case 21: msg = StringResources.Language.PanasonicMewStatus21; break;
case 22: msg = StringResources.Language.PanasonicMewStatus22; break;
case 23: msg = StringResources.Language.PanasonicMewStatus23; break;
case 24: msg = StringResources.Language.PanasonicMewStatus24; break;
case 25: msg = StringResources.Language.PanasonicMewStatus25; break;
case 26: msg = StringResources.Language.PanasonicMewStatus26; break;
case 27: msg = StringResources.Language.PanasonicMewStatus27; break;
case 28: msg = StringResources.Language.PanasonicMewStatus28; break;
case 29: msg = StringResources.Language.PanasonicMewStatus29; break;
case 30: msg = StringResources.Language.PanasonicMewStatus30; break;
case 40: msg = StringResources.Language.PanasonicMewStatus40; break;
case 41: msg = StringResources.Language.PanasonicMewStatus41; break;
case 42: msg = StringResources.Language.PanasonicMewStatus42; break;
case 43: msg = StringResources.Language.PanasonicMewStatus43; break;
case 50: msg = StringResources.Language.PanasonicMewStatus50; break;
case 51: msg = StringResources.Language.PanasonicMewStatus51; break;
case 52: msg = StringResources.Language.PanasonicMewStatus52; break;
case 53: msg = StringResources.Language.PanasonicMewStatus53; break;
case 60: msg = StringResources.Language.PanasonicMewStatus60; break;
case 61: msg = StringResources.Language.PanasonicMewStatus61; break;
case 62: msg = StringResources.Language.PanasonicMewStatus62; break;
case 63: msg = StringResources.Language.PanasonicMewStatus63; break;
case 65: msg = StringResources.Language.PanasonicMewStatus65; break;
case 66: msg = StringResources.Language.PanasonicMewStatus66; break;
case 67: msg = StringResources.Language.PanasonicMewStatus67; break;
default: msg = StringResources.Language.UnknownError; break;
}
return new OperateResult<byte[]>( err, msg );
}
else
{
return new OperateResult<byte[]>( StringResources.Language.UnknownError );
}
}
#endregion
}
}