更新插件

This commit is contained in:
Diego2098
2024-04-16 21:11:42 +08:00
parent faadb03e46
commit c33828a5d9
60 changed files with 818 additions and 1495 deletions

View File

@@ -19,7 +19,7 @@
| Modbus | Rtu/Tcp报文格式支持串口/Tcp/Udp链路 |
| SiemensS7 | 西门子PLC S7系列 |
| Dlt6452007 | 支持串口/Tcp/Udp链路 |
| OpcDaClient | 64位编译 |
| OpcUaClient | 64位编译 |
| OpcUaClient | 支持证书登录扩展对象Json读写 |
#### 业务插件

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;</NoWarn>
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;</NoWarn>
<TargetFrameworks>net8.0;</TargetFrameworks>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -8,15 +8,15 @@
@namespace ThingsGateway.Debug
@inherits AdapterDebugBase
<div class=@($"{ClassString} row mt-2 px-2 mx-1") style="min-height:700px;height: 50%;">
<div class=@($"{ClassString} row mt-2 px-2 mx-1") style="min-height:700px;height: 50%;">
<div class="col-12 col-md-5 h-100">
<Tab class="h-100">
<TabItem Text=@Localizer["CommonFunctions"]>
@if (ShowDefaultReadWriteContent)
{
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
ShowLabel="true" style="width:100%" />
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
ShowLabel="true" style="width:100%" />
<div class="row mx-1 form-inline mt-2">
@@ -70,42 +70,47 @@
@ReadWriteContent
}
</TabItem>
<TabItem Text=@Localizer["SpecialFunctions"]>
@foreach (var item in VariableRunTimes)
<TabItem Text=@Localizer["SpecialFunctions"]>
@if (ShowDefaultOtherContent)
{
<div class="row mx-1 form-inline mt-2">
@foreach (var item in VariableRunTimes)
{
<div class="col-12 col-md-8 p-1">
<div class="row mx-1 form-inline mt-2">
<div class="p-1">
<div class="col-12 col-md-8 p-1">
<div class="p-1">
<BootstrapInput @bind-Value=@item.RegisterAddress title="@Plc?.GetAddressDescription()"
ShowLabel="true" style="width:100%" />
<Select @bind-Value="@DataType" ShowLabel="true" />
<Select @bind-Value="@DataType" ShowLabel="true" />
</div>
</div>
<div class="col-12 col-md-4 p-1">
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
</div>
</div>
<div class="col-12 col-md-4 p-1">
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
}
<Divider />
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
</div>
</div>
}
<Divider />
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
</TabItem>
</Tab>

View File

@@ -254,7 +254,7 @@ internal class ComInterop
if (error != 0)
{
throw new ExternalException("COM初始化安全: " + GetSystemMessage(error), error);
throw new ExternalException("InitializeSecurity fail: " + GetSystemMessage(error), error);
}
}

View File

@@ -307,11 +307,11 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count);
if (PErrors.Any(a => a > 0))
{
throw new("读取错误,错误代码" + pErrors);
throw new("Read failCode" + pErrors);
}
}
else
throw new("连接无效");
throw new ArgumentNullException(nameof(m_Async2IO));
}
finally
{
@@ -387,7 +387,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
}
}
else
throw new("连接无效");
throw new ArgumentNullException(nameof(m_Async2IO));
}
protected virtual void Dispose(bool disposing)

View File

@@ -55,7 +55,7 @@ internal class OpcServer : IDisposable
internal OpcGroup AddGroup(string groupName, bool active, int reqUpdateRate, float deadBand)
{
if (null == m_OpcServer || IsConnected == false)
throw new("未初始化连接!");
throw new("Uninitialized connection");
OpcGroup group = new(groupName, active, reqUpdateRate, deadBand);
Guid riid = typeof(IOPCItemMgt).GUID;
m_OpcServer?.AddGroup(group.Name,
@@ -76,7 +76,7 @@ internal class OpcServer : IDisposable
}
else
{
throw new("添加OPC组错误OPC服务器返回null");
throw new("Error adding OPC group, OPC server returns null");
}
return group;
}
@@ -89,7 +89,7 @@ internal class OpcServer : IDisposable
lock (this)
{
if (null == m_OpcServer || IsConnected == false)
throw new("未初始化连接!");
throw new("Uninitialized connection");
var count = 0;
var moreElements = 0;
@@ -137,13 +137,13 @@ internal class OpcServer : IDisposable
object o = Comn.ComInterop.CreateInstance(info.CLSID, Host);
if (o == null)
{
throw new(string.Format("{0}{1}无法创建com对象", info.CLSID, Host));
throw new(string.Format("{0} {1} Unable to create com object", info.CLSID, Host));
}
m_OpcServer = (IOPCServer)o;
IsConnected = true;
}
else
throw new("应初始化Host与Name");
throw new("Host and Name should be initialized");
}
/// <summary>
@@ -156,7 +156,7 @@ internal class OpcServer : IDisposable
try
{
if (null == m_OpcServer || IsConnected == false)
throw new("未初始化连接!");
throw new("Uninitialized connection");
IntPtr statusPtr = IntPtr.Zero;
m_OpcServer?.GetStatus(out statusPtr);
OPCSERVERSTATUS status;
@@ -183,13 +183,13 @@ internal class OpcServer : IDisposable
else
{
IsConnected = false;
throw new("未知错误");
throw new("GetServerStatus error");
}
}
else
{
IsConnected = false;
throw new("未知错误");
throw new("GetServerStatus error");
}
}
finally

View File

@@ -38,13 +38,13 @@ internal class OpcDiscovery
{
if (string.IsNullOrEmpty(serverName))
{
throw new("检索失败需提供OPCName");
throw new ArgumentNullException(nameof(serverName));
}
ServerInfo result = null;
ServerInfo[] serverInfos = null;
object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host);
if (o_Server == null)
throw new("检索失败,请检查是否安装OPC Runtime");
throw new("GetOpcServer failed, please check if OPC runtime is installed");
try
{
Guid catid = CATID_OPC_DA20;
@@ -72,10 +72,7 @@ internal class OpcDiscovery
{
sb.AppendLine(item.ToString());
}
throw new($"无法创建OPCServer连接请检查OPC名称是否一致以下为{host}中的OPC列表:"
+ Environment.NewLine +
sb.ToString()
);
throw new($"Unable to create OPCServer connection. Please check if the OPC name is consistent. The following is a list of OPCServers in {host}:{Environment.NewLine}{sb}");
}
return result;
}

View File

@@ -1,9 +1,22 @@
{
"ThingsGateway.Foundation.OpcDa.OpcDaMaster": {
"ThingsGateway.Foundation.OpcDa.OpcDaProperty": {
"ActiveSubscribe": "ActiveSubscribe",
"CheckRate": "CheckRate(min)",
"DeadBand": "DeadBand",
"GroupSize": "GroupSize",
"OpcIP": "OpcIP",
"OpcName": "OpcName",
"UpdateRate": "UpdateRate",
"Connect": "Connect",
"Disconnect": "Disconnect",
"Add": "Add",
"Remove": "Remove",
"Read": "Read",
"Write": "Write",
"ShowImport": "View OPC space"
},
"ThingsGateway.Foundation.OpcDa.OpcDaResource": {
}
}

View File

@@ -1,9 +1,23 @@
{
"ThingsGateway.Foundation.OpcDa.OpcDaMaster": {
"ThingsGateway.Foundation.OpcDa.OpcDaProperty": {
"ActiveSubscribe": "订阅",
"CheckRate": "心跳间隔(min)",
"DeadBand": "死区",
"GroupSize": "最大组大小",
"OpcIP": "OpcIP",
"OpcName": "OpcName",
"UpdateRate": "订阅推送间隔",
"Connect": "连接",
"Disconnect": "断开",
"Add": "添加",
"Remove": "移除",
"Read": "读取",
"Write": "写入",
"ShowImport": "查看OPC空间"
},
"ThingsGateway.Foundation.OpcDa.OpcDaResource": {
}
}

View File

@@ -50,7 +50,7 @@ public class OpcDaMaster : IDisposable
/// <summary>
/// 当前配置
/// </summary>
public OpcDaConfig OpcDaConfig { get; private set; }
public OpcDaProperty OpcDaProperty { get; private set; }
/// <summary>
/// 数据变化事件
@@ -86,7 +86,7 @@ public class OpcDaMaster : IDisposable
#if (NET6_0_OR_GREATER)
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
throw new NotSupportedException("不支持非windows系统");
throw new NotSupportedException("Non Windows systems are not supported");
}
#endif
}
@@ -113,14 +113,14 @@ public class OpcDaMaster : IDisposable
/// <param name="items">组名称/变量节点,注意每次添加的组名称不能相同</param>
public void AddItems(Dictionary<string, List<OpcItem>> items)
{
if (IsExit == 1) throw new ObjectDisposedException("对象已释放");
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
foreach (var item in items)
{
if (IsExit == 1) throw new("对象已释放");
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
try
{
var subscription = m_server.AddGroup(item.Key, true, OpcDaConfig.UpdateRate, OpcDaConfig.DeadBand);
subscription.ActiveSubscribe = OpcDaConfig.ActiveSubscribe;
var subscription = m_server.AddGroup(item.Key, true, OpcDaProperty.UpdateRate, OpcDaProperty.DeadBand);
subscription.ActiveSubscribe = OpcDaProperty.ActiveSubscribe;
subscription.OnDataChanged += Subscription_OnDataChanged;
subscription.OnReadCompleted += Subscription_OnDataChanged;
@@ -132,7 +132,7 @@ public class OpcDaMaster : IDisposable
{
stringBuilder.Append($"{item1.Item1.ItemID}{item1.Item2}");
}
LogEvent?.Invoke(3, this, $"添加变量失败{stringBuilder}", null);
LogEvent?.Invoke(3, this, $"Failed to add variable{stringBuilder}", null);
}
else
{
@@ -141,7 +141,7 @@ public class OpcDaMaster : IDisposable
}
catch (Exception ex)
{
LogEvent?.Invoke(3, this, $"添加组失败{ex.Message}", ex);
LogEvent?.Invoke(3, this, $"Failed to add group{ex.Message}", ex);
}
}
for (int i = 0; i < Groups?.Count; i++)
@@ -166,7 +166,7 @@ public class OpcDaMaster : IDisposable
public Dictionary<string, List<OpcItem>> AddItemsWithSave(List<string> items)
{
int i = 0;
ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OpcDaConfig.GroupSize).ToDictionary(a => "default" + (i++));
ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OpcDaProperty.GroupSize).ToDictionary(a => "default" + (i++));
return ItemDicts;
}
@@ -198,8 +198,9 @@ public class OpcDaMaster : IDisposable
}
catch (Exception ex)
{
LogEvent?.Invoke(3, this, $"连接释放失败{ex.Message}", ex);
LogEvent?.Invoke(3, this, $"Disconnect warn{ex.Message}", ex);
}
checkTimer?.Dispose();
Interlocked.CompareExchange(ref IsExit, 1, 0);
}
@@ -226,13 +227,13 @@ public class OpcDaMaster : IDisposable
/// 初始化设置
/// </summary>
/// <param name="config"></param>
public void Init(OpcDaConfig config)
public void Init(OpcDaProperty config)
{
if (config != null)
OpcDaConfig = config;
OpcDaProperty = config;
checkTimer?.Stop();
checkTimer?.Dispose();
checkTimer = new Timer(Math.Min(OpcDaConfig.CheckRate, 1) * 60 * 1000);
checkTimer = new Timer(Math.Min(OpcDaProperty.CheckRate, 1) * 60 * 1000);
checkTimer.Elapsed += CheckTimer_Elapsed;
checkTimer.Start();
try
@@ -241,9 +242,9 @@ public class OpcDaMaster : IDisposable
}
catch (Exception ex)
{
LogEvent?.Invoke(3, this, $"连接释放失败{ex.Message}", ex);
LogEvent?.Invoke(3, this, $"Disconnect warn{ex.Message}", ex);
}
m_server = new OpcServer(OpcDaConfig.OpcName, OpcDaConfig.OpcIP);
m_server = new OpcServer(OpcDaProperty.OpcName, OpcDaProperty.OpcIP);
}
/// <summary>
@@ -253,7 +254,7 @@ public class OpcDaMaster : IDisposable
/// <returns></returns>
public void ReadItemsWithGroup(string groupName = null)
{
if (IsExit == 1) throw new ObjectDisposedException("对象已释放");
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
{
var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups;
foreach (var group in groups)
@@ -298,7 +299,7 @@ public class OpcDaMaster : IDisposable
/// <inheritdoc/>
public override string ToString()
{
return OpcDaConfig?.ToString();
return OpcDaProperty?.ToString();
}
/// <summary>
@@ -307,7 +308,7 @@ public class OpcDaMaster : IDisposable
/// <returns></returns>
public Dictionary<string, Tuple<bool, string>> WriteItem(Dictionary<string, object> writeInfos)
{
if (IsExit == 1) throw new ObjectDisposedException("对象已释放");
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
Dictionary<string, Tuple<bool, string>> results = new();
var valueGroup = writeInfos.GroupBy(itemId =>
@@ -324,7 +325,7 @@ public class OpcDaMaster : IDisposable
{
foreach (var item2 in item1)
{
results.AddOrUpdate(item2.Key, Tuple.Create(false, $"不存在该变量{item2.Key}"));
results.AddOrUpdate(item2.Key, Tuple.Create(false, $"The variable does not exist {item2.Key}"));
}
}
else
@@ -345,12 +346,12 @@ public class OpcDaMaster : IDisposable
var data = item1.ToList();
foreach (var item2 in result)
{
results.AddOrUpdate(handleItems[item2.Item1].ItemID, Tuple.Create(false, $"错误代码{item2.Item2}"));
results.AddOrUpdate(handleItems[item2.Item1].ItemID, Tuple.Create(false, $"Error code{item2.Item2}"));
}
}
foreach (var item2 in item1)
{
results.AddOrUpdate(item2.Key, Tuple.Create(true, $"成功"));
results.AddOrUpdate(item2.Key, Tuple.Create(true, $"Success"));
}
}
catch (Exception ex)
@@ -383,11 +384,11 @@ public class OpcDaMaster : IDisposable
try
{
PrivateConnect();
LogEvent?.Invoke(1, this, $"重新链接成功", null);
LogEvent?.Invoke(1, this, $"Successfully reconnected", null);
}
catch (Exception ex)
{
LogEvent?.Invoke(3, this, $"重新链接失败{ex.Message}", ex);
LogEvent?.Invoke(3, this, $"Reconnect failed{ex.Message}", ex);
}
}
}
@@ -409,7 +410,7 @@ public class OpcDaMaster : IDisposable
}
catch (Exception ex)
{
LogEvent?.Invoke(3, this, $"添加点位失败{ex.Message}", ex);
LogEvent?.Invoke(3, this, $"Add variable failed{ex.Message}", ex);
}
}
@@ -431,18 +432,18 @@ public class OpcDaMaster : IDisposable
}
catch
{
Init(OpcDaConfig);
Init(OpcDaProperty);
m_server?.Connect();
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - Connection successful", null);
PrivateAddItems();
}
}
}
else
{
Init(OpcDaConfig);
Init(OpcDaProperty);
m_server?.Connect();
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - Connection successful", null);
PrivateAddItems();
}
}
@@ -453,7 +454,7 @@ public class OpcDaMaster : IDisposable
lock (this)
{
if (IsConnected)
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 断开连接", null);
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - Disconnect", null);
if (checkTimer != null)
{
checkTimer.Enabled = false;
@@ -467,7 +468,7 @@ public class OpcDaMaster : IDisposable
}
catch (Exception ex)
{
LogEvent?.Invoke(3, this, $"连接释放失败{ex.Message}", ex);
LogEvent?.Invoke(3, this, $"Connection dispose failed{ex.Message}", ex);
}
}
}

