OPC UA → Modbus TCP 桥接
老设备只支持 Modbus, 新设备只暴露 OPC UA. 写一个轻量桥: 订阅 OPC UA → 写 Modbus 寄存器. 用 C# + NModbus 示例.
配套示例
- 推到云 → OPC UA → MQTT 桥接
- 多 Tag 监控 → 大规模监控
依赖
dotnet add package DarraOpcUa
dotnet add package NModbus
完整代码
using DarraOpcUa_Client;
using NModbus;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
class Program
{
// 映射表: OPC UA NodeId → Modbus 寄存器地址
record TagMap(string NodeId, ushort Reg, string Type);
static readonly TagMap[] Mapping = new[]
{
new TagMap("ns=2;s=Boiler1.Temperature", 0, "Float"), // 占 reg 0/1
new TagMap("ns=2;s=Boiler1.Pressure", 2, "Float"), // 占 reg 2/3
new TagMap("ns=2;s=Boiler1.IsRunning", 100, "Bool"), // coil 100
};
static async Task Main()
{
const string opcuaEndpoint = "opc.tcp://192.168.1.100:4840"; // 替换成现场 PLC 工控机 IP
const int modbusPort = 502;
// 1. 启动 Modbus TCP Slave
var listener = new TcpListener(IPAddress.Any, modbusPort);
listener.Start();
var factory = new ModbusFactory();
var slaveNet = factory.CreateSlaveNetwork(listener);
var slave = factory.CreateSlave(unitId: 1);
slaveNet.AddSlave(slave);
_ = slaveNet.ListenAsync();
Console.WriteLine($"[OK] Modbus TCP Slave listen on :{modbusPort}");
// 2. 启动 OPC UA Client
using var ua = new DarraOpcUa(opcuaEndpoint);
ua.Connect();
Console.WriteLine($"[OK] OPC UA connected to {opcuaEndpoint}");
// 3. 订阅 + 类型转换写 Modbus
using var sub = ua.CreateSubscription(500);
sub.DataChanged += (s, e) =>
{
var map = Mapping.FirstOrDefault(m => m.NodeId == e.NodeId);
if (map == null || !e.Status.IsGood) return;
try
{
switch (map.Type)
{
case "Float":
var f = float.Parse(e.ValueString);
var bytes = BitConverter.GetBytes(f);
slave.DataStore.HoldingRegisters[map.Reg] = BitConverter.ToUInt16(bytes, 0);
slave.DataStore.HoldingRegisters[map.Reg + 1] = BitConverter.ToUInt16(bytes, 2);
break;
case "Bool":
slave.DataStore.CoilDiscretes[map.Reg] = bool.Parse(e.ValueString);
break;
}
Console.WriteLine($" {e.NodeId} = {e.ValueString} -> Modbus[{map.Reg}]");
}
catch (Exception ex) { Console.WriteLine($" map error: {ex.Message}"); }
};
sub.AddMany(Mapping.Select(m => m.NodeId).ToList());
Console.WriteLine("\nBridge 运行中, Ctrl+C 退出...");
await Task.Delay(Timeout.Infinite);
}
}
代码要点
- Modbus 寄存器是 16 位,
Float拆 2 个 /Double拆 4 个; 字节序 (Big/Little Endian, Word Swap) 各厂家不同, 用 Modbus Poll 对调 - HoldingRegisters / InputRegisters / Coils / DiscreteInputs 四张表独立, 设计映射前先想清楚放哪张
- 反向写 (Modbus Master → OPC UA) 挂
ModbusSlaveRequestReceived事件, 在里面调ua.Write - 单监听口可挂多个
unitId, 一个进程桥接多设备 - NModbus 不支持 RTU over TCP, 要 RTU 改用
NModbus.Serial或EasyModbusTCP
注意事项
- HoldingRegisters / InputRegisters / Coils / DiscreteInputs 四张表独立, 设计映射前先想清楚.
- NModbus 不支持 Modbus RTU over TCP, 要 RTU 用
NModbus.Serial或EasyModbusTCP.
断线哨兵值
OPC UA 断线时 Modbus 寄存器不会自动清零 — 上层应用可能误以为数据正常. 建议挂 ua.Events.Disconnected 把对应寄存器写一个"无效值哨兵" (如 0xFFFF).