本篇博文主要说说hslcommunication的结果链的知识,说一下前因后果,以及目前最新的功能扩充,(V9.5.0以上)
以前也写过一篇文章:https://www.cnblogs.com/dathlin/p/7865682.html 不看也没事,参考这篇新的文章就好了。
首先还是聊聊,为什么会诞生这个 OperateResult ,比如我有个方法,获取一些信息的,或是执行一些操作的,比如读取文件的内容。
public string ReadFileContent( string path )
{
return System.IO.File.ReadAllText( path );
}
很简单吧,方法里面复杂也没有关系的,如果这个方法保证不会发生异常,或是失败,那就没有关系,这样写也挺好的,但是事实就是极容易发生异常,就拿这个例子来说,可能因为文件不存在,可能因为其他异常。如果我们需要返回的内容包含下面三大块,肯定包括 1. 是否成功 2.错误消息 3.内容 于是我加了一个错误码,就有了下面的类(以下是简写)
public class OperateResult
{
public bool IsSuccess { get; set; }
public string Message { get; set; }
public int ErrorCode { get; set; }
}
然后可能携带各种不同类型的结果内容,又可能是多个的,所以有了泛型的派生类,这算是泛型的一个经典的例子,另一个例子就是List<T>数组了。
public class OperateResult<T> : OperateResult public class OperateResult<T1, T2> : OperateResult public class OperateResult<T1, T2, T3> : OperateResult public class OperateResult<T1, T2, T3, T4> : OperateResult public class OperateResult<T1, T2, T3, T4, T5> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8, T9> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : OperateResult
又定义了十个泛型类对象,最多可以携带10个不同类型的参数信息,当然,为了扩充一些转化信息,整个 OperateResult.cs 文件的源代码长达 3577 行源代码。
好了,所以上面的方法可以改写为:
public OperateResult<string> ReadFileContent( string path )
{
try
{
return OperateResult.CreateSuccessResult( System.IO.File.ReadAllText( path ) );
}
catch(Exception ex)
{
return new OperateResult<string>( ex.Message );
}
}
这样我们就能把结果信息返回了,当然了,实际可能更加复杂一点,比如下面所示,在读取文件之前,还需要检查当前账户是否有权限。
public bool CheckPermission( )
{
// 检查账户合法性,是否有权利下载
return true;
}
public OperateResult<string> ReadFileContent( string path )
{
if (!CheckPermission( )) return new OperateResult<string>( "当前无权读取文件的内容" );
try
{
return OperateResult.CreateSuccessResult( System.IO.File.ReadAllText( path ) );
}
catch(Exception ex)
{
return new OperateResult<string>( ex.Message );
}
}
到这里,已经成型基本的意思了。我们再来说一下HslCommunication自身的经典应用,我们来看一个三菱PLC的数据读取示例,我们为了要读取一个地址的原始字节数据,会提供这样的方法,
public override OperateResult<byte[]> Read( string address, ushort length )
但是呢,实际上错误的原因是很多的,可能一开始地址输入错误了,可能网络发生了错误,可能PLC返回了一个错误码,然后进行解析得到正确的数据。那么底层这么实现
public override OperateResult<byte[]> Read( string address, ushort length )
{
// 获取指令
var command = BuildReadCommand(address, length, false, PLCNumber);
if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command);
// 核心交互
var read = ReadFromCoreServer(command.Content);
if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(read);
// 错误代码验证
OperateResult check = CheckResponseLegal( read.Content );
if (!check.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( check );
// 数据解析,需要传入是否使用位的参数
return ExtractActualData(read.Content, false);
}
我们再来看看这个核心交互是怎么实现的?
public OperateResult<byte[]> ReadFromCoreServer( byte[] send )
{
var result = new OperateResult<byte[]>( );
OperateResult<Socket> resultSocket = null;
InteractiveLock.Enter( );
try
{
// 获取有用的网络通道,如果没有,就建立新的连接
resultSocket = GetAvailableSocket( );
if (!resultSocket.IsSuccess)
{
IsSocketError = true;
AlienSession?.Offline( );
InteractiveLock.Leave( );
result.CopyErrorFromOther( resultSocket );
return result;
}
OperateResult<byte[]> read = ReadFromCoreServer( resultSocket.Content, send );
if (read.IsSuccess)
{
IsSocketError = false;
result.IsSuccess = read.IsSuccess;
result.Content = read.Content;
result.Message = StringResources.Language.SuccessText;
}
else
{
IsSocketError = true;
AlienSession?.Offline( );
result.CopyErrorFromOther( read );
}
ExtraAfterReadFromCoreServer( read );
InteractiveLock.Leave( );
}
catch
{
InteractiveLock.Leave( );
throw;
}
if (!isPersistentConn) resultSocket?.Content?.Close( );
return result;
}
我们可以看到,一旦中间的某个环节发生了错误或是异常,这个错误信息会一直向上传递,直到传递给最上层的调用者。以此形成上下的链条。
那么Convert,Check,Then是什么意思呢?主要是简化代码的。我们来看看下面的代码
public OperateResult<string> Write( )
{
OperateResult write = siemens.Write( "M100", (short)12 );
if (!write.IsSuccess) return OperateResult.CreateFailedResult<string>( write );
return OperateResult.CreateSuccessResult( "M100写入成功" );
}
这个代码就可以简化为:
public OperateResult<string> Write( ) => siemens.Write( "M100", (short)12 ).Convert<string>( "M100写入成功" );
我们看到代码简化了很多,所以Convert意思就是,如果原来的结果对象失败,就直接返回,如果成功,就返回给定的结果内容。
我们再来看第二种情况:这种情况主要是对读取的内容进行一些判断操作。
public OperateResult Check( )
{
OperateResult<short> read = siemens.ReadInt16( "M100" );
if (!read.IsSuccess) return OperateResult.CreateFailedResult<string>( read );
if (read.Content == 10) return OperateResult.CreateSuccessResult( );
else return new OperateResult( "设备的数据值不对" );
}
这个代码可以简化为:
public OperateResult Check( ) => siemens.ReadInt16( "M100" ).Check( m => m == 10, "设备的数据值不对" );
当然,如果我的检查的方法比较复杂,也可以这么写:
public OperateResult CheckStatus(short value )
{
if (value == 1) return new OperateResult( "错误原因1" );
if (value == 2) return new OperateResult( "错误原因2" );
if (value == 3) return new OperateResult( "错误原因3" );
if (value == 4) return new OperateResult( "错误原因4" );
return OperateResult.CreateSuccessResult( );
}
public OperateResult Check( ) => siemens.ReadInt16( "M100" ).Check( m => CheckStatus( m ) );
我们再来看看一种更复杂的情况。
public OperateResult StartPLC( )
{
// 这是一个启动PLC的方法,逻辑就是,M100.0是启动PLC,但是在启动之前,需要向PLC的多个地址写入初始参数。
OperateResult write = siemens.Write( "M200", (short)123 );
if (!write.IsSuccess) return write;
write = siemens.Write( "M202", 123f );
if (!write.IsSuccess) return write;
write = siemens.Write( "M206", "123456" );
if (!write.IsSuccess) return write;
return siemens.Write( "M100.0", true );
}
嗯,这时候,就需要使用Then方法了,可以简化为:
public OperateResult StartPLC( ) => siemens.Write( "M200", (short)123 ). Then( ( ) => siemens.Write( "M202", 123f ) ). Then( ( ) => siemens.Write( "M206", "123456" ) ). Then( ( ) => siemens.Write( "M100.0", true ) );
一旦发生失败,就会立即回传。现在我们来看个更复杂的综合例子,这是一个现场流程中间的一个小环节,当AGV车到达库位后,需要通知PLC进行连串的交互,以及读取条码信息:
string barcode = string.Empty;
public OperateResult CheckSignalAfterAgvReach( )
{
// 通知PLC信息,AGV已经到达
OperateResult write = siemens.Write( "DB101.3.1", true );
if (!write.IsSuccess) return write;
// 等待PLC复位 允许AGV放胚信号 为false
OperateResult wait = siemens.Wait( "DB101.3.2", false );
if (wait.IsSuccess) return wait;
// 复位AGV放胚完成信号
write = siemens.Write( "DB101.3.1", false );
if (!write.IsSuccess) return write;
// 等待允许读取条码信息
wait = siemens.Wait( "DB101.1.3", true );
if (wait.IsSuccess) return wait;
// 读取条码的信息
var readBarCode = siemens.ReadString( "DB102.0" );
if (!readBarCode.IsSuccess) return readBarCode;
// 条码用于其他用途
barcode = readBarCode.Content;
// 将上料读取条码完成值true
write = siemens.Write( "DB101.1.4", true );
if (!write.IsSuccess) return write;
// 等待上料允许读取条码设置为false
wait = siemens.Wait( "DB101.1.3", false );
if (wait.IsSuccess) return wait;
// 复位上料条码读取完成信号
return siemens.Write( "DB101.1.4", false );
}
那么这部分的代码可以简写为:
public OperateResult CheckSignalAfterAgvReach2( ) => siemens.Write( "DB101.3.1", true ).
Then( ( ) => siemens.Wait( "DB101.3.2", false ) ).
Then( ( ) => siemens.Write( "DB101.3.1", false ) ).
Then( ( ) => siemens.Wait( "DB101.1.3", true ) ).
Then( ( ) => siemens.ReadString( "DB102.0" ) ).
Then( m => { barcode = m; return siemens.Write( "DB101.1.4", true ); } ).
Then( ( ) => siemens.Wait( "DB101.1.3", false ) ).
Then( ( ) => siemens.Write( "DB101.1.4", false ) );
string barcode = string.Empty;
emmmm,好像写多了,代码是简化了,可读性并没有提升很多,也是给了一个方向。