跳到主要内容
版本:2.1

串口客户端

定义

命名空间:TouchSocket.SerialPorts
程序集:TouchSocket.SerialPorts.dll

一、说明

SerialPortClient是串口系客户端,他直接参与串口的连接、发送、接收、处理、断开等。

二、特点

  • 简单易用。
  • 内存池支持
  • 高性能
  • 适配器预处理,一键式解决分包粘包、对象解析(modbus)等。
  • 超简单的同步发送、异步发送、接收等操作。
  • 基于委托、插件驱动,让每一步都能执行AOP。

三、产品应用场景

  • 所有串口基础使用场景:可跨平台、跨语言使用。
  • 自定义协议解析场景:可解析任意数据格式的串口数据报文。

四、可配置项

可配置项

SetMaxPackageSize

数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。

SetSerialPortOption

配置串口相关。例如:端口、波特率、数据位、校验位、停止位等。

五、支持插件

插件方法功能
ISerialConnectingPlugin在串口连接之前触发。
ISerialConnectedPlugin同意连接,且成功启动接收后触发
ISerialClosingPlugin当客户端主动调用Close时触发
ISerialClosedPlugin当客户端断开连接后触发
ISerialReceivingPlugin在收到原始数据时触发,所有的数据均在ByteBlock里面。
ISerialReceivedPlugin在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。
ISerialSendingPlugin当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。

六、创建SerialPortClient

6.1 简单创建

简单的处理逻辑可通过ConnectingConnectedReceived等委托直接实现。

代码如下:

var client = new SerialPortClient();
client.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到端口
client.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到端口
client.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从端口断开连接。此处仅主动断开才有效。
client.Closed = (client, e) => { return EasyTask.CompletedTask; };//从端口断开连接,当连接不成功时不会触发。
client.Received = async (c, e) =>
{
await Console.Out.WriteLineAsync(e.ByteBlock.Span.ToString(Encoding.UTF8));
};

await client.SetupAsync(new TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption()
{
BaudRate = 9600,//波特率
DataBits = 8,//数据位
Parity = System.IO.Ports.Parity.None,//校验位
PortName = "COM1",//COM
StopBits = System.IO.Ports.StopBits.One//停止位
})
.SetSerialDataHandlingAdapter(() => new PeriodPackageAdapter())
.ConfigurePlugins(a =>
{
a.Add<MyPlugin>();
}));

await client.ConnectAsync();
Console.WriteLine("连接成功");

6.2 继承实现

一般继承实现的话,可以从SerialPortClientBase继承。

class MySerialClient : SerialPortClientBase
{
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
{
//此处逻辑单线程处理。

//此处处理数据,功能相当于Received委托。
string mes = e.ByteBlock.Span.ToString(Encoding.UTF8);
Console.WriteLine($"已接收到信息:{mes}");

await base.OnSerialReceived(e);
}
}
var client = new MySerialClient();
await client.SetupAsync(new TouchSocket.Core.TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption()
{
BaudRate = 9600,//波特率
DataBits = 8,//数据位
Parity = System.IO.Ports.Parity.None,//校验位
PortName = "COM1",//COM
StopBits = System.IO.Ports.StopBits.One//停止位
}));
await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。

七、接收数据

在串口中,接收数据的方式有很多种。多种方式可以组合使用。

7.1 Received委托处理

当使用SerialPortClient创建客户端时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。

var client = new SerialPortClient();
client.Received = (client, e) =>
{
//收到信息
string mes = e.ByteBlock.Span.ToString(Encoding.UTF8);
Console.WriteLine($"接收到信息:{mes}");
return EasyTask.CompletedTask;
};

7.2 ReadAsync异步读取

在使用串口时,可能更需要在当前代码上下文读取的情况。所以此处提供了异步读取方法。

await client.ConnectAsync();

using (var receiver = client.CreateReceiver())
{
while (true)
{
using (CancellationTokenSource tokenSource=new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
using (var receiverResult = await receiver.ReadAsync(tokenSource.Token))
{
if (receiverResult.IsCompleted)
{
//断开
}

//按照适配器类型。此处可以获取receiverResult.ByteBlock或者receiverResult.RequestInfo
await Console.Out.WriteLineAsync(receiverResult.ByteBlock.Span.ToString(Encoding.UTF8));
}
}
}
}
提示

receiver在被Create之后,其优先级最高,即:Received委托和插件都不再被调用。

7.3 插件处理

按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下:

(1)声明插件

插件可以先继承PluginBase,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。

如果已经有继承类,直接实现IPlugin接口即可。

public class MyPlugin : PluginBase, ISerialReceivedPlugin
{
public async Task OnSerialReceived(ISerialPortSession client, ReceivedDataEventArgs e)
{
//这里处理数据接收
//根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。
var byteBlock = e.ByteBlock;
var requestInfo = e.RequestInfo;

//e.Handled = true;//表示该数据已经被本插件处理,无需再投递到其他插件。

await e.InvokeNext();
}
}

(2)创建使用插件处理的客户端

var client = new SerialPortClient();
await client.SetupAsync(new TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption()
{
BaudRate = 9600,//波特率
DataBits = 8,//数据位
Parity = System.IO.Ports.Parity.None,//校验位
PortName = "COM1",//COM
StopBits = System.IO.Ports.StopBits.One//停止位
})
.ConfigurePlugins(a =>
{
a.Add<MyPlugin>();
}));

await client.ConnectAsync();

八、发送数据

8.1 发送

SerialPortClient已经内置了多种发送方法,直接调用就可以发送。

但需要注意的是,通过该方法发送的数据,会经过适配器,如果想要直接发送,请继承以后,使用DefaultSendAsync。如果发送失败,则会立即抛出异常。

Task SendAsync(ReadOnlyMemory<byte> memory);
提示

框架不仅内置了SendAsync字节的发送,也扩展了字符串等常见数据的发送。而且还包括了TrySend等不会抛出异常的发送方法。

8.2 同步发送等待

WaitingClientTouchSocket提供的一个等待客户端发送数据的客户端。例如:串口客户端发送一个"Hello",等待对方回信"Hi",此时即可使用WaitingClient

await client.ConnectAsync();

//调用CreateWaitingClient获取到IWaitingClient的对象。
var waitClient = client.CreateWaitingClient(new WaitingOptions()
{
FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回
{
return true;

//if (response.Data.Length == 1)
//{
// return true;
//}
//return false;
}
});

//然后使用SendThenReturn。
byte[] returnData = waitClient.SendThenReturn(Encoding.UTF8.GetBytes("Hello"));
client.Logger.Info($"收到回应消息:{Encoding.UTF8.GetString(returnData)}");

//同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse.
ResponsedData responsedData = waitClient.SendThenResponse(Encoding.UTF8.GetBytes("Hello"));
IRequestInfo requestInfo = responsedData.RequestInfo;//同步收到的RequestInfo
提示

SendThenReturn时,通过其他参数,还可以设置Timeout,以及可取消的等待Token

注意事项
  1. 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。
  2. 发送采用同步锁,一个事务没结束,另一个请求也发不出去。
  3. Net461及以下版本中,SendThenReturnSendThenReturnAsync不能混合使用。即:要么全同步,要么全异步。

本文示例Demo