diff --git a/Modbus.Net/Modbus.Net.Opc/AddressFormaterOpc.cs b/Modbus.Net/Modbus.Net.Opc/AddressFormaterOpc.cs
new file mode 100644
index 0000000..045f999
--- /dev/null
+++ b/Modbus.Net/Modbus.Net.Opc/AddressFormaterOpc.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+
+namespace Modbus.Net.Opc
+{
+ ///
+ /// Opc地址编码器
+ ///
+ public class AddressFormaterOpc : AddressFormater where TMachineKey : IEquatable
+ where TUnitKey : IEquatable
+ {
+ ///
+ /// 协议构造器
+ ///
+ /// 如何通过BaseMachine和AddressUnit构造Opc的标签
+ /// 调用这个编码器的设备
+ /// 每两个标签之间用什么符号隔开,默认为/
+ public AddressFormaterOpc(Func, AddressUnit, string[]> tagGeter,
+ BaseMachine machine,
+ char seperator = '/')
+ {
+ Machine = machine;
+ TagGeter = tagGeter;
+ Seperator = seperator;
+ }
+
+ ///
+ /// 设备
+ ///
+ public BaseMachine Machine { get; set; }
+
+ ///
+ /// 标签构造器
+ /// (设备,地址)->不具备分隔符的标签数组
+ ///
+ protected Func, AddressUnit, string[]> TagGeter { get; set; }
+
+ ///
+ /// 分割符
+ ///
+ public char Seperator { get; protected set; }
+
+ ///
+ /// 编码地址
+ ///
+ /// 地址所在的数据区域
+ /// 地址
+ /// 编码后的地址
+ public override string FormatAddress(string area, int address)
+ {
+ var findAddress = Machine?.GetAddresses.FirstOrDefault(p => p.Area == area && p.Address == address);
+ if (findAddress == null) return null;
+ var strings = TagGeter(Machine, findAddress);
+ var ans = "";
+ for (var i = 0; i < strings.Length; i++)
+ ans += strings[i].Trim().Replace(" ", "") + '\r';
+ ans = ans.Substring(0, ans.Length - 1);
+ return ans;
+ }
+
+ ///
+ /// 编码地址
+ ///
+ /// 地址所在的数据区域
+ /// 地址
+ /// 子地址(忽略)
+ /// 编码后的地址
+ public override string FormatAddress(string area, int address, int subAddress)
+ {
+ return FormatAddress(area, address);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Modbus.Net/Modbus.Net.Opc/AddressTranslatorOpc.cs b/Modbus.Net/Modbus.Net.Opc/AddressTranslatorOpc.cs
new file mode 100644
index 0000000..c9ef4a4
--- /dev/null
+++ b/Modbus.Net/Modbus.Net.Opc/AddressTranslatorOpc.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Modbus.Net.Opc
+{
+ ///
+ /// Opc地址解析器
+ ///
+ public class AddressTranslatorOpc : AddressTranslator
+ {
+ ///
+ /// 地址转换
+ ///
+ /// 格式化的地址
+ /// 是否为读取,是为读取,否为写入
+ /// 翻译后的地址
+ public override AddressDef AddressTranslate(string address, bool isRead)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 获取区域中的单个地址占用的字节长度
+ ///
+ /// 区域名称
+ /// 字节长度
+ public override double GetAreaByteLength(string area)
+ {
+ return 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Modbus.Net/Modbus.Net.Opc/ClientExtend.cs b/Modbus.Net/Modbus.Net.Opc/ClientExtend.cs
new file mode 100644
index 0000000..d68c1c2
--- /dev/null
+++ b/Modbus.Net/Modbus.Net.Opc/ClientExtend.cs
@@ -0,0 +1,146 @@
+using Hylasoft.Opc.Common;
+using Hylasoft.Opc.Da;
+using Hylasoft.Opc.Ua;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Modbus.Net.Opc
+{
+ ///
+ /// Opc Client Extend interface, Unified for DA and UA
+ ///
+ public interface IClientExtend : IDisposable
+ {
+ ///
+ /// Unified Root Node
+ ///
+ Node RootNodeBase { get; }
+
+ ///
+ /// Connect the client to the Opc Server
+ ///
+ void Connect();
+
+ ///
+ /// Read a tag
+ ///
+ /// The type of tag to read
+ ///
+ /// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
+ /// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`
+ ///
+ /// The value retrieved from the Opc
+ ReadEvent Read(string tag);
+
+ ///
+ /// Write a value on the specified Opc tag
+ ///
+ /// The type of tag to write on
+ ///
+ /// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
+ /// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`
+ ///
+ ///
+ void Write(string tag, T item);
+
+ ///
+ /// Read a tag asynchronusly
+ ///
+ Task> ReadAsync(string tag);
+
+ ///
+ /// Write a value on the specified Opc tag asynchronously
+ ///
+ Task WriteAsync(string tag, T item);
+
+ ///
+ /// Finds a node on the Opc Server asynchronously
+ ///
+ Task FindNodeAsync(string tag);
+
+ ///
+ /// Explore a folder on the Opc Server asynchronously
+ ///
+ Task> ExploreFolderAsync(string tag);
+ }
+
+ ///
+ /// UaClient Extend
+ ///
+ public class MyDaClient : DaClient, IClientExtend
+ {
+ ///
+ /// UaClient Extend
+ ///
+ /// Url address of Opc UA server
+ public MyDaClient(Uri serverUrl) : base(serverUrl)
+ {
+ }
+
+ ///
+ /// Unified root node
+ ///
+ public Node RootNodeBase => RootNode;
+ }
+
+ ///
+ /// DaClient Extend
+ ///
+ public class MyUaClient : UaClient, IClientExtend
+ {
+ ///
+ /// DaClient Extend
+ ///
+ public MyUaClient(Uri serverUrl) : base(serverUrl)
+ {
+ }
+
+ ///
+ /// Unified root node
+ ///
+ public Node RootNodeBase => RootNode;
+ }
+
+ ///
+ /// Param input of OpcConnector
+ ///
+ public class OpcParamIn
+ {
+ ///
+ /// Is the action read (not is write)
+ ///
+ public bool IsRead { get; set; }
+
+ ///
+ /// Tag of a node
+ ///
+ public string[] Tag { get; set; }
+
+ ///
+ /// Tag splitter of a node
+ ///
+ public char Split { get; set; }
+
+ ///
+ /// The value set to node(only available when IsRead is false
+ ///
+ public object SetValue { get; set; }
+ }
+
+ ///
+ /// Param output of OpcConnector
+ ///
+ public class OpcParamOut
+ {
+ ///
+ /// Is the action success
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// Action return values
+ ///
+ public byte[] Value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj b/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj
new file mode 100644
index 0000000..93aefd7
--- /dev/null
+++ b/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj
@@ -0,0 +1,43 @@
+
+
+
+ net462
+ 10.0
+ Modbus.Net.Opc
+ Modbus.Net.Opc
+ Modbus.Net.Opc
+ 1.4.1
+ Chris L.(Luo Sheng)
+ Hangzhou Delian Science Technology Co.,Ltd.
+ Modbus.Net.Opc
+ Modbus.Net Opc Implementation
+ Copyright 2023 Hangzhou Delian Science Technology Co.,Ltd.
+ https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net/Modbus.Net.Opc
+ https://github.com/parallelbgls/Modbus.Net
+ git
+ hardware communicate protocol modbus Delian
+ False
+ True
+ True
+ True
+ MIT
+ README.md
+ snupkg
+
+
+
+ bin\Debug\Modbus.Net.Opc.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs b/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs
new file mode 100644
index 0000000..ac35e32
--- /dev/null
+++ b/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs
@@ -0,0 +1,220 @@
+using Hylasoft.Opc.Common;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Modbus.Net.Opc
+{
+ ///
+ /// Opc连接器
+ ///
+ public abstract class OpcConnector : BaseConnector
+ {
+ private static readonly ILogger logger = LogProvider.CreateLogger();
+
+ ///
+ /// 是否正在连接
+ ///
+ protected bool _connect;
+
+ ///
+ /// Opc客户端
+ ///
+ protected IClientExtend Client;
+
+ ///
+ /// 是否开启正则匹配
+ ///
+ protected bool RegexOn { get; set; }
+
+ ///
+ /// 构造函数
+ ///
+ /// 服务端url
+ /// 是否开启正则匹配
+ protected OpcConnector(string host, bool isRegexOn)
+ {
+ ConnectionToken = host;
+ RegexOn = isRegexOn;
+ }
+
+ ///
+ /// 连接标识
+ ///
+ public override string ConnectionToken { get; }
+
+ ///
+ /// 是否正在连接
+ ///
+ public override bool IsConnected => _connect;
+
+ ///
+ /// 断开连接
+ ///
+ ///
+ public override bool Disconnect()
+ {
+ try
+ {
+ Client?.Dispose();
+ Client = null;
+ _connect = false;
+ logger.LogInformation("Opc client {ConnectionToken} disconnected success", ConnectionToken);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Opc client {ConnectionToken} disconnected error", ConnectionToken);
+ _connect = false;
+ return false;
+ }
+ }
+
+ ///
+ protected override void ReceiveMsgThreadStart()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ protected override void ReceiveMsgThreadStop()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ protected override Task SendMsgWithoutConfirm(OpcParamIn message)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 带返回发送数据
+ ///
+ /// 需要发送的数据
+ /// 是否发送成功
+ public override async Task SendMsgAsync(OpcParamIn message)
+ {
+ try
+ {
+ if (message.IsRead)
+ {
+ var split = message.Split;
+ var tag = message.Tag;
+ var rootDirectory = await Client.ExploreFolderAsync("");
+ var answerTag = await SearchTag(tag, split, 0, rootDirectory);
+ if (answerTag != null)
+ {
+ var result = await Client.ReadAsync