mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 10:50:48 +08:00
更新插件
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
| Modbus | Rtu/Tcp报文格式,支持串口/Tcp/Udp链路 |
|
||||
| SiemensS7 | 西门子PLC S7系列 |
|
||||
| Dlt6452007 | 支持串口/Tcp/Udp链路 |
|
||||
| OpcDaClient | 64位编译 |
|
||||
| OpcUaClient | 64位编译 |
|
||||
| OpcUaClient | 支持证书登录,扩展对象,Json读写 |
|
||||
|
||||
#### 业务插件
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;</NoWarn>
|
||||
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;</NoWarn>
|
||||
<TargetFrameworks>net8.0;</TargetFrameworks>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -8,15 +8,15 @@
|
||||
@namespace ThingsGateway.Debug
|
||||
@inherits AdapterDebugBase
|
||||
|
||||
<div class=@($"{ClassString} row mt-2 px-2 mx-1") style="min-height:700px;height: 50%;">
|
||||
<div class=@($"{ClassString} row mt-2 px-2 mx-1") style="min-height:700px;height: 50%;">
|
||||
|
||||
<div class="col-12 col-md-5 h-100">
|
||||
<Tab class="h-100">
|
||||
<TabItem Text=@Localizer["CommonFunctions"]>
|
||||
@if (ShowDefaultReadWriteContent)
|
||||
{
|
||||
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
|
||||
ShowLabel="true" style="width:100%" />
|
||||
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
@@ -70,42 +70,47 @@
|
||||
@ReadWriteContent
|
||||
}
|
||||
</TabItem>
|
||||
<TabItem Text=@Localizer["SpecialFunctions"]>
|
||||
|
||||
@foreach (var item in VariableRunTimes)
|
||||
|
||||
<TabItem Text=@Localizer["SpecialFunctions"]>
|
||||
@if (ShowDefaultOtherContent)
|
||||
{
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
@foreach (var item in VariableRunTimes)
|
||||
{
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="p-1">
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
|
||||
<div class="p-1">
|
||||
|
||||
<BootstrapInput @bind-Value=@item.RegisterAddress title="@Plc?.GetAddressDescription()"
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
<Select @bind-Value="@DataType" ShowLabel="true" />
|
||||
<Select @bind-Value="@DataType" ShowLabel="true" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
|
||||
|
||||
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tab>
|
||||
|
@@ -254,7 +254,7 @@ internal class ComInterop
|
||||
|
||||
if (error != 0)
|
||||
{
|
||||
throw new ExternalException("COM初始化安全: " + GetSystemMessage(error), error);
|
||||
throw new ExternalException("InitializeSecurity fail: " + GetSystemMessage(error), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -307,11 +307,11 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count);
|
||||
if (PErrors.Any(a => a > 0))
|
||||
{
|
||||
throw new("读取错误,错误代码:" + pErrors);
|
||||
throw new("Read fail,Code:" + pErrors);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new("连接无效");
|
||||
throw new ArgumentNullException(nameof(m_Async2IO));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -387,7 +387,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new("连接无效");
|
||||
throw new ArgumentNullException(nameof(m_Async2IO));
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
@@ -55,7 +55,7 @@ internal class OpcServer : IDisposable
|
||||
internal OpcGroup AddGroup(string groupName, bool active, int reqUpdateRate, float deadBand)
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
throw new("未初始化连接!");
|
||||
throw new("Uninitialized connection");
|
||||
OpcGroup group = new(groupName, active, reqUpdateRate, deadBand);
|
||||
Guid riid = typeof(IOPCItemMgt).GUID;
|
||||
m_OpcServer?.AddGroup(group.Name,
|
||||
@@ -76,7 +76,7 @@ internal class OpcServer : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new("添加OPC组错误,OPC服务器返回null");
|
||||
throw new("Error adding OPC group, OPC server returns null");
|
||||
}
|
||||
return group;
|
||||
}
|
||||
@@ -89,7 +89,7 @@ internal class OpcServer : IDisposable
|
||||
lock (this)
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
throw new("未初始化连接!");
|
||||
throw new("Uninitialized connection");
|
||||
|
||||
var count = 0;
|
||||
var moreElements = 0;
|
||||
@@ -137,13 +137,13 @@ internal class OpcServer : IDisposable
|
||||
object o = Comn.ComInterop.CreateInstance(info.CLSID, Host);
|
||||
if (o == null)
|
||||
{
|
||||
throw new(string.Format("{0}{1}无法创建com对象", info.CLSID, Host));
|
||||
throw new(string.Format("{0} {1} Unable to create com object", info.CLSID, Host));
|
||||
}
|
||||
m_OpcServer = (IOPCServer)o;
|
||||
IsConnected = true;
|
||||
}
|
||||
else
|
||||
throw new("应初始化Host与Name");
|
||||
throw new("Host and Name should be initialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +156,7 @@ internal class OpcServer : IDisposable
|
||||
try
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
throw new("未初始化连接!");
|
||||
throw new("Uninitialized connection");
|
||||
IntPtr statusPtr = IntPtr.Zero;
|
||||
m_OpcServer?.GetStatus(out statusPtr);
|
||||
OPCSERVERSTATUS status;
|
||||
@@ -183,13 +183,13 @@ internal class OpcServer : IDisposable
|
||||
else
|
||||
{
|
||||
IsConnected = false;
|
||||
throw new("未知错误");
|
||||
throw new("GetServerStatus error");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsConnected = false;
|
||||
throw new("未知错误");
|
||||
throw new("GetServerStatus error");
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@@ -38,13 +38,13 @@ internal class OpcDiscovery
|
||||
{
|
||||
if (string.IsNullOrEmpty(serverName))
|
||||
{
|
||||
throw new("检索失败,需提供OPCName");
|
||||
throw new ArgumentNullException(nameof(serverName));
|
||||
}
|
||||
ServerInfo result = null;
|
||||
ServerInfo[] serverInfos = null;
|
||||
object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host);
|
||||
if (o_Server == null)
|
||||
throw new("检索失败,请检查是否安装OPC Runtime");
|
||||
throw new("GetOpcServer failed, please check if OPC runtime is installed");
|
||||
try
|
||||
{
|
||||
Guid catid = CATID_OPC_DA20;
|
||||
@@ -72,10 +72,7 @@ internal class OpcDiscovery
|
||||
{
|
||||
sb.AppendLine(item.ToString());
|
||||
}
|
||||
throw new($"无法创建OPCServer连接,请检查OPC名称是否一致,以下为{host}中的OPC列表:"
|
||||
+ Environment.NewLine +
|
||||
sb.ToString()
|
||||
);
|
||||
throw new($"Unable to create OPCServer connection. Please check if the OPC name is consistent. The following is a list of OPCServers in {host}:{Environment.NewLine}{sb}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@@ -1,9 +1,22 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.OpcDa.OpcDaMaster": {
|
||||
"ThingsGateway.Foundation.OpcDa.OpcDaProperty": {
|
||||
"ActiveSubscribe": "ActiveSubscribe",
|
||||
"CheckRate": "CheckRate(min)",
|
||||
"DeadBand": "DeadBand",
|
||||
"GroupSize": "GroupSize",
|
||||
"OpcIP": "OpcIP",
|
||||
"OpcName": "OpcName",
|
||||
"UpdateRate": "UpdateRate",
|
||||
|
||||
"Connect": "Connect",
|
||||
"Disconnect": "Disconnect",
|
||||
"Add": "Add",
|
||||
"Remove": "Remove",
|
||||
"Read": "Read",
|
||||
"Write": "Write",
|
||||
"ShowImport": "View OPC space"
|
||||
},
|
||||
|
||||
"ThingsGateway.Foundation.OpcDa.OpcDaResource": {
|
||||
|
||||
}
|
||||
}
|
@@ -1,9 +1,23 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.OpcDa.OpcDaMaster": {
|
||||
"ThingsGateway.Foundation.OpcDa.OpcDaProperty": {
|
||||
"ActiveSubscribe": "订阅",
|
||||
"CheckRate": "心跳间隔(min)",
|
||||
"DeadBand": "死区",
|
||||
"GroupSize": "最大组大小",
|
||||
"OpcIP": "OpcIP",
|
||||
"OpcName": "OpcName",
|
||||
"UpdateRate": "订阅推送间隔",
|
||||
|
||||
|
||||
"Connect": "连接",
|
||||
"Disconnect": "断开",
|
||||
"Add": "添加",
|
||||
"Remove": "移除",
|
||||
"Read": "读取",
|
||||
"Write": "写入",
|
||||
"ShowImport": "查看OPC空间"
|
||||
},
|
||||
|
||||
"ThingsGateway.Foundation.OpcDa.OpcDaResource": {
|
||||
|
||||
}
|
||||
}
|
@@ -50,7 +50,7 @@ public class OpcDaMaster : IDisposable
|
||||
/// <summary>
|
||||
/// 当前配置
|
||||
/// </summary>
|
||||
public OpcDaConfig OpcDaConfig { get; private set; }
|
||||
public OpcDaProperty OpcDaProperty { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据变化事件
|
||||
@@ -86,7 +86,7 @@ public class OpcDaMaster : IDisposable
|
||||
#if (NET6_0_OR_GREATER)
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
throw new NotSupportedException("不支持非windows系统");
|
||||
throw new NotSupportedException("Non Windows systems are not supported");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -113,14 +113,14 @@ public class OpcDaMaster : IDisposable
|
||||
/// <param name="items">组名称/变量节点,注意每次添加的组名称不能相同</param>
|
||||
public void AddItems(Dictionary<string, List<OpcItem>> items)
|
||||
{
|
||||
if (IsExit == 1) throw new ObjectDisposedException("对象已释放");
|
||||
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (IsExit == 1) throw new("对象已释放");
|
||||
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
|
||||
try
|
||||
{
|
||||
var subscription = m_server.AddGroup(item.Key, true, OpcDaConfig.UpdateRate, OpcDaConfig.DeadBand);
|
||||
subscription.ActiveSubscribe = OpcDaConfig.ActiveSubscribe;
|
||||
var subscription = m_server.AddGroup(item.Key, true, OpcDaProperty.UpdateRate, OpcDaProperty.DeadBand);
|
||||
subscription.ActiveSubscribe = OpcDaProperty.ActiveSubscribe;
|
||||
subscription.OnDataChanged += Subscription_OnDataChanged;
|
||||
subscription.OnReadCompleted += Subscription_OnDataChanged;
|
||||
|
||||
@@ -132,7 +132,7 @@ public class OpcDaMaster : IDisposable
|
||||
{
|
||||
stringBuilder.Append($"{item1.Item1.ItemID}:{item1.Item2}");
|
||||
}
|
||||
LogEvent?.Invoke(3, this, $"添加变量失败:{stringBuilder}", null);
|
||||
LogEvent?.Invoke(3, this, $"Failed to add variable:{stringBuilder}", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -141,7 +141,7 @@ public class OpcDaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEvent?.Invoke(3, this, $"添加组失败:{ex.Message}", ex);
|
||||
LogEvent?.Invoke(3, this, $"Failed to add group:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < Groups?.Count; i++)
|
||||
@@ -166,7 +166,7 @@ public class OpcDaMaster : IDisposable
|
||||
public Dictionary<string, List<OpcItem>> AddItemsWithSave(List<string> items)
|
||||
{
|
||||
int i = 0;
|
||||
ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OpcDaConfig.GroupSize).ToDictionary(a => "default" + (i++));
|
||||
ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OpcDaProperty.GroupSize).ToDictionary(a => "default" + (i++));
|
||||
return ItemDicts;
|
||||
}
|
||||
|
||||
@@ -198,8 +198,9 @@ public class OpcDaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEvent?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
|
||||
LogEvent?.Invoke(3, this, $"Disconnect warn:{ex.Message}", ex);
|
||||
}
|
||||
checkTimer?.Dispose();
|
||||
Interlocked.CompareExchange(ref IsExit, 1, 0);
|
||||
}
|
||||
|
||||
@@ -226,13 +227,13 @@ public class OpcDaMaster : IDisposable
|
||||
/// 初始化设置
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public void Init(OpcDaConfig config)
|
||||
public void Init(OpcDaProperty config)
|
||||
{
|
||||
if (config != null)
|
||||
OpcDaConfig = config;
|
||||
OpcDaProperty = config;
|
||||
checkTimer?.Stop();
|
||||
checkTimer?.Dispose();
|
||||
checkTimer = new Timer(Math.Min(OpcDaConfig.CheckRate, 1) * 60 * 1000);
|
||||
checkTimer = new Timer(Math.Min(OpcDaProperty.CheckRate, 1) * 60 * 1000);
|
||||
checkTimer.Elapsed += CheckTimer_Elapsed;
|
||||
checkTimer.Start();
|
||||
try
|
||||
@@ -241,9 +242,9 @@ public class OpcDaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEvent?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
|
||||
LogEvent?.Invoke(3, this, $"Disconnect warn:{ex.Message}", ex);
|
||||
}
|
||||
m_server = new OpcServer(OpcDaConfig.OpcName, OpcDaConfig.OpcIP);
|
||||
m_server = new OpcServer(OpcDaProperty.OpcName, OpcDaProperty.OpcIP);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -253,7 +254,7 @@ public class OpcDaMaster : IDisposable
|
||||
/// <returns></returns>
|
||||
public void ReadItemsWithGroup(string groupName = null)
|
||||
{
|
||||
if (IsExit == 1) throw new ObjectDisposedException("对象已释放");
|
||||
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
|
||||
{
|
||||
var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups;
|
||||
foreach (var group in groups)
|
||||
@@ -298,7 +299,7 @@ public class OpcDaMaster : IDisposable
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return OpcDaConfig?.ToString();
|
||||
return OpcDaProperty?.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -307,7 +308,7 @@ public class OpcDaMaster : IDisposable
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, Tuple<bool, string>> WriteItem(Dictionary<string, object> writeInfos)
|
||||
{
|
||||
if (IsExit == 1) throw new ObjectDisposedException("对象已释放");
|
||||
if (IsExit == 1) throw new ObjectDisposedException(nameof(OpcDaMaster));
|
||||
Dictionary<string, Tuple<bool, string>> results = new();
|
||||
|
||||
var valueGroup = writeInfos.GroupBy(itemId =>
|
||||
@@ -324,7 +325,7 @@ public class OpcDaMaster : IDisposable
|
||||
{
|
||||
foreach (var item2 in item1)
|
||||
{
|
||||
results.AddOrUpdate(item2.Key, Tuple.Create(false, $"不存在该变量{item2.Key}"));
|
||||
results.AddOrUpdate(item2.Key, Tuple.Create(false, $"The variable does not exist {item2.Key}"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -345,12 +346,12 @@ public class OpcDaMaster : IDisposable
|
||||
var data = item1.ToList();
|
||||
foreach (var item2 in result)
|
||||
{
|
||||
results.AddOrUpdate(handleItems[item2.Item1].ItemID, Tuple.Create(false, $"错误代码{item2.Item2}"));
|
||||
results.AddOrUpdate(handleItems[item2.Item1].ItemID, Tuple.Create(false, $"Error code{item2.Item2}"));
|
||||
}
|
||||
}
|
||||
foreach (var item2 in item1)
|
||||
{
|
||||
results.AddOrUpdate(item2.Key, Tuple.Create(true, $"成功"));
|
||||
results.AddOrUpdate(item2.Key, Tuple.Create(true, $"Success"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -383,11 +384,11 @@ public class OpcDaMaster : IDisposable
|
||||
try
|
||||
{
|
||||
PrivateConnect();
|
||||
LogEvent?.Invoke(1, this, $"重新链接成功", null);
|
||||
LogEvent?.Invoke(1, this, $"Successfully reconnected", null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEvent?.Invoke(3, this, $"重新链接失败:{ex.Message}", ex);
|
||||
LogEvent?.Invoke(3, this, $"Reconnect failed:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,7 +410,7 @@ public class OpcDaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEvent?.Invoke(3, this, $"添加点位失败:{ex.Message}", ex);
|
||||
LogEvent?.Invoke(3, this, $"Add variable failed:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,18 +432,18 @@ public class OpcDaMaster : IDisposable
|
||||
}
|
||||
catch
|
||||
{
|
||||
Init(OpcDaConfig);
|
||||
Init(OpcDaProperty);
|
||||
m_server?.Connect();
|
||||
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
|
||||
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - Connection successful", null);
|
||||
PrivateAddItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Init(OpcDaConfig);
|
||||
Init(OpcDaProperty);
|
||||
m_server?.Connect();
|
||||
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
|
||||
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - Connection successful", null);
|
||||
PrivateAddItems();
|
||||
}
|
||||
}
|
||||
@@ -453,7 +454,7 @@ public class OpcDaMaster : IDisposable
|
||||
lock (this)
|
||||
{
|
||||
if (IsConnected)
|
||||
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 断开连接", null);
|
||||
LogEvent?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - Disconnect", null);
|
||||
if (checkTimer != null)
|
||||
{
|
||||
checkTimer.Enabled = false;
|
||||
@@ -467,7 +468,7 @@ public class OpcDaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEvent?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
|
||||
LogEvent?.Invoke(3, this, $"Connection dispose failed:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,55 +8,46 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.OpcDa;
|
||||
|
||||
/// <summary>
|
||||
/// OpcDA连接配置项
|
||||
/// </summary>
|
||||
public class OpcDaConfig
|
||||
public class OpcDaProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否订阅
|
||||
/// </summary>
|
||||
[Description("订阅")]
|
||||
public bool ActiveSubscribe { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 内部检测重连间隔/min
|
||||
/// </summary>
|
||||
[Description("重连间隔/min")]
|
||||
public int CheckRate { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// 死区
|
||||
/// </summary>
|
||||
[Description("死区")]
|
||||
public float DeadBand { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 最大组大小
|
||||
/// </summary>
|
||||
[Description("最大组大小")]
|
||||
public int GroupSize { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// OpcIP
|
||||
/// </summary>
|
||||
[Description("OpcIP")]
|
||||
public string OpcIP { get; set; } = "localhost";
|
||||
|
||||
/// <summary>
|
||||
/// OpcNAME
|
||||
/// OpcName
|
||||
/// </summary>
|
||||
[Description("OpcNAME")]
|
||||
public string OpcName { get; set; } = "Kepware.KEPServerEX.V6";
|
||||
|
||||
/// <summary>
|
||||
/// 订阅间隔
|
||||
/// 订阅推送间隔
|
||||
/// </summary>
|
||||
[Description("订阅间隔")]
|
||||
public int UpdateRate { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
@@ -6,14 +6,12 @@
|
||||
<DocumentationFile></DocumentationFile>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Locales\en-US.json" />
|
||||
<None Remove="Locales\zh-CN.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Locales\en-US.json" />
|
||||
<EmbeddedResource Include="Locales\zh-CN.json" />
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
|
@@ -27,7 +27,7 @@ internal static class DictionaryExtension
|
||||
/// <param name="pairs"></param>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
|
||||
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func) where TKey : notnull
|
||||
{
|
||||
var list = new List<TKey>();
|
||||
foreach (var item in pairs)
|
||||
@@ -54,13 +54,13 @@ internal static class DictionaryExtension
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
internal static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
@@ -75,13 +75,13 @@ internal static class DictionaryExtension
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
internal static void AddOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value) where TKey : notnull
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
@@ -96,12 +96,12 @@ internal static class DictionaryExtension
|
||||
/// <summary>
|
||||
/// 获取值。如果键不存在,则返回默认值。
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <returns></returns>
|
||||
internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
|
||||
internal static TValue GetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey) where TKey : notnull
|
||||
{
|
||||
return dictionary.TryGetValue(tkey, out var value) ? value : default;
|
||||
}
|
||||
|
@@ -1,9 +1,19 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaMaster": {
|
||||
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaProperty": {
|
||||
"OpcUrl": "OpcUrl",
|
||||
"UserName": "UserName",
|
||||
"Password": "Password",
|
||||
"CheckDomain": "CheckDomain",
|
||||
"UpdateRate": "UpdateRate",
|
||||
"ActiveSubscribe": "ActiveSubscribe",
|
||||
"GroupSize": "GroupSize",
|
||||
"DeadBand": "DeadBand",
|
||||
"KeepAliveInterval": "KeepAliveInterval(ms)",
|
||||
"UseSecurity": "IsUseSecurity",
|
||||
"LoadType": "LoadType"
|
||||
},
|
||||
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaResource": {
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -1,6 +1,16 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaMaster": {
|
||||
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaProperty": {
|
||||
"OpcUrl": "OpcUrl",
|
||||
"UserName": "登录账号",
|
||||
"Password": "登录密码",
|
||||
"CheckDomain": "检查域",
|
||||
"UpdateRate": "推送间隔",
|
||||
"ActiveSubscribe": "订阅",
|
||||
"GroupSize": "分组大小",
|
||||
"DeadBand": "死区",
|
||||
"KeepAliveInterval": "心跳间隔(ms)",
|
||||
"UseSecurity": "安全策略",
|
||||
"LoadType": "加载服务端数据类型"
|
||||
},
|
||||
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaResource": {
|
||||
|
@@ -10,32 +10,7 @@
|
||||
|
||||
namespace ThingsGateway.Foundation.OpcUa;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA的状态更新消息
|
||||
/// </summary>
|
||||
public class OpcUaStatusEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志等级,<br></br>
|
||||
/// 更为详细的步骤型日志输出 Trace = 0,<br></br>
|
||||
/// 调试信息日志Debug = 1,<br></br>
|
||||
/// 消息类日志输出 Info = 2,<br></br>
|
||||
/// 警告类日志输出 Warning = 3,<br></br>
|
||||
/// 错误类日志输出 Error = 4,<br></br>
|
||||
/// 不可控中断类日输出Critical = 5,
|
||||
/// </summary>
|
||||
public int LogLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文本
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取属性过程中用于描述的
|
||||
|
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation.OpcUa;
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public delegate void DataChangedEventHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) value);
|
||||
/// <summary>
|
||||
/// 日志输出
|
||||
/// </summary>
|
||||
public delegate void LogEventHandler(byte level, object sender, string message, Exception ex);
|
||||
|
||||
/// <summary>
|
||||
/// OpcUaMaster
|
||||
@@ -28,7 +32,7 @@ public class OpcUaMaster : IDisposable
|
||||
/// <summary>
|
||||
/// 当前配置
|
||||
/// </summary>
|
||||
public OpcUaConfig OpcUaConfig;
|
||||
public OpcUaProperty OpcUaProperty;
|
||||
|
||||
/// <summary>
|
||||
/// ProductUri
|
||||
@@ -60,7 +64,6 @@ public class OpcUaMaster : IDisposable
|
||||
private EventHandler m_ReconnectStarting;
|
||||
private EventHandler<KeepAliveEventArgs> m_KeepAliveComplete;
|
||||
private EventHandler<bool> m_ConnectComplete;
|
||||
private EventHandler<OpcUaStatusEventArgs> m_OpcStatusChange;
|
||||
|
||||
private ISession m_session;
|
||||
|
||||
@@ -202,11 +205,7 @@ public class OpcUaMaster : IDisposable
|
||||
/// <summary>
|
||||
/// Raised after the client status change
|
||||
/// </summary>
|
||||
public event EventHandler<OpcUaStatusEventArgs> OpcStatusChange
|
||||
{
|
||||
add { m_OpcStatusChange += value; }
|
||||
remove { m_OpcStatusChange -= value; }
|
||||
}
|
||||
public LogEventHandler LogEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 配置信息
|
||||
@@ -263,15 +262,15 @@ public class OpcUaMaster : IDisposable
|
||||
StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = items[i],
|
||||
Filter = OpcUaConfig.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaConfig.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
|
||||
SamplingInterval = OpcUaConfig?.UpdateRate ?? 1000,
|
||||
Filter = OpcUaProperty.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaProperty.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
|
||||
SamplingInterval = OpcUaProperty?.UpdateRate ?? 1000,
|
||||
};
|
||||
item.Notification += Callback;
|
||||
monitoredItems.Add(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"初始化{items[i]}变量订阅失败,错误原因:{ex}");
|
||||
Log(3, ex, $"Failed to initialize {items[i]} variable subscription");
|
||||
}
|
||||
}
|
||||
m_subscription.AddItems(monitoredItems);
|
||||
@@ -280,14 +279,14 @@ public class OpcUaMaster : IDisposable
|
||||
m_subscription.Create();
|
||||
foreach (var item in m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)))
|
||||
{
|
||||
item.Filter = OpcUaConfig.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaConfig.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
|
||||
item.Filter = OpcUaProperty.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OpcUaProperty.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
|
||||
}
|
||||
m_subscription.ApplyChanges();
|
||||
|
||||
var isError = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
|
||||
if (isError)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"创建以下变量订阅失败:{Environment.NewLine}{m_subscription.MonitoredItems.Where(
|
||||
Log(3, null, $"Failed to create subscription for the following variables:{Environment.NewLine}{m_subscription.MonitoredItems.Where(
|
||||
a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode))
|
||||
.Select(a => $"{a.StartNodeId.ToString()}:{a.Status.Error.ToString()}").ToJsonString()}");
|
||||
}
|
||||
@@ -467,7 +466,7 @@ public class OpcUaMaster : IDisposable
|
||||
/// </summary>
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await ConnectAsync(OpcUaConfig.OpcUrl, cancellationToken);
|
||||
await ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -544,7 +543,7 @@ public class OpcUaMaster : IDisposable
|
||||
results.Add(keys[i], Tuple.Create(false, result.Results[i].ToString()));
|
||||
else
|
||||
{
|
||||
results.Add(keys[i], Tuple.Create(true, "成功"));
|
||||
results.Add(keys[i], Tuple.Create(true, "Success"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,7 +805,7 @@ public class OpcUaMaster : IDisposable
|
||||
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
|
||||
if (data == null && value.Value != null)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}转换出错,原始值String为{value.Value}");
|
||||
Log(3, null, $"{monitoreditem.StartNodeId}Conversion error, original value is{value.Value}");
|
||||
var data1 = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
|
||||
}
|
||||
DataChangedHandler?.Invoke((variableNode, value, data!));
|
||||
@@ -821,7 +820,7 @@ public class OpcUaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}订阅处理错误,错误原因:" + ex);
|
||||
Log(3, ex, $"{monitoreditem.StartNodeId}Subscription processing error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,17 +838,17 @@ public class OpcUaMaster : IDisposable
|
||||
|
||||
if (m_configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException("未初始化配置");
|
||||
throw new ArgumentNullException(nameof(m_configuration));
|
||||
}
|
||||
var useSecurity = OpcUaConfig?.IsUseSecurity ?? true;
|
||||
var useSecurity = OpcUaProperty?.UseSecurity ?? true;
|
||||
|
||||
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity, 10000);
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
|
||||
ConfiguredEndpoint endpoint = new(null, endpointDescription, endpointConfiguration);
|
||||
UserIdentity userIdentity;
|
||||
if (!string.IsNullOrEmpty(OpcUaConfig.UserName))
|
||||
if (!string.IsNullOrEmpty(OpcUaProperty.UserName))
|
||||
{
|
||||
userIdentity = new UserIdentity(OpcUaConfig.UserName, OpcUaConfig.Password);
|
||||
userIdentity = new UserIdentity(OpcUaProperty.UserName, OpcUaProperty.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -862,7 +861,7 @@ public class OpcUaMaster : IDisposable
|
||||
m_configuration,
|
||||
endpoint,
|
||||
false,
|
||||
OpcUaConfig.CheckDomain,
|
||||
OpcUaProperty.CheckDomain,
|
||||
(string.IsNullOrEmpty(OPCUAName)) ? m_configuration.ApplicationName : OPCUAName,
|
||||
60000,
|
||||
userIdentity,
|
||||
@@ -870,20 +869,20 @@ public class OpcUaMaster : IDisposable
|
||||
).ConfigureAwait(false);
|
||||
typeSystem = new ComplexTypeSystem(m_session);
|
||||
|
||||
m_session.KeepAliveInterval = OpcUaConfig.KeepAliveInterval == 0 ? 60000 : OpcUaConfig.KeepAliveInterval;
|
||||
m_session.KeepAliveInterval = OpcUaProperty.KeepAliveInterval == 0 ? 60000 : OpcUaProperty.KeepAliveInterval;
|
||||
m_session.KeepAlive += Session_KeepAlive;
|
||||
|
||||
// raise an event.
|
||||
DoConnectComplete(true);
|
||||
|
||||
UpdateStatus(2, DateTime.UtcNow, "Connected");
|
||||
Log(2, null, "Connected");
|
||||
|
||||
//如果是订阅模式,连接时添加订阅组
|
||||
if (OpcUaConfig.ActiveSubscribe)
|
||||
if (OpcUaProperty.ActiveSubscribe)
|
||||
{
|
||||
foreach (var item in Variables)
|
||||
{
|
||||
await AddSubscriptionAsync(Guid.NewGuid().ToString(), item.ToArray(), OpcUaConfig.LoadType);
|
||||
await AddSubscriptionAsync(Guid.NewGuid().ToString(), item.ToArray(), OpcUaProperty.LoadType);
|
||||
}
|
||||
}
|
||||
return m_session;
|
||||
@@ -906,7 +905,7 @@ public class OpcUaMaster : IDisposable
|
||||
|
||||
if (state)
|
||||
{
|
||||
UpdateStatus(2, DateTime.UtcNow, "Disconnected");
|
||||
Log(2, null, "Disconnected");
|
||||
DoConnectComplete(false);
|
||||
}
|
||||
}
|
||||
@@ -922,7 +921,7 @@ public class OpcUaMaster : IDisposable
|
||||
{
|
||||
if (m_session == null)
|
||||
{
|
||||
throw new("服务器未初始化连接");
|
||||
throw new ArgumentNullException(nameof(m_session));
|
||||
}
|
||||
ReadValueIdCollection nodesToRead = new();
|
||||
for (int i = 0; i < nodeIds.Length; i++)
|
||||
@@ -970,7 +969,7 @@ public class OpcUaMaster : IDisposable
|
||||
}
|
||||
NodeId nodeToRead = new(nodeIdStr);
|
||||
var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
|
||||
if (OpcUaConfig.LoadType)
|
||||
if (OpcUaProperty.LoadType)
|
||||
await typeSystem.LoadType(node.DataType).ConfigureAwait(false);
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, node);
|
||||
return node;
|
||||
@@ -1016,7 +1015,7 @@ public class OpcUaMaster : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"获取服务器节点信息失败{nodes.Item2[i]}");
|
||||
Log(3, null, $"Failed to obtain server node information: {nodes.Item2[i]}");
|
||||
}
|
||||
}
|
||||
return nodes.Item1.ToList();
|
||||
@@ -1033,7 +1032,7 @@ public class OpcUaMaster : IDisposable
|
||||
else if (eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted)
|
||||
eventArgs.Accept = true;
|
||||
else
|
||||
throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
|
||||
throw new Exception(string.Format("Verification certificate failed with error code: {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken cancellationToken)
|
||||
@@ -1145,7 +1144,7 @@ public class OpcUaMaster : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"{nameof(Server_ReconnectComplete)}错误:{ex}");
|
||||
Log(3, ex, $"{nameof(Server_ReconnectComplete)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,14 +1155,9 @@ public class OpcUaMaster : IDisposable
|
||||
/// <param name="time">The time associated with the status.</param>
|
||||
/// <param name="status">The status message.</param>
|
||||
/// <param name="args">Arguments used to format the status message.</param>
|
||||
private void UpdateStatus(int logLevel, DateTime time, string status, params object[] args)
|
||||
private void Log(byte logLevel, Exception exception, string status, params object[] args)
|
||||
{
|
||||
m_OpcStatusChange?.Invoke(this, new OpcUaStatusEventArgs()
|
||||
{
|
||||
LogLevel = logLevel,
|
||||
Time = time.ToLocalTime(),
|
||||
Text = String.Format(status, args),
|
||||
});
|
||||
LogEvent?.Invoke(logLevel, this, string.Format(status, args), exception);
|
||||
}
|
||||
|
||||
private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
|
||||
@@ -1179,11 +1173,11 @@ public class OpcUaMaster : IDisposable
|
||||
{
|
||||
if (m_session.KeepAliveInterval <= 0)
|
||||
{
|
||||
UpdateStatus(3, e.CurrentTime, "Communication Error ({0})", e.Status);
|
||||
Log(3, null, "Communication Error ({0})", e.Status);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateStatus(3, e.CurrentTime, "Reconnecting in {0}s", 10);
|
||||
Log(3, null, "Reconnecting in {0}s", 10);
|
||||
|
||||
if (m_reConnectHandler == null)
|
||||
{
|
||||
@@ -1196,7 +1190,7 @@ public class OpcUaMaster : IDisposable
|
||||
}
|
||||
|
||||
// update status.
|
||||
UpdateStatus(0, e.CurrentTime, "Session_KeepAlive Connected [{0}]", session.Endpoint.EndpointUrl);
|
||||
Log(0, null, "Session_KeepAlive Connected [{0}]", session.Endpoint.EndpointUrl);
|
||||
|
||||
// raise any additional notifications.
|
||||
m_KeepAliveComplete?.Invoke(this, e);
|
||||
|
@@ -8,79 +8,64 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.OpcUa;
|
||||
|
||||
/// <summary>
|
||||
/// OpcUaMaster配置项
|
||||
/// </summary>
|
||||
public class OpcUaConfig
|
||||
public class OpcUaProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// OpcUrl
|
||||
/// </summary>
|
||||
[Description("OpcUrl")]
|
||||
public string OpcUrl { get; set; } = "opc.tcp://127.0.0.1:49320";
|
||||
|
||||
/// <summary>
|
||||
/// 登录账号
|
||||
/// </summary>
|
||||
[Description("登录账号")]
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录密码
|
||||
/// </summary>
|
||||
[Description("登录密码")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检查域
|
||||
/// </summary>
|
||||
[Description("检查域")]
|
||||
public bool CheckDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新间隔
|
||||
/// </summary>
|
||||
[Description("更新间隔")]
|
||||
public int UpdateRate { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// 是否订阅
|
||||
/// </summary>
|
||||
[Description("是否订阅")]
|
||||
public bool ActiveSubscribe { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 分组大小
|
||||
/// </summary>
|
||||
[Description("分组大小")]
|
||||
public int GroupSize { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// 死区
|
||||
/// </summary>
|
||||
[Description("死区")]
|
||||
public double DeadBand { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳间隔/ms
|
||||
/// </summary>
|
||||
[Description("心跳间隔/ms")]
|
||||
|
||||
public int KeepAliveInterval { get; set; } = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// 安全策略
|
||||
/// </summary>
|
||||
[Description("安全策略")]
|
||||
public bool IsUseSecurity { get; set; } = false;
|
||||
public bool UseSecurity { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 加载服务端数据类型
|
||||
/// </summary>
|
||||
[Description("加载服务端数据类型")]
|
||||
public bool LoadType { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
@@ -11,20 +11,16 @@
|
||||
<PropertyGroup>
|
||||
<Description>工业设备通讯协议-OpcUa协议</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Locales\en-US.json" />
|
||||
<None Remove="Locales\zh-CN.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Locales\en-US.json" />
|
||||
<EmbeddedResource Include="Locales\zh-CN.json" />
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.374.27" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.374.27" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.374.36" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.374.36" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
|
@@ -27,7 +27,7 @@ internal static class DictionaryExtension
|
||||
/// <param name="pairs"></param>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
|
||||
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func) where TKey : notnull
|
||||
{
|
||||
var list = new List<TKey>();
|
||||
foreach (var item in pairs)
|
||||
@@ -54,13 +54,13 @@ internal static class DictionaryExtension
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
internal static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
@@ -75,13 +75,13 @@ internal static class DictionaryExtension
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
internal static void AddOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey, TValue value) where TKey : notnull
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
@@ -96,12 +96,12 @@ internal static class DictionaryExtension
|
||||
/// <summary>
|
||||
/// 获取值。如果键不存在,则返回默认值。
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <returns></returns>
|
||||
internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
|
||||
internal static TValue GetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey tkey) where TKey : notnull
|
||||
{
|
||||
return dictionary.TryGetValue(tkey, out var value) ? value : default;
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@
|
||||
|
||||
}
|
||||
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage ></AdapterDebugComponent>
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent ></AdapterDebugComponent>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -34,7 +34,7 @@ namespace ThingsGateway.Debug
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
private AdapterDebugComponent AdapterDebugPage { get; set; }
|
||||
private AdapterDebugComponent AdapterDebugComponent { get; set; }
|
||||
|
||||
private ChannelData ChannelData { get; set; }
|
||||
|
||||
|
@@ -37,7 +37,7 @@
|
||||
|
||||
}
|
||||
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage ></AdapterDebugComponent>
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent ></AdapterDebugComponent>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -34,7 +34,7 @@ namespace ThingsGateway.Debug
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
private AdapterDebugComponent AdapterDebugPage { get; set; }
|
||||
private AdapterDebugComponent AdapterDebugComponent { get; set; }
|
||||
|
||||
private ChannelData ChannelData { get; set; }
|
||||
|
||||
|
@@ -39,7 +39,7 @@
|
||||
|
||||
}
|
||||
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText"></AdapterDebugComponent>
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent HeaderText="HeaderText"></AdapterDebugComponent>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -32,7 +32,7 @@ namespace ThingsGateway.Debug
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
private AdapterDebugComponent AdapterDebugPage { get; set; }
|
||||
private AdapterDebugComponent AdapterDebugComponent { get; set; }
|
||||
|
||||
private ChannelData ChannelData { get; set; }
|
||||
|
||||
|
@@ -11,5 +11,4 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
|
||||
global using ThingsGateway.Components;
|
||||
global using ThingsGateway.Foundation;
|
@@ -1,6 +1,19 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
|
||||
"NoVariablesAvailable": "No variables available",
|
||||
"Success": "Success"
|
||||
|
||||
"ThingsGateway.Plugin.OpcDa.OpcDaMasterProperty": {
|
||||
"ActiveSubscribe": "ActiveSubscribe",
|
||||
"CheckRate": "CheckRate(min)",
|
||||
"DeadBand": "DeadBand",
|
||||
"GroupSize": "GroupSize",
|
||||
"OpcIP": "OpcIP",
|
||||
"OpcName": "OpcName",
|
||||
"UpdateRate": "UpdateRate",
|
||||
"SourceTimestampEnable": "SourceTimestampEnable"
|
||||
},
|
||||
|
||||
|
||||
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
|
||||
"NoVariablesAvailable": "No variables available",
|
||||
"Success": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,16 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
|
||||
"NoVariablesAvailable": "无可用变量",
|
||||
"Success": "成功"
|
||||
}
|
||||
"ThingsGateway.Plugin.OpcDa.OpcDaMasterProperty": {
|
||||
"ActiveSubscribe": "订阅",
|
||||
"CheckRate": "心跳间隔(min)",
|
||||
"DeadBand": "死区",
|
||||
"GroupSize": "最大组大小",
|
||||
"OpcIP": "OpcIP",
|
||||
"OpcName": "OpcName",
|
||||
"UpdateRate": "订阅推送间隔",
|
||||
"SourceTimestampEnable": "服务端时间戳"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcDa.OpcDaImportVariable": {
|
||||
"NoVariablesAvailable": "无可用变量",
|
||||
"Success": "成功"
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using NewLife.Threading;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Foundation.OpcDa;
|
||||
@@ -35,21 +37,13 @@ public class OpcDaMaster : CollectBase
|
||||
public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcDaMaster);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override CollectPropertyBase DriverProperties => _driverProperties;
|
||||
public override CollectPropertyBase CollectProperties => _driverProperties;
|
||||
|
||||
public override Type DriverUIType => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IProtocol Protocol => null;
|
||||
|
||||
protected override bool IsSingleThread
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{_driverProperties.OpcIP}-{_driverProperties.OpcName}";
|
||||
@@ -59,7 +53,7 @@ public class OpcDaMaster : CollectBase
|
||||
public override void Init(IChannel? channel = null)
|
||||
{
|
||||
//载入配置
|
||||
OpcDaConfig opcNode = new()
|
||||
OpcDaProperty opcNode = new()
|
||||
{
|
||||
OpcIP = _driverProperties.OpcIP,
|
||||
OpcName = _driverProperties.OpcName,
|
||||
@@ -73,10 +67,10 @@ public class OpcDaMaster : CollectBase
|
||||
{
|
||||
_plc = new();
|
||||
_plc.DataChangedHandler += DataChangedHandler;
|
||||
_plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
|
||||
}
|
||||
_plc.Init(opcNode);
|
||||
|
||||
base.Init(channel);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -126,13 +120,14 @@ public class OpcDaMaster : CollectBase
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<OperResult<byte[]>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken)
|
||||
{
|
||||
if (IsSingleThread)
|
||||
// 如果是单线程模式,并且有其他线程正在等待写入锁
|
||||
if (IsSingleThread && WriteLock.IsWaitting)
|
||||
{
|
||||
while (WriteLock.IsWaitting)
|
||||
{
|
||||
//等待写入完成
|
||||
await Task.Delay(100);
|
||||
}
|
||||
// 等待写入锁释放
|
||||
await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 立即释放写入锁,允许其他线程继续执行写入操作
|
||||
WriteLock.Release();
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -180,11 +175,11 @@ public class OpcDaMaster : CollectBase
|
||||
if (IsConnected())
|
||||
{
|
||||
//更新设备活动时间
|
||||
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 0);
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 999);
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, 999);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -211,7 +206,7 @@ public class OpcDaMaster : CollectBase
|
||||
return;
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
LogMessage.Trace($"{ToString()} 状态变化:{Environment.NewLine} {values?.ToJsonString()}");
|
||||
LogMessage.Trace($"{ToString()} Change:{Environment.NewLine} {values?.ToJsonString()}");
|
||||
|
||||
foreach (var data in values)
|
||||
{
|
||||
@@ -224,7 +219,7 @@ public class OpcDaMaster : CollectBase
|
||||
{
|
||||
type = type.GetElementType();
|
||||
}
|
||||
var itemReads = CurrentDevice.VariableRunTimes.Where(it => it.RegisterAddress == data.Name).ToList();
|
||||
var itemReads = CurrentDevice.VariableRunTimes.Values.Where(it => it.RegisterAddress == data.Name);
|
||||
foreach (var item in itemReads)
|
||||
{
|
||||
if (!CurrentDevice.KeepRun)
|
||||
@@ -264,7 +259,7 @@ public class OpcDaMaster : CollectBase
|
||||
else
|
||||
{
|
||||
item.SetValue(null, time, false);
|
||||
item.VariableSource.LastErrorMessage = $"错误质量戳:{quality}";
|
||||
item.VariableSource.LastErrorMessage = $"Bad quality:{quality}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,43 +20,50 @@ public class OpcDaMasterProperty : CollectPropertyBase
|
||||
/// <summary>
|
||||
/// OpcIP
|
||||
/// </summary>
|
||||
[DynamicProperty("IP", "")] public string OpcIP { get; set; } = "localhost";
|
||||
[DynamicProperty]
|
||||
public string OpcIP { get; set; } = "localhost";
|
||||
|
||||
/// <summary>
|
||||
/// Opc名称
|
||||
/// </summary>
|
||||
[DynamicProperty("Opc名称", "")] public string OpcName { get; set; } = "Kepware.KEPServerEX.V6";
|
||||
[DynamicProperty]
|
||||
public string OpcName { get; set; } = "Kepware.KEPServerEX.V6";
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用SourceTime
|
||||
/// </summary>
|
||||
[DynamicProperty("使用SourceTime", "")]
|
||||
[DynamicProperty]
|
||||
public bool SourceTimestampEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 激活订阅
|
||||
/// </summary>
|
||||
[DynamicProperty("激活订阅", "")] public bool ActiveSubscribe { get; set; } = true;
|
||||
[DynamicProperty]
|
||||
public bool ActiveSubscribe { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 检测重连频率min
|
||||
/// </summary>
|
||||
[DynamicProperty("检测重连频率min", "")] public int CheckRate { get; set; } = 10;
|
||||
[DynamicProperty]
|
||||
public int CheckRate { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 死区
|
||||
/// </summary>
|
||||
[DynamicProperty("死区", "")] public float DeadBand { get; set; } = 0;
|
||||
[DynamicProperty]
|
||||
public float DeadBand { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 最大组大小
|
||||
/// </summary>
|
||||
[DynamicProperty("最大组大小", "")] public int GroupSize { get; set; } = 500;
|
||||
[DynamicProperty]
|
||||
public int GroupSize { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// 更新频率
|
||||
/// </summary>
|
||||
[DynamicProperty("更新频率", "")] public int UpdateRate { get; set; } = 1000;
|
||||
[DynamicProperty]
|
||||
public int UpdateRate { get; set; } = 1000;
|
||||
|
||||
public override int ConcurrentCount { get; set; } = 1;
|
||||
}
|
@@ -1,75 +1,99 @@
|
||||
@page "/OpcDaMaster"
|
||||
@using BootstrapBlazor.Components
|
||||
@using ThingsGateway.Core.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@namespace ThingsGateway.Debug
|
||||
@using ThingsGateway.Foundation.OpcDa
|
||||
@using TouchSocket.Core
|
||||
@inherits BaseComponentBase
|
||||
|
||||
<OpcDaMasterConnectPage @ref=opcDaMasterConnectPage OnConnectClick=OnConnectClick></OpcDaMasterConnectPage>
|
||||
|
||||
|
||||
<AdapterDebugPage LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText" Height=@($"calc(100vh - {BlazorAppService.DefaultHeight+400}px)") ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
|
||||
<div style="height:100%; width:100%;">
|
||||
|
||||
<ReadWriteContent>
|
||||
@if (_plc?.OpcDaProperty != null)
|
||||
{
|
||||
<Card class="mt-2">
|
||||
<BodyTemplate>
|
||||
|
||||
<MContainer>
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
|
||||
<MTooltip Right Context="tip">
|
||||
<ActivatorContent>
|
||||
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
|
||||
Label=@AppService.I18n.T("Register Address") @attributes="@tip.Attrs" @bind-Value=@RegisterAddress />
|
||||
</ActivatorContent>
|
||||
<ChildContent>
|
||||
<span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span>
|
||||
</ChildContent>
|
||||
</MTooltip>
|
||||
<EditorForm Model="_plc.OpcDaProperty" AutoGenerateAllItem=false RowType="RowType.Inline" ItemsPerRow="4" ShowLabelTooltip="true" ShowLabel="true">
|
||||
<FieldItems>
|
||||
|
||||
|
||||
<MCol Align="AlignTypes.Center">
|
||||
<MButton Class="mx-1 my-1" Color="primary" OnClick="Add">
|
||||
@AppService.I18n.T("添加")
|
||||
<EditorItem @bind-Field=context.OpcIP />
|
||||
<EditorItem @bind-Field=context.OpcName />
|
||||
<EditorItem @bind-Field=context.CheckRate />
|
||||
<EditorItem @bind-Field=context.GroupSize />
|
||||
<EditorItem @bind-Field=context.ActiveSubscribe />
|
||||
<EditorItem @bind-Field=context.UpdateRate />
|
||||
<EditorItem @bind-Field=context.DeadBand />
|
||||
|
||||
</MButton>
|
||||
<MButton Class="mx-1 my-1" Color="primary" OnClick="Remove">
|
||||
@AppService.I18n.T("移除")
|
||||
</FieldItems>
|
||||
|
||||
</MButton>
|
||||
<MButton Class="ma-1" Color="primary" OnClick="ReadAsync">
|
||||
@AppService.I18n.T("读取")
|
||||
</MButton>
|
||||
</EditorForm>
|
||||
|
||||
</MCol>
|
||||
</MRow>
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Connect">@OpcDaPropertyLocalizer["Connect"]</Button>
|
||||
|
||||
<Button IsAsync Color="Color.Warning" OnClick="Disconnect">@OpcDaPropertyLocalizer["Disconnect"]</Button>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
}
|
||||
|
||||
<AdapterDebugComponent Plc=null LogPath=@LogPath @ref=AdapterDebugComponent ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
|
||||
|
||||
<ReadWriteContent>
|
||||
|
||||
<BootstrapInput @bind-Value=@RegisterAddress
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
|
||||
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
|
||||
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
|
||||
Label=@AppService.I18n.T("WriteValue") @bind-Value=@WriteValue />
|
||||
<MCol Align="AlignTypes.Center">
|
||||
<MButton Class="ma-1" Color="primary" OnClick="WriteAsync">
|
||||
@AppService.I18n.T("Write")
|
||||
</MButton>
|
||||
</MCol>
|
||||
</MRow>
|
||||
|
||||
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
|
||||
<MButton Class="ma-1" Color="primary" OnClick="ShowImport">
|
||||
@AppService.I18n.T("查看Opc节点空间")
|
||||
</MButton>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
</MRow>
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Add">@OpcDaPropertyLocalizer["Add"]</Button>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Remove">@OpcDaPropertyLocalizer["Remove"]</Button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@OpcDaPropertyLocalizer["Read"]</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
|
||||
ShowLabel="true" />
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@OpcDaPropertyLocalizer["Write"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="ShowImport">@OpcDaPropertyLocalizer["ShowImport"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</MContainer>
|
||||
</ReadWriteContent>
|
||||
|
||||
|
||||
</ReadWriteContent>
|
||||
</AdapterDebugComponent>
|
||||
</div>
|
||||
|
||||
|
||||
</AdapterDebugPage>
|
||||
|
||||
|
@@ -8,117 +8,158 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ThingsGateway.Debug;
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using ThingsGateway.Foundation.OpcDa;
|
||||
using ThingsGateway.Foundation.OpcDa.Da;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class OpcDaMaster : IDisposable
|
||||
{
|
||||
public partial class OpcDaMaster
|
||||
public LoggerGroup? LogMessage;
|
||||
private readonly OpcDaProperty OpcDaProperty = new();
|
||||
private ThingsGateway.Foundation.OpcDa.OpcDaMaster _plc;
|
||||
private string LogPath;
|
||||
private string RegisterAddress;
|
||||
private string WriteValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
~OpcDaMaster()
|
||||
{
|
||||
private ThingsGateway.Foundation.OpcDa.OpcDaMaster Plc => opcDaMasterConnectPage.Plc;
|
||||
private OpcDaMasterConnectPage opcDaMasterConnectPage;
|
||||
private string RegisterAddress;
|
||||
this.SafeDispose();
|
||||
}
|
||||
private AdapterDebugComponent AdapterDebugComponent { get; set; }
|
||||
[Inject]
|
||||
IStringLocalizer<OpcDaProperty> OpcDaPropertyLocalizer { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
_plc?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_plc = new ThingsGateway.Foundation.OpcDa.OpcDaMaster();
|
||||
|
||||
private string LogPath;
|
||||
private AdapterDebugComponent AdapterDebugPage { get; set; }
|
||||
_plc.Init(OpcDaProperty);
|
||||
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
var logger = TextFileLogger.Create(_plc.GetHashCode().ToLong().GetDebugLogPath());
|
||||
logger.LogLevel = LogLevel.Trace;
|
||||
LogMessage.AddLogger(logger);
|
||||
|
||||
private void OnConnectClick()
|
||||
_plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
|
||||
_plc.DataChangedHandler += (a) => LogMessage.Trace(a.ToJsonString());
|
||||
base.OnInitialized();
|
||||
}
|
||||
private void Add()
|
||||
{
|
||||
var tags = new Dictionary<string, List<OpcItem>>();
|
||||
var tag = new OpcItem(RegisterAddress);
|
||||
tags.Add(Guid.NewGuid().ToString(), new List<OpcItem>() { tag });
|
||||
try
|
||||
{
|
||||
_plc.AddItems(tags);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
catch (Exception ex)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
if (firstRender)
|
||||
{
|
||||
//载入配置
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private string WriteValue;
|
||||
|
||||
private void Add()
|
||||
{
|
||||
var tags = new Dictionary<string, List<OpcItem>>();
|
||||
var tag = new OpcItem(RegisterAddress);
|
||||
tags.Add(Guid.NewGuid().ToString(), new List<OpcItem>() { tag });
|
||||
try
|
||||
{
|
||||
Plc.AddItems(tags);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
opcDaMasterConnectPage.LogMessage?.LogWarning(ex, $"添加失败");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
JToken tagValue = WriteValue.GetJTokenFromString();
|
||||
var obj = tagValue.GetObjectFromJToken();
|
||||
|
||||
var data = Plc.WriteItem(
|
||||
new()
|
||||
{
|
||||
{RegisterAddress, obj}
|
||||
}
|
||||
);
|
||||
if (data.Count > 0)
|
||||
{
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value.Item1)
|
||||
opcDaMasterConnectPage.LogMessage?.LogInformation(item.ToJsonString());
|
||||
else
|
||||
opcDaMasterConnectPage.LogMessage?.LogWarning(item.ToJsonString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
opcDaMasterConnectPage.LogMessage?.LogWarning(ex, $"写入失败");
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ShowImport()
|
||||
{
|
||||
await PopupService.OpenAsync(typeof(OpcDaImportVariable), new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(OpcDaImportVariable.Plc),Plc},
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ReadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc.ReadItemsWithGroup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
opcDaMasterConnectPage.LogMessage?.LogWarning(ex, $"读取失败");
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Remove()
|
||||
{
|
||||
Plc.RemoveItems(new List<string>() { RegisterAddress });
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
_plc.Disconnect();
|
||||
LogPath = _plc?.GetHashCode().ToLong().GetDebugLogPath();
|
||||
GetOpc().Connect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
private void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
_plc.Disconnect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ThingsGateway.Foundation.OpcDa.OpcDaMaster GetOpc()
|
||||
{
|
||||
//载入配置
|
||||
_plc.Init(OpcDaProperty);
|
||||
return _plc;
|
||||
}
|
||||
private async Task ReadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_plc.ReadItemsWithGroup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Remove()
|
||||
{
|
||||
_plc.RemoveItems(new List<string>() { RegisterAddress });
|
||||
}
|
||||
|
||||
private async Task ShowImport()
|
||||
{
|
||||
// await PopupService.OpenAsync(typeof(OpcDaImportVariable), new Dictionary<string, object?>()
|
||||
//{
|
||||
// {nameof(OpcDaImportVariable._plc),_plc},
|
||||
//});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task WriteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
JToken tagValue = WriteValue.GetJTokenFromString();
|
||||
var obj = tagValue.GetObjectFromJToken();
|
||||
|
||||
var data = _plc.WriteItem(
|
||||
new()
|
||||
{
|
||||
{RegisterAddress, obj}
|
||||
}
|
||||
);
|
||||
if (data.Count > 0)
|
||||
{
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value.Item1)
|
||||
LogMessage?.LogInformation(item.ToJsonString());
|
||||
else
|
||||
LogMessage?.LogWarning(item.ToJsonString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
@namespace ThingsGateway.Debug
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using System.Collections.Concurrent;
|
||||
@using ThingsGateway.Foundation.OpcDa;
|
||||
@using ThingsGateway.Core.Extension;
|
||||
@using Masa.Blazor
|
||||
@inherits BaseComponentBase
|
||||
@implements IDisposable
|
||||
<MCard Class="ma-2">
|
||||
<MSubheader Style="max-height:32px">
|
||||
@AppService.I18n.T("Adapter Config")
|
||||
</MSubheader>
|
||||
<MContainer Class="ma-0">
|
||||
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.GroupSize) @bind-Value=@config.GroupSize />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.UpdateRate) @bind-Value=@config.UpdateRate />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.DeadBand) @bind-Value=@config.DeadBand />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.CheckRate) @bind-Value=@config.CheckRate />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.OpcIP) @bind-Value=@config.OpcIP />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:250px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.OpcName) @bind-Value=@config.OpcName />
|
||||
|
||||
|
||||
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@(AppService.I18n.T(config.Description(x => x.ActiveSubscribe))) @bind-Value=@config.ActiveSubscribe></MCheckbox>
|
||||
|
||||
|
||||
<MButton Class="ma-1" OnClick=@Connect Color="primary">
|
||||
连接
|
||||
</MButton>
|
||||
|
||||
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
|
||||
断开
|
||||
</MButton>
|
||||
|
||||
|
||||
</MRow>
|
||||
</MContainer>
|
||||
</MCard>
|
||||
|
@@ -1,84 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.OpcDa;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class OpcDaMasterConnectPage : IDisposable
|
||||
{
|
||||
public ThingsGateway.Foundation.OpcDa.OpcDaMaster Plc;
|
||||
|
||||
private readonly OpcDaConfig config = new();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
Plc.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnConnectClick { get; set; }
|
||||
|
||||
public LoggerGroup? LogMessage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Plc = new ThingsGateway.Foundation.OpcDa.OpcDaMaster();
|
||||
|
||||
Plc.Init(config);
|
||||
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
var logger = TextFileLogger.Create(Plc.GetHashCode().ToLong().GetDebugLogPath());
|
||||
logger.LogLevel = LogLevel.Trace;
|
||||
LogMessage.AddLogger(logger);
|
||||
|
||||
Plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
|
||||
Plc.DataChangedHandler += (a) => LogMessage.Trace(a.ToJsonString());
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc.Disconnect();
|
||||
GetOpc().Connect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc.Disconnect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ThingsGateway.Foundation.OpcDa.OpcDaMaster GetOpc()
|
||||
{
|
||||
//载入配置
|
||||
Plc.Init(config);
|
||||
return Plc;
|
||||
}
|
||||
}
|
@@ -7,29 +7,22 @@
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
<Exec Command="@echo off
setlocal enabledelayedexpansion

set "targetFWS=net6.0 net7.0 net8.0"
for %25%25f in (%25targetFWS%25) do (
 set "dir=$(SolutionDir)bin\$(Configuration)\ThingsGateway.Web.Entry\%25%25f\Plugins\$(AssemblyName)"
 if not exist "!dir!" md "!dir!"
 copy "$(TargetDir)*OpcDa*.dll" "!dir!"
)

endlocal


" />
|
||||
<Exec Command="@echo off
setlocal enabledelayedexpansion

set targetFWS=$(TargetFrameworks)
for %25%25f in (%25targetFWS%25) do (
 set "dir=$(SolutionDir)ThingsGateway.Server\bin\$(Configuration)\%25%25f\Plugins\$(AssemblyName)"
 if not exist "!dir!" md "!dir!"
 copy "$(TargetDir)*OpcDa*.dll" "!dir!"
)

endlocal

" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\en-US.json" />
|
||||
<Content Remove="Locales\zh-CN.json" />
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Locales\en-US.json" />
|
||||
<EmbeddedResource Include="Locales\zh-CN.json" />
|
||||
<Compile Remove="Pages\OpcDaImportVariable.razor.cs" />
|
||||
<Content Remove="Pages\OpcDaImportVariable.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<ProjectReference Include="..\..\foundation\ThingsGateway.Foundation.OpcDa\src\ThingsGateway.Foundation.OpcDa.csproj" />
|
||||
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.OpcDa\ThingsGateway.Foundation.OpcDa.csproj" />
|
||||
|
||||
|
||||
<ProjectReference Include="$(SolutionDir)\adapter\ThingsGateway.Foundation.OpcDa\ThingsGateway.Foundation.OpcDa.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
@@ -5,11 +5,8 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using BlazorComponent
|
||||
@using Masa.Blazor
|
||||
@using Masa.Blazor.Presets
|
||||
@using ThingsGateway.Components;
|
||||
@using ThingsGateway.Core;
|
||||
@using ThingsGateway.Razor
|
||||
@using ThingsGateway.Core
|
||||
@using System.Net.Http.Json
|
||||
@using System.IO;
|
||||
@using System.IO
|
||||
@using System.Text.Json;
|
||||
|
@@ -11,5 +11,4 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
|
||||
global using ThingsGateway.Components;
|
||||
global using ThingsGateway.Foundation;
|
@@ -2,5 +2,33 @@
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaImportVariable": {
|
||||
"NoVariablesAvailable": "No variables available",
|
||||
"Success": "Success"
|
||||
},
|
||||
"ThingsGateway.Foundation.OpcUa.OpcUaProperty": {
|
||||
"OpcUrl": "OpcUrl",
|
||||
"UserName": "UserName",
|
||||
"Password": "Password",
|
||||
"CheckDomain": "CheckDomain",
|
||||
"UpdateRate": "UpdateRate",
|
||||
"ActiveSubscribe": "ActiveSubscribe",
|
||||
"GroupSize": "GroupSize",
|
||||
"DeadBand": "DeadBand",
|
||||
"KeepAliveInterval": "KeepAliveInterval(ms)",
|
||||
"UseSecurity": "UseSecurity",
|
||||
"LoadType": "LoadType",
|
||||
"SourceTimestampEnable": "SourceTimestampEnable"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaServerProperty": {
|
||||
"IsAllVariable": "IsAllVariable",
|
||||
"OpcUaStringUrl": "OpcUaStringUrl",
|
||||
"BigTextSubjectName": "SubjectName",
|
||||
"BigTextApplicationUri": "ApplicationUri",
|
||||
"SecurityPolicy": "SecurityPolicy",
|
||||
"AutoAcceptUntrustedCertificates": "AutoAcceptUntrustedCertificates"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaServerVariableProperty": {
|
||||
"DataType": "DataType"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaServer": {
|
||||
"CanStartService": "Can start service"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,35 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaImportVariable": {
|
||||
"NoVariablesAvailable": "无可用变量",
|
||||
"Success": "成功"
|
||||
}
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaImportVariable": {
|
||||
"NoVariablesAvailable": "无可用变量",
|
||||
"Success": "成功"
|
||||
},
|
||||
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaMasterProperty": {
|
||||
"OpcUrl": "OpcUrl",
|
||||
"UserName": "登录账号",
|
||||
"Password": "登录密码",
|
||||
"CheckDomain": "检查域",
|
||||
"UpdateRate": "推送间隔",
|
||||
"ActiveSubscribe": "订阅",
|
||||
"GroupSize": "分组大小",
|
||||
"DeadBand": "死区",
|
||||
"KeepAliveInterval": "心跳间隔(ms)",
|
||||
"UseSecurity": "安全策略",
|
||||
"LoadType": "加载服务端数据类型",
|
||||
"SourceTimestampEnable": "服务端时间戳"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaServerProperty": {
|
||||
"IsAllVariable": "选择全部变量",
|
||||
"OpcUaStringUrl": "服务地址",
|
||||
"BigTextSubjectName": "SubjectName",
|
||||
"BigTextApplicationUri": "ApplicationUri",
|
||||
"SecurityPolicy": "安全策略",
|
||||
"AutoAcceptUntrustedCertificates": "自动接受不受信任的证书"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaServerVariableProperty": {
|
||||
"DataType": "数据类型"
|
||||
},
|
||||
"ThingsGateway.Plugin.OpcUa.OpcUaServer": {
|
||||
"CanStartService": "无法启动服务"
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using NewLife.Threading;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using Opc.Ua;
|
||||
@@ -37,7 +39,7 @@ public class OpcUaMaster : CollectBase
|
||||
public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcUaMaster);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override CollectPropertyBase DriverProperties => _driverProperties;
|
||||
public override CollectPropertyBase CollectProperties => _driverProperties;
|
||||
|
||||
public override Type DriverUIType => null;
|
||||
|
||||
@@ -61,14 +63,14 @@ public class OpcUaMaster : CollectBase
|
||||
public override void Init(IChannel? channel = null)
|
||||
{
|
||||
//载入配置
|
||||
OpcUaConfig config = new()
|
||||
OpcUaProperty config = new()
|
||||
{
|
||||
OpcUrl = _driverProperties.OpcUrl,
|
||||
UpdateRate = _driverProperties.UpdateRate,
|
||||
DeadBand = _driverProperties.DeadBand,
|
||||
GroupSize = _driverProperties.GroupSize,
|
||||
KeepAliveInterval = _driverProperties.KeepAliveInterval,
|
||||
IsUseSecurity = _driverProperties.IsUseSecurity,
|
||||
UseSecurity = _driverProperties.UseSecurity,
|
||||
ActiveSubscribe = _driverProperties.ActiveSubscribe,
|
||||
UserName = _driverProperties.UserName,
|
||||
Password = _driverProperties.Password,
|
||||
@@ -78,12 +80,11 @@ public class OpcUaMaster : CollectBase
|
||||
if (_plc == null)
|
||||
{
|
||||
_plc = new();
|
||||
_plc.OpcStatusChange += _plc_OpcStatusChange;
|
||||
_plc.LogEvent += _plc_LogEvent;
|
||||
_plc.DataChangedHandler += DataChangedHandler;
|
||||
}
|
||||
_plc.OpcUaConfig = config;
|
||||
_plc.OpcUaProperty = config;
|
||||
|
||||
base.Init(channel);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -96,12 +97,6 @@ public class OpcUaMaster : CollectBase
|
||||
await base.ProtectedBeforStartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override Task ProtectedAfterStopAsync()
|
||||
{
|
||||
_plc?.Disconnect();
|
||||
return base.ProtectedAfterStopAsync();
|
||||
}
|
||||
|
||||
protected override string GetAddressDescription()
|
||||
{
|
||||
return _plc?.GetAddressDescription();
|
||||
@@ -182,12 +177,12 @@ public class OpcUaMaster : CollectBase
|
||||
item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString();
|
||||
}
|
||||
}
|
||||
LogMessage.Trace($"{ToString()} 状态变化:{Environment.NewLine}{data.Item1} : {data.Item3}");
|
||||
LogMessage.Trace($"{ToString()} Change:{Environment.NewLine}{data.Item1} : {data.Item3}");
|
||||
}
|
||||
}
|
||||
if (result.Any(a => StatusCode.IsBad(a.Item2.StatusCode)))
|
||||
{
|
||||
return new OperResult<byte[]>($"读取失败");
|
||||
return new OperResult<byte[]>($"OPC quality bad");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -210,7 +205,7 @@ public class OpcUaMaster : CollectBase
|
||||
var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken);
|
||||
return result.ToDictionary(a =>
|
||||
{
|
||||
return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name;
|
||||
return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name!;
|
||||
}
|
||||
, a =>
|
||||
{
|
||||
@@ -218,7 +213,7 @@ public class OpcUaMaster : CollectBase
|
||||
return new OperResult(a.Value.Item2);
|
||||
else
|
||||
return new();
|
||||
});
|
||||
})!;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -235,11 +230,11 @@ public class OpcUaMaster : CollectBase
|
||||
if (IsConnected())
|
||||
{
|
||||
//更新设备活动时间
|
||||
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 0);
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(DateTimeUtil.TimerXNow, 999);
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, 999);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -255,7 +250,7 @@ public class OpcUaMaster : CollectBase
|
||||
if (_plc != null)
|
||||
{
|
||||
_plc.DataChangedHandler -= DataChangedHandler;
|
||||
_plc.OpcStatusChange -= _plc_OpcStatusChange;
|
||||
_plc.LogEvent -= _plc_LogEvent;
|
||||
|
||||
_plc.Disconnect();
|
||||
_plc.SafeDispose();
|
||||
@@ -263,9 +258,9 @@ public class OpcUaMaster : CollectBase
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void _plc_OpcStatusChange(object? sender, OpcUaStatusEventArgs e)
|
||||
private void _plc_LogEvent(byte level, object sender, string message, Exception ex)
|
||||
{
|
||||
LogMessage?.Log((LogLevel)e.LogLevel, null, e.Text, null);
|
||||
LogMessage?.Log((LogLevel)level, sender, message, ex);
|
||||
}
|
||||
|
||||
private void DataChangedHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) data)
|
||||
@@ -277,7 +272,7 @@ public class OpcUaMaster : CollectBase
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
LogMessage.Trace($"{ToString()} 状态变化: {Environment.NewLine} {data.variableNode.NodeId} : {data.jToken?.ToString()}");
|
||||
LogMessage.Trace($"{ToString()} Change: {Environment.NewLine} {data.variableNode.NodeId} : {data.jToken?.ToString()}");
|
||||
|
||||
if (!CurrentDevice.KeepRun)
|
||||
{
|
||||
@@ -286,7 +281,7 @@ public class OpcUaMaster : CollectBase
|
||||
//尝试固定点位的数据类型
|
||||
var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
|
||||
|
||||
var itemReads = CurrentDevice.VariableRunTimes.Where(it => it.RegisterAddress == data.variableNode.NodeId).ToList();
|
||||
var itemReads = CurrentDevice.VariableRunTimes.Values.Where(it => it.RegisterAddress == data.variableNode.NodeId);
|
||||
|
||||
object value;
|
||||
if (data.jToken is JValue jValue)
|
||||
|
@@ -20,73 +20,73 @@ public class OpcUaMasterProperty : CollectPropertyBase
|
||||
/// <summary>
|
||||
/// 连接Url
|
||||
/// </summary>
|
||||
[DynamicProperty("连接Url", "")]
|
||||
[DynamicProperty]
|
||||
public string OpcUrl { get; set; } = "opc.tcp://127.0.0.1:49320";
|
||||
|
||||
/// <summary>
|
||||
/// 登录账号
|
||||
/// </summary>
|
||||
[DynamicProperty("登录账号", "为空时将采用匿名方式登录")]
|
||||
[DynamicProperty]
|
||||
public string? UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录密码
|
||||
/// </summary>
|
||||
[DynamicProperty("登录密码", "")]
|
||||
[DynamicProperty]
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检查域
|
||||
/// </summary>
|
||||
[DynamicProperty("检查域", "默认false")]
|
||||
[DynamicProperty]
|
||||
public bool CheckDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 安全策略
|
||||
/// </summary>
|
||||
[DynamicProperty("安全策略", "True为使用安全策略,False为无")]
|
||||
public bool IsUseSecurity { get; set; } = true;
|
||||
[DynamicProperty]
|
||||
public bool UseSecurity { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用SourceTime
|
||||
/// </summary>
|
||||
[DynamicProperty("使用SourceTime", "")]
|
||||
[DynamicProperty]
|
||||
public bool SourceTimestampEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 加载服务端数据类型
|
||||
/// </summary>
|
||||
[DynamicProperty("加载服务端数据类型")]
|
||||
[DynamicProperty]
|
||||
public bool LoadType { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 激活订阅
|
||||
/// </summary>
|
||||
[DynamicProperty("激活订阅", "")]
|
||||
[DynamicProperty]
|
||||
public bool ActiveSubscribe { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 更新频率
|
||||
/// </summary>
|
||||
[DynamicProperty("更新频率", "")]
|
||||
[DynamicProperty]
|
||||
public int UpdateRate { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// 死区
|
||||
/// </summary>
|
||||
[DynamicProperty("死区", "")]
|
||||
[DynamicProperty]
|
||||
public double DeadBand { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 最大组大小
|
||||
/// </summary>
|
||||
[DynamicProperty("最大组大小", "")]
|
||||
[DynamicProperty]
|
||||
public int GroupSize { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳频率
|
||||
/// </summary>
|
||||
[DynamicProperty("心跳频率", "")]
|
||||
[DynamicProperty]
|
||||
public int KeepAliveInterval { get; set; } = 3000;
|
||||
|
||||
public override int ConcurrentCount { get; set; } = 1;
|
||||
|
@@ -66,8 +66,8 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
AddRootNotifier(rootFolder);
|
||||
|
||||
//创建设备树
|
||||
var _geviceGroup = _businessBase.CurrentDevice.VariableRunTimes
|
||||
.GroupBy(a => { if (a.DeviceName.IsNullOrEmpty()) return "内存变量"; else return a.DeviceName; });
|
||||
var _geviceGroup = _businessBase.CurrentDevice.VariableRunTimes.Values
|
||||
.GroupBy(a => a.DeviceName);
|
||||
// 开始寻找设备信息,并计算一些节点信息
|
||||
foreach (var item in _geviceGroup)
|
||||
{
|
||||
@@ -284,7 +284,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
_businessBase.LogMessage.LogWarning(ex, "转化值错误");
|
||||
_businessBase.LogMessage.LogWarning(ex, "Conversion value error");
|
||||
success = false;
|
||||
newValue = value;
|
||||
}
|
||||
@@ -400,7 +400,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
/// <returns></returns>
|
||||
private NodeId DataNodeType(VariableRunTime variableRunTime)
|
||||
{
|
||||
var str = variableRunTime.GetPropertyValue(_businessBase.DeviceId, nameof(OpcUaServerVariableProperty.DataTypeEnum))?.Value ?? "";
|
||||
var str = variableRunTime.GetPropertyValue(_businessBase.DeviceId, nameof(OpcUaServerVariableProperty.DataType)) ?? "";
|
||||
Type tp;
|
||||
if (Enum.TryParse(str, out DataTypeEnum result))
|
||||
{
|
@@ -132,13 +132,13 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
// 当用户身份改变时请求。
|
||||
server.SessionManager.ImpersonateUser += SessionManager_ImpersonateUser;
|
||||
base.OnServerStarted(server);
|
||||
_businessBase.LogMessage.LogInformation("OPCUAServer启动成功");
|
||||
_businessBase.LogMessage.LogInformation("OPCUAServer Started");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnServerStarting(ApplicationConfiguration configuration)
|
||||
{
|
||||
_businessBase.LogMessage.LogInformation("OPCUAServer正在启动");
|
||||
_businessBase.LogMessage.LogInformation("OPCUAServer Starting");
|
||||
base.OnServerStarting(configuration);
|
||||
|
||||
// 由应用程序决定如何验证用户身份令牌。
|
||||
@@ -149,7 +149,7 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
/// <inheritdoc/>
|
||||
protected override void OnServerStopping()
|
||||
{
|
||||
_businessBase.LogMessage.LogInformation("OPCUAServer正在停止");
|
||||
_businessBase.LogMessage.LogInformation("OPCUAServer Stoping");
|
||||
base.OnServerStopping();
|
||||
}
|
||||
|
||||
@@ -251,8 +251,8 @@ public partial class ThingsGatewayServer : StandardServer
|
||||
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenRejected,
|
||||
"Security cancellationToken is not a valid username cancellationToken. An empty password is not accepted.");
|
||||
}
|
||||
var _openApiUserService = _businessBase.ServiceScope.ServiceProvider.GetService<ISysUserService>();
|
||||
var userInfo = _openApiUserService.GetUserByAccountAsync(userName).ConfigureAwait(true).GetAwaiter().GetResult();//获取用户信息
|
||||
var sysUserService = App.RootServices.GetService<ISysUserService>();
|
||||
var userInfo = sysUserService.GetUserByAccountAsync(userName).ConfigureAwait(true).GetAwaiter().GetResult();//获取用户信息
|
||||
if (userInfo == null)
|
||||
{
|
||||
// construct translation object with default text.
|
@@ -10,15 +10,20 @@
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using NewLife.Threading;
|
||||
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Configuration;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Core.Extension;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.Gateway.Application.Generic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
@@ -47,11 +52,10 @@ public partial class OpcUaServer : BusinessBase
|
||||
|
||||
public override void Init(IChannel? channel = null)
|
||||
{
|
||||
base.Init(channel);
|
||||
if (_driverPropertys.IsAllVariable)
|
||||
{
|
||||
CurrentDevice.VariableRunTimes = GlobalData.AllVariables.ToList();
|
||||
CollectDevices = GlobalData.CollectDevices.ToList();
|
||||
CurrentDevice.VariableRunTimes = GlobalData.ReadOnlyVariables;
|
||||
CollectDevices = GlobalData.ReadOnlyCollectDevices;
|
||||
}
|
||||
|
||||
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(LogMessage);//默认返回true
|
||||
@@ -72,12 +76,15 @@ public partial class OpcUaServer : BusinessBase
|
||||
m_server = new(this);
|
||||
|
||||
CollectVariableRunTimes.Clear();
|
||||
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
CurrentDevice.VariableRunTimes.ForEach(a =>
|
||||
{
|
||||
VariableValueChange(a);
|
||||
a.VariableValueChange += VariableValueChange;
|
||||
VariableValueChange(a.Value, a.Value.Adapt<VariableData>());
|
||||
});
|
||||
Localizer = App.CreateLocalizerByType(typeof(OpcUaServer))!;
|
||||
}
|
||||
protected IStringLocalizer Localizer { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsConnected() => m_server?.CurrentInstance.CurrentState == Opc.Ua.ServerState.Running;
|
||||
@@ -85,20 +92,13 @@ public partial class OpcUaServer : BusinessBase
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
CurrentDevice?.VariableRunTimes?.ForEach(a =>
|
||||
{
|
||||
a.VariableValueChange -= VariableValueChange;
|
||||
});
|
||||
GlobalData.VariableValueChangeEvent -= VariableValueChange;
|
||||
m_application?.Stop();
|
||||
m_server?.SafeDispose();
|
||||
CollectVariableRunTimes.Clear();
|
||||
CollectVariableRunTimes?.Clear();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override Task ProtectedAfterStopAsync()
|
||||
{
|
||||
m_application.Stop();
|
||||
return base.ProtectedAfterStopAsync();
|
||||
}
|
||||
|
||||
protected override async Task ProtectedBeforStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -115,11 +115,11 @@ public partial class OpcUaServer : BusinessBase
|
||||
if (IsConnected())
|
||||
{
|
||||
//更新设备活动时间
|
||||
CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 0);
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 999);
|
||||
CurrentDevice.SetDeviceStatus(TimerX.Now, 999);
|
||||
try
|
||||
{
|
||||
await m_application.CheckApplicationInstanceCertificate(false, 0, 1200);
|
||||
@@ -129,7 +129,7 @@ public partial class OpcUaServer : BusinessBase
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage.LogWarning(ex, "无法启动服务");
|
||||
LogMessage.LogWarning(ex, Localizer["CanStartService"]);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
@@ -329,10 +329,11 @@ public partial class OpcUaServer : BusinessBase
|
||||
return config;
|
||||
}
|
||||
|
||||
private void VariableValueChange(VariableRunTime variableRunTime)
|
||||
private void VariableValueChange(VariableRunTime variableRunTime, VariableData variableData)
|
||||
{
|
||||
if (!CurrentDevice.KeepRun)
|
||||
return;
|
||||
CollectVariableRunTimes.Enqueue(variableRunTime.Adapt<VariableData>());
|
||||
if (DisposedValue) return;
|
||||
CollectVariableRunTimes.Enqueue(variableData);
|
||||
}
|
||||
}
|
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Opc.Ua;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
@@ -17,36 +19,38 @@ namespace ThingsGateway.Plugin.OpcUa;
|
||||
/// <inheritdoc/>
|
||||
public class OpcUaServerProperty : BusinessPropertyBase
|
||||
{
|
||||
[DynamicProperty("是否选择全部变量", "")]
|
||||
[DynamicProperty]
|
||||
public bool IsAllVariable { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 服务地址
|
||||
/// </summary>
|
||||
[DynamicProperty("服务地址", "分号分割数组,可设置多个url")]
|
||||
[DynamicProperty(Remark = "分号分割数组,可设置多个url")]
|
||||
public string OpcUaStringUrl { get; set; } = "opc.tcp://127.0.0.1:49321";
|
||||
|
||||
/// <summary>
|
||||
/// SubjectName
|
||||
/// </summary>
|
||||
[DynamicProperty("SubjectName", "")]
|
||||
[DynamicProperty()]
|
||||
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 1)]
|
||||
public string BigTextSubjectName { get; set; } = "CN=ThingsGateway OPCUAServer, C=CN, S=GUANGZHOU, O=ThingsGateway, DC=" + System.Net.Dns.GetHostName();
|
||||
|
||||
/// <summary>
|
||||
/// ApplicationUri
|
||||
/// </summary>
|
||||
[DynamicProperty("ApplicationUri", "")]
|
||||
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 1)]
|
||||
[DynamicProperty()]
|
||||
public string BigTextApplicationUri { get; set; } = Utils.Format(@"urn:{0}:thingsgatewayopcuaserver", System.Net.Dns.GetHostName());
|
||||
|
||||
/// <summary>
|
||||
/// 安全策略
|
||||
/// </summary>
|
||||
[DynamicProperty("安全策略", "")]
|
||||
[DynamicProperty()]
|
||||
public bool SecurityPolicy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接受不受信任的证书
|
||||
/// </summary>
|
||||
[DynamicProperty("自动接受不受信任的证书", "")]
|
||||
[DynamicProperty()]
|
||||
public bool AutoAcceptUntrustedCertificates { get; set; } = true;
|
||||
}
|
@@ -18,6 +18,6 @@ public class OpcUaServerVariableProperty : VariablePropertyBase
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
[DynamicProperty("数据类型", "")]
|
||||
public DataTypeEnum DataTypeEnum { get; set; } = DataTypeEnum.Object;
|
||||
[DynamicProperty()]
|
||||
public DataTypeEnum DataType { get; set; } = DataTypeEnum.Object;
|
||||
}
|
@@ -1,125 +0,0 @@
|
||||
@namespace ThingsGateway.Debug
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.Reflection;
|
||||
|
||||
@using Opc.Ua
|
||||
@using ThingsGateway.Foundation.OpcUa;
|
||||
@using Masa.Blazor
|
||||
@inherits BasePopupComponentBase
|
||||
|
||||
<PModal Persistent Title=@Plc?.OpcUaConfig?.ToString() Value="Visible" Width=1200 ValueChanged=@((a=>{if(!a)
|
||||
ClosePopupAsync();})) OnSave="()=>{}" SaveText="关闭">
|
||||
|
||||
<SaveContent Context="save">
|
||||
@{
|
||||
#if Plugin
|
||||
<MMenu OffsetY Context="menu">
|
||||
<ActivatorContent>
|
||||
<MButton @attributes="@menu.Attrs" Color="primary" Class="my-1 mx-2 " Disabled="isDownLoading" Loading="isDownLoading">
|
||||
导出
|
||||
<AppChevronDown></AppChevronDown>
|
||||
</MButton>
|
||||
</ActivatorContent>
|
||||
<ChildContent>
|
||||
<MList>
|
||||
<MListItem Color="primary" OnClick="DownDeviceExport" Disabled="isDownLoading" > 导出到excel </MListItem>
|
||||
<MListItem Color="primary" OnClick="DeviceImport" Disabled="isDownLoading" >导入到系统</MListItem>
|
||||
</MList>
|
||||
|
||||
</ChildContent>
|
||||
</MMenu>
|
||||
#endif
|
||||
}
|
||||
</SaveContent>
|
||||
|
||||
<ChildContent>
|
||||
@if (Visible)
|
||||
{
|
||||
|
||||
<MRow Class="pa-4"
|
||||
Justify="JustifyTypes.SpaceBetween">
|
||||
<MCol Cols="12" Md="6">
|
||||
<MTreeview @bind-Value=Selected @bind-Active=Actived SelectionType="SelectionType.Leaf"
|
||||
Items=Nodes TItem=OpcUaTagModel
|
||||
Color="warning" Selectable Activatable OpenOnClick
|
||||
LoadChildren=PopulateBranchAsync
|
||||
TKey=ReferenceDescription ItemKey="r=>r.Tag"
|
||||
Style="height:500px;overflow-y:auto;"
|
||||
ItemText="r=>r.Name"
|
||||
ItemChildren="r=>r.Nodes">
|
||||
|
||||
</MTreeview>
|
||||
|
||||
</MCol>
|
||||
<MCol Cols="12" Md="6">
|
||||
<MCardText Style="height:500px;overflow-y:auto;">
|
||||
@if (Actived?.Count == 0 || Actived?.FirstOrDefault()?.NodeId==null)
|
||||
{
|
||||
<div key="title"
|
||||
class="text-h6 font-weight-light grey--text pa-4 text-center">
|
||||
选择左侧节点
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ScrollXTransition>
|
||||
<MSheet Outlined Class="ma-0 pa-1">
|
||||
<MRow Align="AlignTypes.Center">
|
||||
<MCol>
|
||||
<MListItem>
|
||||
<ItemContent>
|
||||
<MListItemContent>
|
||||
<MListItemTitle>NodeId </MListItemTitle>
|
||||
</MListItemContent>
|
||||
</ItemContent>
|
||||
</MListItem>
|
||||
</MCol>
|
||||
<MDivider Vertical Center />
|
||||
<MCol>
|
||||
<MLabel Class=@($"ma-1")> @Actived.FirstOrDefault().NodeId </MLabel>
|
||||
</MCol>
|
||||
|
||||
</MRow>
|
||||
</MSheet>
|
||||
@foreach (var item in nodeAttributes)
|
||||
{
|
||||
<MSheet Outlined Class="ma-0 pa-1">
|
||||
<MRow Align="AlignTypes.Center">
|
||||
<MCol>
|
||||
<MListItem>
|
||||
<ItemContent>
|
||||
<MListItemContent>
|
||||
<MListItemTitle>@item.Name </MListItemTitle>
|
||||
</MListItemContent>
|
||||
</ItemContent>
|
||||
</MListItem>
|
||||
</MCol>
|
||||
<MDivider Vertical Center />
|
||||
<MCol>
|
||||
<MLabel Class=@($"{(StatusCode.IsBad(item.StatusCode)?"red--text":"green--text")} ma-1")> @item.Value </MLabel>
|
||||
</MCol>
|
||||
|
||||
</MRow>
|
||||
</MSheet>
|
||||
}
|
||||
|
||||
</ScrollXTransition>
|
||||
|
||||
}
|
||||
</MCardText>
|
||||
</MCol>
|
||||
|
||||
<MDivider Vertical></MDivider>
|
||||
|
||||
</MRow>
|
||||
|
||||
<MOverlay Absolute Value="overlay" Opacity="0.8">
|
||||
<span class="green--text text--darken-2">
|
||||
@("正在获取OpcUa节点...")
|
||||
</span>
|
||||
<MProgressCircular Indeterminate Width=6 Size="30"></MProgressCircular>
|
||||
</MOverlay>
|
||||
}
|
||||
</ChildContent>
|
||||
</PModal>
|
@@ -1,425 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BlazorComponent;
|
||||
using BootstrapBlazor.Components;
|
||||
using Masa.Blazor;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Opc.Ua;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Foundation.OpcUa;
|
||||
|
||||
#if Plugin
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.Plugin.OpcUa;
|
||||
|
||||
#endif
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// 导入变量
|
||||
/// </summary>
|
||||
public partial class OpcUaImportVariable : BasePopupComponentBase
|
||||
{
|
||||
private List<ReferenceDescription> actived = new();
|
||||
|
||||
private OPCNodeAttribute[] nodeAttributes;
|
||||
|
||||
private List<OpcUaTagModel> Nodes = new();
|
||||
|
||||
private bool overlay = true;
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IStringLocalizer<OpcUaImportVariable>? Localizer { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private ToastService? ToastService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Opc对象
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public ThingsGateway.Foundation.OpcUa.OpcUaMaster Plc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示子变量
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsShowSubvariable { get; set; }
|
||||
|
||||
private List<ReferenceDescription> Actived
|
||||
{
|
||||
get => actived;
|
||||
set
|
||||
{
|
||||
if (actived?.FirstOrDefault() != value?.FirstOrDefault() && value?.Count > 0)
|
||||
{
|
||||
actived = value;
|
||||
if (actived.FirstOrDefault().NodeId != null)
|
||||
nodeAttributes = Plc.ReadNoteAttributes(actived.FirstOrDefault().NodeId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ReferenceDescription> Selected { get; set; } = new();
|
||||
|
||||
#if Plugin
|
||||
|
||||
private bool isDownLoading;
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备与变量列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<(Channel, Device, List<Variable>)> GetImportVariableList()
|
||||
{
|
||||
var channel = GetImportChannel();
|
||||
var device = GetImportDevice(channel.Id);
|
||||
foreach (var node in Selected.ToList())
|
||||
{
|
||||
List<OpcUaTagModel> nodes = await PopulateBranchAsync((NodeId)node.NodeId, true, IsShowSubvariable);
|
||||
if (nodes.Count > 0)
|
||||
{
|
||||
Selected.AddRange(nodes.SelectMany(a => a.GetAllTags()).Select(a => a.Tag).Where(a => a != null).ToList());
|
||||
}
|
||||
}
|
||||
var data = (await SelectAsync(Selected, async a =>
|
||||
{
|
||||
var nodeClass = (await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.NodeClass)).FirstOrDefault().Value.ToString();
|
||||
if (nodeClass == nameof(NodeClass.Variable))
|
||||
{
|
||||
ProtectTypeEnum level = ProtectTypeEnum.ReadOnly;
|
||||
DataTypeEnum dataTypeEnum = DataTypeEnum.Object;
|
||||
try
|
||||
{
|
||||
var userAccessLevel = (AccessLevelType)(await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.UserAccessLevel)).FirstOrDefault().Value;
|
||||
level = (userAccessLevel.HasFlag(AccessLevelType.CurrentRead)) ?
|
||||
userAccessLevel.HasFlag(AccessLevelType.CurrentWrite) ?
|
||||
ProtectTypeEnum.ReadWrite : ProtectTypeEnum.ReadOnly : ProtectTypeEnum.WriteOnly;
|
||||
|
||||
var dataTypeId = (Opc.Ua.NodeId)(await Plc.ReadNoteAttributeAsync(a.NodeId.ToString(), Opc.Ua.Attributes.DataType)).FirstOrDefault().Value;
|
||||
var dataType = Opc.Ua.TypeInfo.GetSystemType(dataTypeId, Plc.Session.Factory);
|
||||
var result = dataType != null && Enum.TryParse<DataTypeEnum>(dataType.Name, out dataTypeEnum);
|
||||
if (!result)
|
||||
{
|
||||
dataTypeEnum = DataTypeEnum.Object;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
var id = Yitter.IdGenerator.YitIdHelper.NextId();
|
||||
|
||||
return new Variable()
|
||||
{
|
||||
Name = a.DisplayName.Text + "-" + id,
|
||||
RegisterAddress = a.NodeId.ToString(),
|
||||
DeviceId = device.Id,
|
||||
DataType = dataTypeEnum,
|
||||
Enable = true,
|
||||
Id = id,
|
||||
ProtectType = level,
|
||||
IntervalTime = 1000,
|
||||
RpcWriteEnable = true,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
})).Where(a => a != null).ToList();
|
||||
return (channel, device, data);
|
||||
}
|
||||
|
||||
private Device GetImportDevice(long channelId)
|
||||
{
|
||||
var id = Yitter.IdGenerator.YitIdHelper.NextId();
|
||||
|
||||
var data = new Device()
|
||||
{
|
||||
Name = Plc.OpcUaConfig.OpcUrl + "-" + id,
|
||||
Id = id,
|
||||
ChannelId = channelId,
|
||||
Enable = true,
|
||||
DevicePropertys = new(),
|
||||
PluginName = "ThingsGateway.Plugin.OpcUa.OpcUaMaster",
|
||||
};
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.OpcUrl),Plc.OpcUaConfig.OpcUrl);
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UserName), Plc.OpcUaConfig.UserName);
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.Password), Plc.OpcUaConfig.Password);
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.CheckDomain), Plc.OpcUaConfig.CheckDomain.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.LoadType), Plc.OpcUaConfig.LoadType.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.IsUseSecurity), Plc.OpcUaConfig.IsUseSecurity.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.ActiveSubscribe), true.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.DeadBand), Plc.OpcUaConfig.DeadBand.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.GroupSize), Plc.OpcUaConfig.GroupSize.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.UpdateRate), Plc.OpcUaConfig.UpdateRate.ToString());
|
||||
data.DevicePropertys.Add(nameof(OpcUaMasterProperty.KeepAliveInterval), Plc.OpcUaConfig.KeepAliveInterval.ToString());
|
||||
return data;
|
||||
}
|
||||
|
||||
private Channel GetImportChannel()
|
||||
{
|
||||
var id = Yitter.IdGenerator.YitIdHelper.NextId();
|
||||
var data = new Channel()
|
||||
{
|
||||
Name = Plc.OpcUaConfig.OpcUrl + "-" + id,
|
||||
Id = id,
|
||||
Enable = true,
|
||||
ChannelType = ChannelTypeEnum.Other,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
private async Task DeviceImport()
|
||||
{
|
||||
isDownLoading = true;
|
||||
await InvokeStateHasChangedAsync();
|
||||
try
|
||||
{
|
||||
var data = await GetImportVariableList();
|
||||
if (data.Item3 == null || data.Item3?.Count == 0)
|
||||
{
|
||||
await ToastService.Warning(Localizer["NoVariablesAvailable"], Localizer["NoVariablesAvailable"]);
|
||||
return;
|
||||
}
|
||||
await App.RootServices.GetRequiredService<IChannelService>().SaveChannelAsync(data.Item1, ItemChangedType.Add);
|
||||
await App.RootServices.GetRequiredService<IDeviceService>().SaveDeviceAsync(data.Item2, ItemChangedType.Add);
|
||||
await App.RootServices.GetRequiredService<IVariableService>().AddBatchAsync(data.Item3, ItemChangedType.Add);
|
||||
await ToastService.Success(Localizer["Success"], Localizer["Success"]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isDownLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownDeviceExport()
|
||||
{
|
||||
isDownLoading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
try
|
||||
{
|
||||
var data = await GetImportVariableList();
|
||||
if (data.Item3 == null || data.Item3?.Count == 0)
|
||||
{
|
||||
await ToastService.Warning(Localizer["NoVariablesAvailable"], Localizer["NoVariablesAvailable"]);
|
||||
return;
|
||||
}
|
||||
|
||||
await DownChannelExportAsync(data.Item1);
|
||||
await DownDeviceExportAsync(data.Item2, data.Item1.Name);
|
||||
await DownDeviceVariableExportAsync(data.Item3, data.Item2.Name);
|
||||
await ToastService.Success(Localizer["Success"], Localizer["Success"]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isDownLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出到excel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task DownChannelExportAsync(Channel data)
|
||||
{
|
||||
using var memoryStream = await App.RootServices.GetRequiredService<IChannelService>().ExportMemoryStream(new List<Channel>() { data });
|
||||
//await AppService.DownXlsxAsync(memoryStream, "通道导出");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出到excel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task DownDeviceExportAsync(Device data, string channelName)
|
||||
{
|
||||
using var memoryStream = await App.RootServices.GetRequiredService<IDeviceService>().ExportMemoryStream(new List<Device>() { data }, PluginTypeEnum.Collect, channelName);
|
||||
//await AppService.DownXlsxAsync(memoryStream, "设备导出");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出到excel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task DownDeviceVariableExportAsync(List<Variable> data, string devName)
|
||||
{
|
||||
using var memoryStream = await App.RootServices.GetRequiredService<IVariableService>().ExportMemoryStream(data, devName);
|
||||
//await AppService.DownXlsxAsync(memoryStream, "变量导出");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Nodes = await PopulateBranchAsync(ObjectIds.ObjectsFolder, isShowSubvariable: IsShowSubvariable);
|
||||
overlay = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task<ReferenceDescriptionCollection> GetReferenceDescriptionCollectionAsync(NodeId sourceId)
|
||||
{
|
||||
BrowseDescription nodeToBrowse1 = new()
|
||||
{
|
||||
NodeId = sourceId,
|
||||
BrowseDirection = BrowseDirection.Forward,
|
||||
ReferenceTypeId = ReferenceTypeIds.Aggregates,
|
||||
IncludeSubtypes = true,
|
||||
NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.View | NodeClass.VariableType | NodeClass.DataType),
|
||||
ResultMask = (uint)BrowseResultMask.All
|
||||
};
|
||||
|
||||
BrowseDescription nodeToBrowse2 = new()
|
||||
{
|
||||
NodeId = sourceId,
|
||||
BrowseDirection = BrowseDirection.Forward,
|
||||
ReferenceTypeId = ReferenceTypeIds.Organizes,
|
||||
IncludeSubtypes = true,
|
||||
NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.View | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType | NodeClass.DataType),
|
||||
ResultMask = (uint)BrowseResultMask.All
|
||||
};
|
||||
|
||||
BrowseDescriptionCollection nodesToBrowse = new()
|
||||
{
|
||||
nodeToBrowse1,
|
||||
nodeToBrowse2
|
||||
};
|
||||
|
||||
ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(Plc.Session, nodesToBrowse, false);
|
||||
return references;
|
||||
}
|
||||
|
||||
private async Task PopulateBranchAsync(OpcUaTagModel model)
|
||||
{
|
||||
var sourceId = (NodeId)model.Tag.NodeId;
|
||||
model.Nodes = await PopulateBranchAsync(sourceId, isShowSubvariable: IsShowSubvariable);
|
||||
}
|
||||
|
||||
private async Task<List<OpcUaTagModel>> PopulateBranchAsync(NodeId sourceId, bool isAll = false, bool isShowSubvariable = false)
|
||||
{
|
||||
if (!Plc.Connected)
|
||||
{
|
||||
return new() { new() { Name = "未完成连接", Tag = new(), Nodes = null } };
|
||||
}
|
||||
List<OpcUaTagModel> nodes = new()
|
||||
{
|
||||
new OpcUaTagModel() { Name = "Browsering..." }
|
||||
};
|
||||
|
||||
ReferenceDescriptionCollection references = await GetReferenceDescriptionCollectionAsync(sourceId);
|
||||
List<OpcUaTagModel> list = new();
|
||||
if (references != null)
|
||||
{
|
||||
for (int ii = 0; ii < references.Count; ii++)
|
||||
{
|
||||
ReferenceDescription target = references[ii];
|
||||
OpcUaTagModel child = new()
|
||||
{
|
||||
Name = Utils.Format("{0}", target),
|
||||
Tag = target
|
||||
};
|
||||
if (isShowSubvariable || target.NodeClass != NodeClass.Variable)
|
||||
{
|
||||
var data = await GetReferenceDescriptionCollectionAsync((NodeId)target.NodeId);
|
||||
if (data != null && data.Count > 0)
|
||||
{
|
||||
if (isAll)
|
||||
child.Nodes = await PopulateBranchAsync((NodeId)target.NodeId, isShowSubvariable: IsShowSubvariable);
|
||||
else
|
||||
child.Nodes = new();
|
||||
}
|
||||
else
|
||||
{
|
||||
child.Nodes = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
child.Nodes = null;
|
||||
}
|
||||
|
||||
////if (target.NodeClass != NodeClass.Variable) //这个判断注释后会让子节点也是变量的情况下无法加载
|
||||
//{
|
||||
// var data = await GetReferenceDescriptionCollectionAsync((NodeId)target.NodeId);
|
||||
// if (data != null && data.Count > 0)
|
||||
// {
|
||||
// if (isAll)
|
||||
// child.Nodes = await PopulateBranchAsync((NodeId)target.NodeId);
|
||||
// else
|
||||
// child.Nodes = new();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// child.Nodes = null;
|
||||
// }
|
||||
//}
|
||||
////else
|
||||
////{
|
||||
//// child.Nodes = null;
|
||||
////}
|
||||
list.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
List<OpcUaTagModel> listNode = list;
|
||||
|
||||
nodes.Clear();
|
||||
nodes.AddRange(listNode.ToArray());
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private async Task<TResult[]> SelectAsync<T, TResult>(IEnumerable<T> source, Func<T, Task<TResult>> selector)
|
||||
{
|
||||
return await Task.WhenAll(source.Select(selector));
|
||||
}
|
||||
|
||||
internal class OpcUaTagModel
|
||||
{
|
||||
internal string Name { get; set; }
|
||||
internal string NodeId => (Tag.NodeId).ToString();
|
||||
internal List<OpcUaTagModel> Nodes { get; set; } = new();
|
||||
internal ReferenceDescription Tag { get; set; }
|
||||
|
||||
public List<OpcUaTagModel> GetAllTags()
|
||||
{
|
||||
List<OpcUaTagModel> allTags = new();
|
||||
GetAllTagsRecursive(this, allTags);
|
||||
return allTags;
|
||||
}
|
||||
|
||||
private void GetAllTagsRecursive(OpcUaTagModel parentTag, List<OpcUaTagModel> allTags)
|
||||
{
|
||||
allTags.Add(parentTag);
|
||||
|
||||
if (parentTag.Nodes != null)
|
||||
foreach (OpcUaTagModel childTag in parentTag.Nodes)
|
||||
{
|
||||
GetAllTagsRecursive(childTag, allTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,75 +1,103 @@
|
||||
@page "/OpcUaMaster"
|
||||
@using BootstrapBlazor.Components
|
||||
@using ThingsGateway.Core.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@namespace ThingsGateway.Debug
|
||||
@using ThingsGateway.Foundation.OpcUa
|
||||
@using TouchSocket.Core
|
||||
@inherits BaseComponentBase
|
||||
|
||||
<OpcUaMasterConnectPage @ref=opcUaMasterConnectPage OnConnectClick=OnConnectClick></OpcUaMasterConnectPage>
|
||||
|
||||
<AdapterDebugPage LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText" Height=@($"calc(100vh - {BlazorAppService.DefaultHeight+400}px)") ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
|
||||
|
||||
<ReadWriteContent>
|
||||
|
||||
<MContainer>
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
|
||||
<MTooltip Right Context="tip">
|
||||
<ActivatorContent>
|
||||
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
|
||||
Label=@AppService.I18n.T("Register Address") @attributes="@tip.Attrs" @bind-Value=@RegisterAddress />
|
||||
</ActivatorContent>
|
||||
<ChildContent>
|
||||
<span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span>
|
||||
</ChildContent>
|
||||
</MTooltip>
|
||||
|
||||
|
||||
<MCol Align="AlignTypes.Center">
|
||||
<MButton Class="mx-1 my-1" Color="primary" OnClick="Add">
|
||||
@AppService.I18n.T("添加")
|
||||
<div style="height:100%; width:100%;">
|
||||
|
||||
</MButton>
|
||||
<MButton Class="mx-1 my-1" Color="primary" OnClick="Remove">
|
||||
@AppService.I18n.T("移除")
|
||||
@if (_plc?.OpcUaProperty != null)
|
||||
{
|
||||
<Card class="mt-2">
|
||||
<BodyTemplate>
|
||||
|
||||
</MButton>
|
||||
<MButton Class="ma-1" Color="primary" OnClick="ReadAsync">
|
||||
@AppService.I18n.T("读取")
|
||||
</MButton>
|
||||
|
||||
</MCol>
|
||||
</MRow>
|
||||
<EditorForm Model="_plc.OpcUaProperty" AutoGenerateAllItem=false RowType="RowType.Inline" ItemsPerRow="4" ShowLabelTooltip="true" ShowLabel="true">
|
||||
<FieldItems>
|
||||
|
||||
|
||||
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
<EditorItem @bind-Field=context.CheckDomain />
|
||||
<EditorItem @bind-Field=context.UseSecurity />
|
||||
<EditorItem @bind-Field=context.ActiveSubscribe />
|
||||
<EditorItem @bind-Field=context.GroupSize />
|
||||
<EditorItem @bind-Field=context.UpdateRate />
|
||||
<EditorItem @bind-Field=context.DeadBand />
|
||||
<EditorItem @bind-Field=context.Password />
|
||||
<EditorItem @bind-Field=context.OpcUrl />
|
||||
<EditorItem @bind-Field=context.UserName />
|
||||
<EditorItem @bind-Field=context.LoadType />
|
||||
<EditorItem @bind-Field=context.KeepAliveInterval />
|
||||
|
||||
<MTextarea Class="pa-1" Style="max-width:200px" Dense Outlined HideDetails="@("auto")"
|
||||
Label=@AppService.I18n.T("WriteValue") @bind-Value=@WriteValue />
|
||||
<MCol Align="AlignTypes.Center">
|
||||
<MButton Class="ma-1" Color="primary" OnClick="WriteAsync">
|
||||
@AppService.I18n.T("Write")
|
||||
</MButton>
|
||||
</MCol>
|
||||
</MRow>
|
||||
</FieldItems>
|
||||
|
||||
<MRow Class="mt-8" Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
</EditorForm>
|
||||
|
||||
<MCheckbox Class="ma-1" HideDetails="@("auto")" @bind-Value=IsShowSubvariable Dense Label="是否显示子变量" />
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Connect">@OpcUaPropertyLocalizer["Connect"]</Button>
|
||||
|
||||
<MButton Class="ma-1" Color="primary" OnClick="ShowImport">
|
||||
@AppService.I18n.T("查看Opc节点空间")
|
||||
</MButton>
|
||||
<Button IsAsync Color="Color.Warning" OnClick="Disconnect">@OpcUaPropertyLocalizer["Disconnect"]</Button>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
</MRow>
|
||||
}
|
||||
|
||||
<AdapterDebugComponent Plc=null LogPath=@LogPath @ref=AdapterDebugComponent ShowDefaultOtherContent=false ShowDefaultReadWriteContent=false>
|
||||
|
||||
<ReadWriteContent>
|
||||
|
||||
<BootstrapInput @bind-Value=@RegisterAddress
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
|
||||
</MContainer>
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
|
||||
</ReadWriteContent>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Add">@OpcUaPropertyLocalizer["Add"]</Button>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="Remove">@OpcUaPropertyLocalizer["Remove"]</Button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@OpcUaPropertyLocalizer["Read"]</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
|
||||
ShowLabel="true" />
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@OpcUaPropertyLocalizer["Write"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="ShowImport">@OpcUaPropertyLocalizer["ShowImport"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ReadWriteContent>
|
||||
|
||||
|
||||
</AdapterDebugComponent>
|
||||
</div>
|
||||
|
||||
|
||||
</AdapterDebugPage>
|
||||
|
||||
|
@@ -8,126 +8,151 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Debug;
|
||||
using ThingsGateway.Foundation;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Foundation.OpcUa;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class OpcUaMaster : IDisposable
|
||||
{
|
||||
public partial class OpcUaMaster
|
||||
public LoggerGroup? LogMessage;
|
||||
private readonly OpcUaProperty OpcUaProperty = new();
|
||||
private ThingsGateway.Foundation.OpcUa.OpcUaMaster _plc;
|
||||
private string LogPath;
|
||||
private string RegisterAddress;
|
||||
private string WriteValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
~OpcUaMaster()
|
||||
{
|
||||
private ThingsGateway.Foundation.OpcUa.OpcUaMaster Plc => opcUaMasterConnectPage.Plc;
|
||||
private OpcUaMasterConnectPage opcUaMasterConnectPage;
|
||||
private string RegisterAddress;
|
||||
this.SafeDispose();
|
||||
}
|
||||
private AdapterDebugComponent AdapterDebugComponent { get; set; }
|
||||
[Inject]
|
||||
IStringLocalizer<OpcUaProperty> OpcUaPropertyLocalizer { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
_plc?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_plc = new ThingsGateway.Foundation.OpcUa.OpcUaMaster();
|
||||
_plc.OpcUaProperty = OpcUaProperty;
|
||||
|
||||
private string LogPath;
|
||||
private AdapterDebugComponent AdapterDebugPage { get; set; }
|
||||
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
var logger = TextFileLogger.Create(_plc.GetHashCode().ToLong().GetDebugLogPath());
|
||||
logger.LogLevel = LogLevel.Trace;
|
||||
LogMessage.AddLogger(logger);
|
||||
|
||||
private void OnConnectClick()
|
||||
_plc.LogEvent = (a, b, c, d) => LogMessage.Log((LogLevel)a, b, c, d);
|
||||
_plc.DataChangedHandler += (a) => LogMessage.Trace(a.ToJsonString());
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
_plc.Disconnect();
|
||||
LogPath = _plc?.GetHashCode().ToLong().GetDebugLogPath();
|
||||
await GetOpc().ConnectAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
catch (Exception ex)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
if (firstRender)
|
||||
{
|
||||
//载入配置
|
||||
StateHasChanged();
|
||||
}
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
}
|
||||
private void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
return base.OnInitializedAsync();
|
||||
_plc.Disconnect();
|
||||
}
|
||||
|
||||
private string WriteValue;
|
||||
|
||||
private async Task Add()
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Plc.Connected)
|
||||
await Plc.AddSubscriptionAsync(Guid.NewGuid().ToString(), new[] { RegisterAddress });
|
||||
else
|
||||
{
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
|
||||
}
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteAsync()
|
||||
private ThingsGateway.Foundation.OpcUa.OpcUaMaster GetOpc()
|
||||
{
|
||||
//载入配置
|
||||
_plc.OpcUaProperty = OpcUaProperty;
|
||||
return _plc;
|
||||
}
|
||||
|
||||
private async Task Add()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_plc.Connected)
|
||||
await _plc.AddSubscriptionAsync(Guid.NewGuid().ToString(), new[] { RegisterAddress });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReadAsync()
|
||||
{
|
||||
|
||||
if (_plc.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Plc.Connected)
|
||||
{
|
||||
var data = await Plc.WriteNodeAsync(
|
||||
new()
|
||||
{
|
||||
{RegisterAddress, WriteValue.GetJTokenFromString()}
|
||||
}
|
||||
);
|
||||
var data = await _plc.ReadJTokenValueAsync(new string[] { RegisterAddress });
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value.Item1)
|
||||
opcUaMasterConnectPage.LogMessage?.LogInformation(item.ToJsonString());
|
||||
else
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning(item.ToJsonString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
|
||||
}
|
||||
LogMessage?.LogInformation($" {data[0].Item1}:{data[0].Item3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning(ex, $"写入失败");
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowImport()
|
||||
{
|
||||
await PopupService.OpenAsync(typeof(OpcUaImportVariable), new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(OpcUaImportVariable.Plc),Plc},
|
||||
{nameof(OpcUaImportVariable.IsShowSubvariable),IsShowSubvariable},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsShowSubvariable;
|
||||
private void Remove()
|
||||
{
|
||||
if (_plc.Connected)
|
||||
_plc.RemoveSubscription("");
|
||||
}
|
||||
|
||||
private async Task ReadAsync()
|
||||
private async Task ShowImport()
|
||||
{
|
||||
// await PopupService.OpenAsync(typeof(OpcUaImportVariable), new Dictionary<string, object?>()
|
||||
//{
|
||||
// {nameof(OpcUaImportVariable._plc),_plc},
|
||||
//});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task WriteAsync()
|
||||
{
|
||||
if (_plc.Connected)
|
||||
{
|
||||
if (Plc.Connected)
|
||||
{
|
||||
try
|
||||
var data = await _plc.WriteNodeAsync(
|
||||
new()
|
||||
{
|
||||
var data = await Plc.ReadJTokenValueAsync(new string[] { RegisterAddress });
|
||||
{RegisterAddress, WriteValue.GetJTokenFromString()}
|
||||
}
|
||||
);
|
||||
|
||||
opcUaMasterConnectPage.LogMessage?.LogInformation($" {data[0].Item1}:{data[0].Item3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning(ex, $"读取失败");
|
||||
}
|
||||
}
|
||||
else
|
||||
foreach (var item in data)
|
||||
{
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
|
||||
if (item.Value.Item1)
|
||||
LogMessage?.LogInformation(item.ToJsonString());
|
||||
else
|
||||
LogMessage?.LogWarning(item.ToJsonString());
|
||||
}
|
||||
}
|
||||
|
||||
private void Remove()
|
||||
{
|
||||
if (Plc.Connected)
|
||||
Plc.RemoveSubscription("");
|
||||
else
|
||||
{
|
||||
opcUaMasterConnectPage.LogMessage?.LogWarning($"未连接");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
@namespace ThingsGateway.Debug
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using System.Collections.Concurrent;
|
||||
@using ThingsGateway.Foundation.OpcUa;
|
||||
@using ThingsGateway.Core.Extension;
|
||||
@using Masa.Blazor
|
||||
@inherits BaseComponentBase
|
||||
@implements IDisposable
|
||||
<MCard Class="ma-2">
|
||||
<MSubheader Style="max-height:32px">
|
||||
@AppService.I18n.T("Adapter Config")
|
||||
</MSubheader>
|
||||
<MContainer Class="ma-0">
|
||||
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.GroupSize) @bind-Value=@config.GroupSize />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.UpdateRate) @bind-Value=@config.UpdateRate />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.DeadBand) @bind-Value=@config.DeadBand />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:150px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.KeepAliveInterval) @bind-Value=@config.KeepAliveInterval />
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.OpcUrl) @bind-Value=@config.OpcUrl />
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.UserName) @bind-Value=@config.UserName />
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@config.Description(a=>a.Password) @bind-Value=@config.Password />
|
||||
|
||||
|
||||
|
||||
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@(AppService.I18n.T(config.Description(x => x.ActiveSubscribe))) @bind-Value=@config.ActiveSubscribe></MCheckbox>
|
||||
|
||||
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@(AppService.I18n.T(config.Description(x => x.IsUseSecurity))) @bind-Value=@config.IsUseSecurity></MCheckbox>
|
||||
|
||||
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@(AppService.I18n.T(config.Description(x => x.CheckDomain))) @bind-Value=@config.CheckDomain></MCheckbox>
|
||||
|
||||
<MCheckbox Class="ma-1" Style="max-width:200px" Dense HideDetails="@("auto")"
|
||||
Label=@(AppService.I18n.T(config.Description(x => x.LoadType))) @bind-Value=@config.LoadType></MCheckbox>
|
||||
|
||||
|
||||
<MButton Class="ma-1" OnClick=@Connect Color="primary">
|
||||
连接
|
||||
</MButton>
|
||||
|
||||
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
|
||||
断开
|
||||
</MButton>
|
||||
|
||||
|
||||
</MRow>
|
||||
</MContainer>
|
||||
</MCard>
|
||||
|
@@ -1,88 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.OpcUa;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class OpcUaMasterConnectPage : IDisposable
|
||||
{
|
||||
public ThingsGateway.Foundation.OpcUa.OpcUaMaster Plc;
|
||||
|
||||
private readonly OpcUaConfig config = new();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
Plc.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnConnectClick { get; set; }
|
||||
|
||||
public LoggerGroup? LogMessage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Plc = new ThingsGateway.Foundation.OpcUa.OpcUaMaster();
|
||||
Plc.OpcUaConfig = config;
|
||||
LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
var logger = TextFileLogger.Create(Plc.GetHashCode().ToLong().GetDebugLogPath());
|
||||
logger.LogLevel = LogLevel.Trace;
|
||||
LogMessage.AddLogger(logger);
|
||||
|
||||
Plc.OpcStatusChange += Plc_OpcStatusChange;
|
||||
Plc.DataChangedHandler += (a) => LogMessage.Trace((a.variableNode.NodeId, a.jToken).ToJsonString());
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private void Plc_OpcStatusChange(object? sender, OpcUaStatusEventArgs e)
|
||||
{
|
||||
LogMessage?.Log((LogLevel)e.LogLevel, null, e.Text, null);
|
||||
}
|
||||
|
||||
private async Task Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc.Disconnect();
|
||||
await GetOpc().ConnectAsync(CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
Plc.Disconnect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.Log(LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ThingsGateway.Foundation.OpcUa.OpcUaMaster GetOpc()
|
||||
{
|
||||
//载入配置
|
||||
Plc.OpcUaConfig = config;
|
||||
return Plc;
|
||||
}
|
||||
}
|
@@ -8,34 +8,22 @@
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
<Exec Command="@echo off
setlocal enabledelayedexpansion

set "targetFWS=net6.0 net7.0 net8.0"
for %25%25f in (%25targetFWS%25) do (
 set "dir=$(SolutionDir)bin\$(Configuration)\ThingsGateway.Web.Entry\%25%25f\Plugins\$(AssemblyName)"
 if not exist "!dir!" md "!dir!"
 copy "$(TargetDir)*OpcUa*.dll" "!dir!"
 copy "$(TargetDir)*Opc.Ua*.dll" "!dir!"
 copy "$(TargetDir)*System.Formats.Asn1*.dll" "!dir!"
)

endlocal


" />
|
||||
<Exec Command="@echo off
setlocal enabledelayedexpansion

set targetFWS=$(TargetFrameworks)
for %25%25f in (%25targetFWS%25) do (
 set "dir=$(SolutionDir)ThingsGateway.Server\bin\$(Configuration)\%25%25f\Plugins\$(AssemblyName)"
 if not exist "!dir!" md "!dir!"
 copy "$(TargetDir)*OpcUa*.dll" "!dir!"
 copy "$(TargetDir)*Opc.Ua*.dll" "!dir!"

)

endlocal



" />
|
||||
</Target>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\en-US.json" />
|
||||
<Content Remove="Locales\zh-CN.json" />
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Locales\en-US.json" />
|
||||
<EmbeddedResource Include="Locales\zh-CN.json" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.374.27" />
|
||||
|
||||
<ProjectReference Include="..\..\foundation\ThingsGateway.Foundation.OpcUa\src\ThingsGateway.Foundation.OpcUa.csproj" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.374.36" />
|
||||
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
@@ -5,10 +5,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using BlazorComponent
|
||||
@using Masa.Blazor
|
||||
@using Masa.Blazor.Presets
|
||||
@using ThingsGateway.Components;
|
||||
@using ThingsGateway.Razor;
|
||||
@using ThingsGateway.Core;
|
||||
@using System.Net.Http.Json
|
||||
@using System.IO;
|
||||
|
@@ -37,7 +37,7 @@
|
||||
|
||||
}
|
||||
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugPage HeaderText="HeaderText"></AdapterDebugComponent>
|
||||
<AdapterDebugComponent Plc=_plc LogPath=@LogPath @ref=AdapterDebugComponent HeaderText="HeaderText"></AdapterDebugComponent>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -30,7 +30,7 @@ public partial class SiemensS7Master : IDisposable
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
private AdapterDebugComponent AdapterDebugPage { get; set; }
|
||||
private AdapterDebugComponent AdapterDebugComponent { get; set; }
|
||||
|
||||
private ChannelData ChannelData { get; set; }
|
||||
|
||||
|
Reference in New Issue
Block a user