feat: 更新依赖包

This commit is contained in:
2248356998 qq.com
2025-02-03 19:57:16 +08:00
parent ee4936b8c9
commit f87ce10dc4
71 changed files with 6382 additions and 68 deletions

View File

@@ -21,15 +21,19 @@
<PackageReference Include="BootstrapBlazor.MiniExcel" Version="1.38.0" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="MailKit" Version="4.9.0" />
<PackageReference Include="MailKit" Version="4.10.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.175" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.*" />
<PackageReference Include="System.Formats.Asn1" Version="8.*" />
<PackageReference Include="System.Threading.RateLimiting" Version="8.*" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.*" />
<PackageReference Include="System.Formats.Asn1" Version="9.*" />
<PackageReference Include="System.Threading.RateLimiting" Version="9.*" />
</ItemGroup>
<ItemGroup>
<Content Remove="SeedData\Admin\*.json" />

View File

@@ -41,13 +41,20 @@
</Content>
</ItemGroup>
<!--安装服务守护-->
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.*" />
</ItemGroup>
<!--安装服务守护-->
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.*" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.*" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.*" />
</ItemGroup>
<ItemGroup>
<None Update="Dockerfile">

View File

@@ -38,23 +38,27 @@
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="System.Text.Json" Version="9.*" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.*" />
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.3.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.*" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.*" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.*" />
<PackageReference Include="System.Text.Json" Version="8.*" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.*" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.*" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.*" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.*" />
<PackageReference Include="System.Text.Json" Version="9.*" />
</ItemGroup>

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor" Version="9.2.8" />
<PackageReference Include="BootstrapBlazor" Version="9.3.0" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.0.0.19</PluginVersion>
<ProPluginVersion>10.0.0.19</ProPluginVersion>
<PluginVersion>10.0.0.20</PluginVersion>
<ProPluginVersion>10.0.0.20</ProPluginVersion>
</PropertyGroup>
<PropertyGroup>

View File

@@ -181,7 +181,7 @@ public partial class LogConsole : IDisposable
string pattern = @"[\\/:*?""<>|]";
// 使用正则表达式将不符合规则的部分替换为下划线
string sanitizedFileName = Regex.Replace(HeaderText, pattern, "_");
await DownloadService.DownloadFromStreamAsync(sanitizedFileName + DateTime.Now.ToFileDateTimeFormat(), memoryStream);
await DownloadService.DownloadFromStreamAsync($"{sanitizedFileName}{DateTime.Now.ToFileDateTimeFormat()}.txt", memoryStream);
}
else
{

View File

@@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.*" />
<PackageReference Include="TouchSocket" Version="3.0.12" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.12" />
<PackageReference Include="TouchSocket" Version="3.0.13" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.13" />
</ItemGroup>
<ItemGroup>

View File

@@ -101,7 +101,7 @@ public class RuntimeInfoController : ControllerBase
if (GlobalData.ReadOnlyRealAlarmVariables.TryGetValue(variableName, out var variable))
{
await GlobalData.SysUserService.CheckApiDataScopeAsync(variable.CreateOrgId, variable.CreateUserId).ConfigureAwait(false);
GlobalData.AlarmHostedService.ConfirmAlarm(variable);
GlobalData.AlarmHostedService.ConfirmAlarm(variableName);
}
}

View File

@@ -94,7 +94,7 @@ public abstract class BusinessBase : DriverBase
// 获取当前设备需要采集的设备
CollectDevices = GlobalData.GetEnableDevices().Where(a => CurrentDevice.VariableRuntimes.Select(b => b.Value.DeviceId).ToHashSet().Contains(a.Key)).ToDictionary(a => a.Key, a => a.Value);
CollectDevices = GlobalData.GetEnableDevices().Where(a => variableRuntimes.Select(b => b.Value.DeviceId).ToHashSet().Contains(a.Key)).ToDictionary(a => a.Key, a => a.Value);
VariableRuntimes = variableRuntimes.ToDictionary();
}

View File