View File

@@ -8,55 +8,46 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.ComponentModel;
namespace ThingsGateway.Foundation.OpcDa;
/// <summary>
/// OpcDA连接配置项
/// </summary>
public class OpcDaConfig
public class OpcDaProperty
{
/// <summary>
/// 是否订阅
/// </summary>
[Description("订阅")]
public bool ActiveSubscribe { get; set; } = true;
/// <summary>
/// 内部检测重连间隔/min
/// </summary>
[Description("重连间隔/min")]
public int CheckRate { get; set; } = 30;
/// <summary>
/// 死区
/// </summary>
[Description("死区")]
public float DeadBand { get; set; } = 0;
/// <summary>
/// 最大组大小
/// </summary>
[Description("最大组大小")]
public int GroupSize { get; set; } = 500;
/// <summary>
/// OpcIP
/// </summary>
[Description("OpcIP")]
public string OpcIP { get; set; } = "localhost";
/// <summary>
/// OpcNAME
/// OpcName
/// </summary>
[Description("OpcNAME")]
public string OpcName { get; set; } = "Kepware.KEPServerEX.V6";
/// <summary>
/// 订阅间隔
/// 订阅推送间隔
/// </summary>
[Description("订阅间隔")]
public int UpdateRate { get; set; } = 1000;
/// <summary>

View File

@@ -6,14 +6,12 @@
<DocumentationFile></DocumentationFile>
<IncludeSymbols>false</IncludeSymbols>
</PropertyGroup>
<ItemGroup>
<None Remove="Locales\en-US.json" />
<None Remove="Locales\zh-CN.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\en-US.json" />
<EmbeddedResource Include="Locales\zh-CN.json" />
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
</ItemGroup>

View File

@@ -27,7 +27,7 @@ internal static class DictionaryExtension
/// <param name="pairs"></param>
/// <param name="func"></param>
/// <returns></returns>
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func) where TKey : notnull
{
var list = new List<TKey>();
foreach (var item in pairs)
@@ -54,13 +54,13 @@ internal static class DictionaryExtension
/// <summary>
/// 尝试添加
/// </summary>
/// <typeparam name="Tkey"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="tkey"></param>
/// <param name="value"></param>
/// <returns></returns>
internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
internal static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value)
{
if (dictionary.ContainsKey(tkey))
{
@@ -75,13 +75,13 @@ internal static class DictionaryExtension
/// <summary>
/// 尝试添加
/// </summary>
/// <typeparam name="Tkey"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="tkey"></param>
/// <param name="value"></param>
/// <returns></returns>
internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
internal static void AddOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value) where TKey : notnull
{
if (dictionary.ContainsKey(tkey))
{
@@ -96,12 +96,12 @@ internal static class DictionaryExtension
/// <summary>
/// 获取值。如果键不存在,则返回默认值。
/// </summary>
/// <typeparam name="Tkey"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="tkey"></param>
/// <returns></returns>
internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
internal static TValue GetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey) where TKey : notnull
{
return dictionary.TryGetValue(tkey, out var value) ? value : default;
}

View File

@@ -1,9 +1,19 @@
{
"ThingsGateway.Foundation.OpcUa.OpcUaMaster": {
"ThingsGateway.Foundation.OpcUa.OpcUaProperty": {
"OpcUrl": "OpcUrl",
"UserName": "UserName",
"Password": "Password",
"CheckDomain": "CheckDomain",
"UpdateRate": "UpdateRate",
"ActiveSubscribe": "ActiveSubscribe",
"GroupSize": "GroupSize",
"DeadBand": "DeadBand",
"KeepAliveInterval": "KeepAliveInterval(ms)",
"UseSecurity": "IsUseSecurity",
"LoadType": "LoadType"
},
"ThingsGateway.Foundation.OpcUa.OpcUaResource": {
}
}

View File

@@ -1,6 +1,16 @@
{
"ThingsGateway.Foundation.OpcUa.OpcUaMaster": {
"ThingsGateway.Foundation.OpcUa.OpcUaProperty": {
"OpcUrl": "OpcUrl",
"UserName": "登录账号",
"Password": "登录密码",
"CheckDomain": "检查域",
"UpdateRate": "推送间隔",
"ActiveSubscribe": "订阅",
"GroupSize": "分组大小",
"DeadBand": "死区",
"KeepAliveInterval": "心跳间隔(ms)",
"UseSecurity": "安全策略",
"LoadType": "加载服务端数据类型"
},
"ThingsGateway.Foundation.OpcUa.OpcUaResource": {

View File

@@ -10,32 +10,7 @@
namespace ThingsGateway.Foundation.OpcUa;
/// <summary>
/// OPC UA的状态更新消息
/// </summary>
public class OpcUaStatusEventArgs
{
/// <summary>
/// 日志等级,<br></br>
/// 更为详细的步骤型日志输出 Trace = 0,<br></br>
/// 调试信息日志Debug = 1,<br></br>
/// 消息类日志输出 Info = 2,<br></br>
/// 警告类日志输出 Warning = 3,<br></br>
/// 错误类日志输出 Error = 4,<br></br>
/// 不可控中断类日输出Critical = 5,
/// </summary>
public int LogLevel { get; set; }
/// <summary>
/// 时间
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// 文本
/// </summary>
public string Text { get; set; }
}
/// <summary>
/// 读取属性过程中用于描述的

View File

@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation.OpcUa;
/// </summary>
/// <param name="value"></param>
public delegate void DataChangedEventHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) value);
/// <summary>
/// 日志输出
/// </summary>
public delegate void LogEventHandler(byte level, object sender, string message, Exception ex);
/// <summary>
/// OpcUaMaster
@@ -28,7 +32,7 @@ public class OpcUaMaster : IDisposable
/// <summary>
/// 当前配置
/// </summary>
public OpcUaConfig OpcUaConfig;
public OpcUaProperty OpcUaProperty;
/// <summary>
/// ProductUri
@@ -60,7 +64,6 @@ public class OpcUaMaster : IDisposable
private EventHandler m_ReconnectStarting;
private EventHandler<KeepAliveEventArgs> m_KeepAliveComplete;
private EventHandler<bool> m_ConnectComplete;
private EventHandler<OpcUaStatusEventArgs> m_OpcStatusChange;
private ISession m_session;
@@ -202,11 +205,7 @@ public class OpcUaMaster : IDisposable
/// <summary>
/// Raised after the client status change
/// </summary>
public event EventHandler<OpcUaStatusEventArgs> OpcStatusChange
{
add { m_OpcStatusChange += value; }
remove { m_OpcStatusChange -= value; }
}
public LogEventHandler LogEvent;
/// <summary>
/// 配置信息
@@ -263,15 +262,15 @@ public class OpcUaMaster : IDisposable
StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
AttributeId = Attributes.Value,
DisplayName = items[i],
Filter = OpcUaConfig.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaConfig.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
SamplingInterval = OpcUaConfig?.UpdateRate ?? 1000,
Filter = OpcUaProperty.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaProperty.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
SamplingInterval = OpcUaProperty?.UpdateRate ?? 1000,
};
item.Notification += Callback;
monitoredItems.Add(item);
}
catch (Exception ex)
{
UpdateStatus(3, DateTime.Now, $"初始化{items[i]}变量订阅失败,错误原因:{ex}");
Log(3, ex, $"Failed to initialize {items[i]} variable subscription");
}
}
m_subscription.AddItems(monitoredItems);
@@ -280,14 +279,14 @@ public class OpcUaMaster : IDisposable
m_subscription.Create();
foreach (var item in m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)))
{
item.Filter = OpcUaConfig.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaConfig.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
item.Filter = OpcUaProperty.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaProperty.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
}
m_subscription.ApplyChanges();
var isError = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
if (isError)
{
UpdateStatus(3, DateTime.Now, $"创建以下变量订阅失败{Environment.NewLine}{m_subscription.MonitoredItems.Where(
Log(3, null, $"Failed to create subscription for the following variables{Environment.NewLine}{m_subscription.MonitoredItems.Where(
a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode))
.Select(a => $"{a.StartNodeId.ToString()}{a.Status.Error.ToString()}").ToJsonString()}");
}
@@ -467,7 +466,7 @@ public class OpcUaMaster : IDisposable
/// </summary>
public async Task ConnectAsync(CancellationToken cancellationToken)
{
await ConnectAsync(OpcUaConfig.OpcUrl, cancellationToken);
await ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken);
}
/// <summary>
@@ -544,7 +543,7 @@ public class OpcUaMaster : IDisposable
results.Add(keys[i], Tuple.Create(false, result.Results[i].ToString()));
else
{
results.Add(keys[i], Tuple.Create(true, "成功"));
results.Add(keys[i], Tuple.Create(true, "Success"));
}
}
@@ -806,7 +805,7 @@ public class OpcUaMaster : IDisposable
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
if (data == null && value.Value != null)
{
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}转换出错原始值String为{value.Value}");
Log(3, null, $"{monitoreditem.StartNodeId}Conversion error, original value is{value.Value}");
var data1 = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
}
DataChangedHandler?.Invoke((variableNode, value, data!));
@@ -821,7 +820,7 @@ public class OpcUaMaster : IDisposable
}
catch (Exception ex)
{
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}订阅处理错误,错误原因:" + ex);
Log(3, ex, $"{monitoreditem.StartNodeId}Subscription processing error");
}
}
@@ -839,17 +838,17 @@ public class OpcUaMaster : IDisposable
if (m_configuration == null)
{
throw new ArgumentNullException("未初始化配置");
throw new ArgumentNullException(nameof(m_configuration));
}
var useSecurity = OpcUaConfig?.IsUseSecurity ?? true;
var useSecurity = OpcUaProperty?.UseSecurity ?? true;
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity, 10000);
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
ConfiguredEndpoint endpoint = new(null, endpointDescription, endpointConfiguration);
UserIdentity userIdentity;
if (!string.IsNullOrEmpty(OpcUaConfig.UserName))
if (!string.IsNullOrEmpty(OpcUaProperty.UserName))
{
userIdentity = new UserIdentity(OpcUaConfig.UserName, OpcUaConfig.Password);
userIdentity = new UserIdentity(OpcUaProperty.UserName, OpcUaProperty.Password);
}
else
{
@@ -862,7 +861,7 @@ public class OpcUaMaster : IDisposable
m_configuration,
endpoint,
false,
OpcUaConfig.CheckDomain,
OpcUaProperty.CheckDomain,
(string.IsNullOrEmpty(OPCUAName)) ? m_configuration.ApplicationName : OPCUAName,
60000,
userIdentity,
@@ -870,20 +869,20 @@ public class OpcUaMaster : IDisposable
).ConfigureAwait(false);
typeSystem = new ComplexTypeSystem(m_session);
m_session.KeepAliveInterval = OpcUaConfig.KeepAliveInterval == 0 ? 60000 : OpcUaConfig.KeepAliveInterval;
m_session.KeepAliveInterval = OpcUaProperty.KeepAliveInterval == 0 ? 60000 : OpcUaProperty.KeepAliveInterval;
m_session.KeepAlive += Session_KeepAlive;
// raise an event.
DoConnectComplete(true);
UpdateStatus(2, DateTime.UtcNow, "Connected");
Log(2, null, "Connected");
//如果是订阅模式,连接时添加订阅组
if (OpcUaConfig.ActiveSubscribe)
if (OpcUaProperty.ActiveSubscribe)
{
foreach (var item in Variables)
{
await AddSubscriptionAsync(Guid.NewGuid().ToString(), item.ToArray(), OpcUaConfig.LoadType);
await AddSubscriptionAsync(Guid.NewGuid().ToString(), item.ToArray(), OpcUaProperty.LoadType);
}
}
return m_session;
@@ -906,7 +905,7 @@ public class OpcUaMaster : IDisposable
if (state)
{
UpdateStatus(2, DateTime.UtcNow, "Disconnected");
Log(2, null, "Disconnected");
DoConnectComplete(false);
}
}
@@ -922,7 +921,7 @@ public class OpcUaMaster : IDisposable
{
if (m_session == null)
{
throw new("服务器未初始化连接");
throw new ArgumentNullException(nameof(m_session));
}
ReadValueIdCollection nodesToRead = new();
for (int i = 0; i < nodeIds.Length; i++)
@@ -970,7 +969,7 @@ public class OpcUaMaster : IDisposable
}
NodeId nodeToRead = new(nodeIdStr);
var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
if (OpcUaConfig.LoadType)
if (OpcUaProperty.LoadType)
await typeSystem.LoadType(node.DataType).ConfigureAwait(false);
_variableDicts.AddOrUpdate(nodeIdStr, node);
return node;
@@ -1016,7 +1015,7 @@ public class OpcUaMaster : IDisposable
}
else
{
UpdateStatus(3, DateTime.Now, $"获取服务器节点信息失败{nodes.Item2[i]}");
Log(3, null, $"Failed to obtain server node information {nodes.Item2[i]}");
}
}
return nodes.Item1.ToList();
@@ -1033,7 +1032,7 @@ public class OpcUaMaster : IDisposable
else if (eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted)
eventArgs.Accept = true;
else
throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
throw new Exception(string.Format("Verification certificate failed with error code: {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
}
private async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken cancellationToken)
@@ -1145,7 +1144,7 @@ public class OpcUaMaster : IDisposable
}
catch (Exception ex)
{
UpdateStatus(3, DateTime.Now, $"{nameof(Server_ReconnectComplete)}错误:{ex}");
Log(3, ex, $"{nameof(Server_ReconnectComplete)}");
}
}
@@ -1156,14 +1155,9 @@ public class OpcUaMaster : IDisposable
/// <param name="time">The time associated with the status.</param>
/// <param name="status">The status message.</param>
/// <param name="args">Arguments used to format the status message.</param>
private void UpdateStatus(int logLevel, DateTime time, string status, params object[] args)
private void Log(byte logLevel, Exception exception, string status, params object[] args)
{
m_OpcStatusChange?.Invoke(this, new OpcUaStatusEventArgs()
{
LogLevel = logLevel,
Time = time.ToLocalTime(),
Text = String.Format(status, args),
});
LogEvent?.Invoke(logLevel, this, string.Format(status, args), exception);
}
private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
@@ -1179,11 +1173,11 @@ public class OpcUaMaster : IDisposable
{
if (m_session.KeepAliveInterval <= 0)
{
UpdateStatus(3, e.CurrentTime, "Communication Error ({0})", e.Status);
Log(3, null, "Communication Error ({0})", e.Status);
return;
}
UpdateStatus(3, e.CurrentTime, "Reconnecting in {0}s", 10);
Log(3, null, "Reconnecting in {0}s", 10);
if (m_reConnectHandler == null)
{
@@ -1196,7 +1190,7 @@ public class OpcUaMaster : IDisposable
}
// update status.
UpdateStatus(0, e.CurrentTime, "Session_KeepAlive Connected [{0}]", session.Endpoint.EndpointUrl);
Log(0, null, "Session_KeepAlive Connected [{0}]", session.Endpoint.EndpointUrl);
// raise any additional notifications.
m_KeepAliveComplete?.Invoke(this, e);

View File

@@ -8,79 +8,64 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.ComponentModel;
namespace ThingsGateway.Foundation.OpcUa;
/// <summary>
/// OpcUaMaster配置项
/// </summary>
public class OpcUaConfig
public class OpcUaProperty
{
/// <summary>
/// OpcUrl
/// </summary>
[Description("OpcUrl")]
public string OpcUrl { get; set; } = "opc.tcp://127.0.0.1:49320";
/// <summary>
/// 登录账号
/// </summary>
[Description("登录账号")]
public string UserName { get; set; }
/// <summary>
/// 登录密码
/// </summary>
[Description("登录密码")]
public string Password { get; set; }
/// <summary>
/// 检查域
/// </summary>
[Description("检查域")]
public bool CheckDomain { get; set; }
/// <summary>
/// 更新间隔
/// </summary>
[Description("更新间隔")]
public int UpdateRate { get; set; } = 1000;
/// <summary>
/// 是否订阅
/// </summary>
[Description("是否订阅")]
public bool ActiveSubscribe { get; set; } = true;
/// <summary>
/// 分组大小
/// </summary>
[Description("分组大小")]
public int GroupSize { get; set; } = 500;
/// <summary>
/// 死区
/// </summary>
[Description("死区")]
public double DeadBand { get; set; } = 0;
/// <summary>
/// 心跳间隔/ms
/// </summary>
[Description("心跳间隔/ms")]
public int KeepAliveInterval { get; set; } = 3000;
/// <summary>
/// 安全策略
/// </summary>
[Description("安全策略")]
public bool IsUseSecurity { get; set; } = false;
public bool UseSecurity { get; set; } = false;
/// <summary>
/// 加载服务端数据类型
/// </summary>
[Description("加载服务端数据类型")]
public bool LoadType { get; set; } = true;
/// <inheritdoc/>

View File

@@ -11,20 +11,16 @@
<PropertyGroup>
<Description>工业设备通讯协议-OpcUa协议</Description>
</PropertyGroup>
<ItemGroup>
<None Remove="Locales\en-US.json" />
<None Remove="Locales\zh-CN.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\en-US.json" />
<EmbeddedResource Include="Locales\zh-CN.json" />
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.374.27" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.374.27" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.374.36" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.374.36" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />

View File

@@ -27,7 +27,7 @@ internal static class DictionaryExtension
/// <param name="pairs"></param>
/// <param name="func"></param>
/// <returns></returns>
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func) where TKey : notnull
{
var list = new List<TKey>();
foreach (var item in pairs)
@@ -54,13 +54,13 @@ internal static class DictionaryExtension
/// <summary>
/// 尝试添加
/// </summary>
/// <typeparam name="Tkey"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="tkey"></param>
/// <param name="value"></param>
/// <returns></returns>
internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
internal static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value)
{
if (dictionary.ContainsKey(tkey))
{
@@ -75,13 +75,13 @@ internal static class DictionaryExtension
/// <summary>
/// 尝试添加
/// </summary>
/// <typeparam name="Tkey"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="tkey"></param>
/// <param name="value"></param>
/// <returns></returns>
internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
internal static void AddOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value) where TKey : notnull
{
if (dictionary.ContainsKey(tkey))
{
@@ -96,12 +96,12 @@ internal static class DictionaryExtension
/// <summary>
/// 获取值。如果键不存在,则返回默认值。
/// </summary>
/// <typeparam name="Tkey"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="tkey"></param>
/// <returns></returns>
internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
internal static TValue GetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey) where TKey : notnull
{
return dictionary.TryGetValue(tkey, out var value) ? value : default;
}

View File

@@ -39,7 +39,7 @@
}
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage ></AdapterDebugComponent>
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent ></AdapterDebugComponent>
</div>

View File

@@ -34,7 +34,7 @@ namespace ThingsGateway.Debug
this.SafeDispose();
}
private AdapterDebugComponent AdapterDebugPage { get; set; }
private AdapterDebugComponent AdapterDebugComponent { get; set; }
private ChannelData ChannelData { get; set; }

View File

@@ -37,7 +37,7 @@
}
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage ></AdapterDebugComponent>
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent ></AdapterDebugComponent>
</div>

