Mbus 0.9.1

dotnet add package Mbus --version 0.9.1
                    
NuGet\Install-Package Mbus -Version 0.9.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="Mbus" Version="0.9.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Mbus" Version="0.9.1" />
                    
Directory.Packages.props
<PackageReference Include="Mbus" />
                    
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 Mbus --version 0.9.1
                    
#r "nuget: Mbus, 0.9.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 Mbus@0.9.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=Mbus&version=0.9.1
                    
Install as a Cake Addin
#tool nuget:?package=Mbus&version=0.9.1
                    
Install as a Cake Tool

mbus

.NET 的 Modbus 采集库(主站 + 从站模拟器)。目标 netstandard2.0(.NET Framework 4.6.2+)与 net8.0——老工控机上的 .NET Framework 项目和现代 .NET 项目都能直接引用。两层设计:

  • 底层:裸寄存器读写,等价 NModbus 等传统库的用法——一个对象直连从站,读写寄存器/线圈
  • 上层:点表(Tag)驱动的业务闭环——注册点位(名称/地址/类型/字序/换算),自动轮询、缓存、变化事件、按点名写回

特性一览:Modbus TCP / RTU over TCP / 串口 RTU · 11 个功能码(0x01-0x06 / 0x0F / 0x10 / 0x16 / 0x17 / 0x2B 设备识别)· 四种字序(ABCD/CDAB/BADC/DCBA)· Scale/Offset 工程量换算 · 寄存器位映射 · 多周期轮询组 · 相邻点自动合并读 · 断线自动重连 + 质量标记(Good/LastKnown/Bad)· JSON 点表导入(加载期校验:地址冲突/越界/类型匹配)· 规则报警引擎(点名 + 表达式,边沿触发/恢复,质量冻结,恢复表达式滞环,限值报警 WithHigh/WithLow 点表直配)· 变化死区(轮询降噪,基准=上次上报值)· 虚拟点(表达式计算点,级联/质量传播)· 内置从站模拟器(内存虚拟总线 + TCP 从站服务器)· WinForms/WPF 友好(MbusUi.Capture() 事件封送)

包结构与引入

项目 内容
src/Mbus 核心包:TCP / RTU over TCP / 虚拟总线 / JSON 点表导入 / 规则引擎(net8.0 零第三方依赖;ns2.0 目标的 JSON 支持由 System.Text.Json NuGet 包提供)
src/Mbus.Serial 串口(RTU)扩展,依赖 System.IO.Ports
demo/Mbus.Demo WinForms 演示程序(net8.0-windows)

安装(NuGet 包 Mbus / Mbus.Serial):

dotnet add package Mbus            # 核心包:TCP / RTU over TCP / 虚拟总线 / 从站模拟器
dotnet add package Mbus.Serial     # 串口设备再加这个

或以源码引用:

<ItemGroup>
  <ProjectReference Include="path\to\mbus\src\Mbus\Mbus.csproj" />
  
  <ProjectReference Include="path\to\mbus\src\Mbus.Serial\Mbus.Serial.csproj" />
</ItemGroup>
dotnet test Mbus.sln   # 165 个测试,含虚拟从站上的全链路端到端

快速上手(底层用法)

var dev = new MbusDeviceBuilder("plc-01")
    .Tcp("192.168.1.10", 502)
    .Unit(1)
    .Build();

await dev.ConnectAsync();

var regs = await dev.ReadHoldingRegistersAsync(0, 10);   // 功能码 0x03
await dev.WriteSingleRegisterAsync(0, 1234);             // 功能码 0x06
var coils = await dev.ReadCoilsAsync(0, 16);             // 功能码 0x01

// 扩展功能码(部分精简设备不支持,会返回 IllegalFunction)
await dev.MaskWriteRegisterAsync(0, 0xFFF7, 0x0008);             // 0x16 掩码写:单事务原子置位 bit3(and=~bit 给 or 让路)
var after = await dev.ReadWriteMultipleRegistersAsync(9, 1, 9, [0x1234]);  // 0x17 先写后读:写设定值并回读,一个往返
var id = await dev.ReadDeviceIdentificationAsync();               // 0x2B 设备识别(自动处理分片续传)
Console.WriteLine($"{id.VendorName} {id.ProductName} v{id.MajorMinorRevision}");

业务闭环(上层用法)

await using var client = new MbusClient();

