Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6594937d0a | ||
![]() |
64bc6084be | ||
![]() |
20434d5bd2 | ||
![]() |
9ecc9380e6 | ||
![]() |
a57c55080b | ||
![]() |
b8ca06c6be | ||
![]() |
5aff6461a1 | ||
![]() |
6cf53fefec | ||
![]() |
45132f3503 | ||
![]() |
f1e78a0e8a | ||
![]() |
0bf28ec275 | ||
![]() |
41f8412c97 | ||
![]() |
c535974362 | ||
![]() |
1860c5f215 | ||
![]() |
6d778b2d39 | ||
![]() |
f48b99c259 |
@@ -10,9 +10,6 @@
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using ThingsGateway.List;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
/// <summary>
|
||||
@@ -169,7 +166,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
|
||||
public void RemoveAllClientId()
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Updateable<VerificatInfo>().SetColumns("ClientIds", new ConcurrentList<long>().ToJsonNetString()).Where(a => a.Id >= 0).ExecuteCommand();
|
||||
db.Updateable<VerificatInfo>().SetColumns(a => a.ClientIds == null).Where(a => a.Id > 0).ExecuteCommand();
|
||||
VerificatInfoService.RemoveCache();
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,11 @@ public sealed class SqlSugarOption : ConnectionConfig
|
||||
/// </summary>
|
||||
public bool InitSeedData { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化数据库
|
||||
/// </summary>
|
||||
public bool InitDatabase { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表
|
||||
/// </summary>
|
||||
|
@@ -89,7 +89,7 @@ public class Startup : AppStartup
|
||||
DbContext.DbConfigs?.ForEach(it =>
|
||||
{
|
||||
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
|
||||
if (it.InitTable == true)
|
||||
if (it.InitDatabase == true)
|
||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||
});
|
||||
|
||||
|
@@ -324,13 +324,21 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
// 移除扩展空闲集合里面的超时项
|
||||
while (_free2.TryPeek(out var pi) && pi.LastTime < exp)
|
||||
{
|
||||
// 取出来销毁
|
||||
if (_free2.TryDequeue(out pi))
|
||||
// 取出来销毁。在并行操作中,此时返回可能是另一个对象
|
||||
if (_free2.TryDequeue(out var pi2))
|
||||
{
|
||||
pi.Value.TryDispose();
|
||||
if (pi2.LastTime < exp)
|
||||
{
|
||||
pi2.Value.TryDispose();
|
||||
|
||||
count++;
|
||||
Interlocked.Decrement(ref _FreeCount);
|
||||
count++;
|
||||
Interlocked.Decrement(ref _FreeCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 可能是另一个对象,放回去
|
||||
_free2.Enqueue(pi2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ public partial class CultureChooser
|
||||
{
|
||||
if (_firstRender)
|
||||
{
|
||||
if (OperatingSystem.IsBrowser() || !Runtime.IsWeb)
|
||||
if (OperatingSystem.IsBrowser() || !Runtime.IsWeb || App.EffectiveTypes.FirstOrDefault(a => a.Name.Contains("WebView")) != null)
|
||||
{
|
||||
var cultureName = item.Value;
|
||||
if (cultureName != CultureInfo.CurrentCulture.Name)
|
||||
|
@@ -51,48 +51,6 @@ public static class GenericExtensions
|
||||
return differences;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static IEnumerable<PropertyInfo> GetProperties(this IEnumerable<dynamic> value, params string[] names)
|
||||
{
|
||||
// 获取动态对象集合的类型
|
||||
var type = value.GetType().GetGenericArguments().LastOrDefault() ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
var namesStr = System.Text.Json.JsonSerializer.Serialize(names);
|
||||
// 构建缓存键,包括属性名和类型信息
|
||||
var cacheKey = $"{namesStr}-{type.FullName}-{type.TypeHandle.Value}";
|
||||
|
||||
// 从缓存中获取属性信息,如果缓存不存在,则创建并缓存
|
||||
var result = Instance.GetOrAdd(cacheKey, a =>
|
||||
{
|
||||
// 获取动态对象类型中指定名称的属性信息
|
||||
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty)
|
||||
.Where(pi => names.Contains(pi.Name)) // 筛选出指定属性名的属性信息
|
||||
.Where(pi => pi != null) // 过滤空属性信息
|
||||
.AsEnumerable();
|
||||
|
||||
// 检查是否找到了所有指定名称的属性,如果没有找到,则抛出异常
|
||||
if (names.Length != properties.Count())
|
||||
{
|
||||
throw new InvalidOperationException($"Couldn't find properties on type:{type.Name},{Environment.NewLine}names:{namesStr}");
|
||||
}
|
||||
|
||||
return properties; // 返回属性信息集合
|
||||
}, 3600); // 缓存有效期为3600秒
|
||||
|
||||
return result!; // 返回属性信息集合
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static IEnumerable<IGrouping<object[], dynamic>> GroupByKeys(this IEnumerable<dynamic> values, IEnumerable<string> keys)
|
||||
{
|
||||
// 获取动态对象集合中指定键的属性信息
|
||||
var properties = GetProperties(values, keys.ToArray());
|
||||
|
||||
// 使用对象数组作为键进行分组
|
||||
return values.GroupBy(v => properties.Select(property => property.GetValue(v)).ToArray(), new ArrayEqualityComparer());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否都包含
|
||||
/// </summary>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.6.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.6.3" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.6.0</PluginVersion>
|
||||
<ProPluginVersion>10.6.0</ProPluginVersion>
|
||||
<PluginVersion>10.6.6</PluginVersion>
|
||||
<ProPluginVersion>10.6.6</ProPluginVersion>
|
||||
<AuthenticationVersion>2.1.7</AuthenticationVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@@ -82,7 +82,7 @@ namespace ThingsGateway.Foundation
|
||||
public virtual int CacheTimeout { get; set; } = 500;
|
||||
/// <inheritdoc/>
|
||||
[MinValue(100)]
|
||||
public virtual ushort ConnectTimeout { get; set; } = 3000;
|
||||
public virtual int ConnectTimeout { get; set; } = 3000;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@@ -88,7 +88,7 @@ public interface IChannelOptions
|
||||
/// <summary>
|
||||
/// 连接超时时间
|
||||
/// </summary>
|
||||
ushort ConnectTimeout { get; set; }
|
||||
int ConnectTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道并发控制锁
|
||||
|
@@ -10,8 +10,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.5" />
|
||||
<PackageReference Include="TouchSocket" Version="3.1.2" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.2" />
|
||||
<PackageReference Include="TouchSocket" Version="3.1.3" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -130,7 +130,7 @@ public class ControlController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpPost("writeVariables")]
|
||||
[DisplayName("写入变量")]
|
||||
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
public async Task<Dictionary<string, Dictionary<string, IOperResult>>> WriteVariablesAsync([FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var deviceData in deviceDatas)
|
||||
{
|
||||
|
@@ -105,7 +105,7 @@ public abstract class BusinessBase : DriverBase
|
||||
var ids = IdVariableRuntimes.Select(b => b.Value.DeviceId).ToHashSet();
|
||||
// 获取当前设备需要采集的设备
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => ids.Contains(a.Id)).ToDictionary(a => a.Id);
|
||||
VariableRuntimeGroups = IdVariableRuntimes.Where(a => !a.Value.Group.IsNullOrEmpty()).GroupBy(a => a.Value.Group ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
VariableRuntimeGroups = IdVariableRuntimes.Where(a => !a.Value.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
@@ -75,7 +75,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.Group ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
|
@@ -74,7 +74,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.Group ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
|
@@ -23,7 +23,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// 业务插件,额外实现脚本切换实体
|
||||
/// </summary>
|
||||
public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevModel, AlarmModel> : BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel, AlarmModel> where DevModel : class where VarModel : class where AlarmModel : class
|
||||
public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevModel, AlarmModel> : BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel, AlarmModel>
|
||||
{
|
||||
protected sealed override BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval => _businessPropertyWithCacheIntervalScript;
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
|
||||
if (topics.Count > 0)
|
||||
@@ -133,7 +133,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
|
||||
if (topics.Count > 0)
|
||||
@@ -200,7 +200,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
@@ -376,7 +376,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
protected List<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
|
||||
if (topics.Count > 0)
|
||||
@@ -441,7 +441,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
protected List<TopicArray> GetDeviceTopicArray(IEnumerable<DevModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
|
||||
if (topics.Count > 0)
|
||||
@@ -508,7 +508,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
protected List<TopicArray> GetVariableTopicArray(IEnumerable<VarModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
|
@@ -62,7 +62,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<VarModel> : Bus
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.Group ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
|
@@ -21,5 +21,5 @@ public abstract class VariablePropertyBase
|
||||
/// 启用
|
||||
/// </summary>
|
||||
[DynamicProperty()]
|
||||
public bool Enable { get; set; } = true;
|
||||
public virtual bool Enable { get; set; } = true;
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// 采集插件,继承实现不同PLC通讯
|
||||
/// <para></para>
|
||||
/// </summary>
|
||||
public abstract class CollectBase : DriverBase
|
||||
public abstract class CollectBase : DriverBase, IRpcDriver
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件配置项
|
||||
@@ -550,7 +550,7 @@ public abstract class CollectBase : DriverBase
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
internal async ValueTask<Dictionary<string, OperResult<object>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
// 初始化结果字典
|
||||
Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>();
|
||||
@@ -610,7 +610,13 @@ public abstract class CollectBase : DriverBase
|
||||
}
|
||||
|
||||
// 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
|
||||
return results.Concat(operResults).ToDictionary(a => a.Key, a => a.Value);
|
||||
return new Dictionary<string, Dictionary<string, IOperResult>>()
|
||||
{
|
||||
{
|
||||
this.DeviceName ,
|
||||
results.Concat(operResults).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -619,7 +625,7 @@ public abstract class CollectBase : DriverBase
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
internal async ValueTask<Dictionary<string, OperResult>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
// 初始化结果字典
|
||||
Dictionary<string, OperResult> results = new Dictionary<string, OperResult>();
|
||||
@@ -664,7 +670,14 @@ public abstract class CollectBase : DriverBase
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
|
||||
return results.Concat(results1).ToDictionary(a => a.Key, a => a.Value);
|
||||
|
||||
return new Dictionary<string, Dictionary<string, IOperResult>>()
|
||||
{
|
||||
{
|
||||
this.DeviceName ,
|
||||
results.Concat(results1).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -22,17 +22,17 @@ public static class DynamicModelExtension
|
||||
/// <summary>
|
||||
/// GetDynamicModel
|
||||
/// </summary>
|
||||
public static IEnumerable<dynamic> GetDynamicModel<T>(this IEnumerable<T> datas, string script) where T : class
|
||||
public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(script))
|
||||
{
|
||||
//执行脚本,获取新实体
|
||||
var getDeviceModel = CSharpScriptEngineExtension.Do<IDynamicModel>(script);
|
||||
return getDeviceModel.GetList(datas);
|
||||
return getDeviceModel.GetList(datas.Cast<object>());
|
||||
}
|
||||
else
|
||||
{
|
||||
return datas;
|
||||
return datas.Cast<object>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public static class DynamicModelExtension
|
||||
return null; // 未找到对应的业务设备Id,返回null
|
||||
}
|
||||
|
||||
public static IEnumerable<IGrouping<object[], dynamic>> GroupByKeys(this IEnumerable<dynamic> values, IEnumerable<string> keys)
|
||||
public static IEnumerable<IGrouping<object[], T>> GroupByKeys<T>(this IEnumerable<T> values, IEnumerable<string> keys)
|
||||
{
|
||||
// 获取动态对象集合中指定键的属性信息
|
||||
var properties = GetProperties(values, keys.ToArray());
|
||||
@@ -87,7 +87,7 @@ public static class DynamicModelExtension
|
||||
return values.GroupBy(v => properties.Select(property => property.GetValue(v)).ToArray(), new ArrayEqualityComparer());
|
||||
}
|
||||
|
||||
private static PropertyInfo[] GetProperties(this IEnumerable<dynamic> value, params string[] names)
|
||||
private static PropertyInfo[] GetProperties<T>(this IEnumerable<T> value, params string[] names)
|
||||
{
|
||||
// 获取动态对象集合的类型
|
||||
var type = value.GetType().GetGenericArguments().FirstOrDefault() ?? throw new ArgumentNullException(nameof(value));
|
||||
|
@@ -0,0 +1,23 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public interface IRpcDriver
|
||||
{
|
||||
|
||||
ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken);
|
||||
|
||||
}
|
@@ -151,7 +151,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
|
||||
[SugarColumn(ColumnDescription = "连接超时", IsNullable = true, DefaultValue = "3000")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
[MinValue(100)]
|
||||
public override ushort ConnectTimeout { get; set; } = 3000;
|
||||
public override int ConnectTimeout { get; set; } = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// 最大并发数
|
||||
|
@@ -63,7 +63,7 @@ public class Variable : BaseDataEntity, IValidatableObject
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "分组名称", IsNullable = true)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 1)]
|
||||
public virtual string Group { get; set; }
|
||||
public virtual string BusinessGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
|
@@ -467,6 +467,8 @@ public static class GlobalData
|
||||
/// <param name="deviceRuntime">设备运行时对象</param>
|
||||
internal static void DeviceStatusChange(DeviceRuntime deviceRuntime)
|
||||
{
|
||||
deviceRuntime.Driver?.LogMessage?.LogInformation($"Status changed: {deviceRuntime.DeviceStatus}");
|
||||
|
||||
if (DeviceStatusChangeEvent != null)
|
||||
{
|
||||
// 触发设备状态变化事件,并将设备运行时对象转换为设备数据对象进行传递
|
||||
|
@@ -148,7 +148,8 @@
|
||||
"RawValue": "RawValue",
|
||||
"Value": "Value",
|
||||
"AlarmEnable": "AlarmEnable",
|
||||
|
||||
"BusinessGroup": "BusinessGroup",
|
||||
"CollectGroup": "CollectGroup",
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"DeviceId": "CollectionDevice",
|
||||
@@ -400,7 +401,7 @@
|
||||
"Name": "Name",
|
||||
"Name.Required": "{0} cannot be empty",
|
||||
"Description": "Description",
|
||||
"Group": "Group",
|
||||
"BusinessGroup": "BusinessGroup",
|
||||
"CollectGroup": "CollectGroup",
|
||||
"DeviceId": "CollectionDevice",
|
||||
"DeviceId.MinValue": "{0} cannot be empty",
|
||||
|
@@ -144,7 +144,8 @@
|
||||
"RawValue": "原始值",
|
||||
"Value": "实时值",
|
||||
"AlarmEnable": "报警使能",
|
||||
|
||||
"BusinessGroup": "业务组",
|
||||
"CollectGroup": "采集组",
|
||||
"Name": "名称",
|
||||
"Description": "描述",
|
||||
"DeviceId": "采集设备",
|
||||
@@ -419,7 +420,7 @@
|
||||
"Name": "名称",
|
||||
"Name.Required": " {0} 不可为空",
|
||||
"Description": "描述",
|
||||
"Group": "业务组",
|
||||
"BusinessGroup": "业务组",
|
||||
"CollectGroup": "采集组",
|
||||
"DeviceId": "采集设备",
|
||||
"DeviceId.MinValue": " {0} 不可为空",
|
||||
|
@@ -17,13 +17,17 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// 设备业务变化数据
|
||||
/// </summary>
|
||||
public class DeviceBasicData : IPrimaryIdEntity
|
||||
public class DeviceBasicData
|
||||
{
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public DeviceRuntime DeviceRuntime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="PrimaryIdEntity.Id"/>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Device.Name"/>
|
||||
public string Name { get; set; }
|
||||
public string Name => DeviceRuntime?.Name;
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.ActiveTime"/>
|
||||
public DateTime ActiveTime { get; set; }
|
||||
@@ -37,37 +41,37 @@ public class DeviceBasicData : IPrimaryIdEntity
|
||||
public string LastErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.PluginName"/>
|
||||
public string PluginName { get; set; }
|
||||
public string PluginName => DeviceRuntime?.PluginName;
|
||||
|
||||
/// <inheritdoc cref="Device.Description"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Description { get; set; }
|
||||
public string? Description => DeviceRuntime?.Description;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark1"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark1 { get; set; }
|
||||
public string Remark1 => DeviceRuntime?.Remark1;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark2"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark2 { get; set; }
|
||||
public string Remark2 => DeviceRuntime?.Remark2;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark3"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark3 { get; set; }
|
||||
public string Remark3 => DeviceRuntime?.Remark3;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark4"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark4 { get; set; }
|
||||
public string Remark4 => DeviceRuntime?.Remark4;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark5"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark5 { get; set; }
|
||||
public string Remark5 => DeviceRuntime?.Remark5;
|
||||
|
||||
|
||||
}
|
||||
@@ -75,23 +79,36 @@ public class DeviceBasicData : IPrimaryIdEntity
|
||||
/// <summary>
|
||||
/// 变量业务变化数据
|
||||
/// </summary>
|
||||
public class VariableBasicData : IPrimaryIdEntity
|
||||
public class VariableBasicData
|
||||
{
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public VariableRuntime VariableRuntime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="PrimaryIdEntity.Id"/>
|
||||
public long Id { get; set; }
|
||||
public long Id => VariableRuntime.Id;
|
||||
|
||||
/// <inheritdoc cref="Variable.Name"/>
|
||||
public string Name { get; set; }
|
||||
/// <inheritdoc cref="Variable.Group"/>
|
||||
public string Name => VariableRuntime.Name;
|
||||
|
||||
/// <inheritdoc cref="Variable.CollectGroup"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Group { get; set; }
|
||||
public string CollectGroup => VariableRuntime.CollectGroup;
|
||||
|
||||
/// <inheritdoc cref="Variable.BusinessGroup"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string BusinessGroup => VariableRuntime.BusinessGroup;
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.DeviceName"/>
|
||||
public string DeviceName { get; set; }
|
||||
public string DeviceName => VariableRuntime.DeviceName;
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.RuntimeType"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string RuntimeType { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.Value"/>
|
||||
public object Value { get; set; }
|
||||
/// <inheritdoc cref="VariableRuntime.RawValue"/>
|
||||
@@ -121,48 +138,48 @@ public class VariableBasicData : IPrimaryIdEntity
|
||||
/// <inheritdoc cref="Variable.RegisterAddress"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? RegisterAddress { get; set; }
|
||||
public string? RegisterAddress => VariableRuntime.RegisterAddress;
|
||||
|
||||
/// <inheritdoc cref="Variable.Unit"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Unit { get; set; }
|
||||
public string? Unit => VariableRuntime.Unit;
|
||||
|
||||
/// <inheritdoc cref="Variable.Description"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Description { get; set; }
|
||||
public string? Description => VariableRuntime.Description;
|
||||
|
||||
/// <inheritdoc cref="Variable.ProtectType"/>
|
||||
public ProtectTypeEnum ProtectType { get; set; }
|
||||
public ProtectTypeEnum ProtectType => VariableRuntime.ProtectType;
|
||||
|
||||
/// <inheritdoc cref="Variable.DataType"/>
|
||||
public DataTypeEnum DataType { get; set; }
|
||||
public DataTypeEnum DataType => VariableRuntime.DataType;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark1"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark1 { get; set; }
|
||||
public string Remark1 => VariableRuntime.Remark1;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark2"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark2 { get; set; }
|
||||
public string Remark2 => VariableRuntime.Remark2;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark3"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark3 { get; set; }
|
||||
public string Remark3 => VariableRuntime.Remark3;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark4"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark4 { get; set; }
|
||||
public string Remark4 => VariableRuntime.Remark4;
|
||||
|
||||
/// <inheritdoc cref="Device.Remark5"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Remark5 { get; set; }
|
||||
public string Remark5 => VariableRuntime.Remark5;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -224,7 +224,16 @@ public class DeviceRuntime : Device, IDisposable
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public IDriver? Driver { get; internal set; }
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AdaptIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public IRpcDriver? RpcDriver { get; set; }
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public string? Tag { get; set; }
|
||||
|
||||
public void Init(ChannelRuntime channelRuntime)
|
||||
{
|
||||
|
@@ -22,5 +22,12 @@ public class VariableMapper : IRegister
|
||||
|
||||
config.ForType<VariableRuntime, VariableRuntime>()
|
||||
.Ignore(dest => dest.DeviceRuntime);
|
||||
|
||||
config.ForType<VariableRuntime, VariableBasicData>()
|
||||
.BeforeMapping((a, b) => b.VariableRuntime = a);
|
||||
config.ForType<DeviceRuntime, DeviceBasicData>()
|
||||
.BeforeMapping((a, b) => b.DeviceRuntime = a);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -87,18 +87,13 @@ public class VariableMethod
|
||||
}
|
||||
|
||||
dynamic result;
|
||||
switch (MethodInfo.TaskType)
|
||||
switch (MethodInfo.ReturnKind)
|
||||
{
|
||||
case TaskReturnType.Task:
|
||||
await MethodInfo.InvokeAsync(driverBase, os).ConfigureAwait(false);
|
||||
result = OperResult.Success;
|
||||
case MethodReturnKind.Awaitable:
|
||||
case MethodReturnKind.AwaitableObject:
|
||||
result = await MethodInfo.InvokeAsync(driverBase, os).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case TaskReturnType.TaskObject:
|
||||
result = await MethodInfo.InvokeObjectAsync(driverBase, os).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case TaskReturnType.None:
|
||||
default:
|
||||
result = MethodInfo.Invoke(driverBase, os);
|
||||
break;
|
||||
|
@@ -32,7 +32,10 @@ public class VariableRuntime : Variable, IVariable, IDisposable
|
||||
private bool? _isOnlineChanged;
|
||||
protected object? _value;
|
||||
|
||||
public VariableRuntime()
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 变化时间
|
||||
/// </summary>
|
||||
@@ -375,7 +378,7 @@ public class VariableRuntime : Variable, IVariable, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<OperResult> RpcAsync(string value, string? executive = "brower", CancellationToken cancellationToken = default)
|
||||
public async ValueTask<IOperResult> RpcAsync(string value, string? executive = "brower", CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>()
|
||||
{
|
||||
|
@@ -19,5 +19,5 @@ public interface IRpcService
|
||||
/// <param name="deviceDatas">指定键为变量名称,值为附带方法参数或写入值,方法参数会按逗号分割解析</param>
|
||||
/// <param name="cancellationToken"><see cref="CancellationToken"/> 取消令箭</param>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, Dictionary<string, OperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default);
|
||||
Task<Dictionary<string, Dictionary<string, IOperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
@@ -42,16 +42,15 @@ internal sealed class RpcService : IRpcService
|
||||
private IStringLocalizer Localizer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<string, Dictionary<string, OperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default)
|
||||
public async Task<Dictionary<string, Dictionary<string, IOperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 初始化用于存储将要写入的变量和方法的字典
|
||||
Dictionary<CollectBase, Dictionary<VariableRuntime, JToken>> writeVariables = new();
|
||||
Dictionary<CollectBase, Dictionary<VariableRuntime, JToken>> writeMethods = new();
|
||||
Dictionary<IRpcDriver, Dictionary<VariableRuntime, JToken>> writeVariables = new();
|
||||
Dictionary<IRpcDriver, Dictionary<VariableRuntime, JToken>> writeMethods = new();
|
||||
// 用于存储结果的并发字典
|
||||
ConcurrentDictionary<string, Dictionary<string, OperResult>> results = new();
|
||||
ConcurrentDictionary<string, Dictionary<string, IOperResult>> results = new();
|
||||
deviceDatas.ForEach(a => results.TryAdd(a.Key, new()));
|
||||
|
||||
|
||||
var deviceDict = GlobalData.Devices;
|
||||
|
||||
// 对每个要操作的变量进行检查和处理
|
||||
@@ -68,7 +67,8 @@ internal sealed class RpcService : IRpcService
|
||||
}
|
||||
|
||||
// 查找变量对应的设备
|
||||
var collect = device.Driver as CollectBase;
|
||||
var collect = device.Driver as IRpcDriver;
|
||||
collect ??= device.RpcDriver;
|
||||
if (collect == null)
|
||||
{
|
||||
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
|
||||
@@ -78,7 +78,7 @@ internal sealed class RpcService : IRpcService
|
||||
continue;
|
||||
}
|
||||
// 检查设备状态,如果设备处于暂停状态,则添加相应的错误信息到结果中并继续下一个变量的处理
|
||||
if (collect.CurrentDevice.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
if (device.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
{
|
||||
deviceData.Value.ForEach(a =>
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DevicePause", deviceData.Key]))
|
||||
@@ -136,46 +136,51 @@ internal sealed class RpcService : IRpcService
|
||||
// 写入日志
|
||||
foreach (var resultItem in result)
|
||||
{
|
||||
var empty = string.IsNullOrEmpty(resultItem.Key);
|
||||
string operObj = empty ? deviceDatas[driverData.Key.DeviceName].Select(x => x.Key).ToJsonNetString() : resultItem.Key;
|
||||
|
||||
string parJson = empty ? deviceDatas.Select(x => x.Value).ToJsonNetString() : deviceDatas[driverData.Key.DeviceName][resultItem.Key];
|
||||
|
||||
if (!resultItem.Value.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
LogTime = DateTime.Now,
|
||||
OperateMessage = resultItem.Value.IsSuccess ? null : resultItem.Value.ToString(),
|
||||
IsSuccess = resultItem.Value.IsSuccess,
|
||||
OperateMethod = Localizer["WriteVariable"],
|
||||
OperateDevice = driverData.Key.DeviceName,
|
||||
OperateObject = operObj,
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = parJson,
|
||||
ResultJson = null
|
||||
}
|
||||
);
|
||||
|
||||
// 不返回详细错误
|
||||
if (!resultItem.Value.IsSuccess)
|
||||
foreach (var variableResult in resultItem.Value)
|
||||
{
|
||||
OperResult result1 = resultItem.Value;
|
||||
result1.Exception = null;
|
||||
result[resultItem.Key] = result1;
|
||||
}
|
||||
}
|
||||
|
||||
// 将结果添加到结果字典中
|
||||
results[driverData.Key.DeviceName].AddRange(result);
|
||||
string operObj = variableResult.Key;
|
||||
|
||||
string parJson = deviceDatas[resultItem.Key][variableResult.Key];
|
||||
|
||||
if (!variableResult.Value.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
LogTime = DateTime.Now,
|
||||
OperateMessage = variableResult.Value.IsSuccess ? null : variableResult.Value.ToString(),
|
||||
IsSuccess = variableResult.Value.IsSuccess,
|
||||
OperateMethod = Localizer["WriteVariable"],
|
||||
OperateDevice = resultItem.Key,
|
||||
OperateObject = operObj,
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = parJson,
|
||||
ResultJson = null
|
||||
}
|
||||
);
|
||||
|
||||
// 不返回详细错误
|
||||
if (!variableResult.Value.IsSuccess)
|
||||
{
|
||||
var result1 = variableResult.Value;
|
||||
result1.Exception = null;
|
||||
resultItem.Value[variableResult.Key] = result1;
|
||||
}
|
||||
}
|
||||
|
||||
// 将结果添加到结果字典中
|
||||
results[resultItem.Key].AddRange(resultItem.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 将异常信息添加到结果字典中
|
||||
results[driverData.Key.DeviceName].AddRange(driverData.Value.Select((KeyValuePair<VariableRuntime, JToken> a) =>
|
||||
foreach (var item in driverData.Value)
|
||||
{
|
||||
return new KeyValuePair<string, OperResult>(a.Key.Name, new OperResult(ex));
|
||||
}));
|
||||
results[item.Key.DeviceName].Add(item.Key.Name, new OperResult(ex));
|
||||
}
|
||||
}
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -192,42 +197,51 @@ internal sealed class RpcService : IRpcService
|
||||
// 写入日志
|
||||
foreach (var resultItem in result)
|
||||
{
|
||||
// 写入日志
|
||||
if (!resultItem.Value.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
LogTime = DateTime.Now,
|
||||
OperateMessage = resultItem.Value.IsSuccess ? null : resultItem.Value.ToString(),
|
||||
IsSuccess = resultItem.Value.IsSuccess,
|
||||
OperateMethod = operateMethods[resultItem.Key],
|
||||
OperateDevice = driverData.Key.DeviceName,
|
||||
OperateObject = resultItem.Key,
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = deviceDatas[driverData.Key.DeviceName][resultItem.Key]?.ToString(),
|
||||
ResultJson = resultItem.Value.Content?.ToJsonNetString()
|
||||
}
|
||||
);
|
||||
|
||||
// 不返回详细错误
|
||||
if (!resultItem.Value.IsSuccess)
|
||||
foreach (var variableResult in resultItem.Value)
|
||||
{
|
||||
OperResult<object> result1 = resultItem.Value;
|
||||
result1.Exception = null;
|
||||
result[resultItem.Key] = result1;
|
||||
}
|
||||
}
|
||||
string operObj = variableResult.Key;
|
||||
|
||||
results[driverData.Key.DeviceName].AddRange(result.ToDictionary(a => a.Key, a => (OperResult)a.Value));
|
||||
string parJson = deviceDatas[resultItem.Key][variableResult.Key];
|
||||
|
||||
// 写入日志
|
||||
if (!variableResult.Value.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
LogTime = DateTime.Now,
|
||||
OperateMessage = variableResult.Value.IsSuccess ? null : variableResult.Value.ToString(),
|
||||
IsSuccess = variableResult.Value.IsSuccess,
|
||||
OperateMethod = operateMethods[variableResult.Key],
|
||||
OperateDevice = resultItem.Key,
|
||||
OperateObject = operObj,
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = parJson?.ToString(),
|
||||
ResultJson = variableResult.Value is IOperResult<object> operResult ? operResult.Content?.ToJsonNetString() : string.Empty
|
||||
}
|
||||
);
|
||||
|
||||
// 不返回详细错误
|
||||
if (!variableResult.Value.IsSuccess)
|
||||
{
|
||||
var result1 = variableResult.Value;
|
||||
result1.Exception = null;
|
||||
resultItem.Value[variableResult.Key] = result1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
results[resultItem.Key].AddRange(resultItem.Value.ToDictionary(a => a.Key, a => a.Value));
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 将异常信息添加到结果字典中
|
||||
results[driverData.Key.DeviceName].AddRange(driverData.Value.Select((KeyValuePair<VariableRuntime, JToken> a) =>
|
||||
foreach (var item in driverData.Value)
|
||||
{
|
||||
return new KeyValuePair<string, OperResult>(a.Key.Name, new OperResult(ex));
|
||||
}));
|
||||
results[item.Key.DeviceName].Add(item.Key.Name, new OperResult(ex));
|
||||
}
|
||||
}
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@@ -21,7 +21,7 @@ namespace ThingsGateway.Gateway.Application
|
||||
Task<Dictionary<string, object>> ExportVariableAsync(ExportFilter exportFilter);
|
||||
|
||||
Task ImportVariableAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart, CancellationToken cancellationToken);
|
||||
Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool restart, CancellationToken cancellationToken);
|
||||
Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart, CancellationToken cancellationToken);
|
||||
|
||||
|
||||
Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart, CancellationToken cancellationToken);
|
||||
|
@@ -80,7 +80,7 @@ internal interface IVariableService
|
||||
/// <summary>
|
||||
/// 创建n个modbus变量
|
||||
/// </summary>
|
||||
Task<(List<Channel>, List<Device>, List<Variable>)> InsertTestDataAsync(int variableCount, int deviceCount, string slaveUrl = "127.0.0.1:502");
|
||||
Task<(List<Channel>, List<Device>, List<Variable>)> InsertTestDataAsync(int variableCount, int deviceCount, string slaveUrl = "127.0.0.1:502", bool businessEnable = false);
|
||||
|
||||
/// <summary>
|
||||
/// 表格查询
|
||||
|
@@ -165,7 +165,7 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
|
||||
}
|
||||
|
||||
public async Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool restart, CancellationToken cancellationToken)
|
||||
public async Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -173,7 +173,7 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
|
||||
|
||||
|
||||
var datas = await GlobalData.VariableService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl).ConfigureAwait(false);
|
||||
var datas = await GlobalData.VariableService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable).ConfigureAwait(false);
|
||||
|
||||
{
|
||||
var newChannelRuntimes = (datas.Item1).Adapt<List<ChannelRuntime>>();
|
||||
|
@@ -54,7 +54,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
|
||||
#region 测试
|
||||
|
||||
public async Task<(List<Channel>, List<Device>, List<Variable>)> InsertTestDataAsync(int variableCount, int deviceCount, string slaveUrl = "127.0.0.1:502")
|
||||
public async Task<(List<Channel>, List<Device>, List<Variable>)> InsertTestDataAsync(int variableCount, int deviceCount, string slaveUrl = "127.0.0.1:502", bool businessEnable = false)
|
||||
{
|
||||
if (slaveUrl.IsNullOrWhiteSpace()) slaveUrl = "127.0.0.1:502";
|
||||
if (deviceCount > variableCount) variableCount = deviceCount;
|
||||
@@ -124,64 +124,69 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
}
|
||||
}
|
||||
|
||||
//Channel serviceChannel = new Channel();
|
||||
//Device serviceDevice = new Device();
|
||||
|
||||
//{
|
||||
// var id = CommonUtils.GetSingleId();
|
||||
// var name = $"modbusSlaveChannel{id}";
|
||||
// serviceChannel.ChannelType = ChannelTypeEnum.TcpService;
|
||||
// serviceChannel.Name = name;
|
||||
// serviceChannel.Enable = true;
|
||||
// serviceChannel.Id = id;
|
||||
// serviceChannel.CreateUserId = UserManager.UserId;
|
||||
// serviceChannel.CreateOrgId = UserManager.OrgId;
|
||||
// serviceChannel.BindUrl = "127.0.0.1:502";
|
||||
// serviceChannel.PluginName = "ThingsGateway.Plugin.Modbus.ModbusSlave";
|
||||
// newChannels.Add(serviceChannel);
|
||||
//}
|
||||
//{
|
||||
// var id = CommonUtils.GetSingleId();
|
||||
// var name = $"modbusSlaveDevice{id}";
|
||||
// serviceDevice.Name = name;
|
||||
// serviceDevice.Id = id;
|
||||
// serviceDevice.CreateUserId = UserManager.UserId;
|
||||
// serviceDevice.CreateOrgId = UserManager.OrgId;
|
||||
// serviceDevice.ChannelId = serviceChannel.Id;
|
||||
// serviceDevice.IntervalTime = "1000";
|
||||
// newDevices.Add(serviceDevice);
|
||||
//}
|
||||
|
||||
Channel mqttChannel = new Channel();
|
||||
Device mqttDevice = new Device();
|
||||
|
||||
if (businessEnable)
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
var name = $"mqttChannel{id}";
|
||||
mqttChannel.ChannelType = ChannelTypeEnum.Other;
|
||||
mqttChannel.Name = name;
|
||||
mqttChannel.Id = id;
|
||||
mqttChannel.CreateUserId = UserManager.UserId;
|
||||
mqttChannel.CreateOrgId = UserManager.OrgId;
|
||||
mqttChannel.PluginName = "ThingsGateway.Plugin.Mqtt.MqttServer";
|
||||
newChannels.Add(mqttChannel);
|
||||
}
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
var name = $"mqttDevice{id}";
|
||||
mqttDevice.Name = name;
|
||||
mqttDevice.Id = id;
|
||||
mqttDevice.CreateUserId = UserManager.UserId;
|
||||
mqttDevice.CreateOrgId = UserManager.OrgId;
|
||||
mqttDevice.ChannelId = mqttChannel.Id;
|
||||
mqttDevice.IntervalTime = "1000";
|
||||
mqttDevice.DevicePropertys = new Dictionary<string, string>
|
||||
|
||||
Channel serviceChannel = new Channel();
|
||||
Device serviceDevice = new Device();
|
||||
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
var name = $"modbusSlaveChannel{id}";
|
||||
serviceChannel.ChannelType = ChannelTypeEnum.TcpService;
|
||||
serviceChannel.Name = name;
|
||||
serviceChannel.Enable = true;
|
||||
serviceChannel.Id = id;
|
||||
serviceChannel.CreateUserId = UserManager.UserId;
|
||||
serviceChannel.CreateOrgId = UserManager.OrgId;
|
||||
serviceChannel.BindUrl = "127.0.0.1:502";
|
||||
serviceChannel.PluginName = "ThingsGateway.Plugin.Modbus.ModbusSlave";
|
||||
newChannels.Add(serviceChannel);
|
||||
}
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
var name = $"modbusSlaveDevice{id}";
|
||||
serviceDevice.Name = name;
|
||||
serviceDevice.Id = id;
|
||||
serviceDevice.CreateUserId = UserManager.UserId;
|
||||
serviceDevice.CreateOrgId = UserManager.OrgId;
|
||||
serviceDevice.ChannelId = serviceChannel.Id;
|
||||
serviceDevice.IntervalTime = "1000";
|
||||
newDevices.Add(serviceDevice);
|
||||
}
|
||||
|
||||
Channel mqttChannel = new Channel();
|
||||
Device mqttDevice = new Device();
|
||||
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
var name = $"mqttChannel{id}";
|
||||
mqttChannel.ChannelType = ChannelTypeEnum.Other;
|
||||
mqttChannel.Name = name;
|
||||
mqttChannel.Id = id;
|
||||
mqttChannel.CreateUserId = UserManager.UserId;
|
||||
mqttChannel.CreateOrgId = UserManager.OrgId;
|
||||
mqttChannel.PluginName = "ThingsGateway.Plugin.Mqtt.MqttServer";
|
||||
newChannels.Add(mqttChannel);
|
||||
}
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
var name = $"mqttDevice{id}";
|
||||
mqttDevice.Name = name;
|
||||
mqttDevice.Id = id;
|
||||
mqttDevice.CreateUserId = UserManager.UserId;
|
||||
mqttDevice.CreateOrgId = UserManager.OrgId;
|
||||
mqttDevice.ChannelId = mqttChannel.Id;
|
||||
mqttDevice.IntervalTime = "1000";
|
||||
mqttDevice.DevicePropertys = new Dictionary<string, string>
|
||||
{
|
||||
{"IsAllVariable", "true"}
|
||||
};
|
||||
newDevices.Add(mqttDevice);
|
||||
}
|
||||
newDevices.Add(mqttDevice);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Channel opcuaChannel = new Channel();
|
||||
//Device opcuaDevice = new Device();
|
||||
|
@@ -84,7 +84,7 @@ public class Startup : AppStartup
|
||||
{
|
||||
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
|
||||
|
||||
if (it.InitTable == true)
|
||||
if (it.InitDatabase == true)
|
||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||
});
|
||||
|
||||
|
@@ -8,8 +8,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.2" />
|
||||
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.2" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.3" />
|
||||
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.3" />
|
||||
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
|
||||
|
||||
</ItemGroup>
|
||||
|
@@ -70,7 +70,8 @@
|
||||
|
||||
"TestVariableCount": "TestVariableCount",
|
||||
"TestDeviceCount": "TestDeviceCount",
|
||||
"SlaveUrl": "SlaveUrlUrl",
|
||||
"SlaveUrl": "SlaveUrl",
|
||||
"BusinessEnable": "BusinessEnable",
|
||||
"Test": "Addition of test variables"
|
||||
},
|
||||
|
||||
|
@@ -29,6 +29,7 @@
|
||||
"TestVariableCount": "变量数量",
|
||||
"TestDeviceCount": "采集设备数量",
|
||||
"SlaveUrl": "服务端Url",
|
||||
"BusinessEnable": "添加业务设备",
|
||||
"Test": "一键添加测试变量"
|
||||
},
|
||||
|
||||
|
@@ -36,92 +36,18 @@ public partial class PropertyComponent : IPropertyUIBase
|
||||
|
||||
private async Task CheckScript(BusinessPropertyWithCacheIntervalScript businessProperty, string pname)
|
||||
{
|
||||
IEnumerable<object> data = null;
|
||||
string script = null;
|
||||
if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel))
|
||||
{
|
||||
data = new List<AlarmVariable>() { new() {
|
||||
Name = "testName",
|
||||
DeviceName = "testDevice",
|
||||
AlarmCode = "1",
|
||||
AlarmTime = DateTime.Now,
|
||||
EventTime = DateTime.Now,
|
||||
AlarmLimit = "3",
|
||||
AlarmType = AlarmTypeEnum.L,
|
||||
EventType=EventTypeEnum.Alarm,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
},
|
||||
new() {
|
||||
Name = "testName2",
|
||||
DeviceName = "testDevice",
|
||||
AlarmCode = "1",
|
||||
AlarmTime = DateTime.Now,
|
||||
EventTime = DateTime.Now,
|
||||
AlarmLimit = "3",
|
||||
AlarmType = AlarmTypeEnum.L,
|
||||
EventType=EventTypeEnum.Alarm,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
}};
|
||||
script = businessProperty.BigTextScriptAlarmModel;
|
||||
}
|
||||
else if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptVariableModel))
|
||||
{
|
||||
data = new List<VariableBasicData>() { new() {
|
||||
Name = "testName",
|
||||
DeviceName = "testDevice",
|
||||
Value = "1",
|
||||
ChangeTime = DateTime.Now,
|
||||
CollectTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} ,
|
||||
new() {
|
||||
Name = "testName2",
|
||||
DeviceName = "testDevice",
|
||||
Value = "1",
|
||||
ChangeTime = DateTime.Now,
|
||||
CollectTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} };
|
||||
script = businessProperty.BigTextScriptVariableModel;
|
||||
|
||||
}
|
||||
else if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel))
|
||||
{
|
||||
data = new List<DeviceBasicData>() { new() {
|
||||
Name = "testDevice",
|
||||
ActiveTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} ,
|
||||
new() {
|
||||
Name = "testDevice2",
|
||||
ActiveTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
}};
|
||||
|
||||
script = businessProperty.BigTextScriptDeviceModel;
|
||||
}
|
||||
else
|
||||
@@ -141,7 +67,7 @@ public partial class PropertyComponent : IPropertyUIBase
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ScriptCheck>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ScriptCheck.Data),data },
|
||||
{nameof(ScriptCheck.Data),Array.Empty<object>() },
|
||||
{nameof(ScriptCheck.Script),script },
|
||||
{nameof(ScriptCheck.OnGetDemo),()=>
|
||||
{
|
||||
|
@@ -45,7 +45,7 @@
|
||||
|
||||
<EditorItem @bind-Field="@context.Description" />
|
||||
<EditorItem @bind-Field="@context.CollectGroup" />
|
||||
<EditorItem @bind-Field="@context.Group" />
|
||||
<EditorItem @bind-Field="@context.BusinessGroup" />
|
||||
|
||||
<EditorItem @bind-Field="@context.Unit" />
|
||||
<EditorItem @bind-Field="@context.ProtectType" />
|
||||
|
@@ -33,7 +33,7 @@
|
||||
<TableColumn Field="@context.DeviceName" FieldExpression=@(()=>context.DeviceName) ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn @bind-Field="@context.Name" ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn @bind-Field="@context.Description" ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn @bind-Field="@context.Group" ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
<TableColumn @bind-Field="@context.BusinessGroup" ShowTips=true Filterable=true Sortable=true Visible=true />
|
||||
|
||||
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="false" />
|
||||
<TableColumn Field="@context.ChangeTime" ShowTips=true FieldExpression=@(()=>context.ChangeTime) Filterable=true Sortable=true Visible=false />
|
||||
@@ -122,7 +122,7 @@
|
||||
<BodyTemplate>
|
||||
<BootstrapInput @bind-Value=TestVariableCount ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<BootstrapInput @bind-Value=TestDeviceCount ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<BootstrapInput @bind-Value=SlaveUrl ShowLabel="true" ShowLabelTooltip="true" />
|
||||
<Checkbox @bind-Value=BusinessEnable ShowLabel="true" ShowLabelTooltip="true" />
|
||||
</BodyTemplate>
|
||||
|
||||
</PopConfirmButton>
|
||||
|
@@ -133,6 +133,7 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
private int TestDeviceCount { get; set; }
|
||||
|
||||
private string SlaveUrl { get; set; }
|
||||
private bool BusinessEnable { get; set; }
|
||||
|
||||
#region 修改
|
||||
private async Task Copy(IEnumerable<Variable> variables)
|
||||
@@ -417,7 +418,7 @@ finally
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => GlobalData.VariableRuntimeService.InsertTestDataAsync(TestVariableCount, TestDeviceCount, SlaveUrl, AutoRestartThread, default));
|
||||
await Task.Run(() => GlobalData.VariableRuntimeService.InsertTestDataAsync(TestVariableCount, TestDeviceCount, SlaveUrl, BusinessEnable, AutoRestartThread, default));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -16,7 +16,6 @@ namespace ThingsGateway.Management;
|
||||
|
||||
public class DeviceDataWithValue
|
||||
{
|
||||
public long Id { get; set; }
|
||||
/// <inheritdoc cref="Device.Name"/>
|
||||
public string Name { get; set; }
|
||||
|
||||
@@ -34,3 +33,17 @@ public class DeviceDataWithValue
|
||||
/// <inheritdoc cref="DeviceRuntime.ReadOnlyVariableRuntimes"/>
|
||||
public Dictionary<string, VariableDataWithValue> ReadOnlyVariableRuntimes { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class DataWithDatabase
|
||||
{
|
||||
public Channel Channel { get; set; }
|
||||
public List<DeviceDataWithDatabase> DeviceVariables { get; set; }
|
||||
}
|
||||
|
||||
public class DeviceDataWithDatabase
|
||||
{
|
||||
public Device Device { get; set; }
|
||||
public List<Variable> Variables { get; set; }
|
||||
}
|
@@ -44,6 +44,7 @@
|
||||
"RedundancyDisable": "Redundant gateway site not enabled"
|
||||
},
|
||||
"ThingsGateway.Management.RedundancyOptions": {
|
||||
"ForcedSync": "Forced Synchronous",
|
||||
"Enable": "Enable Dual-Machine Redundancy",
|
||||
"MasterUri": "Master Node URL",
|
||||
"IsMaster": "IsMaster",
|
||||
@@ -59,8 +60,9 @@
|
||||
"Switch": "Switch",
|
||||
|
||||
"Restart": "The redundant service will be restarted soon",
|
||||
"Confirm": "Confirm switching to redundant state"
|
||||
"Confirm": "Confirm switching to redundant state",
|
||||
|
||||
"ForcedSyncWarning": "Forcing synchronization will generate database configuration information.Are you sure you want to continue?"
|
||||
|
||||
},
|
||||
|
||||
|
@@ -46,7 +46,7 @@
|
||||
},
|
||||
|
||||
"ThingsGateway.Management.RedundancyOptions": {
|
||||
|
||||
"ForcedSync": "强制同步",
|
||||
"Enable": "启用双机冗余",
|
||||
"MasterUri": "主站Url",
|
||||
"IsMaster": "是否为主站",
|
||||
@@ -62,7 +62,8 @@
|
||||
"Switch": "切换",
|
||||
|
||||
"Restart": "即将重新启动冗余服务",
|
||||
"Confirm": "确认切换冗余状态"
|
||||
"Confirm": "确认切换冗余状态",
|
||||
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?"
|
||||
},
|
||||
|
||||
"ThingsGateway.Management._Imports": {
|
||||
|
@@ -17,6 +17,8 @@ internal interface IRedundancyHostedService : IHostedService
|
||||
{
|
||||
Task<OperResult> StartRedundancyTaskAsync();
|
||||
Task StopRedundancyTaskAsync();
|
||||
ValueTask ForcedSync(CancellationToken cancellationToken = default);
|
||||
|
||||
public TextFileLogger TextLogger { get; }
|
||||
public string LogPath { get; }
|
||||
}
|
||||
|
@@ -13,6 +13,9 @@ using Mapster;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
@@ -24,7 +27,7 @@ using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService
|
||||
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService, IRpcDriver
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IRedundancyService _redundancyService;
|
||||
@@ -50,16 +53,17 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
private IStringLocalizer Localizer { get; }
|
||||
private DoTask RedundancyTask { get; set; }
|
||||
private WaitLock RedundancyRestartLock { get; } = new();
|
||||
private LoggerGroup _log { get; set; }
|
||||
public ILog LogMessage { get; set; }
|
||||
public TextFileLogger TextLogger { get; }
|
||||
public string LogPath { get; }
|
||||
private TcpDmtpClient TcpDmtpClient;
|
||||
private TcpDmtpService TcpDmtpService;
|
||||
private async Task<TcpDmtpClient> GetTcpDmtpClient(RedundancyOptions redundancy)
|
||||
{
|
||||
_log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
_log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
_log?.AddLogger(TextLogger);
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(redundancy.MasterUri)
|
||||
@@ -67,10 +71,10 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(_log);
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer());
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
@@ -87,9 +91,10 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
|
||||
private async Task<TcpDmtpService> GetTcpDmtpService(RedundancyOptions redundancy)
|
||||
{
|
||||
_log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
_log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
_log?.AddLogger(TextLogger);
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(redundancy.MasterUri)
|
||||
@@ -97,10 +102,10 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(_log);
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer());
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
@@ -161,8 +166,8 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await item.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpdateGatewayData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
_log?.LogTrace($"{item.GetIPPort()} Update StandbyStation data success");
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()} Update StandbyStation data success");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -170,7 +175,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
_log?.LogWarning(ex, Localizer["ErrorSynchronizingData"]);
|
||||
LogMessage?.LogWarning(ex, Localizer["ErrorSynchronizingData"]);
|
||||
}
|
||||
await Task.Delay(syncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -182,7 +187,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.LogWarning(ex, "Execute");
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +253,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
else
|
||||
{
|
||||
// 如果设备在线
|
||||
_log?.LogTrace($"Ping ActiveStation {redundancy.MasterUri} success");
|
||||
LogMessage?.LogTrace($"Ping ActiveStation {redundancy.MasterUri} success");
|
||||
await StandbyAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -264,10 +269,61 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.LogWarning(ex, "Execute");
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ForcedSync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
online = (await TcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
|
||||
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
var data = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(
|
||||
nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
|
||||
|
||||
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"ForcedSync data success");
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "ForcedSync data error");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private WaitLock _switchLock = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -291,12 +347,12 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
{
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
RedundancyTask = new DoTask(a => DoMasterWork(TcpDmtpService, RedundancyOptions.SyncInterval, a), _log); // 创建新的任务
|
||||
RedundancyTask = new DoTask(a => DoMasterWork(TcpDmtpService, RedundancyOptions.SyncInterval, a), LogMessage); // 创建新的任务
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
RedundancyTask = new DoTask(a => DoSlaveWork(TcpDmtpClient, RedundancyOptions, a), _log); // 创建新的任务
|
||||
RedundancyTask = new DoTask(a => DoSlaveWork(TcpDmtpClient, RedundancyOptions, a), LogMessage); // 创建新的任务
|
||||
}
|
||||
|
||||
RedundancyTask?.Start(default); // 启动任务
|
||||
@@ -306,7 +362,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.LogError(ex, "Start"); // 记录错误日志
|
||||
LogMessage?.LogError(ex, "Start"); // 记录错误日志
|
||||
return new(ex);
|
||||
}
|
||||
finally
|
||||
@@ -326,6 +382,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
TcpDmtpService = await GetTcpDmtpService(RedundancyOptions).ConfigureAwait(false);
|
||||
|
||||
await TcpDmtpService.StartAsync().ConfigureAwait(false);//启动
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
@@ -350,7 +407,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
{
|
||||
// 输出日志,指示主站已恢复,从站将切换到备用状态
|
||||
if (first)
|
||||
_log?.Warning(Localizer["SwitchSlaveState"]);
|
||||
LogMessage?.Warning(Localizer["SwitchSlaveState"]);
|
||||
|
||||
// 将 IsStart 设置为 false,表示当前设备为从站,切换到备用状态
|
||||
_gatewayRedundantSerivce.StartCollectChannelEnable = false;
|
||||
@@ -376,7 +433,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
{
|
||||
// 输出日志,指示无法连接冗余站点,本机将切换到正常状态
|
||||
if (first)
|
||||
_log?.Warning(Localizer["SwitchMasterState"]);
|
||||
LogMessage?.Warning(Localizer["SwitchMasterState"]);
|
||||
_gatewayRedundantSerivce.StartCollectChannelEnable = true;
|
||||
await RestartAsync().ConfigureAwait(false);
|
||||
}
|
||||
@@ -426,7 +483,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log?.LogError(ex, "Stop"); // 记录错误日志
|
||||
LogMessage?.LogError(ex, "Stop"); // 记录错误日志
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -435,4 +492,164 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, OperResult<object>>> dataResult = new();
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas = new();
|
||||
foreach (var item in writeInfoLists)
|
||||
{
|
||||
if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas))
|
||||
{
|
||||
variableDatas.Add(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceDatas.Add(item.Key.DeviceName ?? string.Empty, new());
|
||||
deviceDatas[item.Key.DeviceName ?? string.Empty].Add(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!RedundancyOptions.IsMaster)
|
||||
{
|
||||
online = (await TcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
dataResult = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, deviceDatas).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
|
||||
return dataResult;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(item.Key, out var device))
|
||||
{
|
||||
var key = device.Tag;
|
||||
|
||||
if (TcpDmtpService.TryGetClient(key, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var data = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, new Dictionary<string, Dictionary<string, string>>() { { item.Key, item.Value } }).ConfigureAwait(false);
|
||||
|
||||
dataResult.AddRange(data);
|
||||
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].Add(vItem.Key, new OperResult<object>(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].Add(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
return dataResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage?.LogWarning("Rpc error, no client online");
|
||||
}
|
||||
}
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "Rpc error");
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, Dictionary<string, OperResult<object>>> NoOnline(Dictionary<string, Dictionary<string, OperResult<object>>> dataResult, Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
return dataResult;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
}
|
||||
|
@@ -10,21 +10,33 @@
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
public partial class ReverseCallbackServer : SingletonRpcServer
|
||||
internal partial class ReverseCallbackServer : SingletonRpcServer
|
||||
{
|
||||
RedundancyHostedService RedundancyHostedService;
|
||||
public ReverseCallbackServer(RedundancyHostedService redundancyHostedService)
|
||||
{
|
||||
RedundancyHostedService = redundancyHostedService;
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public void UpdateGatewayData(List<DeviceDataWithValue> deviceDatas)
|
||||
public void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas)
|
||||
{
|
||||
|
||||
foreach (var deviceData in deviceDatas)
|
||||
{
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var device))
|
||||
{
|
||||
device.RpcDriver = RedundancyHostedService;
|
||||
device.Tag = callContext.Caller is IIdClient idClient ? idClient.Id : string.Empty;
|
||||
|
||||
|
||||
device.SetDeviceStatus(deviceData.ActiveTime, deviceData.DeviceStatus == DeviceStatusEnum.OnLine ? false : true, lastErrorMessage: deviceData.LastErrorMessage);
|
||||
|
||||
foreach (var variableData in deviceData.ReadOnlyVariableRuntimes)
|
||||
@@ -38,5 +50,41 @@ public partial class ReverseCallbackServer : SingletonRpcServer
|
||||
|
||||
}
|
||||
}
|
||||
RedundancyHostedService.LogMessage?.Trace("Update data success");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public List<DataWithDatabase> GetData()
|
||||
{
|
||||
List<DataWithDatabase> dataWithDatabases = new();
|
||||
foreach (var channels in GlobalData.ReadOnlyChannels)
|
||||
{
|
||||
DataWithDatabase dataWithDatabase = new();
|
||||
dataWithDatabase.Channel = channels.Value;
|
||||
dataWithDatabase.DeviceVariables = new();
|
||||
foreach (var devices in channels.Value.ReadDeviceRuntimes)
|
||||
{
|
||||
DeviceDataWithDatabase deviceDataWithDatabase = new();
|
||||
|
||||
deviceDataWithDatabase.Device = devices.Value;
|
||||
deviceDataWithDatabase.Variables = devices.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).Cast<Variable>().ToList();
|
||||
|
||||
|
||||
dataWithDatabase.DeviceVariables.Add(deviceDataWithDatabase);
|
||||
}
|
||||
|
||||
dataWithDatabases.Add(dataWithDatabase);
|
||||
}
|
||||
return dataWithDatabases;
|
||||
|
||||
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public Task<Dictionary<string, Dictionary<string, IOperResult>>> Rpc(Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
|
||||
{
|
||||
return GlobalData.RpcService.InvokeDeviceMethodAsync("Management", deviceDatas, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
|
||||
<EditComponent ItemsPerRow=1 Model="Model" OnSave="OnSaveRedundancy" />
|
||||
|
||||
<Button IsDisabled=@(Model.IsMaster) OnClick="ForcedSync">@RedundancyLocalizer["ForcedSync"]</Button>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 h-100">
|
||||
|
||||
|
@@ -12,6 +12,7 @@
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
@@ -82,6 +83,8 @@ public partial class RedundancyOptionsPage
|
||||
else
|
||||
await ToastService.Warning(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Fail", result.ToString()]}");
|
||||
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -92,4 +95,16 @@ public partial class RedundancyOptionsPage
|
||||
}
|
||||
|
||||
|
||||
private async Task ForcedSync(MouseEventArgs args)
|
||||
{
|
||||
var ret = await SwalService.ShowModal(new SwalOption()
|
||||
{
|
||||
Category = SwalCategory.Warning,
|
||||
Title = RedundancyLocalizer["ForcedSyncWarning"]
|
||||
});
|
||||
if (ret)
|
||||
{
|
||||
await RedundancyHostedService.ForcedSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ public class Startup : AppStartup
|
||||
DbContext.DbConfigs?.ForEach(it =>
|
||||
{
|
||||
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
|
||||
if (it.InitTable == true)
|
||||
if (it.InitDatabase == true)
|
||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||
});
|
||||
var fullName = Assembly.GetExecutingAssembly().FullName;//获取程序集全名
|
||||
|
@@ -14,54 +14,60 @@ public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBa
|
||||
Title = "ExecuteScriptNode"; Placeholder = "ExecuteScriptNode.Placeholder";
|
||||
Text =
|
||||
"""
|
||||
using ThingsGateway.RulesEngine;
|
||||
using ThingsGateway.Foundation;
|
||||
using TouchSocket.Core;
|
||||
|
||||
using System.Text;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.RulesEngine;
|
||||
|
||||
public class TestEx : IExexcuteExpressions
|
||||
|
||||
public class TestExexcuteExpressions : IExexcuteExpressions
|
||||
{
|
||||
|
||||
public TouchSocket.Core.ILog Logger { get; set; }
|
||||
|
||||
public async System.Threading.Tasks.Task<NodeOutput> ExecuteAsync(NodeInput input, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
|
||||
//想上传mqtt,可以自己写mqtt上传代码,或者通过mqtt插件的公开方法上传
|
||||
|
||||
//直接获取mqttclient插件类型的第一个设备
|
||||
var driver = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver;
|
||||
if (driver != null)
|
||||
{
|
||||
//找到对应的MqttClient插件设备
|
||||
var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
|
||||
if (mqttClient == null)
|
||||
throw new("mqttClient NOT FOUND");
|
||||
var result = await mqttClient.MqttUpAsync("test", Encoding.UTF8.GetBytes("test"),1, default);// 主题 和 负载
|
||||
if (!result.IsSuccess)
|
||||
throw new(result.ErrorMessage);
|
||||
return new NodeOutput() { Value = result };
|
||||
}
|
||||
throw new("mqttClient NOT FOUND");
|
||||
var mqttClient = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver as ThingsGateway.Plugin.Mqtt.MqttClient;
|
||||
if (mqttClient == null)
|
||||
throw new("mqttClient NOT FOUND");
|
||||
|
||||
TopicArray topicArray = new()
|
||||
{
|
||||
Topic = "test",
|
||||
Json = Encoding.UTF8.GetBytes("test")
|
||||
};
|
||||
var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载
|
||||
if (!result.IsSuccess)
|
||||
throw new(result.ErrorMessage);
|
||||
return new NodeOutput() { Value = result };
|
||||
|
||||
//通过设备名称找出mqttClient插件
|
||||
//var driver = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver;
|
||||
//if (driver != null)
|
||||
//var mqttClient = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver as ThingsGateway.Plugin.Mqtt.MqttClient;
|
||||
|
||||
//if (mqttClient == null)
|
||||
// throw new("mqttClient NOT FOUND");
|
||||
|
||||
|
||||
//TopicArray topicArray = new()
|
||||
//{
|
||||
// //找到对应的MqttClient插件设备
|
||||
// var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
|
||||
// if (mqttClient == null)
|
||||
// throw new("mqttClient NOT FOUND");
|
||||
// var result = await mqttClient.MqttUpAsync("test", "test", default);// 主题 和 负载
|
||||
// if (!result.IsSuccess)
|
||||
// throw new(result.ErrorMessage);
|
||||
// return new NodeOutput() { Value = result };
|
||||
//}
|
||||
//throw new("mqttClient NOT FOUND");
|
||||
// Topic = "test",
|
||||
// Json = Encoding.UTF8.GetBytes("test")
|
||||
//};
|
||||
//var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载
|
||||
//if (!result.IsSuccess)
|
||||
// throw new(result.ErrorMessage);
|
||||
//return new NodeOutput() { Value = result };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
""";
|
||||
|
@@ -42,7 +42,7 @@ public class Startup : AppStartup
|
||||
DbContext.DbConfigs?.ForEach(it =>
|
||||
{
|
||||
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
|
||||
if (it.InitTable == true)
|
||||
if (it.InitDatabase == true)
|
||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||
});
|
||||
var fullName = Assembly.GetExecutingAssembly().FullName;//获取程序集全名
|
||||
|
@@ -1,51 +0,0 @@
|
||||
|
||||
//using ThingsGateway.Gateway.Application;
|
||||
//using ThingsGateway.RulesEngine;
|
||||
|
||||
|
||||
//public class TestEx : IExexcuteExpressions
|
||||
//{
|
||||
|
||||
// public TouchSocket.Core.ILog Logger { get; set; }
|
||||
|
||||
// public async System.Threading.Tasks.Task<NodeOutput> ExecuteAsync(NodeInput input, System.Threading.CancellationToken cancellationToken)
|
||||
// {
|
||||
|
||||
|
||||
// //想上传mqtt,可以自己写mqtt上传代码,或者通过mqtt插件的公开方法上传
|
||||
|
||||
// //直接获取mqttclient插件类型的第一个设备
|
||||
// //var driver = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver;
|
||||
// //if (driver != null)
|
||||
// //{
|
||||
// // //找到对应的MqttClient插件设备
|
||||
// // var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
|
||||
// // if (mqttClient == null)
|
||||
// // throw new("mqttClient NOT FOUND");
|
||||
// // var result = await mqttClient.MqttUpAsync("test", "test", default);// 主题 和 负载
|
||||
// // if (!result.IsSuccess)
|
||||
// // throw new(result.ErrorMessage);
|
||||
// // return new NodeOutput() { Value = result };
|
||||
// //}
|
||||
// //throw new("mqttClient NOT FOUND");
|
||||
|
||||
|
||||
// //通过设备名称找出mqttClient插件
|
||||
// //var driver = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver;
|
||||
// //if (driver != null)
|
||||
// //{
|
||||
// // //找到对应的MqttClient插件设备
|
||||
// // var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
|
||||
// // if (mqttClient == null)
|
||||
// // throw new("mqttClient NOT FOUND");
|
||||
// // var result = await mqttClient.MqttUpAsync("test", "test", default);// 主题 和 负载
|
||||
// // if (!result.IsSuccess)
|
||||
// // throw new(result.ErrorMessage);
|
||||
// // return new NodeOutput() { Value = result };
|
||||
// //}
|
||||
// //throw new("mqttClient NOT FOUND");
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
@@ -18,7 +18,7 @@ namespace ThingsGateway.Foundation.Modbus;
|
||||
/// <summary>
|
||||
/// ChannelEventHandler
|
||||
/// </summary>
|
||||
public delegate ValueTask<OperResult> ModbusServerWriteEventHandler(ModbusAddress modbusAddress, IThingsGatewayBitConverter bitConverter, IClientChannel channel);
|
||||
public delegate ValueTask<IOperResult> ModbusServerWriteEventHandler(ModbusAddress modbusAddress, IThingsGatewayBitConverter bitConverter, IClientChannel channel);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ModbusSlave : DeviceBase, IModbusAddress
|
||||
|
@@ -401,6 +401,9 @@ public partial class SiemensS7Master : DeviceBase
|
||||
var result2 = await SendThenReturnMessageBaseAsync(new S7Send(ISO_CR), channel).ConfigureAwait(false);
|
||||
if (!result2.IsSuccess)
|
||||
{
|
||||
if (result2.Exception is OperationCanceledException)
|
||||
return true;
|
||||
|
||||
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError1", channel.ToString(), result2]);
|
||||
await channel.CloseAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
@@ -418,6 +421,9 @@ public partial class SiemensS7Master : DeviceBase
|
||||
var result2 = await SendThenReturnMessageBaseAsync(new S7Send(S7_PN), channel).ConfigureAwait(false);
|
||||
if (!result2.IsSuccess)
|
||||
{
|
||||
if (result2.Exception is OperationCanceledException)
|
||||
return true;
|
||||
|
||||
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError2", channel.ToString(), result2]);
|
||||
await channel.CloseAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
|
@@ -51,8 +51,8 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -75,7 +75,7 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
|
||||
|
||||
private void UpdateVariable(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
AddQueueVarModel(new CacheDBItem<List<QuestDBHistoryValue>>(variableRuntimeGroup.Adapt<List<QuestDBHistoryValue>>(_config)));
|
||||
|
@@ -46,33 +46,8 @@ namespace ThingsGateway.Debug
|
||||
|
||||
private async Task CheckScript(SqlDBProducerProperty businessProperty, string pname)
|
||||
{
|
||||
IEnumerable<object> data = null;
|
||||
string script = null;
|
||||
{
|
||||
data = new List<VariableBasicData>() { new() {
|
||||
Name = "testName",
|
||||
DeviceName = "testDevice",
|
||||
Value = "1",
|
||||
ChangeTime = DateTime.Now,
|
||||
CollectTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} ,
|
||||
new() {
|
||||
Name = "testName2",
|
||||
DeviceName = "testDevice",
|
||||
Value = "1",
|
||||
ChangeTime = DateTime.Now,
|
||||
CollectTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} };
|
||||
script = pname == businessProperty.BigTextScriptHistoryTable ? businessProperty.BigTextScriptHistoryTable : businessProperty.BigTextScriptRealTable;
|
||||
|
||||
}
|
||||
@@ -90,7 +65,7 @@ namespace ThingsGateway.Debug
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ScriptCheck>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ScriptCheck.Data),data },
|
||||
{nameof(ScriptCheck.Data),Array.Empty < object >() },
|
||||
{nameof(ScriptCheck.Script),script },
|
||||
{nameof(ScriptCheck.OnGetDemo),()=>
|
||||
{
|
||||
|
@@ -217,7 +217,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
var varList = IdVariableRuntimes.Select(a => a.Value);
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var groups = varList.GroupBy(a => a.Group);
|
||||
var groups = varList.GroupBy(a => a.BusinessGroup);
|
||||
foreach (var item in groups)
|
||||
{
|
||||
var result = await UpdateAsync(item.Adapt<List<SQLRealValue>>(), cancellationToken).ConfigureAwait(false);
|
||||
|
@@ -54,8 +54,8 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -79,7 +79,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
{
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
AddQueueVarModel(new CacheDBItem<List<SQLHistoryValue>>(variableRuntimeGroup.Adapt<List<SQLHistoryValue>>(_config)));
|
||||
|
@@ -55,8 +55,8 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -78,7 +78,7 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
|
||||
|
||||
private void UpdateVariable(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
AddQueueVarModel(new CacheDBItem<List<TDengineDBHistoryValue>>(variableRuntimeGroup.Adapt<List<TDengineDBHistoryValue>>(_config)));
|
||||
|
@@ -91,8 +91,8 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -118,7 +118,7 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
|
||||
{
|
||||
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(variableRuntimeGroup.Adapt<List<VariableBasicData>>()));
|
||||
|
@@ -86,8 +86,8 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -112,7 +112,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
|
||||
{
|
||||
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(variableRuntimeGroup.Adapt<List<VariableBasicData>>()));
|
||||
|
@@ -127,7 +127,7 @@ public class ModbusSlave : BusinessBase
|
||||
_modbusVariableQueue?.Clear();
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
VariableValueChange(a.Value, null);
|
||||
VariableValueChange(a.Value, default);
|
||||
});
|
||||
|
||||
ModbusVariables = IdVariableRuntimes.ToDictionary(a =>
|
||||
@@ -201,7 +201,7 @@ public class ModbusSlave : BusinessBase
|
||||
/// <summary>
|
||||
/// RPC写入
|
||||
/// </summary>
|
||||
private async ValueTask<OperResult> OnWriteData(ModbusRequest modbusRequest, IThingsGatewayBitConverter bitConverter, IChannel channel)
|
||||
private async ValueTask<IOperResult> OnWriteData(ModbusRequest modbusRequest, IThingsGatewayBitConverter bitConverter, IChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@@ -50,8 +50,9 @@
|
||||
"UserName": "UserName",
|
||||
"Password": "Password",
|
||||
"ConnectId": "ConnectId",
|
||||
"ConnectTimeout": "ConnectTimeout"
|
||||
|
||||
"ConnectTimeout": "ConnectTimeout",
|
||||
"CheckClearTime": "VariableCheckClearTime(s)"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -51,7 +51,8 @@
|
||||
"UserName": "用户名",
|
||||
"Password": "密码",
|
||||
"ConnectId": "连接ID",
|
||||
"ConnectTimeout": "连接超时时间"
|
||||
"ConnectTimeout": "连接超时时间",
|
||||
"CheckClearTime": "变量过期时间(s)"
|
||||
|
||||
|
||||
|
||||
|
@@ -144,8 +144,8 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -170,7 +170,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
{
|
||||
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
//获取组内全部变量
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(variableRuntimeGroup.Adapt<List<VariableBasicData>>()));
|
||||
@@ -282,9 +282,9 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult>>> GetResult(MqttApplicationMessageReceivedEventArgs args, Dictionary<string, Dictionary<string, JToken>> rpcDatas)
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetResult(MqttApplicationMessageReceivedEventArgs args, Dictionary<string, Dictionary<string, JToken>> rpcDatas)
|
||||
{
|
||||
var mqttRpcResult = new Dictionary<string, Dictionary<string, OperResult>>();
|
||||
var mqttRpcResult = new Dictionary<string, Dictionary<string, IOperResult>>();
|
||||
rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new()));
|
||||
try
|
||||
{
|
||||
@@ -472,7 +472,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
if (isConnect.IsSuccess)
|
||||
{
|
||||
var variableMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topicArray.Topic).WithQualityOfServiceLevel(_driverPropertys.MqttQualityOfServiceLevel)
|
||||
.WithTopic(topicArray.Topic).WithQualityOfServiceLevel(_driverPropertys.MqttQualityOfServiceLevel).WithRetainFlag()
|
||||
.WithPayload(topicArray.Json).Build();
|
||||
var result = await _mqttClient.PublishAsync(variableMessage, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
|
@@ -136,92 +136,18 @@ namespace ThingsGateway.Plugin.Mqtt
|
||||
|
||||
private async Task CheckScript(BusinessPropertyWithCacheIntervalScript businessProperty, string pname)
|
||||
{
|
||||
IEnumerable<object> data = null;
|
||||
string script = null;
|
||||
if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel))
|
||||
{
|
||||
data = new List<AlarmVariable>() { new() {
|
||||
Name = "testName",
|
||||
DeviceName = "testDevice",
|
||||
AlarmCode = "1",
|
||||
AlarmTime = DateTime.Now,
|
||||
EventTime = DateTime.Now,
|
||||
AlarmLimit = "3",
|
||||
AlarmType = AlarmTypeEnum.L,
|
||||
EventType=EventTypeEnum.Alarm,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
},
|
||||
new() {
|
||||
Name = "testName2",
|
||||
DeviceName = "testDevice",
|
||||
AlarmCode = "1",
|
||||
AlarmTime = DateTime.Now,
|
||||
EventTime = DateTime.Now,
|
||||
AlarmLimit = "3",
|
||||
AlarmType = AlarmTypeEnum.L,
|
||||
EventType=EventTypeEnum.Alarm,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
}};
|
||||
script = businessProperty.BigTextScriptAlarmModel;
|
||||
}
|
||||
else if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptVariableModel))
|
||||
{
|
||||
data = new List<VariableBasicData>() { new() {
|
||||
Name = "testName",
|
||||
DeviceName = "testDevice",
|
||||
Value = "1",
|
||||
ChangeTime = DateTime.Now,
|
||||
CollectTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} ,
|
||||
new() {
|
||||
Name = "testName2",
|
||||
DeviceName = "testDevice",
|
||||
Value = "1",
|
||||
ChangeTime = DateTime.Now,
|
||||
CollectTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} };
|
||||
script = businessProperty.BigTextScriptVariableModel;
|
||||
|
||||
}
|
||||
else if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel))
|
||||
{
|
||||
data = new List<DeviceBasicData>() { new() {
|
||||
Name = "testDevice",
|
||||
ActiveTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
} ,
|
||||
new() {
|
||||
Name = "testDevice2",
|
||||
ActiveTime = DateTime.Now,
|
||||
Remark1="1",
|
||||
Remark2="2",
|
||||
Remark3="3",
|
||||
Remark4="4",
|
||||
Remark5="5",
|
||||
}};
|
||||
|
||||
script = businessProperty.BigTextScriptDeviceModel;
|
||||
}
|
||||
else
|
||||
@@ -241,14 +167,16 @@ namespace ThingsGateway.Plugin.Mqtt
|
||||
|
||||
op.Component = BootstrapDynamicComponent.CreateComponent<ScriptCheck>(new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(ScriptCheck.Data),data },
|
||||
{nameof(ScriptCheck.Data),Array.Empty<object>() },
|
||||
{nameof(ScriptCheck.Script),script },
|
||||
{nameof(ScriptCheck.OnGetDemo),()=>
|
||||
{
|
||||
return
|
||||
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel)?
|
||||
"""
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using System.Dynamic;
|
||||
using TouchSocket.Core;
|
||||
public class S1 : IDynamicModel
|
||||
{
|
||||
@@ -287,6 +215,9 @@ namespace ThingsGateway.Plugin.Mqtt
|
||||
|
||||
"""
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using System.Dynamic;
|
||||
using TouchSocket.Core;
|
||||
public class S2 : IDynamicModel
|
||||
{
|
||||
|
@@ -65,7 +65,7 @@ public partial class MqttCollect : CollectBase
|
||||
}
|
||||
|
||||
比如vendor/device;ModuleUnoccupied.EquipId,结果是"E12"
|
||||
比如vendor/device;ModuleUnoccupied.EquipId;raw.SelectToken("ModuleUnoccupied.LotId").ToString().ToInt()==1,结果是"E12"
|
||||
比如vendor/device;ModuleUnoccupied.EquipId;((JToken)raw).SelectToken("ModuleUnoccupied.LotId").ToString().ToInt()==1,结果是"E12"
|
||||
|
||||
""";
|
||||
}
|
||||
@@ -181,6 +181,7 @@ public partial class MqttCollect : CollectBase
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
ETime = TimeSpan.FromSeconds(_driverPropertys.CheckClearTime);
|
||||
|
||||
#region 初始化
|
||||
|
||||
@@ -216,10 +217,34 @@ public partial class MqttCollect : CollectBase
|
||||
_mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync;
|
||||
|
||||
#endregion 初始化
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private TimeSpan ETime = TimeSpan.FromSeconds(60000);
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested && !DisposedValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var item in IdVariableRuntimes)
|
||||
{
|
||||
if (DateTime.Now - item.Value.CollectTime > ETime)
|
||||
{
|
||||
item.Value.SetValue(null, DateTime.Now, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (_mqttClient != null)
|
||||
{
|
||||
|
@@ -74,6 +74,7 @@ public class MqttCollectProperty : CollectPropertyBase
|
||||
[DynamicProperty]
|
||||
public int ConnectTimeout { get; set; } = 3000;
|
||||
|
||||
public override int ReIntervalTime { get; set; } = 30;
|
||||
public override int RetryCount { get; set; } = 3;
|
||||
[DynamicProperty]
|
||||
public int CheckClearTime { get; set; } = 60000;
|
||||
|
||||
}
|
||||
|
@@ -100,8 +100,8 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -126,7 +126,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
{
|
||||
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
|
||||
@@ -184,9 +184,9 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
|
||||
#endregion private
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult>>> GetResult(InterceptingPublishEventArgs args, Dictionary<string, Dictionary<string, JToken>> rpcDatas)
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetResult(InterceptingPublishEventArgs args, Dictionary<string, Dictionary<string, JToken>> rpcDatas)
|
||||
{
|
||||
var mqttRpcResult = new Dictionary<string, Dictionary<string, OperResult>>();
|
||||
var mqttRpcResult = new Dictionary<string, Dictionary<string, IOperResult>>();
|
||||
rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new()));
|
||||
try
|
||||
{
|
||||
@@ -400,7 +400,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
try
|
||||
{
|
||||
var message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topicArray.Topic).WithQualityOfServiceLevel(_driverPropertys.MqttQualityOfServiceLevel)
|
||||
.WithTopic(topicArray.Topic).WithQualityOfServiceLevel(_driverPropertys.MqttQualityOfServiceLevel).WithRetainFlag()
|
||||
.WithPayload(topicArray.Json).Build();
|
||||
await _mqttServer.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(message), cancellationToken).ConfigureAwait(false);
|
||||
|
@@ -42,9 +42,9 @@ public class OpcUaMaster : CollectBase
|
||||
/// <inheritdoc/>
|
||||
public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcUaMaster);
|
||||
|
||||
public override Type DriverPropertyUIType => typeof(OpcUaMasterPropertyRazor);
|
||||
|
||||
public override Type DriverPropertyUIType => typeof(OpcUaMasterRuntimeRazor);
|
||||
|
||||
public override Type DriverUIType => typeof(OpcUaMasterRuntimeRazor);
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@@ -60,6 +60,14 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
lock (Lock)
|
||||
{
|
||||
if (rootFolder == null) return;
|
||||
|
||||
rootFolder?.SafeDispose();
|
||||
rootFolder = null;
|
||||
rootFolder = CreateFolder(null, "ThingsGateway", "ThingsGateway");
|
||||
rootFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
|
||||
|
||||
rootFolder.ClearChangeMasks(SystemContext, true);
|
||||
|
||||
rootFolder.RemoveReferences(ReferenceTypes.Organizes, true);
|
||||
rootFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
|
||||
AddRootNotifier(rootFolder);
|
||||
@@ -84,6 +92,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
}
|
||||
AddPredefinedNode(SystemContext, rootFolder);
|
||||
|
||||
rootFolder.ClearChangeMasks(SystemContext, true);
|
||||
|
||||
}
|
||||
|
||||
@@ -110,6 +119,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
|
||||
RefreshVariable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -51,9 +51,19 @@ public partial class OpcUaServer : BusinessBase
|
||||
|
||||
private static readonly string[] separator = new string[] { ";" };
|
||||
|
||||
|
||||
private volatile int VariableChangedCount = 0;
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
//opcua类库内部有大量缓存,如果刷新变量次数大于一定数量,应该重启服务以防止OOM
|
||||
if (Interlocked.Increment(ref VariableChangedCount) > 100)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await this.DeviceThreadManage.RestartDeviceAsync(this.CurrentDevice, false).ConfigureAwait(false);
|
||||
}
|
||||
, cancellationToken);
|
||||
return;
|
||||
}
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_driverPropertys.IsAllVariable)
|
||||
{
|
||||
@@ -74,6 +84,8 @@ public partial class OpcUaServer : BusinessBase
|
||||
{
|
||||
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
|
||||
});
|
||||
|
||||
|
||||
m_server?.NodeManager?.RefreshVariable();
|
||||
|
||||
}
|
||||
|
@@ -1,25 +0,0 @@
|
||||
@using BootstrapBlazor.Components
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@using ThingsGateway.Plugin.OpcUa
|
||||
@namespace ThingsGateway.Plugin.OpcUa
|
||||
|
||||
|
||||
<ValidateForm Model="Model.Value"
|
||||
@key=@($"DeviceEditValidateForm{Id}{Model.Value.GetType().TypeHandle.Value}")
|
||||
@ref=Model.ValidateForm
|
||||
Id=@($"DeviceEditValidateForm{Id}{Model.Value.GetType().TypeHandle.Value}")>
|
||||
|
||||
<EditorFormObject class="p-2" Items=PluginPropertyEditorItems IsDisplay="!CanWrite" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=@(CanWrite?2:3) ShowLabelTooltip=true LabelWidth=@(CanWrite?240:120) Model="Model.Value" ShowLabel="true" @key=@($"DeviceEditEditorFormObject{Id}{Model.Value.GetType().TypeHandle.Value}")>
|
||||
|
||||
<Buttons>
|
||||
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="Export">@Localizer["ExportC"]</Button>
|
||||
</Buttons>
|
||||
</EditorFormObject>
|
||||
</ValidateForm>
|
||||
|
||||
|
@@ -0,0 +1,16 @@
|
||||
@using BootstrapBlazor.Components
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@using ThingsGateway.Plugin.OpcUa
|
||||
@namespace ThingsGateway.Plugin.OpcUa
|
||||
|
||||
|
||||
|
||||
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="Export">@OpcUaMasterPropertyLocalizer["ExportC"]</Button>
|
||||
|
||||
|
||||
|
@@ -18,32 +18,19 @@ using ThingsGateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Plugin.OpcUa
|
||||
{
|
||||
public partial class OpcUaMasterPropertyRazor : IPropertyUIBase
|
||||
public partial class OpcUaMasterRuntimeRazor : IDriverUIBase
|
||||
{
|
||||
[Parameter, EditorRequired]
|
||||
public string Id { get; set; }
|
||||
[Parameter, EditorRequired]
|
||||
public bool CanWrite { get; set; }
|
||||
[Parameter, EditorRequired]
|
||||
public ModelValueValidateForm Model { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public IEnumerable<IEditorItem> PluginPropertyEditorItems { get; set; }
|
||||
|
||||
IStringLocalizer Localizer { get; set; }
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
Localizer = App.CreateLocalizerByType(Model.Value.GetType());
|
||||
|
||||
return base.OnParametersSetAsync();
|
||||
}
|
||||
public object Driver { get; set; }
|
||||
|
||||
[Inject]
|
||||
private DownloadService DownloadService { get; set; }
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
[Inject]
|
||||
IStringLocalizer<OpcUaMasterProperty> OpcUaMasterPropertyLocalizer { get; set; }
|
||||
|
||||
|
||||
private async Task Export()
|
||||
{
|
@@ -87,8 +87,8 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate)
|
||||
{
|
||||
var varList = variables.Where(a => a.Group.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.Group.IsNullOrEmpty()).GroupBy(a => a.Group);
|
||||
var varList = variables.Where(a => a.BusinessGroup.IsNullOrEmpty());
|
||||
var varGroup = variables.Where(a => !a.BusinessGroup.IsNullOrEmpty()).GroupBy(a => a.BusinessGroup);
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
@@ -113,7 +113,7 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
|
||||
{
|
||||
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && !variable.Group.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.Group, out var variableRuntimeGroup))
|
||||
if (_driverPropertys.GroupUpdate && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
|
||||
{
|
||||
//获取组内全部变量
|
||||
|
@@ -0,0 +1,18 @@
|
||||
<Project>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包-->
|
||||
<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Synchronization*.dll">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>Content</PackagePath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
@@ -0,0 +1,16 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
global using ThingsGateway.Gateway.Application;
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Plugin.Synchronization.SynchronizationProperty": {
|
||||
"IsServer": "IsServer",
|
||||
"IsMaster": "IsMaster",
|
||||
"IsAllVariable": "IsAllVariable",
|
||||
"ServerUri": "ServerUri",
|
||||
"VerifyToken": "VerifyToken",
|
||||
"HeartbeatInterval": "HeartbeatInterval",
|
||||
"SyncInterval": "SyncInterval"
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Plugin.Synchronization.SynchronizationProperty": {
|
||||
"IsServer": "是否Tcp服务",
|
||||
"IsMaster": "是否主站",
|
||||
"IsAllVariable": "选择全部变量",
|
||||
"ServerUri": "服务Uri",
|
||||
"VerifyToken": "验证令牌",
|
||||
"HeartbeatInterval": "心跳间隔",
|
||||
"SyncInterval": "同步间隔"
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public class DeviceDataWithValue
|
||||
{
|
||||
/// <inheritdoc cref="Device.Name"/>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.ActiveTime"/>
|
||||
public DateTime ActiveTime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.DeviceStatus"/>
|
||||
public DeviceStatusEnum DeviceStatus { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.LastErrorMessage"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string LastErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.ReadOnlyVariableRuntimes"/>
|
||||
public Dictionary<string, VariableDataWithValue> ReadOnlyVariableRuntimes { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class DataWithDatabase
|
||||
{
|
||||
public Channel Channel { get; set; }
|
||||
public List<DeviceDataWithDatabase> DeviceVariables { get; set; }
|
||||
}
|
||||
|
||||
public class DeviceDataWithDatabase
|
||||
{
|
||||
public Device Device { get; set; }
|
||||
public List<Variable> Variables { get; set; }
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public partial class ReverseCallbackServer : SingletonRpcServer
|
||||
{
|
||||
Synchronization Synchronization;
|
||||
public ReverseCallbackServer(Synchronization synchronization)
|
||||
{
|
||||
Synchronization = synchronization;
|
||||
}
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas)
|
||||
{
|
||||
foreach (var deviceData in deviceDatas)
|
||||
{
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var device))
|
||||
{
|
||||
device.RpcDriver = Synchronization;
|
||||
device.Tag = callContext.Caller is IIdClient idClient ? idClient.Id : string.Empty;
|
||||
|
||||
device.SetDeviceStatus(deviceData.ActiveTime, deviceData.DeviceStatus == DeviceStatusEnum.OnLine ? false : true, lastErrorMessage: deviceData.LastErrorMessage);
|
||||
|
||||
foreach (var variableData in deviceData.ReadOnlyVariableRuntimes)
|
||||
{
|
||||
if (device.ReadOnlyVariableRuntimes.TryGetValue(variableData.Key, out var value))
|
||||
{
|
||||
value.SetValue(variableData.Value.RawValue, variableData.Value.CollectTime, variableData.Value.IsOnline);
|
||||
value.SetErrorMessage(variableData.Value.LastErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Synchronization.LogMessage?.Trace("Update data success");
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public List<DataWithDatabase> GetData()
|
||||
{
|
||||
List<DataWithDatabase> dataWithDatabases = new();
|
||||
foreach (var device in Synchronization.CollectDevices)
|
||||
{
|
||||
DataWithDatabase dataWithDatabase = new();
|
||||
dataWithDatabase.Channel = device.Value.ChannelRuntime;
|
||||
dataWithDatabase.DeviceVariables = new();
|
||||
DeviceDataWithDatabase deviceDataWithDatabase = new();
|
||||
|
||||
deviceDataWithDatabase.Device = device.Value;
|
||||
|
||||
deviceDataWithDatabase.Variables = device.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).Cast<Variable>().ToList();
|
||||
|
||||
dataWithDatabase.DeviceVariables.Add(deviceDataWithDatabase);
|
||||
|
||||
dataWithDatabases.Add(dataWithDatabase);
|
||||
}
|
||||
return dataWithDatabases;
|
||||
}
|
||||
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public Task<Dictionary<string, Dictionary<string, IOperResult>>> Rpc(Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
|
||||
{
|
||||
return GlobalData.RpcService.InvokeDeviceMethodAsync(Synchronization.DeviceName, deviceDatas, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,463 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
|
||||
public partial class Synchronization : BusinessBase, IRpcDriver
|
||||
{
|
||||
public override VariablePropertyBase VariablePropertys => new SynchronizationVariableProperty();
|
||||
internal SynchronizationProperty _driverPropertys = new();
|
||||
protected override BusinessPropertyBase _businessPropertyBase => _driverPropertys;
|
||||
|
||||
public override Type DriverUIType => typeof(SynchronizationRuntimeRazor);
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_driverPropertys.IsAllVariable)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override bool IsConnected()
|
||||
{
|
||||
return _driverPropertys.IsServer ? _tcpDmtpService?.ServerState == ServerState.Running : _tcpDmtpClient?.Online == true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override async ValueTask ProtectedExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
if (_driverPropertys.IsServer)
|
||||
{
|
||||
if (_tcpDmtpService.ServerState != ServerState.Running)
|
||||
{
|
||||
if (_tcpDmtpService.ServerState != ServerState.Stopped)
|
||||
{
|
||||
await _tcpDmtpService.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await _tcpDmtpService.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
var deviceRunTimes = CollectDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).Adapt<List<DeviceDataWithValue>>();
|
||||
|
||||
foreach (var item in _tcpDmtpService.Clients)
|
||||
{
|
||||
if (item.Online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await item.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()} Update data success");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
bool online = true;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
if (!_tcpDmtpClient.Online)
|
||||
online = (await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
{
|
||||
var deviceRunTimes = CollectDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).Adapt<List<DeviceDataWithValue>>();
|
||||
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await _tcpDmtpClient.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"Update data success");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
private TcpDmtpClient? _tcpDmtpClient;
|
||||
private TcpDmtpService? _tcpDmtpService;
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_driverPropertys.IsServer)
|
||||
{
|
||||
_tcpDmtpService = await GetTcpDmtpService().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tcpDmtpClient = await GetTcpDmtpClient().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_tcpDmtpClient?.SafeDispose();
|
||||
_tcpDmtpService?.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private async Task<TcpDmtpClient> GetTcpDmtpClient()
|
||||
{
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(_driverPropertys.ServerUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = _driverPropertys.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_driverPropertys.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
});
|
||||
|
||||
await tcpDmtpClient.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpClient;
|
||||
}
|
||||
|
||||
private async Task<TcpDmtpService> GetTcpDmtpService()
|
||||
{
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(_driverPropertys.ServerUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = _driverPropertys.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_driverPropertys.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
});
|
||||
|
||||
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpService;
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask ForcedSync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!_driverPropertys.IsServer)
|
||||
{
|
||||
online = (await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
var data = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(
|
||||
nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
|
||||
|
||||
data.ForEach(a => a.Channel.Enable = false);
|
||||
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"ForcedSync data success");
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
foreach (var item in _tcpDmtpService.Clients)
|
||||
{
|
||||
var data = await item.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
|
||||
data.ForEach(a => a.Channel.Enable = false);
|
||||
|
||||
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()}: ForcedSync data success");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage?.LogWarning("ForcedSync data error, no client online");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "ForcedSync data error");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, OperResult<object>>> dataResult = new();
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas = new();
|
||||
foreach (var item in writeInfoLists)
|
||||
{
|
||||
if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas))
|
||||
{
|
||||
variableDatas.Add(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceDatas.Add(item.Key.DeviceName ?? string.Empty, new());
|
||||
deviceDatas[item.Key.DeviceName ?? string.Empty].Add(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!_driverPropertys.IsServer)
|
||||
{
|
||||
online = (await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
dataResult = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, deviceDatas).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
|
||||
return dataResult;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(item.Key, out var device))
|
||||
{
|
||||
var key = device.Tag;
|
||||
|
||||
if (_tcpDmtpService.TryGetClient(key, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var data = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, new Dictionary<string, Dictionary<string, string>>() { { item.Key, item.Value } }).ConfigureAwait(false);
|
||||
|
||||
dataResult.AddRange(data);
|
||||
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].Add(vItem.Key, new OperResult<object>(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].Add(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
return dataResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage?.LogWarning("Rpc error, no client online");
|
||||
}
|
||||
}
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "Rpc error");
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, Dictionary<string, OperResult<object>>> NoOnline(Dictionary<string, Dictionary<string, OperResult<object>>> dataResult, Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
return dataResult;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public partial class Synchronization : BusinessBase
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class SynchronizationProperty : BusinessPropertyBase
|
||||
{
|
||||
[DynamicProperty]
|
||||
public bool IsServer { get; set; } = true;
|
||||
[DynamicProperty]
|
||||
public bool IsMaster { get; set; }
|
||||
|
||||
[DynamicProperty]
|
||||
public bool IsAllVariable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置远程 URI,用于通信。
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public string ServerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用于验证的令牌。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DynamicProperty]
|
||||
public string VerifyToken { get; set; } = "ThingsGateway";
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置心跳间隔。
|
||||
/// </summary>
|
||||
[MinValue(3000)]
|
||||
[DynamicProperty]
|
||||
public int HeartbeatInterval { get; set; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置冗余数据同步间隔(ms)。
|
||||
/// </summary>
|
||||
[MinValue(1000)]
|
||||
[DynamicProperty]
|
||||
public int SyncInterval { get; set; } = 3000;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
@using BootstrapBlazor.Components
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@namespace ThingsGateway.Plugin.Synchronization
|
||||
|
||||
|
||||
|
||||
<Button IsDisabled=Synchronization._driverPropertys.IsMaster IsAsync class="mx-2" Color=Color.Primary OnClick="ForcedSync">ForcedSync</Button>
|
||||
|
||||
|
||||
|
@@ -0,0 +1,44 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization
|
||||
{
|
||||
public partial class SynchronizationRuntimeRazor : IDriverUIBase
|
||||
{
|
||||
[Parameter, EditorRequired]
|
||||
public object Driver { get; set; }
|
||||
public Synchronization Synchronization => Driver as Synchronization;
|
||||
[Inject]
|
||||
private DownloadService DownloadService { get; set; }
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
|
||||
private async Task ForcedSync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Synchronization.ForcedSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class SynchronizationVariableProperty : VariablePropertyBase
|
||||
{
|
||||
public override bool Enable { get; set; } = true;
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public class VariableDataWithValue
|
||||
{
|
||||
/// <inheritdoc cref="Variable.Name"/>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.Value"/>
|
||||
public object RawValue { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.CollectTime"/>
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.IsOnline"/>
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.LastErrorMessage"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? LastErrorMessage { get; set; }
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Import Project="$(SolutionDir)Version.props" />
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
|
@@ -14,7 +14,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -27,7 +28,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -40,7 +42,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -53,7 +56,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -66,7 +70,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
|
@@ -14,7 +14,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -27,7 +28,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -40,7 +42,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -53,7 +56,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
@@ -66,7 +70,8 @@
|
||||
"DbType": "Sqlite", //数据库类型
|
||||
"LanguageType": "Chinese", //中文提示
|
||||
"IsAutoCloseConnection": true, //是否自动释放
|
||||
"InitTable": true, //是否初始化数据库,适用于codefirst
|
||||
"InitDatabase": true, //是否初始化数据库,适用于codefirst
|
||||
"InitTable": true, //是否初始化表,适用于codefirst
|
||||
"InitSeedData": true, //是否初始化种子数据
|
||||
"IsUpdateSeedData": false, //是否更新种子数据
|
||||
"IsShowSql": false //是否控制台显示Sql语句
|
||||
|
52
src/ThingsGateway.Server/Program/TestExexcuteExpressions.cs
Normal file
52
src/ThingsGateway.Server/Program/TestExexcuteExpressions.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
//using System.Text;
|
||||
|
||||
//using ThingsGateway.Gateway.Application;
|
||||
//using ThingsGateway.RulesEngine;
|
||||
|
||||
|
||||
//public class TestExexcuteExpressions : IExexcuteExpressions
|
||||
//{
|
||||
|
||||
// public TouchSocket.Core.ILog Logger { get; set; }
|
||||
|
||||
// public async System.Threading.Tasks.Task<NodeOutput> ExecuteAsync(NodeInput input, System.Threading.CancellationToken cancellationToken)
|
||||
// {
|
||||
// //想上传mqtt,可以自己写mqtt上传代码,或者通过mqtt插件的公开方法上传
|
||||
|
||||
// //直接获取mqttclient插件类型的第一个设备
|
||||
// var mqttClient = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver as ThingsGateway.Plugin.Mqtt.MqttClient;
|
||||
// if (mqttClient == null)
|
||||
// throw new("mqttClient NOT FOUND");
|
||||
|
||||
// TopicArray topicArray = new()
|
||||
// {
|
||||
// Topic = "test",
|
||||
// Json = Encoding.UTF8.GetBytes("test")
|
||||
// };
|
||||
// var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载
|
||||
// if (!result.IsSuccess)
|
||||
// throw new(result.ErrorMessage);
|
||||
// return new NodeOutput() { Value = result };
|
||||
|
||||
// //通过设备名称找出mqttClient插件
|
||||
// //var mqttClient = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver as ThingsGateway.Plugin.Mqtt.MqttClient;
|
||||
|
||||
// //if (mqttClient == null)
|
||||
// // throw new("mqttClient NOT FOUND");
|
||||
|
||||
|
||||
// //TopicArray topicArray = new()
|
||||
// //{
|
||||
// // Topic = "test",
|
||||
// // Json = Encoding.UTF8.GetBytes("test")
|
||||
// //};
|
||||
// //var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载
|
||||
// //if (!result.IsSuccess)
|
||||
// // throw new(result.ErrorMessage);
|
||||
// //return new NodeOutput() { Value = result };
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
@@ -61,6 +61,13 @@
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> native;</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
|
||||
<PackageReference Include="ThingsGateway.Plugin.Synchronization" Version="$(PluginVersion)" GeneratePathProperty="true">
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> native;</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyPluginNugetPackages" AfterTargets="Build">
|
||||
@@ -88,6 +95,7 @@
|
||||
<PkgThingsGateway_Plugin_OpcUaPackageFiles Include="$(PkgThingsGateway_Plugin_OpcUa)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_RabbitMQPackageFiles Include="$(PkgThingsGateway_Plugin_RabbitMQ)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_HttpPackageFiles Include="$(PkgThingsGateway_Plugin_Http)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_SynchronizationPackageFiles Include="$(PkgThingsGateway_Plugin_Synchronization)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Message Text="将插件复制到插件目录 $(GatewayPluginFolder)" Importance="high" />
|
||||
@@ -100,7 +108,8 @@
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_OpcDaPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.OpcDa%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_OpcUaPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.OpcUa%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_RabbitMQPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.RabbitMQ%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_HttpPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.RabbitMQ%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_HttpPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.Http%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_SynchronizationPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.Synchronization%(RecursiveDir)" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
@@ -63,6 +63,11 @@
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> native;</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="ThingsGateway.Plugin.Synchronization" Version="$(PluginVersion)" GeneratePathProperty="true">
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> native;</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyPluginNugetPackages" AfterTargets="Build">
|
||||
@@ -90,6 +95,7 @@
|
||||
<PkgThingsGateway_Plugin_OpcUaPackageFiles Include="$(PkgThingsGateway_Plugin_OpcUa)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_RabbitMQPackageFiles Include="$(PkgThingsGateway_Plugin_RabbitMQ)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_HttpPackageFiles Include="$(PkgThingsGateway_Plugin_Http)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_SynchronizationPackageFiles Include="$(PkgThingsGateway_Plugin_Synchronization)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -105,6 +111,7 @@
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_OpcUaPackageFiles)" DestinationFolder="$(PluginFolder)%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_RabbitMQPackageFiles)" DestinationFolder="$(PluginFolder)%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_HttpPackageFiles)" DestinationFolder="$(PluginFolder)%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_SynchronizationPackageFiles)" DestinationFolder="$(PluginFolder)%(RecursiveDir)" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user