Compare commits

...

12 Commits

Author SHA1 Message Date
Diego
64bc6084be 更新sln 2025-05-19 12:23:41 +08:00
Diego
20434d5bd2 10.6.5 2025-05-19 12:13:11 +08:00
Diego
9ecc9380e6 同步更改 2025-05-19 12:12:44 +08:00
Diego
a57c55080b 增加数据同步插件 2025-05-19 12:10:11 +08:00
2248356998 qq.com
b8ca06c6be fix: hybrid运行切换语言错误 2025-05-17 16:32:36 +08:00
Diego
5aff6461a1 fix: opcuaServer 业务设备刷新变量时可能导致内存泄露 2025-05-16 19:26:33 +08:00
Diego
6cf53fefec 优化内存占用 2025-05-16 18:00:36 +08:00
Diego
45132f3503 更新依赖 2025-05-16 10:51:44 +08:00
Diego
f1e78a0e8a 恢复配置json 2025-05-15 12:17:43 +08:00
Diego
0bf28ec275 更新语言资源 2025-05-15 12:17:09 +08:00
Diego
41f8412c97 支持达梦数据库 2025-05-15 12:15:32 +08:00
Diego
c535974362 更新规则引擎示例 2025-05-15 10:44:12 +08:00
81 changed files with 1112 additions and 415 deletions

View File

@@ -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(a=>a.ClientIds==null).Where(a => a.Id > 0).ExecuteCommand();
db.Updateable<VerificatInfo>().SetColumns(a => a.ClientIds == null).Where(a => a.Id > 0).ExecuteCommand();
VerificatInfoService.RemoveCache();
}

View File

@@ -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);
}
}
}
}

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.6.1</PluginVersion>
<ProPluginVersion>10.6.1</ProPluginVersion>
<PluginVersion>10.6.5</PluginVersion>
<ProPluginVersion>10.6.5</ProPluginVersion>
<AuthenticationVersion>2.1.7</AuthenticationVersion>
</PropertyGroup>

View File

@@ -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

View File

@@ -88,7 +88,7 @@ public interface IChannelOptions
/// <summary>
/// 连接超时时间
/// </summary>
ushort ConnectTimeout { get; set; }
int ConnectTimeout { get; set; }
/// <summary>
/// 通道并发控制锁

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -21,5 +21,5 @@ public abstract class VariablePropertyBase
/// 启用
/// </summary>
[DynamicProperty()]
public bool Enable { get; set; } = true;
public virtual bool Enable { get; set; } = true;
}

View File

@@ -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));

View File

@@ -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>
/// 最大并发数

View File

@@ -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>
/// 描述

View File

@@ -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)
{
// 触发设备状态变化事件,并将设备运行时对象转换为设备数据对象进行传递

View File

@@ -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",

View File

@@ -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} 不可为空",

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -32,7 +32,10 @@ public class VariableRuntime : Variable, IVariable, IDisposable
private bool? _isOnlineChanged;
protected object? _value;
public VariableRuntime()
{
}
/// <summary>
/// 变化时间
/// </summary>

View File

@@ -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>

View File

@@ -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),()=>
{

View File

@@ -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" />

View File

@@ -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 />

View File

@@ -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; }
}

View File

@@ -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?"
},

View File

@@ -46,7 +46,7 @@
},
"ThingsGateway.Management.RedundancyOptions": {
"ForcedSync": "强制同步",
"Enable": "启用双机冗余",
"MasterUri": "主站Url",
"IsMaster": "是否为主站",
@@ -62,7 +62,8 @@
"Switch": "切换",
"Restart": "即将重新启动冗余服务",
"Confirm": "确认切换冗余状态"
"Confirm": "确认切换冗余状态",
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?"
},
"ThingsGateway.Management._Imports": {

View File

@@ -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; }
}

View File

@@ -13,6 +13,8 @@ using Mapster;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
@@ -268,6 +270,59 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
}
}
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.GetGatewayData), 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);
_log?.LogTrace($"ForcedSync data success");
}
}
catch (Exception ex)
{
// 输出警告日志,指示同步数据到从站时发生错误
_log?.LogWarning(ex, "ForcedSync data error");
}
}
catch (OperationCanceledException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception ex)
{
_log?.LogWarning(ex, "Execute");
}
}
private WaitLock _switchLock = new();
/// <inheritdoc/>
@@ -326,6 +381,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);
}

View File

@@ -39,4 +39,34 @@ public partial class ReverseCallbackServer : SingletonRpcServer
}
}
}
[DmtpRpc(MethodInvoke = true)]
public List<DataWithDatabase> GetGatewayData()
{
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;
}
}

View File

@@ -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">

View File

@@ -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();
}
}
}

View File

