LgEasyIot 1.5.0
dotnet add package LgEasyIot --version 1.5.0
NuGet\Install-Package LgEasyIot -Version 1.5.0
<PackageReference Include="LgEasyIot" Version="1.5.0" />
<PackageVersion Include="LgEasyIot" Version="1.5.0" />
<PackageReference Include="LgEasyIot" />
paket add LgEasyIot --version 1.5.0
#r "nuget: LgEasyIot, 1.5.0"
#:package LgEasyIot@1.5.0
#addin nuget:?package=LgEasyIot&version=1.5.0
#tool nuget:?package=LgEasyIot&version=1.5.0
LgEasyIot
轻量级工业通讯类库:Modbus(TCP/RTU/ASCII)、西门子 S7、三菱 Melsec MC(3E)、欧姆龙 FINS(TCP/UDP) 四种协议族的统一客户端 + 虚拟 PLC 服务器。
- 目标框架:.NET Framework 4.7.2 及以上(
net472)与 .NET 8.0 及以上(net8.0,可用于 .NET 8/9/10) - 全异步 API,统一返回
IotResult/IotResult<T>结果对象(错误不抛异常,携带错误码 + 报文 HEX) - 单一程序集
LgEasyIot.dll,net472 零第三方依赖;net8.0 仅依赖System.IO.Ports
| 协议 | 客户端 | 虚拟服务器 | 地址示例 |
|---|---|---|---|
| Modbus | TCP / RTU / ASCII | TCP | 100、x=4;100、s=1;x=3;10(线圈 x=0) |
| Siemens S7 | TCP (102) | ✓ | DB1.DBD0、DB1.DBX0.3、M100、I0.3、Q0.5 |
| Melsec MC | 3E 二进制 / ASCII | 二进制 | D100、M0、X0、W100、D100(16 进制可加前缀) |
| Omron FINS | TCP / UDP | TCP | DM100、CIO0、WR100、DM100.5 |
安装
dotnet add package LgEasyIot
<PackageReference Include="LgEasyIot" Version="1.0.0" />
Visual Studio 包管理器控制台:Install-Package LgEasyIot
net472 工程:直接引用即可(串口使用 .NET Framework 内置
System.IO.Ports,无需额外安装)。 net8.0 工程:会自动引入System.IO.Ports(NuGet 依赖),Linux 上串口需赋予/dev/tty*权限。
1. 快速开始
using LgEasyIot;
// 连一台 Modbus-TCP 设备(默认端口 502、站号 1)
using IPlcClient plc = Plc.Open(ProtocolKind.Modbus, "192.168.0.10");
// 读一个 int(占 2 个保持寄存器)
IotResult<int> temp = await plc.ReadAsync<int>("100");
// 写一个 float(字面量需显式泛型参数指定寄存器宽度)
IotResult write = await plc.WriteAsync<float>("200", 25.5f);
if (temp.IsSuccess)
Console.WriteLine($"温度 = {temp.Value}");
else
Console.WriteLine($"读取失败:{temp.Error}");
换 PLC 品牌只改创建入口,业务代码一行不动:
using IPlcClient s7 = Plc.Open(ProtocolKind.S7, "192.168.0.11"); // 西门子,默认机架 0 槽 2
using IPlcClient mc = Plc.Open(ProtocolKind.Melsec, "192.168.0.12"); // 三菱 MC 3E 二进制帧
using IPlcClient fins = Plc.Open(ProtocolKind.Fins, "192.168.0.13"); // 欧姆龙 FINS-TCP
2. 三种创建客户端的方式
// 方式一:枚举入口(编译期安全)
IPlcClient a = Plc.Open(ProtocolKind.Modbus, "192.168.0.10", 502);
// 方式二:命名工厂(参数带 IntelliSense 提示)
IPlcClient b = Plc.OpenModbusTcp("192.168.0.10", port: 502, station: 1);
IPlcClient c = Plc.OpenModbusRtu("COM3", baudRate: 9600, station: 1); // 串口 RTU
IPlcClient d = Plc.OpenS7("192.168.0.11", port: 102, destTsap: 0x0102); // S7-1200/1500 常用 0x0101/0x0201
IPlcClient e = Plc.OpenMelsec("192.168.0.12", port: 5000, protocolType: McProtocolType.Binary);
IPlcClient f = Plc.OpenFins("192.168.0.13", port: 9600);
// 方式三:连接字符串(适合放 appsettings.json / 数据库,换 PLC 不用改代码)
IPlcClient g = Plc.Open("modbus://192.168.0.10:502?station=1");
// modbus-rtu://COM3?baud=9600&station=1
// s7://192.168.0.11?destTsap=0x0201
// melsec://192.168.0.12:5000?type=ascii&network=1
// fins://192.168.0.13:9600 fins-udp://192.168.0.13:9600
| ProtocolKind | 说明 | 默认端口 |
|---|---|---|
Modbus |
Modbus-TCP(以太网) | 502 |
ModbusRtu |
Modbus-RTU(串口,host 填 COM 口名,port 填波特率) | 9600 |
ModbusAscii |
Modbus-ASCII(串口) | 9600 |
S7 |
西门子 S7 ISO-on-TCP | 102 |
Melsec |
三菱 MC 3E 帧 | 5000 |
Fins / FinsUdp |
欧姆龙 FINS-TCP / UDP | 9600 |
3. 统一读写 API(IPlcClient)
| 方法 | 说明 |
|---|---|
ReadAsync<T>(address) |
读单值:bool、short/ushort/int/uint/long/ulong/float/double |
ReadAsync<T>(address, count) |
读数组(元素个数;超过协议单次上限自动分包) |
ReadStringAsync(address, words) |
读字符串(读到 0x00 截止,不做 Trim) |
ReadRawAsync(address, words) |
读原始字节(words 为字数,1 字 = 2 字节) |
ReadBitsAsync(address, count) |
读位(线圈 / 位软元件) |
WriteAsync<T>(address, value) |
写值 / 写数组(单值与数组共用,由 T 区分) |
WriteAsync<float>(address, 25.5) |
字面量写入用显式泛型指定寄存器宽度(float=2 字,double=4 字) |
WriteStringAsync(address, value) |
写字符串(不足偶数字节自动补 0x00) |
WriteBitsAsync(address, bool[]) |
写位 |
CreateReadPlan() |
批量读取计划(见第 6 节) |
ConnectAsync() / CloseAsync() |
显式连接/断开(多数设备首次读写会自动连接) |
ByteOrder / ReceiveTimeoutMs / Logger / StringEncoding |
字节序 / 超时(ms) / 日志 / 字符串编码 |
// 类型化读写(所有协议同一套 API)
IotResult<float> v = await plc.ReadAsync<float>("DB1.DBD0"); // S7 数据块双整数→浮点
IotResult<short[]> arr = await plc.ReadAsync<short>("D100", 8); // 三菱 D100~D107(泛型参数为元素类型)
IotResult<bool[]> bits = await plc.ReadBitsAsync("M0", 16);
IotResult<string> sn = await plc.ReadStringAsync("D200", 20); // 条码等定长字符串
IotResult w1 = await plc.WriteAsync("D100", (short)1234);
IotResult w2 = await plc.WriteAsync("D110", new float[] { 1.1f, 2.2f, 3.3f });
IotResult w3 = await plc.WriteBitsAsync("M0", new[] { true, false, true });
IotResult w4 = await plc.WriteStringAsync("D200", "SN20260828001");
4. 结果对象与错误处理
库内所有操作返回 IotResult / IotResult<T>,失败不抛异常,错误信息集中在 IotError(错误码 + 描述 + 收发报文 HEX,排查现场问题一目了然):
IotResult<int> r = await plc.ReadAsync<int>("100");
if (r.IsSuccess) DoSomething(r.Value);
else Console.WriteLine($"{r.Error.CodeName}: {r.Error.Message}\n{r.Error}");
// 惯用写法
int v = (await plc.ReadAsync<int>("100")).ValueOr(0); // 失败给默认值
int w = (await plc.ReadAsync<int>("100")).Unwrap(); // 失败抛 PlcException
var (ok, value, error) = await plc.ReadAsync<int>("100"); // 解构
(await plc.WriteAsync("200", 1)).ThrowOnError(); // 写失败抛 PlcException
IPlcClient 实现 IDisposable:Dispose 后所有读写返回 ConnectionClosed 错误结果(不抛异常),线程安全且幂等。
5. 对象映射(DTO 整体读写)
用 [Address] 把 DTO 字段映射到 PLC 地址,一次调用读回整块点表:
public class MachineStatus
{
[Address("D100")] public short Code { get; set; }
[Address("D102")] public float Temperature { get; set; }
[Address("D104", Length = 4)] public float[] Pressures { get; set; } = new float[4];
[Address("D112", Length = 10)] public string Barcode { get; set; } = "";
[Address("M0")] public bool Running { get; set; }
}
// 整体读取(设备支持时走批量计划,一次执行覆盖全部字段)
IotResult<MachineStatus> st = await plc.ReadObjectAsync<MachineStatus>();
if (st.IsSuccess) Console.WriteLine(st.Value.Temperature);
// 整体写入
await plc.WriteObjectAsync(new MachineStatus { Code = 1, Temperature = 36.5f, ... });
DTO 必须是引用类型(class + 无参构造)。
6. 批量读取计划(ReadPlan,高频轮询首选)
固定点表高频轮询的核心优化:构建一次、反复执行——地址解析、分组、相邻地址合并、分包在构建期一次完成,执行期零重解析;单项失败不影响其他项:
ReadPlan plan = plc.CreateReadPlan()
.Add<float>("100") // 保持寄存器 100(2 字)
.Add<short>("104", 4) // 数组
.AddBits("0", 16) // 线圈区
.AddString("D200", 10) // 字符串
.Build(); // 支持批量计划的设备在此完成分组/合并/分包
while (running)
{
// 异步执行(经典姿势)
ReadPlanResult result = await plan.ExecuteAsync();
// 同步执行(1.2.0,零分配稳态:内部同步 Socket 直收发 + 缓冲池化,
// 读完项后必须 Release 归还缓冲——忘记调用也不出错,仅失去复用收益)
// ReadPlanResult result = plan.ExecuteSync();
// try { ... } finally { result.Release(); }
if (result.TryGet<float>(0, out float v1, out _)) ...
int idx = result.FindIndex("104");
if (result.TryGet<short[]>(idx, out short[] arr, out _)) ...
Console.WriteLine($"请求合并后实际包数={result.RequestCount},失败项={result.FailedCount}");
await Task.Delay(100);
}
1.1.0 起:
ReadPlanResult为按需解码——TryGet/Get读取某项时才从原始字节转换, 数值路径零装箱。同一结果多次TryGet同一值会重复解码(结果一致);重度重复读取方建议自行缓存。IsAllSuccess只反映通信层成败,不再反映解码失败(类型不匹配等仍由TryGet逐项返回错误)。1.2.0 起:
ExecuteSync(阻塞执行)+Release(归还池化缓冲)构成轮询零分配路径;Release后继续读取会抛InvalidOperationException防护。
6.1 同步读写 API(1.3.0,阻塞执行)
与异步方法一一对应的同步版本,语义完全相同、阻塞调用线程(建议专用/后台线程调用):
plc.ConnectSync(); // 建连(多数实现首次读写也会自动连接)
IotResult<ushort> v = plc.ReadSync<ushort>("100"); // 单值
IotResult<ushort[]> a = plc.ReadSync<ushort>("100", 10); // 数组(超上限自动分包)
IotResult<string> s = plc.ReadStringSync("200", 8);
IotResult<byte[]> r = plc.ReadRawSync("300", 4);
IotResult<bool[]> b = plc.ReadBitsSync("1", 16);
plc.WriteSync("100", 12345); // 类型化 / 字面量 / 非泛型重载同异步版
plc.WriteSync("100", new byte[] { 0x12, 0x34 }); // 原始字节
plc.WriteStringSync("200", "AB");
plc.WriteBitsSync("1", new[] { true, false });
plc.CloseSync();
- 超时沿用
ReceiveTimeoutMs;取消入口检查,阻塞中途取消最迟在接收超时后生效。 - Modbus/S7 为真同步实现(S7 首次收发自动同步握手);MC/FINS 自动回退为异步执行的同步等待。
- 设备层可直接使用:
device.ReadSync / ReadBoolSync / WriteSync / WriteBoolSync / ConnectSync / CloseSync。
7. 32 位及以上类型的字序(ByteOrder)
各协议默认字序与真机惯例(低字在低地址)对齐,可直接用 ByteOrder 调整:
| 协议 | 32 位默认模式 | 0x12345678 线上字节 |
|---|---|---|
| Modbus / S7 | ABCD | 12 34 56 78 |
| Omron FINS | CDAB | 56 78 12 34 |
| Melsec MC | DCBA | 78 56 34 12 |
plc.ByteOrder = LgEasyIot.Common.ByteOrder.CDAB; // 现场数值"对不上/翻转"时优先查这里
16 位读写不受多字字序影响;64 位类型按同一模式的字序延展。
8. 虚拟 PLC 服务器(仿真/联调/测试)
无需实物 PLC,本库自带四个协议的仿真服务器,客户端可与之间完整跑通读写:
using var server = new LgEasyIot.Modbus.ModbusTcpServer(); // 或 S7Server / MelsecMcServer / OmronFinsServer
IotResult start = server.Start(502); // 端口 0 = 系统自动分配,通过 server.Port 读取
// 之后用 Plc.Open(ProtocolKind.Modbus, "127.0.0.1", 502) 即可连上
9. 超时、日志与协议专属功能
plc.ReceiveTimeoutMs = 3000; // 单次操作超时(默认毫秒数见各设备)
plc.Logger = new MyLogger(); // 实现 LgEasyIot.Common.IotLogger 可拿到每笔收发报文 HEX
plc.StringEncoding = System.Text.Encoding.GetEncoding("GBK"); // 中文条码场景
// 逃生口:需要协议专属功能(如三菱远程 RUN/STOP)时取回底层设备
if (plc.TryAs<LgEasyIot.Melsec.MelsecMcDevice>(out var mc))
{
// mc 的协议专属成员
}
10. 常见问题
- 读取失败
ConnectionClosed:客户端已Dispose,检查using作用域。 - 数值"对不上":多半是 32 位字序问题,见第 7 节。
- Modbus 地址偏移:库默认 0 起始(
AddressStartWithZero=true,填写的地址直接进报文,0 = 首个寄存器);遇到按 1 编号的设备(如 40001)时,创建时传addressStartWithZero: false、连接字符串加zeroAddr=false,或设device.AddressStartWithZero = false(报文中减 1)。 - 写浮点字面量:
WriteAsync<float>("D0", 25.1)(f 后缀或显式泛型参数均可,寄存器宽度由 .NET 类型决定)。 - 连接字符串放在配置里:用
Plc.Open(connectionString),支持modbus://、modbus-rtu://、modbus-ascii://、s7://、melsec://、fins://、fins-udp://七种 scheme。
版本变更
- 1.4.0(行为变更):Modbus 地址默认改为 0 起始——
ModbusDeviceBase.AddressStartWithZero默认true,Plc.OpenModbusTcp/OpenModbusRtu/OpenModbusAscii的addressStartWithZero参数与连接字符串zeroAddr同步默认;填写的地址直接进报文(0 = 首个寄存器)。依赖旧版 1 起始(如 40001)行为的,创建时显式传addressStartWithZero: false/ 连接字符串zeroAddr=false,或设device.AddressStartWithZero = false。 - 1.3.0(新增同步读写 API,公共 API 增量):
IPlcClient新增ConnectSync/CloseSync、ReadSync<T>(单值/数组)、ReadStringSync/ReadRawSync/ReadBitsSync、WriteSync系列、WriteStringSync/WriteBitsSync——与对应XxxAsync语义完全一致,阻塞调用线程;- 新增
ISyncReadWriteDevice能力接口 +IReadWriteDevice同步扩展:Modbus/S7 真同步实现(走帧通道同步路径),MC/FINS 自动回退异步执行的同步等待; FrameChannel新增公开ConnectSync/CloseSync。
- 1.2.0(轮询热路径零分配):
ReadPlan.ExecuteSync(阻塞执行)+ReadPlanResult.Release(归还池化缓冲):结果缓冲池化复用,忘记Release自动回退全新分配(安全);FrameChannel.SendReceiveSyncView:TCP/串口阻塞直收发、发送/接收缓冲复用;IViewUnpackFrame零拷贝拆包;ReadPlanResult数值解码去装箱(IsAllSuccess/FailedCount改具体数组遍历)。
- 1.1.0(性能优化,公共 API 兼容):
ReadPlanResult按需解码:数值路径零装箱(.NET 8 走Unsafe.As位模式重解释,net472 回退装箱);同项多次TryGet重复解码、值一致;FrameChannel接收缓冲与超时CancellationTokenSource通道级复用,流式/数据报/服务器接收路径每请求分配显著下降(微基准每次计划执行分配 11,998 → 9,233 字节,-23%);IProtocolFrame新增TryUnpack(byte[] buffer, int length)重载:自实现该接口的代码需同步实现(返回值须为独立副本);- 服务器(Modbus/S7/FINS)
BigEndianTransform静态复用。
- 1.0.1:修正作者信息,新增包图标。
- 1.0.0:首次发布。
| 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. |
| .NET Framework | net472 is compatible. net48 was computed. net481 was computed. |
-
.NETFramework 4.7.2
- No dependencies.
-
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.
1.5.0:S7 DB 区地址简写。① S7Address.Parse 新增简写语法:DB100.0(字节偏移 0,等价 DBB0,位读即 DBX0.0)与 DB100.0.3(等价 DBX0.3),与 M 区 byte.bit 习惯一致;地址只定起始字节,读写宽度由类型决定,完整格式 DB100.DBX0.0 / DBB0 / DBW2 / DBD4 保持不变;② 非法简写(DB100.0.8、DB100.、DB0.0)明确解析失败,不静默截断。真机 S7-1200 全类型(bool/short/ushort/int/uint/float/long/ulong/double/string)简写读写 1500 次压测 0 失败,与完整格式逐值等价。1.4.0:[行为变更] Modbus 地址默认改为 0 起始。① ModbusDeviceBase.AddressStartWithZero 默认 true:输入地址直接作为报文地址(输入 100 即发 100,地址 0 合法),覆盖 TCP/RTU/ASCII 全部未显式指定的创建路径;② Plc.OpenModbusTcp 的 addressStartWithZero 参数默认同步改为 true。设备手册按 1 起始编号的,请显式设置 AddressStartWithZero=false 或连接串 zeroAddr=false。1.3.0:新增同步读写 API。① IPlcClient 新增 ConnectSync/CloseSync/ReadSync<T>/ReadStringSync/ReadRawSync/ReadBitsSync/WriteSync 系列(语义与对应 XxxAsync 完全一致,阻塞调用线程);② 新增 ISyncReadWriteDevice 能力接口 + IReadWriteDevice 同步扩展:Modbus/S7 提供真同步实现(走 FrameChannel 同步路径),MC/FINS 自动回退异步执行的同步等待;③ FrameChannel 新增公开 ConnectSync/CloseSync。1.2.0:轮询热路径零分配优化。ReadPlan.ExecuteSync + ReadPlanResult.Release(结果缓冲池化复用,未 Release 自动回退全新分配);FrameChannel.SendReceiveSyncView(TCP/串口阻塞直收发、缓冲复用);IViewUnpackFrame 零拷贝拆包;ReadPlanResult 数值解码去装箱。