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(answerTag); + logger.LogDebug($"Opc Machine {ConnectionToken} Read Opc tag {answerTag} for value {result.Value}"); + return new OpcParamOut + { + Success = true, + Value = BigEndianValueHelper.Instance.GetBytes(result.Value, result.Value.GetType()) + }; + } + return new OpcParamOut + { + Success = false, + Value = Encoding.ASCII.GetBytes("NoData") + }; + } + else + { + var tag = message.Tag; + var split = message.Split; + var value = message.SetValue; + + var rootDirectory = await Client.ExploreFolderAsync(""); + var answerTag = await SearchTag(tag, split, 0, rootDirectory); + if (answerTag != null) + { + try + { + await Client.WriteAsync(answerTag, value); + logger.LogDebug($"Opc Machine {ConnectionToken} Write Opc tag {answerTag} for value {value}"); + } + catch (Exception e) + { + logger.LogError(e, "Opc client {ConnectionToken} write exception", ConnectionToken); + return new OpcParamOut + { + Success = false + }; + } + return new OpcParamOut + { + Success = true + }; + } + return new OpcParamOut + { + Success = false + }; + } + } + catch (Exception e) + { + logger.LogError(e, "Opc client {ConnectionToken} read exception", ConnectionToken); + return new OpcParamOut + { + Success = false, + Value = Encoding.ASCII.GetBytes("NoData") + }; + } + } + + /// + /// 搜索标签 + /// + /// 标签 + /// 分隔符 + /// 递归深度(第几级标签) + /// 当前搜索的节点 + /// 搜索到的标签 + private async Task SearchTag(string[] tags, char split, int deep, IEnumerable nodes) + { + foreach (var node in nodes) + { + var currentTag = node.Tag.Substring(node.Tag.LastIndexOf(split) + 1); + if (RegexOn && Regex.IsMatch(currentTag, tags[deep]) || !RegexOn && currentTag == tags[deep]) + { + if (deep == tags.Length - 1) return node.Tag; + var subDirectories = await Client.ExploreFolderAsync(node.Tag); + var answerTag = await SearchTag(tags, split, deep + 1, subDirectories); + if (answerTag != null) return answerTag; + } + } + return null; + } + + private bool Connect() + { + try + { + Client.Connect(); + _connect = true; + logger.LogInformation("Opc client {ConnectionToken} connect success", ConnectionToken); + return true; + } + catch (Exception ex) + { + logger.LogError(ex, "Opc client {ConnectionToken} connected failed", ConnectionToken); + _connect = false; + return false; + } + } + + /// + /// 连接PLC,异步 + /// + /// 是否连接成功 + public override Task ConnectAsync() + { + return Task.FromResult(Connect()); + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs b/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs new file mode 100644 index 0000000..12fa468 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Modbus.Net.Opc +{ + /// + /// Opc DA连接实现 + /// + public class OpcDaConnector : OpcConnector + { + /// + /// DA单例管理 + /// + protected static Dictionary _instances = new Dictionary(); + + /// + /// 构造函数 + /// + /// Opc DA 服务地址 + /// 是否开启正则匹配 + protected OpcDaConnector(string host, bool isRegexOn) : base(host, isRegexOn) + { + Client = new MyDaClient(new Uri(ConnectionToken)); + } + + /// + /// 根据服务地址生成DA单例 + /// + /// Opc DA 服务地址 + /// 是否开启正则匹配 + /// Opc DA 连接器实例 + public static OpcDaConnector Instance(string host, bool isRegexOn) + { + if (!_instances.ContainsKey(host)) + { + var connector = new OpcDaConnector(host, isRegexOn); + _instances.Add(host, connector); + } + return _instances[host]; + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcDaProtocal.cs b/Modbus.Net/Modbus.Net.Opc/OpcDaProtocal.cs new file mode 100644 index 0000000..330b54f --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcDaProtocal.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; + +namespace Modbus.Net.Opc +{ + /// + /// Opc Da协议 + /// + public class OpcDaProtocol : OpcProtocol + { + private readonly string _host; + + private readonly bool _isRegexOn; + + /// + /// 构造函数 + /// + /// Opc DA服务地址 + /// 是否开启正则匹配 + public OpcDaProtocol(string host, bool isRegexOn) + { + _host = host; + _isRegexOn = isRegexOn; + } + + + /// + /// 连接设备 + /// + /// 是否连接成功 + public override async Task ConnectAsync() + { + ProtocolLinker = new OpcDaProtocolLinker(_host, _isRegexOn); + if (!await ProtocolLinker.ConnectAsync()) + return false; + return true; + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcDaProtocalLinker.cs b/Modbus.Net/Modbus.Net.Opc/OpcDaProtocalLinker.cs new file mode 100644 index 0000000..caec5d4 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcDaProtocalLinker.cs @@ -0,0 +1,26 @@ +namespace Modbus.Net.Opc +{ + /// + /// Opc Da协议连接器 + /// + public class OpcDaProtocolLinker : OpcProtocolLinker + { + /// + /// 构造函数 + /// + /// 是否开启正则匹配 + public OpcDaProtocolLinker(bool isRegexOn) : this(ConfigurationReader.GetValueDirect("OpcDa", "Host"), isRegexOn) + { + } + + /// + /// 构造函数 + /// + /// Opc DA服务地址 + /// 是否开启正则匹配 + public OpcDaProtocolLinker(string host, bool isRegexOn) + { + BaseConnector = OpcDaConnector.Instance(host, isRegexOn); + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcMachine.cs b/Modbus.Net/Modbus.Net.Opc/OpcMachine.cs new file mode 100644 index 0000000..3769471 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcMachine.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace Modbus.Net.Opc +{ + /// + /// Opc设备 + /// + public class OpcMachine : BaseMachine where TKey : IEquatable + where TUnitKey : IEquatable + { + /// + /// 构造函数 + /// + /// 设备的ID号 + /// 连接类型 + /// 连接地址 + /// 需要读写的地址 + /// 开启正则匹配 + public OpcMachine(TKey id, OpcType connectionType, string connectionString, IEnumerable> getAddresses, bool isRegexOn = false) + : base(id, getAddresses, true) + { + BaseUtility = new OpcUtility(connectionType, connectionString, isRegexOn); + AddressFormater = new AddressFormaterOpc((machine, unit) => { return new string[] { unit.Area }; }, this); + ((OpcUtility)BaseUtility).GetSeperator += + () => ((AddressFormaterOpc)AddressFormater).Seperator; + AddressCombiner = new AddressCombinerSingle(); + AddressCombinerSet = new AddressCombinerSingle(); + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcProtocol.cs b/Modbus.Net/Modbus.Net.Opc/OpcProtocol.cs new file mode 100644 index 0000000..cea9f7b --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcProtocol.cs @@ -0,0 +1,197 @@ +namespace Modbus.Net.Opc +{ + /// + /// Opc协议 + /// + public abstract class OpcProtocol : BaseProtocol, + PipeUnit, + ProtocolUnit>> + { + /// + /// 构造函数 + /// + protected OpcProtocol() : base(0, 0, Endian.BigEndianLsb) + { + } + } + + #region 读数据 + + /// + /// 读数据输入 + /// + public class ReadRequestOpcInputStruct : IInputStruct + { + /// + /// 构造函数 + /// + /// 标签 + /// 分隔符 + public ReadRequestOpcInputStruct(string[] tag, char split) + { + Tag = tag; + Split = split; + } + + /// + /// 标签 + /// + public string[] Tag { get; } + + /// + /// 分隔符 + /// + public char Split { get; } + } + + /// + /// 读地址输出 + /// + public class ReadRequestOpcOutputStruct : IOutputStruct + { + /// + /// 构造函数 + /// + /// 读取的数据 + public ReadRequestOpcOutputStruct(byte[] value) + { + GetValue = value; + } + + /// + /// 读取的地址 + /// + public byte[] GetValue { get; private set; } + } + + /// + /// 读数据协议 + /// + [SpecialProtocolUnit] + public class ReadRequestOpcProtocol : ProtocolUnit + { + /// + /// 从对象的参数数组格式化 + /// + /// 非结构化的输入数据 + /// 格式化后的字节流 + public override OpcParamIn Format(IInputStruct message) + { + var r_message = (ReadRequestOpcInputStruct)message; + return new OpcParamIn + { + IsRead = true, + Tag = r_message.Tag, + Split = r_message.Split + }; + } + + /// + /// 把仪器返回的内容填充到输出结构中 + /// + /// 返回数据的字节流 + /// 转换标记位 + /// 结构化的输出数据 + public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos) + { + return new ReadRequestOpcOutputStruct(messageBytes.Value); + } + } + + #endregion + + #region 写数据 + + /// + /// 写数据输入 + /// + public class WriteRequestOpcInputStruct : IInputStruct + { + /// + /// 构造函数 + /// + /// 标签 + /// 分隔符 + /// 写入的数据 + public WriteRequestOpcInputStruct(string[] tag, char split, object setValue) + { + Tag = tag; + Split = split; + SetValue = setValue; + } + + /// + /// 标签 + /// + public string[] Tag { get; } + + /// + /// 分隔符 + /// + public char Split { get; } + + /// + /// 写入的数据 + /// + public object SetValue { get; } + } + + /// + /// 写数据输出 + /// + public class WriteRequestOpcOutputStruct : IOutputStruct + { + /// + /// 构造函数 + /// + /// 写入是否成功 + public WriteRequestOpcOutputStruct(bool writeResult) + { + WriteResult = writeResult; + } + + /// + /// 写入是否成功 + /// + public bool WriteResult { get; private set; } + } + + /// + /// 写数据协议 + /// + [SpecialProtocolUnit] + public class WriteRequestOpcProtocol : ProtocolUnit + { + /// + /// 从对象的参数数组格式化 + /// + /// 非结构化的输入数据 + /// 格式化后的字节流 + public override OpcParamIn Format(IInputStruct message) + { + var r_message = (WriteRequestOpcInputStruct)message; + return new OpcParamIn + { + IsRead = false, + Tag = r_message.Tag, + Split = r_message.Split, + SetValue = r_message.SetValue + }; + } + + /// + /// 把仪器返回的内容填充到输出结构中 + /// + /// 返回数据的字节流 + /// 转换标记位 + /// 结构化的输出数据 + public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos) + { + var ansByte = BigEndianValueHelper.Instance.GetByte(messageBytes.Value, ref pos); + var ans = ansByte != 0; + return new WriteRequestOpcOutputStruct(ans); + } + } + + #endregion +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcProtocolLinker.cs b/Modbus.Net/Modbus.Net.Opc/OpcProtocolLinker.cs new file mode 100644 index 0000000..dd1c778 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcProtocolLinker.cs @@ -0,0 +1,55 @@ +using System.Text; +using System.Threading.Tasks; + +namespace Modbus.Net.Opc +{ + /// + /// Opc协议连接器 + /// + public abstract class OpcProtocolLinker : ProtocolLinker + { + /// + /// 发送并接收数据 + /// + /// 发送协议的内容 + /// 接收协议的内容 + public override async Task SendReceiveAsync(OpcParamIn content) + { + var extBytes = BytesExtend(content); + var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes); + return receiveBytes == null + ? null + : receiveBytes.Value.Length == 0 ? receiveBytes : BytesDecact(receiveBytes); + } + + /// + /// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议 + /// + /// 发送协议的内容 + /// 接收协议的内容 + public override async Task SendReceiveWithoutExtAndDecAsync(OpcParamIn content) + { + //发送数据 + var receiveBytes = await BaseConnector.SendMsgAsync(content); + //容错处理 + var checkRight = CheckRight(receiveBytes); + return checkRight == null + ? new OpcParamOut { Success = false, Value = new byte[0] } + : (!checkRight.Value ? null : receiveBytes); + //返回字符 + } + + /// + /// 检查接收的数据是否正确 + /// + /// 接收协议的内容 + /// 协议是否是正确的 + public override bool? CheckRight(OpcParamOut content) + { + if (content == null || !content.Success) return false; + if (content.Value.Length == 6 && Encoding.ASCII.GetString(content.Value) == "NoData") + return null; + return base.CheckRight(content); + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs b/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs new file mode 100644 index 0000000..75ad592 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Modbus.Net.Opc +{ + /// + /// Opc UA连接实现 + /// + public class OpcUaConnector : OpcConnector + { + /// + /// UA单例管理 + /// + protected static Dictionary _instances = new Dictionary(); + + /// + /// 构造函数 + /// + /// Opc UA 服务地址 + /// 是否开启正则匹配 + protected OpcUaConnector(string host, bool isRegexOn) : base(host, isRegexOn) + { + Client = new MyUaClient(new Uri(ConnectionToken)); + } + + /// + /// 根据地址获取UA连接器单例 + /// + /// Opc UA服务地址 + /// 是否开启正则匹配 + /// Opc UA实例 + public static OpcUaConnector Instance(string host, bool isRegexOn) + { + if (!_instances.ContainsKey(host)) + { + var connector = new OpcUaConnector(host, isRegexOn); + _instances.Add(host, connector); + } + return _instances[host]; + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcUaProtocol.cs b/Modbus.Net/Modbus.Net.Opc/OpcUaProtocol.cs new file mode 100644 index 0000000..3e9dbac --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcUaProtocol.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; + +namespace Modbus.Net.Opc +{ + /// + /// Opc UA协议 + /// + public class OpcUaProtocol : OpcProtocol + { + private readonly string _host; + + private readonly bool _isRegexOn; + + /// + /// 构造函数 + /// + /// Opc UA服务地址 + /// 是否开启正则匹配 + public OpcUaProtocol(string host, bool isRegexOn) + { + _host = host; + _isRegexOn = isRegexOn; + } + + /// + /// 连接设备 + /// + /// 是否连接成功 + public override async Task ConnectAsync() + { + ProtocolLinker = new OpcUaProtocolLinker(_host, _isRegexOn); + if (!await ProtocolLinker.ConnectAsync()) return false; + return true; + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcUaProtocolLinker.cs b/Modbus.Net/Modbus.Net.Opc/OpcUaProtocolLinker.cs new file mode 100644 index 0000000..acb1632 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcUaProtocolLinker.cs @@ -0,0 +1,26 @@ +namespace Modbus.Net.Opc +{ + /// + /// Opc UA协议连接器 + /// + public class OpcUaProtocolLinker : OpcProtocolLinker + { + /// + /// 构造函数 + /// + /// 是否开启正则匹配 + public OpcUaProtocolLinker(bool isRegexOn) : this(ConfigurationReader.GetValueDirect("OpcUa", "Host"), isRegexOn) + { + } + + /// + /// 构造函数 + /// + /// Opc UA服务地址 + /// 是否开启正则匹配 + public OpcUaProtocolLinker(string host, bool isRegexOn) + { + BaseConnector = OpcUaConnector.Instance(host, isRegexOn); + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcUtility.cs b/Modbus.Net/Modbus.Net.Opc/OpcUtility.cs new file mode 100644 index 0000000..8712cef --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/OpcUtility.cs @@ -0,0 +1,190 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; + +namespace Modbus.Net.Opc +{ + /// + /// Opc类型 + /// + public enum OpcType + { + /// + /// DA连接 + /// + Da = 0, + /// + /// UA连接 + /// + Ua = 1 + } + + /// + /// Opc通用Api入口 + /// + public class OpcUtility : BaseUtility, + PipeUnit, + ProtocolUnit>> + { + private static readonly ILogger logger = LogProvider.CreateLogger(); + + private OpcType _opcType; + + private bool IsRegexOn { get; set; } + + /// + /// 协议类型 + /// + public OpcType OpcType + { + get { return _opcType; } + set + { + _opcType = value; + switch (_opcType) + { + //Da协议 + case OpcType.Da: + { + Wrapper = new OpcDaProtocol(ConnectionString, IsRegexOn); + break; + } + //Ua协议 + case OpcType.Ua: + { + Wrapper = new OpcUaProtocol(ConnectionString, IsRegexOn); + break; + } + } + } + } + + /// + /// 获取分隔符 + /// + /// 分隔符 + public delegate char GetSeperatorDelegate(); + + /// + /// 构造函数 + /// + /// 连接类型 + /// 连接地址 + /// 是否开启正则匹配 + public OpcUtility(int connectionType, string connectionString, bool isRegexOn = false) : base(0, 0) + { + ConnectionString = connectionString; + IsRegexOn = isRegexOn; + OpcType = (OpcType)connectionType; + AddressTranslator = new AddressTranslatorOpc(); + } + + /// + /// 构造函数 + /// + /// 连接类型 + /// 连接地址 + /// 是否开启正则匹配 + public OpcUtility(OpcType connectionType, string connectionString, bool isRegexOn = false) : base(0, 0) + { + ConnectionString = connectionString; + IsRegexOn = isRegexOn; + OpcType = connectionType; + AddressTranslator = new AddressTranslatorOpc(); + } + + /// + /// 端格式(大端) + /// + public override Endian Endian => Endian.BigEndianLsb; + + /// + /// 获取分隔符 + /// + public event GetSeperatorDelegate GetSeperator; + + /// + /// 设置连接方式(Opc忽略该函数) + /// + /// 连接方式 + public override void SetConnectionType(int connectionType) + { + //ignore + } + + /// + /// 获取数据 + /// + /// 开始地址 + /// 获取字节数个数 + /// 接收到的byte数据 + public override async Task> GetDatasAsync(string startAddress, int getByteCount) + { + try + { + var split = GetSeperator?.Invoke() ?? '/'; + var readRequestOpcInputStruct = new ReadRequestOpcInputStruct(startAddress.Split('\r'), split); + var readRequestOpcOutputStruct = + await + Wrapper.SendReceiveAsync(Wrapper[typeof(ReadRequestOpcProtocol)], + readRequestOpcInputStruct); + return new ReturnStruct + { + Datas = readRequestOpcOutputStruct?.GetValue, + IsSuccess = true, + ErrorCode = 0, + ErrorMsg = "" + }; + } + catch (Exception e) + { + logger.LogError(e, $"OpcUtility -> GetDatas: {ConnectionString} error: {e.Message}"); + return new ReturnStruct + { + Datas = null, + IsSuccess = true, + ErrorCode = -100, + ErrorMsg = e.Message + }; + } + } + + /// + /// 设置数据 + /// + /// 开始地址 + /// 设置数据 + /// 是否设置成功 + public override async Task> SetDatasAsync(string startAddress, object[] setContents) + { + try + { + var split = GetSeperator?.Invoke() ?? '/'; + var writeRequestOpcInputStruct = + new WriteRequestOpcInputStruct(startAddress.Split('\r'), split, setContents[0]); + var writeRequestOpcOutputStruct = + await + Wrapper.SendReceiveAsync(Wrapper[typeof(WriteRequestOpcProtocol)], + writeRequestOpcInputStruct); + return new ReturnStruct + { + Datas = writeRequestOpcOutputStruct?.WriteResult == true, + IsSuccess = writeRequestOpcOutputStruct?.WriteResult == true, + ErrorCode = writeRequestOpcOutputStruct?.WriteResult == true ? 0 : 1, + ErrorMsg = writeRequestOpcOutputStruct?.WriteResult == true ? "" : "Write Failed" + }; + } + catch (Exception e) + { + logger.LogError(e, $"OpcUtility -> SetDatas: {ConnectionString} error: {e.Message}"); + return new ReturnStruct + { + Datas = false, + IsSuccess = false, + ErrorCode = -100, + ErrorMsg = e.Message + }; + } + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/README.md b/Modbus.Net/Modbus.Net.Opc/README.md new file mode 100644 index 0000000..70560c8 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Opc/README.md @@ -0,0 +1,7 @@ +Modbus.Net.OPC +=================== +[![NuGet](https://img.shields.io/nuget/v/Modbus.Net.OPC.svg)](https://www.nuget.org/packages/Modbus.Net.OPC/) + +OPC Implementation of Modbus.Net + +Doc has been moved to wiki. \ No newline at end of file