View File

@@ -34,7 +34,7 @@ namespace ThingsGateway.Debug
this.SafeDispose();
}
private AdapterDebugComponent AdapterDebugPage { get; set; }
private AdapterDebugComponent AdapterDebugComponent { get; set; }
private ChannelData ChannelData { get; set; }

View File

@@ -39,7 +39,7 @@
}
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText"></AdapterDebugComponent>
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent HeaderText="HeaderText"></AdapterDebugComponent>
</div>

View File

@@ -32,7 +32,7 @@ namespace ThingsGateway.Debug
this.SafeDispose();
}
private AdapterDebugComponent AdapterDebugPage { get; set; }
private AdapterDebugComponent AdapterDebugComponent { get; set; }
private ChannelData ChannelData { get; set; }

View File

@@ -11,5 +11,4 @@
global using System;
global using System.Collections.Generic;
global using ThingsGateway.Components;
global using ThingsGateway.Foundation;

View File

@@ -1,6 +1,19 @@
{
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
"NoVariablesAvailable": "No variables available",
"Success": "Success"
"ThingsGateway.Plugin.OpcDa.OpcDaMasterProperty": {
"ActiveSubscribe": "ActiveSubscribe",
"CheckRate": "CheckRate(min)",
"DeadBand": "DeadBand",
"GroupSize": "GroupSize",
"OpcIP": "OpcIP",
"OpcName": "OpcName",
"UpdateRate": "UpdateRate",
"SourceTimestampEnable": "SourceTimestampEnable"
},
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
"NoVariablesAvailable": "No variables available",
"Success": "Success"
}
}
}

View File

@@ -1,6 +1,16 @@
{
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
"NoVariablesAvailable": "无可用变量",
"Success": "成功"
}
"ThingsGateway.Plugin.OpcDa.OpcDaMasterProperty": {
"ActiveSubscribe": "订阅",
"CheckRate": "心跳间隔(min)",
"DeadBand": "死区",
"GroupSize": "最大组大小",
"OpcIP": "OpcIP",
"OpcName": "OpcName",
"UpdateRate": "订阅推送间隔",
"SourceTimestampEnable": "服务端时间戳"
},
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
"NoVariablesAvailable": "无可用变量",
"Success": "成功"
}
}

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using NewLife.Threading;
using Newtonsoft.Json.Linq;
using ThingsGateway.Foundation.OpcDa;
@@ -35,21 +37,13 @@ public class OpcDaMaster : CollectBase
public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcDaMaster);
/// <inheritdoc/>
public override CollectPropertyBase DriverProperties => _driverProperties;
public override CollectPropertyBase CollectProperties => _driverProperties;
public override Type DriverUIType => null;
/// <inheritdoc/>
protected override IProtocol Protocol => null;
protected override bool IsSingleThread
{
get
{
return true;
}
}
public override string ToString()
{
return $"{_driverProperties.OpcIP}-{_driverProperties.OpcName}";
@@ -59,7 +53,7 @@ public class OpcDaMaster : CollectBase
public override void Init(IChannel? channel = null)
{
//载入配置
OpcDaConfig opcNode = new()
OpcDaProperty opcNode = new()
{
OpcIP = _driverProperties.OpcIP,
OpcName = _driverProperties.OpcName,
@@ -73,10 +67,10 @@ public class OpcDaMaster : CollectBase
{
_plc = new();
_plc.DataChangedHandler += DataChangedHandler;
_plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
}
_plc.Init(opcNode);
base.Init(channel);
}
/// <inheritdoc/>
@@ -126,13 +120,14 @@ public class OpcDaMaster : CollectBase
/// <inheritdoc/>
protected override async Task<OperResult<byte[]>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken)
{
if (IsSingleThread)
// 如果是单线程模式,并且有其他线程正在等待写入锁
if (IsSingleThread && WriteLock.IsWaitting)
{
while (WriteLock.IsWaitting)
{
//等待写入完成
await Task.Delay(100);
}
// 等待写入锁释放
await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
// 立即释放写入锁,允许其他线程继续执行写入操作
WriteLock.Release();
}
try
{
@@ -180,11 +175,11 @@ public class OpcDaMaster : CollectBase
if (IsConnected())
{
//更新设备活动时间
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 0);
CurrentDevice.SetDeviceStatus(TimerX.Now, 0);
}
else
{
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 999);
CurrentDevice.SetDeviceStatus(TimerX.Now, 999);
}
}
else
@@ -211,7 +206,7 @@ public class OpcDaMaster : CollectBase
return;
if (_token.IsCancellationRequested)
return;
LogMessage.Trace($"{ToString()} 状态变化:{Environment.NewLine} {values?.ToJsonString()}");
LogMessage.Trace($"{ToString()} Change:{Environment.NewLine} {values?.ToJsonString()}");
foreach (var data in values)
{
@@ -224,7 +219,7 @@ public class OpcDaMaster : CollectBase
{
type = type.GetElementType();
}
var itemReads = CurrentDevice.VariableRunTimes.Where(it => it.RegisterAddress == data.Name).ToList();
var itemReads = CurrentDevice.VariableRunTimes.Values.Where(it => it.RegisterAddress == data.Name);
foreach (var item in itemReads)
{
if (!CurrentDevice.KeepRun)
@@ -264,7 +259,7 @@ public class OpcDaMaster : CollectBase
else
{
item.SetValue(null, time, false);
item.VariableSource.LastErrorMessage = $"错误质量戳{quality}";
item.VariableSource.LastErrorMessage = $"Bad quality{quality}";
}
}
}