@@ -62,7 +62,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
{
VariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().ToDictionary();
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
{

View File

@@ -67,7 +67,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
if (_businessPropertyWithCacheInterval.IsAllVariable)
{
VariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().ToDictionary();
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
{

View File

@@ -55,7 +55,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
if (_businessPropertyWithCacheInterval.IsAllVariable)
{
VariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().ToDictionary();
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
{

View File

@@ -52,7 +52,7 @@ public abstract class CollectBase : DriverBase
public override void AfterVariablesChanged()
{
var currentDevice = CurrentDevice;
VariableRuntimes = currentDevice.VariableRuntimes.Where(a => a.Value.Enable).ToDictionary();
VariableRuntimes = currentDevice.VariableRuntimes.Where(a => a.Value.Enable).ToDictionary(a => a.Value.Name, a => a.Value);
//预热脚本,加速编译
VariableRuntimes.Where(a => !string.IsNullOrWhiteSpace(a.Value.ReadExpressions))

View File

@@ -387,7 +387,7 @@ public static class GlobalData
/// <summary>
/// 内部使用的报警配置变量字典
/// </summary>
internal static ConcurrentDictionary<string, VariableRuntime> AlarmEnableVariables { get; } = new();
internal static ConcurrentDictionary<long, VariableRuntime> AlarmEnableVariables { get; } = new();
/// <summary>
/// 内部使用的报警配置变量字典

View File

@@ -128,7 +128,7 @@ public class DeviceRuntime : Device, IDisposable
[Newtonsoft.Json.JsonIgnore]
[AdaptIgnore]
[AutoGenerateColumn(Ignore = true)]
public IReadOnlyDictionary<string, VariableRuntime>? ReadVariableRuntimes => VariableRuntimes;
public IReadOnlyDictionary<long, VariableRuntime>? ReadVariableRuntimes => VariableRuntimes;
/// <summary>
/// 设备变量
@@ -137,7 +137,7 @@ public class DeviceRuntime : Device, IDisposable
[Newtonsoft.Json.JsonIgnore]
[AdaptIgnore]
[AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; set; } = new(Environment.ProcessorCount, 1000);
internal ConcurrentDictionary<long, VariableRuntime>? VariableRuntimes { get; set; } = new(Environment.ProcessorCount, 1000);
#region
/// <summary>

View File

@@ -316,31 +316,31 @@ public class VariableRuntime : Variable, IVariable, IDisposable
#endregion
public void Init(DeviceRuntime deviceRuntime)
{
DeviceRuntime?.VariableRuntimes?.TryRemove(Name, out _);
DeviceRuntime?.VariableRuntimes?.TryRemove(Id, out _);
DeviceRuntime = deviceRuntime;
DeviceRuntime.VariableRuntimes.TryAdd(Name, this);
DeviceRuntime.VariableRuntimes.TryAdd(Id, this);
GlobalData.IdVariables.TryRemove(Id, out _);
GlobalData.IdVariables.TryAdd(Id, this);
GlobalData.Variables.TryRemove(Name, out _);
GlobalData.Variables.TryAdd(Name, this);
if (AlarmEnable)
{
GlobalData.AlarmEnableVariables.TryRemove(Name, out _);
GlobalData.AlarmEnableVariables.TryAdd(Name, this);
GlobalData.AlarmEnableVariables.TryRemove(Id, out _);
GlobalData.AlarmEnableVariables.TryAdd(Id, this);
}
}
public void Dispose()
{
DeviceRuntime?.VariableRuntimes?.TryRemove(Name, out _);
DeviceRuntime?.VariableRuntimes?.TryRemove(Id, out _);
GlobalData.IdVariables.TryRemove(Id, out _);
GlobalData.Variables.TryRemove(Name, out _);
GlobalData.AlarmEnableVariables.TryRemove(Name, out _);
GlobalData.AlarmEnableVariables.TryRemove(Id, out _);
GC.SuppressFinalize(this);
}

View File

@@ -376,12 +376,15 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
}
public void ConfirmAlarm(VariableRuntime item)
public void ConfirmAlarm(string variableName)
{
// 如果是确认报警事件
item.EventType = EventTypeEnum.Confirm;
item.EventTime = DateTime.Now;
GlobalData.AlarmChange(item.Adapt<AlarmVariable>());
if (GlobalData.RealAlarmVariables.TryGetValue(variableName, out var variableRuntime))
{
variableRuntime.EventType = EventTypeEnum.Confirm;
variableRuntime.EventTime = DateTime.Now;
GlobalData.AlarmChange(variableRuntime.Adapt<AlarmVariable>());
}
}
#endregion
@@ -425,7 +428,7 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
foreach (var item in GlobalData.RealAlarmVariables)
{
if (!GlobalData.AlarmEnableVariables.ContainsKey(item.Key))
if (!GlobalData.AlarmEnableVariables.ContainsKey(item.Value.Id))
{
if (GlobalData.RealAlarmVariables.TryRemove(item.Key, out var oldAlarm))
{

View File

@@ -17,6 +17,5 @@ public interface IAlarmHostedService : IHostedService
/// <summary>
/// 确认报警
/// </summary>
/// <param name="item"></param>
void ConfirmAlarm(VariableRuntime item);
void ConfirmAlarm(string variableName);
}

View File

@@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.1" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.12" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.12" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.13" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.13" />
</ItemGroup>

View File

@@ -466,7 +466,7 @@ public partial class ChannelDeviceTree : IDisposable
{
var op = new DialogOption()
{
IsScrolling = false,
IsScrolling = true,
ShowMaximizeButton = true,
Size = Size.ExtraLarge,
Title = item.Text,
@@ -515,7 +515,7 @@ public partial class ChannelDeviceTree : IDisposable
var op = new DialogOption()
{
IsScrolling = false,
IsScrolling = true,
ShowMaximizeButton = true,
Size = Size.ExtraLarge,
Title = item.Text,

View File

@@ -147,7 +147,7 @@ public partial class VariableEditComponent
var op = new DialogOption()
{
IsScrolling = false,
IsScrolling = true,
ShowMaximizeButton = true,
Size = Size.ExtraLarge,
Title = DeviceLocalizer["SaveDevice"],

View File

@@ -130,7 +130,7 @@ public partial class VariableRuntimeInfo : IDisposable
{
var op = new DialogOption()
{
IsScrolling = false,
IsScrolling = true,
ShowMaximizeButton = true,
Size = Size.ExtraLarge,
Title = RazorLocalizer["BatchEdit"],

View File

@@ -6,8 +6,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "other", "other", "{0B748352
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
..\git_pull.bat = ..\git_pull.bat
PluginVersion.props = PluginVersion.props
Version.props = Version.props
EndProjectSection
EndProject

View File

@@ -31,11 +31,15 @@
"Url": "/OpcUaMaster",
"Text": "OpcUaMaster"
},
{
"Url": "/OpcUaMaster105",
"Text": "OpcUaMaster105"
},
{
"Url": "/OpcDaMaster",
"Text": "OpcDaMaster"
}
]
}
}

View File

@@ -31,6 +31,10 @@
"Url": "/OpcUaMaster",
"Text": "OpcUaMaster"
},
{
"Url": "/OpcUaMaster105",
"Text": "OpcUaMaster105"
},
{
"Url": "/OpcDaMaster",
"Text": "OpcDaMaster"

View File

@@ -12,8 +12,6 @@ namespace ThingsGateway.Foundation.OpcUa;
internal static class CollectionExtension
{
/// <summary>
/// 将项目列表分解为特定大小的块
/// </summary>

View File

@@ -0,0 +1,16 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
global using Newtonsoft.Json.Linq;
global using Opc.Ua;
global using Opc.Ua.Client;
global using Opc.Ua.Client.ComplexTypes;
global using Opc.Ua.Configuration;

View File

@@ -0,0 +1,37 @@
{
"ThingsGateway.Foundation.OpcUa105.OpcUaProperty": {
"OpcUrl": "OpcUrl",
"UserName": "UserName",
"Password": "Password",
"CheckDomain": "CheckDomain",
"UpdateRate": "UpdateRate",
"ActiveSubscribe": "ActiveSubscribe",
"GroupSize": "GroupSize",
"DeadBand": "DeadBand",
"KeepAliveInterval": "KeepAliveInterval(ms)",
"UseSecurity": "IsUseSecurity",
"LoadType": "LoadType",
"AutoAcceptUntrustedCertificates": "AutoAcceptUntrustedCertificates",
"ExportC": "ExportCertificate",
"Connect": "Connect",
"Disconnect": "Disconnect",
"Add": "Add",
"Remove": "Remove",
"Read": "Read",
"Write": "Write",
"ShowImport": "View OPC space",
"RegisterAddress": "RegisterAddress",
"WriteValue": "WriteValue",
"NoVariablesAvailable": "No variables available",
"Success": "Success",
"Close": "Close",
"Save": "Import to system",
"Export": "Export",
"ShowSubvariable": "ShowSubvariable"
},
"ThingsGateway.Foundation.OpcUa105.OpcUaResource": {
}
}

View File

@@ -0,0 +1,36 @@
{
"ThingsGateway.Foundation.OpcUa105.OpcUaProperty": {
"OpcUrl": "OpcUrl",
"UserName": "登录账号",
"Password": "登录密码",
"CheckDomain": "检查域",
"UpdateRate": "推送间隔",
"ActiveSubscribe": "订阅",
"GroupSize": "分组大小",
"DeadBand": "死区",
"KeepAliveInterval": "心跳间隔(ms)",
"UseSecurity": "安全策略",
"LoadType": "加载服务端数据类型",
"AutoAcceptUntrustedCertificates": "自动接受不受信任的证书",
"ExportC": "导出证书",
"Connect": "连接",
"Disconnect": "断开",
"Add": "添加",
"Remove": "移除",
"Read": "读取",
"Write": "写入",
"ShowImport": "查看OPC空间",
"RegisterAddress": "地址",
"WriteValue": "写入值",
"NoVariablesAvailable": "无可用变量",
"Success": "成功",
"Close": "关闭",
"Save": "导入到系统",
"Export": "导出",
"ShowSubvariable": "显示变量子节点"
},
"ThingsGateway.Foundation.OpcUa105.OpcUaResource": {
}
}

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation.OpcUa105;
/// <summary>
/// 读取属性过程中用于描述的
/// </summary>
public class OPCNodeAttribute
{
/// <summary>
/// 属性的名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 操作结果状态描述
/// </summary>
public StatusCode StatusCode { get; set; }
/// <summary>
/// 属性的类型描述
/// </summary>
public string Type { get; set; }
/// <summary>
/// 属性的值,如果读取错误,返回文本描述
/// </summary>
public object Value { get; set; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation.OpcUa105;
/// <summary>
/// OpcUaMaster配置项
/// </summary>
public class OpcUaProperty
{
/// <summary>
/// 是否订阅
/// </summary>
public bool ActiveSubscribe { get; set; } = true;
/// <summary>
/// 自动接受证书
/// </summary>
public bool AutoAcceptUntrustedCertificates { get; set; } = true;
/// <summary>
/// 检查域
/// </summary>
public bool CheckDomain { get; set; }
/// <summary>
/// 死区
/// </summary>
public double DeadBand { get; set; } = 0;
/// <summary>
/// 分组大小
/// </summary>
public int GroupSize { get; set; } = 500;
public int KeepAliveInterval { get; set; } = 3000;
/// <summary>
/// 加载服务端数据类型
/// </summary>
public bool LoadType { get; set; } = true;
/// <summary>
/// OpcUrl
/// </summary>
public string OpcUrl { get; set; } = "opc.tcp://127.0.0.1:49320";
/// <summary>
/// 登录密码
/// </summary>
public string Password { get; set; }
/// <summary>
/// 更新间隔
/// </summary>
public int UpdateRate { get; set; } = 1000;
/// <summary>
/// 登录账号
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 安全策略
/// </summary>
public bool UseSecurity { get; set; } = false;
/// <inheritdoc/>
public override string ToString()
{
return OpcUrl;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)PackNuget.props" />
<Import Project="$(SolutionDir)Version.props" />
<PropertyGroup>
<TargetFrameworks>net48;netstandard2.1;net6.0;</TargetFrameworks>
<Description>工业设备通讯协议-OpcUa协议</Description>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.375.443" />
</ItemGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation.OpcUa105;
internal static class CollectionExtension
{
/// <summary>
/// 将项目列表分解为特定大小的块
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">原数组</param>
/// <param name="chunkSize">分组大小</param>
/// <param name="isToList">是否ToList</param>
/// <returns></returns>
internal static IEnumerable<IEnumerable<T>> ChunkBetter<T>(this IEnumerable<T> source, int chunkSize, bool isToList = false)
{
if (chunkSize <= 0)
chunkSize = source.Count();
var pos = 0;
while (source.Skip(pos).Any())
{
var chunk = source.Skip(pos).Take(chunkSize);
yield return isToList ? chunk.ToList() : chunk;
pos += chunkSize;
}
}
/// <summary>
/// 移除符合条件的元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="this"></param>
/// <param name="where"></param>
internal static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
{
foreach (var obj in @this.Where(where).ToList())
{
@this.Remove(obj);
}
}
/// <summary>
/// 异步Select
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="selector"></param>
/// <returns></returns>
internal static Task<TResult[]> SelectAsync<T, TResult>(this IEnumerable<T> source, Func<T, Task<TResult>> selector)
{
return Task.WhenAll(source.Select(selector));
}
}

View File

@@ -0,0 +1,106 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
namespace ThingsGateway.Foundation.OpcUa105;
/// <summary>
/// DictionaryExtension
/// </summary>
internal static class DictionaryExtension
{
#region
/// <summary>
/// 移除满足条件的项目。
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <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) where TKey : notnull
{
var list = new List<TKey>();
foreach (var item in pairs)
{
if (func?.Invoke(item) == true)
{
list.Add(item.Key);
}
}
var count = 0;
foreach (var item in list)
{
if (pairs.TryRemove(item, out _))
{
count++;
}
}
return count;
}
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER
/// <summary>
/// 尝试添加
/// </summary>
/// <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)
{
if (dictionary.ContainsKey(tkey))
{
return false;
}
dictionary.Add(tkey, value);
return true;
}
#endif
/// <summary>
/// 尝试添加
/// </summary>
/// <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) where TKey : notnull
{
if (!dictionary.TryAdd(tkey, value))
{
dictionary[tkey] = value;
}
}
/// <summary>
/// 获取值。如果键不存在,则返回默认值。
/// </summary>
/// <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) where TKey : notnull
{
return dictionary.TryGetValue(tkey, out var value) ? value : default;
}
#endregion
}

View File

@@ -0,0 +1,530 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json;
using System.Collections;
using System.Text;
using System.Xml;
#pragma warning disable CS8605 // 取消装箱可能为 null 的值。
namespace ThingsGateway.Foundation.OpcUa105;
/// <summary>
/// 扩展方法
/// </summary>
public static class JsonUtils
{
#region Decode
/// <summary>
/// 解析获取DataValue
/// </summary>
/// <returns></returns>
public static DataValue Decode(
IServiceMessageContext Context,
NodeId dataTypeId,
BuiltInType builtInType,
int valueRank,
JToken json
)
{
var data = DecoderObject(Context, dataTypeId, builtInType, valueRank, json);
var dataValue = new DataValue(new Variant(data));
return dataValue;
}
/// <summary>
/// 解析获取object
/// </summary>
/// <returns></returns>
public static object DecoderObject(
IServiceMessageContext Context,
NodeId dataTypeId,
BuiltInType builtInType,
int valueRank,
JToken json
)
{
object newData;
switch (builtInType)
{
case BuiltInType.ExtensionObject:
newData = new
{
Value = new
{
TypeId = new { Id = dataTypeId.Identifier, Namespace = dataTypeId.NamespaceIndex },
Body = json
}
}.ToJsonString();
break;
case BuiltInType.Variant:
var type = TypeInfo.GetDataTypeId(GetSystemType(json.Type));
newData = new
{
Value = new
{
Type = type.Identifier,
Body = json
}
}.ToJsonString();
break;
default:
newData = new
{
Value = json
}.ToJsonString();
break;
}
using var decoder = new JsonDecoder(newData.ToString(), Context);
var data = DecodeRawData(decoder, builtInType, valueRank, "Value");
return data;
}
/// <summary>
/// DecodeRawData
/// </summary>
/// <returns></returns>
private static object DecodeRawData(JsonDecoder decoder, BuiltInType builtInType, int ValueRank, string fieldName)
{
if (builtInType != 0)
{
if (ValueRank == ValueRanks.Scalar)
{
switch (builtInType)
{
case BuiltInType.Null: { var variant = decoder.ReadVariant(fieldName); return variant.Value; }
case BuiltInType.Boolean: { return decoder.ReadBoolean(fieldName); }
case BuiltInType.SByte: { return decoder.ReadSByte(fieldName); }
case BuiltInType.Byte: { return decoder.ReadByte(fieldName); }
case BuiltInType.Int16: { return decoder.ReadInt16(fieldName); }
case BuiltInType.UInt16: { return decoder.ReadUInt16(fieldName); }
case BuiltInType.Int32: { return decoder.ReadInt32(fieldName); }
case BuiltInType.UInt32: { return decoder.ReadUInt32(fieldName); }
case BuiltInType.Int64: { return decoder.ReadInt64(fieldName); }
case BuiltInType.UInt64: { return decoder.ReadUInt64(fieldName); }
case BuiltInType.Float: { return decoder.ReadFloat(fieldName); }
case BuiltInType.Double: { return decoder.ReadDouble(fieldName); }
case BuiltInType.String: { return decoder.ReadField(fieldName, out var cancellationToken) ? cancellationToken?.ToString() : null; }
case BuiltInType.DateTime: { return decoder.ReadDateTime(fieldName); }
case BuiltInType.Guid: { return decoder.ReadGuid(fieldName); }
case BuiltInType.ByteString: { return decoder.ReadByteString(fieldName); }
case BuiltInType.XmlElement: { return decoder.ReadXmlElement(fieldName); }
case BuiltInType.NodeId: { return decoder.ReadNodeId(fieldName); }
case BuiltInType.ExpandedNodeId: { return decoder.ReadExpandedNodeId(fieldName); }
case BuiltInType.StatusCode: { return decoder.ReadStatusCode(fieldName); }
case BuiltInType.QualifiedName: { return decoder.ReadQualifiedName(fieldName); }
case BuiltInType.LocalizedText: { return decoder.ReadLocalizedText(fieldName); }
case BuiltInType.ExtensionObject: { return decoder.ReadExtensionObject(fieldName); }
case BuiltInType.DataValue: { return decoder.ReadDataValue(fieldName); }
case BuiltInType.Enumeration:
{
Type type = TypeInfo.GetSystemType(builtInType, ValueRank);
return type.IsEnum ? decoder.ReadEnumerated(fieldName, type) : decoder.ReadInt32(fieldName);
}
case BuiltInType.DiagnosticInfo: { return decoder.ReadDiagnosticInfo(fieldName); }
case BuiltInType.Variant: { return decoder.ReadVariant(fieldName); }
}
}
if (ValueRank >= ValueRanks.OneDimension)
{
return decoder.ReadArray(fieldName, ValueRank, builtInType);
}
}
return null;
}
#endregion Decode
#region Encode
/// <summary>
/// OPCUAValue解析为Jtoken
/// </summary>
/// <param name="Context"></param>
/// <param name="type"></param>
/// <param name="value"></param>
/// <returns></returns>
internal static JToken Encode(
IServiceMessageContext Context,
BuiltInType type,
object value
)
{
//对于IntegerInt64Number等会转化为string JValue
using var encoder = CreateEncoder(Context, null, false);
Encode(encoder, type, "Value", value);
var textbuffer = encoder.CloseAndReturnText();
using var stringReader = new StringReader(textbuffer);
using var jsonReader = new JsonTextReader(stringReader);
var jToken = JToken.Load(jsonReader);
return jToken["Value"];
}
/// <summary>
/// CreateEncoder
/// </summary>
/// <returns></returns>
private static JsonEncoder CreateEncoder(
IServiceMessageContext context,
Stream stream,
bool useReversibleEncoding = false,
bool topLevelIsArray = false,
bool includeDefaultValues = true,
bool includeDefaultNumbers = true
)
{
return new JsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream)
{
IncludeDefaultValues = includeDefaultValues,
IncludeDefaultNumberValues = includeDefaultNumbers
};
}
private static void Encode(JsonEncoder encoder, BuiltInType builtInType, string fieldName, object value)
{
bool isArray = (value?.GetType().IsArray ?? false) && (builtInType != BuiltInType.ByteString);
bool isCollection = (value is IList) && (builtInType != BuiltInType.ByteString);
if (!isArray && !isCollection)
{
switch (builtInType)
{
case BuiltInType.Null: { encoder.WriteVariant(fieldName, new Variant(value)); return; }
//case BuiltInType.Boolean: { encoder.WriteBoolean(fieldName, (bool)value); return; }
//case BuiltInType.SByte: { encoder.WriteSByte(fieldName, (sbyte)value); return; }
//case BuiltInType.Byte: { encoder.WriteByte(fieldName, (byte)value); return; }
//case BuiltInType.Int16: { encoder.WriteInt16(fieldName, (short)value); return; }
//case BuiltInType.UInt16: { encoder.WriteUInt16(fieldName, (ushort)value); return; }
//case BuiltInType.Int32: { encoder.WriteInt32(fieldName, (int)value); return; }
//case BuiltInType.UInt32: { encoder.WriteUInt32(fieldName, (uint)value); return; }
//case BuiltInType.Int64: { encoder.WriteInt64(fieldName, (long)value); return; }
//case BuiltInType.UInt64: { encoder.WriteUInt64(fieldName, (ulong)value); return; }
//case BuiltInType.Float: { encoder.WriteFloat(fieldName, (float)value); return; }
//case BuiltInType.Double: { encoder.WriteDouble(fieldName, (double)value); return; }
//case BuiltInType.Integer: { encoder.WriteInt64(fieldName, (long)value); return; }
//case BuiltInType.Number: { encoder.WriteInt64(fieldName, (long)value); return; }
//case BuiltInType.UInteger: { encoder.WriteUInt64(fieldName, (ulong)value); return; }
//case BuiltInType.String: { encoder.WriteString(fieldName, value?.ToString()); return; }
//case BuiltInType.DateTime: { encoder.WriteDateTime(fieldName, (DateTime)value); return; }
case BuiltInType.Boolean:
{
encoder.WriteBoolean(fieldName, Convert.ToBoolean(value));
return;
}
case BuiltInType.SByte:
{
encoder.WriteSByte(fieldName, Convert.ToSByte(value));
return;
}
case BuiltInType.Byte:
{
encoder.WriteByte(fieldName, Convert.ToByte(value));
return;
}
case BuiltInType.Int16:
{
encoder.WriteInt16(fieldName, Convert.ToInt16(value));
return;
}
case BuiltInType.UInt16:
{
encoder.WriteUInt16(fieldName, Convert.ToUInt16(value));
return;
}
case BuiltInType.Int32:
{
encoder.WriteInt32(fieldName, Convert.ToInt32(value));
return;
}
case BuiltInType.UInt32:
{
encoder.WriteUInt32(fieldName, Convert.ToUInt32(value));
return;
}
case BuiltInType.Int64:
{
encoder.WriteInt64(fieldName, Convert.ToInt64(value));
return;
}
case BuiltInType.UInt64:
{
encoder.WriteUInt64(fieldName, Convert.ToUInt64(value));
return;
}
case BuiltInType.Float:
{
encoder.WriteFloat(fieldName, Convert.ToSingle(value));
return;
}
case BuiltInType.Double:
{
encoder.WriteDouble(fieldName, Convert.ToDouble(value));
return;
}
case BuiltInType.Integer:
{
encoder.WriteInt64(fieldName, Convert.ToInt64(value));
return;
}
case BuiltInType.Number:
{
encoder.WriteInt64(fieldName, Convert.ToInt64(value));
return;
}
case BuiltInType.UInteger:
{
encoder.WriteUInt64(fieldName, Convert.ToUInt64(value));
return;
}
case BuiltInType.String:
{
encoder.WriteString(fieldName, Convert.ToString(value));
return;
}
case BuiltInType.DateTime:
{
encoder.WriteDateTime(fieldName, Convert.ToDateTime(value));
return;
}
case BuiltInType.Guid: { encoder.WriteGuid(fieldName, (Uuid)value); return; }
case BuiltInType.ByteString: { encoder.WriteByteString(fieldName, (byte[])value); return; }
case BuiltInType.XmlElement: { encoder.WriteXmlElement(fieldName, (XmlElement)value); return; }
case BuiltInType.NodeId: { encoder.WriteNodeId(fieldName, (NodeId)value); return; }
case BuiltInType.ExpandedNodeId: { encoder.WriteExpandedNodeId(fieldName, (ExpandedNodeId)value); return; }
case BuiltInType.StatusCode: { encoder.WriteStatusCode(fieldName, (StatusCode)value); return; }
case BuiltInType.QualifiedName: { encoder.WriteQualifiedName(fieldName, (QualifiedName)value); return; }
case BuiltInType.LocalizedText: { encoder.WriteLocalizedText(fieldName, (LocalizedText)value); return; }
case BuiltInType.ExtensionObject: { encoder.WriteExtensionObject(fieldName, (ExtensionObject)value); return; }
case BuiltInType.DataValue: { encoder.WriteDataValue(fieldName, (DataValue)value); return; }
case BuiltInType.Enumeration:
{
if (value?.GetType().IsEnum == true)
{
encoder.WriteEnumerated(fieldName, (Enum)value);
}
else
{
encoder.WriteEnumerated(fieldName, (Enumeration)value);
}
return;
}
case BuiltInType.Variant: { encoder.WriteVariant(fieldName, new Variant(value)); return; }
case BuiltInType.DiagnosticInfo: { encoder.WriteDiagnosticInfo(fieldName, (DiagnosticInfo)value); return; }
//case BuiltInType.Boolean:
//case BuiltInType.SByte:
//case BuiltInType.Byte:
//case BuiltInType.Int16:
//case BuiltInType.UInt16:
//case BuiltInType.Int32:
//case BuiltInType.UInt32:
//case BuiltInType.Int64:
//case BuiltInType.UInt64:
//case BuiltInType.Float:
//case BuiltInType.Double:
//case BuiltInType.String:
//case BuiltInType.Number:
//case BuiltInType.Integer:
//case BuiltInType.UInteger:
// { encoder.WriteString(fieldName, value?.ToString()); return; }
}
}
else
{
Array c = value as Array;
encoder.WriteArray(fieldName, c, c.Rank, builtInType);
}
}
#endregion Encode
#region json
/// <summary>
/// 维度
/// </summary>
/// <param name="jToken"></param>
/// <returns></returns>
internal static int CalculateActualValueRank(this JToken jToken)
{
if (jToken.Type != JTokenType.Array)
return -1;
var jArray = jToken.ToArray();
int numDimensions = 1;
while (jArray.GetElementsType() == JTokenType.Array)
{
jArray = jArray.Children().ToArray();
numDimensions++;
}
return numDimensions;
}
private static bool ElementsHasSameType(this JToken[] jTokens)
{
var checkType = jTokens[0].Type == JTokenType.Integer ? JTokenType.Float : jTokens[0].Type;
return jTokens
.Select(x => (x.Type == JTokenType.Integer) ? JTokenType.Float : x.Type)
.All(t => t == checkType);
}
private static JTokenType GetElementsType(this JToken[] jTokens)
{
if (!jTokens.ElementsHasSameType())
throw new Exception("The array sent must have the same type of element in each dimension");
return jTokens.First().Type;
}
private static Type GetSystemType(JTokenType jsonType)
{
return jsonType switch
{
JTokenType.None => typeof(string),
JTokenType.Object => typeof(string),
JTokenType.Array => typeof(Array),
JTokenType.Constructor => typeof(string),
JTokenType.Property => typeof(string),
JTokenType.Comment => typeof(string),
JTokenType.Integer => typeof(long),
JTokenType.Float => typeof(float),
JTokenType.String => typeof(string),
JTokenType.Boolean => typeof(bool),
JTokenType.Null => typeof(string),
JTokenType.Undefined => typeof(string),
JTokenType.Date => typeof(DateTime),
JTokenType.Raw => typeof(string),
JTokenType.Bytes => typeof(byte[]),
JTokenType.Guid => typeof(Guid),
JTokenType.Uri => typeof(Uri),
JTokenType.TimeSpan => typeof(TimeSpan),
_ => null,
};
}
#endregion json
#region Json序列化和反序列化
/// <summary>
/// 从字符串到json
/// </summary>
/// <param name="json"></param>
/// <param name="type"></param>
/// <returns></returns>
internal static object FromJsonString(this string json, Type type)
{
return Newtonsoft.Json.JsonConvert.DeserializeObject(json, type);
}
/// <summary>
/// 从字符串到json
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <returns></returns>
internal static T FromJsonString<T>(this string json)
{
return (T)FromJsonString(json, typeof(T));
}
/// <summary>
/// Json反序列化
/// </summary>
/// <typeparam name="T">反序列化类型</typeparam>
/// <param name="datas">数据</param>
/// <returns></returns>
internal static T JsonDeserializeFromBytes<T>(byte[] datas)
{
return (T)JsonDeserializeFromBytes(datas, typeof(T));
}
/// <summary>
/// Json反序列化
/// </summary>
/// <param name="datas"></param>
/// <param name="type"></param>
/// <returns></returns>
internal static object JsonDeserializeFromBytes(byte[] datas, Type type)
{
return FromJsonString(Encoding.UTF8.GetString(datas), type);
}
/// <summary>
/// Json反序列化
/// </summary>
/// <typeparam name="T">反序列化类型</typeparam>
/// <param name="path">文件路径</param>
/// <returns></returns>
internal static T JsonDeserializeFromFile<T>(string path)
{
return JsonDeserializeFromString<T>(File.ReadAllText(path));
}
/// <summary>
/// Json反序列化
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="json">json字符串</param>
/// <returns></returns>
internal static T JsonDeserializeFromString<T>(string json)
{
return FromJsonString<T>(json);
}
/// <summary>
/// Json序列化数据对象
/// </summary>
/// <param name="obj">数据对象</param>
/// <returns></returns>
internal static byte[] JsonSerializeToBytes(object obj)
{
return Encoding.UTF8.GetBytes(ToJsonString(obj));
}
/// <summary>
/// Json序列化至文件
/// </summary>
/// <param name="obj"></param>
/// <param name="path"></param>
internal static void JsonSerializeToFile(object obj, string path)
{
using (var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
var date = JsonSerializeToBytes(obj);
fileStream.Write(date, 0, date.Length);
fileStream.Close();
}
}
/// <summary>
/// 转换为Json
/// </summary>
/// <param name="item"></param>
/// <param name="isIndented"></param>
/// <returns></returns>
internal static string ToJsonString(this object item, bool isIndented = false)
{
if (isIndented)
return Newtonsoft.Json.JsonConvert.SerializeObject(item, Newtonsoft.Json.Formatting.Indented);
else
return Newtonsoft.Json.JsonConvert.SerializeObject(item);
}
#endregion Json序列化和反序列化
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
<PkgOPCFoundation_NetStandard_Opc_Ua_Security_CertificatesPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Security_Certificates)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_ClientPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Client)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypesPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypes)\lib\net8.0\*.*" />
<PkgSystem_Formats_Asn1PackageFiles Include="$(PkgSystem_Formats_Asn1)\lib\net9.0\*.*" />
<PkgSystem_Formats_Asn1PackageFiles Include="$(PkgSystem_Formats_Asn1)\lib\net8.0\*.*" />
</ItemGroup>
<PropertyGroup>

View File

@@ -167,7 +167,7 @@ public partial class ThingsGatewayServer : StandardServer
configuration.SecurityConfiguration.UserIssuerCertificates != null)
{
CertificateValidator certificateValidator = new();
certificateValidator.Update(configuration.SecurityConfiguration).Wait();
certificateValidator.Update(configuration).GetAwaiter().GetResult();
certificateValidator.Update(configuration.SecurityConfiguration.UserIssuerCertificates,
configuration.SecurityConfiguration.TrustedUserCertificates,
configuration.SecurityConfiguration.RejectedCertificateStore);

View File

@@ -57,7 +57,7 @@ public partial class OpcUaServer : BusinessBase
{
VariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().ToDictionary();
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
{

View File

@@ -1,4 +1,4 @@
@page "/OpcUaMaster"
@page "/OpcUaMaster105"
@using BootstrapBlazor.Components
@using ThingsGateway.Extension
@using ThingsGateway.Foundation

View File

@@ -49,7 +49,7 @@
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Formats.Asn1" Version="9.*" GeneratePathProperty="true">
<PackageReference Include="System.Formats.Asn1" Version="8.*" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>

View File

@@ -0,0 +1,42 @@
<Project>
<Target Name="CopyNugetPackages" AfterTargets="Build">
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0'">
<PkgOPCFoundation_NetStandard_Opc_Ua_ServerPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Server)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_ConfigurationPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Configuration)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_CorePackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Core)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_ServerPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Server)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_Security_CertificatesPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Security_Certificates)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_ClientPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Client)\lib\net8.0\*.*" />
<PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypesPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypes)\lib\net8.0\*.*" />
<PkgSystem_Formats_Asn1PackageFiles Include="$(PkgSystem_Formats_Asn1)\lib\net8.0\*.*" />
</ItemGroup>
<PropertyGroup>
<ApplicationFolder>$(TargetDir)</ApplicationFolder>
</PropertyGroup>
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_ServerPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_ConfigurationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_CorePackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_ServerPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_Security_CertificatesPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(PkgSystem_Formats_Asn1PackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<!--<Copy SourceFiles="@(PkgThingsGateway_Foundation_OpcUaPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />-->
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_ClientPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypesPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
</Target>
<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包-->
<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build">
<ItemGroup>
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Opc*.dll">
<Pack>true</Pack>
<PackagePath>Content</PackagePath>
</Content>
</ItemGroup>
</Target>
</Project>

View File

@@ -0,0 +1,14 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
global using System;
global using System.Collections.Generic;
global using ThingsGateway.Foundation;

View File

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

View File

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

View File

@@ -0,0 +1,359 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json.Linq;
using Opc.Ua;
using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.Foundation.OpcUa105;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.NewLife.Threading;
using TouchSocket.Core;
namespace ThingsGateway.Plugin.OpcUa105;
/// <summary>
/// <inheritdoc/>
/// </summary>
public class OpcUaMaster : CollectBase
{
private readonly OpcUaMasterProperty _driverProperties = new();
private ThingsGateway.Foundation.OpcUa105.OpcUaMaster _plc;
private CancellationToken _token;
private volatile bool connectFirstFail;
private volatile bool connectFirstFailLoged;
private volatile bool success = true;
/// <inheritdoc/>
public override CollectPropertyBase CollectProperties => _driverProperties;
/// <inheritdoc/>
public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcUaMaster);
public override Type DriverPropertyUIType => typeof(OpcUaMasterPropertyRazor);
protected override void InitChannel(IChannel? channel = null)
{
//载入配置
OpcUaProperty config = new()
{
OpcUrl = _driverProperties.OpcUrl,
UpdateRate = _driverProperties.UpdateRate,
DeadBand = _driverProperties.DeadBand,
GroupSize = _driverProperties.GroupSize,
KeepAliveInterval = _driverProperties.KeepAliveInterval,
UseSecurity = _driverProperties.UseSecurity,
ActiveSubscribe = _driverProperties.ActiveSubscribe,
UserName = _driverProperties.UserName,
Password = _driverProperties.Password,
CheckDomain = _driverProperties.CheckDomain,
LoadType = _driverProperties.LoadType,
AutoAcceptUntrustedCertificates = _driverProperties.AutoAcceptUntrustedCertificates,
};
if (_plc == null)
{
_plc = new();
_plc.LogEvent += _plc_LogEvent;
_plc.DataChangedHandler += DataChangedHandler;
}
_plc.OpcUaProperty = config;
base.InitChannel(channel);
}
/// <inheritdoc/>
public override bool IsConnected() => _plc?.Connected == true;
public override string ToString()
{
return $"{_driverProperties.OpcUrl}";
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (_plc != null)
{
_plc.DataChangedHandler -= DataChangedHandler;
_plc.LogEvent -= _plc_LogEvent;
_plc.Disconnect();
_plc.SafeDispose();
}
base.Dispose(disposing);
}
protected override string GetAddressDescription()
{
return _plc?.GetAddressDescription();
}
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
{
_token = cancellationToken;
try
{
await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "Connect Fail");
connectFirstFail = true;
}
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
}
protected override async ValueTask ProtectedExecuteAsync(CancellationToken cancellationToken)
{
if (connectFirstFail && !IsConnected())
{
try
{
await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
connectFirstFail = false;
}
catch (Exception ex)
{
if (!connectFirstFailLoged)
LogMessage?.LogWarning(ex, "Connect Fail");
connectFirstFailLoged = true;
CurrentDevice.SetDeviceStatus(TimerX.Now, true, ex.Message);
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
}
}
else
{
connectFirstFail = false;
}
if (_driverProperties.ActiveSubscribe)
{
//获取设备连接状态
if (IsConnected())
{
//更新设备活动时间
CurrentDevice.SetDeviceStatus(TimerX.Now, false);
}
else
{
CurrentDevice.SetDeviceStatus(TimerX.Now, true);
}
ScriptVariableRun(cancellationToken);
}
else
{
await base.ProtectedExecuteAsync(cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc/>
protected override List<VariableSourceRead> ProtectedLoadSourceRead(List<VariableRuntime> deviceVariables)
{
if (deviceVariables.Count > 0)
{
var dataLists = deviceVariables.ChunkBetter(_driverProperties.GroupSize);
_plc.Variables = new();
_plc.Variables.AddRange(dataLists.Select(a => a.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToList()).ToList());
var dataResult = new List<VariableSourceRead>();
foreach (var variable in dataLists)
{
var sourVars = new VariableSourceRead()
{
TimeTick = new(_driverProperties.UpdateRate.ToString()),
RegisterAddress = "",
};
foreach (var item in variable)
{
sourVars.AddVariable(item);
}
dataResult.Add(sourVars);
}
return dataResult;
}
else
{
return new();
}
}
/// <inheritdoc/>
protected override async ValueTask<OperResult<byte[]>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken)
{
DateTime time = DateTime.Now;
var addresss = deviceVariableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToArray();
try
{
await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
var result = await _plc.ReadJTokenValueAsync(addresss, cancellationToken).ConfigureAwait(false);
foreach (var data in result)
{
if (!cancellationToken.IsCancellationRequested)
{
var data1 = deviceVariableSourceRead.VariableRuntimes.Where(a => a.RegisterAddress == data.Item1);
foreach (var item in data1)
{
object value;
if (data.Item3 is JValue jValue)
{
value = jValue.Value;
}
else
{
value = data.Item3;
}
var isGood = StatusCode.IsGood(data.Item2.StatusCode);
if (_driverProperties.SourceTimestampEnable)
{
time = data.Item2.SourceTimestamp.ToLocalTime();
}
if (isGood)
{
item.SetValue(value, time);
}
else
{
item.SetValue(null, time, false);
item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString();
}
}
LogMessage.Trace($"{ToString()} Change:{Environment.NewLine}{data.Item1} : {data.Item3}");
}
}
if (result.Any(a => StatusCode.IsBad(a.Item2.StatusCode)))
{
return new OperResult<byte[]>($"OPC quality bad");
}
else
{
return OperResult.CreateSuccessResult<byte[]>(null);
}
}
catch (Exception ex)
{
return new OperResult<byte[]>($"ReadSourceAsync {addresss.ToJsonNetString()}{Environment.NewLine}{ex}");
}
finally
{
}
}
/// <inheritdoc/>
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
{
using var writeLock = ReadWriteLock.WriterLock();
try
{
var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken).ConfigureAwait(false);
return result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a =>
{
return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name!;
}
, a =>
{
if (!a.Value.Item1)
return new OperResult(a.Value.Item2);
else
return OperResult.Success;
})!;
}
finally
{
}
}
private void _plc_LogEvent(byte level, object sender, string message, Exception ex)
{
LogMessage?.Log((LogLevel)level, sender, message, ex);
}
private void DataChangedHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) data)
{
DateTime time = DateTime.Now;
try
{
if (CurrentDevice.Pause)
return;
if (_token.IsCancellationRequested)
return;
LogMessage.Trace($"{ToString()} Change: {Environment.NewLine} {data.variableNode.NodeId} : {data.jToken?.ToString()}");
if (CurrentDevice.Pause)
{
return;
}
//尝试固定点位的数据类型
var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
var itemReads = VariableRuntimes.Select(a => a.Value).Where(it => it.RegisterAddress == data.variableNode.NodeId);
object value;
if (data.jToken is JValue jValue)
{
value = jValue.Value;
}
else
{
value = data.jToken;
}
var isGood = StatusCode.IsGood(data.dataValue.StatusCode);
if (_driverProperties.SourceTimestampEnable)
{
time = data.dataValue.SourceTimestamp.ToLocalTime();
}
foreach (var item in itemReads)
{
if (CurrentDevice.Pause)
return;
if (_token.IsCancellationRequested)
return;
if (item.DataType == DataTypeEnum.Object)
if (type.Namespace.StartsWith("System"))
{
var enumValues = Enum.GetValues<DataTypeEnum>();
var stringList = enumValues.Select(e => e.ToString());
if (stringList.Contains(type.Name))
try { item.DataType = Enum.Parse<DataTypeEnum>(type.Name); } catch { }
}
if (isGood)
{
item.SetValue(value, time);
}
else
{
item.SetValue(null, time, false);
item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString();
}
}
success = true;
}
catch (Exception ex)
{
if (success)
LogMessage?.LogWarning(ex);
success = false;
}
}
}

View File

@@ -0,0 +1,98 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Gateway.Application;
namespace ThingsGateway.Plugin.OpcUa105;
/// <summary>
/// <inheritdoc/>
/// </summary>
public class OpcUaMasterProperty : CollectPropertyBase
{
/// <summary>
/// 连接Url
/// </summary>
[DynamicProperty]
public string OpcUrl { get; set; } = "opc.tcp://127.0.0.1:49320";
/// <summary>
/// 登录账号
/// </summary>
[DynamicProperty]
public string? UserName { get; set; }
/// <summary>
/// 登录密码
/// </summary>
[DynamicProperty]
public string? Password { get; set; }
/// <summary>
/// 检查域
/// </summary>
[DynamicProperty]
public bool CheckDomain { get; set; }
/// <summary>
/// 安全策略
/// </summary>
[DynamicProperty]
public bool UseSecurity { get; set; } = true;
/// <summary>
/// 接受不受信任的证书
/// </summary>
[DynamicProperty()]
public bool AutoAcceptUntrustedCertificates { get; set; } = true;
/// <summary>
/// 是否使用SourceTime
/// </summary>
[DynamicProperty]
public bool SourceTimestampEnable { get; set; } = true;
/// <summary>
/// 加载服务端数据类型
/// </summary>
[DynamicProperty]
public bool LoadType { get; set; } = true;
/// <summary>
/// 激活订阅
/// </summary>
[DynamicProperty]
public bool ActiveSubscribe { get; set; } = true;
/// <summary>
/// 更新频率
/// </summary>
[DynamicProperty]
public int UpdateRate { get; set; } = 1000;
/// <summary>
/// 死区
/// </summary>
[DynamicProperty]
public double DeadBand { get; set; } = 0;
/// <summary>
/// 最大组大小
/// </summary>
[DynamicProperty]
public int GroupSize { get; set; } = 500;
/// <summary>
/// 心跳频率
/// </summary>
[DynamicProperty]
public int KeepAliveInterval { get; set; } = 3000;
}

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Opc.Ua.Configuration;
using TouchSocket.Core;
namespace ThingsGateway.Plugin.OpcUa105;
public class ApplicationMessageDlg : IApplicationMessageDlg
{
private ILog _log;
private string message = string.Empty;
public ApplicationMessageDlg(ILog log)
{
_log = log;
}
public override void Message(string text, bool ask)
{
message = text;
}
public override Task<bool> ShowAsync()
{
_log.Warning(message);
return Task.FromResult(true);
}
}

View File

@@ -0,0 +1,65 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using TouchSocket.Core;
#pragma warning disable CS8633 // 类型参数的约束中的为 Null 性与隐式实现接口方法中的类型参数的约束不匹配。
#pragma warning disable CS8766 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配(可能是由于为 Null 性特性)。
namespace ThingsGateway.Plugin.OpcUa105;
internal sealed class OpcUaLogger : ILogger
{
private ILog _log;
public OpcUaLogger(ILog log)
{
_log = log;
}
/// <summary>
/// Set the log level
/// </summary>
public Microsoft.Extensions.Logging.LogLevel LogLevel { get; set; } = Microsoft.Extensions.Logging.LogLevel.Trace;
/// <inheritdoc/>
public IDisposable BeginScope<TState>(TState state) where TState : notnull
{
return default;
}
/// <inheritdoc/>
public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) => logLevel >= LogLevel;
/// <inheritdoc/>
public void Log<TState>(
Microsoft.Extensions.Logging.LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
else
{
var message = formatter(state, exception);
//if (logLevel > Microsoft.Extensions.Logging.LogLevel.Warning)
{
_log.Log((TouchSocket.Core.LogLevel)(byte)logLevel, state, message, exception);
}
}
}
}

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Opc.Ua;
namespace ThingsGateway.Plugin.OpcUa105;
internal sealed class OpcUaTag : BaseDataVariableState
{
internal OpcUaTag(NodeState parent) : base(parent)
{
}
/// <summary>
/// 变量Id
/// </summary>
internal long Id { get; set; }
internal bool IsDataTypeInit { get; set; }
}

View File

@@ -0,0 +1,477 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json.Linq;
using Opc.Ua;
using Opc.Ua.Server;
using ThingsGateway.Foundation.OpcUa105;
using ThingsGateway.Gateway.Application;
using TouchSocket.Core;
namespace ThingsGateway.Plugin.OpcUa105;
/// <summary>
/// 数据节点
/// </summary>
public class ThingsGatewayNodeManager : CustomNodeManager2
{
private const string ReferenceServer = "https://thingsgateway.cn/";
//private readonly TypeAdapterConfig _config;
/// <summary>
/// OPC和网关对应表
/// </summary>
private readonly Dictionary<string, OpcUaTag> NodeIdTags = new();
private BusinessBase _businessBase;
private volatile bool success = true;
/// <inheritdoc cref="ThingsGatewayNodeManager"/>
public ThingsGatewayNodeManager(BusinessBase businessBase, IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, ReferenceServer)
{
_businessBase = businessBase;
//_config = new TypeAdapterConfig();
//_config.ForType<HistoryValue, DataValue>()
//.Map(dest => dest.WrappedValue, (src) => new Variant(src.Value))
//.Map(dest => dest.SourceTimestamp, src => DateTime.SpecifyKind(src.CollectTime, DateTimeKind.Utc))
//.Map(dest => dest.StatusCode, (src) =>
//src.IsOnline ? StatusCodes.Good : StatusCodes.Bad);
}
/// <summary>
/// 创建服务目录结构
/// </summary>
/// <param name="externalReferences"></param>
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
{
lock (Lock)
{
if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out IList<IReference> references))
{
externalReferences[ObjectIds.ObjectsFolder] = references = new List<IReference>();
}
//首节点
FolderState rootFolder = CreateFolder(null, "ThingsGateway", "ThingsGateway");
rootFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, rootFolder.NodeId));
rootFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
AddRootNotifier(rootFolder);
//创建设备树
var _geviceGroup = _businessBase.VariableRuntimes.Select(a => a.Value)
.GroupBy(a => a.DeviceName);
// 开始寻找设备信息,并计算一些节点信息
foreach (var item in _geviceGroup)
{
//设备树会有两层
FolderState fs = CreateFolder(rootFolder, item.Key, item.Key);
fs.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
fs.EventNotifier = EventNotifiers.SubscribeToEvents;
if (item?.Count() > 0)
{
foreach (var item2 in item)
{
CreateVariable(fs, item2);
}
}
}
AddPredefinedNode(SystemContext, rootFolder);
}
}
///// <summary>
///// 读取历史数据
///// </summary>
//public override void HistoryRead(OperationContext context,
// HistoryReadDetails details,
// TimestampsToReturn timestampsToReturn,
// bool releaseContinuationPoints,
// IList<HistoryReadValueId> nodesToRead,
// IList<HistoryReadResult> results,
// IList<ServiceResult> errors)
//{
// base.HistoryRead(context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors);
// //必须带有时间范围
// if (details is not ReadRawModifiedDetails readDetail || readDetail.StartTime == DateTime.MinValue || readDetail.EndTime == DateTime.MinValue)
// {
// errors[0] = StatusCodes.BadHistoryOperationUnsupported;
// return;
// }
// var service = BackgroundServiceUtil.GetBackgroundService<HistoryValueWorker>();
// if (!service.StatuString.IsSuccess)
// {
// errors[0] = StatusCodes.BadHistoryOperationUnsupported;
// return;
// }
// var db = service.GetHistoryDbAsync().GetAwaiter().GetResult();
// if (!db.IsSuccess)
// {
// errors[0] = StatusCodes.BadHistoryOperationUnsupported;
// return;
// }
// var startTime = readDetail.StartTime;
// var endTime = readDetail.EndTime;
// for (int i = 0; i < nodesToRead.Count; i++)
// {
// var historyRead = nodesToRead[i];
// if (NodeIdTags.TryGetValue(historyRead.NodeId, out OPCUATag tag))
// {
// var data = db.Content.Queryable<HistoryValue>()
// .Where(a => a.Name == tag.SymbolicName)
// .Where(a => a.CollectTime >= startTime)
// .Where(a => a.CollectTime <= endTime)
// .ToList();
// if (data.Count > 0)
// {
// var hisDataValue = data.Adapt<List<DataValue>>(_config);
// HistoryData hisData = new();
// hisData.DataValues.AddRange(hisDataValue);
// errors[i] = StatusCodes.Good;
// //切记Processed设为true否则客户端会报错
// historyRead.Processed = true;
// results[i] = new HistoryReadResult()
// {
// StatusCode = StatusCodes.Good,
// HistoryData = new ExtensionObject(hisData)
// };
// }
// else
// {
// results[i] = new HistoryReadResult()
// {
// StatusCode = StatusCodes.GoodNoData
// };
// }
// }
// else
// {
// results[i] = new HistoryReadResult()
// {
// StatusCode = StatusCodes.BadNotFound
// };
// }
// }
//}
/// <inheritdoc/>
public override NodeId New(ISystemContext context, NodeState node)
{
if (node is BaseInstanceState instance && instance.Parent != null)
{
string id = instance.Parent.NodeId.Identifier?.ToString();
if (id != null)
{
//用下划线分割
return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
}
}
return node.NodeId;
}
/// <summary>
/// 更新变量
/// </summary>
/// <param name="variable"></param>
public void UpVariable(VariableData variable)
{
if (!NodeIdTags.TryGetValue(variable.Name, out var uaTag))
return;
object initialItemValue = null;
initialItemValue = variable.Value;
if (initialItemValue != null)
{
var code = variable.IsOnline ? StatusCodes.Good : StatusCodes.Bad;
if (code == StatusCodes.Good)
{
ChangeNodeData(uaTag, initialItemValue, variable.ChangeTime);
}
if (uaTag.StatusCode != code)
{
uaTag.StatusCode = code;
}
uaTag.UpdateChangeMasks(NodeStateChangeMasks.Value);
uaTag.ClearChangeMasks(SystemContext, false);
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
NodeIdTags.Clear();
base.Dispose(disposing);
}
private static NodeId DataNodeType(Type tp)
{
if (tp == typeof(bool))
return DataTypeIds.Boolean;
if (tp == typeof(byte))
return DataTypeIds.Byte;
if (tp == typeof(sbyte))
return DataTypeIds.SByte;
if (tp == typeof(short))
return DataTypeIds.Int16;
if (tp == typeof(ushort))
return DataTypeIds.UInt16;
if (tp == typeof(int))
return DataTypeIds.Int32;
if (tp == typeof(uint))
return DataTypeIds.UInt32;
if (tp == typeof(long))
return DataTypeIds.Int64;
if (tp == typeof(ulong))
return DataTypeIds.UInt64;
if (tp == typeof(float))
return DataTypeIds.Float;
if (tp == typeof(double))
return DataTypeIds.Double;
if (tp == typeof(string))
return DataTypeIds.String;
if (tp == typeof(DateTime))
return DataTypeIds.DateTime;
return DataTypeIds.String;
}
/// <summary>
/// 在服务器端直接更改对应数据节点的值
/// </summary>
private void ChangeNodeData(OpcUaTag tag, object value, DateTime dateTime)
{
object newValue;
try
{
if (!tag.IsDataTypeInit)
{
if (tag.DataType == DataTypeIds.String)
{
if (value != null)
{
SetDataType(tag, value);
}
}
else
{
SetRank(tag, value);
}
}
var jToken = JToken.FromObject((tag.DataType == DataTypeIds.String ? value?.ToString() : value));
var dataValue = JsonUtils.DecoderObject(
Server.MessageContext,
tag.DataType,
TypeInfo.GetBuiltInType(tag.DataType, SystemContext.TypeTable),
jToken.CalculateActualValueRank(),
jToken
);
newValue = dataValue;
success = true;
}
catch (Exception ex)
{
if (success)
_businessBase.LogMessage.LogWarning(ex, "Conversion value error");
success = false;
newValue = value;
}
tag.Value = newValue;
tag.Timestamp = dateTime;
void SetDataType(OpcUaTag tag, object value)
{
tag.IsDataTypeInit = true;
var tp = value.GetType();
if (tp == typeof(JArray))
{
try
{
tp = ((JValue)((JArray)value).FirstOrDefault()).Value.GetType();
tag.ValueRank = ValueRanks.OneOrMoreDimensions;
}
catch
{
}
}
if (tp == typeof(JValue))
{
tp = ((JValue)value).Value.GetType();
tag.ValueRank = ValueRanks.Scalar;
}
tag.DataType = DataNodeType(tp);
tag.ClearChangeMasks(SystemContext, false);
}
void SetRank(OpcUaTag tag, object value)
{
tag.IsDataTypeInit = true;
var tp = value.GetType();
if (tp == typeof(JArray))
{
try
{
tp = ((JValue)((JArray)value).FirstOrDefault()).Value.GetType();
tag.ValueRank = ValueRanks.OneOrMoreDimensions;
}
catch
{
}
}
tag.ClearChangeMasks(SystemContext, false);
}
}
/// <summary>
/// 创建文件夹
/// </summary>
private FolderState CreateFolder(NodeState parent, string name, string description)
{
FolderState folder = new(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = ObjectTypeIds.FolderType,
Description = description,
NodeId = new NodeId(name, NamespaceIndex),
BrowseName = new QualifiedName(name, NamespaceIndex),
DisplayName = new LocalizedText(name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None,
EventNotifier = EventNotifiers.None
};
parent?.AddChild(folder);
return folder;
}
/// <summary>
/// 创建一个值节点,类型需要在创建的时候指定
/// </summary>
private OpcUaTag CreateVariable(NodeState parent, VariableRuntime variableRuntime)
{
OpcUaTag variable = new(parent)
{
SymbolicName = variableRuntime.Name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
NodeId = new NodeId(variableRuntime.Name, NamespaceIndex),
Description = variableRuntime.Description,
BrowseName = new QualifiedName(variableRuntime.Name, NamespaceIndex),
DisplayName = new LocalizedText(variableRuntime.Name),
WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
ValueRank = ValueRanks.Scalar,
Id = variableRuntime.Id,
DataType = DataNodeType(variableRuntime)
};
var level = ThingsGatewayNodeManager.ProtectTypeTrans(variableRuntime);
variable.AccessLevel = level;
variable.UserAccessLevel = level;
variable.Historizing = false;//历史存储状态
variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(variable.DataType, ValueRanks.Any, Server.TypeTree);
var code = variableRuntime.IsOnline ? StatusCodes.Good : StatusCodes.Bad;
variable.StatusCode = code;
variable.Timestamp = variableRuntime.CollectTime ?? DateTime.MinValue;
variable.OnWriteValue = OnWriteDataValue;
parent?.AddChild(variable);
NodeIdTags.AddOrUpdate(variable.SymbolicName, variable);
return variable;
}
/// <summary>
/// 网关转OPC数据类型
/// </summary>
/// <param name="variableRuntime"></param>
/// <returns></returns>
private NodeId DataNodeType(VariableRuntime variableRuntime)
{
var str = variableRuntime.GetPropertyValue(_businessBase.DeviceId, nameof(OpcUaServerVariableProperty.DataType)) ?? "";
Type tp;
if (Enum.TryParse(str, out DataTypeEnum result))
{
tp = result.GetSystemType();
}
else
{
tp = variableRuntime.DataType.GetSystemType(); ;
}
return DataNodeType(tp);
}
private ServiceResult OnWriteDataValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
{
try
{
var context1 = context as ServerSystemContext;
//取消注释,插件不限制匿名用户的写入
//if (context1.UserIdentity.TokenType == UserTokenType.Anonymous)
//{
// return StatusCodes.BadUserAccessDenied;
//}
OpcUaTag variable = node as OpcUaTag;
if (NodeIdTags.TryGetValue(variable.SymbolicName, out OpcUaTag tag))
{
if (StatusCode.IsGood(variable.StatusCode))
{
//仅当指定了值时才将值写入
if (variable.Value != null)
{
var result = GlobalData.RpcService.InvokeDeviceMethodAsync("OpcUaSlave - " + context1?.OperationContext?.Session?.Identity?.DisplayName,
new()
{
{ variable.SymbolicName, value?.ToString() }
}
).ConfigureAwait(true).GetAwaiter().GetResult();
if (result.Values.FirstOrDefault().IsSuccess)
{
return StatusCodes.Good;
}
else
{
return new(StatusCodes.BadWaitingForResponse, result.Values.FirstOrDefault().ErrorMessage);
}
}
}
}
return StatusCodes.BadWaitingForResponse;
}
catch
{
return StatusCodes.BadTypeMismatch;
}
}
private static byte ProtectTypeTrans(VariableRuntime variableRuntime)
{
byte result = 0;
result = variableRuntime.ProtectType switch
{
ProtectTypeEnum.ReadOnly => (byte)(result | AccessLevels.CurrentRead),
ProtectTypeEnum.ReadWrite => (byte)(result | AccessLevels.CurrentReadOrWrite),
_ => (byte)(result | AccessLevels.CurrentRead),
};
//if (variableRuntime.HistoryEnable)
//{
// result = (byte)(result | AccessLevels.HistoryRead);
//}
return result;
}
}

View File

@@ -0,0 +1,328 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Server;
using System.Security.Cryptography.X509Certificates;
using ThingsGateway.Admin.Application;
using ThingsGateway.Gateway.Application;
using TouchSocket.Core;
namespace ThingsGateway.Plugin.OpcUa105;
/// <summary>
/// UAServer核心实现
/// </summary>
public partial class ThingsGatewayServer : StandardServer
{
/// <summary>
/// 自定义节点
/// </summary>
public ThingsGatewayNodeManager NodeManager;
private readonly BusinessBase _businessBase;
private ICertificateValidator m_userCertificateValidator;
/// <inheritdoc cref="ThingsGatewayServer"/>
public ThingsGatewayServer(BusinessBase businessBase)
{
_businessBase = businessBase;
}
/// <inheritdoc/>
public override UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description)
{
var policies = base.GetUserTokenPolicies(configuration, description);
// 样品如何修改默认用户令牌的政策
if (description.SecurityPolicyUri == SecurityPolicies.Aes256_Sha256_RsaPss &&
description.SecurityMode == MessageSecurityMode.SignAndEncrypt)
{
policies = new UserTokenPolicyCollection(policies.Where(u => u.TokenType != UserTokenType.Certificate));
}
else if (description.SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep &&
description.SecurityMode == MessageSecurityMode.Sign)
{
policies = new UserTokenPolicyCollection(policies.Where(u => u.TokenType != UserTokenType.Anonymous));
}
else if (description.SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep &&
description.SecurityMode == MessageSecurityMode.SignAndEncrypt)
{
policies = new UserTokenPolicyCollection(policies.Where(u => u.TokenType != UserTokenType.UserName));
}
return policies;
}
/// <inheritdoc/>
protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration)
{
List<INodeManager> nodeManagers = new List<INodeManager>();
// 创建自定义节点管理器.
NodeManager = new ThingsGatewayNodeManager(_businessBase, server, configuration);
nodeManagers.Add(NodeManager);
// 创建主节点管理器.
var masterNodeManager = new MasterNodeManager(server, configuration, null, nodeManagers.ToArray());
return masterNodeManager;
}
/// <inheritdoc/>
protected override ResourceManager CreateResourceManager(IServerInternal server, ApplicationConfiguration configuration)
{
ResourceManager resourceManager = new(server, configuration);
System.Reflection.FieldInfo[] fields = typeof(StatusCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
foreach (System.Reflection.FieldInfo field in fields)
{
uint? id = field.GetValue(typeof(StatusCodes)) as uint?;
if (id != null)
{
resourceManager.Add(id.Value, "en-US", field.Name);
}
}
resourceManager.Add("InvalidPassword", "zh-cn", "密码验证失败,'{0}'.");
resourceManager.Add("UnexpectedUserTokenError", "zh-cn", "错误的用户令牌。");
resourceManager.Add("BadUserAccessDenied", "zh-cn", "当前用户名不存在。");
return resourceManager;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
NodeManager?.SafeDispose();
base.Dispose(disposing);
}
/// <inheritdoc/>
protected override ServerProperties LoadServerProperties()
{
ServerProperties properties = new()
{
ManufacturerName = "Diego",
ProductName = "ThingsGateway OPCUAServer",
ProductUri = "https://gitee.com/diego2098/ThingsGateway",
SoftwareVersion = Utils.GetAssemblySoftwareVersion(),
BuildNumber = Utils.GetAssemblyBuildNumber(),
BuildDate = Utils.GetAssemblyTimestamp()
};
return properties;
}
/// <inheritdoc/>
protected override void OnServerStarted(IServerInternal server)
{
// 当用户身份改变时请求。
server.SessionManager.ImpersonateUser += SessionManager_ImpersonateUser;
base.OnServerStarted(server);
_businessBase.LogMessage.LogInformation("OPCUAServer Started");
}
/// <inheritdoc/>
protected override void OnServerStarting(ApplicationConfiguration configuration)
{
_businessBase.LogMessage.LogInformation("OPCUAServer Starting");
base.OnServerStarting(configuration);
// 由应用程序决定如何验证用户身份令牌。
// 此函数为 X509 身份令牌创建验证器。
CreateUserIdentityValidators(configuration);
}
/// <inheritdoc/>
protected override void OnServerStopping()
{
_businessBase.LogMessage.LogInformation("OPCUAServer Stoping");
base.OnServerStopping();
}
private void CreateUserIdentityValidators(ApplicationConfiguration configuration)
{
for (int ii = 0; ii < configuration.ServerConfiguration.UserTokenPolicies.Count; ii++)
{
UserTokenPolicy policy = configuration.ServerConfiguration.UserTokenPolicies[ii];
// 为证书令牌策略创建验证器。
if (policy.TokenType == UserTokenType.Certificate)
{
// check if user certificate trust lists are specified in configuration.
if (configuration.SecurityConfiguration.TrustedUserCertificates != null &&
configuration.SecurityConfiguration.UserIssuerCertificates != null)
{
CertificateValidator certificateValidator = new();
certificateValidator.Update(configuration).GetAwaiter().GetResult();
certificateValidator.Update(configuration.SecurityConfiguration.UserIssuerCertificates,
configuration.SecurityConfiguration.TrustedUserCertificates,
configuration.SecurityConfiguration.RejectedCertificateStore);
// set custom validator for user certificates.
m_userCertificateValidator = certificateValidator.GetChannelValidator();
}
}
}
}
private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArgs args)
{
// check for a user name cancellationToken.
if (args.NewIdentity is UserNameIdentityToken userNameToken)
{
args.Identity = VerifyPassword(userNameToken);
// set AuthenticatedUser role for accepted user/password authentication
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser);
if (args.Identity is SystemConfigurationIdentity)
{
// set ConfigureAdmin role for user with permission to configure server
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_ConfigureAdmin);
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_SecurityAdmin);
}
return;
}
// check for x509 user cancellationToken.
if (args.NewIdentity is X509IdentityToken x509Token)
{
VerifyUserTokenCertificate(x509Token.Certificate);
args.Identity = new UserIdentity(x509Token);
Utils.LogInfo(Utils.TraceMasks.Security, "X509 Token Accepted: {0}", args.Identity?.DisplayName);
// set AuthenticatedUser role for accepted certificate authentication
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser);
return;
}
// check for anonymous cancellationToken.
if (args.NewIdentity is AnonymousIdentityToken || args.NewIdentity == null)
{
// allow anonymous authentication and set Anonymous role for this authentication
args.Identity = new UserIdentity();
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_Anonymous);
return;
}
// unsuported identity cancellationToken type.
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid,
"Not supported user cancellationToken type: {0}.", args.NewIdentity);
}
/// <summary>
/// 从第三方用户中校验
/// </summary>
/// <param name="userNameToken"></param>
/// <returns></returns>
private IUserIdentity VerifyPassword(UserNameIdentityToken userNameToken)
{
var userName = userNameToken.UserName;
var password = userNameToken.DecryptedPassword;
if (string.IsNullOrEmpty(userName))
{
// an empty username is not accepted.
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid,
"Security cancellationToken is not a valid username cancellationToken. An empty username is not accepted.");
}
if (string.IsNullOrEmpty(password))
{
// an empty password is not accepted.
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenRejected,
"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();//获取用户信息
if (userInfo == null)
{
// construct translation object with default text.
TranslationInfo info = new(
"InvalidPassword",
"en-US",
"Invalid username or password.",
userName);
// create an exception with a vendor defined sub-code.
throw new ServiceResultException(new ServiceResult(
StatusCodes.BadUserAccessDenied,
"InvalidPassword",
LoadServerProperties().ProductUri,
new LocalizedText(info)));
}
// 有权配置服务器的用户
if (userName == userInfo.Account && password == userInfo.Password)
{
return new SystemConfigurationIdentity(new UserIdentity(userNameToken));
}
else
{
return new UserIdentity(userNameToken);
}
}
private void VerifyUserTokenCertificate(X509Certificate2 certificate)
{
try
{
if (m_userCertificateValidator != null)
{
m_userCertificateValidator.Validate(certificate);
}
else
{
CertificateValidator.Validate(certificate);
}
}
catch (Exception e)
{
TranslationInfo info;
StatusCode result = StatusCodes.BadIdentityTokenRejected;
if (e is ServiceResultException se && se.StatusCode == StatusCodes.BadCertificateUseNotAllowed)
{
info = new TranslationInfo(
"InvalidCertificate",
"en-US",
"'{0}' is an invalid user certificate.",
certificate.Subject);
result = StatusCodes.BadIdentityTokenInvalid;
}
else
{
// construct translation object with default text.
info = new TranslationInfo(
"UntrustedCertificate",
"en-US",
"'{0}' is not a trusted user certificate.",
certificate.Subject);
}
// create an exception with a vendor defined sub-code.
throw new ServiceResultException(new ServiceResult(
result,
info.Key,
LoadServerProperties().ProductUri,
new LocalizedText(info)));
}
}
}

