LgEasyIot 1.5.0

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

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 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. 
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.5.0 108 9/16/2026
1.4.0 100 9/1/2026
1.3.0 111 8/30/2026
1.0.1 105 8/29/2026

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 数值解码去装箱。