View File

@@ -20,43 +20,50 @@ public class OpcDaMasterProperty : CollectPropertyBase
/// <summary>
/// OpcIP
/// </summary>
[DynamicProperty("IP", "")] public string OpcIP { get; set; } = "localhost";
[DynamicProperty]
public string OpcIP { get; set; } = "localhost";
/// <summary>
/// Opc名称
/// </summary>
[DynamicProperty("Opc名称", "")] public string OpcName { get; set; } = "Kepware.KEPServerEX.V6";
[DynamicProperty]
public string OpcName { get; set; } = "Kepware.KEPServerEX.V6";
/// <summary>
/// 是否使用SourceTime
/// </summary>
[DynamicProperty("使用SourceTime", "")]
[DynamicProperty]
public bool SourceTimestampEnable { get; set; } = true;
/// <summary>
/// 激活订阅
/// </summary>
[DynamicProperty("激活订阅", "")] public bool ActiveSubscribe { get; set; } = true;
[DynamicProperty]
public bool ActiveSubscribe { get; set; } = true;
/// <summary>
/// 检测重连频率min
/// </summary>
[DynamicProperty("检测重连频率min", "")] public int CheckRate { get; set; } = 10;
[DynamicProperty]
public int CheckRate { get; set; } = 10;
/// <summary>
/// 死区
/// </summary>
[DynamicProperty("死区", "")] public float DeadBand { get; set; } = 0;
[DynamicProperty]
public float DeadBand { get; set; } = 0;
/// <summary>
/// 最大组大小
/// </summary>
[DynamicProperty("最大组大小", "")] public int GroupSize { get; set; } = 500;
[DynamicProperty]
public int GroupSize { get; set; } = 500;
/// <summary>
/// 更新频率
/// </summary>
[DynamicProperty("更新频率", "")] public int UpdateRate { get; set; } = 1000;
[DynamicProperty]
public int UpdateRate { get; set; } = 1000;
public override int ConcurrentCount { get; set; } = 1;
}

View File

@@ -1,75 +1,99 @@
@page "/OpcDaMaster"
@using BootstrapBlazor.Components
@using ThingsGateway.Core.Extension
@using ThingsGateway.Foundation
@namespace ThingsGateway.Debug
@using ThingsGateway.Foundation.OpcDa
@using TouchSocket.Core
@inherits BaseComponentBase
<OpcDaMasterConnectPage @ref=opcDaMasterConnectPage OnConnectClick=OnConnectClick></OpcDaMasterConnectPage>
<AdapterDebugPage LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText" Height=@($"calc(100vh - {BlazorAppService.DefaultHeight+400}px)") ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
<div style="height:100%; width:100%;">
<ReadWriteContent>
@if (_plc?.OpcDaProperty != null)
{
<Card class="mt-2">
<BodyTemplate>
<MContainer>
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<MTooltip Right Context="tip">
<ActivatorContent>
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
Label=@AppService.I18n.T("Register Address") @attributes="@tip.Attrs" @bind-Value=@RegisterAddress />
</ActivatorContent>
<ChildContent>
<span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span>
</ChildContent>
</MTooltip>
<EditorForm Model="_plc.OpcDaProperty" AutoGenerateAllItem=false RowType="RowType.Inline" ItemsPerRow="4" ShowLabelTooltip="true" ShowLabel="true">
<FieldItems>
<MCol Align="AlignTypes.Center">
<MButton Class="mx-1 my-1" Color="primary" OnClick="Add">
@AppService.I18n.T("添加")
<EditorItem @bind-Field=context.OpcIP />
<EditorItem @bind-Field=context.OpcName />
<EditorItem @bind-Field=context.CheckRate />
<EditorItem @bind-Field=context.GroupSize />
<EditorItem @bind-Field=context.ActiveSubscribe />
<EditorItem @bind-Field=context.UpdateRate />
<EditorItem @bind-Field=context.DeadBand />
</MButton>
<MButton Class="mx-1 my-1" Color="primary" OnClick="Remove">
@AppService.I18n.T("移除")
</FieldItems>
</MButton>
<MButton Class="ma-1" Color="primary" OnClick="ReadAsync">
@AppService.I18n.T("读取")
</MButton>
</EditorForm>
</MCol>
</MRow>
<Button IsAsync Color="Color.Primary" OnClick="Connect">@OpcDaPropertyLocalizer["Connect"]</Button>
<Button IsAsync Color="Color.Warning" OnClick="Disconnect">@OpcDaPropertyLocalizer["Disconnect"]</Button>
</BodyTemplate>
</Card>
}
<AdapterDebugComponent Plc=null LogPath=@LogPath @ref=AdapterDebugComponent ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
<ReadWriteContent>
<BootstrapInput @bind-Value=@RegisterAddress
ShowLabel="true" style="width:100%" />
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
Label=@AppService.I18n.T("WriteValue") @bind-Value=@WriteValue />
<MCol Align="AlignTypes.Center">
<MButton Class="ma-1" Color="primary" OnClick="WriteAsync">
@AppService.I18n.T("Write")
</MButton>
</MCol>
</MRow>
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<div class="row mx-1 form-inline mt-2">
<MButton Class="ma-1" Color="primary" OnClick="ShowImport">
@AppService.I18n.T("查看Opc节点空间")
</MButton>
<div class="col-12 col-md-4 p-1">
</MRow>
<Button IsAsync Color="Color.Primary" OnClick="Add">@OpcDaPropertyLocalizer["Add"]</Button>
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="Remove">@OpcDaPropertyLocalizer["Remove"]</Button>
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@OpcDaPropertyLocalizer["Read"]</Button>
</div>
</div>
<Divider />
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-8 p-1">
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
ShowLabel="true" />
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@OpcDaPropertyLocalizer["Write"]</Button>
</div>
</div>
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="ShowImport">@OpcDaPropertyLocalizer["ShowImport"]</Button>
</div>
</div>
</MContainer>
</ReadWriteContent>
</ReadWriteContent>
</AdapterDebugComponent>
</div>
</AdapterDebugPage>

View File

@@ -8,117 +8,158 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json.Linq;
using ThingsGateway.Debug;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.OpcDa;
using ThingsGateway.Foundation.OpcDa.Da;
using TouchSocket.Core;
namespace ThingsGateway.Debug
namespace ThingsGateway.Debug;
public partial class OpcDaMaster : IDisposable
{
public partial class OpcDaMaster
public LoggerGroup? LogMessage;
private readonly OpcDaProperty OpcDaProperty = new();
private ThingsGateway.Foundation.OpcDa.OpcDaMaster _plc;
private string LogPath;
private string RegisterAddress;
private string WriteValue;
/// <inheritdoc/>
~OpcDaMaster()
{
private ThingsGateway.Foundation.OpcDa.OpcDaMaster Plc => opcDaMasterConnectPage.Plc;
private OpcDaMasterConnectPage opcDaMasterConnectPage;
private string RegisterAddress;
this.SafeDispose();
}
private AdapterDebugComponent AdapterDebugComponent { get; set; }
[Inject]
IStringLocalizer<OpcDaProperty> OpcDaPropertyLocalizer { get; set; }
/// <inheritdoc/>
public void Dispose()
{
_plc?.SafeDispose();
GC.SuppressFinalize(this);
}
protected override void OnInitialized()
{
_plc = new ThingsGateway.Foundation.OpcDa.OpcDaMaster();
private string LogPath;
private AdapterDebugComponent AdapterDebugPage { get; set; }
_plc.Init(OpcDaProperty);
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
var logger = TextFileLogger.Create(_plc.GetHashCode().ToLong().GetDebugLogPath());
logger.LogLevel = LogLevel.Trace;
LogMessage.AddLogger(logger);
private void OnConnectClick()
_plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
_plc.DataChangedHandler += (a) => LogMessage.Trace(a.ToJsonString());
base.OnInitialized();
}
private void Add()
{
var tags = new Dictionary<string, List<OpcItem>>();
var tag = new OpcItem(RegisterAddress);
tags.Add(Guid.NewGuid().ToString(), new List<OpcItem>() { tag });
try
{
_plc.AddItems(tags);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
catch (Exception ex)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
//载入配置
StateHasChanged();
}
}
protected override Task OnInitializedAsync()
{
return base.OnInitializedAsync();
}
private string WriteValue;
private void Add()
{
var tags = new Dictionary<string, List<OpcItem>>();
var tag = new OpcItem(RegisterAddress);
tags.Add(Guid.NewGuid().ToString(), new List<OpcItem>() { tag });
try
{
Plc.AddItems(tags);
}
catch (Exception ex)
{
opcDaMasterConnectPage.LogMessage?.LogWarning(ex, $"添加失败");
}
}
private async Task WriteAsync()
{
try
{
JToken tagValue = WriteValue.GetJTokenFromString();
var obj = tagValue.GetObjectFromJToken();
var data = Plc.WriteItem(
new()
{
{RegisterAddress, obj}
}
);
if (data.Count > 0)
{
foreach (var item in data)
{
if (item.Value.Item1)
opcDaMasterConnectPage.LogMessage?.LogInformation(item.ToJsonString());
else
opcDaMasterConnectPage.LogMessage?.LogWarning(item.ToJsonString());
}
}
}
catch (Exception ex)
{
opcDaMasterConnectPage.LogMessage?.LogWarning(ex, $"写入失败");
}
await Task.CompletedTask;
}
private async Task ShowImport()
{
await PopupService.OpenAsync(typeof(OpcDaImportVariable), new Dictionary<string, object?>()
{
{nameof(OpcDaImportVariable.Plc),Plc},
});
}
private async Task ReadAsync()
{
try
{
Plc.ReadItemsWithGroup();
}
catch (Exception ex)
{
opcDaMasterConnectPage.LogMessage?.LogWarning(ex, $"读取失败");
}
await Task.CompletedTask;
}
private void Remove()
{
Plc.RemoveItems(new List<string>() { RegisterAddress });
LogMessage?.LogWarning(ex);
}
}
private void Connect()
{
try
{
_plc.Disconnect();
LogPath = _plc?.GetHashCode().ToLong().GetDebugLogPath();
GetOpc().Connect();
}
catch (Exception ex)
{
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private void Disconnect()
{
try
{
_plc.Disconnect();
}
catch (Exception ex)
{
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private ThingsGateway.Foundation.OpcDa.OpcDaMaster GetOpc()
{
//载入配置
_plc.Init(OpcDaProperty);
return _plc;
}
private async Task ReadAsync()
{
try
{
_plc.ReadItemsWithGroup();
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex);
}
await Task.CompletedTask;
}
private void Remove()
{
_plc.RemoveItems(new List<string>() { RegisterAddress });
}
private async Task ShowImport()
{
// await PopupService.OpenAsync(typeof(OpcDaImportVariable), new Dictionary<string, object?>()
//{
// {nameof(OpcDaImportVariable._plc),_plc},
//});
await Task.CompletedTask;
}
private async Task WriteAsync()
{
try
{
JToken tagValue = WriteValue.GetJTokenFromString();
var obj = tagValue.GetObjectFromJToken();
var data = _plc.WriteItem(
new()
{
{RegisterAddress, obj}
}
);
if (data.Count > 0)
{
foreach (var item in data)
{
if (item.Value.Item1)
LogMessage?.LogInformation(item.ToJsonString());
else
LogMessage?.LogWarning(item.ToJsonString());
}
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex);
}
await Task.CompletedTask;
}
}

View File

@@ -1,53 +0,0 @@
@namespace ThingsGateway.Debug
@using BlazorComponent;
@using Microsoft.AspNetCore.Components.Web;
@using System.IO.Ports;
@using System.Collections.Concurrent;
@using ThingsGateway.Foundation.OpcDa;
@using ThingsGateway.Core.Extension;
@using Masa.Blazor
@inherits BaseComponentBase
@implements IDisposable
<MCard Class="ma-2">
<MSubheader Style="max-height:32px">
@AppService.I18n.T("Adapter Config")
</MSubheader>
<MContainer Class="ma-0">
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.GroupSize) @bind-Value=@config.GroupSize />
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.UpdateRate) @bind-Value=@config.UpdateRate />
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.DeadBand) @bind-Value=@config.DeadBand />
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.CheckRate) @bind-Value=@config.CheckRate />
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.OpcIP) @bind-Value=@config.OpcIP />
<MTextField Class="ma-1" Outlined Style="max-width:250px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.OpcName) @bind-Value=@config.OpcName />
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@(AppService.I18n.T(config.Description(x => x.ActiveSubscribe))) @bind-Value=@config.ActiveSubscribe></MCheckbox>
<MButton Class="ma-1" OnClick=@Connect Color="primary">
连接
</MButton>
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
断开
</MButton>
</MRow>
</MContainer>
</MCard>

View File