View File

@@ -0,0 +1,391 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Mapster;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Configuration;
using System.Collections.Concurrent;
using ThingsGateway.Extension;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife.Threading;
using TouchSocket.Core;
namespace ThingsGateway.Plugin.OpcUa105;
/// <summary>
/// OPCUA服务端
/// </summary>
public partial class OpcUaServer : BusinessBase
{
private readonly OpcUaServerProperty _driverPropertys = new();
private readonly OpcUaServerVariableProperty _variablePropertys = new();
private ApplicationInstance m_application;
private ApplicationConfiguration m_configuration;
private ThingsGatewayServer m_server;
private volatile bool success = true;
/// <inheritdoc/>
public override VariablePropertyBase VariablePropertys => _variablePropertys;
/// <inheritdoc/>
protected override BusinessPropertyBase _businessPropertyBase => _driverPropertys;
protected IStringLocalizer Localizer { get; private set; }
private ConcurrentQueue<VariableData> CollectVariableRuntimes { get; set; } = new();
private static readonly string[] separator = new string[] { ";" };
public override void AfterVariablesChanged()
{
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
if (_driverPropertys.IsAllVariable)
{
VariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
{
base.AfterVariablesChanged();
}
VariableRuntimes.ForEach(a =>
{
VariableValueChange(a.Value, a.Value.Adapt<VariableData>());
});
}
protected override void InitChannel(IChannel? channel = null)
{
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(LogMessage);//默认返回true
//Utils.SetLogger(new OpcUaLogger(LogMessage)); //调试用途
m_application = new ApplicationInstance();
m_configuration = GetDefaultConfiguration();
m_configuration.Validate(ApplicationType.Server).GetAwaiter().GetResult();
m_application.ApplicationConfiguration = m_configuration;
if (m_configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates)
{
m_configuration.CertificateValidator.CertificateValidation += (s, e) =>
{
e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted);
};
}
m_server = new(this);
CollectVariableRuntimes.Clear();
GlobalData.VariableValueChangeEvent += VariableValueChange;
Localizer = App.CreateLocalizerByType(typeof(OpcUaServer))!;
base.InitChannel(channel);
}
/// <inheritdoc/>
public override bool IsConnected()
{
try
{
return m_server?.CurrentInstance.CurrentState == Opc.Ua.ServerState.Running;
}
catch (Exception)
{
return false;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
GlobalData.VariableValueChangeEvent -= VariableValueChange;
m_application?.Stop();
m_server?.SafeDispose();
CollectVariableRuntimes?.Clear();
base.Dispose(disposing);
}
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
{
// 启动服务器。
await m_application.CheckApplicationInstanceCertificates(false, 1200, cancellationToken).ConfigureAwait(false);
await m_application.Start(m_server).ConfigureAwait(false);
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
}
protected override async ValueTask ProtectedExecuteAsync(CancellationToken cancellationToken)
{
try
{
if (IsConnected())
{
//更新设备活动时间
CurrentDevice.SetDeviceStatus(TimerX.Now, false);
}
else
{
CurrentDevice.SetDeviceStatus(TimerX.Now, true);
try
{
await m_application.CheckApplicationInstanceCertificates(false, 1200, cancellationToken).ConfigureAwait(false);
await m_application.Start(m_server).ConfigureAwait(false);
success = true;
}
catch (Exception ex)
{
if (success)
LogMessage.LogWarning(ex, Localizer["CanStartService"]);
success = false;
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
}
}
var data = CollectVariableRuntimes.ToListWithDequeue();
data.Reverse();
////变化推送
var varList = data.DistinctBy(a => a.Name).ToList();
if (varList?.Count > 0)
{
foreach (var item in varList)
{
try
{
if (!cancellationToken.IsCancellationRequested)
{
m_server?.NodeManager?.UpVariable(item);
}
else
{
break;
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex);
}
}
}
success = true;
}
catch (Exception ex)
{
if (success)
LogMessage.LogWarning(ex);
success = false;
}
}
private ApplicationConfiguration GetDefaultConfiguration()
{
ApplicationConfiguration config = new();
var urls = _driverPropertys.OpcUaStringUrl.Split(separator, StringSplitOptions.RemoveEmptyEntries);
// 签名及加密验证
ServerSecurityPolicyCollection policies = new();
var userTokens = new UserTokenPolicyCollection();
if (_driverPropertys.SecurityPolicy)
{
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.Sign,
SecurityPolicyUri = SecurityPolicies.Basic128Rsa15
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.Sign,
SecurityPolicyUri = SecurityPolicies.Basic256
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.Sign,
SecurityPolicyUri = SecurityPolicies.Basic256Sha256
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.Sign,
SecurityPolicyUri = SecurityPolicies.Aes128_Sha256_RsaOaep
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.Sign,
SecurityPolicyUri = SecurityPolicies.Aes256_Sha256_RsaPss
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.SignAndEncrypt,
SecurityPolicyUri = SecurityPolicies.Basic128Rsa15
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.SignAndEncrypt,
SecurityPolicyUri = SecurityPolicies.Basic256
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.SignAndEncrypt,
SecurityPolicyUri = SecurityPolicies.Basic256Sha256
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.SignAndEncrypt,
SecurityPolicyUri = SecurityPolicies.Aes128_Sha256_RsaOaep
});
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.SignAndEncrypt,
SecurityPolicyUri = SecurityPolicies.Aes256_Sha256_RsaPss
});
userTokens.Add(new UserTokenPolicy(UserTokenType.UserName));
}
else
{
userTokens.Add(new UserTokenPolicy(UserTokenType.UserName));
policies.Add(new ServerSecurityPolicy()
{
SecurityMode = MessageSecurityMode.None,
SecurityPolicyUri = SecurityPolicies.None
});
userTokens.Add(new UserTokenPolicy(UserTokenType.Anonymous));
}
config.ApplicationName = "ThingsGateway OPCUAServer";
config.ApplicationType = ApplicationType.Server;
config.ApplicationUri = _driverPropertys.BigTextApplicationUri;
config.ServerConfiguration = new ServerConfiguration()
{
// 配置登录的地址
BaseAddresses = urls,
SecurityPolicies = policies,
UserTokenPolicies = userTokens,
ShutdownDelay = 1,
MinRequestThreadCount = 50,
MaxRequestThreadCount = 1000,
MaxQueuedRequestCount = 20000,
DiagnosticsEnabled = false, // 是否启用诊断
MaxSessionCount = 1000, // 最大打开会话数
MinSessionTimeout = 10000, // 允许该会话在与客户端断开时(单位毫秒)仍然保持连接的最小时间
MaxSessionTimeout = 60000, // 允许该会话在与客户端断开时(单位毫秒)仍然保持连接的最大时间
MaxBrowseContinuationPoints = 1000, // 用于Browse / BrowseNext操作的连续点的最大数量。
MaxQueryContinuationPoints = 1000, // 用于Query / QueryNext操作的连续点的最大数量
MaxHistoryContinuationPoints = 500, // 用于HistoryRead操作的最大连续点数。
MaxRequestAge = 1000000, // 传入请求的最大年龄(旧请求被拒绝)。
MinPublishingInterval = 50, // 服务器支持的最小发布间隔(以毫秒为单位)
MaxPublishingInterval = 3600000, // 服务器支持的最大发布间隔以毫秒为单位1小时
PublishingResolution = 50, // 支持的发布间隔(以毫秒为单位)的最小差异
MaxSubscriptionLifetime = 3600000, // 订阅将在没有客户端发布的情况下保持打开多长时间 1小时
MaxMessageQueueSize = 100, // 每个订阅队列中保存的最大消息数
MaxNotificationQueueSize = 100, // 为每个被监视项目保存在队列中的最大证书数
MaxNotificationsPerPublish = 1000, // 每次发布的最大通知数
MinMetadataSamplingInterval = 1000, // 元数据的最小采样间隔
MaxRegistrationInterval = -1, // 两次注册尝试之间的最大时间(以毫秒为单位)//不提供注册
MinSubscriptionLifetime = 1000,
MaxPublishRequestCount = 200,
MaxSubscriptionCount = 1000,
MaxEventQueueSize = 50000,
MaxTrustListSize = 0,
MultiCastDnsEnabled = false,
};
config.SecurityConfiguration = new SecurityConfiguration()
{
AddAppCertToTrustedStore = true,
SendCertificateChain = true,
AutoAcceptUntrustedCertificates = _driverPropertys.AutoAcceptUntrustedCertificates,
UseValidatedCertificates = true,
RejectSHA1SignedCertificates = false,
RejectUnknownRevocationStatus = true,
MinimumCertificateKeySize = 1024,
SuppressNonceValidationErrors = true,
ApplicationCertificate = new CertificateIdentifier()
{
StoreType = CertificateStoreType.X509Store,
StorePath = "CurrentUser\\UAServer_ThingsGateway",
SubjectName = _driverPropertys.BigTextSubjectName,
//ValidationOptions = CertificateValidationOptions.SuppressHostNameInvalid,
},
TrustedPeerCertificates = new CertificateTrustList()
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAServerCertificate\pki\trustedPeer",
},
TrustedIssuerCertificates = new CertificateTrustList()
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAServerCertificate\pki\trustedIssuer",
},
RejectedCertificateStore = new CertificateStoreIdentifier()
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAServerCertificate\pki\rejected",
},
UserIssuerCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAServerCertificate\pki\issuerUser",
},
TrustedUserCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAServerCertificate\pki\trustedUser",
}
};
config.TransportConfigurations = new TransportConfigurationCollection();
config.TransportQuotas = new TransportQuotas
{
OperationTimeout = 6000000,
MaxStringLength = int.MaxValue,
MaxByteStringLength = int.MaxValue,
MaxArrayLength = 65535,
MaxMessageSize = 419430400,
MaxBufferSize = 65535,
ChannelLifetime = 300000,
SecurityTokenLifetime = 3600000
};
config.ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 };
config.TraceConfiguration = new TraceConfiguration();
config.CertificateValidator = new CertificateValidator();
config.CertificateValidator.Update(config).GetAwaiter().GetResult();
config.Extensions = new XmlElementCollection();
return config;
}
private void VariableValueChange(VariableRuntime variableRuntime, VariableData variableData)
{
if (CurrentDevice.Pause)
return;
if (DisposedValue) return;
if (VariableRuntimes.ContainsKey(variableData.Name))
CollectVariableRuntimes.Enqueue(variableData);
}
}

