mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 10:50:48 +08:00
10.11.65
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.64</PluginVersion>
|
||||
<ProPluginVersion>10.11.64</ProPluginVersion>
|
||||
<DefaultVersion>10.11.64</DefaultVersion>
|
||||
<PluginVersion>10.11.65</PluginVersion>
|
||||
<ProPluginVersion>10.11.65</ProPluginVersion>
|
||||
<DefaultVersion>10.11.65</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.20</NET8Version>
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public interface IMemoryVariableRuntime
|
||||
public interface IMemoryVariableRpc
|
||||
{
|
||||
OperResult MemoryVariableRpc(string value, CancellationToken cancellationToken = default);
|
||||
}
|
@@ -58,27 +58,27 @@ internal sealed class RpcService : IRpcService
|
||||
deviceDatas.ForEach(a => results.TryAdd(a.Key, new()));
|
||||
|
||||
// 对每个要操作的变量进行检查和处理(内存变量)
|
||||
foreach (var item in deviceDatas.Where(a => a.Key.IsNullOrEmpty()).SelectMany(a => a.Value))
|
||||
foreach (var item in deviceDatas.Where(a => a.Key.IsNullOrEmpty() || a.Key.Equals("Memory", StringComparison.OrdinalIgnoreCase)).SelectMany(a => a.Value))
|
||||
{
|
||||
// 查找变量是否存在
|
||||
if (!(GlobalData.MemoryVariables.TryGetValue(item.Key, out var tag) &&
|
||||
tag is IMemoryVariableRuntime memoryVariableRuntime))
|
||||
tag is IMemoryVariableRpc memoryVariableRuntime))
|
||||
{
|
||||
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
|
||||
results[string.Empty].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
|
||||
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查变量的保护类型和远程写入权限
|
||||
if (tag.ProtectType == ProtectTypeEnum.ReadOnly)
|
||||
{
|
||||
results[string.Empty].TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
|
||||
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tag.RpcWriteEnable)
|
||||
{
|
||||
results[string.Empty].TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
|
||||
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ internal sealed class RpcService : IRpcService
|
||||
var end = DateTime.Now;
|
||||
|
||||
string operObj = tag.Name;
|
||||
string parJson = deviceDatas[string.Empty][tag.Name];
|
||||
string parJson = deviceDatas["Memory"][tag.Name];
|
||||
|
||||
if (!variableResult.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
{
|
||||
@@ -118,19 +118,19 @@ internal sealed class RpcService : IRpcService
|
||||
variableResult = result1;
|
||||
}
|
||||
|
||||
results[string.Empty].Add(tag.Name, variableResult);
|
||||
results["Memory"].Add(tag.Name, variableResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 将异常信息添加到结果字典中
|
||||
results[string.Empty].Add(tag.Name, new OperResult(ex));
|
||||
results["Memory"].Add(tag.Name, new OperResult(ex));
|
||||
}
|
||||
}
|
||||
|
||||
var deviceDict = GlobalData.Devices;
|
||||
|
||||
// 对每个要操作的变量进行检查和处理(设备变量)
|
||||
foreach (var deviceData in deviceDatas.Where(a => !a.Key.IsNullOrEmpty()))
|
||||
foreach (var deviceData in deviceDatas.Where(a => (!a.Key.IsNullOrEmpty() && !a.Key.Equals("Memory", StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
// 查找设备是否存在
|
||||
if (!deviceDict.TryGetValue(deviceData.Key, out var device))
|
||||
|
@@ -23,7 +23,7 @@
|
||||
LogLevel=a;
|
||||
if(LogLevelChanged!=null)
|
||||
{
|
||||
await LogLevelChanged?.Invoke(a);
|
||||
await LogLevelChanged.Invoke(a);
|
||||
}
|
||||
}" LogPath=@LogPath HeaderText=@HeaderText></ThingsGateway.Debug.LogConsole>
|
||||
|
||||
|
@@ -47,7 +47,7 @@ public partial class RedundancyOptionsPage
|
||||
LogLevel = logLevel;
|
||||
if (LogLevelChanged != null)
|
||||
{
|
||||
await LogLevelChanged?.Invoke(LogLevel);
|
||||
await LogLevelChanged.Invoke(LogLevel);
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
@@ -11,6 +11,9 @@
|
||||
//修改自https://github.com/dathlin/OpcUaHelper 与OPC基金会net库
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.Collections.Frozen;
|
||||
#endif
|
||||
|
||||
namespace ThingsGateway.Foundation.OpcUa;
|
||||
|
||||
@@ -28,7 +31,7 @@ public delegate void LogEventHandler(byte level, object sender, string message,
|
||||
/// <summary>
|
||||
/// OpcUaMaster
|
||||
/// </summary>
|
||||
public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
public class OpcUaMaster : IAsyncDisposable
|
||||
{
|
||||
#region 属性,变量等
|
||||
|
||||
@@ -157,9 +160,9 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
},
|
||||
};
|
||||
|
||||
certificateValidator.Update(m_configuration);
|
||||
Task.Run(() => certificateValidator.UpdateAsync(m_configuration)).GetAwaiter().GetResult();
|
||||
|
||||
m_configuration.Validate(ApplicationType.Client);
|
||||
Task.Run(() => m_configuration.ValidateAsync(ApplicationType.Client)).GetAwaiter().GetResult();
|
||||
m_application.ApplicationConfiguration = m_configuration;
|
||||
}
|
||||
|
||||
@@ -367,19 +370,21 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
/// </summary>
|
||||
/// <param name="tagParent">方法的父节点tag</param>
|
||||
/// <param name="tag">方法的节点tag</param>
|
||||
/// <param name="cancellationToken">cancellationToken</param>
|
||||
/// <param name="args">传递的参数</param>
|
||||
/// <returns>输出的结果值</returns>
|
||||
public object[] CallMethodByNodeId(string tagParent, string tag, params object[] args)
|
||||
public async Task<IList<object>> CallMethodByNodeIdAsync(string tagParent, string tag, CancellationToken cancellationToken, params object[] args)
|
||||
{
|
||||
if (m_session == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IList<object> outputArguments = m_session.Call(
|
||||
IList<object> outputArguments = await m_session.CallAsync(
|
||||
new NodeId(tagParent),
|
||||
new NodeId(tag),
|
||||
args);
|
||||
cancellationToken,
|
||||
args).ConfigureAwait(false);
|
||||
|
||||
return outputArguments.ToArray();
|
||||
}
|
||||
@@ -392,19 +397,6 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
await ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接。
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
PrivateDisconnect();
|
||||
// disconnect any existing session.
|
||||
if (m_session != null)
|
||||
{
|
||||
m_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接。
|
||||
/// </summary>
|
||||
@@ -417,14 +409,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
m_session = null;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Disconnect();
|
||||
_variableDicts?.Clear();
|
||||
_subscriptionDicts?.Clear();
|
||||
waitLock?.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisconnectAsync().ConfigureAwait(false);
|
||||
@@ -573,8 +558,9 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
/// 读取一个节点的所有属性
|
||||
/// </summary>
|
||||
/// <param name="tag">节点信息</param>
|
||||
/// <param name="cancellationToken">cancellationToken</param>
|
||||
/// <returns>节点的特性值</returns>
|
||||
public OPCNodeAttribute[] ReadNoteAttributes(string tag)
|
||||
public async Task<OPCNodeAttribute[]> ReadNoteAttributesAsync(string tag, CancellationToken cancellationToken)
|
||||
{
|
||||
NodeId sourceId = new(tag);
|
||||
ReadValueIdCollection nodesToRead = new();
|
||||
@@ -608,7 +594,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
};
|
||||
|
||||
// fetch property references from the server.
|
||||
ReferenceDescriptionCollection references = OpcUaUtils.Browse(m_session, nodesToBrowse, false);
|
||||
ReferenceDescriptionCollection references = await OpcUaUtils.BrowseAsync(m_session, nodesToBrowse, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (references == null)
|
||||
{
|
||||
@@ -631,15 +617,15 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
nodesToRead.Add(nodeToRead);
|
||||
}
|
||||
|
||||
// read all values.
|
||||
|
||||
m_session.Read(
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
nodesToRead,
|
||||
out DataValueCollection results,
|
||||
out DiagnosticInfoCollection diagnosticInfos);
|
||||
// 读取当前的值
|
||||
var result = await m_session.ReadAsync(
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
nodesToRead,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var results = result.Results;
|
||||
var diagnosticInfos = result.DiagnosticInfos;
|
||||
|
||||
ClientBase.ValidateResponse(results, nodesToRead);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
|
||||
@@ -661,7 +647,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
}
|
||||
|
||||
// get the name of the attribute.
|
||||
item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);
|
||||
item.Name = GetBrowseName(nodesToRead[ii].AttributeId);
|
||||
|
||||
// display any unexpected error.
|
||||
if (StatusCode.IsBad(results[ii].StatusCode))
|
||||
@@ -726,7 +712,34 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
|
||||
return nodeAttribute.ToArray();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the browse name for the attribute.
|
||||
/// </summary>
|
||||
public static string GetBrowseName(uint identifier)
|
||||
{
|
||||
return s_attributesIdToName.Value.TryGetValue(identifier, out string name)
|
||||
? name : string.Empty;
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a dictionary of identifiers to browse names for the attributes.
|
||||
/// </summary>
|
||||
private static readonly Lazy<IReadOnlyDictionary<long, string>> s_attributesIdToName =
|
||||
new(() =>
|
||||
{
|
||||
System.Reflection.FieldInfo[] fields = typeof(Attributes).GetFields(
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
|
||||
var keyValuePairs = new Dictionary<long, string>();
|
||||
foreach (System.Reflection.FieldInfo field in fields)
|
||||
{
|
||||
keyValuePairs.TryAdd(Convert.ToInt64(field.GetValue(typeof(Attributes))), field.Name);
|
||||
}
|
||||
#if NET8_0_OR_GREATER
|
||||
return keyValuePairs.ToFrozenDictionary();
|
||||
#else
|
||||
return new System.Collections.ObjectModel.ReadOnlyDictionary<long, string>(keyValuePairs);
|
||||
#endif
|
||||
});
|
||||
/// <summary>
|
||||
/// 移除所有的订阅消息
|
||||
/// </summary>
|
||||
@@ -825,7 +838,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
{
|
||||
foreach (var value in monitoreditem.DequeueValues())
|
||||
{
|
||||
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode));
|
||||
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode)).GetAwaiter().GetResult();
|
||||
|
||||
if (value.Value != null)
|
||||
{
|
||||
@@ -861,7 +874,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
/// <returns></returns>
|
||||
public async Task CheckApplicationInstanceCertificate()
|
||||
{
|
||||
await m_application.CheckApplicationInstanceCertificates(true, 1200).ConfigureAwait(false);
|
||||
await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SemaphoreSlim waitLock = new(1, 1);
|
||||
@@ -910,11 +923,12 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
}
|
||||
//创建本地证书
|
||||
if (useSecurity)
|
||||
await m_application.CheckApplicationInstanceCertificates(true, 1200, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
m_session = await Opc.Ua.Client.Session.Create(
|
||||
await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
m_session = await Opc.Ua.Client.Session.CreateAsync(
|
||||
DefaultSessionFactory.Instance,
|
||||
m_configuration,
|
||||
(ITransportWaitingConnection)null,
|
||||
endpoint,
|
||||
false,
|
||||
OpcUaProperty.CheckDomain,
|
||||
@@ -940,29 +954,6 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void PrivateDisconnect()
|
||||
{
|
||||
bool state = m_session?.Connected == true;
|
||||
|
||||
if (m_reConnectHandler != null)
|
||||
{
|
||||
try { m_reConnectHandler.Dispose(); } catch { }
|
||||
m_reConnectHandler = null;
|
||||
}
|
||||
if (m_session != null)
|
||||
{
|
||||
m_session.KeepAlive -= Session_KeepAlive;
|
||||
m_session.Close(10000);
|
||||
m_session.Dispose();
|
||||
m_session = null;
|
||||
}
|
||||
|
||||
if (state)
|
||||
{
|
||||
Log(2, null, "Disconnected");
|
||||
DoConnectComplete(false);
|
||||
}
|
||||
}
|
||||
private async Task PrivateDisconnectAsync()
|
||||
{
|
||||
bool state = m_session?.Connected == true;
|
||||
@@ -1035,7 +1026,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
/// <summary>
|
||||
/// 从服务器或缓存读取节点
|
||||
/// </summary>
|
||||
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true)
|
||||
private async Task<VariableNode> ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!isOnlyServer)
|
||||
{
|
||||
@@ -1061,16 +1052,17 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
}
|
||||
|
||||
// read from server.
|
||||
DataValueCollection values = null;
|
||||
DiagnosticInfoCollection diagnosticInfos = null;
|
||||
|
||||
ResponseHeader responseHeader = m_session.Read(
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
itemsToRead,
|
||||
out values,
|
||||
out diagnosticInfos);
|
||||
var result = await m_session.ReadAsync(
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
itemsToRead,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var values = result.Results;
|
||||
var diagnosticInfos = result.DiagnosticInfos;
|
||||
var responseHeader = result.ResponseHeader;
|
||||
|
||||
|
||||
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
|
||||
|
||||
@@ -1121,7 +1113,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
|
||||
|
||||
if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject)
|
||||
await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
|
||||
await typeSystem.LoadTypeAsync(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cache)
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
|
||||
@@ -1222,7 +1214,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
_variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
|
||||
if (node.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(node.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject)
|
||||
{
|
||||
await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false);
|
||||
await typeSystem.LoadTypeAsync(node.DataType, ct: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
result.Add(node);
|
||||
@@ -1314,7 +1306,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
|
||||
continue;
|
||||
}
|
||||
|
||||
item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);
|
||||
item.Name = GetBrowseName(nodesToRead[ii].AttributeId);
|
||||
if (StatusCode.IsBad(nodeValue.StatusCode))
|
||||
{
|
||||
item.Type = Utils.Format("{0}", Attributes.GetDataTypeId(nodesToRead[ii].AttributeId));
|
||||
|
@@ -13,7 +13,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.377.21" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -56,133 +56,6 @@ public static class OpcUaUtils
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Browses the address space and returns the references found.
|
||||
/// </summary>
|
||||
/// <param name="session">The session.</param>
|
||||
/// <param name="nodesToBrowse">The set of browse operations to perform.</param>
|
||||
/// <param name="throwOnError">if set to <c>true</c> a exception will be thrown on an error.</param>
|
||||
/// <returns>
|
||||
/// The references found. Null if an error occurred.
|
||||
/// </returns>
|
||||
public static ReferenceDescriptionCollection Browse(ISession session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReferenceDescriptionCollection references = new();
|
||||
BrowseDescriptionCollection unprocessedOperations = new();
|
||||
|
||||
while (nodesToBrowse.Count > 0)
|
||||
{
|
||||
// start the browse operation.
|
||||
|
||||
session.Browse(
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
nodesToBrowse,
|
||||
out BrowseResultCollection results,
|
||||
out DiagnosticInfoCollection diagnosticInfos);
|
||||
|
||||
ClientBase.ValidateResponse(results, nodesToBrowse);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);
|
||||
|
||||
ByteStringCollection continuationPoints = new();
|
||||
|
||||
for (int ii = 0; ii < nodesToBrowse.Count; ii++)
|
||||
{
|
||||
// check for error.
|
||||
if (StatusCode.IsBad(results[ii].StatusCode))
|
||||
{
|
||||
// this error indicates that the server does not have enough simultaneously active
|
||||
// continuation points. This request will need to be resent after the other operations
|
||||
// have been completed and their continuation points released.
|
||||
if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints)
|
||||
{
|
||||
unprocessedOperations.Add(nodesToBrowse[ii]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if all references have been fetched.
|
||||
if (results[ii].References.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// save results.
|
||||
references.AddRange(results[ii].References);
|
||||
|
||||
// check for continuation point.
|
||||
if (results[ii].ContinuationPoint != null)
|
||||
{
|
||||
continuationPoints.Add(results[ii].ContinuationPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// process continuation points.
|
||||
ByteStringCollection revisedContiuationPoints = new();
|
||||
|
||||
while (continuationPoints.Count > 0)
|
||||
{
|
||||
// continue browse operation.
|
||||
session.BrowseNext(
|
||||
null,
|
||||
true,
|
||||
continuationPoints,
|
||||
out results,
|
||||
out diagnosticInfos);
|
||||
|
||||
ClientBase.ValidateResponse(results, continuationPoints);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);
|
||||
|
||||
for (int ii = 0; ii < continuationPoints.Count; ii++)
|
||||
{
|
||||
// check for error.
|
||||
if (StatusCode.IsBad(results[ii].StatusCode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if all references have been fetched.
|
||||
if (results[ii].References.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// save results.
|
||||
references.AddRange(results[ii].References);
|
||||
|
||||
// check for continuation point.
|
||||
if (results[ii].ContinuationPoint != null)
|
||||
{
|
||||
revisedContiuationPoints.Add(results[ii].ContinuationPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// check if browsing must continue;
|
||||
revisedContiuationPoints = continuationPoints;
|
||||
}
|
||||
|
||||
// check if unprocessed results exist.
|
||||
nodesToBrowse = unprocessedOperations;
|
||||
}
|
||||
|
||||
// return complete list.
|
||||
return references;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (throwOnError)
|
||||
{
|
||||
throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浏览地址空间
|
||||
/// </summary>
|
||||
@@ -570,7 +443,7 @@ public static class OpcUaUtils
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
/// <returns>A list of server urls.</returns>
|
||||
public static IList<string> DiscoverServers(ApplicationConfiguration configuration)
|
||||
public static async Task<IList<string>> DiscoverServers(ApplicationConfiguration configuration)
|
||||
{
|
||||
List<string> serverUrls = new();
|
||||
|
||||
@@ -581,7 +454,7 @@ public static class OpcUaUtils
|
||||
// Connect to the local discovery server and find the available servers.
|
||||
using (DiscoveryClient client = DiscoveryClient.Create(new Uri("opc.tcp://localhost:4840"), endpointConfiguration))
|
||||
{
|
||||
ApplicationDescriptionCollection servers = client.FindServers(null);
|
||||
ApplicationDescriptionCollection servers = await client.FindServersAsync(null).ConfigureAwait(false);
|
||||
|
||||
// populate the drop down list with the discovery URLs for the available servers.
|
||||
for (int ii = 0; ii < servers.Count; ii++)
|
||||
@@ -641,7 +514,7 @@ public static class OpcUaUtils
|
||||
/// <summary>
|
||||
/// 指定的属性的显示文本。
|
||||
/// </summary>
|
||||
public static string GetAttributeDisplayText(ISession session, uint attributeId, Variant value)
|
||||
public static async Task<string> GetAttributeDisplayTextAsync(ISession session, uint attributeId, Variant value)
|
||||
{
|
||||
if (value == Variant.Null)
|
||||
{
|
||||
@@ -677,7 +550,7 @@ public static class OpcUaUtils
|
||||
|
||||
case Attributes.DataType:
|
||||
{
|
||||
return session.NodeCache.GetDisplayText(value.Value as NodeId);
|
||||
return await session.NodeCache.GetDisplayTextAsync(value.Value as NodeId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
case Attributes.ValueRank:
|
||||
@@ -727,102 +600,6 @@ public static class OpcUaUtils
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the endpoint that best matches the current settings.
|
||||
/// </summary>
|
||||
/// <param name="discoveryUrl">The discovery URL.</param>
|
||||
/// <param name="useSecurity">if set to <c>true</c> select an endpoint that uses security.</param>
|
||||
/// <returns>The best available endpoint.</returns>
|
||||
public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity)
|
||||
{
|
||||
// needs to add the '/discovery' back onto non-UA TCP URLs.
|
||||
if (!discoveryUrl.StartsWith(Utils.UriSchemeOpcTcp))
|
||||
{
|
||||
if (!discoveryUrl.EndsWith("/discovery"))
|
||||
{
|
||||
discoveryUrl += "/discovery";
|
||||
}
|
||||
}
|
||||
|
||||
// parse the selected URL.
|
||||
Uri uri = new(discoveryUrl);
|
||||
|
||||
// set a short timeout because this is happening in the drop down event.
|
||||
EndpointConfiguration configuration = EndpointConfiguration.Create();
|
||||
configuration.OperationTimeout = 5000;
|
||||
|
||||
EndpointDescription selectedEndpoint = null;
|
||||
|
||||
// Connect to the server's discovery endpoint and find the available configuration.
|
||||
using (DiscoveryClient client = DiscoveryClient.Create(uri, configuration))
|
||||
{
|
||||
EndpointDescriptionCollection endpoints = client.GetEndpoints(null);
|
||||
|
||||
// select the best endpoint to use based on the selected URL and the UseSecurity checkbox.
|
||||
for (int ii = 0; ii < endpoints.Count; ii++)
|
||||
{
|
||||
EndpointDescription endpoint = endpoints[ii];
|
||||
|
||||
// check for a match on the URL scheme.
|
||||
if (endpoint.EndpointUrl.StartsWith(uri.Scheme))
|
||||
{
|
||||
// check if security was requested.
|
||||
if (useSecurity)
|
||||
{
|
||||
if (endpoint.SecurityMode == MessageSecurityMode.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (endpoint.SecurityMode != MessageSecurityMode.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// pick the first available endpoint by default.
|
||||
selectedEndpoint ??= endpoint;
|
||||
|
||||
// The security level is a relative measure assigned by the server to the
|
||||
// endpoints that it returns. Clients should always pick the highest level
|
||||
// unless they have a reason not too.
|
||||
if (endpoint.SecurityLevel > selectedEndpoint.SecurityLevel)
|
||||
{
|
||||
selectedEndpoint = endpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pick the first available endpoint by default.
|
||||
if (selectedEndpoint == null && endpoints.Count > 0)
|
||||
{
|
||||
selectedEndpoint = endpoints[0];
|
||||
}
|
||||
}
|
||||
|
||||
// if a server is behind a firewall it may return URLs that are not accessible to the client.
|
||||
// This problem can be avoided by assuming that the domain in the URL used to call
|
||||
// GetEndpoints can be used to access any of the endpoints. This code makes that conversion.
|
||||
// Note that the conversion only makes sense if discovery uses the same protocol as the endpoint.
|
||||
|
||||
Uri endpointUrl = Utils.ParseUri(selectedEndpoint.EndpointUrl);
|
||||
|
||||
if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme)
|
||||
{
|
||||
UriBuilder builder = new(endpointUrl)
|
||||
{
|
||||
Host = uri.DnsSafeHost,
|
||||
Port = uri.Port
|
||||
};
|
||||
selectedEndpoint.EndpointUrl = builder.ToString();
|
||||
}
|
||||
|
||||
// return the selected endpoint.
|
||||
return selectedEndpoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回一组相对路径的节点id
|
||||
/// </summary>
|
||||
|
@@ -31,6 +31,15 @@ public abstract class DynamicSQLBase
|
||||
/// <returns></returns>
|
||||
public virtual Task DBInsertable(ISqlSugarClient db, IEnumerable<object> datas, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除n天前数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual Task<int> DBDeleteable(ISqlSugarClient db, int days, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,9 @@ public class RealDBProducerProperty : BusinessPropertyWithCacheInterval
|
||||
[Required]
|
||||
public string StringTableName { get; set; } = "historyStringValue";
|
||||
|
||||
[DynamicProperty]
|
||||
public int SaveDays { get; set; } = 3650;
|
||||
|
||||
public string NumberTableNameLow => NumberTableName.ToLower();
|
||||
public string StringTableNameLow => StringTableName.ToLower();
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.DB.RealDBProducerProperty": {
|
||||
"BigTextConnectStr": "ConnectString",
|
||||
"SaveDays": "SaveDays",
|
||||
"BigTextScriptHistoryTable": "DynamicScriptHistoryTable",
|
||||
"RealTableBusinessInterval": "RealTableBusinessInterval",
|
||||
"StringTableName": "StringTableName",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Value": "Value"
|
||||
},
|
||||
"ThingsGateway.Plugin.SqlDB.SqlDBProducerProperty": {
|
||||
"SaveDays": "SaveDays",
|
||||
"BigTextConnectStr": "ConnectString",
|
||||
"BigTextScriptHistoryTable": "DynamicScriptHistoryTable",
|
||||
"BigTextScriptRealTable": "DynamicScriptRealTable",
|
||||
@@ -87,6 +89,7 @@
|
||||
"Remark5": "Remark5"
|
||||
},
|
||||
"ThingsGateway.Plugin.DB.SQLHistoryAlarmProperty": {
|
||||
"SaveDays": "SaveDays",
|
||||
"BigTextConnectStr": "ConnectString",
|
||||
"DbType": "DbType",
|
||||
"TableName": "TableName",
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.DB.RealDBProducerProperty": {
|
||||
"BigTextConnectStr": "连接字符串",
|
||||
"SaveDays": "保留天数",
|
||||
"BigTextScriptHistoryTable": "历史表动态脚本",
|
||||
"RealTableBusinessInterval": "实时表定时上传间隔",
|
||||
"NumberTableName": "数值变量历史表名称",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Value": "变量值"
|
||||
},
|
||||
"ThingsGateway.Plugin.SqlDB.SqlDBProducerProperty": {
|
||||
"SaveDays": "保留天数",
|
||||
"BigTextConnectStr": "链接字符串",
|
||||
"BigTextScriptHistoryTable": "历史表动态脚本",
|
||||
"BigTextScriptRealTable": "实时表动态脚本",
|
||||
@@ -87,6 +89,7 @@
|
||||
"Remark5": "备用5"
|
||||
},
|
||||
"ThingsGateway.Plugin.DB.SQLHistoryAlarmProperty": {
|
||||
"SaveDays": "保留天数",
|
||||
"BigTextConnectStr": "链接字符串",
|
||||
"DbType": "数据库类型",
|
||||
"TableName": "表名称",
|
||||
|
@@ -16,6 +16,7 @@ using ThingsGateway.Debug;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
using ThingsGateway.Plugin.DB;
|
||||
using ThingsGateway.SqlSugar;
|
||||
|
||||
@@ -69,6 +70,56 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
|
||||
#if !Management
|
||||
|
||||
|
||||
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = base.ProtectedGetTasks(cancellationToken);
|
||||
list.Add(ScheduledTaskHelper.GetTask("0 0 * * *", DeleteByDayAsync, null, LogMessage, cancellationToken));
|
||||
|
||||
return list;
|
||||
}
|
||||
private async Task DeleteByDayAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
||||
await hisModel.DBDeleteable(db, _driverPropertys.SaveDays, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
|
||||
|
||||
string sql = $"""
|
||||
ALTER TABLE {_driverPropertys.NumberTableNameLow}
|
||||
DROP PARTITION
|
||||
WHERE createtime < to_timestamp('{time.ToString("yyyy-MM-dd:HH:mm:ss")}', 'yyyy-MM-dd:HH:mm:ss');
|
||||
""";
|
||||
await db.Ado.ExecuteCommandAsync("", default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
sql = $"""
|
||||
ALTER TABLE {_driverPropertys.StringTableNameLow}
|
||||
DROP PARTITION
|
||||
WHERE createtime < to_timestamp('{time.ToString("yyyy-MM-dd:HH:mm:ss")}', 'yyyy-MM-dd:HH:mm:ss');
|
||||
""";
|
||||
await db.Ado.ExecuteCommandAsync("", default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
LogMessage?.LogInformation($"Clean up historical data from {_driverPropertys.SaveDays} days ago");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Clearing historical data error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
_db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
|
||||
|
@@ -18,6 +18,7 @@ using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
using ThingsGateway.Plugin.DB;
|
||||
using ThingsGateway.SqlSugar;
|
||||
|
||||
@@ -70,7 +71,82 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
return $" {nameof(SqlDBProducer)}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#if !Management
|
||||
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = base.ProtectedGetTasks(cancellationToken);
|
||||
list.Add(ScheduledTaskHelper.GetTask("0/10 * * * * *", DeleteByDayAsync, null, LogMessage, cancellationToken));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private async Task DeleteByDayAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
await hisModel.DBDeleteable(db, _driverPropertys.SaveDays, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
{
|
||||
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
|
||||
var tableNames = db.SplitHelper<SQLHistoryValue>().GetTables();//根据时间获取表名
|
||||
var filtered = tableNames.Where(a => a.Date < time).ToList();
|
||||
// 去掉最后一个
|
||||
var oldTable = filtered.Take(filtered.Count - 1);
|
||||
|
||||
foreach (var table in oldTable)
|
||||
{
|
||||
db.DbMaintenance.DropTable(table.TableName);
|
||||
}
|
||||
var deldata = filtered.LastOrDefault();
|
||||
if (deldata != null)
|
||||
{
|
||||
await db.Deleteable<SQLHistoryValue>().AS(deldata.TableName).Where(a => a.CreateTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
|
||||
var tableNames = db.SplitHelper<SQLNumberHistoryValue>().GetTables();//根据时间获取表名
|
||||
var filtered = tableNames.Where(a => a.Date < time).ToList();
|
||||
// 去掉最后一个
|
||||
var oldTable = filtered.Take(filtered.Count - 1);
|
||||
|
||||
foreach (var table in oldTable)
|
||||
{
|
||||
db.DbMaintenance.DropTable(table.TableName);
|
||||
}
|
||||
var deldata = filtered.LastOrDefault();
|
||||
if (deldata != null)
|
||||
{
|
||||
await db.Deleteable<SQLNumberHistoryValue>().AS(deldata.TableName).Where(a => a.CreateTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
LogMessage?.LogInformation($"Clean up historical data from {_driverPropertys.SaveDays} days ago");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Clearing historical data error");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
|
@@ -58,6 +58,9 @@ public class SqlDBProducerProperty : BusinessPropertyWithCacheInterval
|
||||
[DynamicProperty]
|
||||
public DbType DbType { get; set; } = DbType.SqlServer;
|
||||
|
||||
[DynamicProperty]
|
||||
public int SaveDays { get; set; } = 3650;
|
||||
|
||||
[DynamicProperty]
|
||||
public SqlDBSplitType SqlDBSplitType { get; set; } = SqlDBSplitType.Week;
|
||||
|
||||
|
@@ -16,6 +16,7 @@ using ThingsGateway.Debug;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
using ThingsGateway.SqlSugar;
|
||||
|
||||
namespace ThingsGateway.Plugin.DB;
|
||||
@@ -65,6 +66,44 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
|
||||
}
|
||||
|
||||
#if !Management
|
||||
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = base.ProtectedGetTasks(cancellationToken);
|
||||
list.Add(ScheduledTaskHelper.GetTask("0 0 * * *", DeleteByDayAsync, null, LogMessage, cancellationToken));
|
||||
|
||||
return list;
|
||||
}
|
||||
private async Task DeleteByDayAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = BusinessDatabaseUtil.GetDb((DbType)_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
||||
await hisModel.DBDeleteable(db, _driverPropertys.SaveDays, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
|
||||
|
||||
await db.Deleteable<HistoryAlarm>().Where(a => a.EventTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogInformation($"Clean up historical data from {_driverPropertys.SaveDays} days ago");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Clearing historical data error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
|
@@ -23,6 +23,10 @@ public class SqlHistoryAlarmProperty : BusinessPropertyWithCache
|
||||
{
|
||||
[DynamicProperty]
|
||||
public DbType DbType { get; set; } = DbType.SqlServer;
|
||||
|
||||
[DynamicProperty]
|
||||
public int SaveDays { get; set; } = 3650;
|
||||
|
||||
[DynamicProperty]
|
||||
[Required]
|
||||
public string TableName { get; set; } = "historyAlarm";
|
||||
|
@@ -130,6 +130,12 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
`value` DOUBLE ) TAGS(`devicename` VARCHAR(100) ,`name` VARCHAR(100))
|
||||
""";
|
||||
await _db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
|
||||
await _db.Ado.ExecuteCommandAsync($"ALTER STABLE `{_driverPropertys.StringTableNameLow}` KEEP 10;", default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
await _db.Ado.ExecuteCommandAsync($"ALTER STABLE `{_driverPropertys.NumberTableNameLow}` KEEP 10;", default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
@@ -250,7 +250,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
NodeIdTags.Clear();
|
||||
NodeIdTags?.Clear();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
@@ -972,7 +972,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
{
|
||||
var str = variableRuntime.GetPropertyValue(_businessBase.DeviceId, nameof(OpcUaServerVariableProperty.DataType)) ?? "";
|
||||
Type tp;
|
||||
if (Enum.TryParse(str, out DataTypeEnum result))
|
||||
if (Enum.TryParse(str, out DataTypeEnum result) && result != DataTypeEnum.Object)
|
||||
{
|
||||
tp = result.GetSystemType();
|
||||
return DataNodeType(tp);
|
||||
|
@@ -57,7 +57,7 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
/// <inheritdoc/>
|
||||
protected override ResourceManager CreateResourceManager(IServerInternal server, ApplicationConfiguration configuration)
|
||||
{
|
||||
ResourceManager resourceManager = new(server, configuration);
|
||||
ResourceManager resourceManager = new(configuration);
|
||||
|
||||
System.Reflection.FieldInfo[] fields = typeof(StatusCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
|
||||
@@ -117,7 +117,6 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
|
||||
// 由应用程序决定如何验证用户身份令牌。
|
||||
// 此函数为 X509 身份令牌创建验证器。
|
||||
CreateUserIdentityValidators(configuration);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -127,7 +126,7 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
base.OnServerStopping();
|
||||
}
|
||||
|
||||
private void CreateUserIdentityValidators(ApplicationConfiguration configuration)
|
||||
public async Task CreateUserIdentityValidators(ApplicationConfiguration configuration)
|
||||
{
|
||||
for (int ii = 0; ii < configuration.ServerConfiguration.UserTokenPolicies.Count; ii++)
|
||||
{
|
||||
@@ -141,7 +140,7 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
configuration.SecurityConfiguration.UserIssuerCertificates != null)
|
||||
{
|
||||
CertificateValidator certificateValidator = new();
|
||||
certificateValidator.Update(configuration).GetAwaiter().GetResult();
|
||||
await certificateValidator.UpdateAsync(configuration).ConfigureAwait(false);
|
||||
certificateValidator.Update(configuration.SecurityConfiguration.UserIssuerCertificates,
|
||||
configuration.SecurityConfiguration.TrustedUserCertificates,
|
||||
configuration.SecurityConfiguration.RejectedCertificateStore);
|
||||
@@ -153,7 +152,7 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArgs args)
|
||||
private void SessionManager_ImpersonateUser(ISession session, ImpersonateEventArgs args)
|
||||
{
|
||||
// check for a user name cancellationToken.
|
||||
|
||||
@@ -226,7 +225,7 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
"Security cancellationToken is not a valid username cancellationToken. An empty password is not accepted.");
|
||||
}
|
||||
var sysUserService = App.RootServices.GetService<ISysUserService>();
|
||||
var userInfo = sysUserService.GetUserByAccountAsync(userName, null).ConfigureAwait(true).GetAwaiter().GetResult();//获取用户信息
|
||||
var userInfo = sysUserService.GetUserByAccountAsync(userName, null).ConfigureAwait(false).GetAwaiter().GetResult();//获取用户信息
|
||||
if (userInfo == null)
|
||||
{
|
||||
// construct translation object with default text.
|
||||
|
@@ -112,8 +112,8 @@ public partial class OpcUaServer : BusinessBase
|
||||
|
||||
//Utils.SetLogger(new OpcUaLogger(LogMessage)); //调试用途
|
||||
m_application = new ApplicationInstance();
|
||||
m_configuration = GetDefaultConfiguration();
|
||||
await m_configuration.Validate(ApplicationType.Server).ConfigureAwait(false);
|
||||
m_configuration = await GetDefaultConfigurationAsync().ConfigureAwait(false);
|
||||
await m_configuration.ValidateAsync(ApplicationType.Server).ConfigureAwait(false);
|
||||
m_application.ApplicationConfiguration = m_configuration;
|
||||
if (m_configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates)
|
||||
{
|
||||
@@ -121,6 +121,7 @@ public partial class OpcUaServer : BusinessBase
|
||||
}
|
||||
|
||||
m_server = new(this);
|
||||
await m_server.CreateUserIdentityValidators(m_configuration).ConfigureAwait(false);
|
||||
}
|
||||
private void UaDispose()
|
||||
{
|
||||
@@ -142,7 +143,7 @@ public partial class OpcUaServer : BusinessBase
|
||||
typeof(EncodeableFactory).GetField("s_globalFactory", BindingFlags.NonPublic | BindingFlags.Static)?.SetValue(null, new EncodeableFactory());
|
||||
typeof(ServiceMessageContext).GetField("s_globalContext", BindingFlags.NonPublic | BindingFlags.Static)?.SetValue(null, typeof(ServiceMessageContext).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(bool) }, null).Invoke(new object[] { true }));
|
||||
|
||||
var listeners = m_server.GetValue("m_listeners") as List<ITransportListener>;
|
||||
var listeners = m_server.GetValue("TransportListeners") as List<ITransportListener>;
|
||||
if (listeners != null)
|
||||
{
|
||||
foreach (var item in listeners)
|
||||
@@ -189,9 +190,9 @@ public partial class OpcUaServer : BusinessBase
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 启动服务器。
|
||||
await m_application.CheckApplicationInstanceCertificates(true, 1200, cancellationToken).ConfigureAwait(false);
|
||||
await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await m_application.Start(m_server).ConfigureAwait(false);
|
||||
await m_application.StartAsync(m_server).ConfigureAwait(false);
|
||||
IdVariableRuntimes.ForEach(a => VariableValueChange(a.Value, a.Value.AdaptVariableBasicData()));
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -205,8 +206,8 @@ public partial class OpcUaServer : BusinessBase
|
||||
try
|
||||
{
|
||||
await Task.Delay(2000, cancellationToken).ConfigureAwait(false);
|
||||
await m_application.CheckApplicationInstanceCertificates(true, 1200, cancellationToken).ConfigureAwait(false);
|
||||
await m_application.Start(m_server).ConfigureAwait(false);
|
||||
await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200, cancellationToken).ConfigureAwait(false);
|
||||
await m_application.StartAsync(m_server).ConfigureAwait(false);
|
||||
connect_success = true;
|
||||
await Task.Delay(2000, cancellationToken).ConfigureAwait(false);
|
||||
IdVariableRuntimes.ForEach(a => VariableValueChange(a.Value, a.Value.AdaptVariableBasicData()));
|
||||
@@ -249,7 +250,7 @@ public partial class OpcUaServer : BusinessBase
|
||||
}
|
||||
}
|
||||
|
||||
private ApplicationConfiguration GetDefaultConfiguration()
|
||||
private async Task<ApplicationConfiguration> GetDefaultConfigurationAsync()
|
||||
{
|
||||
ApplicationConfiguration config = new();
|
||||
var urls = _driverPropertys.OpcUaStringUrl.Split(separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -428,7 +429,7 @@ public partial class OpcUaServer : BusinessBase
|
||||
config.TraceConfiguration = new TraceConfiguration();
|
||||
|
||||
config.CertificateValidator = new CertificateValidator();
|
||||
config.CertificateValidator.Update(config).ConfigureAwait(false);
|
||||
await config.CertificateValidator.UpdateAsync(config).ConfigureAwait(false);
|
||||
config.Extensions = new XmlElementCollection();
|
||||
|
||||
return config;
|
||||
|
@@ -10,15 +10,7 @@
|
||||
|
||||
<div class="col-12 col-md-6 p-1" style="min-height:500px;max-height:80vh;overflow: auto;">
|
||||
<TreeView TItem="OpcUaTagModel" Items="Items" ShowIcon="true" ShowCheckbox="true" AutoCheckParent="true" AutoCheckChildren="true" IsVirtualize="true"
|
||||
OnExpandNodeAsync=OnExpandNodeAsync OnTreeItemChecked="OnTreeItemChecked" OnTreeItemClick=@(async a=>
|
||||
{
|
||||
if(a?.Value?.Tag?.NodeId!=null)
|
||||
{
|
||||
ClickItem=a;
|
||||
NodeAttributes = Plc.ReadNoteAttributes(ClickItem.Value.NodeId.ToString());
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}) ShowSkeleton=ShowSkeleton
|
||||
OnExpandNodeAsync=OnExpandNodeAsync OnTreeItemChecked="OnTreeItemChecked" OnTreeItemClick=@(OnTreeItemClick) ShowSkeleton=ShowSkeleton
|
||||
IsAccordion ClickToggleNode ModelEqualityComparer="OpcUaImportVariable.ModelEqualityComparer" />
|
||||
</div>
|
||||
<div class="col-12 col-md-6 p-2" style="min-height:500px;max-height:80vh;overflow: auto;">
|
||||
|
@@ -46,6 +46,8 @@ public partial class OpcUaImportVariable
|
||||
private IEnumerable<OpcUaTagModel> Nodes;
|
||||
private bool ShowSkeleton = true;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Opc对象
|
||||
/// </summary>
|
||||
@@ -77,6 +79,17 @@ public partial class OpcUaImportVariable
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
private async Task OnTreeItemClick(TreeViewItem<OpcUaTagModel> item)
|
||||
{
|
||||
if (item?.Value?.Tag?.NodeId != null && Plc != null)
|
||||
{
|
||||
ClickItem = item;
|
||||
NodeAttributes = await Plc.ReadNoteAttributesAsync(ClickItem.Value.NodeId.ToString(), default).ConfigureAwait(false);
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构建树节点,传入的列表已经是树结构
|
||||
/// </summary>
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<Buttons>
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Connect">@OpcUaPropertyLocalizer["Connect"]</Button>
|
||||
|
||||
<Button IsAsync Color="Color.Warning" OnClick="Disconnect">@OpcUaPropertyLocalizer["Disconnect"]</Button>
|
||||
<Button IsAsync Color="Color.Warning" OnClick="DisconnectAsync">@OpcUaPropertyLocalizer["Disconnect"]</Button>
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Export">@OpcUaPropertyLocalizer["ExportC"]</Button>
|
||||
</Buttons>
|
||||
|
@@ -24,7 +24,7 @@ using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class OpcUaMaster : IDisposable
|
||||
public partial class OpcUaMaster : IAsyncDisposable
|
||||
{
|
||||
public LoggerGroup? LogMessage;
|
||||
private readonly OpcUaProperty OpcUaProperty = new();
|
||||
@@ -37,7 +37,8 @@ public partial class OpcUaMaster : IDisposable
|
||||
/// <inheritdoc/>
|
||||
~OpcUaMaster()
|
||||
{
|
||||
this.SafeDispose();
|
||||
if (_plc != null)
|
||||
_ = _plc.SafeDisposeAsync();
|
||||
}
|
||||
|
||||
private DeviceComponent DeviceComponent { get; set; }
|
||||
@@ -49,9 +50,10 @@ public partial class OpcUaMaster : IDisposable
|
||||
private IStringLocalizer<OpcUaProperty> OpcUaPropertyLocalizer { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_plc?.SafeDispose();
|
||||
if (_plc != null)
|
||||
await _plc.SafeDisposeAsync();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
[Inject]
|
||||
@@ -129,11 +131,11 @@ public partial class OpcUaMaster : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
private async Task DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_plc.Disconnect();
|
||||
await _plc.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@@ -25,27 +25,27 @@
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" />
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.377.21" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.377.21" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.377.21" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.377.21" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.377.21" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.377.21" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
|
Reference in New Issue
Block a user