简介基于C#/.NET的跨平台物联网网关完整源代码面向需要接入多品牌PLC、串口设备、数据库及第三方物联网平台的开发者和集成商。通过浏览器可视化配置即可连接AB(罗克韦尔)、三菱、Modbus全协议、MT机床等设备并支持自定义驱动扩展与边缘计算。内置Mqtt服务端和OPCUA服务端可实现与Thingsboard、IoTSharp等平台的双向数据通讯适合工业数据采集、设备上云及SCADA项目二次开发。压缩包共1147个文件约28.46MB包含487个C#源码、124个JavaScript、98个cshtml页面、97个PNG图片、40个TXT说明、29个DLL库及17个工程文件等类型覆盖驱动实现、前端交互、配置界面与项目文档便于直接编译学习和部署。目前已有566人浏览学习适合具备一定C#基础、希望快速构建网关或参考驱动写法的人群。资源中附有完整的项目结构、OPCUA/Mqtt配置示例及多款PLC驱动实现可作为工业通信与边缘计算开发的实用参考。1. 基于 .NET 6 的物联网网关这本开源方案把 PLC、Modbus、MQTT 串成了一条线做工业数据采集的同行应该都有过这种经历现场设备五花八门AB 的 PLC、三菱的 FX 系列、一堆挂 485 总线的 Modbus 仪表再加上几台老掉牙的机床每家的协议都不一样。以前的做法是每个设备写一个采集程序再弄个 OPC 服务中转折腾几天才能把数据送到上层平台。这套基于 C# .NET 6 的物联网网关源码把这件事做成了浏览器里拖拽配置的活。它内置了 AB、三菱、Modbus 全协议、MT 机床驱动还自带 MQTT 服务端和 OPC UA 服务端数据可以直接推给 ThingsBoard、IoTSharp 或者你自己的平台。对正在选型或者想自己搭采集网关的工程师来说这套源码的价值在于不用再从零写驱动照着改就行。2. 网关的骨架从 WTM 可视化配置到驱动加载机制2.1 为什么用 WTM 框架做配置界面这套网关的配置界面是基于 WTMWalkingTec.Mvvm框架开发的。WTM 是 .NET 生态里一个比较成熟的管理系统脚手架它把 MVC、EF Core、Vue 和 Element UI 揉在了一起开发者不需要单独写前后端接口。网关的配置页面比如设备列表、变量绑定、采集周期这些功能都是通过 WTM 的 BaseCRUDVM、BasePagedListVM 这些基类快速生成的。从源码里的BaseCRUDVM.cs、BasePagedListVM.cs、DataContext.cs能看出整个配置模块遵循的是 WTM 的标准套路每个设备类型对应一个 ViewModelCRUD 操作由基类完成DataContext 统一管理数据库上下文。这样设计的好处是你新增一个设备类型或者采集点的时候不需要动前端页面只要在 ViewModel 里加字段就行。这套方案的实用性在于网关的配置界面跑在浏览器里现场工程师不需要装任何客户端软件打开网页就能添加设备、绑定寄存器地址、设置采集频率。整个网关是 .NET 6 写的跨平台部署Windows 和 Linux 都能跑这就解决了工业现场常见的网关程序只能装在 Windows 工控机上的痛点。2.2 网关的启动流程和模块装配网关的入口配置在applicationhost.config这是 IIS Express 的配置文件说明项目默认按 Web 应用方式运行。启动流程大致是// Program.cs 中典型的 .NET 6 Web 应用启动代码 var builder WebApplication.CreateBuilder(args); // 注册网关核心服务设备驱动管理、采集任务调度、MQTT 服务、OPC UA 服务 builder.Services.AddSingletonDriverManager(); builder.Services.AddHostedServiceCollectWorker(); builder.Services.AddSingletonMqttServerService(); builder.Services.AddSingletonOpcUaServerService(); var app builder.Build(); // 启动内置 MQTT 服务端监听 1888 端口 var mqttServer app.Services.GetRequiredServiceMqttServerService(); mqttServer.Start(1888); // 启动内置 OPC UA 服务端匿名访问端口 62541 var opcServer app.Services.GetRequiredServiceOpcUaServerService(); opcServer.Start(62541); app.Run();这段代码反映的是这套网关的模块装配思想DriverManager负责按配置加载对应的 PLC 驱动CollectWorker是后台采集服务MqttServerService和OpcUaServerService是两个数据输出通道。实际源码里还有一个ReferenceNodeManager.cs它是 OPC UA 服务端的节点管理器负责把采集到的实时数据映射成 OPC UA 节点供外部订阅。参数上MQTT 服务端默认端口是 1888管理员账号密码是 admin / 000000OPC UA 服务端默认地址是opc.tcp://localhost:62541/Quickstarts/ReferenceServer匿名访问。这两个参数在源码里都是明文常量部署的时候建议直接改掉。2.3 驱动的加载方式与扩展接口这套网关的驱动机制值得单独说。源码里有CodeGenVM.cs和BaseImportVM.cs这两个文件它们的用途是代码生成和数据导入也就是说驱动模块有一些代码是自动生成的导入设备表、点位表后可以批量生成采集配置。驱动扩展接口的思路大概是// 驱动接口定义示意 public interface IDeviceDriver { string DeviceType { get; } Taskbool ConnectAsync(DeviceConfig config); TaskDictionarystring, object PollAsync(); Task DisconnectAsync(); }采集服务轮询每个驱动实例的时候只关心这个接口不关心底层是 Modbus 还是 AB。而DCExtension.cs可能是驱动模块的扩展方法集比如对字节数组做高低位转换、处理 CRC 校验、解析 PLC 的 DB 块数据等。这套设计对做二次开发很友好。你要是现场有非标的设备只需要实现这个接口再把驱动类挂到 DriverManager 的注册列表里就能在浏览器页面上看到新的设备类型。对只想用时下成熟协议的工程师来说内置的 AB、三菱、Modbus、MT 机床驱动已经覆盖了大部分工厂场景。3. Modbus 全协议采集从串口 RTU 到 TCP 的配置与调试3.1 Modbus 协议栈的组成与选型Modbus 驱动是这套网关里最实用的部分。所谓的全协议支持指的是同时覆盖 Modbus RTU、Modbus ASCII、Modbus TCP 三种模式。RTU 走串口RS-232/RS-485TCP 走以太网ASCII 多用于老旧设备或无线数传电台。源码里 Modbus 驱动的核心是 CRC 校验算法和报文解析这两块在工业现场几乎是每天都要面对的问题。Modbus RTU 的报文格式是地址码1 字节 功能码1 字节 数据区N 字节 CRC 校验2 字节。功能码里最常用的是 03读保持寄存器、04读输入寄存器、06写单个寄存器、16写多个寄存器。当你用这套网关添加一个 Modbus 设备的时候配置界面里填的就是这些参数。3.2 添加一个 Modbus RTU 设备的配置流程在网关的浏览器配置界面里添加 Modbus 设备通常按这几步操作新建设备时选择 Modbus 驱动填入设备名称和通讯参数串口参数按要求填写波特率 9600、数据位 8、停止位 1、无校验是仪表出厂最常见的默认值但 RS-485 总线挂多台设备时建议用 9600 8 E 1因为偶校验能减少总线冲突时的误码率设置从站地址站号范围 1-247要和设备侧拨码或配置一致在点位表里添加要采集的寄存器读写类型、寄存器地址、数据类型在这里逐一指定保存后启动采集网关会按周期轮询这个设备的数据// Modbus RTU 读取保持寄存器的报文构建代码示意 public static byte[] BuildReadHoldingRegisters(byte slaveId, ushort startAddress, ushort quantity) { byte[] frame new byte[8]; frame[0] slaveId; frame[1] 0x03; // 功能码读保持寄存器 frame[2] (byte)(startAddress 8); // 起始地址高字节 frame[3] (byte)(startAddress 0xFF); // 起始地址低字节 frame[4] (byte)(quantity 8); // 寄存器数量高字节 frame[5] (byte)(quantity 0xFF); // 寄存器数量低字节 byte[] crc CalculateCrc16(frame, 6); // 对前 6 个字节做 CRC 校验 frame[6] crc[0]; frame[7] crc[1]; return frame; }这段代码的核心是最后两字节的 CRC 校验。Modbus 用的是 CRC-16/MODBUS 算法多项式是 0x8005初值 0xFFFF。需要注意输出时是先低字节后高字节和大多数人的直觉相反。写设备时也经常有人在这里翻车把 CRC 高低字节写反设备直接不响应。3.3 数据类型的解析与字节序问题Modbus 寄存器里存的数据类型五花八门最坑的是字节序。一个 32 位浮点数占两个寄存器4 字节但不同厂家存储的顺序不同有的高字在前Big-Endian有的低字在前Little-Endian还有的把字内字节也调换。这套网关的配置界面里一般都会有字节序选项但你需要确认自己的设备是哪种。// 把两个 Modbus 寄存器转换成 IEEE 754 浮点数 public static float RegistersToFloat(ushort highRegister, ushort lowRegister, bool wordSwap) { byte[] bytes new byte[4]; if (wordSwap) { // 高字在前先放高寄存器再放低寄存器 bytes[0] (byte)(highRegister 8); bytes[1] (byte)(highRegister 0xFF); bytes[2] (byte)(lowRegister 8); bytes[3] (byte)(lowRegister 0xFF); } else { // 低字在前先放低寄存器再放高寄存器 bytes[0] (byte)(lowRegister 8); bytes[1] (byte)(lowRegister 0xFF); bytes[2] (byte)(highRegister 8); bytes[3] (byte)(highRegister 0xFF); } return BitConverter.ToSingle(bytes, 0); }我一般会在测试阶段用 Modbus Poll 这类工具先读一遍设备把原始字节打出来看确认字节序之后再往网关点位表里填。千万别相信设备手册里写的IEEE 754 标准格式——标准归标准很多国产仪表厂商实现的时候私心很重字节序怎么放的都有。3.4 Modbus 轮询策略与超时参数采集网关挂在 RS-485 总线上时轮询策略直接决定了系统的稳定性。这套网关里 Modbus 驱动的轮询周期、超时时间和重试次数需要重点设置。常见做法是轮询周期默认 1000ms 起总线挂的设备越多周期越长不然单台设备响应慢会造成总线上报文重叠超时时间建议 2000ms有些老式仪表是慢速芯片处理一条报文需要 500ms 以上500ms 超时会被频繁判失败重试次数设 1 次就够了。工业场景下偶尔丢一帧很正常连续丢两帧再报故障也不迟有大量保持寄存器要采集的时候按连续地址块批量读取不要逐地址去读效率差好几倍现场出现过一种情况网关通过 USB 转 485 线连接仪表轮询一快就死机。后来排查发现是 USB 转串口芯片的驱动缓冲区太小网关把多台设备的请求并发发出去了芯片缓冲爆掉。解决方法是把采集改成串行队列每台设备按顺序执行即使间隔 20ms 也要串行不能再并发。4. MQTT 和 OPC UA 双通道输出对接 ThingsBoard 与第三方平台4.1 内置 MQTT 服务端的定位与用途这套网关自带了一个 MQTT 服务端监听 1888 端口支持 WebSocket 接入。这意味着网关不仅可以作为 MQTT 客户端往上层平台推数据还能做为一个独立的 MQTT Broker让现场的 HMI、触摸屏或者手机 App 直接订阅它的主题Topic拿数据。内置 MQTT 服务端的管理员账号密码默认是 admin / 000000这在源码里是直接写死的。部署到生产环境时这个默认凭据必须改。常见的做法是在配置文件里增加一个 MqttBroker 配置节{ MqttBroker: { Port: 1888, Username: admin, Password: 000000, AllowAnonymous: false, EnableWebSockets: true } }AllowAnonymous 设为 false 之后所有客户端连接都需要认证否则会被拒之门外。这个配置项的调整在编译前修改源码里的配置绑定就行。4.2 MQTT 主题设计和数据上报格式网关采集到的数据要推给 ThingsBoard 这类平台Topic 设计和 Payload 格式是关键。ThingsBoard 支持两种接入方式一种是设备直接连接 ThingsBoard 自带的 MQTT Broker端口 1883另一种是网关先把数据通过 HTTP 或 MQTT 推到 ThingsBoard 的网关模块。这套网关走的是后一种思路。数据上报的主题一般设计成// MQTT 客户端发布数据的主题格式 string topic $v1/gateway/telemetry; string payload JsonSerializer.Serialize(new { deviceName deviceName, // 设备名称比如 modbus-slave-01 telemetry new { temperature 23.5f, // 采集到的温度值 pressure 101.3f // 采集到的压力值 } });这段代码反映的是 ThingsBoard 网关 API 的标准格式外层是 deviceName 和 telemetry 两个字段telemetry 对象里是实际的键值对。提交到 ThingsBoard 之后平台会自动按设备维度存储时序数据。对接 IoTSharp 也类似它同样提供了一组 MQTT 主题格式上的差异可以在网关源码里做适配。4.3 OPC UA 服务端把网关变成一个标准数据源网关内置 OPC UA 服务端的价值在于上层系统如组态软件、MES、SCADA如果支持 OPC UA 客户端就不用再去适配 Modbus 或者各家 PLC 的私有协议直接连网关的 UA 服务端就能拿数据。ReferenceNodeManager.cs是 UA 服务端的节点管理器。它做的事是为每个采集变量创建一个 UA 节点按地址空间层级组织起来外部客户端通过 NodeId 读取或订阅节点值。// OPC UA 节点创建逻辑示意 public void AddVariableNode(string deviceId, string tagName, object initialValue) { var folderId new NodeId($ns2;s{deviceId}); // 为当前测点创建变量节点 var variableId new NodeId($ns2;s{deviceId}.{tagName}); var variableState new DataVariableState(variableId, folderId); variableState.Value initialValue; variableState.DataType DataTypes.Double; // 将节点添加到地址空间 _systemContext.NodeManager.AddPredefinedNode(_systemContext, variableState); }这个设计里 NodeId 的命名规范很重要。我一般用ns2;s设备名.点名的结构这样 UA 客户端的地址空间里看起来清晰比如ns2;sboiler-01.temperature。如果你使用 UA Expert 这类工具连接测试可以按这个 NodeId 直接定位数据。OPC UA 服务端默认端口是 62541匿名访问不需要额外装证书这在局域网内部使用足够了。4.4 双通道同时输出的必要性与排错方法MQTT 通道和 OPC UA 通道同时开着看起来好像重复建设实际上各有用途。MQTT 适合对接物联网平台、移动端、云端应用轻量且穿透性强OPC UA 则适合对接 SCADA、组态软件这些工业软件因为它们在 OPC UA 上有一套成熟的数据模型比如地址空间、历史数据、报警事件。双通道在实际部署时偶尔会遇到想让 MQTT 推数据但 UA 服务端启动不了的情况。通常的排错路径是先确认端口有没有被占用netstat -ano | findstr 端口号再看 UA 服务端的日志输出确认节点创建过程中是否遇到数据类型异常MQTT 不生效时优先用 MQTTX 这类桌面客户端订阅#通配符主题看网关到底有没有把报文发出来提示如果你打算用这套网关对接自己的平台先别急着改源码。用 MQTTX 订阅网关的主题用 UA Expert 连接 UA 端口把两个通道的数据都验证通了再动协议适配层。5. AB、三菱、西门子和欧姆龙驱动协议适配的边界与踩坑排查5.1 AB罗克韦尔PLC 驱动的实现要点AB PLC 驱动的默认场景是 ControlLogix 和 CompactLogix 系列。这套网关对 AB PLC 的采集实现依赖的是 Ethernet/IP 协议即通过 CIPCommon Industrial Protocol在以太网上读写标签数据。源码里 AB 驱动的核心工作是标签发现、数据类型映射、以及 CIP 报文的封装解析。配置 AB PLC 时需要注意的点必须填写 PLC 的 IP 地址和槽号Slot Number槽号不对会直接连接失败标签名要和 PLC 程序里定义的完全一致大小写敏感AB PLC 的标签类型是分 Timer、Counter、Control、Analog 等结构的网关需要按结构解析出底层字段才能取到你要的值5.2 三菱 PLC 驱动MC 协议的报文细节三菱 PLC 驱动走的是 MC 协议Melsec Communication Protocol。FX 系列走串口时常用 MC 协议的二进制格式帧格式 1Q/L 系列走以太网时用 MC 协议的 TCP 格式3E 帧。// 三菱 MC 协议 3E 帧读取 D 寄存器 public static byte[] BuildMc3EReadD(ushort startDevice, ushort points) { // 3E 帧报文结构帧头 子头 网络号 PC号 IO号 站号 请求数据长度 监视定时器 指令 子指令 起始地址 点数 byte[] frame new byte[40]; frame[0] 0x50; // 帧头ASCII 说明是 ASCII 格式二进制为 0xD0 frame[1] 0x00; // 子头 frame[2] 0x00; // 网络号 frame[3] 0xFF; // PC 号 frame[4] 0xFF; // IO 号 frame[5] 0x03; // 站号 // ... 中间省略长度和监视定时器填充 return frame; }三菱 MC 协议有个特点地址编码是十六进制 ASCII 码和二进制数混合的。3E 帧默认是二进制帧但有的型号需要切换成 ASCII 帧模式。源码里 AB 驱动和三菱驱动的代码风格相近但三菱的报文构造明显更繁琐这跟日系 PLC 协议设计偏保守有关。如果你的 PLC 型号比较老Frames 里的响应帧长度字段也要留够不然会截断数据。5.3 西门子和欧姆龙协议的接入难度差异项目简介里提到了西门子和欧姆龙协议。欧姆龙 PLC 走的是 FINS 协议Host Link 或 FINS/TCP和欧姆龙的海量指令需要一条条对照。而西门子的 S7 协议实现起来难度更高主要体现在S7 协议需要完成 S7Comm 握手、读取请求、PDU 协商等多个阶段而且不同型号S7-200 SMART、S7-300/400、S7-1200/1500之间报文格式有差异。这套网关的源码里西门子驱动的实现思路是参考了 S7 协议的开源实现如 S7.Net Plus改过来的。它的工作路径是连接 PLC 的 102 端口完成 Coat of Arms 握手再进行读取。如果你要接的是 S7-1200/1500还需要在 PLC 侧勾选允许来自远程对象的通信选项否则网关会被拒绝连接。5.4 常见问题与排查从连接失败到数据跳变现象网关能启动但某个 PLC 设备一直提示连接失败。原因设备侧防火墙拦了网关程序的访问端口尤其是 S7-1200/1500 的 102 端口常被 Windows 防火墙拦。另外连接超时时间设置过短PLC 响应稍慢就被判死。解决先在 PLC 所在电脑上用 Telnet 测试端口连通性再在网关配置里把连接超时调到 3000ms 以上。现象设备显示已连接但采集到的数据偶尔会跳变到极大值。原因PLC 内部分配给这个地址的数据类型与网关配置的不一致比如 PLC 里是 16 位整数你按 32 位浮点解析了高低字节拼出来一个天文数字是常有的事。解决用抓包工具先看一眼 PLC 返回的原始字节确认字节序和数据类型再回头改网关的点位配置。现象AB PLC 的标签读出来全部为 0但 PLC 程序里值不是 0。原因标签在 PLC 里定义的类型是 BOOL 数组或者结构体网关卡仅按基本数据类型解析没有展开数组或结构。解决在网关的标签映射里把这个标签拆成标签名.数组索引或标签名.成员名再采集。现象三菱 PLC 通讯偶尔失败重试后恢复。原因三菱 MC 协议的响应帧里有个监视定时器通常默认 0 表示无限等待如果网关报文里没关注这个字段PLC 侧超时会断开连接。解决把请求帧的监视定时器设置为 1000单位 250ms请求超时时间对应加大让 PLC 有足够时间缓冲。现象多个设备同时采集时偶尔出现某个设备的数据一直不更新。原因网关的采集任务是并发执行的但 Modbus 串口设备本身是半双工总线多个驱动实例同时通过同一个串口发报文会造成总线冲突。解决把 Modbus 串口设备全部加到同一个串口通道下由网关按顺序排程不能一个设备一个串口对象。6. 把网关跑起来从源码编译到连接 ThingsBoard 的验证方法源码拿到手之后编译运行是第一步门槛。项目是基于 .NET 6 的你需要先装好 .NET 6 SDK然后用 Visual Studio 2022 打开项目文件直接编译。启动后浏览器访问本地端口就能看到 WTM 的配置界面。整个验证链路我建议按下面这套流程走一遍编译启动网关确认 Web 界面能正常打开用 MQTTX 连接本地 1888 端口账号 admin / 000000订阅#主题在配置界面添加一个 Modbus TCP 设备比如模拟器或者真实仪表的地址为 127.0.0.1:502站号 1添加几个测试点位地址填 40001 或 30001 这类常规地址保存并启动采集观察 MQTTX 里有没有收到 JSON 报文用 UA Expert 连接opc.tcp://localhost:62541/Quickstarts/ReferenceServer查看数据节点在 ThingsBoard 里创建设备使用网关 API 接入确认遥测数据上云第三步里我建议先用 Modbus Slave 或者 Modbus Poll 这类工具在本地模拟一个从站设备这样不用去现场搬 PLC 也能完整验证链路。Modbus Poll 是主站工具Modbus Slave 是从站模拟器你用 Modbus Slave 起一个设备监听 502 端口网关作为主站去采集它这样整条链路就是通的。验证 MQTT 数据是否到达 ThingsBoard 时打开 ThingsBoard 的设备详情页切到最新遥测标签页。如果能看到 key 和 value说明网关已经把数据推上来了。如果看不到先检查网关日志里有没有 MQTT 发布的成功记录再用 MQTTX 看主题和 payload 是否正常——大概率是设备名不匹配ThingsBoard 要求网关报文里的 deviceName 必须对应平台里已存在的设备名称。整套跑通之后你手里就拥有了一条完整的采集链路Modbus 从站 → 网关采集 → MQTT 双通道输出 → 物联网平台遥测展示。从那以后我每次调试这套网关都强制走一遍上述流程从编译到遥测上云半小时内全部拉通。如果哪一步失败了就看日志定位——日志里会把连接超时、寄存器地址无效、JSON 解析失败这些问题直接打印出来。希望帮到你。本文还有配套的精品资源点击获取