View File

@@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Opc.Ua;
using ThingsGateway.Gateway.Application;
namespace ThingsGateway.Plugin.OpcUa105;
/// <inheritdoc/>
public class OpcUaServerProperty : BusinessPropertyBase
{
[DynamicProperty]
public bool IsAllVariable { get; set; } = false;
/// <summary>
/// 服务地址
/// </summary>
[DynamicProperty(Remark = "分号分割数组可设置多个url")]
public string OpcUaStringUrl { get; set; } = "opc.tcp://127.0.0.1:49321";
/// <summary>
/// SubjectName
/// </summary>
[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>
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 1)]
[DynamicProperty()]
public string BigTextApplicationUri { get; set; } = Utils.Format(@"urn:{0}:thingsgatewayopcuaserver", System.Net.Dns.GetHostName());
/// <summary>
/// 安全策略
/// </summary>
[DynamicProperty()]
public bool SecurityPolicy { get; set; }
/// <summary>
/// 接受不受信任的证书
/// </summary>
[DynamicProperty()]
public bool AutoAcceptUntrustedCertificates { get; set; } = true;
}

View File

@@ -0,0 +1,23 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Gateway.Application;
namespace ThingsGateway.Plugin.OpcUa105;
/// <inheritdoc/>
public class OpcUaServerVariableProperty : VariablePropertyBase
{
/// <summary>
/// 数据类型
/// </summary>
[DynamicProperty()]
public DataTypeEnum DataType { get; set; } = DataTypeEnum.Object;
}