@@ -1,84 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.OpcDa;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
public partial class OpcDaMasterConnectPage : IDisposable
{
public ThingsGateway.Foundation.OpcDa.OpcDaMaster Plc;
private readonly OpcDaConfig config = new();
protected override void Dispose(bool disposing)
{
Plc.SafeDispose();
base.Dispose(disposing);
}
[Parameter]
public EventCallback OnConnectClick { get; set; }
public LoggerGroup? LogMessage;
/// <inheritdoc/>
protected override void OnInitialized()
{
Plc = new ThingsGateway.Foundation.OpcDa.OpcDaMaster();
Plc.Init(config);
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
var logger = TextFileLogger.Create(Plc.GetHashCode().ToLong().GetDebugLogPath());
logger.LogLevel = LogLevel.Trace;
LogMessage.AddLogger(logger);
Plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
Plc.DataChangedHandler += (a) => LogMessage.Trace(a.ToJsonString());
base.OnInitialized();
}
private void Connect()
{
try
{
Plc.Disconnect();
GetOpc().Connect();
}
catch (Exception ex)
{
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private void DisConnect()
{
try
{
Plc.Disconnect();
}
catch (Exception ex)
{
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private ThingsGateway.Foundation.OpcDa.OpcDaMaster GetOpc()
{
//载入配置
Plc.Init(config);
return Plc;
}
}

View File

@@ -7,29 +7,22 @@
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="@echo off&#xD;&#xA;setlocal enabledelayedexpansion&#xD;&#xA;&#xD;&#xA;set &quot;targetFWS=net6.0 net7.0 net8.0&quot;&#xD;&#xA;for %25%25f in (%25targetFWS%25) do (&#xD;&#xA; set &quot;dir=$(SolutionDir)bin\$(Configuration)\ThingsGateway.Web.Entry\%25%25f\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist &quot;!dir!&quot; md &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*OpcDa*.dll&quot; &quot;!dir!&quot;&#xD;&#xA;)&#xD;&#xA;&#xD;&#xA;endlocal&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
<Exec Command="@echo off&#xD;&#xA;setlocal enabledelayedexpansion&#xD;&#xA;&#xD;&#xA;set targetFWS=$(TargetFrameworks)&#xD;&#xA;for %25%25f in (%25targetFWS%25) do (&#xD;&#xA; set &quot;dir=$(SolutionDir)ThingsGateway.Server\bin\$(Configuration)\%25%25f\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist &quot;!dir!&quot; md &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*OpcDa*.dll&quot; &quot;!dir!&quot;&#xD;&#xA;)&#xD;&#xA;&#xD;&#xA;endlocal&#xD;&#xA;&#xD;&#xA;" />
</Target>
<ItemGroup>
<Content Remove="Locales\en-US.json" />
<Content Remove="Locales\zh-CN.json" />
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\en-US.json" />
<EmbeddedResource Include="Locales\zh-CN.json" />
<Compile Remove="Pages\OpcDaImportVariable.razor.cs" />
<Content Remove="Pages\OpcDaImportVariable.razor" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\foundation\ThingsGateway.Foundation.OpcDa\src\ThingsGateway.Foundation.OpcDa.csproj" />
<ProjectReference Include="..\ThingsGateway.Foundation.OpcDa\ThingsGateway.Foundation.OpcDa.csproj" />
<ProjectReference Include="$(SolutionDir)\adapter\ThingsGateway.Foundation.OpcDa\ThingsGateway.Foundation.OpcDa.csproj" />
</ItemGroup>
</Project>

View File

@@ -5,11 +5,8 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorComponent
@using Masa.Blazor
@using Masa.Blazor.Presets
@using ThingsGateway.Components;
@using ThingsGateway.Core;
@using ThingsGateway.Razor
@using ThingsGateway.Core
@using System.Net.Http.Json
@using System.IO;
@using System.IO
@using System.Text.Json;

View File

@@ -11,5 +11,4 @@
global using System;
global using System.Collections.Generic;
global using ThingsGateway.Components;
global using ThingsGateway.Foundation;

View File

@@ -2,5 +2,33 @@
"ThingsGateway.Plugin.OpcUa.OpcUaImportVariable": {
"NoVariablesAvailable": "No variables available",
"Success": "Success"
},
"ThingsGateway.Foundation.OpcUa.OpcUaProperty": {
"OpcUrl": "OpcUrl",
"UserName": "UserName",
"Password": "Password",
"CheckDomain": "CheckDomain",
"UpdateRate": "UpdateRate",
"ActiveSubscribe": "ActiveSubscribe",
"GroupSize": "GroupSize",
"DeadBand": "DeadBand",
"KeepAliveInterval": "KeepAliveInterval(ms)",
"UseSecurity": "UseSecurity",
"LoadType": "LoadType",
"SourceTimestampEnable": "SourceTimestampEnable"
},
"ThingsGateway.Plugin.OpcUa.OpcUaServerProperty": {
"IsAllVariable": "IsAllVariable",
"OpcUaStringUrl": "OpcUaStringUrl",
"BigTextSubjectName": "SubjectName",
"BigTextApplicationUri": "ApplicationUri",
"SecurityPolicy": "SecurityPolicy",
"AutoAcceptUntrustedCertificates": "AutoAcceptUntrustedCertificates"
},
"ThingsGateway.Plugin.OpcUa.OpcUaServerVariableProperty": {
"DataType": "DataType"
},
"ThingsGateway.Plugin.OpcUa.OpcUaServer": {
"CanStartService": "Can start service"
}
}

View File

@@ -1,6 +1,35 @@
{
"ThingsGateway.Plugin.OpcUa.OpcUaImportVariable": {
"NoVariablesAvailable": "无可用变量",
"Success": "成功"
}
"ThingsGateway.Plugin.OpcUa.OpcUaImportVariable": {
"NoVariablesAvailable": "无可用变量",
"Success": "成功"
},
"ThingsGateway.Plugin.OpcUa.OpcUaMasterProperty": {
"OpcUrl": "OpcUrl",
"UserName": "登录账号",
"Password": "登录密码",
"CheckDomain": "检查域",
"UpdateRate": "推送间隔",
"ActiveSubscribe": "订阅",
"GroupSize": "分组大小",
"DeadBand": "死区",
"KeepAliveInterval": "心跳间隔(ms)",
"UseSecurity": "安全策略",
"LoadType": "加载服务端数据类型",
"SourceTimestampEnable": "服务端时间戳"
},
"ThingsGateway.Plugin.OpcUa.OpcUaServerProperty": {
"IsAllVariable": "选择全部变量",
"OpcUaStringUrl": "服务地址",
"BigTextSubjectName": "SubjectName",
"BigTextApplicationUri": "ApplicationUri",
"SecurityPolicy": "安全策略",
"AutoAcceptUntrustedCertificates": "自动接受不受信任的证书"
},
"ThingsGateway.Plugin.OpcUa.OpcUaServerVariableProperty": {
"DataType": "数据类型"
},
"ThingsGateway.Plugin.OpcUa.OpcUaServer": {
"CanStartService": "无法启动服务"
}
}

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using NewLife.Threading;
using Newtonsoft.Json.Linq;
using Opc.Ua;
@@ -37,7 +39,7 @@ public class OpcUaMaster : CollectBase
public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcUaMaster);
/// <inheritdoc/>
public override CollectPropertyBase DriverProperties => _driverProperties;
public override CollectPropertyBase CollectProperties => _driverProperties;
public override Type DriverUIType => null;
@@ -61,14 +63,14 @@ public class OpcUaMaster : CollectBase
public override void Init(IChannel? channel = null)
{
//载入配置
OpcUaConfig config = new()
OpcUaProperty config = new()
{
OpcUrl = _driverProperties.OpcUrl,
UpdateRate = _driverProperties.UpdateRate,
DeadBand = _driverProperties.DeadBand,
GroupSize = _driverProperties.GroupSize,
KeepAliveInterval = _driverProperties.KeepAliveInterval,
IsUseSecurity = _driverProperties.IsUseSecurity,
UseSecurity = _driverProperties.UseSecurity,
ActiveSubscribe = _driverProperties.ActiveSubscribe,
UserName = _driverProperties.UserName,
Password = _driverProperties.Password,
@@ -78,12 +80,11 @@ public class OpcUaMaster : CollectBase
if (_plc == null)
{
_plc = new();
_plc.OpcStatusChange += _plc_OpcStatusChange;
_plc.LogEvent += _plc_LogEvent;
_plc.DataChangedHandler += DataChangedHandler;
}
_plc.OpcUaConfig = config;
_plc.OpcUaProperty = config;
base.Init(channel);
}
/// <inheritdoc/>
@@ -96,12 +97,6 @@ public class OpcUaMaster : CollectBase
await base.ProtectedBeforStartAsync(cancellationToken);
}
protected override Task ProtectedAfterStopAsync()
{
_plc?.Disconnect();
return base.ProtectedAfterStopAsync();
}
protected override string GetAddressDescription()
{
return _plc?.GetAddressDescription();
@@ -182,12 +177,12 @@ public class OpcUaMaster : CollectBase
item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString();
}
}
LogMessage.Trace($"{ToString()} 状态变化:{Environment.NewLine}{data.Item1} : {data.Item3}");
LogMessage.Trace($"{ToString()} Change:{Environment.NewLine}{data.Item1} : {data.Item3}");
}
}
if (result.Any(a => StatusCode.IsBad(a.Item2.StatusCode)))
{
return new OperResult<byte[]>($"读取失败");
return new OperResult<byte[]>($"OPC quality bad");
}
else
{
@@ -210,7 +205,7 @@ public class OpcUaMaster : CollectBase
var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken);
return result.ToDictionary(a =>
{
return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name;
return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name!;
}
, a =>
{
@@ -218,7 +213,7 @@ public class OpcUaMaster : CollectBase
return new OperResult(a.Value.Item2);
else
return new();
});
})!;
}
finally
{
@@ -235,11 +230,11 @@ public class OpcUaMaster : CollectBase
if (IsConnected())
{
//更新设备活动时间
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 0);
CurrentDevice.SetDeviceStatus(TimerX.Now, 0);
}
else
{
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 999);
CurrentDevice.SetDeviceStatus(TimerX.Now, 999);
}
}
else
@@ -255,7 +250,7 @@ public class OpcUaMaster : CollectBase
if (_plc != null)
{
_plc.DataChangedHandler -= DataChangedHandler;
_plc.OpcStatusChange -= _plc_OpcStatusChange;
_plc.LogEvent -= _plc_LogEvent;
_plc.Disconnect();
_plc.SafeDispose();
@@ -263,9 +258,9 @@ public class OpcUaMaster : CollectBase
base.Dispose(disposing);
}
private void _plc_OpcStatusChange(object? sender, OpcUaStatusEventArgs e)
private void _plc_LogEvent(byte level, object sender, string message, Exception ex)
{
LogMessage?.Log((LogLevel)e.LogLevel, null, e.Text, null);
LogMessage?.Log((LogLevel)level, sender, message, ex);
}
private void DataChangedHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) data)
@@ -277,7 +272,7 @@ public class OpcUaMaster : CollectBase
if (_token.IsCancellationRequested)
return;
LogMessage.Trace($"{ToString()} 状态变化: {Environment.NewLine} {data.variableNode.NodeId} : {data.jToken?.ToString()}");
LogMessage.Trace($"{ToString()} Change: {Environment.NewLine} {data.variableNode.NodeId} : {data.jToken?.ToString()}");
if (!CurrentDevice.KeepRun)
{
@@ -286,7 +281,7 @@ public class OpcUaMaster : CollectBase
//尝试固定点位的数据类型
var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
var itemReads = CurrentDevice.VariableRunTimes.Where(it => it.RegisterAddress == data.variableNode.NodeId).ToList();
var itemReads = CurrentDevice.VariableRunTimes.Values.Where(it => it.RegisterAddress == data.variableNode.NodeId);
object value;
if (data.jToken is JValue jValue)

View File

@@ -20,73 +20,73 @@ public class OpcUaMasterProperty : CollectPropertyBase
/// <summary>
/// 连接Url
/// </summary>
[DynamicProperty("连接Url", "")]
[DynamicProperty]
public string OpcUrl { get; set; } = "opc.tcp://127.0.0.1:49320";
/// <summary>
/// 登录账号
/// </summary>
[DynamicProperty("登录账号", "为空时将采用匿名方式登录")]
[DynamicProperty]
public string? UserName { get; set; }
/// <summary>
/// 登录密码
/// </summary>
[DynamicProperty("登录密码", "")]
[DynamicProperty]
public string? Password { get; set; }
/// <summary>
/// 检查域
/// </summary>
[DynamicProperty("检查域", "默认false")]
[DynamicProperty]
public bool CheckDomain { get; set; }
/// <summary>
/// 安全策略
/// </summary>
[DynamicProperty("安全策略", "True为使用安全策略False为无")]
public bool IsUseSecurity { get; set; } = true;
[DynamicProperty]
public bool UseSecurity { get; set; } = true;
/// <summary>
/// 是否使用SourceTime
/// </summary>
[DynamicProperty("使用SourceTime", "")]
[DynamicProperty]
public bool SourceTimestampEnable { get; set; } = true;
/// <summary>
/// 加载服务端数据类型
/// </summary>
[DynamicProperty("加载服务端数据类型")]
[DynamicProperty]
public bool LoadType { get; set; } = true;
/// <summary>
/// 激活订阅
/// </summary>
[DynamicProperty("激活订阅", "")]
[DynamicProperty]
public bool ActiveSubscribe { get; set; } = true;
/// <summary>
/// 更新频率
/// </summary>
[DynamicProperty("更新频率", "")]
[DynamicProperty]
public int UpdateRate { get; set; } = 1000;
/// <summary>
/// 死区
/// </summary>
[DynamicProperty("死区", "")]
[DynamicProperty]
public double DeadBand { get; set; } = 0;
/// <summary>
/// 最大组大小
/// </summary>
[DynamicProperty("最大组大小", "")]
[DynamicProperty]
public int GroupSize { get; set; } = 500;
/// <summary>
/// 心跳频率
/// </summary>
[DynamicProperty("心跳频率", "")]
[DynamicProperty]
public int KeepAliveInterval { get; set; } = 3000;
public override int ConcurrentCount { get; set; } = 1;

View File

@@ -66,8 +66,8 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
AddRootNotifier(rootFolder);
//创建设备树
var _geviceGroup = _businessBase.CurrentDevice.VariableRunTimes
.GroupBy(a => { if (a.DeviceName.IsNullOrEmpty()) return "内存变量"; else return a.DeviceName; });
var _geviceGroup = _businessBase.CurrentDevice.VariableRunTimes.Values
.GroupBy(a => a.DeviceName);
// 开始寻找设备信息,并计算一些节点信息
foreach (var item in _geviceGroup)
{
@@ -284,7 +284,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
catch (Exception ex)
{
if (success)
_businessBase.LogMessage.LogWarning(ex, "转化值错误");
_businessBase.LogMessage.LogWarning(ex, "Conversion value error");
success = false;
newValue = value;
}
@@ -400,7 +400,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
/// <returns></returns>
private NodeId DataNodeType(VariableRunTime variableRunTime)
{
var str = variableRunTime.GetPropertyValue(_businessBase.DeviceId, nameof(OpcUaServerVariableProperty.DataTypeEnum))?.Value ?? "";
var str = variableRunTime.GetPropertyValue(_businessBase.DeviceId, nameof(OpcUaServerVariableProperty.DataType)) ?? "";
Type tp;
if (Enum.TryParse(str, out DataTypeEnum result))
{

View File

@@ -132,13 +132,13 @@ public partial class ThingsGatewayServer : StandardServer
// 当用户身份改变时请求。
server.SessionManager.ImpersonateUser += SessionManager_ImpersonateUser;
base.OnServerStarted(server);
_businessBase.LogMessage.LogInformation("OPCUAServer启动成功");
_businessBase.LogMessage.LogInformation("OPCUAServer Started");
}
/// <inheritdoc/>
protected override void OnServerStarting(ApplicationConfiguration configuration)
{
_businessBase.LogMessage.LogInformation("OPCUAServer正在启动");
_businessBase.LogMessage.LogInformation("OPCUAServer Starting");
base.OnServerStarting(configuration);
// 由应用程序决定如何验证用户身份令牌。
@@ -149,7 +149,7 @@ public partial class ThingsGatewayServer : StandardServer
/// <inheritdoc/>
protected override void OnServerStopping()
{
_businessBase.LogMessage.LogInformation("OPCUAServer正在停止");
_businessBase.LogMessage.LogInformation("OPCUAServer Stoping");
base.OnServerStopping();
}
@@ -251,8 +251,8 @@ public partial class ThingsGatewayServer : StandardServer
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenRejected,
"Security cancellationToken is not a valid username cancellationToken. An empty password is not accepted.");
}
var _openApiUserService = _businessBase.ServiceScope.ServiceProvider.GetService<ISysUserService>();
var userInfo = _openApiUserService.GetUserByAccountAsync(userName).ConfigureAwait(true).GetAwaiter().GetResult();//获取用户信息
var sysUserService = App.RootServices.GetService<ISysUserService>();
var userInfo = sysUserService.GetUserByAccountAsync(userName).ConfigureAwait(true).GetAwaiter().GetResult();//获取用户信息
if (userInfo == null)
{
// construct translation object with default text.

View File

@@ -10,15 +10,20 @@
using Mapster;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using NewLife.Threading;
using Opc.Ua;
using Opc.Ua.Configuration;
using System.Collections.Concurrent;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Admin.Application;
using ThingsGateway.Core.Extension;
using ThingsGateway.Gateway.Application;
using ThingsGateway.Gateway.Application.Generic;
using TouchSocket.Core;
@@ -47,11 +52,10 @@ public partial class OpcUaServer : BusinessBase
public override void Init(IChannel? channel = null)
{
base.Init(channel);
if (_driverPropertys.IsAllVariable)
{
CurrentDevice.VariableRunTimes = GlobalData.AllVariables.ToList();
CollectDevices = GlobalData.CollectDevices.ToList();
CurrentDevice.VariableRunTimes = GlobalData.ReadOnlyVariables;
CollectDevices = GlobalData.ReadOnlyCollectDevices;
}
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(LogMessage);//默认返回true
@@ -72,12 +76,15 @@ public partial class OpcUaServer : BusinessBase
m_server = new(this);
CollectVariableRunTimes.Clear();
GlobalData.VariableValueChangeEvent += VariableValueChange;
CurrentDevice.VariableRunTimes.ForEach(a =>
{
VariableValueChange(a);
a.VariableValueChange += VariableValueChange;
VariableValueChange(a.Value, a.Value.Adapt<VariableData>());
});
Localizer = App.CreateLocalizerByType(typeof(OpcUaServer))!;
}
protected IStringLocalizer Localizer { get; private set; }
/// <inheritdoc/>
public override bool IsConnected() => m_server?.CurrentInstance.CurrentState == Opc.Ua.ServerState.Running;
@@ -85,20 +92,13 @@ public partial class OpcUaServer : BusinessBase
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
CurrentDevice?.VariableRunTimes?.ForEach(a =>
{
a.VariableValueChange -= VariableValueChange;
});
GlobalData.VariableValueChangeEvent -= VariableValueChange;
m_application?.Stop();
m_server?.SafeDispose();
CollectVariableRunTimes.Clear();
CollectVariableRunTimes?.Clear();
base.Dispose(disposing);
}
protected override Task ProtectedAfterStopAsync()
{
m_application.Stop();
return base.ProtectedAfterStopAsync();
}
protected override async Task ProtectedBeforStartAsync(CancellationToken cancellationToken)
{
@@ -115,11 +115,11 @@ public partial class OpcUaServer : BusinessBase
if (IsConnected())
{
//更新设备活动时间
CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 0);
CurrentDevice.SetDeviceStatus(TimerX.Now, 0);
}
else
{
CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 999);
CurrentDevice.SetDeviceStatus(TimerX.Now, 999);
try
{
await m_application.CheckApplicationInstanceCertificate(false, 0, 1200);
@@ -129,7 +129,7 @@ public partial class OpcUaServer : BusinessBase
catch (Exception ex)
{
if (success)
LogMessage.LogWarning(ex, "无法启动服务");
LogMessage.LogWarning(ex, Localizer["CanStartService"]);
success = false;
}
}
@@ -329,10 +329,11 @@ public partial class OpcUaServer : BusinessBase
return config;
}
private void VariableValueChange(VariableRunTime variableRunTime)
private void VariableValueChange(VariableRunTime variableRunTime, VariableData variableData)
{
if (!CurrentDevice.KeepRun)
return;
CollectVariableRunTimes.Enqueue(variableRunTime.Adapt<VariableData>());
if (DisposedValue) return;
CollectVariableRunTimes.Enqueue(variableData);
}
}

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Opc.Ua;
using ThingsGateway.Gateway.Application;
@@ -17,36 +19,38 @@ namespace ThingsGateway.Plugin.OpcUa;
/// <inheritdoc/>
public class OpcUaServerProperty : BusinessPropertyBase
{
[DynamicProperty("是否选择全部变量", "")]
[DynamicProperty]
public bool IsAllVariable { get; set; } = false;
/// <summary>
/// 服务地址
/// </summary>
[DynamicProperty("服务地址", "分号分割数组可设置多个url")]
[DynamicProperty(Remark = "分号分割数组可设置多个url")]
public string OpcUaStringUrl { get; set; } = "opc.tcp://127.0.0.1:49321";
/// <summary>
/// SubjectName
/// </summary>
[DynamicProperty("SubjectName", "")]
[DynamicProperty()]
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 1)]
public string BigTextSubjectName { get; set; } = "CN=ThingsGateway OPCUAServer, C=CN, S=GUANGZHOU, O=ThingsGateway, DC=" + System.Net.Dns.GetHostName();
/// <summary>
/// ApplicationUri
/// </summary>
[DynamicProperty("ApplicationUri", "")]
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 1)]
[DynamicProperty()]
public string BigTextApplicationUri { get; set; } = Utils.Format(@"urn:{0}:thingsgatewayopcuaserver", System.Net.Dns.GetHostName());
/// <summary>
/// 安全策略
/// </summary>
[DynamicProperty("安全策略", "")]
[DynamicProperty()]
public bool SecurityPolicy { get; set; }
/// <summary>
/// 接受不受信任的证书
/// </summary>
[DynamicProperty("自动接受不受信任的证书", "")]
[DynamicProperty()]
public bool AutoAcceptUntrustedCertificates { get; set; } = true;
}