var dev = client.Device("plc-01", d => d
    .Tcp("192.168.1.10")
    .Unit(1)
    .DefaultWordOrder(WordOrder.CDAB)                          // 这台设备整机 CDAB
    .Register("泵.运行",   DataType.Bool, 0, Area.Coil)
    .Register("电机.频率", DataType.F32,  1, o => o.WithReadOnly())
    .Register("电机.电流", DataType.F32,  3, o => o.WithScale(0.1))
    .Register("报警.过热", DataType.Bool, 5, o => o.WithBit(2))        // 寄存器第 2 位
    .Register("统计.累计", DataType.U32,  6, o => o.WithWordOrder(WordOrder.ABCD))  // 单点例外
    .Register("统计.名称", DataType.String, 8, o => o.WithLength(6)));

client.TagChanged     += (_, e) => Console.WriteLine($"{e.Device.Id}/{e.Tag.Name} = {e.NewValue.Value} ({e.NewValue.Quality})");
client.LinkStateChanged += (_, e) => Console.WriteLine($"[{e.Link.Id}] {e.State} {e.Message}");

dev.Poll(100, "电机.*", "泵.*")      // 快组:100ms
   .Poll(2000, "统计.*", "报警.*");  // 慢组:2s

await client.StartAsync();

await dev.WriteAsync("泵.运行", true);        // 按点名写回:工程值反算、字序编码、单事务原子写
var v = client["plc-01.电机.频率"];           // 跨设备一跳取缓存,断线返回旧值 + LastKnown

JSON 点表导入

点表让工程师按设备手册填文件、不写代码;连接参数(IP/串口)留在代码里——连接常因环境而异,语义配置才值得外置。

var dev = client.Device("plc-01", d => d
    .Tcp("192.168.1.10")
    .Unit(1)
    .LoadTagsFromFile("plc-01.json"));

// 或:从字符串加载 / 一份点表应用到多台同型号设备
var table = MbusTagTable.FromFile("meter.json");
d1.LoadTags(table);
d2.LoadTags(table);

plc-01.json(支持注释与尾逗号,枚举值不区分大小写):

{
  // 按设备手册逐行填
  "tags": [
    { "name": "泵.运行",   "type": "Bool",   "address": 0, "area": "coil" },
    { "name": "电机.频率", "type": "F32",    "address": 1, "readOnly": true },
    { "name": "电机.电流", "type": "F32",    "address": 3, "scale": 0.1 },
    { "name": "报警.过热", "type": "Bool",   "address": 5, "bitIndex": 2 },
    { "name": "统计.累计", "type": "U32",    "address": 6, "wordOrder": "ABCD" },
    { "name": "统计.名称", "type": "String", "address": 8, "length": 6, "category": "统计" }
  ]
}

字段与 Register/WithXxx 一一对应:name / type / address 必填;area(默认 holding)、wordOrderscaleoffsetreadOnlylengthbitIndexcategorydeadbandhigh/low/hysteresis(限值报警)可选。导入与代码注册走同一通道、同一校验。

加载期校验一次报全(文件内的全部错误聚合为一条异常;与代码已注册点位的冲突在应用时立即抛出):

Mbus.ModbusException: JSON 点表校验失败,共 2 处:
- 第 1 项(电机.电压):未知类型 X32。
- 第 2 项(统计.累计):与点 电机.频率 地址重叠:Holding 区寄存器 2 已被占用。

地址冲突校验对代码注册同样生效:同区内寄存器区间不得相交;Bool + bitIndex 可共用寄存器但位号不得重复;整寄存器 Bool 与位点互斥(写回 0x0000/0x0001 会清掉其他位);地址 + 占用寄存器数超出 0-65535 拒绝。

规则与报警

点名 + 表达式构成规则,电机.温度 > 50 就是完整的一条:

dev.Rule("过热", "电机.温度 > 50")                      // 单参数
   .Rule("过流且泵停", "电机.电流 > 10 && !泵.运行")     // 多参数复合
   .Rule("温差大", "(电机.温度 - 环境.温度) * 2 >= 30")  // 算术与括号
   .Rule("过温", "电机.温度 > 50", "电机.温度 < 48")     // 滞环:恢复表达式,阈值附近抖动不闪报警
   .Rule("overheat", "device.temp > 50");               // 英文点名同样适用

client.RuleTriggered += (_, e) => logger.LogWarning("告警 {Device}.{Rule} 触发,由 {Tag} 引起:{Values}",
    e.Device.Id, e.Rule.Name, e.TriggeringTag.Name,
    string.Join(", ", e.Values.Select(p => $"{p.Key}={p.Value.Value}")));
