BestChoice.Communication
1.0.1
dotnet add package BestChoice.Communication --version 1.0.1
NuGet\Install-Package BestChoice.Communication -Version 1.0.1
<PackageReference Include="BestChoice.Communication" Version="1.0.1" />
<PackageVersion Include="BestChoice.Communication" Version="1.0.1" />
<PackageReference Include="BestChoice.Communication" />
paket add BestChoice.Communication --version 1.0.1
#r "nuget: BestChoice.Communication, 1.0.1"
#:package BestChoice.Communication@1.0.1
#addin nuget:?package=BestChoice.Communication&version=1.0.1
#tool nuget:?package=BestChoice.Communication&version=1.0.1
CommunicationService 使用说明
BestChoice.Communication 分为两层:
Communication:底层工业通讯客户端,负责不同协议的连接、组包、解析和读写。CommunicationService:统一服务层,业务系统只需要按“连接名 + 地址/点位名 + 数据类型”调用,不需要直接关心底层协议客户端。
本文档面向直接使用服务层的用户,重点说明 ICommunicationConnectionService 对外可调用的全部方法、可直接读取的状态属性、事件、配置方式和典型示例。
安装与引用
dotnet add package BestChoice.Communication
常用命名空间:
using Communication.Common;
using Communication.Industrial;
using CommunicationService;
如果配置串口,还需要:
using System.IO.Ports;
当前服务层支持的协议
ProtocolType 枚举中包含 OpcUa、Udp、Can、Mqtt、Custom 等值,但当前 CommunicationConnectionService 的工厂和校验器已经接入并可直接创建的协议如下:
| 协议 | 连接参数 | 服务层能力 |
|---|---|---|
ModbusTcp |
IpAddress、Port、SlaveId |
单点读写、连续块读写、数组读写、批量读写 |
ModbusRtu |
PortName、串口参数、SlaveId |
单点读写、连续块读写、数组读写、批量读写 |
SiemensS7 |
IpAddress、Port、Rack、Slot 或 TSAP |
单点读写、连续块读写、数组读写、批量读写 |
MitsubishiMc |
IpAddress、Port、MC 网络参数 |
单点读写、连续块读写、数组读写、批量读写 |
OmronFins |
IpAddress、Port、FINS 节点参数 |
单点读写、连续块读写、数组读写、批量读写 |
InovanceModbusTcp |
IpAddress、Port、SlaveId |
单点读写、连续块读写、数组读写、批量读写 |
InovanceModbusRtu |
PortName、串口参数、SlaveId |
单点读写、连续块读写、数组读写、批量读写 |
InovanceEthernet |
IpAddress、Port、Station |
单点读写、连续块读写、数组读写、批量读写 |
Tcp |
IpAddress、Port |
字节发送、接收、发送并接收 |
SerialPort |
PortName、串口参数 |
字节发送、接收、发送并接收 |
如果调用了当前协议不具备的能力,例如对 Tcp 连接调用 ReadAsync<T>,服务层会抛出 NotSupportedException。
最小完整示例
下面示例创建一个 Siemens S7 连接,连接 PLC,读取单个地址,写入单个地址,按点位名读取,然后断开连接。
var options = new CommunicationServiceOptions
{
Connections =
{
new CommunicationConnectionConfig
{
Name = "plc",
Protocol = ProtocolType.SiemensS7,
IpAddress = "192.168.1.10",
Port = 102,
Rack = 0,
Slot = 1,
ConnectTimeout = 3000,
ReadWriteTimeout = 3000
}
},
Tags =
{
new CommunicationTagConfig
{
Name = "DbValue",
ConnectionName = "plc",
Address = "DB1.DBW0",
DataType = "short",
Access = "ReadWrite",
Description = "DB1 中的 16 位整数"
}
}
};
await using var service = new CommunicationConnectionService(options);
await service.InitializeAsync();
await service.ConnectAsync("plc");
try
{
short value = await service.ReadAsync<short>("plc", "DB1.DBW0");
await service.WriteAsync("plc", "DB1.DBW10", (short)123);
short tagValue = await service.ReadTagAsync<short>("DbValue");
}
finally
{
await service.DisconnectAsync("plc");
}
配置方式
代码配置
CommunicationServiceOptions 包含两部分:
Connections:通讯连接列表,一个连接对应一个底层协议客户端。Tags:点位列表,用点位名屏蔽真实协议地址,适合业务系统长期使用。
var options = new CommunicationServiceOptions
{
Connections =
{
new CommunicationConnectionConfig
{
Name = "modbus",
Protocol = ProtocolType.ModbusTcp,
IpAddress = "192.168.1.20",
Port = 502,
SlaveId = 1,
ByteOrder = Communication.Protocols.Modbus.ModbusByteOrder.CDAB,
AutoConnect = false
},
new CommunicationConnectionConfig
{
Name = "rawTcp",
Protocol = ProtocolType.Tcp,
IpAddress = "192.168.1.50",
Port = 9000
}
},
Tags =
{
new CommunicationTagConfig
{
Name = "Temperature",
ConnectionName = "modbus",
Address = "40001",
DataType = "float",
Access = "Read"
},
new CommunicationTagConfig
{
Name = "SetSpeed",
ConnectionName = "modbus",
Address = "40010",
DataType = "short",
Access = "ReadWrite"
}
}
};
快速创建 Modbus TCP 配置
CreateModbusTcp 适合只有一个 Modbus TCP 连接、还不需要点位表的场景。
var options = CommunicationServiceOptions.CreateModbusTcp(
connectionName: "modbus",
ipAddress: "192.168.1.20",
port: 502,
slaveId: 1,
autoConnect: false);
JSON 配置
服务层支持从 JSON 文件加载配置,枚举值可以直接写字符串。
{
"Connections": [
{
"Name": "plc",
"Protocol": "SiemensS7",
"Enabled": true,
"AutoConnect": false,
"IpAddress": "192.168.1.10",
"Port": 102,
"Rack": 0,
"Slot": 1,
"ConnectTimeout": 3000,
"ReadWriteTimeout": 3000
}
],
"Tags": [
{
"Name": "RunState",
"ConnectionName": "plc",
"Address": "DB1.DBX0.0",
"DataType": "bool",
"Access": "ReadWrite",
"Description": "运行状态"
},
{
"Name": "Speed",
"ConnectionName": "plc",
"Address": "DB1.DBW2",
"DataType": "short",
"Access": "ReadWrite",
"Description": "速度设定"
}
]
}
加载 JSON:
var options = await CommunicationServiceOptions.LoadFromJsonFileAsync(
"communication.settings.json");
await using var service = new CommunicationConnectionService(options);
await service.InitializeAsync();
await service.ConnectAsync("plc");
JSON 解析选项由 CommunicationServiceOptions.CreateJsonSerializerOptions() 提供,已启用:
- 属性名大小写不敏感。
- 枚举字符串转换。
- 允许注释和末尾逗号。
- 序列化时格式化缩进。
配置项说明
CommunicationConnectionConfig
| 属性 | 说明 |
|---|---|
Name |
连接名,服务层调用时用它定位连接。启用连接内必须唯一。 |
Protocol |
协议类型,例如 ModbusTcp、SiemensS7、Tcp。 |
Enabled |
是否启用该连接。禁用连接不会初始化,也不能被点位引用。 |
AutoConnect |
InitializeAsync 结束后是否自动连接。 |
IpAddress、Port |
TCP 类协议目标地址和端口。 |
PortName、BaudRate、DataBits、Parity、StopBits |
串口类协议参数。 |
ConnectTimeout |
连接超时时间,单位毫秒。 |
ReadWriteTimeout |
读写超时时间,单位毫秒。 |
AutoReconnect |
底层客户端是否启用自动重连。 |
ReconnectInterval |
自动重连间隔,单位毫秒。 |
MaxReconnectCount |
最大重连次数,-1 表示无限重连。 |
SlaveId |
Modbus 从站号;汇川 Modbus 中也作为站号使用。 |
ByteOrder |
多寄存器数据字节序,常见为 ABCD、CDAB 等。 |
StringEncodingName |
字符串编码名称,常用 ASCII、UTF-8、GBK。 |
DefaultStringRegisterLength |
Modbus/汇川字符串默认寄存器长度。 |
DefaultStringLength |
Siemens S7 字符串默认字节长度。 |
DefaultStringWordLength |
Mitsubishi MC / Omron FINS 字符串默认字数,1 字等于 2 字节。 |
OptimizeBatchRead |
是否启用批量读取优化。 |
EnableHeartbeat |
是否启用心跳读取。 |
HeartbeatAddress、HeartbeatDataType |
心跳地址和心跳数据类型。 |
HeartbeatFailureThreshold |
心跳连续失败多少次后判定连接异常。 |
SilentInterval |
Modbus RTU 请求静默间隔,单位毫秒。 |
CpuType、Rack、Slot、TsapMode、ConnectionType、LocalTsap、RemoteTsap |
Siemens S7 连接参数。 |
IsoTpduSize、RequestedPduSize、MaxAmqCaller、MaxAmqCallee |
Siemens S7 ISO/S7 通讯协商参数。 |
MaxItemsPerReadRequest、MaxItemsPerWriteRequest、MaxPayloadBytesPerRequest |
Siemens S7 批量读写分包参数。 |
StringFormat |
Siemens S7 字符串解析格式,常用 RawBytes 或 S7String。 |
UsePutGetCommunication、RequireStandardDbAccess |
Siemens S7 绝对地址访问相关开关。S7-1200/1500 常需要允许 PUT/GET 并关闭对应 DB 的优化访问。 |
CpuSeries、NetworkNumber、PcNumber、DestinationModuleIoNumber、StationNumber、MonitoringTimer |
Mitsubishi MC 参数。 |
MaxWordPointsPerRequest、MaxBitPointsPerRequest |
Mitsubishi MC / Omron FINS 单次最大点数。 |
UseTcpHandshake、GatewayCount、DestinationNetwork、DestinationNode、DestinationUnit、SourceNetwork、SourceNode、SourceUnit |
Omron FINS 参数。 |
PlcSeries、UseInovanceAddressMapping、Station |
汇川 PLC 参数。当前汇川 Modbus 客户端要求启用 UseInovanceAddressMapping。 |
CommunicationTagConfig
| 属性 | 说明 |
|---|---|
Name |
点位名。启用点位内必须唯一。 |
ConnectionName |
点位所属连接名,必须引用一个已启用连接。 |
Address |
真实协议地址,例如 DB1.DBW0、40001、D100。 |
DataType |
点位数据类型。支持 bool、short、ushort、int、uint、float、double、string 及常见别名。 |
Length |
数据长度,对字符串、数组、连续读取等场景有效。 |
IsBit |
是否是位地址。 |
Scale、Offset |
预留缩放参数,表示实际值 = 原始值 * Scale + Offset。 |
Access |
访问权限。可读:Read、ReadOnly、R、ReadWrite、RW;可写:Write、WriteOnly、W、ReadWrite、RW。大小写不敏感。 |
Description |
点位说明。 |
Enabled |
是否启用该点位。 |
服务生命周期
推荐生命周期:
- 构造
CommunicationConnectionService。 - 订阅事件。
- 调用
InitializeAsync初始化配置和底层客户端。 - 调用
ConnectAsync或ConnectAllAsync建立连接。 - 调用读写方法。
- 调用
DisconnectAsync或DisconnectAllAsync断开连接。 - 使用
await using或DisposeAsync释放资源。
await using var service = new CommunicationConnectionService(options);
service.ConnectionStateChanged += (_, e) =>
{
Console.WriteLine(
$"{e.Time:HH:mm:ss} {e.ConnectionName}: {e.OldState} -> {e.NewState}");
};
service.ErrorOccurred += (_, e) =>
{
Console.WriteLine(
$"{e.Time:HH:mm:ss} {e.ConnectionName}: {e.Message} {e.Exception.Message}");
};
await service.InitializeAsync();
try
{
await service.ConnectAsync("plc");
bool alive = await service.IsAliveAsync("plc");
Console.WriteLine($"Alive = {alive}");
}
finally
{
await service.DisconnectAsync("plc");
}
InitializeAsync 只能初始化一次;重复调用会直接返回。除构造和订阅事件外,其他服务方法都应在 InitializeAsync 后调用。
可直接获取的状态、属性和事件
服务事件
ConnectionStateChanged:连接状态变化时触发。
| 字段 | 说明 |
|---|---|
ConnectionName |
连接名。 |
Protocol |
协议类型。 |
OldState |
变化前状态。 |
NewState |
变化后状态。 |
Message |
状态变化说明,可能为空。 |
Time |
事件发生时间。 |
service.ConnectionStateChanged += (_, e) =>
{
Console.WriteLine(
$"[{e.Time:yyyy-MM-dd HH:mm:ss}] {e.ConnectionName} {e.Protocol}: {e.OldState} -> {e.NewState}");
};
ErrorOccurred:底层通讯错误时触发。
| 字段 | 说明 |
|---|---|
ConnectionName |
连接名。 |
Protocol |
协议类型。 |
Exception |
原始异常对象。 |
Message |
错误说明,可能为空。 |
Time |
事件发生时间。 |
service.ErrorOccurred += (_, e) =>
{
Console.WriteLine(
$"[{e.Time:yyyy-MM-dd HH:mm:ss}] {e.ConnectionName}: {e.Message}");
Console.WriteLine(e.Exception);
};
CommunicationConnectionSnapshot
通过 GetSnapshot 或 GetSnapshots 获取。
| 属性 | 说明 |
|---|---|
Name |
连接名。 |
Protocol |
协议类型。 |
State |
当前连接状态。 |
IsConnected |
是否已连接。 |
SnapshotTime |
快照生成时间。 |
var snapshot = service.GetSnapshot("plc");
Console.WriteLine(snapshot.Name);
Console.WriteLine(snapshot.Protocol);
Console.WriteLine(snapshot.State);
Console.WriteLine(snapshot.IsConnected);
Console.WriteLine(snapshot.SnapshotTime);
ICommunicationClient
通过 GetClient 获取底层客户端基础接口。
| 属性 | 说明 |
|---|---|
Name |
客户端名称。 |
ClientProtocolType |
客户端协议类型。 |
Category |
协议分类,Industrial 或 General。 |
ConnectionState |
当前连接状态。 |
IsConnected |
是否已连接。 |
Options |
底层协议配置对象。 |
var client = service.GetClient("plc");
Console.WriteLine(client.Name);
Console.WriteLine(client.ClientProtocolType);
Console.WriteLine(client.Category);
Console.WriteLine(client.ConnectionState);
Console.WriteLine(client.IsConnected);
Console.WriteLine(client.Options.GetType().Name);
一般业务系统优先使用服务层方法。只有需要读取底层协议属性或进行协议专用扩展时,再使用 GetClient。
TagItem
批量读取会返回 TagItem。
| 属性 | 说明 |
|---|---|
Name |
点位名称。 |
Address |
点位地址。 |
Value |
点位值。 |
DataType |
数据类型。 |
UpdateTime |
更新时间。 |
Quality |
通讯质量。 |
ErrorMessage |
错误信息,正常时通常为空。 |
TagQuality 可能值:
| 值 | 说明 |
|---|---|
Good |
正常。 |
Bad |
异常。 |
Uncertain |
不确定。 |
Timeout |
通讯超时。 |
AddressError |
地址错误。 |
TypeError |
数据类型错误。 |
Disconnected |
连接断开。 |
IReadOnlyList<TagItem> tags = await service.BatchReadTagsAsync(
new[] { "RunState", "Speed" });
foreach (var tag in tags)
{
Console.WriteLine(
$"{tag.Name} {tag.Address} = {tag.Value}, Quality={tag.Quality}");
if (!string.IsNullOrWhiteSpace(tag.ErrorMessage))
Console.WriteLine(tag.ErrorMessage);
}
ICommunicationConnectionService 方法说明和示例
InitializeAsync
初始化服务层。该方法会校验连接和点位配置,创建底层客户端,调用底层客户端初始化方法,并注册底层事件。如果某个连接配置了 AutoConnect = true,初始化完成后会自动连接。
await using var service = new CommunicationConnectionService(options);
await service.InitializeAsync();
常见异常:
- 没有配置启用连接。
- 连接名或点位名重复。
- 点位引用了不存在或未启用的连接。
- 协议类型当前服务层不支持。
- 必填参数缺失,例如 TCP 类协议缺少
IpAddress,串口类协议缺少PortName。
ConnectAsync
连接指定连接名的设备。
await service.InitializeAsync();
await service.ConnectAsync("plc");
ConnectAllAsync
连接所有已启用并已初始化的连接。
await service.InitializeAsync();
await service.ConnectAllAsync();
DisconnectAsync
断开指定连接名的设备。
await service.DisconnectAsync("plc");
DisconnectAllAsync
断开所有连接。
await service.DisconnectAllAsync();
ReconnectAsync
重新连接指定连接。适合在业务层检测到通讯异常后主动重连。
await service.ReconnectAsync("plc");
IsAliveAsync
检测指定连接的通讯是否正常。底层协议会按自身能力执行存活检测。
bool alive = await service.IsAliveAsync("plc");
if (!alive)
{
await service.ReconnectAsync("plc");
}
GetClient
获取底层客户端基础接口。可以读取底层客户端属性,也可以在确认具体协议类型后自行转型做扩展。
var client = service.GetClient("plc");
Console.WriteLine($"Name = {client.Name}");
Console.WriteLine($"Protocol = {client.ClientProtocolType}");
Console.WriteLine($"Category = {client.Category}");
Console.WriteLine($"State = {client.ConnectionState}");
Console.WriteLine($"Connected = {client.IsConnected}");
GetSnapshot
获取指定连接的状态快照。
CommunicationConnectionSnapshot snapshot = service.GetSnapshot("plc");
Console.WriteLine(
$"{snapshot.Name} {snapshot.Protocol} {snapshot.State} Connected={snapshot.IsConnected}");
GetSnapshots
获取所有连接的状态快照。
IReadOnlyList<CommunicationConnectionSnapshot> snapshots =
service.GetSnapshots();
foreach (var item in snapshots)
{
Console.WriteLine(
$"{item.Name} {item.Protocol} {item.State} Connected={item.IsConnected}");
}
ReadAsync<T>
按连接名和真实地址读取单个值。适用于工业协议连接。
short speed = await service.ReadAsync<short>(
connectionName: "plc",
address: "DB1.DBW0");
bool runState = await service.ReadAsync<bool>(
"plc",
"DB1.DBX0.0");
Modbus 示例:
float temperature = await service.ReadAsync<float>(
"modbus",
"40001");
ReadAsync(string connectionName, string address, Type dataType)
按运行时数据类型读取单个值。适合数据类型来自界面、数据库或 JSON 配置的场景。
Type dataType = typeof(float);
object? value = await service.ReadAsync(
connectionName: "plc",
address: "DB1.DBD20",
dataType: dataType);
Console.WriteLine(value);
WriteAsync<T>
按连接名和真实地址写入单个值。适用于工业协议连接。
await service.WriteAsync(
connectionName: "plc",
address: "DB1.DBW10",
value: (short)1200);
await service.WriteAsync(
"plc",
"DB1.DBX0.1",
true);
WriteAsync(string connectionName, string address, object value)
按对象值写入单个地址。服务层会根据 value 的真实 .NET 类型选择底层泛型写入方法。
object value = 123.45f;
await service.WriteAsync(
connectionName: "plc",
address: "DB1.DBD20",
value: value);
支持写入的对象类型包括 bool、short、ushort、int、uint、float、double、string。
ReadTagAsync<T>
按点位名读取单个值。服务层会从 Tags 配置中找到连接名、地址和数据类型。
short speed = await service.ReadTagAsync<short>("Speed");
bool runState = await service.ReadTagAsync<bool>("RunState");
如果点位 Access 不允许读取,会抛出 InvalidOperationException。
ReadTagAsync(string tagName)
按点位名读取单个值,返回 object?。适合点位名来自界面或数据库,调用方不提前知道泛型类型的场景。
object? value = await service.ReadTagAsync("Temperature");
Console.WriteLine(value);
如果底层批量读取结果的 Quality 不是 Good,该方法会抛出 InvalidOperationException。
WriteTagAsync<T>
按点位名写入单个值。服务层会按点位配置中的 DataType 转换值。
await service.WriteTagAsync<short>(
tagName: "Speed",
value: 1500);
await service.WriteTagAsync<bool>(
"RunState",
true);
如果点位 Access 不允许写入,会抛出 InvalidOperationException。
WriteTagAsync(string tagName, object value)
按点位名写入对象值。适合值来自 JSON、界面输入或数据库的场景。
object speed = 1500;
await service.WriteTagAsync(
tagName: "Speed",
value: speed);
ReadBlockAsync
读取连续原始字节。适用于寄存器、DB 块、D 区等连续地址。返回值是原始 byte[]。
byte[] bytes = await service.ReadBlockAsync(
connectionName: "plc",
startAddress: "DB1.DBB0",
byteLength: 16);
Console.WriteLine(BitConverter.ToString(bytes));
Modbus 示例:
byte[] bytes = await service.ReadBlockAsync(
"modbus",
"HR:0",
20);
WriteBlockAsync
写入连续原始字节。
byte[] data = { 0x01, 0x02, 0x03, 0x04 };
await service.WriteBlockAsync(
connectionName: "plc",
startAddress: "DB1.DBB20",
data: data);
ReadArrayAsync<T>
读取连续数组。适合连续的数值区、寄存器区、DB 块等。
short[] values = await service.ReadArrayAsync<short>(
connectionName: "plc",
startAddress: "DB1.DBW0",
count: 10);
Console.WriteLine(string.Join(", ", values));
Modbus 示例:
float[] temperatures = await service.ReadArrayAsync<float>(
"modbus",
"40001",
4);
WriteArrayAsync<T>
写入连续数组。
short[] values = { 100, 200, 300 };
await service.WriteArrayAsync(
connectionName: "plc",
startAddress: "DB1.DBW20",
values: values);
BatchReadAsync
按真实地址批量读取多个点位。适合多个不连续地址;地址、数据类型、长度等由 IndustrialAddress 指定。
var addresses = new[]
{
new IndustrialAddress
{
Address = "DB1.DBX0.0",
DataType = typeof(bool),
Description = "运行状态"
},
new IndustrialAddress
{
Address = "DB1.DBW2",
DataType = typeof(short),
Description = "速度"
},
new IndustrialAddress
{
Address = "DB1.DBD4",
DataType = typeof(float),
Description = "温度"
}
};
IReadOnlyList<TagItem> result = await service.BatchReadAsync(
connectionName: "plc",
addresses: addresses);
foreach (var item in result)
{
Console.WriteLine(
$"{item.Address} = {item.Value}, Quality={item.Quality}");
}
BatchReadTagsAsync
按点位名批量读取。该方法会按 ConnectionName 自动分组,同一批点位可以来自不同连接。
IReadOnlyList<TagItem> result = await service.BatchReadTagsAsync(
new[] { "RunState", "Speed", "Temperature" });
foreach (var item in result)
{
Console.WriteLine(
$"{item.Name} {item.Address} = {item.Value}, Quality={item.Quality}");
}
如果某个点位不可读,服务层会在读取前抛出异常。若底层返回地址错误、超时等结果,TagItem.Quality 和 TagItem.ErrorMessage 会携带对应信息。
BatchWriteAsync
按真实地址批量写入多个值。字典的 key 是地址,value 是写入值。
var values = new Dictionary<string, object>
{
["DB1.DBW10"] = (short)1200,
["DB1.DBX0.1"] = true,
["DB1.DBD20"] = 12.34f
};
await service.BatchWriteAsync(
connectionName: "plc",
values: values);
Modbus 示例:
await service.BatchWriteAsync(
"modbus",
new Dictionary<string, object>
{
["40010"] = (short)1200,
["C:0"] = true
});
BatchWriteTagsAsync
按点位名批量写入多个值。该方法会按点位配置中的 ConnectionName 自动分组,再转换为真实地址批量写入。
await service.BatchWriteTagsAsync(
new Dictionary<string, object>
{
["Speed"] = 1500,
["RunState"] = true
});
SendAsync
发送原始字节。适用于 Tcp、SerialPort 这类通用字节通讯协议。
byte[] command = { 0x02, 0x10, 0x00, 0x03 };
await service.SendAsync(
connectionName: "rawTcp",
data: command);
ReceiveAsync
接收原始字节。适用于 Tcp、SerialPort 这类通用字节通讯协议。
byte[] response = await service.ReceiveAsync("rawTcp");
Console.WriteLine(BitConverter.ToString(response));
SendAndReceiveAsync
发送请求并等待响应。适用于有请求/响应结构的自定义 TCP 或串口设备。
byte[] request = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02 };
byte[] response = await service.SendAndReceiveAsync(
connectionName: "rawTcp",
request: request);
Console.WriteLine(BitConverter.ToString(response));
DisposeAsync
释放服务层和底层客户端资源。推荐使用 await using 自动释放。
await using var service = new CommunicationConnectionService(options);
await service.InitializeAsync();
await service.ConnectAllAsync();
// 使用服务...
如果不使用 await using,可以手动释放:
var service = new CommunicationConnectionService(options);
try
{
await service.InitializeAsync();
await service.ConnectAllAsync();
}
finally
{
await service.DisposeAsync();
}
常见业务场景示例
连接、读写、断开连接
await using var service = new CommunicationConnectionService(options);
await service.InitializeAsync();
try
{
await service.ConnectAsync("plc");
short currentSpeed = await service.ReadAsync<short>("plc", "DB1.DBW2");
await service.WriteAsync("plc", "DB1.DBW10", (short)(currentSpeed + 10));
}
finally
{
await service.DisconnectAsync("plc");
}
读取单个地址
float temperature = await service.ReadAsync<float>(
connectionName: "plc",
address: "DB1.DBD20");
读取多个地址
多个真实地址:
var addresses = new[]
{
new IndustrialAddress { Address = "DB1.DBW0", DataType = typeof(short) },
new IndustrialAddress { Address = "DB1.DBW2", DataType = typeof(short) },
new IndustrialAddress { Address = "DB1.DBD4", DataType = typeof(float) }
};
IReadOnlyList<TagItem> values = await service.BatchReadAsync(
"plc",
addresses);
多个配置点位:
IReadOnlyList<TagItem> values = await service.BatchReadTagsAsync(
new[] { "RunState", "Speed", "Temperature" });
连续数组:
short[] values = await service.ReadArrayAsync<short>(
"plc",
"DB1.DBW0",
count: 20);
写入多个地址
await service.BatchWriteAsync(
"plc",
new Dictionary<string, object>
{
["DB1.DBW10"] = (short)100,
["DB1.DBW12"] = (short)200,
["DB1.DBX0.1"] = true
});
写入多个点位
await service.BatchWriteTagsAsync(
new Dictionary<string, object>
{
["Speed"] = 1200,
["RunState"] = true
});
周期读取
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(500));
while (await timer.WaitForNextTickAsync())
{
var tags = await service.BatchReadTagsAsync(
new[] { "RunState", "Speed", "Temperature" });
foreach (var tag in tags)
{
Console.WriteLine(
$"{DateTime.Now:HH:mm:ss.fff} {tag.Name}={tag.Value} Quality={tag.Quality}");
}
}
通讯异常后重连
try
{
bool alive = await service.IsAliveAsync("plc");
if (!alive)
await service.ReconnectAsync("plc");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
await service.ReconnectAsync("plc");
}
通用 TCP 请求响应
var options = new CommunicationServiceOptions
{
Connections =
{
new CommunicationConnectionConfig
{
Name = "scanner",
Protocol = ProtocolType.Tcp,
IpAddress = "192.168.1.80",
Port = 9000
}
}
};
await using var service = new CommunicationConnectionService(options);
await service.InitializeAsync();
await service.ConnectAsync("scanner");
byte[] request = { 0x02, 0x31, 0x03 };
byte[] response = await service.SendAndReceiveAsync("scanner", request);
地址格式参考
Modbus
支持常见 5 位地址和前缀地址:
| 地址示例 | 含义 |
|---|---|
00001 |
线圈区 Coil,内部地址 0。 |
10001 |
离散输入 DiscreteInput,内部地址 0。 |
30001 |
输入寄存器 InputRegister,内部地址 0。 |
40001 |
保持寄存器 HoldingRegister,内部地址 0。 |
Coil:0、C:0、C0 |
线圈区,内部地址 0。 |
DI:0、X0 |
离散输入区,内部地址 0。 |
IR:0、I0 |
输入寄存器区,内部地址 0。 |
HR:0、D0 |
保持寄存器区,内部地址 0。 |
bool coil = await service.ReadAsync<bool>("modbus", "C0");
short register = await service.ReadAsync<short>("modbus", "40001");
Siemens S7
支持 DB、输入、输出、M 区、V 区、L 区、外设、定时器、计数器等绝对地址。
| 地址示例 | 含义 |
|---|---|
DB1.DBX0.0 |
DB1 第 0 字节第 0 位。 |
DB1.DBB0 |
DB1 第 0 字节。 |
DB1.DBW0 |
DB1 从第 0 字节开始的 Word。 |
DB1.DBD0 |
DB1 从第 0 字节开始的 DWord。 |
DB1.DBL0 |
DB1 从第 0 字节开始的 LWord。 |
I0.0、IB0、IW0、ID0 |
输入区地址。 |
Q0.0、QB0、QW0、QD0 |
输出区地址。 |
M0.0、MB0、MW0、MD0 |
M 区地址。 |
PIW0、PQW0 |
外设输入/输出地址。 |
T0、C0 |
定时器、计数器。 |
S7-1200 / S7-1500 使用 DB1.DBW0 这类绝对地址访问时,PLC 侧通常需要允许 PUT/GET 通讯,并且对应 DB 需要关闭优化访问。
Mitsubishi MC
支持常见三菱软元件地址:
| 地址示例 | 说明 |
|---|---|
X0、Y0 |
X/Y 位设备,编号按十六进制解析。 |
M100、L100、B100 |
位设备。 |
D100、R100、W100、ZR100 |
字设备。 |
D100.0 |
字设备中的 bit,bit 范围 0 - 15。 |
TN0、CN0 |
定时器/计数器当前值。 |
short value = await service.ReadAsync<short>("mc", "D100");
bool bit = await service.ReadAsync<bool>("mc", "M100");
Omron FINS
支持 Omron 常见区:
| 地址示例 | 说明 |
|---|---|
D0、DM0 |
DM 区字地址。 |
CIO0 |
CIO 区字地址。 |
W0、WR0 |
WR 区。 |
H0、HR0 |
HR 区。 |
A0、AR0 |
AR 区。 |
E0、EM0 |
EM 区。 |
D0.00、CIO0.01 |
bit 地址,bit 范围 0 - 15。 |
short value = await service.ReadAsync<short>("omron", "D0");
bool bit = await service.ReadAsync<bool>("omron", "CIO0.00");
汇川 PLC
InovanceModbusTcp、InovanceModbusRtu、InovanceEthernet 使用汇川软元件地址映射。
| 地址示例 | 说明 |
|---|---|
D0 |
D 区寄存器。 |
SD0 |
特殊寄存器,通常只读。 |
R0 |
R 区寄存器。 |
T0、C0 |
定时器、计数器。 |
M0、SM0、S0、B0 |
位软元件。 |
X0、Y0 |
输入/输出位软元件,X/Y 编号按八进制解析。 |
D0X、D0Z、D0XZ |
地址后缀用于调整多寄存器数据字节序。 |
short value = await service.ReadAsync<short>("inovance", "D0");
bool input = await service.ReadAsync<bool>("inovance", "X0");
float floatValue = await service.ReadAsync<float>("inovance", "D10Z");
数据类型参考
服务层可解析的点位数据类型:
| 配置值 | .NET 类型 |
|---|---|
bool、boolean、System.Boolean |
bool |
short、int16、System.Int16 |
short |
ushort、uint16、System.UInt16 |
ushort |
int、int32、System.Int32 |
int |
uint、uint32、System.UInt32 |
uint |
float、single、System.Single |
float |
double、System.Double |
double |
string、System.String |
string |
ReadAsync(string, string, Type) 和 ReadTagAsync<T> 会使用这些类型做读取或转换。WriteAsync(object) 和 WriteTagAsync(object) 支持写入 bool、short、ushort、int、uint、float、double、string。
连续数组读写建议用于 bool 和数值类型;字符串建议使用单点字符串读写,或用 ReadBlockAsync / WriteBlockAsync 按字节处理。
常见异常与处理建议
| 异常 | 常见原因 | 建议 |
|---|---|---|
InvalidOperationException |
未调用 InitializeAsync、配置无效、点位不可读/不可写、读取点位质量非 Good |
先检查初始化流程和配置;批量读取时检查 TagItem.Quality。 |
KeyNotFoundException |
连接名或点位名不存在 | 检查 Name、ConnectionName、tagName 是否一致,注意启用状态。 |
NotSupportedException |
协议未接入服务层,或协议不支持当前能力 | 查看能力矩阵,工业读写用 PLC 协议,原始字节收发用 Tcp / SerialPort。 |
ArgumentException / FormatException |
地址格式、数据类型、枚举值无效 | 对照地址格式和数据类型表修正。 |
TimeoutException 或底层通讯异常 |
网络、串口、设备、站号、地址或超时参数异常 | 检查物理连接、端口、站号、PLC 通讯权限和超时设置。 |
推荐在业务入口统一订阅事件,并在关键读写点捕获异常:
try
{
var values = await service.BatchReadTagsAsync(
new[] { "RunState", "Speed", "Temperature" });
foreach (var value in values)
{
if (value.Quality != TagQuality.Good)
{
Console.WriteLine(
$"{value.Name} read failed: {value.Quality} {value.ErrorMessage}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Communication operation failed: {ex.Message}");
}
推荐使用方式
- 设备连接少、地址固定时,可以直接使用
ReadAsync<T>/WriteAsync<T>。 - 业务系统长期运行、点位多、需要给用户配置时,建议使用
Tags,通过ReadTagAsync、BatchReadTagsAsync、WriteTagAsync、BatchWriteTagsAsync调用。 - 连续区域读写使用
ReadArrayAsync<T>/WriteArrayAsync<T>,比逐点读取更适合连续寄存器或 DB 块。 - 不连续点位读取使用
BatchReadAsync或BatchReadTagsAsync。 Tcp和SerialPort用于原始字节通讯,不使用工业地址读写方法。- 生产代码中建议使用
try/finally确保断开连接,或使用await using确保释放底层资源。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- System.IO.Ports (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.