View File

@@ -18,6 +18,6 @@ public class OpcUaServerVariableProperty : VariablePropertyBase
/// <summary>
/// 数据类型
/// </summary>
[DynamicProperty("数据类型", "")]
public DataTypeEnum DataTypeEnum { get; set; } = DataTypeEnum.Object;
[DynamicProperty()]
public DataTypeEnum DataType { get; set; } = DataTypeEnum.Object;
}

View File

@@ -1,125 +0,0 @@
@namespace ThingsGateway.Debug
@using BlazorComponent;
@using Microsoft.AspNetCore.Components.Web;
@using System.Reflection;
@using Opc.Ua
@using ThingsGateway.Foundation.OpcUa;
@using Masa.Blazor
@inherits BasePopupComponentBase
<PModal Persistent Title=@Plc?.OpcUaConfig?.ToString() Value="Visible" Width=1200 ValueChanged=@((a=>{if(!a)
ClosePopupAsync();})) OnSave="()=>{}" SaveText="关闭">
<SaveContent Context="save">
@{
#if Plugin
<MMenu OffsetY Context="menu">
<ActivatorContent>
<MButton @attributes="@menu.Attrs" Color="primary" Class="my-1 mx-2 " Disabled="isDownLoading" Loading="isDownLoading">
导出
<AppChevronDown></AppChevronDown>
</MButton>
</ActivatorContent>
<ChildContent>
<MList>
<MListItem Color="primary" OnClick="DownDeviceExport" Disabled="isDownLoading" > 导出到excel </MListItem>
<MListItem Color="primary" OnClick="DeviceImport" Disabled="isDownLoading" >导入到系统</MListItem>
</MList>
</ChildContent>
</MMenu>
#endif
}
</SaveContent>
<ChildContent>
@if (Visible)
{
<MRow Class="pa-4"
Justify="JustifyTypes.SpaceBetween">
<MCol Cols="12" Md="6">
<MTreeview @bind-Value=Selected @bind-Active=Actived SelectionType="SelectionType.Leaf"
Items=Nodes TItem=OpcUaTagModel
Color="warning" Selectable Activatable OpenOnClick
LoadChildren=PopulateBranchAsync
TKey=ReferenceDescription ItemKey="r=>r.Tag"
Style="height:500px;overflow-y:auto;"
ItemText="r=>r.Name"
ItemChildren="r=>r.Nodes">
</MTreeview>
</MCol>
<MCol Cols="12" Md="6">
<MCardText Style="height:500px;overflow-y:auto;">
@if (Actived?.Count == 0 || Actived?.FirstOrDefault()?.NodeId==null)
{
<div key="title"
class="text-h6 font-weight-light grey--text pa-4 text-center">
选择左侧节点
</div>
}
else
{
<ScrollXTransition>
<MSheet Outlined Class="ma-0 pa-1">
<MRow Align="AlignTypes.Center">
<MCol>
<MListItem>
<ItemContent>
<MListItemContent>
<MListItemTitle>NodeId </MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
</MCol>
<MDivider Vertical Center />
<MCol>
<MLabel Class=@($"ma-1")> @Actived.FirstOrDefault().NodeId </MLabel>
</MCol>
</MRow>
</MSheet>
@foreach (var item in nodeAttributes)
{
<MSheet Outlined Class="ma-0 pa-1">
<MRow Align="AlignTypes.Center">
<MCol>
<MListItem>
<ItemContent>
<MListItemContent>
<MListItemTitle>@item.Name </MListItemTitle>
</MListItemContent>
</ItemContent>
</MListItem>
</MCol>
<MDivider Vertical Center />
<MCol>
<MLabel Class=@($"{(StatusCode.IsBad(item.StatusCode)?"red--text":"green--text")} ma-1")> @item.Value </MLabel>
</MCol>
</MRow>
</MSheet>
}
</ScrollXTransition>
}
</MCardText>
</MCol>
<MDivider Vertical></MDivider>
</MRow>
<MOverlay Absolute Value="overlay" Opacity="0.8">
<span class="green--text text--darken-2">
@("正在获取OpcUa节点...")
</span>
<MProgressCircular Indeterminate Width=6 Size="30"></MProgressCircular>
</MOverlay>
}
</ChildContent>
</PModal>

View File