client.RuleReset     += (_, e) => logger.LogInformation("告警 {Device}.{Rule} 恢复", e.Device.Id, e.Rule.Name);
  • 表达式:点名(可含 .,中文(电机.温度)与英文(device.temp)皆可,整体一个词,匹配忽略大小写)+ C 风格运算符 > >= < <= == != && || ! + - * / ( ),数字 / true / false / "字符串" 字面量;含 - 等其他符号的点名不能在表达式中引用(注册不受影响)
  • 绑定期静态类型检查:引用不存在的点、类型不匹配(布尔参与算术、字符串比大小)在 Rule() 时立即抛出,错误位置精确到字符
  • 边沿语义:条件由假变真触发一次 RuleTriggered,由真变假触发一次 RuleResetrule.IsTriggered 可查当前态,首轮采集即为真也算上升沿
  • 滞环(恢复表达式):第三个参数给恢复条件后,触发态只看恢复条件、成立才恢复——(">50", "<48") 时温度在 49↔51 抖动既不再触发也不假恢复;两个表达式都做绑定期检查,可引用不同点位
  • 质量冻结:任一引用点非 Good 质量时不评估——断线旧值既不触发报警,也不假恢复
  • 事件携带触发时刻全部引用点的值快照(e.Values,含质量与时间戳),报警消息不用再回头查值
  • 规则引用的点应加入 Poll,否则始终无值不评估

限值报警:两个参数的就进点表

手册里写明的限值("报警温度 80℃")是设备域知识,直接进点表,不用写表达式:

.Register("电机.温度", DataType.F32, 0x2010, o => o
    .WithScale(0.1)
    .WithHigh(80, hysteresis: 2)   // 值 > 80 触发"电机.温度 High",≤ 78 恢复
    .WithLow(-10))                 // 值 < -10 触发"电机.温度 Low",≥ -10 恢复(默认零滞环)

底层合成为规则(Rule("电机.温度 High", "电机.温度 > 80", "电机.温度 <= 78")),走同一注册通道——边沿、滞环、质量冻结、值快照全部继承,事件照常从 RuleTriggered/RuleReset 出,rule.IsTagLimit 可区分手写/合成。仅数值点;同设 High/Low 时须 High 阈值 > Low 阈值;复合条件("过流且泵停")仍用 Rule。JSON 字段:high / low / hysteresis(同时作用于两者)。

虚拟点(计算点)

物理点不够用时,表达式造点——温差 = 进气 - 出气 一行注册,缓存、事件、规则引用与物理点同一表面:

var dev = client.Device("plc-01", d => d
    .Register("设备.进气温度", DataType.F32, 0)
    .Register("设备.出气温度", DataType.F32, 2)
    .RegisterComputed("设备.进气温差", "设备.进气温度 - 设备.出气温度")
    .RegisterComputed("设备.温差指数", "设备.进气温差 * 2"));   // 虚拟点可引用虚拟点

dev.Rule("温差超限", "设备.进气温差 > 30");   // 规则引用虚拟点,照常工作
var v = client["plc-01.设备.进气温差"];        // 缓存直读,质量/时间戳齐全
  • 表达式语法与规则引擎相同(点名 + C 风格运算符),只能引用已注册的点——先注册者引用不了后注册者,循环引用在注册时天然不可能
  • 类型由表达式推断:算术 → F64(double)、比较/逻辑 → Bool、字符串 → String
  • 求值时机:任一引用点的值或质量变化即重算(链式虚拟点逐级传播),事件 TagChangedReason = Computed
  • 质量冻结(与规则一致):任一引用点非 Good 时冻结旧值、传播最差质量,全部恢复 Good 才重算;引用点尚无读数时不求值
  • 时间戳取引用点最新采集时刻
  • 虚拟点不参与 Poll、直读与写回(显式抛错);ReadAllAsync 自动跳过虚拟点

WinForms / WPF 使用

全部 I/O 异步、轮询在后台线程,await 即用,不会卡 UI 线程。两个要点:

public partial class MainForm : Form
{
    private readonly MbusClient _client = new MbusClient();