View File

@@ -0,0 +1,59 @@
@namespace ThingsGateway.Debug
@using BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web;
@using System.Reflection;
@using Opc.Ua
@using ThingsGateway.Foundation.OpcUa105;
<div class="row ">
<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
IsAccordion ClickToggleNode ModelEqualityComparer="OpcUaImportVariable.ModelEqualityComparer" />
</div>
<div class="col-12 col-md-6 p-2" style="min-height:500px;max-height:80vh;overflow: auto;">
@if (ClickItem?.Value?.Tag?.NodeId != null && NodeAttributes != null)
{
<Display @bind-Value=@ClickItem.Value.Tag.NodeId DisplayText="NodeId" ShowLabel="true" />
foreach (var item in NodeAttributes)
{
<Display class=@($"{(StatusCode.IsBad(item.StatusCode)?"red--text":"green--text")}") @bind-Value=@item.Value DisplayText=@item.Name ShowLabel="true" />
}
}
</div>
</div>
@{
#if Plugin
}
<div class="form-footer">
<Button IsAsync Color="Color.Secondary" Icon="fa-solid fa-xmark" Text="@OpcUaPropertyLocalizer["Close"]" OnClickWithoutRender="OnClickClose" />
<Button IsAsync Color="Color.Primary" Icon="fa-solid fa-check" Text="@OpcUaPropertyLocalizer["Export"]" OnClickWithoutRender="OnClickExport" />
<Button IsAsync Color="Color.Primary" Icon="fa-solid fa-check" Text="@OpcUaPropertyLocalizer["Save"]" OnClickWithoutRender="OnClickSave" />
</div>
@{
#endif
}
@code {
RenderFragment<OpcUaTagModel> RenderTreeItem => item =>
@<span class="flex-fill">@item.Name</span>
;
private OPCNodeAttribute[] NodeAttributes = new OPCNodeAttribute[] { };
private TreeViewItem<OpcUaTagModel> ClickItem;
}