@@ -1,425 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using BlazorComponent;
using BootstrapBlazor.Components;
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Opc.Ua;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ThingsGateway.Admin.Application;
using ThingsGateway.Foundation.OpcUa;
#if Plugin
using ThingsGateway.Gateway.Application;
using ThingsGateway.Plugin.OpcUa;
#endif
namespace ThingsGateway.Debug;
/// <summary>
/// 导入变量
/// </summary>
public partial class OpcUaImportVariable : BasePopupComponentBase
{
private List<ReferenceDescription> actived = new();
private OPCNodeAttribute[] nodeAttributes;
private List<OpcUaTagModel> Nodes = new();
private bool overlay = true;
[Inject]
[NotNull]
private IStringLocalizer<OpcUaImportVariable>? Localizer { get; set; }
[Inject]
[NotNull]
private ToastService? ToastService { get; set; }
/// <summary>
/// Opc对象
/// </summary>
[Parameter]
public ThingsGateway.Foundation.OpcUa.OpcUaMaster Plc { get; set; }
/// <summary>
/// 是否显示子变量
/// </summary>
[Parameter]
public bool IsShowSubvariable { get; set; }
private List<ReferenceDescription> Actived
{
get => actived;
set
{
if (actived?.FirstOrDefault() != value?.FirstOrDefault() && value?.Count > 0)
{
actived = value;
if (actived.FirstOrDefault().NodeId != null)
nodeAttributes = Plc.ReadNoteAttributes(actived.FirstOrDefault().NodeId.ToString());
}
}
}
private List<ReferenceDescription> Selected { get; set; } = new();
#if Plugin
private bool isDownLoading;
/// <summary>
/// 获取设备与变量列表
/// </summary>
/// <returns></returns>
public async Task<(Channel, Device, List<Variable>)> GetImportVariableList()
{
var channel = GetImportChannel();
var device = GetImportDevice(channel.Id);
foreach (var node in Selected.ToList())
{
List<OpcUaTagModel> nodes = await PopulateBranchAsync((NodeId)node.NodeId, true, IsShowSubvariable);
if (nodes.Count > 0)
{
Selected.AddRange(nodes.SelectMany(a => a.GetAllTags()).Select(a => a.Tag).Where(a => a != null).ToList());
}
}
var data = (await SelectAsync(Selected, async a =>
{
var nodeClass = (await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.NodeClass)).FirstOrDefault().Value.ToString();
if (nodeClass == nameof(NodeClass.Variable))
{
ProtectTypeEnum level = ProtectTypeEnum.ReadOnly;
DataTypeEnum dataTypeEnum = DataTypeEnum.Object;
try
{
var userAccessLevel = (AccessLevelType)(await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.UserAccessLevel)).FirstOrDefault().Value;
level = (userAccessLevel.HasFlag(AccessLevelType.CurrentRead)) ?
userAccessLevel.HasFlag(AccessLevelType.CurrentWrite) ?
ProtectTypeEnum.ReadWrite : ProtectTypeEnum.ReadOnly : ProtectTypeEnum.WriteOnly;
var dataTypeId = (Opc.Ua.NodeId)(await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.DataType)).FirstOrDefault().Value;
var dataType = Opc.Ua.TypeInfo.GetSystemType(dataTypeId, Plc.Session.Factory);
var result = dataType != null && Enum.TryParse<DataTypeEnum>(dataType.Name, out dataTypeEnum);
if (!result)
{
dataTypeEnum = DataTypeEnum.Object;
}
}
catch
{
}
var id = Yitter.IdGenerator.YitIdHelper.NextId();
return new Variable()
{
Name = a.DisplayName.Text + "-" + id,
RegisterAddress = a.NodeId.ToString(),
DeviceId = device.Id,
DataType = dataTypeEnum,
Enable = true,
Id = id,
ProtectType = level,
IntervalTime = 1000,
RpcWriteEnable = true,
};
}
else
{
return null;
}
})).Where(a => a != null).ToList();
return (channel, device, data);
}
private Device GetImportDevice(long channelId)
{
var id = Yitter.IdGenerator.YitIdHelper.NextId();
var data = new Device()
{
Name = Plc.OpcUaConfig.OpcUrl + "-" + id,
Id = id,
ChannelId = channelId,
Enable = true,
DevicePropertys = new(),
PluginName = "ThingsGateway.Plugin.OpcUa.OpcUaMaster",
};
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.OpcUrl),Plc.OpcUaConfig.OpcUrl);
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UserName), Plc.OpcUaConfig.UserName);
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.Password), Plc.OpcUaConfig.Password);
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.CheckDomain), Plc.OpcUaConfig.CheckDomain.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.LoadType), Plc.OpcUaConfig.LoadType.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.IsUseSecurity), Plc.OpcUaConfig.IsUseSecurity.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.ActiveSubscribe), true.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.DeadBand), Plc.OpcUaConfig.DeadBand.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.GroupSize), Plc.OpcUaConfig.GroupSize.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UpdateRate), Plc.OpcUaConfig.UpdateRate.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.KeepAliveInterval), Plc.OpcUaConfig.KeepAliveInterval.ToString());
return data;
}
private Channel GetImportChannel()
{
var id = Yitter.IdGenerator.YitIdHelper.NextId();
var data = new Channel()
{
Name = Plc.OpcUaConfig.OpcUrl + "-" + id,
Id = id,
Enable = true,
ChannelType = ChannelTypeEnum.Other,
};
return data;
}
private async Task DeviceImport()
{
isDownLoading = true;
await InvokeStateHasChangedAsync();
try
{
var data = await GetImportVariableList();
if (data.Item3 == null || data.Item3?.Count == 0)
{
await ToastService.Warning(Localizer["NoVariablesAvailable"], Localizer["NoVariablesAvailable"]);
return;
}
await App.RootServices.GetRequiredService<IChannelService>().SaveChannelAsync(data.Item1, ItemChangedType.Add);
await App.RootServices.GetRequiredService<IDeviceService>().SaveDeviceAsync(data.Item2, ItemChangedType.Add);
await App.RootServices.GetRequiredService<IVariableService>().AddBatchAsync(data.Item3, ItemChangedType.Add);
await ToastService.Success(Localizer["Success"], Localizer["Success"]);
}
finally
{
isDownLoading = false;
}
}
private async Task DownDeviceExport()
{
isDownLoading = true;
await InvokeAsync(StateHasChanged);
try
{
var data = await GetImportVariableList();
if (data.Item3 == null || data.Item3?.Count == 0)
{
await ToastService.Warning(Localizer["NoVariablesAvailable"], Localizer["NoVariablesAvailable"]);
return;
}
await DownChannelExportAsync(data.Item1);
await DownDeviceExportAsync(data.Item2, data.Item1.Name);
await DownDeviceVariableExportAsync(data.Item3, data.Item2.Name);
await ToastService.Success(Localizer["Success"], Localizer["Success"]);
}
finally
{
isDownLoading = false;
}
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownChannelExportAsync(Channel data)
{
using var memoryStream = await App.RootServices.GetRequiredService<IChannelService>().ExportMemoryStream(new List<Channel>() { data });
//await AppService.DownXlsxAsync(memoryStream, "通道导出");
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownDeviceExportAsync(Device data, string channelName)
{
using var memoryStream = await App.RootServices.GetRequiredService<IDeviceService>().ExportMemoryStream(new List<Device>() { data }, PluginTypeEnum.Collect, channelName);
//await AppService.DownXlsxAsync(memoryStream, "设备导出");
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownDeviceVariableExportAsync(List<Variable> data, string devName)
{
using var memoryStream = await App.RootServices.GetRequiredService<IVariableService>().ExportMemoryStream(data, devName);
//await AppService.DownXlsxAsync(memoryStream, "变量导出");
}
#endif
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
Task.Run(async () =>
{
Nodes = await PopulateBranchAsync(ObjectIds.ObjectsFolder, isShowSubvariable: IsShowSubvariable);
overlay = false;
await InvokeAsync(StateHasChanged);
});
base.OnInitialized();
}
private async Task<ReferenceDescriptionCollection> GetReferenceDescriptionCollectionAsync(NodeId sourceId)
{
BrowseDescription nodeToBrowse1 = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.Aggregates,
IncludeSubtypes = true,
NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.View | NodeClass.VariableType | NodeClass.DataType),
ResultMask = (uint)BrowseResultMask.All
};
BrowseDescription nodeToBrowse2 = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.Organizes,
IncludeSubtypes = true,
NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.View | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType | NodeClass.DataType),
ResultMask = (uint)BrowseResultMask.All
};
BrowseDescriptionCollection nodesToBrowse = new()
{
nodeToBrowse1,
nodeToBrowse2
};
ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(Plc.Session, nodesToBrowse, false);
return references;
}
private async Task PopulateBranchAsync(OpcUaTagModel model)
{
var sourceId = (NodeId)model.Tag.NodeId;
model.Nodes = await PopulateBranchAsync(sourceId, isShowSubvariable: IsShowSubvariable);
}
private async Task<List<OpcUaTagModel>> PopulateBranchAsync(NodeId sourceId, bool isAll = false, bool isShowSubvariable = false)
{
if (!Plc.Connected)
{
return new() { new() { Name = "未完成连接", Tag = new(), Nodes = null } };
}
List<OpcUaTagModel> nodes = new()
{
new OpcUaTagModel() { Name = "Browsering..." }
};
ReferenceDescriptionCollection references = await GetReferenceDescriptionCollectionAsync(sourceId);
List<OpcUaTagModel> list = new();
if (references != null)
{
for (int ii = 0; ii < references.Count; ii++)
{
ReferenceDescription target = references[ii];
OpcUaTagModel child = new()
{
Name = Utils.Format("{0}", target),
Tag = target
};
if (isShowSubvariable || target.NodeClass != NodeClass.Variable)
{
var data = await GetReferenceDescriptionCollectionAsync((NodeId)target.NodeId);
if (data != null && data.Count > 0)
{
if (isAll)
child.Nodes = await PopulateBranchAsync((NodeId)target.NodeId, isShowSubvariable: IsShowSubvariable);
else
child.Nodes = new();
}
else
{
child.Nodes = null;
}
}
else
{
child.Nodes = null;
}
////if (target.NodeClass != NodeClass.Variable) //这个判断注释后会让子节点也是变量的情况下无法加载
//{
// var data = await GetReferenceDescriptionCollectionAsync((NodeId)target.NodeId);
// if (data != null && data.Count > 0)
// {
// if (isAll)
// child.Nodes = await PopulateBranchAsync((NodeId)target.NodeId);
// else
// child.Nodes = new();
// }
// else
// {
// child.Nodes = null;
// }
//}
////else
////{
//// child.Nodes = null;
////}
list.Add(child);
}
}
List<OpcUaTagModel> listNode = list;
nodes.Clear();
nodes.AddRange(listNode.ToArray());
return nodes;
}
private async Task<TResult[]> SelectAsync<T, TResult>(IEnumerable<T> source, Func<T, Task<TResult>> selector)
{
return await Task.WhenAll(source.Select(selector));
}
internal class OpcUaTagModel
{
internal string Name { get; set; }
internal string NodeId => (Tag.NodeId).ToString();
internal List<OpcUaTagModel> Nodes { get; set; } = new();
internal ReferenceDescription Tag { get; set; }
public List<OpcUaTagModel> GetAllTags()
{
List<OpcUaTagModel> allTags = new();
GetAllTagsRecursive(this, allTags);
return allTags;
}
private void GetAllTagsRecursive(OpcUaTagModel parentTag, List<OpcUaTagModel> allTags)
{
allTags.Add(parentTag);
if (parentTag.Nodes != null)
foreach (OpcUaTagModel childTag in parentTag.Nodes)
{
GetAllTagsRecursive(childTag, allTags);
}
}
}
}

View File

@@ -1,75 +1,103 @@
@page "/OpcUaMaster"
@using BootstrapBlazor.Components
@using ThingsGateway.Core.Extension
@using ThingsGateway.Foundation
@namespace ThingsGateway.Debug
@using ThingsGateway.Foundation.OpcUa
@using TouchSocket.Core
@inherits BaseComponentBase
<OpcUaMasterConnectPage @ref=opcUaMasterConnectPage OnConnectClick=OnConnectClick></OpcUaMasterConnectPage>
<AdapterDebugPage LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText" Height=@($"calc(100vh - {BlazorAppService.DefaultHeight+400}px)") ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
<ReadWriteContent>
<MContainer>
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<MTooltip Right Context="tip">
<ActivatorContent>
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
Label=@AppService.I18n.T("Register Address") @attributes="@tip.Attrs" @bind-Value=@RegisterAddress />
</ActivatorContent>
<ChildContent>
<span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span>
</ChildContent>
</MTooltip>
<MCol Align="AlignTypes.Center">
<MButton Class="mx-1 my-1" Color="primary" OnClick="Add">
@AppService.I18n.T("添加")
<div style="height:100%; width:100%;">
</MButton>
<MButton Class="mx-1 my-1" Color="primary" OnClick="Remove">
@AppService.I18n.T("移除")
@if (_plc?.OpcUaProperty != null)
{
<Card class="mt-2">
<BodyTemplate>
</MButton>
<MButton Class="ma-1" Color="primary" OnClick="ReadAsync">
@AppService.I18n.T("读取")
</MButton>
</MCol>
</MRow>
<EditorForm Model="_plc.OpcUaProperty" AutoGenerateAllItem=false RowType="RowType.Inline" ItemsPerRow="4" ShowLabelTooltip="true" ShowLabel="true">
<FieldItems>
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<EditorItem @bind-Field=context.CheckDomain />
<EditorItem @bind-Field=context.UseSecurity />
<EditorItem @bind-Field=context.ActiveSubscribe />
<EditorItem @bind-Field=context.GroupSize />
<EditorItem @bind-Field=context.UpdateRate />
<EditorItem @bind-Field=context.DeadBand />
<EditorItem @bind-Field=context.Password />
<EditorItem @bind-Field=context.OpcUrl />
<EditorItem @bind-Field=context.UserName />
<EditorItem @bind-Field=context.LoadType />
<EditorItem @bind-Field=context.KeepAliveInterval />
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
Label=@AppService.I18n.T("WriteValue") @bind-Value=@WriteValue />
<MCol Align="AlignTypes.Center">
<MButton Class="ma-1" Color="primary" OnClick="WriteAsync">
@AppService.I18n.T("Write")
</MButton>
</MCol>
</MRow>
</FieldItems>
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
</EditorForm>
<MCheckbox Class="ma-1" HideDetails="@("auto")" @bind-Value=IsShowSubvariable Dense Label="是否显示子变量" />
<Button IsAsync Color="Color.Primary" OnClick="Connect">@OpcUaPropertyLocalizer["Connect"]</Button>
<MButton Class="ma-1" Color="primary" OnClick="ShowImport">
@AppService.I18n.T("查看Opc节点空间")
</MButton>
<Button IsAsync Color="Color.Warning" OnClick="Disconnect">@OpcUaPropertyLocalizer["Disconnect"]</Button>
</BodyTemplate>
</Card>
</MRow>
}
<AdapterDebugComponent Plc=null LogPath=@LogPath @ref=AdapterDebugComponent ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
<ReadWriteContent>
<BootstrapInput @bind-Value=@RegisterAddress
ShowLabel="true" style="width:100%" />
</MContainer>
<div class="row mx-1 form-inline mt-2">
</ReadWriteContent>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="Add">@OpcUaPropertyLocalizer["Add"]</Button>
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="Remove">@OpcUaPropertyLocalizer["Remove"]</Button>
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@OpcUaPropertyLocalizer["Read"]</Button>
</div>
</div>
<Divider />
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-8 p-1">
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
ShowLabel="true" />
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@OpcUaPropertyLocalizer["Write"]</Button>
</div>
</div>
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="ShowImport">@OpcUaPropertyLocalizer["ShowImport"]</Button>
</div>
</div>
</ReadWriteContent>
</AdapterDebugComponent>
</div>
</AdapterDebugPage>