    private async void MainForm_Load(object? sender, EventArgs e)
    {
        var ui = MbusUi.Capture();   // 在 UI 线程调用一次,捕获上下文

        var dev = _client.Device("plc-01", d => d.Tcp("192.168.1.10").Register("电机.频率", DataType.F32, 1));
        // 事件在后台线程触发——经 ui() 封送后可直接摸控件
        dev.TagChanged += (_, e) => ui(() => labelFreq.Text = $"{e.NewValue.Value:F1}");

        dev.Poll(100, "电机.频率");
        await _client.StartAsync();
    }

    private void timer1_Tick(object? sender, EventArgs e)
        => labelFreq.Text = _client["plc-01.电机.频率"].Value?.ToString();   // 缓存直读,同步即取、断线不抛

    private async void MainForm_FormClosed(object? sender, FormClosedEventArgs e)
        => await _client.DisposeAsync();     // 释放串口/TCP——忘了这步,进程活着就一直占口
}
  • 缓存索引器 dev[...] / client[...] 是纯字典读,UI 定时器里直读即可
  • SerialPort.Open() 本身是同步的(驱动初始化偶尔几十毫秒),敏感场景用 Task.Run(() => client.StartAsync()) 包一层

事件与日志出口

库不做日志,但把钩子收在一个地方:容器场景只订阅 client 一个对象的九个事件,连接/设备/报文/写回/报警全覆盖,运行中新增的连接和设备自动纳入:

client.Trace            += (_, e) => logger.LogDebug("{Link} {Dir} unit={Unit} {Frame}",
                               e.Link!.Id, e.Direction, e.UnitId, BitConverter.ToString(e.Frame.Span));
client.TagChanged       += (_, e) => logger.LogInformation("{Device}.{Tag} = {Value} ({Quality})",
                               e.Device.Id, e.Tag.Name, e.NewValue.Value, e.NewValue.Quality);
client.LinkStateChanged += (_, e) => e.State == LinkState.Connected
                               ? logger.LogInformation("连接 {Link} 已恢复", e.Link.Id)
                               : logger.LogError("连接 {Link} {State}: {Msg}", e.Link.Id, e.State, e.Message);
client.DeviceStateChanged += (_, e) => logger.LogWarning("设备 {Device} {State}: {Msg}", e.Device.Id, e.State, e.Message);
client.PollFailed       += (_, e) => logger.LogWarning(e.Exception, "轮询失败 {Device} @{Period}ms", e.Device.Id, e.PeriodMs);
client.WriteCompleted   += (_, e) => e.Success
                               ? logger.LogInformation("写回 {Device}.{Tag} = {Value}", e.Device.Id, e.Tag.Name, e.Value)
                               : logger.LogError(e.Exception, "写回失败 {Device}.{Tag}", e.Device.Id, e.Tag.Name);
client.RuleTriggered    += (_, e) => logger.LogWarning("告警触发 {Device}.{Rule}({Expr}):{Values}",
                               e.Device.Id, e.Rule.Name, e.Rule.Expression,
                               string.Join(", ", e.Values.Select(p => $"{p.Key}={p.Value.Value}")));
client.RuleReset        += (_, e) => logger.LogInformation("告警恢复 {Device}.{Rule}", e.Device.Id, e.Rule.Name);
事件 内容 日志级别参考
Trace 全部连接的原始报文(聚合自各连接,带 Link 上下文) Debug
TagChanged 值/质量变化(新旧值、时间戳、来源) Info
LinkStateChanged 连接通断(含掉线原因) 断开 Error / 恢复 Info
DeviceStateChanged unit 级健康(不应答 = Offline) Warn
PollFailed 轮询失败(不中断,自动重试) Warn
PollReceived 每轮成功采集的原始采样帧(值不变也触发,不受死区影响;趋势曲线/落库数据源,非日志用途)
WriteCompleted 写回审计(成功/失败,失败带异常) 成功 Info / 失败 Error
RuleTriggered 规则触发(表达式由假变真,带引用点值快照) Warn
RuleReset 规则恢复(表达式由真变假) Info

单设备直用(不经容器)时订阅 device 上的同名事件,原始报文订阅 device.Link.Master.Trace。 连接级故障由库内部消化为掉线状态,统一从 LinkStateChanged 出来,不需要另订底层故障事件。Trace 无订阅者零开销。

演示程序(WinForms)

demo/Mbus.Demo 开箱即看:本机起 MbusTcpServer 模拟从站(正弦波频率/电流/温度、累计计数、泵状态翻转),主站走真实 TCP 回环轮询,表格实时刷值,可写回设定值与线圈。

