PlcLibrary.Modbus
1.0.0
See the version list below for details.
dotnet add package PlcLibrary.Modbus --version 1.0.0
NuGet\Install-Package PlcLibrary.Modbus -Version 1.0.0
<PackageReference Include="PlcLibrary.Modbus" Version="1.0.0" />
<PackageVersion Include="PlcLibrary.Modbus" Version="1.0.0" />
<PackageReference Include="PlcLibrary.Modbus" />
paket add PlcLibrary.Modbus --version 1.0.0
#r "nuget: PlcLibrary.Modbus, 1.0.0"
#:package PlcLibrary.Modbus@1.0.0
#addin nuget:?package=PlcLibrary.Modbus&version=1.0.0
#tool nuget:?package=PlcLibrary.Modbus&version=1.0.0
PlcLibrary
PLC 数据采集库,提供连接池管理、定时采集调度、数据分发管道。
- 协议无关驱动接口,一行注册新协议
- 连接池 + Polly 弹性策略(重试、超时、断路器),每设备独立隔离,覆盖连接和 IO 两级
- 设备配置热更新,差量 reconcile
- Channel 管道 fan-out 到多个
IDataHandler - 主动读写 + 自动采集双模式
System.Diagnostics.Metrics内置可观测性,0 依赖接入 Prometheus / Grafana
支持的驱动
| 驱动 | 协议 | 状态 |
|---|---|---|
S7Driver |
Siemens S7 (S7-200/300/400/1200/1500) | 可用 |
ModbusTcpDriver |
Modbus TCP | 可用 |
ModbusUdpDriver |
Modbus UDP | 可用 |
OpcUaDriver |
OPC UA | 可用 |
MitsubishiDriver |
Mitsubishi MC / A1E / FX | 可用 |
OmronDriver |
Omron FINS TCP | 可用 |
AllenBradleyDriver |
Allen-Bradley Logix Tag (CIP/EtherNet/IP) | 可用 |
BacnetDriver |
BACnet/IP | 可用 |
ModbusRtuDriver |
Modbus RTU | 待 NModbus 更新 |
ModbusAsciiDriver |
Modbus ASCII | 待 NModbus 更新 |
安装
dotnet add package PlcLibrary
按需添加驱动包:
dotnet add package PlcLibrary.S7
dotnet add package PlcLibrary.Modbus
dotnet add package PlcLibrary.OpcUa
dotnet add package PlcLibrary.Mitsubishi
dotnet add package PlcLibrary.Omron
dotnet add package PlcLibrary.AllenBradley
dotnet add package PlcLibrary.Bacnet
快速开始
using Microsoft.Extensions.Logging;
using PlcLibrary.Controller.Interfaces;
using PlcLibrary.DriverDomain.Models;
using PlcLibrary.Extensions;
using PlcLibrary.General.Configuration;
using PlcLibrary.Pipeline.Interfaces;
using PlcLibrary.S7;
var builder = Host.CreateApplicationBuilder(args);
var devices = new[]
{
new DeviceConfiguration
{
Id = "plc-001",
Name = "1号线",
Protocol = "S7",
ConnectionString = "host:10.38.103.107;port:102;timeout:3000;rack:0;slot:0;cpu:S71200;",
TagPoints = new[]
{
new TagPointConfiguration { TagId = "t1", Address = "DB21.DBX10.2", DataType = "System.Boolean" },
new TagPointConfiguration { TagId = "t2", Address = "DB21.DBX10.0", DataType = "System.Boolean" },
},
CollectionInterval = TimeSpan.FromSeconds(1),
},
};
builder.Services
.AddPlcLibrary()
.AddDriver<S7Driver>()
.AddSingleton<IDataHandler, ConsoleHandler>();
var host = builder.Build();
await host.Services.GetRequiredService<IDeviceScheduler>().ApplyDevicesAsync(devices);
await host.RunAsync();
internal sealed class ConsoleHandler(ILogger<ConsoleHandler> logger) : IDataHandler
{
public ValueTask HandleAsync(DriverResult result, CancellationToken ct)
{
logger.LogInformation("[{DeviceId}] {Address} = {Value} ({Status})",
result.DeviceId, result.Address, result.Value, result.Status);
return ValueTask.CompletedTask;
}
}
设备多时可用
BackgroundService+IConfiguration从 appsettings.json 读取,参考下文 JSON 配置方式。
使用 JSON 配置
builder.Services.AddPlcLibrary();
builder.Services.Configure<PoolOptions>(builder.Configuration.GetSection("DriverPool"));
builder.Services.Configure<PipelineOptions>(builder.Configuration.GetSection("Pipeline"));
{
"Devices": [
{
"Enabled": true,
"Id": "plc-01",
"Protocol": "S7",
"ConnectionString": "host:192.168.1.1;port:102;rack:0;slot:1;cpu:S71200",
"CollectionInterval": "00:00:01",
"TagPoints": [
{ "TagId": "temp", "Address": "DB1.DBD0", "DataType": "Real" },
{ "TagId": "pressure", "Address": "DB1.DBD4", "DataType": "Real" }
]
}
]
}
internal sealed class DeviceLoader(IConfiguration config, IDeviceScheduler scheduler) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
var devices = config.GetSection("Devices").Get<DeviceConfiguration[]>();
if (devices is { Length: > 0 })
await scheduler.ApplyDevicesAsync(devices, ct);
}
}
连接字符串
格式 key:value;key:value,大小写不敏感。
S7
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | 127.0.0.1 | PLC 地址 |
| port | 102 | 端口 |
| rack | 0 | 机架 |
| slot | 0 | 插槽 |
| timeout | 3000 | 超时 (ms) |
| cpu | S71200 | CpuType |
示例:host:192.168.1.1;port:102;rack:0;slot:1;cpu:S71500
Modbus TCP / UDP
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | 127.0.0.1 | 设备地址 |
| port | 502 | 端口 |
| slaveid | 1 | 从站 ID |
| timeout | 3000 | 超时 (ms) |
示例:host:10.0.0.1;port:502;slaveid:2
Modbus 地址格式:使用 5 位十进制数字前缀标记数据类型:
| 前缀 | 类型 | 读写 | 示例 | 说明 |
|---|---|---|---|---|
0xxxx |
Coil | 读写 | 00001 |
线圈,布尔量 |
1xxxx |
Discrete Input | 只读 | 10042 |
离散输入,布尔量 |
3xxxx |
Input Register | 只读 | 30001 |
输入寄存器,16 位无符号整数 |
4xxxx |
Holding Register | 读写 | 40042 |
保持寄存器,16 位无符号整数 |
地址为 1-based(PLC 习惯),驱动内部自动转换为 0-based 偏移。连续地址自动合并为单次批量读写。
Modbus RTU / ASCII(待 NModbus 更新)
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | - | 串口号(COM3) |
| baudrate | 9600 | 波特率 |
| parity | None | 校验(None/Odd/Even) |
| databits | 8 | 数据位 |
| stopbits | One | 停止位(One/Two) |
| slaveid | 1 | 从站 ID |
示例:host:COM3;baudrate:19200;parity:Even;slaveid:1
OPC UA
| 字段 | 默认值 | 说明 |
|---|---|---|
| endpoint | opc.tcp://localhost:4840 | 服务器端点 |
| username | - | 用户名(可选) |
| password | - | 密码(可选) |
| security | None | None / Sign / SignAndEncrypt |
| timeout | 5000 | 超时 (ms) |
| publishinginterval | 1000 | 订阅发布间隔 (ms) |
| sessiontimeout | 60000 | 会话超时 (ms) |
| autoacceptcertificate | false | 自动接受证书(生产环境应设为 false) |
示例:endpoint:opc.tcp://10.0.0.1:4840;security:None;timeout:10000
Mitsubishi
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | 127.0.0.1 | PLC 地址 |
| port | 6000 | 端口 |
| timeout | 3000 | 超时 (ms) |
| protocoltype | MC | MC / A1E / FX |
示例:host:192.168.1.1;port:6000;protocoltype:MC
地址格式:三菱标准地址字符串,取决于所选协议类型:
| 协议类型 | 地址示例 | 说明 |
|---|---|---|
MC |
D100、M100、X10、Y20、W0 |
MELSEC MC 协议 |
A1E |
D100、M100、X0、Y0 |
A-1E 协议 |
FX |
D100、M100、X0、Y0 |
FX 编程口协议 |
Omron FINS
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | 127.0.0.1 | PLC 地址 |
| port | 9600 | 端口 |
| timeout | 3000 | 超时 (ms) |
| localnode | 1 | 本机 FINS 节点号 |
| destinynode | 2 | 目标 FINS 节点号 |
| isudp | false | 使用 UDP 传输 |
地址格式:Omron 标准 FINS 地址字符串。
| 区域 | 地址示例 | 说明 |
|---|---|---|
| DM | D100、D200 |
数据存储器 |
| CIO | CIO200、CIO200.5 |
通道 I/O(.5 表示位) |
| WR | W100 |
工作继电器 |
| HR | H0、H10 |
保持继电器 |
| AR | A0 |
辅助继电器 |
示例:host:192.168.1.1;port:9600;localnode:1;destinynode:2
Allen-Bradley / Rockwell
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | 127.0.0.1 | PLC / 网关模块地址 |
| port | 44818 | EtherNet/IP 端口 |
| timeout | 5000 | 超时 (ms) |
| path | - | 路由路径(如 1,0 表示背板槽位 0) |
| useconnected | false | Class 3 连接(高频轮询时推荐开启) |
地址格式:Logix 标签名。
| 地址示例 | 说明 |
|---|---|
rate |
基础标签(DINT/REAL/BOOL 等原子类型) |
counts[3] |
数组元素索引 |
Temp[10].AnotherArray[4] |
嵌套数组 |
MyUdt.enable |
UDT 结构体成员 |
matrix[1,2,3] |
多维数组 |
CompactLogix 示例:host:192.168.1.96
ControlLogix 示例:host:192.168.1.96;path:1,0
BACnet
| 字段 | 默认值 | 说明 |
|---|---|---|
| host | 127.0.0.1 | 目标设备 IP |
| port | 47808 | BACnet/IP 端口 (0xBAC0) |
| timeout | 5000 | 超时 (ms) |
| deviceinstance | 0 | 目标设备实例号(0 表示使用 IP 地址通信) |
| localendpointip | - | 多网卡时指定绑定 IP |
地址格式:TYPE:INSTANCE(如 AV:1 = Analog Value 1、BI:0 = Binary Input 0)。
支持类型:AI/AO/AV/BI/BO/BV/MI/MO/MV。
示例:host:192.168.1.50;port:47808;deviceinstance:12345
点位数据类型 (DataType)
部分驱动需要 TagPointConfiguration.DataType 指定点位的数据类型,以正确解析 PLC 返回值:
| 驱动 | DataType 是否必需 | 未指定时的默认值 | 说明 |
|---|---|---|---|
| S7 | 否 | 地址推断 | 地址格式已含类型(DBX=bool, DBW=word) |
| Modbus | 否 | 地址前缀推断 | 0xxxx=bool, 4xxxx=ushort |
| OPC UA | 否 | 服务端返回 | 服务端告知类型,值透传 |
| BACnet | 否 | 服务端返回 | PROP_PRESENT_VALUE 返回原始值 |
| AllenBradley | 推荐 | int |
用于确定 ReadAsync<T> 的泛型类型 |
| Mitsubishi | 推荐 | Int16 |
映射为 Snet DataType 枚举 |
| Omron | 推荐 | int |
分发到 ReadInt16Async/ReadFloatAsync 等方法 |
支持的 DataType 值(大小写不敏感):
| 简写 | 完整名称 | C# 类型 |
|---|---|---|
bool |
System.Boolean |
bool |
short |
System.Int16 |
short |
ushort |
System.UInt16 |
ushort |
int |
System.Int32 |
int |
uint |
System.UInt32 |
uint |
long |
System.Int64 |
long |
ulong |
System.UInt64 |
ulong |
float |
System.Single |
float |
double |
System.Double |
double |
string |
System.String |
string |
// Omron D100 为 32 位浮点数
new TagPointConfiguration { TagId = "temp", Address = "D100", DataType = "float" }
// AllenBradley tag 为布尔量
new TagPointConfiguration { TagId = "run", Address = "MotorRun", DataType = "bool" }
驱动池
{
"DriverPool": {
"MaxConnectionsPerDevice": 2,
"MaxRetryAttempts": 3,
"RetryDelay": "00:00:01",
"CircuitBreakerMinimumThroughput": 5,
"CircuitBreakerDuration": "00:00:30",
"CircuitBreakerFailureRatio": 0.5,
"OperationTimeout": "00:00:10"
}
}
管道
{
"Pipeline": {
"Capacity": 10000,
"MaxHandlerParallelism": 8,
"HandlerTimeout": "00:00:30"
}
}
弹性策略
Polly 弹性分为两级,每设备独立隔离:
| 级别 | 作用域 | 策略 | 触发位置 |
|---|---|---|---|
| 连接级 | ConnectAsync / TryReconnectAsync |
重试(指数退避) + 超时 + 断路器 | DeviceSharedPool.AcquireAsync |
| IO 级 | ReadAsync / WriteAsync |
重试(指数退避) + 超时 | DeviceDriverPool.ReadAsync / WriteAsync |
- 连接级断路器:连续失败达阈值后进入熔断(默认 5 次、失败率 ≥ 50%、冷却 30s),熔断/半开/恢复均有
ILogger日志。 - IO 级重试:每次
ReadAsync/WriteAsync失败时自动重试(默认 3 次),排除OperationCanceledException和TimeoutRejectedException。 - 两级策略均复用
DriverPool配置节中的MaxRetryAttempts、RetryDelay、OperationTimeout。
可观测性
PlcLibrary 通过 System.Diagnostics.Metrics(.NET 6+ 内置,零 NuGet 依赖)暴露以下指标:
Meter: PlcLibrary
| 指标名 | 类型 | 单位 | 说明 |
|---|---|---|---|
plc.reads.total |
Counter<long> |
points | 累计采集点位总数 |
plc.read.duration |
Histogram<double> |
s | ReadAsync 完整耗时(含 IO 重试) |
plc.read.errors |
Counter<long> |
errors | ReadAsync 最终失败次数(含重试耗尽) |
plc.write.total |
Counter<long> |
ops | 累计写操作数 |
plc.acquire.duration |
Histogram<double> |
s | 连接池获取驱动耗时(含 connect) |
plc.pipeline.dispatched |
Counter<long> |
points | 管道分发点数 |
plc.pipeline.dropped |
Counter<long> |
points | 订阅通道溢出丢弃点数 |
接入方式
OpenTelemetry Collector(推荐):
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore
builder.Services.AddOpenTelemetry()
.WithMetrics(m => m
.AddMeter("PlcLibrary")
.AddPrometheusExporter());
启动后访问 /metrics 即可被 Prometheus 抓取,Grafana 中查询 plc_read_duration_seconds_bucket 等指标。
dotnet-counters(本地调试):
dotnet-counters monitor -n MyApp --counters PlcLibrary
Grafana 面板示例
# 采集吞吐 (points/s)
rate(plc_reads_total[1m])
# 读延迟 p50 / p99
histogram_quantile(0.50, rate(plc_read_duration_seconds_bucket[1m]))
histogram_quantile(0.99, rate(plc_read_duration_seconds_bucket[1m]))
# 错误率
rate(plc_read_errors_total[1m]) / rate(plc_reads_total[1m])
# 通道丢弃率
rate(plc_pipeline_dropped_total[1m])
{
"Pipeline": {
"Capacity": 10000,
"MaxHandlerParallelism": 8,
"HandlerTimeout": "00:00:30"
}
}
API
核心接口
| 接口 | 说明 |
|---|---|
IDeviceScheduler |
推送设备配置,差量 reconcile;GetDeviceHealthAsync() 查询运行状态 |
IDataHandler |
接收采集推送 |
IDeviceAccessor |
主动读写设备 |
IProtocolDriver |
协议驱动实现(开发文档见 DEVELOPMENT.md) |
IDriverFactory |
驱动工厂(通常用 AddDriver<T> 替代) |
IDataPipeline |
数据管道(通常不需要直接使用) |
主动读写
public class MyService(IDeviceAccessor accessor)
{
public async Task ReadDevice(DeviceConfiguration device)
{
var values = await accessor.ReadAsync(device, device.TagPoints);
}
public async Task WriteDevice(DeviceConfiguration device)
{
var points = new Dictionary<TagPointConfiguration, object>
{
[device.TagPoints[0]] = 123.45
};
await accessor.WriteAsync(device, points);
}
}
设备级连接超时
DeviceConfiguration.ConnectionTimeout 可覆盖全局 PoolOptions.OperationTimeout,在获取驱动时优先使用设备级配置。默认 TimeSpan.Zero 表示使用全局配置。
断路器状态变更(熔断/半开/恢复)通过 ILogger 输出 Warning/Information 级别日志,每设备独立隔离。
健康状态
var scheduler = host.Services.GetRequiredService<IDeviceScheduler>();
var health = await scheduler.GetDeviceHealthAsync();
foreach (var d in health)
Console.WriteLine($"{d.DeviceId} [{d.Protocol}] {(d.IsRunning ? "OK" : d.Error)}");
返回 IReadOnlyList<DeviceHealthInfo>,每个设备包含 DeviceId、Protocol、IsRunning、Error 和 UpdatedAt。
致谢
本项目基于以下优秀的开源库构建:
| 库 | 用途 | GitHub |
|---|---|---|
| S7netplus | Siemens S7 通信 | github.com/S7NetPlus/s7netplus |
| NModbus | Modbus 协议 | github.com/NModbus/NModbus |
| OPCFoundation.NetStandard.Opc.Ua.Client | OPC UA 客户端 | github.com/OPCFoundation/UA-.NETStandard |
| Snet.Mitsubishi | 三菱 MC/A1E/FX 协议 | github.com/shunnet(组织) |
| NewLife.Omron | 欧姆龙 FINS/HostLink 协议 | github.com/NewLifeX/NewLife.Omron |
| EthernetIPSharp | Allen-Bradley EtherNet/IP | github.com/CristianMori/EthernetIpSharp |
| BACnet | BACnet 协议栈 | github.com/ela-compil/BACnet |
许可证
MIT
| 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
- NModbus (>= 3.0.83)
- PlcLibrary (>= 1.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.