@@ -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 };
}
}
""";

View File

@@ -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;

View File

@@ -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)));

View File

@@ -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),()=>
{

View File

@@ -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);

View File

@@ -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)));

View File

@@ -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)));

View File

@@ -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>>()));

View File

@@ -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>>()));

View File

@@ -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 =>

View File

@@ -50,8 +50,9 @@
"UserName": "UserName",
"Password": "Password",
"ConnectId": "ConnectId",
"ConnectTimeout": "ConnectTimeout"
"ConnectTimeout": "ConnectTimeout",
"CheckClearTime": "VariableCheckClearTime(s)"
}
}

View File

@@ -51,7 +51,8 @@
"UserName": "用户名",
"Password": "密码",
"ConnectId": "连接ID",
"ConnectTimeout": "连接超时时间"
"ConnectTimeout": "连接超时时间",
"CheckClearTime": "变量过期时间(s)"

View File

@@ -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>>()));
@@ -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)

View File

@@ -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
{

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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))
{
@@ -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);

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()
{

View File

@@ -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))
{
//获取组内全部变量

View File

@@ -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>

View File

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

View File

@@ -0,0 +1,14 @@
{
"ThingsGateway.Plugin.Synchronization.SynchronizationProperty": {
"IsServer": "IsServer",
"IsMaster": "IsMaster",
"IsAllVariable": "IsAllVariable",
"ServerUri": "ServerUri",
"VerifyToken": "VerifyToken",
"HeartbeatInterval": "HeartbeatInterval",
"SyncInterval": "SyncInterval"
}
}

View File

@@ -0,0 +1,14 @@
{
"ThingsGateway.Plugin.Synchronization.SynchronizationProperty": {
"IsServer": "是否Tcp服务",
"IsMaster": "是否主站",
"IsAllVariable": "选择全部变量",
"ServerUri": "服务Uri",
"VerifyToken": "验证令牌",
"HeartbeatInterval": "心跳间隔",
"SyncInterval": "同步间隔"
}
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using TouchSocket.Core;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Plugin.Synchronization;
public partial class ReverseCallbackServer : SingletonRpcServer
{
Synchronization Synchronization;
public ReverseCallbackServer(Synchronization synchronization)
{
Synchronization = synchronization;
}
[DmtpRpc(MethodInvoke = true)]
public void UpData(List<DeviceDataWithValue> deviceDatas)
{
foreach (var deviceData in deviceDatas)
{
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var device))
{
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;
}
}

View File

@@ -0,0 +1,303 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Mapster;
using System.Threading;
using ThingsGateway.Extension.Generic;
using ThingsGateway.Foundation;
using ThingsGateway.Gateway.Application;
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
{
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)
{
// 将 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");
}
}
}

View File

@@ -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
{
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -0,0 +1,47 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Rougamo;
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);
}
}
}
}

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -11,6 +11,7 @@
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.OpcUa\ThingsGateway.Plugin.OpcUa.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.RabbitMQ\ThingsGateway.Plugin.RabbitMQ.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.Http\ThingsGateway.Plugin.Http.csproj" />
<ProjectReference Include="..\Plugin\ThingsGateway.Plugin.Synchronization\ThingsGateway.Plugin.Synchronization.csproj" />
</ItemGroup>

View File

@@ -99,6 +99,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Upgrade", "Up
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.UpgradeServer", "Upgrade\ThingsGateway.UpgradeServer\ThingsGateway.UpgradeServer.csproj", "{29DCAC9C-2D0F-E251-E907-F07D804CA117}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Plugin.Synchronization", "Plugin\ThingsGateway.Plugin.Synchronization\ThingsGateway.Plugin.Synchronization.csproj", "{438B86D4-0CAE-DCC3-E952-90CE77BB8661}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -261,6 +263,10 @@ Global
{29DCAC9C-2D0F-E251-E907-F07D804CA117}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29DCAC9C-2D0F-E251-E907-F07D804CA117}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29DCAC9C-2D0F-E251-E907-F07D804CA117}.Release|Any CPU.Build.0 = Release|Any CPU
{438B86D4-0CAE-DCC3-E952-90CE77BB8661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{438B86D4-0CAE-DCC3-E952-90CE77BB8661}.Debug|Any CPU.Build.0 = Debug|Any CPU
{438B86D4-0CAE-DCC3-E952-90CE77BB8661}.Release|Any CPU.ActiveCfg = Release|Any CPU
{438B86D4-0CAE-DCC3-E952-90CE77BB8661}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -303,10 +309,11 @@ Global
{7E4696E3-1170-6AF6-844C-265BBCEC5DA7} = {36510D70-161F-4241-B8D0-781E21032816}
{7D5E01DE-D6D7-E45D-58FD-E01B38A312B2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{29DCAC9C-2D0F-E251-E907-F07D804CA117} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{438B86D4-0CAE-DCC3-E952-90CE77BB8661} = {36510D70-161F-4241-B8D0-781E21032816}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
RESX_NeutralResourcesLanguage = zh-Hans
RESX_Rules = {"EnabledRules":[]}
RESX_NeutralResourcesLanguage = zh-Hans
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
EndGlobalSection
EndGlobal

View File

@@ -16,7 +16,7 @@
<ItemGroup>
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.2" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.3" />
</ItemGroup>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.6.1</Version>
<Version>10.6.5</Version>
</PropertyGroup>
<ItemGroup>