dotnet run --project demo/Mbus.Demo                  # 打开界面,数据实时在动
dotnet run --project demo/Mbus.Demo -- --self-test   # 无界面自检(10 项断言,退出码 0/1)
  • 内置 4 条演示规则(过流 电机.电流 > 10 && 泵.运行、过温、设定超限、泵故障),触发与恢复实时打印在底部告警日志(含触发时刻的值快照)
  • 写回 频率设定(> 50 时)后模拟对象的频率会向新设定值靠拢(写回闭环效果)
  • 写回 泵.运行 后保持 15 秒,然后恢复自动翻转
  • 断掉从站(停止后重启)可观察质量标记:良好 → 旧值/异常

本地真机环境(TCP 从站服务器)

上真设备前,最后一段可以在本机完成——MbusTcpServer 起一个真 socket 从站,客户端走真实 TCP 回环(真实的分包、缓冲、断连语义,不是内存管道):

// 模拟一台 Modbus TCP 设备
var memory = new MbusSlaveMemory();
var bits = BitConverter.SingleToInt32Bits(50.5f);
memory.SetHolding(0, (ushort)(bits >> 16), (ushort)bits);
await using var server = new MbusTcpServer(502, MbusWire.Tcp, new MbusSlaveResponder(1, MbusWire.Tcp, memory));
await server.StartAsync();

// 模拟串口服务器(RTU over TCP 透传)—— wire 传 Rtu 即可
// new MbusTcpServer(port, MbusWire.Rtu, new MbusSlaveResponder(1, MbusWire.Rtu, memory))

一条服务器上可挂多个从站(多 unit),多个主站可并发连接。测试里用它验证过:全链路读写、RTU 透传 CRC、服务器重启后客户端自动重连恢复、双主站并发。