View File

@@ -8,126 +8,151 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Debug;
using ThingsGateway.Foundation;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Foundation.OpcUa;
using TouchSocket.Core;
namespace ThingsGateway.Debug
namespace ThingsGateway.Debug;
public partial class OpcUaMaster : IDisposable
{
public partial class OpcUaMaster
public LoggerGroup? LogMessage;
private readonly OpcUaProperty OpcUaProperty = new();
private ThingsGateway.Foundation.OpcUa.OpcUaMaster _plc;
private string LogPath;
private string RegisterAddress;
private string WriteValue;
/// <inheritdoc/>
~OpcUaMaster()
{
private ThingsGateway.Foundation.OpcUa.OpcUaMaster Plc => opcUaMasterConnectPage.Plc;
private OpcUaMasterConnectPage opcUaMasterConnectPage;
private string RegisterAddress;
this.SafeDispose();
}
private AdapterDebugComponent AdapterDebugComponent { get; set; }
[Inject]
IStringLocalizer<OpcUaProperty> OpcUaPropertyLocalizer { get; set; }
/// <inheritdoc/>
public void Dispose()
{
_plc?.SafeDispose();
GC.SuppressFinalize(this);
}
protected override void OnInitialized()
{
_plc = new ThingsGateway.Foundation.OpcUa.OpcUaMaster();
_plc.OpcUaProperty = OpcUaProperty;
private string LogPath;
private AdapterDebugComponent AdapterDebugPage { get; set; }
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
var logger = TextFileLogger.Create(_plc.GetHashCode().ToLong().GetDebugLogPath());
logger.LogLevel = LogLevel.Trace;
LogMessage.AddLogger(logger);
private void OnConnectClick()
_plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
_plc.DataChangedHandler += (a) => LogMessage.Trace(a.ToJsonString());
base.OnInitialized();
}
private async Task Connect()
{
try
{
_plc.Disconnect();
LogPath = _plc?.GetHashCode().ToLong().GetDebugLogPath();
await GetOpc().ConnectAsync(CancellationToken.None);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
catch (Exception ex)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
//载入配置
StateHasChanged();
}
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
protected override Task OnInitializedAsync()
}
private void Disconnect()
{
try
{
return base.OnInitializedAsync();
_plc.Disconnect();
}
private string WriteValue;
private async Task Add()
catch (Exception ex)
{
if (Plc.Connected)
await Plc.AddSubscriptionAsync(Guid.NewGuid().ToString(), new[] { RegisterAddress });
else
{
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
}
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private async Task WriteAsync()
private ThingsGateway.Foundation.OpcUa.OpcUaMaster GetOpc()
{
//载入配置
_plc.OpcUaProperty = OpcUaProperty;
return _plc;
}
private async Task Add()
{
try
{
if (_plc.Connected)
await _plc.AddSubscriptionAsync(Guid.NewGuid().ToString(), new[] { RegisterAddress });
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex);
}
}
private async Task ReadAsync()
{
if (_plc.Connected)
{
try
{
if (Plc.Connected)
{
var data = await Plc.WriteNodeAsync(
new()
{
{RegisterAddress, WriteValue.GetJTokenFromString()}
}
);
var data = await _plc.ReadJTokenValueAsync(new string[] { RegisterAddress });
foreach (var item in data)
{
if (item.Value.Item1)
opcUaMasterConnectPage.LogMessage?.LogInformation(item.ToJsonString());
else
opcUaMasterConnectPage.LogMessage?.LogWarning(item.ToJsonString());
}
}
else
{
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
}
LogMessage?.LogInformation($" {data[0].Item1}{data[0].Item3}");
}
catch (Exception ex)
{
opcUaMasterConnectPage.LogMessage?.LogWarning(ex, $"写入失败");
LogMessage?.LogWarning(ex);
}
}
private async Task ShowImport()
{
await PopupService.OpenAsync(typeof(OpcUaImportVariable), new Dictionary<string, object?>()
{
{nameof(OpcUaImportVariable.Plc),Plc},
{nameof(OpcUaImportVariable.IsShowSubvariable),IsShowSubvariable},
});
}
}
private bool IsShowSubvariable;
private void Remove()
{
if (_plc.Connected)
_plc.RemoveSubscription("");
}
private async Task ReadAsync()
private async Task ShowImport()
{
// await PopupService.OpenAsync(typeof(OpcUaImportVariable), new Dictionary<string, object?>()
//{
// {nameof(OpcUaImportVariable._plc),_plc},
//});
await Task.CompletedTask;
}
private async Task WriteAsync()
{
if (_plc.Connected)
{
if (Plc.Connected)
{
try
var data = await _plc.WriteNodeAsync(
new()
{
var data = await Plc.ReadJTokenValueAsync(new string[] { RegisterAddress });
{RegisterAddress, WriteValue.GetJTokenFromString()}
}
);
opcUaMasterConnectPage.LogMessage?.LogInformation($" {data[0].Item1}{data[0].Item3}");
}
catch (Exception ex)
{
opcUaMasterConnectPage.LogMessage?.LogWarning(ex, $"读取失败");
}
}
else
foreach (var item in data)
{
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
if (item.Value.Item1)
LogMessage?.LogInformation(item.ToJsonString());
else
LogMessage?.LogWarning(item.ToJsonString());
}
}
private void Remove()
{
if (Plc.Connected)
Plc.RemoveSubscription("");
else
{
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
}
}
}
}

View File

@@ -1,64 +0,0 @@
@namespace ThingsGateway.Debug
@using BlazorComponent;
@using Microsoft.AspNetCore.Components.Web;
@using System.IO.Ports;
@using System.Collections.Concurrent;
@using ThingsGateway.Foundation.OpcUa;
@using ThingsGateway.Core.Extension;
@using Masa.Blazor
@inherits BaseComponentBase
@implements IDisposable
<MCard Class="ma-2">
<MSubheader Style="max-height:32px">
@AppService.I18n.T("Adapter Config")
</MSubheader>
<MContainer Class="ma-0">
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.GroupSize) @bind-Value=@config.GroupSize />
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.UpdateRate) @bind-Value=@config.UpdateRate />
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.DeadBand) @bind-Value=@config.DeadBand />
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.KeepAliveInterval) @bind-Value=@config.KeepAliveInterval />
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.OpcUrl) @bind-Value=@config.OpcUrl />
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.UserName) @bind-Value=@config.UserName />
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@config.Description(a=>a.Password) @bind-Value=@config.Password />
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@(AppService.I18n.T(config.Description(x => x.ActiveSubscribe))) @bind-Value=@config.ActiveSubscribe></MCheckbox>
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@(AppService.I18n.T(config.Description(x => x.IsUseSecurity))) @bind-Value=@config.IsUseSecurity></MCheckbox>
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@(AppService.I18n.T(config.Description(x => x.CheckDomain))) @bind-Value=@config.CheckDomain></MCheckbox>
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
Label=@(AppService.I18n.T(config.Description(x => x.LoadType))) @bind-Value=@config.LoadType></MCheckbox>
<MButton Class="ma-1" OnClick=@Connect Color="primary">
连接
</MButton>
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
断开
</MButton>
</MRow>
</MContainer>
</MCard>

View File

@@ -1,88 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.OpcUa;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
public partial class OpcUaMasterConnectPage : IDisposable
{
public ThingsGateway.Foundation.OpcUa.OpcUaMaster Plc;
private readonly OpcUaConfig config = new();
protected override void Dispose(bool disposing)
{
Plc.SafeDispose();
base.Dispose(disposing);
}
[Parameter]
public EventCallback OnConnectClick { get; set; }
public LoggerGroup? LogMessage;
/// <inheritdoc/>
protected override void OnInitialized()
{
Plc = new ThingsGateway.Foundation.OpcUa.OpcUaMaster();
Plc.OpcUaConfig = config;
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
var logger = TextFileLogger.Create(Plc.GetHashCode().ToLong().GetDebugLogPath());
logger.LogLevel = LogLevel.Trace;
LogMessage.AddLogger(logger);
Plc.OpcStatusChange += Plc_OpcStatusChange;
Plc.DataChangedHandler += (a) => LogMessage.Trace((a.variableNode.NodeId, a.jToken).ToJsonString());
base.OnInitialized();
}
private void Plc_OpcStatusChange(object? sender, OpcUaStatusEventArgs e)
{
LogMessage?.Log((LogLevel)e.LogLevel, null, e.Text, null);
}
private async Task Connect()
{
try
{
Plc.Disconnect();
await GetOpc().ConnectAsync(CancellationToken.None);
}
catch (Exception ex)
{
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private void DisConnect()
{
try
{
Plc.Disconnect();
}
catch (Exception ex)
{
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
}
}
private ThingsGateway.Foundation.OpcUa.OpcUaMaster GetOpc()
{
//载入配置
Plc.OpcUaConfig = config;
return Plc;
}
}

View File

@@ -8,34 +8,22 @@
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="@echo off&#xD;&#xA;setlocal enabledelayedexpansion&#xD;&#xA;&#xD;&#xA;set &quot;targetFWS=net6.0 net7.0 net8.0&quot;&#xD;&#xA;for %25%25f in (%25targetFWS%25) do (&#xD;&#xA; set &quot;dir=$(SolutionDir)bin\$(Configuration)\ThingsGateway.Web.Entry\%25%25f\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist &quot;!dir!&quot; md &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*OpcUa*.dll&quot; &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*Opc.Ua*.dll&quot; &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*System.Formats.Asn1*.dll&quot; &quot;!dir!&quot;&#xD;&#xA;)&#xD;&#xA;&#xD;&#xA;endlocal&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
<Exec Command="@echo off&#xD;&#xA;setlocal enabledelayedexpansion&#xD;&#xA;&#xD;&#xA;set targetFWS=$(TargetFrameworks)&#xD;&#xA;for %25%25f in (%25targetFWS%25) do (&#xD;&#xA; set &quot;dir=$(SolutionDir)ThingsGateway.Server\bin\$(Configuration)\%25%25f\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist &quot;!dir!&quot; md &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*OpcUa*.dll&quot; &quot;!dir!&quot;&#xD;&#xA; copy &quot;$(TargetDir)*Opc.Ua*.dll&quot; &quot;!dir!&quot;&#xD;&#xA;&#xD;&#xA;)&#xD;&#xA;&#xD;&#xA;endlocal&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
</Target>
<ItemGroup>
<Content Remove="Locales\en-US.json" />
<Content Remove="Locales\zh-CN.json" />
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\en-US.json" />
<EmbeddedResource Include="Locales\zh-CN.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.374.27" />
<ProjectReference Include="..\..\foundation\ThingsGateway.Foundation.OpcUa\src\ThingsGateway.Foundation.OpcUa.csproj" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.374.36" />
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" />
</ItemGroup>
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

View File

@@ -5,10 +5,7 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorComponent
@using Masa.Blazor
@using Masa.Blazor.Presets
@using ThingsGateway.Components;
@using ThingsGateway.Razor;
@using ThingsGateway.Core;
@using System.Net.Http.Json
@using System.IO;

View File

@@ -37,7 +37,7 @@
}
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText"></AdapterDebugComponent>
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent HeaderText="HeaderText"></AdapterDebugComponent>
</div>

View File

@@ -30,7 +30,7 @@ public partial class SiemensS7Master : IDisposable
this.SafeDispose();
}
private AdapterDebugComponent AdapterDebugPage { get; set; }
private AdapterDebugComponent AdapterDebugComponent { get; set; }
private ChannelData ChannelData { get; set; }