View File

@@ -0,0 +1,429 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Opc.Ua;
using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Extension;
using ThingsGateway.Foundation.OpcUa105;
#if Plugin
using ThingsGateway.Gateway.Application;
using ThingsGateway.Plugin.OpcUa105;
#endif
using ThingsGateway.Razor;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
/// <summary>
/// 导入变量
/// </summary>
public partial class OpcUaImportVariable
{
private List<TreeViewItem<OpcUaTagModel>> Items = new();
private IEnumerable<OpcUaTagModel> Nodes;
private bool ShowSkeleton = true;
/// <summary>
/// Opc对象
/// </summary>
[Parameter]
public ThingsGateway.Foundation.OpcUa105.OpcUaMaster Plc { get; set; }
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<OpcUaProperty>? OpcUaPropertyLocalizer { get; set; }
[Inject]
[NotNull]
private ToastService? ToastService { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Task.Run(async () =>
{
Items = BuildTreeItemList(await PopulateBranchAsync(ObjectIds.ObjectsFolder), RenderTreeItem).ToList();
ShowSkeleton = false;
await InvokeAsync(StateHasChanged);
});
}
await base.OnAfterRenderAsync(firstRender);
}
/// <summary>
/// 构建树节点,传入的列表已经是树结构
/// </summary>
private static IEnumerable<TreeViewItem<OpcUaTagModel>> BuildTreeItemList(IEnumerable<OpcUaTagModel> opcUaTagModels, Microsoft.AspNetCore.Components.RenderFragment<OpcUaTagModel> render = null, TreeViewItem<OpcUaTagModel>? parent = null)
{
if (opcUaTagModels == null) return Enumerable.Empty<TreeViewItem<OpcUaTagModel>>();
var trees = new List<TreeViewItem<OpcUaTagModel>>();
foreach (var node in opcUaTagModels)
{
if (node == null) continue;
var item = new TreeViewItem<OpcUaTagModel>(node)
{
Text = node.Name,
Parent = parent,
IsExpand = false,
Template = render,
HasChildren = node.Children != null,
};
item.Items = BuildTreeItemList(node.Children, render, item).ToList();
trees.Add(item);
}
return trees;
}
private static bool ModelEqualityComparer(OpcUaTagModel x, OpcUaTagModel y) => x.NodeId == y.NodeId;
private async Task<IEnumerable<TreeViewItem<OpcUaTagModel>>> OnExpandNodeAsync(TreeViewItem<OpcUaTagModel> treeViewItem)
{
var data = BuildTreeItemList(await PopulateBranchAsync(treeViewItem.Value.NodeId), RenderTreeItem);
return data;
}
private Task OnTreeItemChecked(List<TreeViewItem<OpcUaTagModel>> items)
{
Nodes = items.Select(a => a.Value);
return Task.CompletedTask;
}
private async Task PopulateBranch(OpcUaTagModel model)
{
if (model.Children != null)
{
if (model.Children.Count == 0)
{
model.Children = await PopulateBranchAsync((NodeId)model.Tag.NodeId);
}
foreach (var item in model.Children)
{
await PopulateBranch(item);
}
}
}
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 OpcUaUtils.BrowseAsync(Plc.Session, nodesToBrowse, false);
return references;
}
[Parameter]
public bool ShowSubvariable { get; set; }
private async Task<List<OpcUaTagModel>> PopulateBranchAsync(NodeId sourceId, bool isAll = false)
{
if (!Plc.Connected)
{
return new() { };
}
List<OpcUaTagModel> nodes = new()
{
};
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 (ShowSubvariable || target.NodeClass != NodeClass.Variable)
{
var data = await GetReferenceDescriptionCollectionAsync((NodeId)target.NodeId);
if (data != null && data.Count > 0)
{
if (isAll)
child.Children = await PopulateBranchAsync((NodeId)target.NodeId);
else
child.Children = new();
}
}
list.Add(child);
}
}
nodes.Clear();
nodes.AddRange(list.ToArray());
return nodes;
}
#if Plugin
private async Task<List<OpcUaTagModel>> GetAllTag(IEnumerable<OpcUaTagModel> opcUaTagModels)
{
List<OpcUaTagModel> result = new();
foreach (var item in opcUaTagModels)
{
await PopulateBranch(item);
result.AddRange(item.GetAllTags().Where(a => a.Children == null).ToList());
}
return result;
}
private async Task OnClickClose()
{
if (OnCloseAsync != null)
await OnCloseAsync();
}
private async Task OnClickExport()
{
try
{
if (Nodes == null) return;
var data = await GetImportVariableList((await GetAllTag(Nodes)).DistinctBy(a => a.NodeId));
if (data.Item3 == null || data.Item3?.Count == 0)
{
await ToastService.Warning(OpcUaPropertyLocalizer["NoVariablesAvailable"], OpcUaPropertyLocalizer["NoVariablesAvailable"]);
return;
}
await DownChannelExportAsync(data.Item1);
await DownDeviceExportAsync(data.Item2, data.Item1.Name);
await DownDeviceVariableExportAsync(data.Item3.ToList(), data.Item2.Name);
await ToastService.Default();
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
private async Task OnClickSave()
{
try
{
if (Nodes == null) return;
var data = await GetImportVariableList((await GetAllTag(Nodes)).DistinctBy(a => a.NodeId));
if (data.Item3 == null || data.Item3?.Count == 0)
{
await ToastService.Warning(OpcUaPropertyLocalizer["NoVariablesAvailable"], OpcUaPropertyLocalizer["NoVariablesAvailable"]);
return;
}
await App.RootServices.GetRequiredService<IChannelRuntimeService>().SaveChannelAsync(data.Item1, ItemChangedType.Add);
await App.RootServices.GetRequiredService<IDeviceRuntimeService>().SaveDeviceAsync(data.Item2, ItemChangedType.Add);
await App.RootServices.GetRequiredService<IVariableRuntimeService>().AddBatchAsync(data.Item3.ToList());
await ToastService.Default();
}
catch (Exception ex)
{
await ToastService.Warning(ex.Message);
}
}
/// <summary>
/// 获取设备与变量列表
/// </summary>
/// <returns></returns>
private async Task<(Channel, Device, IList<Variable>)> GetImportVariableList(IEnumerable<OpcUaTagModel> opcUaTagModels)
{
var channel = GetImportChannel();
var device = GetImportDevice(channel.Id);
var variables = new ConcurrentList<Variable>();
await opcUaTagModels.ParallelForEachAsync(async (b, token) =>
{
var a = b.Tag;
var nodeClass = (await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.NodeClass, token)).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, token)).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, token)).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 = Admin.Application.CommonUtils.GetSingleId();
variables.Add(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,
});
}
}, Environment.ProcessorCount / 2);
return (channel, device, variables);
}
private Device GetImportDevice(long channelId)
{
var id = Admin.Application.CommonUtils.GetSingleId();
var data = new Device()
{
Name = Plc.OpcUaProperty.OpcUrl + "-" + id,
Id = id,
ChannelId = channelId,
Enable = true,
DevicePropertys = new(),
};
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.OpcUrl), Plc.OpcUaProperty.OpcUrl);
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UserName), Plc.OpcUaProperty.UserName);
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.Password), Plc.OpcUaProperty.Password);
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.CheckDomain), Plc.OpcUaProperty.CheckDomain.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.LoadType), Plc.OpcUaProperty.LoadType.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UseSecurity), Plc.OpcUaProperty.UseSecurity.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.ActiveSubscribe), true.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.DeadBand), Plc.OpcUaProperty.DeadBand.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.GroupSize), Plc.OpcUaProperty.GroupSize.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UpdateRate), Plc.OpcUaProperty.UpdateRate.ToString());
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.KeepAliveInterval), Plc.OpcUaProperty.KeepAliveInterval.ToString());
return data;
}
private Channel GetImportChannel()
{
var id = Admin.Application.CommonUtils.GetSingleId();
var data = new Channel()
{
Name = Plc.OpcUaProperty.OpcUrl + "-" + id,
Id = id,
Enable = true,
ChannelType = ChannelTypeEnum.Other,
PluginName = "ThingsGateway.Plugin.OpcUa105.OpcUaMaster",
};
return data;
}
[Inject]
private DownloadService DownloadService { get; set; }
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownChannelExportAsync(Channel data)
{
using var memoryStream = await App.RootServices.GetRequiredService<IChannelRuntimeService>().ExportMemoryStream(new List<Channel>() { data });
await DownloadService.DownloadFromStreamAsync($"channel{DateTime.Now.ToFileDateTimeFormat()}.xlsx", memoryStream);
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownDeviceExportAsync(Device data, string channelName)
{
using var memoryStream = await App.RootServices.GetRequiredService<IDeviceRuntimeService>().ExportMemoryStream(new List<Device>() { data }, channelName);
await DownloadService.DownloadFromStreamAsync($"device{DateTime.Now.ToFileDateTimeFormat()}.xlsx", memoryStream);
}
/// <summary>
/// 导出到excel
/// </summary>
/// <returns></returns>
public async Task DownDeviceVariableExportAsync(List<Variable> data, string devName)
{
using var memoryStream = await App.RootServices.GetRequiredService<IVariableRuntimeService>().ExportMemoryStream(data, devName);
await DownloadService.DownloadFromStreamAsync($"variable{DateTime.Now.ToFileDateTimeFormat()}.xlsx", memoryStream);
}
#endif
internal sealed class OpcUaTagModel
{
internal List<OpcUaTagModel> Children { get; set; }
internal string Name { get; set; }
internal string NodeId => (Tag?.NodeId)?.ToString();
internal ReferenceDescription Tag { get; set; }
public List<OpcUaTagModel> GetAllTags()
{
List<OpcUaTagModel> allTags = new();
OpcUaTagModel.GetAllTagsRecursive(this, allTags);
return allTags;
}
private static void GetAllTagsRecursive(OpcUaTagModel parentTag, List<OpcUaTagModel> allTags)
{
allTags.Add(parentTag);
if (parentTag.Children != null)
foreach (OpcUaTagModel childTag in parentTag.Children)
{
OpcUaTagModel.GetAllTagsRecursive(childTag, allTags);
}
}
}
}

