BestChoice.Communication 1.0.1

dotnet add package BestChoice.Communication --version 1.0.1
                    
NuGet\Install-Package BestChoice.Communication -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BestChoice.Communication" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BestChoice.Communication" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="BestChoice.Communication" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BestChoice.Communication --version 1.0.1
                    
#r "nuget: BestChoice.Communication, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BestChoice.Communication@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BestChoice.Communication&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=BestChoice.Communication&version=1.0.1
                    
Install as a Cake Tool

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 是否启用该点位。

服务生命周期

推荐生命周期:

  1. 构造 CommunicationConnectionService。
  2. 订阅事件。
  3. 调用 InitializeAsync 初始化配置和底层客户端。
  4. 调用 ConnectAsync 或 ConnectAllAsync 建立连接。
  5. 调用读写方法。
  6. 调用 DisconnectAsync 或 DisconnectAllAsync 断开连接。
  7. 使用 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 151 6/5/2026
1.0.0 113 6/3/2026