真机验收清单(拿到设备后 10 分钟)

  1. new MbusDeviceBuilder("x").Tcp(ip).Build() + ReadHoldingRegistersAsync(0, 4) —— 通不通、事务号/CRC 是否如预期(开 Trace 看帧)
  2. 注册 2-3 个点跑 Poll(100) —— 值是否合理(温度不可能是 1e38 → 字序错了,试 DefaultWordOrder(CDAB)
  3. WriteAsync 写一个设定值 —— 从设备面板/其他工具核对落值
  4. 拔网线 10 秒再插 —— 状态事件、LastKnown → 重连 → Good 全链路走一遍
  5. 串口设备:换 Serial("COM3", 9600),其余代码不变

一条总线多台从站(485 / 网关)

// 串口总线(Mbus.Serial 包)或 TCP 网关后面挂多台 unit,共享一条连接
var bus = client.Link("bus485", l => l.Serial("COM3", 9600).Reconnect(5000));

client.Device("meter-01", d => d.On(bus).Unit(1).Register("流量.瞬时", DataType.F32, 0, o => o.WithScale(0.01)));
client.Device("meter-02", d => d.On(bus).Unit(2).Register("流量.瞬时", DataType.F32, 0, o => o.WithScale(0.01)));
// 同一连接上的事务自动串行排队——半双工总线的硬要求

// 网关走 RTU 透传(而非 Modbus TCP)时,把 Tcp 换成 RtuOverTcp,其余不变:
var gw = client.Link("gw", l => l.RtuOverTcp("192.168.1.20").Reconnect(5000));

无硬件开发 / 自动化测试

var slave = new MbusSlaveResponder(1, MbusWire.Tcp);
slave.Memory.SetHolding(0, 0x1234);

var client = new MbusClient();
var link = client.Link("bus", l => l.Virtual(MbusWire.Tcp, slave));  // 内存总线,可不带硬件

分层

L4  MbusClient / MbusDevice   容器、点表、缓存、事件、轮询调度
L3  ValueCodec / ReadPlanner  类型编解码(字序/scale/bit/字符串)、读合并规划
L2  MbusMaster                串行事务队列、超时重试、全功能码
L1  MbusFrame / Crc16         MBAP / RTU 帧、CRC16
L0  ITransport                TCP / 串口(Mbus.Serial) / 虚拟管道

默认值一览

约定优于配置,但约定必须可见。所有默认值的单一事实来源是 MbusDefaults, 运行时也可直接读取生效配置:link.Master.TimeoutMslink.ReconnectIntervalMsdevice.DefaultWordOrdertag.PollPeriodMs

设置 默认值 在哪改
Modbus TCP 端口 502 Tcp(host, port)
TCP 建连超时 3000 ms TcpTransport 构造参数
应答超时 1000 ms LinkBuilder.Timeout(ms)
超时重试 2 次 LinkBuilder.Retries(n)
掉线重连间隔 3000 ms(≤0 关闭) LinkBuilder.Reconnect(ms)
从站地址 1 DeviceBuilder.Unit(id)
数据区 Holding Register(..., area)
字序 ABCD(协议大端) 设备级 DefaultWordOrder,点级 WithWordOrder 覆盖
缩放/偏移 WithScale / WithOffset
轮询周期下限 10 ms 协议库硬限制
读合并间隔 ≤8 个寄存器的空洞并入同一次读 MbusDefaults.MaxReadGap
单次读上限 125 寄存器 / 2000 位 Modbus 协议规范,不可配置
串口 9600-8-N-1 Serial(port, baud, dataBits, parity, stopBits)

不可配置的行为约定:

  • 地址一律 0 基(40001 风格需自行换算)
  • 名称含 . 时前缀为类别;点名匹配忽略大小写
  • 带 Scale/Offset 的点读出统一为 double(整数类型换算后取整会丢尾数)
  • 字符串点位:ASCII、每寄存器 2 字符高位在前、读出时去尾部 \0
  • Bool 挂寄存器区不带 WithBit = 整个寄存器非 0 即真;Input/Discrete 区强制只读
  • 注册期地址校验:同区内寄存器区间不得相交;Bool+BitIndex 共用寄存器时位号不得重复;地址 + 占用寄存器数超出 0-65535 拒绝
  • JSON 点表字段默认值:area = holding、readOnly = false、length = 2,其余可选字段默认同 Register

已知语义

  • 32 位以上值写回单事务完成,不会半写;寄存器位写走读-改-写(同一连接内串行,跨设备同地址写有理论竞态)
  • 断线后缓存返回旧值 + Quality.LastKnown;超时/异常应答 → Quality.Bad;轮询自动重试
  • TCP 迟到的旧事务应答会被跳过;RTU 无事务号,超时重试后旧应答无法区分(协议本身的限制)
  • 主动 Link.DisconnectAsync() 视为"人为断开",不自动重连;连接级故障(对端断开)自动重连
  • JSON 点表文件内的错误聚合为一条异常报出;与代码已注册点位的冲突在 LoadTags/ApplyTo 时立即抛出
  • 规则数值统一按 double 比较(U64 超过 2^53 精度受限);字符串 ==/!= 区分大小写
  • 规则在轮询线程的 TagChanged 链内同步评估,事件处理应保持轻量(与其他事件约定一致,UI 场景经 MbusUi.Capture() 封送)
  • 撕裂语义:同一次轮询内各点位逐个更新、规则逐点即时评估——跨点位的组合条件可能短暂看到"新值 + 旧值"的混合快照;对瞬时组合敏感的条件可先聚合到虚拟点再比较
  • 变化死区WithDeadband,仅数值点):轮询值与上次上报值之差小于死区视为未变化(不更新缓存、不发事件,慢漂移可累积越过阈值);直读、写回、质量变化不受死区影响

路线图

  • TCP 从站服务器(已完成,MbusTcpServer)
  • NuGet 打包(已完成:Mbus / Mbus.Serial,netstandard2.0 + net8.0 双目标)
  • JSON 点表配置 + 加载期校验(已完成:LoadTagsFromFile / MbusTagTable,地址冲突/越界/类型与区域匹配,错误聚合报告)
  • 报警限(AlarmLow/AlarmHigh)(由规则引擎覆盖并超出:Rule("过热", "电机.温度 > 50"),多参数表达式、边沿触发/恢复、质量冻结)
  • 真机验证矩阵(Modbus TCP PLC / 串口仪表 / 串口服务器透传)

许可证

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Mbus:

Package Downloads
Mbus.Serial

Mbus 的串口(Modbus RTU)传输扩展。目标 netstandard2.0(.NET Framework 4.6.2+)与 net8.0。

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.1 58 9/10/2026
0.9.0 119 8/25/2026
0.8.0 102 8/25/2026
0.7.0 105 8/25/2026
0.6.0 116 8/24/2026
0.5.0 110 8/24/2026
0.4.0 111 8/24/2026
0.3.0 107 8/24/2026
0.2.0 103 8/21/2026
0.1.0 112 8/21/2026