View File

@@ -0,0 +1,111 @@
@page "/OpcUaMaster"
@using BootstrapBlazor.Components
@using ThingsGateway.Extension
@using ThingsGateway.Foundation
@namespace ThingsGateway.Debug
@using ThingsGateway.Foundation.OpcUa105
@using TouchSocket.Core
<div class="w-100 h-100">
@if (_plc?.OpcUaProperty != null)
{
<Card>
<BodyTemplate>
<EditorForm Model="_plc.OpcUaProperty" AutoGenerateAllItem=false RowType="RowType.Inline" ItemsPerRow="4" ShowLabelTooltip="true" ShowLabel="true">
<FieldItems>
<EditorItem @bind-Field=context.OpcUrl />
<EditorItem @bind-Field=context.UserName />
<EditorItem @bind-Field=context.Password />
<EditorItem @bind-Field=context.KeepAliveInterval />
<EditorItem @bind-Field=context.GroupSize />
<EditorItem @bind-Field=context.UpdateRate />
<EditorItem @bind-Field=context.DeadBand />
<EditorItem @bind-Field=context.UseSecurity />
<EditorItem @bind-Field=context.ActiveSubscribe />
<EditorItem @bind-Field=context.CheckDomain />
<EditorItem @bind-Field=context.LoadType />
<EditorItem @bind-Field=context.AutoAcceptUntrustedCertificates />
</FieldItems>
<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.Primary" OnClick="Export">@OpcUaPropertyLocalizer["ExportC"]</Button>
</Buttons>
</EditorForm>
</BodyTemplate>
</Card>
}
<DeviceComponent DefaultAddress="" LogPath=@LogPath Logger="LogMessage" @ref=DeviceComponent ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
<ReadWriteContent>
<BootstrapInput @bind-Value=@RegisterAddress DisplayText=@OpcUaPropertyLocalizer["RegisterAddress"]
ShowLabel="true" class="w-100" />
<div class="row mx-1 form-inline mt-2">
<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" DisplayText=@OpcUaPropertyLocalizer["WriteValue"]
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">
<Checkbox @bind-Value=ShowSubvariable DisplayText=@OpcUaPropertyLocalizer["ShowSubvariable"] ShowLabel="true" />
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="ShowImport">@OpcUaPropertyLocalizer["ShowImport"]</Button>
</div>
</div>
</ReadWriteContent>
</DeviceComponent>
</div>

View File

@@ -0,0 +1,213 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Foundation.OpcUa105;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Razor;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
public partial class OpcUaMaster : IDisposable
{
public LoggerGroup? LogMessage;
private readonly OpcUaProperty OpcUaProperty = new();
private ThingsGateway.Foundation.OpcUa105.OpcUaMaster _plc;
private string LogPath;
private string RegisterAddress;
private bool ShowSubvariable;
private string WriteValue;
/// <inheritdoc/>
~OpcUaMaster()
{
this.SafeDispose();
}
private DeviceComponent DeviceComponent { get; set; }
[Inject]
private DialogService DialogService { get; set; }
[Inject]
private IStringLocalizer<OpcUaProperty> OpcUaPropertyLocalizer { get; set; }
/// <inheritdoc/>
public void Dispose()
{
_plc?.SafeDispose();
GC.SuppressFinalize(this);
}
[Inject]
private DownloadService DownloadService { get; set; }
[Inject]
private ToastService ToastService { get; set; }
private async Task Export()
{
try
{
await _plc.CheckApplicationInstanceCertificate();
string path = $"{AppContext.BaseDirectory}OPCUAClientCertificate/pki/trustedPeer/certs";
Directory.CreateDirectory(path);
var files = Directory.GetFiles(path);
if (files.Length == 0)
{
return;
}
foreach (var item in files)
{
using var fileStream = new FileStream(item, FileMode.Open, FileAccess.Read);
var extension = Path.GetExtension(item);
extension ??= ".der";
await DownloadService.DownloadFromStreamAsync($"ThingsGateway{extension}", fileStream);
}
await ToastService.Default();
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
protected override void OnInitialized()
{
_plc = new ThingsGateway.Foundation.OpcUa105.OpcUaMaster();
_plc.OpcUaProperty = OpcUaProperty;
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
var logger = TextFileLogger.GetMultipleFileLogger(_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.ToJsonNetString());
base.OnInitialized();
}
private async Task Add()
{
try
{
if (_plc.Connected)
await _plc.AddSubscriptionAsync(Guid.NewGuid().ToString(), [RegisterAddress]);
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex);
}
}
private async Task Connect()
{
try
{
_plc.Disconnect();
LogPath = _plc?.GetHashCode().ToLong().GetDebugLogPath();
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.OpcUa105.OpcUaMaster GetOpc()
{
//载入配置
_plc.OpcUaProperty = OpcUaProperty;
return _plc;
}
private async Task ReadAsync()
{
if (_plc.Connected)
{
try
{
var data = await _plc.ReadJTokenValueAsync([RegisterAddress]);
LogMessage?.LogInformation($" {data[0].Item1}{data[0].Item3}");
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex);
}
}
}
private void Remove()
{
if (_plc.Connected)
_plc.RemoveSubscription("");
}
private async Task ShowImport()
{
var op = new DialogOption()
{
IsScrolling = false,
Title = OpcUaPropertyLocalizer["ShowImport"],
ShowFooter = false,
ShowCloseButton = false,
Size = Size.ExtraLarge
};
op.Component = BootstrapDynamicComponent.CreateComponent<OpcUaImportVariable>(new Dictionary<string, object?>
{
[nameof(OpcUaImportVariable.Plc)] = _plc,
[nameof(OpcUaImportVariable.ShowSubvariable)] = ShowSubvariable,
});
await DialogService.Show(op);
}
private async Task WriteAsync()
{
if (_plc.Connected)
{
var data = await _plc.WriteNodeAsync(
new()
{
{RegisterAddress, WriteValue.GetJTokenFromString()}
}
).ConfigureAwait(false);
foreach (var item in data)
{
if (item.Value.Item1)
LogMessage?.LogInformation(item.ToJsonNetString());
else
LogMessage?.LogWarning(item.ToJsonNetString());
}
}
}
}

View File

@@ -0,0 +1,25 @@
@using BootstrapBlazor.Components
@using Microsoft.Extensions.Localization
@using ThingsGateway.Extension
@using ThingsGateway.Foundation
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application
@using ThingsGateway.Plugin.OpcUa105
@namespace ThingsGateway.Plugin.OpcUa105
<ValidateForm Model="Model.Value"
@key=@($"DeviceEditValidateForm{Id}{Model.Value.GetType().TypeHandle.Value}")
@ref=Model.ValidateForm
Id=@($"DeviceEditValidateForm{Id}{Model.Value.GetType().TypeHandle.Value}")>
<EditorFormObject class="p-2" Items=PluginPropertyEditorItems IsDisplay="!CanWrite" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=@(CanWrite?2:3) ShowLabelTooltip=true LabelWidth=@(CanWrite?240:120) Model="Model.Value" ShowLabel="true" @key=@($"DeviceEditEditorFormObject{Id}{Model.Value.GetType().TypeHandle.Value}")>
<Buttons>
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="Export">@Localizer["ExportC"]</Button>
</Buttons>
</EditorFormObject>
</ValidateForm>

View File

@@ -0,0 +1,79 @@
// ------------------------------------------------------------------------------
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
// Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4><EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
// GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
// GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
// ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://thingsgateway.cn/
// QQȺ<51><C8BA>605534569
// ------------------------------------------------------------------------------
#pragma warning disable CA2007 // <20><><EFBFBD>ǶԵȴ<D4B5><C8B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ConfigureAwait
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Razor;
namespace ThingsGateway.Plugin.OpcUa105
{
public partial class OpcUaMasterPropertyRazor : IPropertyUIBase
{
[Parameter, EditorRequired]
public string Id { get; set; }
[Parameter, EditorRequired]
public bool CanWrite { get; set; }
[Parameter, EditorRequired]
public ModelValueValidateForm Model { get; set; }
[Parameter, EditorRequired]
public IEnumerable<IEditorItem> PluginPropertyEditorItems { get; set; }
IStringLocalizer Localizer { get; set; }
protected override Task OnParametersSetAsync()
{
Localizer = App.CreateLocalizerByType(Model.Value.GetType());
return base.OnParametersSetAsync();
}
[Inject]
private DownloadService DownloadService { get; set; }
[Inject]
private ToastService ToastService { get; set; }
private async Task Export()
{
try
{
var plc = new ThingsGateway.Foundation.OpcUa105.OpcUaMaster();
await plc.CheckApplicationInstanceCertificate().ConfigureAwait(false);
string path = $"{AppContext.BaseDirectory}OPCUAClientCertificate/pki/trustedPeer/certs";
Directory.CreateDirectory(path);
var files = Directory.GetFiles(path);
if (files.Length == 0)
{
return;
}
foreach (var item in files)
{
using var fileStream = new FileStream(item, FileMode.Open, FileAccess.Read);
var extension = Path.GetExtension(item);
extension ??= ".der";
await DownloadService.DownloadFromStreamAsync($"ThingsGateway{extension}", fileStream).ConfigureAwait(false);
}
await ToastService.Default();
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
}
}

View File

@@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="$(SolutionDir)Version.props" />
<Import Project="$(SolutionDir)PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>Plugin</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj">
</ProjectReference>
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
</ProjectReference>
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa105\ThingsGateway.Foundation.OpcUa105.csproj" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.375.443" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.375.443" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.375.443" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.375.443" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.375.443" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.375.443" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Formats.Asn1" Version="8.*" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@

@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using ThingsGateway.Razor;
@using ThingsGateway;
@using System.Net.Http.Json
@using System.IO;
@using System.Text.Json;

View File

@@ -5,8 +5,8 @@
<ItemGroup>
<!-- setting up the variable for convenience -->
<ApplicationPackageFiles Include="$(PkgRabbitMQ_Client)\lib\net8.0\*.*" />
<ApplicationPackageFiles1 Include="$(PkgSystem_IO_Pipelines)\lib\net8.0\*.*" />
<ApplicationPackageFiles2 Include="$(PkgSystem_Threading_RateLimiting)\lib\net8.0\*.*" />
<!--<ApplicationPackageFiles1 Include="$(PkgSystem_IO_Pipelines)\lib\net8.0\*.*" />-->
<!--<ApplicationPackageFiles2 Include="$(PkgSystem_Threading_RateLimiting)\lib\net8.0\*.*" />-->
</ItemGroup>
<PropertyGroup>
<ApplicationFolder>$(TargetDir)</ApplicationFolder>

View File

@@ -16,15 +16,9 @@
<PackageReference Include="RabbitMQ.Client" Version="7.0.0" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="System.IO.Pipelines" Version="9.*" GeneratePathProperty="true">
<PrivateAssets>contentFiles;runtime;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Threading.RateLimiting" Version="9.*" GeneratePathProperty="true">
<PrivateAssets>contentFiles;runtime;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />

View File

@@ -59,9 +59,11 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.*" />
</ItemGroup>
<ItemGroup>
<Content Include="..\ThingsGateway.Server\Index\GatewayIndex.razor" Link="Index\GatewayIndex.razor" />

View File

@@ -68,10 +68,16 @@
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<!--安装服务守护-->
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.*" />
</ItemGroup>
<!--安装服务守护-->
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.*" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.*" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.*" />
</ItemGroup>

View File

@@ -9,6 +9,7 @@
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.Mqtt\ThingsGateway.Plugin.Mqtt.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.OpcDa\ThingsGateway.Plugin.OpcDa.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.OpcUa\ThingsGateway.Plugin.OpcUa.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.OpcUa105\ThingsGateway.Plugin.OpcUa105.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.RabbitMQ\ThingsGateway.Plugin.RabbitMQ.csproj" />
</ItemGroup>

View File

@@ -10,8 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "other", "other", "{0B748352
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
..\git_pull.bat = ..\git_pull.bat
PluginVersion.props = PluginVersion.props
Version.props = Version.props
EndProjectSection
EndProject
@@ -34,8 +32,8 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
RESX_Rules = {"EnabledRules":[]}
RESX_NeutralResourcesLanguage = zh-Hans
RESX_Rules = {"EnabledRules":[]}
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
EndGlobalSection
EndGlobal

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.0.0.19</Version>
<Version>10.0.0.20</Version>
</PropertyGroup>
<ItemGroup>