build: 10.1.0.1

feat: 变量名称 由 全局唯一 更改为 采集设备内唯一
fix: opcda订阅方式初始化时未更新变量状态,导致后续变了状态一直是offline
feat: mqttServer 允许的连接ID前缀 为空时可为任意id

破坏性更新:
1、兼容旧版本数据库,但不兼容旧版本excel文件,不同点在于变量excel 业务属性页,添加了设备名称和业务设备名称,旧版本只有业务设备名称
2、业务上传内容更改为 字典类型,键为设备名称,值为变量列表。变量列表内容与旧版本相同
This commit is contained in:
Diego
2025-03-04 21:09:11 +08:00
parent 091a30045d
commit 6af83152de
90 changed files with 1412 additions and 1103 deletions

View File

@@ -125,7 +125,7 @@ public abstract class BaseEntity : PrimaryKeyEntity, IBaseEntity
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = true, Sortable = true, DefaultSortOrder = SortOrder.Asc)]
[IgnoreExcel]
public int? SortCode { get; set; }
public virtual int? SortCode { get; set; }
}
public interface IBaseDataEntity

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.0.2.18</PluginVersion>
<ProPluginVersion>10.0.2.18</ProPluginVersion>
<PluginVersion>10.1.0.1</PluginVersion>
<ProPluginVersion>10.1.0.1</ProPluginVersion>
</PropertyGroup>
<PropertyGroup>

View File

@@ -13,7 +13,7 @@ using System.Text;
namespace ThingsGateway.Foundation;
[PluginOption(Singleton = true)]
internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin
internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin, ITcpClosingPlugin
{
public string DtuId
{
@@ -48,8 +48,9 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
private string _heartbeat;
private ArraySegment<byte> HeartbeatByte;
public int HeartbeatTime { get; set; } = 3;
private Task Task;
private bool SendHeartbeat;
public int HeartbeatTime { get; set; } = 3000;
public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e)
{
@@ -62,40 +63,46 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
if (client is ITcpClient tcpClient)
{
SendHeartbeat = true;
await tcpClient.SendAsync(DtuIdByte).ConfigureAwait(false);
_ = Task.Factory.StartNew(async () =>
{
var failedCount = 0;
while (client.Online)
if (Task == null)
{
Task = Task.Factory.StartNew(async () =>
{
await Task.Delay(HeartbeatTime).ConfigureAwait(false);
if (!client.Online)
var failedCount = 0;
while (SendHeartbeat)
{
return;
}
try
{
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
await Task.Delay(HeartbeatTime).ConfigureAwait(false);
if (!client.Online)
{
await Task.Delay(200).ConfigureAwait(false);
continue;
}
await tcpClient.SendAsync(HeartbeatByte).ConfigureAwait(false);
tcpClient.Logger?.Trace($"{tcpClient}- Heartbeat");
failedCount = 0;
try
{
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}
await tcpClient.SendAsync(HeartbeatByte).ConfigureAwait(false);
tcpClient.Logger?.Trace($"{tcpClient}- Heartbeat");
failedCount = 0;
}
catch
{
failedCount++;
}
if (failedCount > 3)
{
await client.CloseAsync("The automatic heartbeat has failed more than 3 times and has been disconnected.").ConfigureAwait(false);
}
}
catch
{
failedCount++;
}
if (failedCount > 3)
{
await client.CloseAsync("The automatic heartbeat has failed more than 3 times and has been disconnected.").ConfigureAwait(false);
}
}
});
Task = null;
});
}
}
await e.InvokeNext().ConfigureAwait(false);
@@ -123,4 +130,10 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}
public Task OnTcpClosing(ITcpSession client, ClosingEventArgs e)
{
SendHeartbeat = false;
return EasyTask.CompletedTask;
}
}

View File

@@ -363,7 +363,6 @@ public struct OperResult : IOperResult
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonIgnore]
#endif
[JsonIgnore]
public Exception? Exception { get; set; }
@@ -433,7 +432,6 @@ public struct OperResult : IOperResult
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
#endif
[JsonConverter(typeof(StringEnumConverter))]
public ErrorTypeEnum? ErrorType { get; set; }

View File

@@ -23,7 +23,10 @@ public static class ExportString
/// 设备名称
/// </summary>
public static string DeviceName = Localizer["DeviceName"];
/// <summary>
/// 设备名称
/// </summary>
public static string BusinessDeviceName = Localizer["BusinessDeviceName"];
/// <summary>
/// 冗余设备名称
/// </summary>

View File

@@ -117,13 +117,17 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("writeVariables")]
[DisplayName("写入变量")]
public async Task<Dictionary<string, OperResult>> WriteVariablesAsync(Dictionary<string, string> objs)
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync(Dictionary<string, Dictionary<string, string>> deviceDatas)
{
var data = GlobalData.ReadOnlyVariables.Where(a => objs.ContainsKey(a.Key));
if (data != null)
await GlobalData.SysUserService.CheckApiDataScopeAsync(data.Select(a => a.Value.CreateOrgId), data.Select(a => a.Value.CreateUserId)).ConfigureAwait(false);
foreach (var deviceData in deviceDatas)
{
if (GlobalData.Devices.TryGetValue(deviceData.Key, out var device))
{
await GlobalData.SysUserService.CheckApiDataScopeAsync(device.IdVariableRuntimes.Select(a => a.Value.CreateOrgId), device.IdVariableRuntimes.Select(a => a.Value.CreateUserId)).ConfigureAwait(false);
}
}
return await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext.Connection.RemoteIpAddress.MapToIPv4()}", objs).ConfigureAwait(false);
return await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext.Connection.RemoteIpAddress.MapToIPv4()}", deviceDatas).ConfigureAwait(false);
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -76,7 +74,7 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpGet("realAlarmList")]
[DisplayName("获取实时报警变量信息")]
public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList(VariablePageInput input)
public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList(AlarmVariablePageInput input)
{
var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariables().ConfigureAwait(false);
@@ -85,13 +83,8 @@ public class RuntimeInfoController : ControllerBase
.WhereIF(!input.RegisterAddress.IsNullOrEmpty(), a => a.RegisterAddress == input.RegisterAddress)
.WhereIF(!input.Name.IsNullOrEmpty(), a => a.Name == input.Name)
.WhereIF(!input.DeviceName.IsNullOrEmpty(), a => a.DeviceName == input.DeviceName)
.WhereIF(input.BusinessDeviceId > 0, a =>
{
return GlobalData.ContainsVariable(input.BusinessDeviceId, a);
}
)
.ToPagedList(input);
return data.Adapt<SqlSugarPagedList<AlarmVariable>>();
return data;
}
/// <summary>
@@ -100,12 +93,12 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpPost("checkRealAlarm")]
[DisplayName("确认实时报警")]
public async Task CheckRealAlarm(string variableName)
public async Task CheckRealAlarm(long variableId)
{
if (GlobalData.ReadOnlyRealAlarmVariables.TryGetValue(variableName, out var variable))
if (GlobalData.ReadOnlyRealAlarmIdVariables.TryGetValue(variableId, out var variable))
{
await GlobalData.SysUserService.CheckApiDataScopeAsync(variable.CreateOrgId, variable.CreateUserId).ConfigureAwait(false);
GlobalData.AlarmHostedService.ConfirmAlarm(variableName);
GlobalData.AlarmHostedService.ConfirmAlarm(variableId);
}
}
@@ -163,7 +156,17 @@ public class DevicePageInput : BasePageInput
public PluginTypeEnum? PluginType { get; set; }
}
public class AlarmVariablePageInput : BasePageInput
{
/// <inheritdoc/>
public string? DeviceName { get; set; }
/// <inheritdoc/>
public string Name { get; set; }
/// <inheritdoc/>
public string RegisterAddress { get; set; }
}
public class VariablePageInput : BasePageInput
{

View File

@@ -70,7 +70,7 @@ public abstract class BusinessBase : DriverBase
{
LogMessage?.LogInformation("Refresh variable");
// 获取与当前设备相关的变量,CurrentDevice.VariableRuntimes并不适用于业务插件
var variableRuntimes = GlobalData.Variables.Where(a =>
var variableRuntimes = GlobalData.IdVariables.Where(a =>
{
if (!a.Value.Enable) return false;
if (a.Value.VariablePropertys?.TryGetValue(DeviceId, out var values) == true)
@@ -95,10 +95,10 @@ public abstract class BusinessBase : DriverBase
}
);
VariableRuntimes = variableRuntimes.ToDictionary();
IdVariableRuntimes = variableRuntimes.ToDictionary();
// 获取当前设备需要采集的设备
CollectDevices = GlobalData.GetEnableDevices().Where(a => VariableRuntimes.Select(b => b.Value.DeviceId).ToHashSet().Contains(a.Key)).ToDictionary(a => a.Key, a => a.Value);
CollectDevices = GlobalData.GetEnableDevices().Where(a => IdVariableRuntimes.Select(b => b.Value.DeviceId).ToHashSet().Contains(a.Key)).ToDictionary(a => a.Key, a => a.Value);
return Task.CompletedTask;
}

View File

@@ -41,6 +41,11 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
_exT2TimerTick = new(_businessPropertyWithCacheInterval.BusinessInterval);
GlobalData.AlarmChangedEvent -= AlarmValueChange;
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
{
AlarmValueChange(a.Value);
});
GlobalData.AlarmChangedEvent += AlarmValueChange;
// 解绑全局数据的事件
GlobalData.VariableValueChangeEvent -= VariableValueChange;
@@ -63,7 +68,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
if (_businessPropertyWithCacheInterval.IsAllVariable)
{
LogMessage?.LogInformation("Refresh variable");
VariableRuntimes = new(GlobalData.GetEnableVariables());
IdVariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
@@ -79,7 +84,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
VariableRuntimes.ForEach(a =>
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
@@ -151,7 +156,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
if (_exTTimerTick.IsTickHappen())
{
// 间隔推送全部变量
foreach (var variableRuntime in VariableRuntimes.Select(a => a.Value))
foreach (var variableRuntime in IdVariableRuntimes.Select(a => a.Value))
{
VariableTimeInterval(variableRuntime, variableRuntime.Adapt<VariableBasicData>());
}
@@ -226,7 +231,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
// 检查当前设备的变量是否包含此报警变量,如果包含,则触发报警变量的变化处理方法
if (VariableRuntimes.ContainsKey(alarmVariable.Name))
if (IdVariableRuntimes.ContainsKey(alarmVariable.Id))
AlarmChange(alarmVariable);
}
}
@@ -262,7 +267,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
// 检查当前设备的变量是否包含此变量,如果包含,则触发变量的变化处理方法
if (VariableRuntimes.ContainsKey(variable.Name))
if (IdVariableRuntimes.ContainsKey(variable.Id))
VariableChange(variableRuntime, variable);
}
}

View File

@@ -69,7 +69,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
if (_businessPropertyWithCacheInterval.IsAllVariable)
{
LogMessage?.LogInformation("Refresh variable");
VariableRuntimes = new(GlobalData.GetEnableVariables());
IdVariableRuntimes = GlobalData.GetEnableVariables().ToDictionary();
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
@@ -83,7 +83,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
VariableRuntimes.ForEach(a =>
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
@@ -147,7 +147,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
if (_exTTimerTick.IsTickHappen())
{
// 上传所有变量信息
foreach (var variableRuntime in VariableRuntimes.Select(a => a.Value))
foreach (var variableRuntime in IdVariableRuntimes.Select(a => a.Value))
{
VariableTimeInterval(variableRuntime, variableRuntime.Adapt<VariableBasicData>());
}
@@ -198,7 +198,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
/// </summary>
/// <param name="variableRuntime">变量运行时对象</param>
/// <param name="variable">变量数据对象</param>
protected virtual void VariableChange(VariableRuntime variableRuntime, VariableData variable)
protected virtual void VariableChange(VariableRuntime variableRuntime, VariableBasicData variable)
{
}
/// <summary>
@@ -245,7 +245,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
// 检查当前设备是否包含该变量,并进行相应处理
if (VariableRuntimes.ContainsKey(variableRuntime.Name))
if (IdVariableRuntimes.ContainsKey(variableRuntime.Id))
VariableChange(variableRuntime, variable);
}
}

View File

@@ -59,6 +59,276 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
}
#region
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics);
foreach (var group in groups)
{
// 上传主题
// 获取预定义的报警主题
string topic = _businessPropertyWithCacheIntervalScript.AlarmTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
// 如果是报警列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
string json = data.Select(a => a).ToList().ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics).ToList();
if (groups.Count > 0)
{
foreach (var group in groups)
{
// 上传主题
// 获取预定义的设备主题
string topic = _businessPropertyWithCacheIntervalScript.DeviceTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
// 如果是设备列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
string json = data.Select(a => a).ToList().ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics).ToList();
if (groups.Count > 0)
{
foreach (var group in groups)
{
// 上传主题
// 获取预定义的变量主题
string topic = _businessPropertyWithCacheIntervalScript.VariableTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
string json = data.Select(a => a).ToList().ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicJson> GetVariableBasicData(IEnumerable<VariableBasicData> item)
{
IEnumerable<VariableBasicData>? data = null;
if (!_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel.IsNullOrWhiteSpace())
{
return GetVariable(item.Cast<VarModel>());
}
else
{
data = item;
}
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics).ToList();
if (groups.Count > 0)
{
foreach (var group in groups)
{
// 上传主题
// 获取预定义的变量主题
string topic = _businessPropertyWithCacheIntervalScript.VariableTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).GroupBy(a => a.DeviceName, b => b).ToDictionary(a => a.Key, b => b.ToList()).ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
string json = data.Select(a => a).GroupBy(a => a.DeviceName, b => b).ToDictionary(a => a.Key, b => b.ToList()).ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
@@ -138,135 +408,6 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
}
}
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics);
foreach (var group in groups)
{
// 上传主题
// 获取预定义的报警主题
string topic = _businessPropertyWithCacheIntervalScript.AlarmTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
// 如果是报警列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
string json = data.Select(a => a).ToList().ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics).ToList();
if (groups.Count > 0)
{
foreach (var group in groups)
{
// 上传主题
// 获取预定义的设备主题
string topic = _businessPropertyWithCacheIntervalScript.DeviceTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
// 如果是设备列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
string json = data.Select(a => a).ToList().ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicArray> GetDeviceTopicArray(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
@@ -332,72 +473,6 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
return topicJsonList;
}
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics).ToList();
if (groups.Count > 0)
{
foreach (var group in groups)
{
// 上传主题
// 获取预定义的变量主题
string topic = _businessPropertyWithCacheIntervalScript.VariableTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
string json = group.Select(a => a).ToList().ToJsonNetString();
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
string json = JsonExtensions.ToJsonNetString(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
string json = data.Select(a => a).ToList().ToJsonNetString();
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
else
{
foreach (var group in data)
{
string json = JsonExtensions.ToJsonNetString(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
}
}
return topicJsonList;
}
protected List<TopicArray> GetVariableTopicArray(IEnumerable<VarModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
@@ -463,5 +538,81 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
return topicJsonList;
}
protected List<TopicArray> GetVariableBasicDataTopicArray(IEnumerable<VariableBasicData> item)
{
IEnumerable<VariableBasicData>? data = null;
if (!_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel.IsNullOrWhiteSpace())
{
return GetVariableTopicArray(item.Cast<VarModel>());
}
else
{
data = item;
}
List<TopicArray> topicJsonList = new List<TopicArray>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics).ToList();
if (groups.Count > 0)
{
foreach (var group in groups)
{
// 上传主题
// 获取预定义的变量主题
string topic = _businessPropertyWithCacheIntervalScript.VariableTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Count; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var json = Serialize(data.Select(a => a).ToList());
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
}
}
return topicJsonList;
}
#endregion
}

View File

@@ -57,7 +57,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
if (_businessPropertyWithCacheInterval.IsAllVariable)
{
LogMessage?.LogInformation("Refresh variable");
VariableRuntimes = new(GlobalData.GetEnableVariables());
IdVariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
else
@@ -66,7 +66,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
}
// 触发一次变量值变化事件
VariableRuntimes.ForEach(a =>
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
@@ -105,7 +105,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
if (_exTTimerTick.IsTickHappen())
{
//间隔推送全部变量
foreach (var variableRuntime in VariableRuntimes.Select(a => a.Value))
foreach (var variableRuntime in IdVariableRuntimes.Select(a => a.Value))
{
VariableTimeInterval(variableRuntime, variableRuntime.Adapt<VariableBasicData>());
}
@@ -165,7 +165,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
//筛选
if (VariableRuntimes.ContainsKey(variableRuntime.Name))
if (IdVariableRuntimes.ContainsKey(variableRuntime.Id))
VariableChange(variableRuntime, variable);
}
}

View File

@@ -53,10 +53,10 @@ public abstract class CollectBase : DriverBase
{
LogMessage?.LogInformation("Refresh variable");
var currentDevice = CurrentDevice;
VariableRuntimes = currentDevice.VariableRuntimes.Where(a => a.Value.Enable).ToDictionary(a => a.Value.Name, a => a.Value);
IdVariableRuntimes = currentDevice.IdVariableRuntimes.Where(a => a.Value.Enable).ToDictionary();
//预热脚本,加速编译
VariableRuntimes.Where(a => !string.IsNullOrWhiteSpace(a.Value.ReadExpressions))
IdVariableRuntimes.Where(a => !string.IsNullOrWhiteSpace(a.Value.ReadExpressions))
.Select(b => b.Value.ReadExpressions).ToHashSet().ParallelForEach(script =>
{
try
@@ -72,7 +72,7 @@ public abstract class CollectBase : DriverBase
{
// 连读打包
// 从收集的变量运行时信息中筛选需要读取的变量
var tags = VariableRuntimes.Select(a => a.Value)
var tags = IdVariableRuntimes.Select(a => a.Value)
.Where(it => it.ProtectType != ProtectTypeEnum.WriteOnly
&& string.IsNullOrEmpty(it.OtherMethod)
&& !string.IsNullOrEmpty(it.RegisterAddress));
@@ -109,7 +109,7 @@ public abstract class CollectBase : DriverBase
try
{
// 初始化动态方法
var variablesMethod = VariableRuntimes.Select(a => a.Value).Where(it => !string.IsNullOrEmpty(it.OtherMethod));
var variablesMethod = IdVariableRuntimes.Select(a => a.Value).Where(it => !string.IsNullOrEmpty(it.OtherMethod));
// 处理可读的动态方法
{
@@ -472,7 +472,7 @@ public abstract class CollectBase : DriverBase
}
else if (variableRuntime.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase))
{
variableRuntime.SetValue(default, dateTime);
variableRuntime.SetValue(variableRuntime, dateTime);
}
}

View File

@@ -446,7 +446,7 @@ public abstract class DriverBase : DisposableObject, IDriver
/// <summary>
/// 当前关联的变量
/// </summary>
public Dictionary<string, VariableRuntime> VariableRuntimes { get; protected set; } = new();
public Dictionary<long, VariableRuntime> IdVariableRuntimes { get; protected set; } = new();
/// <summary>
/// 是否连接成功

View File

@@ -36,7 +36,7 @@ namespace ThingsGateway.Gateway.Application
bool Pause { get; }
string PluginDirectory { get; }
List<IEditorItem> PluginPropertyEditorItems { get; }
Dictionary<string, VariableRuntime> VariableRuntimes { get; }
Dictionary<long, VariableRuntime> IdVariableRuntimes { get; }
IDeviceThreadManage DeviceThreadManage { get; }
bool IsConnected();

View File

@@ -34,6 +34,7 @@ public class Device : BaseDataEntity, IValidatableObject
[SugarColumn(ColumnDescription = "名称", Length = 200)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[Required]
[RegularExpression(@"^[^.]*$", ErrorMessage = "The field {0} cannot contain a dot ('.')")]
public virtual string Name { get; set; }
/// <summary>

View File

@@ -35,6 +35,13 @@ public class RpcLog : PrimaryIdEntity
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public string OperateSource { get; set; }
/// <summary>
/// 操作设备
///</summary>
[SugarColumn(ColumnDescription = "操作设备", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public string OperateDevice { get; set; }
/// <summary>
/// 操作对象
///</summary>

View File

@@ -28,7 +28,7 @@ namespace ThingsGateway.Gateway.Application;
[SugarTable("variable", TableDescription = "设备变量表")]
[Tenant(SqlSugarConst.DB_Custom)]
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
[SugarIndex("unique_variable_name", nameof(Variable.Name), OrderByType.Asc, true)]
[SugarIndex("unique_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
public class Variable : BaseDataEntity, IValidatableObject
{
/// <summary>
@@ -47,6 +47,7 @@ public class Variable : BaseDataEntity, IValidatableObject
[SugarColumn(ColumnDescription = "变量名称", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 1)]
[Required]
[RegularExpression(@"^[^.]*$", ErrorMessage = "The field {0} cannot contain a dot ('.')")]
public virtual string Name { get; set; }
/// <summary>

View File

@@ -64,10 +64,6 @@ public static class GlobalData
/// </summary>
public static event VariableAlarmEventHandler? AlarmChangedEvent;
/// <summary>
/// 只读的通道字典,提供对通道的只读访问
/// </summary>
public static IReadOnlyDictionary<long, ChannelRuntime> ReadOnlyChannels => Channels;
public static async Task<IEnumerable<KeyValuePair<long, ChannelRuntime>>> GetCurrentUserChannels()
{
@@ -87,22 +83,19 @@ public static class GlobalData
return IdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId);
}
public static async Task<IEnumerable<KeyValuePair<string, VariableRuntime>>> GetCurrentUserVariables()
public static async Task<IEnumerable<KeyValuePair<long, AlarmVariable>>> GetCurrentUserRealAlarmVariables()
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
return Variables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId);
}
public static async Task<IEnumerable<KeyValuePair<string, VariableRuntime>>> GetCurrentUserRealAlarmVariables()
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
return RealAlarmVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId);
}
public static async Task<IEnumerable<KeyValuePair<long, VariableRuntime>>> GetCurrentUserAlarmEnableVariables()
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
return AlarmEnableVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
return AlarmEnableIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId);
}
@@ -130,15 +123,17 @@ public static class GlobalData
return Channels.Where(a => a.Value.Enable);
}
/// <summary>
/// 只读的设备字典,提供对设备的只读访问
/// </summary>
public static IReadOnlyDictionary<long, DeviceRuntime> ReadOnlyIdDevices => IdDevices;
/// <summary>
/// 只读的设备字典,提供对设备的只读访问
/// </summary>
public static IReadOnlyDictionary<string, DeviceRuntime> ReadOnlyDevices => Devices;
public static VariableRuntime GetVariable(string deviceName, string variableName)
{
if (Devices.TryGetValue(deviceName, out var device))
{
if (device.VariableRuntimes.TryGetValue(variableName, out var variable))
{
return variable;
}
}
return null;
}
/// <summary>
/// 只读的通道字典,提供对通道的只读访问
@@ -168,28 +163,10 @@ public static class GlobalData
return false;
}
/// <summary>
/// 报警列表
/// </summary>
public static IReadOnlyDictionary<long, VariableRuntime> ReadOnlyAlarmEnableVariables => AlarmEnableVariables;
/// <summary>
/// 实时报警列表
/// </summary>
public static IReadOnlyDictionary<string, VariableRuntime> ReadOnlyRealAlarmVariables => RealAlarmVariables;
/// <summary>
/// 只读的变量字典
/// </summary>
public static IReadOnlyDictionary<string, VariableRuntime> ReadOnlyVariables => Variables;
/// <summary>
/// 只读的变量字典
/// </summary>
public static IReadOnlyDictionary<long, VariableRuntime> ReadOnlyIdVariables => IdVariables;
public static IEnumerable<KeyValuePair<string, VariableRuntime>> GetEnableVariables()
public static IEnumerable<KeyValuePair<long, VariableRuntime>> GetEnableVariables()
{
return Variables.Where(a => a.Value.Enable);
return IdDevices.SelectMany(a => a.Value.IdVariableRuntimes).Where(a => a.Value.Enable);
}
@@ -407,13 +384,29 @@ public static class GlobalData
public static bool StartBusinessChannelEnable => GatewayRedundantSerivce?.StartBusinessChannelEnable ?? true;
#endregion
/// <summary>
/// 只读的通道字典,提供对通道的只读访问
/// </summary>
public static IReadOnlyDictionary<long, ChannelRuntime> ReadOnlyChannels => Channels;
/// <summary>
/// 内部使用的通道字典,用于存储通道对象
/// </summary>
internal static ConcurrentDictionary<long, ChannelRuntime> Channels { get; } = new();
/// <summary>
/// 只读的设备字典,提供对设备的只读访问
/// </summary>
public static IReadOnlyDictionary<long, DeviceRuntime> ReadOnlyIdDevices => IdDevices;
/// <summary>
/// 只读的设备字典,提供对设备的只读访问
/// </summary>
public static IReadOnlyDictionary<string, DeviceRuntime> ReadOnlyDevices => Devices;
/// <summary>
/// 内部使用的设备字典,用于存储设备对象
/// </summary>
@@ -422,24 +415,36 @@ public static class GlobalData
/// 内部使用的设备字典,用于存储设备对象
/// </summary>
internal static ConcurrentDictionary<long, DeviceRuntime> IdDevices { get; } = new();
/// <summary>
/// 内部使用的报警配置变量字典
/// </summary>
internal static ConcurrentDictionary<long, VariableRuntime> AlarmEnableIdVariables { get; } = new();
/// <summary>
/// 内部使用的报警配置变量字典
/// </summary>
internal static ConcurrentDictionary<long, AlarmVariable> RealAlarmIdVariables { get; } = new();
/// <summary>
/// 内部使用的变量字典,用于存储变量对象
/// </summary>
internal static ConcurrentDictionary<long, VariableRuntime> IdVariables { get; } = new();
/// <summary>
/// 内部使用的变量字典,用于存储变量对象
/// </summary>
internal static ConcurrentDictionary<string, VariableRuntime> Variables { get; } = new();
/// <summary>
/// 内部使用的报警配置变量字典
/// </summary>
internal static ConcurrentDictionary<long, VariableRuntime> AlarmEnableVariables { get; } = new();
/// <summary>
/// 内部使用的报警配置变量字典
/// 实时报警列表
/// </summary>
internal static ConcurrentDictionary<string, VariableRuntime> RealAlarmVariables { get; } = new();
public static IReadOnlyDictionary<long, AlarmVariable> ReadOnlyRealAlarmIdVariables => RealAlarmIdVariables;
/// <summary>
/// 只读的变量字典
/// </summary>
public static IReadOnlyDictionary<long, VariableRuntime> ReadOnlyIdVariables => IdVariables;
#region
/// <summary>
/// 报警状态变化处理方法,用于处理报警状态变化时的逻辑
/// </summary>
@@ -490,4 +495,6 @@ public static class GlobalData
VariableCollectChangeEvent.Invoke(variableRuntime);
}
}
#endregion
}

View File

@@ -223,6 +223,7 @@
"LogTime": "LogTime",
"OperateSource": "OperationSource",
"OperateObject": "OperationObject",
"OperateDevice": "OperateDevice",
"OperateMethod": "RPCMethod",
"IsSuccess": "OperationResult",
"ParamJson": "RequestParameters",
@@ -406,6 +407,7 @@
"ThingsGateway.Gateway.Application.RpcLogPageInput": {
"SearchDate": "SearchDate",
"OperateObject": "OperateObject",
"OperateDevice": "OperateDevice",
"OperateSource": "OperateSource"
},
"ThingsGateway.Gateway.Application.BackendLogPageInput": {
@@ -415,6 +417,7 @@
},
"ThingsGateway.Gateway.Application.RpcService": {
"VariableNotNull": "{0} variable does not exist",
"DeviceNotNull": "Device name does not exist",
"VariableReadOnly": "{0} variable is read-only",
"VariableWriteDisable": "{0} variable is not allowed to write",
"DriverNotNull": "System error, corresponding collection device does not exist, please try again later",
@@ -425,7 +428,8 @@
"VariableName": "Variable",
"RedundantDeviceName": "Redundant Device",
"ChannelName": "Channel",
"DeviceName": "Device"
"DeviceName": "Device",
"BusinessDeviceName": "BusinessDevice"
},
"ThingsGateway.Gateway.Application.ProtectTypeEnum": {
"ReadOnly": "ReadOnly",

View File

@@ -228,6 +228,7 @@
"LogTime": "时间",
"OperateSource": "操作源",
"OperateObject": "操作对象",
"OperateDevice": "操作设备",
"OperateMethod": "RPC方法",
"IsSuccess": "操作结果",
"ParamJson": "请求参数",
@@ -444,6 +445,7 @@
"ThingsGateway.Gateway.Application.RpcLogPageInput": {
"SearchDate": "时间区间",
"OperateObject": "操作对象",
"OperateDevice": "操作设备",
"OperateSource": "操作源"
},
"ThingsGateway.Gateway.Application.BackendLogPageInput": {
@@ -452,6 +454,7 @@
"LogSource": "日志源"
},
"ThingsGateway.Gateway.Application.RpcService": {
"DeviceNotNull": "设备名称不存在",
"VariableNotNull": " {0} 变量不存在",
"VariableReadOnly": " {0} 变量只读",
"VariableWriteDisable": " {0} 变量不允许写入",
@@ -463,7 +466,8 @@
"VariableName": "变量",
"RedundantDeviceName": "冗余设备",
"ChannelName": "通道",
"DeviceName": "设备"
"DeviceName": "设备",
"BusinessDeviceName": "业务设备"
},
"ThingsGateway.Gateway.Application.ProtectTypeEnum": {

View File

@@ -33,6 +33,21 @@ public class AlarmVariable : PrimaryIdEntity, IDBHistoryAlarm
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? Description { get; set; }
/// <inheritdoc cref="IBaseDataEntity.CreateOrgId"/>
[SugarColumn(ColumnDescription = "组织Id", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public long CreateOrgId { get; set; }
/// <inheritdoc cref="IBaseEntity.CreateUserId"/>
[SugarColumn(ColumnDescription = "创建用户Id", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public long CreateUserId { get; set; }
/// <inheritdoc cref="Variable.DeviceId"/>
[SugarColumn(ColumnDescription = "设备Id", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public long DeviceId { get; set; }
/// <inheritdoc cref="VariableRuntime.DeviceName"/>
[SugarColumn(ColumnDescription = "设备名称", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]

View File

@@ -8,14 +8,74 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Newtonsoft.Json;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 设备业务变化数据
/// </summary>
public class DeviceBasicData : IPrimaryIdEntity
{
/// <inheritdoc cref="PrimaryIdEntity.Id"/>
public long Id { get; set; }
/// <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.PluginName"/>
public string PluginName { get; set; }
/// <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; }
/// <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; }
/// <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; }
/// <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; }
/// <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; }
/// <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; }
}
/// <summary>
/// 变量业务变化数据
/// </summary>
public class VariableData : IPrimaryIdEntity
public class VariableBasicData : IPrimaryIdEntity
{
/// <inheritdoc cref="PrimaryIdEntity.Id"/>
public long Id { get; set; }
@@ -42,10 +102,36 @@ public class VariableData : IPrimaryIdEntity
/// <inheritdoc cref="VariableRuntime.IsOnline"/>
public bool IsOnline { get; set; }
/// <inheritdoc cref="VariableRuntime.DeviceRuntime"/>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceBasicData DeviceRuntime { 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; }
/// <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; }
/// <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; }
/// <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; }
/// <inheritdoc cref="Variable.ProtectType"/>
public ProtectTypeEnum ProtectType { get; set; }
/// <inheritdoc cref="Variable.DataType"/>
public DataTypeEnum DataType { get; set; }
/// <inheritdoc cref="Device.Remark1"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
@@ -73,30 +159,4 @@ public class VariableData : IPrimaryIdEntity
public string Remark5 { get; set; }
}
/// <summary>
/// 变量基础数据
/// </summary>
public class VariableBasicData : VariableData
{
/// <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; }
/// <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; }
/// <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; }
/// <inheritdoc cref="Variable.ProtectType"/>
public ProtectTypeEnum ProtectType { get; set; }
/// <inheritdoc cref="Variable.DataType"/>
public DataTypeEnum DataType { get; set; }
}

View File

@@ -1,75 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 设备业务变化数据
/// </summary>
public class DeviceData : IPrimaryIdEntity
{
/// <inheritdoc cref="PrimaryIdEntity.Id"/>
public long Id { get; set; }
/// <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="Device.Remark1"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Remark1 { get; set; }
/// <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; }
/// <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; }
/// <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; }
/// <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; }
}
/// <summary>
/// 设备业务基础数据
/// </summary>
public class DeviceBasicData : DeviceData
{
/// <inheritdoc cref="DeviceRuntime.PluginName"/>
public string PluginName { get; set; }
/// <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; }
}

View File

@@ -91,7 +91,7 @@ public class DeviceRuntime : Device, IDisposable
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get => VariableRuntimes == null ? 0 : VariableRuntimes.Count; }
public int DeviceVariableCount { get => IdVariableRuntimes == null ? 0 : IdVariableRuntimes.Count; }
/// <summary>
/// 暂停
@@ -129,9 +129,16 @@ public class DeviceRuntime : Device, IDisposable
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AdaptIgnore]
[AutoGenerateColumn(Ignore = true)]
public IReadOnlyDictionary<long, VariableRuntime>? ReadVariableRuntimes => VariableRuntimes;
public IReadOnlyDictionary<long, VariableRuntime>? ReadOnlyIdVariableRuntimes => IdVariableRuntimes;
/// <summary>
/// 设备变量
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public IReadOnlyDictionary<string, VariableRuntime>? ReadOnlyVariableRuntimes => VariableRuntimes;
/// <summary>
/// 设备变量
@@ -140,7 +147,17 @@ public class DeviceRuntime : Device, IDisposable
[Newtonsoft.Json.JsonIgnore]
[AdaptIgnore]
[AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<long, VariableRuntime>? VariableRuntimes { get; set; } = new(Environment.ProcessorCount, 1000);
internal ConcurrentDictionary<long, VariableRuntime>? IdVariableRuntimes { get; set; } = new(Environment.ProcessorCount, 1000);
/// <summary>
/// 设备变量
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AdaptIgnore]
[AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; set; } = new(Environment.ProcessorCount, 1000);
#region
/// <summary>

View File

@@ -12,6 +12,8 @@ using BootstrapBlazor.Components;
using Mapster;
using SqlSugar;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife.Json.Extension;
@@ -22,9 +24,14 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public class VariableRuntime : Variable, IVariable, IDisposable
{
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
[IgnoreExcel]
public override int? SortCode { get; set; }
private bool _isOnline;
private bool? _isOnlineChanged;
protected object? _value;
/// <summary>
/// 动态变量
/// </summary>
@@ -42,7 +49,6 @@ public class VariableRuntime : Variable, IVariable, IDisposable
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AdaptIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceRuntime? DeviceRuntime { get; set; }
@@ -321,26 +327,28 @@ public class VariableRuntime : Variable, IVariable, IDisposable
#endregion
public void Init(DeviceRuntime deviceRuntime)
{
GlobalData.AlarmEnableVariables.TryRemove(Id, out _);
if (GlobalData.RealAlarmVariables.TryRemove(Name, out var oldAlarm))
GlobalData.AlarmEnableIdVariables.TryRemove(Id, out _);
if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
{
oldAlarm.EventType = EventTypeEnum.Finish;
oldAlarm.EventTime = DateTime.Now;
GlobalData.AlarmChange(this.Adapt<AlarmVariable>());
}
DeviceRuntime?.VariableRuntimes?.TryRemove(Id, out _);
DeviceRuntime?.VariableRuntimes?.TryRemove(Name, out _);
DeviceRuntime?.IdVariableRuntimes?.TryRemove(Id, out _);
DeviceRuntime = deviceRuntime;
DeviceRuntime.VariableRuntimes.TryAdd(Id, this);
DeviceRuntime.IdVariableRuntimes.TryAdd(Id, this);
DeviceRuntime?.VariableRuntimes?.TryAdd(Name, this);
GlobalData.IdVariables.TryRemove(Id, out _);
GlobalData.IdVariables.TryAdd(Id, this);
GlobalData.Variables.TryRemove(Name, out _);
GlobalData.Variables.TryAdd(Name, this);
if (AlarmEnable)
{
GlobalData.AlarmEnableVariables.TryAdd(Id, this);
GlobalData.AlarmEnableIdVariables.TryAdd(Id, this);
}
}
@@ -348,13 +356,13 @@ public class VariableRuntime : Variable, IVariable, IDisposable
public void Dispose()
{
DeviceRuntime?.VariableRuntimes?.TryRemove(Id, out _);
DeviceRuntime?.IdVariableRuntimes?.TryRemove(Id, out _);
DeviceRuntime?.VariableRuntimes?.TryRemove(Name, out _);
GlobalData.IdVariables.TryRemove(Id, out _);
GlobalData.Variables.TryRemove(Name, out _);
GlobalData.AlarmEnableVariables.TryRemove(Id, out _);
if (GlobalData.RealAlarmVariables.TryRemove(Name, out var oldAlarm))
GlobalData.AlarmEnableIdVariables.TryRemove(Id, out _);
if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
{
oldAlarm.EventType = EventTypeEnum.Finish;
oldAlarm.EventTime = DateTime.Now;
@@ -367,8 +375,11 @@ public class VariableRuntime : Variable, IVariable, IDisposable
/// <inheritdoc/>
public async ValueTask<OperResult> RpcAsync(string value, string? executive = "brower", CancellationToken cancellationToken = default)
{
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, string>() { { Name, value } }, cancellationToken).ConfigureAwait(false);
return data.FirstOrDefault().Value;
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>()
{
{ DeviceName, new Dictionary<string, string>() { { Name,value} } }
}, cancellationToken).ConfigureAwait(false);
return data.FirstOrDefault().Value.FirstOrDefault().Value;
}
public void SetErrorMessage(string? lastErrorMessage)

View File

@@ -92,7 +92,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
channelRuntime.DeviceRuntimes.ParallelForEach(a =>
{
a.Value.VariableRuntimes.ParallelForEach(v => v.Value.Dispose());
a.Value.IdVariableRuntimes.ParallelForEach(v => v.Value.Dispose());
a.Value.Dispose();
});
@@ -197,7 +197,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
public async Task RestartChannelAsync(IEnumerable<ChannelRuntime> oldChannelRuntimes)
{
oldChannelRuntimes.SelectMany(a => a.DeviceRuntimes.SelectMany(a => a.Value.VariableRuntimes)).ParallelForEach(a => a.Value.SafeDispose());
oldChannelRuntimes.SelectMany(a => a.DeviceRuntimes.SelectMany(a => a.Value.IdVariableRuntimes)).ParallelForEach(a => a.Value.SafeDispose());
oldChannelRuntimes.SelectMany(a => a.DeviceRuntimes).ParallelForEach(a => a.Value.SafeDispose());
oldChannelRuntimes.ParallelForEach(a => a.SafeDispose());
var ids = oldChannelRuntimes.Select(a => a.Id).ToHashSet();

View File

@@ -156,7 +156,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
if (channels == null)
{
db ??= GetDB();
channels = await db.Queryable<Channel>().ToListAsync().ConfigureAwait(false);
channels = await db.Queryable<Channel>().OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
App.CacheService.Set(key, channels);
}
return channels;
@@ -301,8 +301,8 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
{
if (item.Key == ExportString.ChannelName)
{
var collectChannelImports = ((ImportPreviewOutput<Channel>)item.Value).Data;
channels = new List<Channel>(collectChannelImports.Values);
var channelImports = ((ImportPreviewOutput<Channel>)item.Value).Data;
channels = channelImports.Select(a => a.Value).ToList();
break;
}
}

View File

@@ -58,7 +58,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
}
if (deviceRuntime != null)
{
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
deviceRuntime.IdVariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
}
}
@@ -100,7 +100,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
foreach (var deviceRuntime in deviceRuntimes)
{
//也需要删除变量
deviceRuntime.VariableRuntimes.ParallelForEach(a =>
deviceRuntime.IdVariableRuntimes.ParallelForEach(a =>
{
a.Value.Dispose();
});
@@ -165,7 +165,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
}
if (deviceRuntime != null)
{
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
deviceRuntime.IdVariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
}
}
@@ -213,7 +213,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
await deviceThreadManage.RemoveDeviceAsync(deviceRuntime.Id).ConfigureAwait(false);
}
deviceRuntime.Dispose();
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
deviceRuntime.IdVariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
}
if (GlobalData.Channels.TryGetValue(newDeviceRuntime.ChannelId, out var channelRuntime))

View File

@@ -169,7 +169,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (devices == null)
{
db ??= GetDB();
devices = await db.Queryable<Device>().ToListAsync().ConfigureAwait(false);
devices = await db.Queryable<Device>().OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
App.CacheService.Set(key, devices);
}
return devices;
@@ -437,8 +437,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
if (item.Key == ExportString.DeviceName)
{
var collectDeviceImports = ((ImportPreviewOutput<Device>)item.Value).Data;
devices = new List<Device>(collectDeviceImports.Values);
var deviceImports = ((ImportPreviewOutput<Device>)item.Value).Data;
devices = deviceImports.Select(a => a.Value).ToList();
break;
}
}

View File

@@ -59,6 +59,10 @@ public class RpcLogPageInput : ITableSearchModel
/// 操作对象
/// </summary>
public string? OperateObject { get; set; }
/// <summary>
/// 操作设备
/// </summary>
public string? OperateDevice { get; set; }
/// <summary>
/// 操作源
@@ -70,11 +74,14 @@ public class RpcLogPageInput : ITableSearchModel
/// </summary>
public DateTimeRangeValue? SearchDate { get; set; }
/// <inheritdoc/>
public IEnumerable<IFilterAction> GetSearches()
{
var ret = new List<IFilterAction>();
ret.AddIF(!string.IsNullOrEmpty(OperateSource), () => new SearchFilterAction(nameof(RpcLog.OperateSource), OperateSource));
ret.AddIF(!string.IsNullOrEmpty(OperateDevice), () => new SearchFilterAction(nameof(RpcLog.OperateDevice), OperateDevice));
ret.AddIF(!string.IsNullOrEmpty(OperateObject), () => new SearchFilterAction(nameof(RpcLog.OperateObject), OperateObject));
ret.AddIF(SearchDate != null, () => new SearchFilterAction(nameof(RpcLog.LogTime), SearchDate!.Start, FilterAction.GreaterThanOrEqual));
ret.AddIF(SearchDate != null, () => new SearchFilterAction(nameof(RpcLog.LogTime), SearchDate!.End, FilterAction.LessThanOrEqual));

View File

@@ -249,7 +249,7 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
{
// 如果是需恢复报警事件
// 如果实时报警列表中不存在该变量,则直接返回
if (!GlobalData.RealAlarmVariables.ContainsKey(item.Name))
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
{
return;
}
@@ -258,7 +258,7 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
{
// 如果是触发报警事件
// 在实时报警列表中查找该变量
if (GlobalData.RealAlarmVariables.TryGetValue(item.Name, out var variable))
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable))
{
// 如果变量已经处于相同的报警类型,则直接返回
if (item.AlarmType == alarmEnum)
@@ -342,7 +342,7 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
{
// 如果是需恢复报警事件
// 获取旧的报警信息
if (GlobalData.RealAlarmVariables.TryGetValue(item.Name, out var oldAlarm))
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmType = oldAlarm.AlarmType;
item.EventType = eventEnum;
@@ -363,23 +363,23 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
//lock (GlobalData. RealAlarmVariables)
{
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
GlobalData.RealAlarmVariables.AddOrUpdate(item.Name, a => item, (a, b) => item);
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.Adapt<AlarmVariable>(), (a, b) => item.Adapt<AlarmVariable>());
}
}
else if (item.EventType == EventTypeEnum.Finish)
{
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
GlobalData.RealAlarmVariables.TryRemove(item.Name, out _);
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
}
GlobalData.AlarmChange(item.Adapt<AlarmVariable>());
}
}
public void ConfirmAlarm(string variableName)
public void ConfirmAlarm(long variableId)
{
// 如果是确认报警事件
if (GlobalData.RealAlarmVariables.TryGetValue(variableName, out var variableRuntime))
if (GlobalData.RealAlarmIdVariables.TryGetValue(variableId, out var variableRuntime))
{
variableRuntime.EventType = EventTypeEnum.Confirm;
variableRuntime.EventTime = DateTime.Now;
@@ -403,7 +403,7 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
//Stopwatch stopwatch = Stopwatch.StartNew();
// 遍历设备变量列表
GlobalData.AlarmEnableVariables.ParallelForEach((kv, state, index) =>
GlobalData.AlarmEnableIdVariables.ParallelForEach((kv, state, index) =>
{
{
// 如果取消请求已经被触发,则结束任务

View File

@@ -17,5 +17,5 @@ public interface IAlarmHostedService : IHostedService
/// <summary>
/// 确认报警
/// </summary>
void ConfirmAlarm(string variableName);
void ConfirmAlarm(long variableId);
}

View File

@@ -17,11 +17,11 @@ using Microsoft.Extensions.Localization;
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using TouchSocket.Core;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
@@ -507,7 +507,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
if (IsCollectChannel == true)
{
saveDevices.AddRange(driver.VariableRuntimes.Where(a => a.Value.SaveValue && !a.Value.DynamicVariable).Select(a => a.Value));
saveDevices.AddRange(driver.IdVariableRuntimes.Where(a => a.Value.SaveValue && !a.Value.DynamicVariable).Select(a => a.Value));
}
// 取消驱动程序的操作
@@ -685,7 +685,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
{
//传入变量
//newDeviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.SafeDispose());
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
deviceRuntime.IdVariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
}
/// <inheritdoc/>

View File

@@ -16,8 +16,8 @@ public interface IRpcService
/// 反向RPC入口方法
/// </summary>
/// <param name="sourceDes">触发该方法的源说明</param>
/// <param name="items">指定键为变量名称,值为附带方法参数或写入值,方法参数会按逗号分割解析</param>
/// <param name="deviceDatas">指定键为变量名称,值为附带方法参数或写入值,方法参数会按逗号分割解析</param>
/// <param name="cancellationToken"><see cref="CancellationToken"/> 取消令箭</param>
/// <returns></returns>
Task<Dictionary<string, OperResult>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, string> items, CancellationToken cancellationToken = default);
Task<Dictionary<string, Dictionary<string, OperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default);
}

View File

@@ -19,6 +19,8 @@ using ThingsGateway.Extension;
using ThingsGateway.Extension.Generic;
using ThingsGateway.NewLife.Json.Extension;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -40,83 +42,104 @@ internal sealed class RpcService : IRpcService
private IStringLocalizer Localizer { get; }
/// <inheritdoc />
public async Task<Dictionary<string, OperResult>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, string> items, CancellationToken cancellationToken = default)
public async Task<Dictionary<string, Dictionary<string, OperResult>>> 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();
// 用于存储结果的并发字典
ConcurrentDictionary<string, OperResult> results = new();
var dict = GlobalData.Variables;
ConcurrentDictionary<string, Dictionary<string, OperResult>> results = new();
deviceDatas.ForEach(a => results.TryAdd(a.Key, new()));
var deviceDict = GlobalData.Devices;
// 对每个要操作的变量进行检查和处理
foreach (var item in items)
foreach (var deviceData in deviceDatas)
{
// 查找变量是否存在
if (!dict.TryGetValue(item.Key, out var tag))
// 查找设备是否存在
if (!deviceDict.TryGetValue(deviceData.Key, out var device))
{
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
results.TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
continue;
}
// 检查变量的保护类型和远程写入权限
if (tag.ProtectType == ProtectTypeEnum.ReadOnly)
{
results.TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
continue;
}
if (!tag.RpcWriteEnable)
{
results.TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
deviceData.Value.ForEach(a =>
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DeviceNotNull", deviceData.Key]))
);
continue;
}
// 查找变量对应的设备
var collect = tag.DeviceRuntime.Driver as CollectBase;
var collect = device.Driver as CollectBase;
if (collect == null)
{
// 如果设备不存在,则添加错误信息到结果中并继续下一个变量的处理
results.TryAdd(item.Key, new OperResult(Localizer["DriverNotNull"]));
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
deviceData.Value.ForEach(a =>
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DriverNotNull", deviceData.Key]))
);
continue;
}
// 检查设备状态,如果设备处于暂停状态,则添加相应的错误信息到结果中并继续下一个变量的处理
if (collect.CurrentDevice.DeviceStatus == DeviceStatusEnum.Pause)
{
results.TryAdd(item.Key, new OperResult(Localizer["DevicePause", collect.CurrentDevice.Name]));
deviceData.Value.ForEach(a =>
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DevicePause", deviceData.Key]))
);
continue;
}
JToken tagValue = JTokenUtil.GetJTokenFromString(item.Value);
bool isOtherMethodEmpty = string.IsNullOrEmpty(tag.OtherMethod);
var collection = isOtherMethodEmpty ? writeVariables : writeMethods;
if (collection.TryGetValue(collect, out var value))
foreach (var item in deviceData.Value)
{
value.Add(tag, tagValue);
}
else
{
collection.Add(collect, new());
collection[collect].Add(tag, tagValue);
// 查找变量是否存在
if (!device.VariableRuntimes.TryGetValue(item.Key, out var tag))
{
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
continue;
}
// 检查变量的保护类型和远程写入权限
if (tag.ProtectType == ProtectTypeEnum.ReadOnly)
{
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
continue;
}
if (!tag.RpcWriteEnable)
{
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
continue;
}
JToken tagValue = JTokenUtil.GetJTokenFromString(item.Value);
bool isOtherMethodEmpty = string.IsNullOrEmpty(tag.OtherMethod);
var collection = isOtherMethodEmpty ? writeVariables : writeMethods;
if (collection.TryGetValue(collect, out var value))
{
value.Add(tag, tagValue);
}
else
{
collection.Add(collect, new());
collection[collect].Add(tag, tagValue);
}
}
}
// 使用并行方式写入变量
await writeVariables.ParallelForEachAsync(async (item, cancellationToken) =>
await writeVariables.ParallelForEachAsync(async (driverData, cancellationToken) =>
{
try
{
// 调用设备的写入方法
var result = await item.Key.InVokeWriteAsync(item.Value, cancellationToken).ConfigureAwait(false);
var result = await driverData.Key.InVokeWriteAsync(driverData.Value, cancellationToken).ConfigureAwait(false);
// 写入日志
foreach (var resultItem in result)
{
var empty = string.IsNullOrEmpty(resultItem.Key);
string operObj = empty ? items.Select(x => x.Key).ToJsonNetString() : resultItem.Key;
string operObj = empty ? deviceDatas[driverData.Key.DeviceName].Select(x => x.Key).ToJsonNetString() : resultItem.Key;
string parJson = empty ? items.Select(x => x.Value).ToJsonNetString() : items[resultItem.Key];
string parJson = empty ? deviceDatas.Select(x => x.Value).ToJsonNetString() : deviceDatas[driverData.Key.DeviceName][resultItem.Key];
if (_rpcLogOptions.SuccessLog)
_logQueues.Enqueue(
@@ -126,6 +149,7 @@ internal sealed class RpcService : IRpcService
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,
@@ -143,12 +167,12 @@ internal sealed class RpcService : IRpcService
}
// 将结果添加到结果字典中
results.AddRange(result);
results[driverData.Key.DeviceName].AddRange(result);
}
catch (Exception ex)
{
// 将异常信息添加到结果字典中
results.AddRange(item.Value.Select((KeyValuePair<VariableRuntime, JToken> a) =>
results[driverData.Key.DeviceName].AddRange(driverData.Value.Select((KeyValuePair<VariableRuntime, JToken> a) =>
{
return new KeyValuePair<string, OperResult>(a.Key.Name, new OperResult(ex));
}));
@@ -156,14 +180,14 @@ internal sealed class RpcService : IRpcService
}, Environment.ProcessorCount, cancellationToken).ConfigureAwait(false);
// 使用并行方式执行方法
await writeMethods.ParallelForEachAsync(async (item, cancellationToken) =>
await writeMethods.ParallelForEachAsync(async (driverData, cancellationToken) =>
{
try
{
// 调用设备的写入方法
var result = await item.Key.InvokeMethodAsync(item.Value, cancellationToken).ConfigureAwait(false);
var result = await driverData.Key.InvokeMethodAsync(driverData.Value, cancellationToken).ConfigureAwait(false);
Dictionary<string, string> operateMethods = item.Value.Select(a => a.Key).ToDictionary(a => a.Name, a => a.OtherMethod!);
Dictionary<string, string> operateMethods = driverData.Value.Select(a => a.Key).ToDictionary(a => a.Name, a => a.OtherMethod!);
// 写入日志
foreach (var resultItem in result)
@@ -177,10 +201,11 @@ internal sealed class RpcService : IRpcService
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 = items[resultItem.Key]?.ToString(),
ResultJson = resultItem.Value.Content?.ToString()
ParamJson = deviceDatas[driverData.Key.DeviceName][resultItem.Key]?.ToString(),
ResultJson = resultItem.Value.Content?.ToJsonNetString()
}
);
@@ -192,11 +217,14 @@ internal sealed class RpcService : IRpcService
result[resultItem.Key] = result1;
}
}
results[driverData.Key.DeviceName].AddRange(result.ToDictionary(a => a.Key, a => (OperResult)a.Value));
}
catch (Exception ex)
{
// 将异常信息添加到结果字典中
results.AddRange(item.Value.Select((KeyValuePair<VariableRuntime, JToken> a) =>
results[driverData.Key.DeviceName].AddRange(driverData.Value.Select((KeyValuePair<VariableRuntime, JToken> a) =>
{
return new KeyValuePair<string, OperResult>(a.Key.Name, new OperResult(ex));
}));

View File

@@ -28,11 +28,11 @@ namespace ThingsGateway.Gateway.Application
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile);
Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart );
Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart);
void PreheatCache();
Task<MemoryStream> ExportMemoryStream(List<Variable> data, string devName);
Task AddDynamicVariable(IEnumerable<VariableRuntime> newVariableRuntimes, bool restart );
Task DeleteDynamicVariable(IEnumerable<long> variableIds, bool restart );
Task AddDynamicVariable(IEnumerable<VariableRuntime> newVariableRuntimes, bool restart);
Task DeleteDynamicVariable(IEnumerable<long> variableIds, bool restart);
}
}

View File

@@ -403,7 +403,7 @@ public class VariableRuntimeService : IVariableRuntimeService
}
if (deviceRuntime != null)
{
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
deviceRuntime.IdVariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
}
}

View File

@@ -62,7 +62,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
List<Channel> newChannels = new();
List<Device> newDevices = new();
List<Variable> newVariables = new();
var addressNum = 1;
// 计算每个设备分配的默认变量数
var groupVariableCount = (int)Math.Ceiling((decimal)variableCount / deviceCount);
@@ -101,13 +100,15 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
? variableCount - (deviceCount - 1) * groupVariableCount // 最后一个设备分配剩余的变量
: groupVariableCount;
var addressNum = 1;
for (int i1 = 0; i1 < currentGroupVariableCount; i1++)
{
if (addressNum > 65535) addressNum = 1;
var address = $"4{addressNum}";
addressNum++;
var id = CommonUtils.GetSingleId();
var name = $"modbusVariable{address}_{id}";
var name = $"modbus{address}";
Variable variable = new Variable();
variable.DataType = DataTypeEnum.Int16;
variable.Name = name;
@@ -177,6 +178,38 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
};
newDevices.Add(mqttDevice);
}
Channel opcuaChannel = new Channel();
Device opcuaDevice = new Device();
{
var id = CommonUtils.GetSingleId();
var name = $"opcuaChannel{id}";
opcuaChannel.ChannelType = ChannelTypeEnum.Other;
opcuaChannel.Name = name;
opcuaChannel.Id = id;
opcuaChannel.CreateUserId = UserManager.UserId;
opcuaChannel.CreateOrgId = UserManager.OrgId;
opcuaChannel.PluginName = "ThingsGateway.Plugin.OpcUa.OpcUaServer";
newChannels.Add(opcuaChannel);
}
{
var id = CommonUtils.GetSingleId();
var name = $"opcuaDevice{id}";
opcuaDevice.Name = name;
opcuaDevice.Id = id;
opcuaDevice.CreateUserId = UserManager.UserId;
opcuaDevice.CreateOrgId = UserManager.OrgId;
opcuaDevice.ChannelId = opcuaChannel.Id;
opcuaDevice.IntervalTime = "1000";
opcuaDevice.DevicePropertys = new Dictionary<string, string>
{
{"IsAllVariable", "true"}
};
newDevices.Add(opcuaDevice);
}
using var db = GetDB();
var result = await db.UseTranAsync(async () =>
@@ -295,12 +328,12 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
using var db = GetDB();
if (devId == null)
{
var deviceVariables = await db.Queryable<Variable>().Where(a => a.DeviceId > 0).ToListAsync().ConfigureAwait(false);
var deviceVariables = await db.Queryable<Variable>().OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
return deviceVariables;
}
else
{
var deviceVariables = await db.Queryable<Variable>().Where(a => a.DeviceId == devId).ToListAsync().ConfigureAwait(false);
var deviceVariables = await db.Queryable<Variable>().Where(a => a.DeviceId == devId).OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
return deviceVariables;
}
}
@@ -458,14 +491,14 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
//插件属性
//单个设备的行数据
Dictionary<string, object> driverInfo = new();
var has = deviceDicts.TryGetValue(item.Key, out var businessDevice);
if (!has)
if (!(deviceDicts.TryGetValue(item.Key, out var businessDevice) && deviceDicts.TryGetValue(variable.DeviceId, out var collectDevice)))
continue;
channelDicts.TryGetValue(businessDevice.ChannelId, out var channel);
//没有包含设备名称,手动插入
driverInfo.TryAdd(ExportString.DeviceName, businessDevice.Name);
driverInfo.TryAdd(ExportString.DeviceName, collectDevice.Name);
driverInfo.TryAdd(ExportString.BusinessDeviceName, businessDevice.Name);
driverInfo.TryAdd(ExportString.VariableName, variable.Name);
var propDict = item.Value;
@@ -588,8 +621,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
{
if (item.Key == ExportString.VariableName)
{
var variableImports = ((ImportPreviewOutput<Variable>)item.Value).Data;
variables = new List<Variable>(variableImports.Values);
var variableImports = ((ImportPreviewOutput<Dictionary<string, Variable>>)item.Value).Data;
variables = variableImports.SelectMany(a => a.Value.Select(a => a.Value)).ToList();
break;
}
}
@@ -605,27 +638,21 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
private static readonly WaitLock _cacheLock = new();
private async Task<Dictionary<string, VariableImportData>> GetVariableImportData()
private async Task<Dictionary<long, Dictionary<string, DeviecIdVariableImportData>>> GetVariableImportData()
{
var key = ThingsGatewayCacheConst.Cache_Variable;
var datas = App.CacheService.Get<Dictionary<string, VariableImportData>>(key);
var datas = App.CacheService.Get<Dictionary<long, Dictionary<string, DeviecIdVariableImportData>>>(key);
if (datas == null)
{
try
{
await _cacheLock.WaitAsync().ConfigureAwait(false);
datas = App.CacheService.Get<Dictionary<string, VariableImportData>>(key);
datas = App.CacheService.Get<Dictionary<long, Dictionary<string, DeviecIdVariableImportData>>>(key);
if (datas == null)
{
using var db = GetDB();
datas = (await db.Queryable<Variable>().Select(it => new VariableImportData()
{
Id = it.Id,
Name = it.Name,
CreateOrgId = it.CreateOrgId,
CreateUserId = it.CreateUserId
}).ToListAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
datas = (await db.Queryable<Variable>().Select<DeviecIdVariableImportData>().ToListAsync().ConfigureAwait(false)).GroupBy(a => a.DeviceId).ToDictionary(a => a.Key, a => a.ToDictionary(a => a.Name));
App.CacheService.Set(key, datas);
}
@@ -642,14 +669,15 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
{
return GetVariableImportData();
}
private sealed class VariableImportData
private sealed class DeviecIdVariableImportData
{
public long Id { get; set; }
public string Name { get; set; }
public long DeviceId { get; set; }
public long CreateOrgId { get; set; }
public long CreateUserId { get; set; }
}
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile)
{
// 上传文件并获取文件路径
@@ -668,7 +696,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
// 设备页导入预览输出
ImportPreviewOutput<Variable> deviceImportPreview = new();
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
// 获取驱动插件的全名和名称的字典
var driverPluginFullNameDict = _pluginService.GetList().ToDictionary(a => a.FullName);
@@ -685,7 +713,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (sheetName == ExportString.VariableName)
{
int row = 0;
ImportPreviewOutput<Variable> importPreviewOutput = new();
ImportPreviewOutput<Dictionary<string, Variable>> importPreviewOutput = new();
ImportPreviews.Add(sheetName, importPreviewOutput);
deviceImportPreview = importPreviewOutput;
@@ -746,7 +774,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
return;
}
if (dbVariableDicts.TryGetValue(variable.Name, out var dbvar1))
if (dbVariableDicts.TryGetValue(variable.DeviceId, out var dbvar1s) && dbvar1s.TryGetValue(variable.Name, out var dbvar1))
{
variable.Id = dbvar1.Id;
variable.CreateOrgId = dbvar1.CreateOrgId;
@@ -787,7 +815,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
}
// 将变量列表转换为字典,并赋值给导入预览输出对象的 Data 属性
importPreviewOutput.Data = variables.OrderBy(a => a.Row).ToDictionary(a => a.Name);
importPreviewOutput.Data = variables.OrderBy(a => a.Row).GroupBy(a => a.DeviceId.ToString()).ToDictionary(a => a.Key, b => b.ToDictionary(a => a.Name));
}
// 其他工作表处理
@@ -861,11 +889,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
// 转化插件名称和变量名称
item.TryGetValue(ExportString.VariableName, out var variableNameObj);
item.TryGetValue(ExportString.DeviceName, out var businessDevName);
item.TryGetValue(ExportString.BusinessDeviceName, out var businessDevName);
item.TryGetValue(ExportString.DeviceName, out var collectDevName);
deviceDicts.TryGetValue(businessDevName?.ToString(), out var businessDevice);
deviceDicts.TryGetValue(collectDevName?.ToString(), out var collectDevice);
// 如果设备名称或变量名称为空,或者找不到对应的设备,则添加错误信息到导入预览结果并返回
if (businessDevName == null || businessDevice == null)
if (businessDevName == null || businessDevice == null || collectDevName == null || collectDevice == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, Localizer["DeviceNotNull"]));
@@ -913,10 +943,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
// 获取变量名称并检查是否存在于设备导入预览数据中
var variableName = variableNameObj?.ToString();
var has = deviceImportPreview.Data.TryGetValue(variableName, out var deviceVariable);
// 如果存在,则更新变量属性字典,并添加成功信息到导入预览结果;否则,添加错误信息到导入预览结果并返回
if (has)
if (deviceImportPreview.Data.TryGetValue(collectDevice.Id.ToString(), out var deviceVariables) && deviceVariables.TryGetValue(variableName, out var deviceVariable))
{
deviceVariable.VariablePropertys ??= new();
deviceVariable.VariablePropertys?.AddOrUpdate(businessDevice.Id, dependencyProperties);

View File

@@ -1055,25 +1055,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
private TreeViewItem<ChannelDeviceTreeItem> BusinessTreeViewItem;
protected override async Task OnInitializedAsync()
{
_ = Task.Run(async () =>
{
while (!Disposed)
{
try
{
await OnClickSearch(SearchText);
await InvokeAsync(StateHasChanged);
}
catch
{
}
finally
{
await Task.Delay(5000);
}
}
});
BusinessTreeViewItem = new TreeViewItem<ChannelDeviceTreeItem>(UnknownItem)
{
@@ -1127,6 +1109,28 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
context = ExecutionContext.Capture();
ChannelRuntimeDispatchService.Subscribe(Refresh);
DeviceRuntimeDispatchService.Subscribe(Refresh);
_ = Task.Run(async () =>
{
while (!Disposed)
{
try
{
await OnClickSearch(SearchText);
await InvokeAsync(StateHasChanged);
}
catch
{
}
finally
{
await Task.Delay(5000);
}
}
});
await base.OnInitializedAsync();
}

View File

@@ -23,6 +23,8 @@ public enum ChannelDevicePluginTypeEnum
}
public class ChannelDeviceTreeItem : IEqualityComparer<ChannelDeviceTreeItem>
{
public long Id { get; set; }
public ChannelDevicePluginTypeEnum ChannelDevicePluginType { get; set; }
public DeviceRuntime DeviceRuntime { get; set; }

View File

@@ -28,11 +28,11 @@ public partial class GatewayMonitorPage
ShowChannelRuntime = channelRuntime;
if (channelRuntime.IsCollect == true)
{
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.SelectMany(a => a.Value.ReadVariableRuntimes.Select(a => a.Value));
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.SelectMany(a => a.Value.ReadOnlyIdVariableRuntimes.Select(a => a.Value));
}
else
{
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.SelectMany(a => a.Value.Driver?.VariableRuntimes?.Select(a => a.Value)).Where(a => a != null);
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.SelectMany(a => a.Value.Driver?.IdVariableRuntimes?.Select(a => a.Value)).Where(a => a != null);
}
}
@@ -41,11 +41,11 @@ public partial class GatewayMonitorPage
ShowDeviceRuntime = deviceRuntime;
if (deviceRuntime.IsCollect == true)
{
VariableRuntimes = deviceRuntime.ReadVariableRuntimes.Select(a => a.Value);
VariableRuntimes = deviceRuntime.ReadOnlyIdVariableRuntimes.Select(a => a.Value);
}
else
{
VariableRuntimes = deviceRuntime.Driver?.VariableRuntimes?
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
}
@@ -56,12 +56,12 @@ public partial class GatewayMonitorPage
if (pluginType == PluginTypeEnum.Collect)
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
VariableRuntimes = channels.Where(a => a.Value.PluginName == pluginName).SelectMany(a => a.Value.ReadDeviceRuntimes).SelectMany(a => a.Value.ReadVariableRuntimes).Select(a => a.Value);
VariableRuntimes = channels.Where(a => a.Value.PluginName == pluginName).SelectMany(a => a.Value.ReadDeviceRuntimes).SelectMany(a => a.Value.ReadOnlyIdVariableRuntimes).Select(a => a.Value);
}
else
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
VariableRuntimes = channels.Where(a => a.Value.PluginName == pluginName).SelectMany(a => a.Value.ReadDeviceRuntimes).SelectMany(a => a.Value.Driver?.VariableRuntimes).Select(a => a.Value).Where(a => a != null);
VariableRuntimes = channels.Where(a => a.Value.PluginName == pluginName).SelectMany(a => a.Value.ReadDeviceRuntimes).SelectMany(a => a.Value.Driver?.IdVariableRuntimes).Select(a => a.Value).Where(a => a != null);
}
}
else

View File

@@ -58,7 +58,7 @@
<TableColumn @bind-Field="@context.Unit" Filterable=true Sortable=true Visible="false" />
<TableColumn @bind-Field="@context.Index" Filterable=true Sortable=true Visible="false" />
<TableColumn @bind-Field="@context.Id" Visible="false" Sortable=true DefaultSort=true DefaultSortOrder="SortOrder.Asc" />
<TableColumn @bind-Field="@context.Id" Filterable=true Sortable=true Visible="false" DefaultSort=true DefaultSortOrder="SortOrder.Asc" />
@* <TableColumn Field="@context.AlarmEnable" FieldExpression=@(()=>context.AlarmEnable) Filterable Sortable="false" Visible="false" />
<TableColumn @bind-Field="@context.BoolOpenAlarmEnable" Filterable Sortable="false" Visible="false" />

View File

@@ -79,10 +79,9 @@ public partial class VariableRuntimeInfo : IDisposable
private Task<QueryData<VariableRuntime>> OnQueryAsync(QueryPageOptions options)
{
var data = Items
.WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText))
.GetQueryData(options);
.WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText))
.GetQueryData(options);
return Task.FromResult(data);
}

View File

@@ -47,6 +47,9 @@
<div class="col-12 ">
<DateTimeRange @bind-Value="model.SearchDate" ShowLabel="true" />
</div>
<div class="col-12 ">
<BootstrapInput @bind-Value="model.OperateDevice" ShowLabel="true" />
</div>
<div class="col-12 ">
<BootstrapInput @bind-Value="model.OperateObject" ShowLabel="true" />
</div>

View File

@@ -10,7 +10,7 @@
<div class="h-100">
<AdminTable @ref=table TItem="VariableRuntime"
<AdminTable @ref=table TItem="AlarmVariable"
AutoGenerateColumns="false"
ShowAdvancedSearch=false
EditDialogSize="Size.Large"
@@ -103,5 +103,5 @@
</div>
@code {
AdminTable<VariableRuntime> table;
AdminTable<AlarmVariable> table;
}

View File

@@ -15,11 +15,11 @@ namespace ThingsGateway.Gateway.Razor;
public partial class RealAlarmPage
{
private VariableRuntime? SearchModel { get; set; } = new();
private AlarmVariable? SearchModel { get; set; } = new();
#region
private static async Task<QueryData<VariableRuntime>> OnQueryAsync(QueryPageOptions options)
private static async Task<QueryData<AlarmVariable>> OnQueryAsync(QueryPageOptions options)
{
var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariables().ConfigureAwait(false);
var data = realAlarmVariables

View File

@@ -120,7 +120,7 @@ public static class ResourceUtil
{
var channelRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Channel, ChannelRuntime = channelRuntime };
var channelRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Channel, ChannelRuntime = channelRuntime, Id = channelRuntime.Id };
var channelTreeItemItem = new TreeViewItem<ChannelDeviceTreeItem>(channelRuntimeTreeItem)
{
Text = channelRuntime.ToString(),
@@ -145,7 +145,7 @@ public static class ResourceUtil
foreach (var keyValue in channelRuntime.ReadDeviceRuntimes.OrderBy(a => a.Value.DeviceStatus))
{
var deviceRuntime = keyValue.Value;
var deviceRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Device, DeviceRuntime = deviceRuntime };
var deviceRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Device, DeviceRuntime = deviceRuntime, Id = deviceRuntime.Id };
var deviceTreeItemItem = new TreeViewItem<ChannelDeviceTreeItem>(deviceRuntimeTreeItem)
{
Text = keyValue.Value.Name,
@@ -165,10 +165,10 @@ public static class ResourceUtil
channelTreeItemItem.Items.Add(deviceTreeItemItem);
}
channelTreeItemItem.Items = channelTreeItemItem.Items.OrderBy(a => a.Value.Id).ToList();
pluginItem.Items.Add(channelTreeItemItem);
}
pluginItem.Items = pluginItem.Items.OrderBy(a => a.Value.Id).ToList();
trees.Add(pluginItem);
}
@@ -208,7 +208,7 @@ public static class ResourceUtil
foreach (var channelRuntime in dict.Where(a => a.Key.PluginName == pluginName))
{
var channelRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Channel, ChannelRuntime = channelRuntime.Key };
var channelRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Channel, ChannelRuntime = channelRuntime.Key, Id = channelRuntime.Key.Id };
var channelTreeItemItem = new TreeViewItem<ChannelDeviceTreeItem>(channelRuntimeTreeItem)
{
Text = channelRuntime.ToString(),
@@ -230,7 +230,7 @@ public static class ResourceUtil
foreach (var deviceRuntime in channelRuntime.Value.OrderBy(a => a.DeviceStatus))
{
var deviceRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Device, DeviceRuntime = deviceRuntime };
var deviceRuntimeTreeItem = new ChannelDeviceTreeItem() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Device, DeviceRuntime = deviceRuntime, Id = deviceRuntime.Id };
var deviceTreeItemItem = new TreeViewItem<ChannelDeviceTreeItem>(deviceRuntimeTreeItem)
{
Text = deviceRuntime.Name,
@@ -251,10 +251,11 @@ public static class ResourceUtil
channelTreeItemItem.Items.Add(deviceTreeItemItem);
}
channelTreeItemItem.Items = channelTreeItemItem.Items.OrderBy(a => a.Value.Id).ToList();
pluginItem.Items.Add(channelTreeItemItem);
}
pluginItem.Items = pluginItem.Items.OrderBy(a => a.Value.Id).ToList();
trees.Add(pluginItem);
}

View File

@@ -30,4 +30,7 @@ public class DeviceDataWithValue
[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; }
}

View File

@@ -13,7 +13,6 @@ using Mapster;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
@@ -23,10 +22,8 @@ using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Management;
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService
{
private readonly ILogger _logger;
@@ -158,30 +155,11 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo
// 如果 online 为 true表示设备在线
if (online)
{
var deviceRunTimes = GlobalData.ReadOnlyIdDevices.Select(a => a.Value).Where(a => a.IsCollect == true).Adapt<List<DeviceDataWithValue>>();
var variableRuntimes = GlobalData.ReadOnlyVariables.Select(a => a.Value).Adapt<List<VariableDataWithValue>>();
var variableRuntimes1 = variableRuntimes.ChunkBetter(80000);
var variableRuntimes1Count = variableRuntimes1.Count();
int itemsPerList = (int)Math.Ceiling((double)deviceRunTimes.Count / variableRuntimes1Count);
var deviceRunTimes1 = deviceRunTimes.ChunkBetter(itemsPerList, true).ToList();
var deviceRunTimes = GlobalData.ReadOnlyIdDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).Adapt<List<DeviceDataWithValue>>();
int i = 0;
List<Task> tasks = new List<Task>();
foreach (var item in variableRuntimes1)
{
List<DeviceDataWithValue> devices = new();
if (deviceRunTimes1.Count >= i + 1)
{
devices = deviceRunTimes1[i].ToList();
}
var variables = item.ToList();
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
Task task = tcpDmtpService.Clients.FirstOrDefault().GetDmtpRpcActor().InvokeAsync(
nameof(ReverseCallbackServer.UpdateGatewayData), null, waitInvoke, devices, variables);
tasks.Add(task);
i++;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
await tcpDmtpService.Clients.FirstOrDefault().GetDmtpRpcActor().InvokeAsync(
nameof(ReverseCallbackServer.UpdateGatewayData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
_log?.LogTrace($"Update StandbyStation data success");
}
}

View File

@@ -18,22 +18,24 @@ namespace ThingsGateway.Management;
public partial class ReverseCallbackServer : RpcServer
{
[DmtpRpc(MethodInvoke = true)]
public void UpdateGatewayData(List<DeviceDataWithValue> deviceDatas, List<VariableDataWithValue> variableDatas)
public void UpdateGatewayData(List<DeviceDataWithValue> deviceDatas)
{
foreach (var deviceData in deviceDatas)
{
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var value))
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var device))
{
value.SetDeviceStatus(deviceData.ActiveTime, deviceData.DeviceStatus == DeviceStatusEnum.OnLine ? false : true, lastErrorMessage: deviceData.LastErrorMessage);
}
}
foreach (var variableData in variableDatas)
{
if (GlobalData.ReadOnlyVariables.TryGetValue(variableData.Name, out var value))
{
value.SetValue(variableData.RawValue, variableData.CollectTime, variableData.IsOnline);
value.SetErrorMessage(variableData.LastErrorMessage);
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);
}
}
}
}
}

View File

@@ -1,58 +0,0 @@
@namespace ThingsGateway.RulesEngine
@using ThingsGateway.Blazor.Diagrams.Components.Renderers
@using ThingsGateway.Extension.Generic
@using ThingsGateway.Gateway.Application
@using ThingsGateway.NewLife.Extension
<div class="custom-node @(Node.Selected ? " selected" : "")">
<div class="card" style="width: 400px;">
<div class="card-body">
<h6 class="card-title mb-2">@Localizer[Node.Title]</h6>
<div class="form-group">
<Select class="form-control"
@bind-Value="@Node.Text"
IsVirtualize
OnQueryAsync="(a)=>OnRedundantDevicesQuery(a)"
ShowSearch="true"
PlaceHolder="@Localizer[Node.Placeholder]"
@onpointerdown:stopPropagation
@onpointerup:stopPropagation
@onpointermove:stopPropagation />
</div>
</div>
</div>
@foreach (var port in Node.Ports)
{
<PortRenderer @key="port" Port="port"></PortRenderer>
}
</div>
@code {
[Inject]
IStringLocalizer<ThingsGateway.RulesEngine._Imports> Localizer { get; set; }
[Parameter]
public TextNode Node { get; set; }
private static async Task<QueryData<SelectedItem>> OnRedundantDevicesQuery(VirtualizeQueryOption option)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var devices = await GlobalData.GetCurrentUserAlarmEnableVariables().ConfigureAwait(false);
var items = devices.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Value.Name.Contains(option.SearchText)).Select(a => a.Value).Take(20)
.Select(a => new SelectedItem(a.Name, a.Name));
ret.TotalCount = items.Count();
ret.Items = items;
return ret;
}
}

View File

@@ -0,0 +1,12 @@
namespace ThingsGateway.RulesEngine;
public abstract class VariableNode : TextNode
{
public VariableNode(string id, Point? position = null) : base(id, position)
{
}
[ModelValue]
public string DeviceText { get; set; }
}

View File

@@ -4,20 +4,30 @@
@using ThingsGateway.Extension.Generic
@using ThingsGateway.Gateway.Application
@using ThingsGateway.NewLife.Extension
<div class="custom-node @(Node.Selected ? " selected" : "")">
<div class="custom-node variable @(Node.Selected ? " selected" : "")">
<div class="card" style="width: 400px;">
<div class="card-body">
<h6 class="card-title mb-2">@Localizer[Node.Title]</h6>
<div class="form-group">
<Select class="form-control"
@bind-Value="@Node.DeviceText"
IsVirtualize
OnQueryAsync="OnRedundantDevicesQuery"
ShowSearch="true"
@onpointerdown:stopPropagation
@onpointerup:stopPropagation
@onpointermove:stopPropagation />
<Select class="form-control mt-2"
@bind-Value="@Node.Text"
IsVirtualize
OnQueryAsync="(a)=>OnRedundantDevicesQuery(a)"
OnQueryAsync="OnRedundantVariablesQuery"
ShowSearch="true"
PlaceHolder="@Localizer[Node.Placeholder]"
@onpointerdown:stopPropagation
@onpointerup:stopPropagation
@onpointermove:stopPropagation />
</div>
</div>
</div>
@@ -28,31 +38,4 @@
}
</div>
@code {
[Inject]
IStringLocalizer<ThingsGateway.RulesEngine._Imports> Localizer { get; set; }
[Parameter]
public TextNode Node { get; set; }
private static async Task<QueryData<SelectedItem>> OnRedundantDevicesQuery(VirtualizeQueryOption option)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var devices = await GlobalData.GetCurrentUserVariables().ConfigureAwait(false);
var items = devices.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Value.Name.Contains(option.SearchText)).Select(a => a.Value).Take(20)
.Select(a => new SelectedItem(a.Name, a.Name));
ret.TotalCount = items.Count();
ret.Items = items;
return ret;
}
}

View File

@@ -0,0 +1,73 @@
// ------------------------------------------------------------------------------
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
// Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4><EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
// GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
// GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
// ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://thingsgateway.cn/
// QQȺ<51><C8BA>605534569
// ------------------------------------------------------------------------------
using ThingsGateway.Extension.Generic;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.RulesEngine
{
public partial class VariableWidget
{
[Inject]
IStringLocalizer<ThingsGateway.RulesEngine._Imports> Localizer { get; set; }
[Parameter]
public VariableNode Node { get; set; }
private static async Task<QueryData<SelectedItem>> OnRedundantDevicesQuery(VirtualizeQueryOption option)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
var items = devices.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Value.Name.Contains(option.SearchText)).Select(a => a.Value).Take(20)
.Select(a => new SelectedItem(a.Name, a.Name));
ret.TotalCount = items.Count();
ret.Items = items;
return ret;
}
private async Task<QueryData<SelectedItem>> OnRedundantVariablesQuery(VirtualizeQueryOption option)
{
await Task.CompletedTask.ConfigureAwait(false);
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
if (GlobalData.ReadOnlyDevices.TryGetValue(Node.DeviceText, out var device))
{
var items = device.ReadOnlyIdVariableRuntimes.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Value.Name.Contains(option.SearchText)).Select(a => a.Value).Take(20)
.Select(a => new SelectedItem(a.Name, a.Name));
ret.TotalCount = items.Count();
ret.Items = items;
return ret;
}
else
{
ret.TotalCount = 0;
ret.Items = new List<SelectedItem>();
return ret;
}
}
}
}

View File

@@ -5,7 +5,7 @@ using TouchSocket.Core;
namespace ThingsGateway.RulesEngine;
[CategoryNode(Category = "Actuator", ImgUrl = "_content/ThingsGateway.RulesEngine/img/Rpc.svg", Desc = nameof(VariableRpcNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(VariableWidget))]
public class VariableRpcNode : TextNode, IActuatorNode
public class VariableRpcNode : VariableNode, IActuatorNode
{
public VariableRpcNode(string id, Point? position = null) : base(id, position)
@@ -13,20 +13,20 @@ public class VariableRpcNode : TextNode, IActuatorNode
async Task<NodeOutput> IActuatorNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{
if (GlobalData.ReadOnlyVariables.TryGetValue(Text, out var value))
if (GlobalData.ReadOnlyDevices.TryGetValue(DeviceText, out var device))
{
var data = await value.RpcAsync(input.JToken.ToString(), $"RulesEngine: {RulesEngineName}", cancellationToken).ConfigureAwait(false);
if (data.IsSuccess)
LogMessage?.Trace($" VariableRpcNode - VariableName {Text} : execute success");
else
LogMessage?.Warning($" VariableRpcNode - VariableName {Text} : {data.ErrorMessage}");
return new NodeOutput() { Value = data };
}
else
{
LogMessage?.Warning($" VariableRpcNode - VariableName {Text} : not found");
return new NodeOutput() { };
if (device.ReadOnlyVariableRuntimes.TryGetValue(Text, out var value))
{
var data = await value.RpcAsync(input.JToken.ToString(), $"RulesEngine: {RulesEngineName}", cancellationToken).ConfigureAwait(false);
if (data.IsSuccess)
LogMessage?.Trace($" VariableRpcNode - VariableName {Text} : execute success");
else
LogMessage?.Warning($" VariableRpcNode - VariableName {Text} : {data.ErrorMessage}");
return new NodeOutput() { Value = data };
}
}
LogMessage?.Warning($" VariableRpcNode - VariableName {Text} : not found");
return new NodeOutput() { };
}

View File

@@ -9,8 +9,8 @@ using TouchSocket.Core;
namespace ThingsGateway.RulesEngine;
[CategoryNode(Category = "Trigger", ImgUrl = "_content/ThingsGateway.RulesEngine/img/ValueChanged.svg", Desc = nameof(AlarmChangedTriggerNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(AlarmVariableWidget))]
public class AlarmChangedTriggerNode : TextNode, ITriggerNode, IDisposable
[CategoryNode(Category = "Trigger", ImgUrl = "_content/ThingsGateway.RulesEngine/img/ValueChanged.svg", Desc = nameof(AlarmChangedTriggerNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(VariableWidget))]
public class AlarmChangedTriggerNode : VariableNode, ITriggerNode, IDisposable
{
public AlarmChangedTriggerNode(string id, Point? position = null) : base(id, position) { Title = "AlarmChangedTriggerNode"; Placeholder = "AlarmChangedTriggerNode.Placeholder"; }
@@ -19,31 +19,50 @@ public class AlarmChangedTriggerNode : TextNode, ITriggerNode, IDisposable
Task ITriggerNode.StartAsync(Func<NodeOutput, Task> func)
{
Func = func;
FuncDict.Add(this, func);
if (!AlarmChangedTriggerNodeDict.TryGetValue(Text, out var list))
FuncDict.TryAdd(this, func);
if (AlarmChangedTriggerNodeDict.TryGetValue(DeviceText, out var deviceVariableDict))
{
var list1 = new ConcurrentList<AlarmChangedTriggerNode>();
list1.Add(this);
AlarmChangedTriggerNodeDict.Add(Text, list1);
if (deviceVariableDict.TryGetValue(Text, out var alarmChangedTriggerNodes))
{
alarmChangedTriggerNodes.Add(this);
}
else
{
deviceVariableDict.TryAdd(Text, new());
deviceVariableDict[Text].Add(this);
}
}
else
{
list.Add(this);
AlarmChangedTriggerNodeDict.TryAdd(DeviceText, new());
AlarmChangedTriggerNodeDict[DeviceText].TryAdd(Text, new());
AlarmChangedTriggerNodeDict[DeviceText][Text].Add(this);
}
return Task.CompletedTask;
}
public static Dictionary<string, ConcurrentList<AlarmChangedTriggerNode>> AlarmChangedTriggerNodeDict = new();
public static Dictionary<AlarmChangedTriggerNode, Func<NodeOutput, Task>> FuncDict = new();
public static ConcurrentDictionary<string, ConcurrentDictionary<string, ConcurrentList<AlarmChangedTriggerNode>>> AlarmChangedTriggerNodeDict = new();
public static ConcurrentDictionary<AlarmChangedTriggerNode, Func<NodeOutput, Task>> FuncDict = new();
public static BlockingCollection<AlarmVariable> AlarmVariables = new();
static AlarmChangedTriggerNode()
{
_ = RunAsync();
GlobalData.AlarmChangedEvent -= AlarmHostedService_OnAlarmChanged;
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
{
AlarmHostedService_OnAlarmChanged(a.Value);
});
GlobalData.AlarmChangedEvent += AlarmHostedService_OnAlarmChanged;
}
private static void AlarmHostedService_OnAlarmChanged(AlarmVariable alarmVariable)
{
if (AlarmChangedTriggerNodeDict.TryGetValue(alarmVariable.Name, out var list) && list?.Count > 0)
if (AlarmChangedTriggerNodeDict.TryGetValue(alarmVariable.DeviceName, out var alarmNodeDict) &&
alarmNodeDict.TryGetValue(alarmVariable.Name, out var alarmChangedTriggerNodes) &&
alarmChangedTriggerNodes?.Count > 0)
{
if (!AlarmVariables.IsAddingCompleted)
{
@@ -59,27 +78,26 @@ public class AlarmChangedTriggerNode : TextNode, ITriggerNode, IDisposable
}
static Task RunAsync()
{
return AlarmVariables.GetConsumingEnumerable().ParallelForEachAsync((async (alarmVariables, token) =>
return AlarmVariables.GetConsumingEnumerable().ParallelForEachAsync((async (alarmVariable, token) =>
{
if (AlarmChangedTriggerNodeDict.TryGetValue(alarmVariables.Name, out var valueChangedTriggerNodes))
if (AlarmChangedTriggerNodeDict.TryGetValue(alarmVariable.DeviceName, out var alarmNodeDict) &&
alarmNodeDict.TryGetValue(alarmVariable.Name, out var alarmChangedTriggerNodes))
{
foreach (var item in valueChangedTriggerNodes)
{
try
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Alarm changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = alarmVariables }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
}
}
await alarmChangedTriggerNodes.ParallelForEachAsync(async (item, token) =>
{
try
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Alarm changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = alarmVariable }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
}
}, Environment.ProcessorCount, token).ConfigureAwait(false);
}
}), Environment.ProcessorCount / 2 <= 1 ? 2 : Environment.ProcessorCount / 2, default);
@@ -87,45 +105,11 @@ public class AlarmChangedTriggerNode : TextNode, ITriggerNode, IDisposable
public void Dispose()
{
FuncDict.Remove(this);
if (AlarmChangedTriggerNodeDict.TryGetValue(Text, out var list))
FuncDict.TryRemove(this, out _);
if (AlarmChangedTriggerNodeDict.TryGetValue(DeviceText, out var alarmNodeDict) &&
alarmNodeDict.TryGetValue(Text, out var alarmChangedTriggerNodes))
{
list.Remove(this);
alarmChangedTriggerNodes.Remove(this);
}
}
}
//using ThingsGateway.Gateway.Application;
//using TouchSocket.Core;
//namespace ThingsGateway.RulesEngine;
//[CategoryNode(Category = "Trigger", ImgUrl = "_content/ThingsGateway.RulesEngine/img/ValueChanged.svg", Desc = nameof(AlarmChangedTriggerNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(TextWidget))]
//public class AlarmChangedTriggerNode : TextNode, ITriggerNode, IDisposable
//{
// public AlarmChangedTriggerNode(string id, Point? position = null) : base(id, position) { Title = "AlarmChangedTriggerNode"; Placeholder = "AlarmChangedTriggerNode.Placeholder"; }
// private Func<NodeOutput, Task> Func { get; set; }
// Task ITriggerNode.StartAsync(Func<NodeOutput, Task> func)
// {
// Func = func;
// GlobalData.AlarmHostedService.OnAlarmChanged += AlarmHostedService_OnAlarmChanged;
// return Task.CompletedTask;
// }
// private void AlarmHostedService_OnAlarmChanged(AlarmVariable alarmVariable)
// {
// if (alarmVariable.Name == Text)
// {
// LogMessage?.Trace($"Alarm changed: {Text}");
// _ = Func?.Invoke(new NodeOutput() { Value = alarmVariable });
// }
// }
// public void Dispose()
// {
// GlobalData.AlarmHostedService.OnAlarmChanged -= AlarmHostedService_OnAlarmChanged;
// }
//}

View File

@@ -21,9 +21,9 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
FuncDict.Add(this, func);
if (!DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
{
var list1 = new ConcurrentList<DeviceChangedTriggerNode>();
list1.Add(this);
DeviceChangedTriggerNodeDict.Add(Text, list1);
var deviceChangedTriggerNodes = new ConcurrentList<DeviceChangedTriggerNode>();
deviceChangedTriggerNodes.Add(this);
DeviceChangedTriggerNodeDict.Add(Text, deviceChangedTriggerNodes);
}
else
{
@@ -34,15 +34,17 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
public static Dictionary<string, ConcurrentList<DeviceChangedTriggerNode>> DeviceChangedTriggerNodeDict = new();
public static Dictionary<DeviceChangedTriggerNode, Func<NodeOutput, Task>> FuncDict = new();
public static BlockingCollection<DeviceData> DeviceDatas = new();
public static BlockingCollection<DeviceBasicData> DeviceDatas = new();
static DeviceChangedTriggerNode()
{
_ = RunAsync();
GlobalData.DeviceStatusChangeEvent += GlobalData_DeviceStatusChangeEvent;
}
private static void GlobalData_DeviceStatusChangeEvent(DeviceRuntime deviceRunTime, DeviceBasicData deviceData)
{
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name, out var list) && list?.Count > 0)
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
{
if (!DeviceDatas.IsAddingCompleted)
{
@@ -59,27 +61,26 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
static Task RunAsync()
{
return DeviceDatas.GetConsumingEnumerable().ParallelForEachAsync((async (deviceDatas, token) =>
{
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name, out var valueChangedTriggerNodes))
{
foreach (var item in valueChangedTriggerNodes)
{
try
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Device changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = deviceDatas }).ConfigureAwait(false);
await valueChangedTriggerNodes.ParallelForEachAsync(async (item, token) =>
{
try
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Device changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = deviceDatas }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
}
}
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
}
}, Environment.ProcessorCount, token).ConfigureAwait(false);
}
}), Environment.ProcessorCount / 2 <= 1 ? 2 : Environment.ProcessorCount / 2, default);
}
@@ -95,40 +96,3 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
}
}
//using ThingsGateway.Gateway.Application;
//using TouchSocket.Core;
//namespace ThingsGateway.RulesEngine;
//[CategoryNode(Category = "Trigger", ImgUrl = "_content/ThingsGateway.RulesEngine/img/ValueChanged.svg", Desc = nameof(DeviceChangedTriggerNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(TextWidget))]
//public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
//{
// public DeviceChangedTriggerNode(string id, Point? position = null) : base(id, position) { Title = "DeviceChangedTriggerNode"; Placeholder = "DeviceChangedTriggerNode.Placeholder"; }
// private Func<NodeOutput, Task> Func { get; set; }
// Task ITriggerNode.StartAsync(Func<NodeOutput, Task> func)
// {
// Func = func;
// GlobalData.DeviceStatusChangeEvent += GlobalData_DeviceStatusChangeEvent;
// return Task.CompletedTask;
// }
// private void GlobalData_DeviceStatusChangeEvent(DeviceRuntime deviceRunTime, DeviceBasicData deviceData)
// {
// if (deviceRunTime.Name == Text)
// {
// LogMessage?.Trace($"Device changed: {Text}");
// _ = Func?.Invoke(new NodeOutput() { Value = deviceRunTime });
// }
// }
// public void Dispose()
// {
// GlobalData.DeviceStatusChangeEvent -= GlobalData_DeviceStatusChangeEvent;
// }
//}

View File

@@ -8,7 +8,7 @@ using TouchSocket.Core;
namespace ThingsGateway.RulesEngine;
[CategoryNode(Category = "Trigger", ImgUrl = "_content/ThingsGateway.RulesEngine/img/ValueChanged.svg", Desc = nameof(ValueChangedTriggerNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(VariableWidget))]
public class ValueChangedTriggerNode : TextNode, ITriggerNode, IDisposable
public class ValueChangedTriggerNode : VariableNode, ITriggerNode, IDisposable
{
public ValueChangedTriggerNode(string id, Point? position = null) : base(id, position) { Title = "ValueChangedTriggerNode"; Placeholder = "ValueChangedTriggerNode.Placeholder"; }
@@ -16,21 +16,31 @@ public class ValueChangedTriggerNode : TextNode, ITriggerNode, IDisposable
Task ITriggerNode.StartAsync(Func<NodeOutput, Task> func)
{
Func = func;
FuncDict.Add(this, func);
if (!ValueChangedTriggerNodeDict.TryGetValue(Text, out var list))
FuncDict.TryAdd(this, func);
if (ValueChangedTriggerNodeDict.TryGetValue(DeviceText, out var deviceVariableDict))
{
var list1 = new ConcurrentList<ValueChangedTriggerNode>();
list1.Add(this);
ValueChangedTriggerNodeDict.Add(Text, list1);
if (deviceVariableDict.TryGetValue(Text, out var valueChangedTriggerNodes))
{
valueChangedTriggerNodes.Add(this);
}
else
{
deviceVariableDict.TryAdd(Text, new());
deviceVariableDict[Text].Add(this);
}
}
else
{
list.Add(this);
ValueChangedTriggerNodeDict.TryAdd(DeviceText, new());
ValueChangedTriggerNodeDict[DeviceText].TryAdd(Text, new());
ValueChangedTriggerNodeDict[DeviceText][Text].Add(this);
}
return Task.CompletedTask;
}
public static Dictionary<string, ConcurrentList<ValueChangedTriggerNode>> ValueChangedTriggerNodeDict = new();
public static Dictionary<ValueChangedTriggerNode, Func<NodeOutput, Task>> FuncDict = new();
public static ConcurrentDictionary<string, ConcurrentDictionary<string, ConcurrentList<ValueChangedTriggerNode>>> ValueChangedTriggerNodeDict = new();
public static ConcurrentDictionary<ValueChangedTriggerNode, Func<NodeOutput, Task>> FuncDict = new();
public static BlockingCollection<VariableBasicData> VariableBasicDatas = new();
static ValueChangedTriggerNode()
@@ -40,7 +50,9 @@ public class ValueChangedTriggerNode : TextNode, ITriggerNode, IDisposable
}
private static void GlobalData_VariableValueChangeEvent(VariableRuntime variableRuntime, VariableBasicData variableData)
{
if (ValueChangedTriggerNodeDict.TryGetValue(variableData.Name, out var list) && list?.Count > 0)
if (ValueChangedTriggerNodeDict.TryGetValue(variableRuntime.DeviceName, out var valueNodeDict) &&
valueNodeDict.TryGetValue(variableRuntime.Name, out var valueChangedTriggerNodes) &&
valueChangedTriggerNodes?.Count > 0)
{
if (!VariableBasicDatas.IsAddingCompleted)
{
@@ -59,24 +71,25 @@ public class ValueChangedTriggerNode : TextNode, ITriggerNode, IDisposable
return VariableBasicDatas.GetConsumingEnumerable().ParallelForEachAsync((async (variableBasicData, token) =>
{
if (ValueChangedTriggerNodeDict.TryGetValue(variableBasicData.Name, out var valueChangedTriggerNodes))
if (ValueChangedTriggerNodeDict.TryGetValue(variableBasicData.DeviceName, out var valueNodeDict) &&
valueNodeDict.TryGetValue(variableBasicData.Name, out var valueChangedTriggerNodes))
{
foreach (var item in valueChangedTriggerNodes)
await valueChangedTriggerNodes.ParallelForEachAsync(async (item, token) =>
{
try
{
try
if (FuncDict.TryGetValue(item, out var func))
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Variable changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = variableBasicData }).ConfigureAwait(false);
item.LogMessage?.Trace($"Variable changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = variableBasicData }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
}
}, Environment.ProcessorCount, token).ConfigureAwait(false);
}
}), Environment.ProcessorCount / 2 <= 1 ? 2 : Environment.ProcessorCount / 2, default);
@@ -85,44 +98,11 @@ public class ValueChangedTriggerNode : TextNode, ITriggerNode, IDisposable
public void Dispose()
{
FuncDict.Remove(this);
if (ValueChangedTriggerNodeDict.TryGetValue(Text, out var list))
FuncDict.TryRemove(this, out _);
if (ValueChangedTriggerNodeDict.TryGetValue(DeviceText, out var valueNodeDict) &&
valueNodeDict.TryGetValue(Text, out var valueChangedTriggerNodes))
{
list.Remove(this);
valueChangedTriggerNodes.Remove(this);
}
}
}
//using ThingsGateway.Gateway.Application;
//using TouchSocket.Core;
//namespace ThingsGateway.RulesEngine;
//[CategoryNode(Category = "Trigger", ImgUrl = "_content/ThingsGateway.RulesEngine/img/ValueChanged.svg", Desc = nameof(ValueChangedTriggerNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(TextWidget))]
//public class ValueChangedTriggerNode : TextNode, ITriggerNode, IDisposable
//{
// public ValueChangedTriggerNode(string id, Point? position = null) : base(id, position) { Title = "ValueChangedTriggerNode"; Placeholder = "ValueChangedTriggerNode.Placeholder"; }
// private Func<NodeOutput, Task> Func { get; set; }
// Task ITriggerNode.StartAsync(Func<NodeOutput, Task> func)
// {
// Func = func;
// GlobalData.VariableValueChangeEvent += GlobalData_VariableValueChangeEvent; ;
// return Task.CompletedTask;
// }
// private void GlobalData_VariableValueChangeEvent(VariableRuntime variableRuntime, VariableBasicData variableData)
// {
// if (variableRuntime.Name == Text)
// {
// LogMessage?.Trace($"Variable changed: {Text}");
// _ = Func?.Invoke(new NodeOutput() { Value = variableRuntime });
// }
// }
// public void Dispose()
// {
// GlobalData.VariableValueChangeEvent -= GlobalData_VariableValueChangeEvent; ;
// }
//}

View File

@@ -68,6 +68,18 @@
left: 115px;
}
.rulesengine ::deep .custom-node .variable .diagram-port.bottom {
position: absolute;
bottom: -10px;
left: 185px;
}
.rulesengine ::deep .custom-node .variable .diagram-port.top {
position: absolute;
top: -10px;
left: 185px;
}
.rulesengine ::deep .diagram-canvas.grid {
background-size: 50px 50px;
background-image: linear-gradient(to right, rgb(0, 0, 0, 0.05) 1px, transparent 1px), linear-gradient(to bottom, rgb(0, 0, 0, 0.05) 1px, transparent 1px);

View File

@@ -454,7 +454,7 @@ public class OpcDaMaster : IDisposable
checkTimer.Enabled = false;
checkTimer.Stop();
}
ItemDicts.Clear();
try
{
m_server?.Dispose();

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using SqlSugar;
namespace ThingsGateway.Plugin.DB;

View File

@@ -26,7 +26,7 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<QuestDBHistoryValue>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
{

View File

@@ -219,7 +219,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
{
try
{
var varList = VariableRuntimes.Select(a => a.Value).Adapt<List<SQLRealValue>>();
var varList = IdVariableRuntimes.Select(a => a.Value).Adapt<List<SQLRealValue>>();
var result = await UpdateAsync(varList, cancellationToken).ConfigureAwait(false);
if (success != result.IsSuccess)

View File

@@ -29,7 +29,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<SQLHistoryValue>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
{

View File

@@ -17,6 +17,8 @@ using SqlSugar;
using ThingsGateway.Admin.Application;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Plugin.SqlHistoryAlarm;
/// <summary>
@@ -38,6 +40,11 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
{
_config.ForType<AlarmVariable, HistoryAlarm>().Map(dest => dest.Id, (src) => CommonUtils.GetSingleId());
GlobalData.AlarmChangedEvent -= AlarmWorker_OnAlarmChanged;
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
{
AlarmWorker_OnAlarmChanged(a.Value);
});
GlobalData.AlarmChangedEvent += AlarmWorker_OnAlarmChanged;
await base.InitChannelAsync(channel).ConfigureAwait(false);
@@ -45,10 +52,10 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
public override async Task AfterVariablesChangedAsync()
{
await base.AfterVariablesChangedAsync().ConfigureAwait(false);
VariableRuntimes = GlobalData.ReadOnlyVariables.Where(a => a.Value.AlarmEnable).ToDictionary(a => a.Key, a => a.Value);
IdVariableRuntimes = GlobalData.ReadOnlyIdVariables.Where(a => a.Value.AlarmEnable).ToDictionary();
CollectDevices = GlobalData.ReadOnlyIdDevices
.Where(a => VariableRuntimes.Select(b => b.Value.DeviceId).Contains(a.Value.Id))
.Where(a => IdVariableRuntimes.Select(b => b.Value.DeviceId).Contains(a.Value.Id))
.ToDictionary(a => a.Key, a => a.Value);
}

View File

@@ -24,7 +24,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
{
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<HistoryAlarm>> item, CancellationToken cancellationToken)
{
return UpdateT(item.Select(a => a.Value), cancellationToken);
return UpdateT(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
private void AlarmWorker_OnAlarmChanged(AlarmVariable alarmVariable)

View File

@@ -29,7 +29,7 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<TDengineDBHistoryValue>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)

View File

@@ -13,7 +13,7 @@ namespace ThingsGateway.Plugin.Webhook;
/// <summary>
/// WebhookClient,RPC方法适配mqttNet
/// </summary>
public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableData, DeviceBasicData, AlarmVariable>
public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private readonly WebhookProperty _driverPropertys = new();
private readonly WebhookVariableProperty _variablePropertys = new();

View File

@@ -20,7 +20,7 @@ namespace ThingsGateway.Plugin.Webhook;
/// <summary>
/// WebhookClient
/// </summary>
public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableData, DeviceBasicData, AlarmVariable>
public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
protected override void AlarmChange(AlarmVariable alarmVariable)
@@ -52,19 +52,19 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableData,
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
{
return UpdateAlarmModel(item.Select(a => a.Value), cancellationToken);
return UpdateAlarmModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
{
return UpdateDevModel(item.Select(a => a.Value), cancellationToken);
return UpdateDevModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
@@ -160,9 +160,9 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableData,
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableData> item, CancellationToken cancellationToken)
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariable(item);
List<TopicJson> topicJsonList = GetVariableBasicData(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}

View File

@@ -19,7 +19,7 @@ namespace ThingsGateway.Plugin.Kafka;
/// <summary>
/// Kafka消息生产
/// </summary>
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableData, DeviceData, AlarmVariable>
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private readonly KafkaProducerProperty _driverPropertys = new();
private readonly KafkaProducerVariableProperty _variablePropertys = new();

View File

@@ -22,7 +22,7 @@ namespace ThingsGateway.Plugin.Kafka;
/// <summary>
/// Kafka消息生产
/// </summary>
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableData, DeviceData, AlarmVariable>
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private IProducer<Null, string> _producer;
private ProducerBuilder<Null, string> _producerBuilder;
@@ -52,17 +52,17 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
{
return UpdateAlarmModel(item.Select(a => a.Value), cancellationToken);
return UpdateAlarmModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
{
return UpdateDevModel(item.Select(a => a.Value), cancellationToken);
return UpdateDevModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
@@ -107,15 +107,15 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
return await Update(topicJsonList, item.Count(), cancellationToken).ConfigureAwait(false);
}
private async ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceData> item, CancellationToken cancellationToken)
private async ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
return await Update(topicJsonList, item.Count(), cancellationToken).ConfigureAwait(false);
}
private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableData> item, CancellationToken cancellationToken)
private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariable(item);
List<TopicJson> topicJsonList = GetVariableBasicData(item);
return await Update(topicJsonList, item.Count(), cancellationToken).ConfigureAwait(false);
}
@@ -127,9 +127,9 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
{
//保留消息
//分解List避免超出字节大小限制
var varData = VariableRuntimes.Select(a => a.Value).Adapt<List<VariableData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
var varData = IdVariableRuntimes.Select(a => a.Value).Adapt<List<VariableBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
foreach (var item in varData)
{
if (!success)

View File

@@ -109,12 +109,12 @@ public class ModbusSlave : BusinessBase
{
await base.AfterVariablesChangedAsync().ConfigureAwait(false);
_modbusVariableQueue.Clear();
VariableRuntimes.ForEach(a =>
IdVariableRuntimes.ForEach(a =>
{
VariableValueChange(a.Value, null);
});
ModbusVariables = VariableRuntimes.ToDictionary(a =>
ModbusVariables = IdVariableRuntimes.ToDictionary(a =>
{
ModbusAddress address = ModbusAddress.ParseFrom(
a.Value.GetPropertyValue(DeviceId,
@@ -234,7 +234,7 @@ public class ModbusSlave : BusinessBase
}
/// <inheritdoc/>
private void VariableValueChange(VariableRuntime variableRuntime, VariableData variableData)
private void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variableData)
{
if (CurrentDevice.Pause == true)
return;

View File

@@ -20,7 +20,7 @@ namespace ThingsGateway.Plugin.Mqtt;
/// <summary>
/// MqttClient,RPC方法适配mqttNet
/// </summary>
public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableData, DeviceBasicData, AlarmVariable>
public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private readonly MqttClientProperty _driverPropertys = new();
private readonly MqttClientVariableProperty _variablePropertys = new();

View File

@@ -8,10 +8,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using CSScripting;
using Mapster;
using MQTTnet;
#if NET6_0
using MQTTnet.Client;
#endif
@@ -21,7 +26,6 @@ using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using System.Text;
using ThingsGateway.Extension.Generic;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.NewLife;
@@ -33,7 +37,7 @@ namespace ThingsGateway.Plugin.Mqtt;
/// <summary>
/// MqttClient
/// </summary>
public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableData, DeviceBasicData, AlarmVariable>
public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private static readonly CompositeFormat RpcTopic = CompositeFormat.Parse("{0}/+");
public const string ThingsBoardRpcTopic = "v1/gateway/rpc";
@@ -120,19 +124,19 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
{
return UpdateAlarmModel(item.Select(a => a.Value), cancellationToken);
return UpdateAlarmModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
{
return UpdateDevModel(item.Select(a => a.Value), cancellationToken);
return UpdateDevModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
{
@@ -187,9 +191,9 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableData> item, CancellationToken cancellationToken)
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariable(item);
List<TopicJson> topicJsonList = GetVariableBasicData(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
@@ -199,9 +203,9 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
{
//保留消息
//分解List避免超出mqtt字节大小限制
var varData = VariableRuntimes.Select(a => a.Value).Adapt<List<VariableData>>().ChunkBetter(_driverPropertys.SplitSize);
var varData = IdVariableRuntimes.Select(a => a.Value).Adapt<List<VariableBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
foreach (var item in varData)
{
if (!success)
@@ -226,33 +230,62 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
}
}
private async ValueTask<Dictionary<string, OperResult>> GetResult(MqttApplicationMessageReceivedEventArgs args, Dictionary<string, JToken> rpcDatas)
private async ValueTask<Dictionary<string, Dictionary<string, OperResult>>> GetResult(MqttApplicationMessageReceivedEventArgs args, Dictionary<string, Dictionary<string, JToken>> rpcDatas)
{
var mqttRpcResult = new Dictionary<string, OperResult>();
var mqttRpcResult = new Dictionary<string, Dictionary<string, OperResult>>();
rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new()));
try
{
foreach (var rpcData in rpcDatas)
{
VariableRuntimes.TryGetValue(rpcData.Key, out var tag);
if (tag != null)
if (GlobalData.ReadOnlyDevices.TryGetValue(rpcData.Key, out var device))
{
var rpcEnable = tag.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean();
if (rpcEnable == false)
foreach (var item in rpcData.Value)
{
mqttRpcResult.Add(rpcData.Key, new OperResult("RPCEnable is False"));
if (device.ReadOnlyVariableRuntimes.TryGetValue(item.Key, out var variable) && IdVariableRuntimes.TryGetValue(variable.Id, out var tag))
{
var rpcEnable = tag.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean();
if (rpcEnable == false)
{
mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("RPCEnable is False"));
}
}
else
{
mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("The variable does not exist"));
}
}
}
else
{
mqttRpcResult.Add(rpcData.Key, new OperResult("The variable does not exist"));
}
}
var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(ToString() + "-" + args.ClientId,
rpcDatas.Where(
a => !mqttRpcResult.Any(b => b.Key == a.Key)).ToDictionary(a => a.Key, a => a.Value.ToString())).ConfigureAwait(false);
Dictionary<string, Dictionary<string, string>> writeData = new();
foreach (var item in rpcDatas)
{
writeData.Add(item.Key, new());
mqttRpcResult.AddRange(result);
foreach (var kv in item.Value)
{
if (!mqttRpcResult[item.Key].ContainsKey(kv.Key))
{
writeData[item.Key].Add(kv.Key, kv.Value?.ToString());
}
}
}
var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(ToString() + "-" + args.ClientId,
writeData).ConfigureAwait(false);
foreach (var dictKv in result)
{
foreach (var item in dictKv.Value)
{
mqttRpcResult[dictKv.Key].TryAdd(item.Key, item.Value);
}
}
}
catch (Exception ex)
{
@@ -285,7 +318,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
if (!_driverPropertys.DeviceRpcEnable)
return;
Dictionary<string, JToken> rpcDatas = null;
Dictionary<string, Dictionary<string, JToken>> rpcDatas = new();
//适配 ThingsBoardRp
if (args.ApplicationMessage.Topic == ThingsBoardRpcTopic)
@@ -293,11 +326,12 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
var thingsBoardRpcData = Encoding.UTF8.GetString(payload).FromJsonNetString<ThingsBoardRpcData>();
if (thingsBoardRpcData == null)
return;
rpcDatas = thingsBoardRpcData.data.@params.ToDictionary(a => a.Key, a => JToken.Parse(a.Value));
rpcDatas.Add(thingsBoardRpcData.device, thingsBoardRpcData.data.@params.ToDictionary(a => a.Key, a => JToken.Parse(a.Value)));
if (rpcDatas == null)
return;
Dictionary<string, OperResult> mqttRpcResult = await GetResult(args, rpcDatas).ConfigureAwait(false);
var mqttRpcResult = await GetResult(args, rpcDatas).ConfigureAwait(false);
try
{
var isConnect = await TryMqttClientAsync(CancellationToken.None).ConfigureAwait(false);
@@ -306,8 +340,8 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
ThingsBoardRpcResponseData thingsBoardRpcResponseData = new();
thingsBoardRpcResponseData.device = thingsBoardRpcData.device;
thingsBoardRpcResponseData.id = thingsBoardRpcData.data.id;
thingsBoardRpcResponseData.data.success = mqttRpcResult.All(a => a.Value.IsSuccess);
thingsBoardRpcResponseData.data.message = mqttRpcResult.Select(a => a.Value.ErrorMessage).ToJsonNetString();
thingsBoardRpcResponseData.data.success = mqttRpcResult[thingsBoardRpcResponseData.device].All(b => b.Value.IsSuccess);
thingsBoardRpcResponseData.data.message = mqttRpcResult[thingsBoardRpcResponseData.device].Select(a => a.Value.ErrorMessage).ToJsonNetString();
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic($"{args.ApplicationMessage.Topic}")
@@ -326,11 +360,11 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
var t = string.Format(null, RpcTopic, _driverPropertys.RpcWriteTopic);
if (MqttTopicFilterComparer.Compare(args.ApplicationMessage.Topic, t) != MqttTopicFilterCompareResult.IsMatch)
return;
rpcDatas = Encoding.UTF8.GetString(payload).FromJsonNetString<Dictionary<string, JToken>>();
rpcDatas = Encoding.UTF8.GetString(payload).FromJsonNetString<Dictionary<string, Dictionary<string, JToken>>>();
if (rpcDatas == null)
return;
Dictionary<string, OperResult> mqttRpcResult = await GetResult(args, rpcDatas).ConfigureAwait(false);
var mqttRpcResult = await GetResult(args, rpcDatas).ConfigureAwait(false);
try
{
var isConnect = await TryMqttClientAsync(CancellationToken.None).ConfigureAwait(false);

View File

@@ -23,7 +23,7 @@ namespace ThingsGateway.Plugin.Mqtt;
/// <summary>
/// MqttServer,RPC方法适配mqttNet
/// </summary>
public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableData, DeviceData, AlarmVariable>
public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private readonly MqttServerProperty _driverPropertys = new();
private readonly MqttClientVariableProperty _variablePropertys = new();

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using CSScripting;
using Mapster;
using Microsoft.AspNetCore.Hosting;
@@ -23,7 +25,6 @@ using Newtonsoft.Json.Linq;
using System.Text;
using ThingsGateway.Admin.Application;
using ThingsGateway.Extension.Generic;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.NewLife.Extension;
@@ -34,7 +35,7 @@ namespace ThingsGateway.Plugin.Mqtt;
/// <summary>
/// MqttServer
/// </summary>
public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableData, DeviceData, AlarmVariable>
public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private static readonly CompositeFormat RpcTopic = CompositeFormat.Parse("{0}/+");
private MQTTnet.Server.MqttServer _mqttServer;
@@ -63,17 +64,17 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
{
return UpdateAlarmModel(item.Select(a => a.Value), cancellationToken);
return UpdateAlarmModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
{
return UpdateDevModel(item.Select(a => a.Value), cancellationToken);
return UpdateDevModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
{
@@ -118,47 +119,76 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceData> item, CancellationToken cancellationToken)
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableData> item, CancellationToken cancellationToken)
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariable(item);
List<TopicJson> topicJsonList = GetVariableBasicData(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
#endregion private
private async ValueTask<Dictionary<string, OperResult>> GetResult(InterceptingPublishEventArgs args, Dictionary<string, JToken> rpcDatas)
private async ValueTask<Dictionary<string, Dictionary<string, OperResult>>> GetResult(InterceptingPublishEventArgs args, Dictionary<string, Dictionary<string, JToken>> rpcDatas)
{
var mqttRpcResult = new Dictionary<string, OperResult>();
var mqttRpcResult = new Dictionary<string, Dictionary<string, OperResult>>();
rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new()));
try
{
foreach (var rpcData in rpcDatas)
{
VariableRuntimes.TryGetValue(rpcData.Key, out var tag);
if (tag != null)
if (GlobalData.ReadOnlyDevices.TryGetValue(rpcData.Key, out var device))
{
var rpcEnable = tag.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean();
if (rpcEnable == false)
foreach (var item in rpcData.Value)
{
mqttRpcResult.Add(rpcData.Key, new OperResult("RPCEnable is False"));
if (device.ReadOnlyVariableRuntimes.TryGetValue(item.Key, out var variable) && IdVariableRuntimes.TryGetValue(variable.Id, out var tag))
{
var rpcEnable = tag.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean();
if (rpcEnable == false)
{
mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("RPCEnable is False"));
}
}
else
{
mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("The variable does not exist"));
}
}
}
else
}
Dictionary<string, Dictionary<string, string>> writeData = new();
foreach (var item in rpcDatas)
{
writeData.Add(item.Key, new());
foreach (var kv in item.Value)
{
mqttRpcResult.Add(rpcData.Key, new OperResult("The variable does not exist"));
if (!mqttRpcResult[item.Key].ContainsKey(kv.Key))
{
writeData[item.Key].Add(kv.Key, kv.Value?.ToString());
}
}
}
var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(ToString() + "-" + args.ClientId,
rpcDatas.Where(
a => !mqttRpcResult.Any(b => b.Key == a.Key)).ToDictionary(a => a.Key, a => a.Value.ToString())).ConfigureAwait(false);
writeData).ConfigureAwait(false);
mqttRpcResult.AddRange(result);
foreach (var dictKv in result)
{
foreach (var item in dictKv.Value)
{
mqttRpcResult[dictKv.Key].TryAdd(item.Key, item.Value);
}
}
}
catch (Exception ex)
{
@@ -172,16 +202,16 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
{
//首次连接时的保留消息
//分解List避免超出mqtt字节大小限制
var varData = VariableRuntimes.Select(a => a.Value).Adapt<List<VariableData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
var varData = IdVariableRuntimes.Select(a => a.Value).Adapt<List<VariableBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
List<MqttApplicationMessage> Messages = new();
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrEmpty())
{
foreach (var item in varData)
{
List<TopicJson> topicJsonList = GetVariable(item);
List<TopicJson> topicJsonList = GetVariableBasicData(item);
foreach (var topicJson in topicJsonList)
{
Messages.Add(new MqttApplicationMessageBuilder()
@@ -246,10 +276,10 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
var t = string.Format(null, RpcTopic, _driverPropertys.RpcWriteTopic);
if (MqttTopicFilterComparer.Compare(args.ApplicationMessage.Topic, t) != MqttTopicFilterCompareResult.IsMatch)
return;
var rpcDatas = Encoding.UTF8.GetString(payload).FromJsonNetString<Dictionary<string, JToken>>();
var rpcDatas = Encoding.UTF8.GetString(payload).FromJsonNetString<Dictionary<string, Dictionary<string, JToken>>>();
if (rpcDatas == null)
return;
Dictionary<string, OperResult> mqttRpcResult = await GetResult(args, rpcDatas).ConfigureAwait(false);
var mqttRpcResult = await GetResult(args, rpcDatas).ConfigureAwait(false);
try
{
@@ -273,7 +303,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
private async Task MqttServer_ValidatingConnectionAsync(ValidatingConnectionEventArgs arg)
{
if (string.IsNullOrEmpty(_driverPropertys.StartWithId) || !arg.ClientId.StartsWith(_driverPropertys.StartWithId))
if (!string.IsNullOrEmpty(_driverPropertys.StartWithId) && !arg.ClientId.StartsWith(_driverPropertys.StartWithId))
{
arg.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
return;

View File

@@ -49,7 +49,7 @@ public class MqttServerProperty : BusinessPropertyWithCacheIntervalScript
/// 允许匿名登录
/// </summary>
[DynamicProperty]
public bool AnonymousEnable { get; set; } = false;
public bool AnonymousEnable { get; set; } = true;
/// <summary>
/// 允许Rpc写入

View File

@@ -125,11 +125,8 @@ public class OpcDaMaster : CollectBase
protected override async Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
await Task.CompletedTask.ConfigureAwait(false);
_plc?.Disconnect();
try
{
if (deviceVariables.Count > 0)
{
var result = _plc.AddItemsWithSave(deviceVariables.Where(a => !string.IsNullOrEmpty(a.RegisterAddress)).Select(a => a.RegisterAddress!).ToList());
@@ -157,7 +154,6 @@ public class OpcDaMaster : CollectBase
}
finally
{
_plc?.Connect();
}
}
@@ -204,9 +200,12 @@ public class OpcDaMaster : CollectBase
}
public override async Task AfterVariablesChangedAsync()
{
_plc?.Disconnect();
await base.AfterVariablesChangedAsync().ConfigureAwait(false);
VariableAddresDicts = VariableRuntimes.Select(a => a.Value).Where(it => !it.RegisterAddress.IsNullOrEmpty()).GroupBy(a => a.RegisterAddress).ToDictionary(a => a.Key!, b => b.ToList());
VariableAddresDicts = IdVariableRuntimes.Select(a => a.Value).Where(it => !it.RegisterAddress.IsNullOrEmpty()).GroupBy(a => a.RegisterAddress).ToDictionary(a => a.Key!, b => b.ToList());
_plc?.Connect();
}
private Dictionary<string, List<VariableRuntime>> VariableAddresDicts { get; set; } = new();

View File

@@ -274,7 +274,7 @@ public partial class OpcDaImportVariable
var id = Admin.Application.CommonUtils.GetSingleId();
return new Variable()
{
Name = a.Name + "-" + id,
Name = a.Name,
RegisterAddress = a.ItemName,
DeviceId = device.Id,
Enable = true,

View File

@@ -155,7 +155,7 @@ public class OpcUaMaster : CollectBase
{
await _plc.AddSubscriptionAsync(variableSourceRead.RegisterAddress, variableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToHashSet().ToArray(), _plc.OpcUaProperty.LoadType, cancellationToken).ConfigureAwait(false);
LogMessage?.LogInformation($"AddSubscription {CurrentDevice.VariableSourceReads.IndexOf(variableSourceRead)} done");
LogMessage?.LogInformation($"AddSubscription index {CurrentDevice.VariableSourceReads.IndexOf(variableSourceRead)} done");
}
}
@@ -309,7 +309,7 @@ public class OpcUaMaster : CollectBase
}
finally
{
VariableAddresDicts = VariableRuntimes.Select(a => a.Value).Where(it => !it.RegisterAddress.IsNullOrEmpty()).GroupBy(a => a.RegisterAddress).ToDictionary(a => a.Key!, b => b.ToList());
VariableAddresDicts = IdVariableRuntimes.Select(a => a.Value).Where(it => !it.RegisterAddress.IsNullOrEmpty()).GroupBy(a => a.RegisterAddress).ToDictionary(a => a.Key!, b => b.ToList());
await _plc.ConnectAsync(default).ConfigureAwait(false);
}

View File

@@ -74,7 +74,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
AddRootNotifier(rootFolder);
//创建设备树
var _geviceGroup = _businessBase.VariableRuntimes.Select(a => a.Value)
var _geviceGroup = _businessBase.IdVariableRuntimes.Select(a => a.Value)
.GroupBy(a => a.DeviceName);
// 开始寻找设备信息,并计算一些节点信息
foreach (var item in _geviceGroup)
@@ -124,7 +124,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
var historyRead = nodesToRead[i];
if (NodeIdTags.TryGetValue(historyRead.NodeId.Identifier.ToString(), out OpcUaTag tag))
{
if (!GlobalData.ReadOnlyVariables.TryGetValue(tag.SymbolicName, out var variableRuntime))
if (!GlobalData.ReadOnlyIdVariables.TryGetValue(tag.Id, out var variableRuntime))
{
results[i] = new HistoryReadResult()
{
@@ -186,28 +186,13 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
}
/// <inheritdoc/>
public override NodeId New(ISystemContext context, NodeState node)
{
if (node is BaseInstanceState instance && instance.Parent != null)
{
string id = instance.Parent.NodeId.Identifier?.ToString();
if (id != null)
{
//用下划线分割
return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
}
}
return node.NodeId;
}
/// <summary>
/// 更新变量
/// </summary>
/// <param name="variable"></param>
public void UpVariable(VariableData variable)
public void UpVariable(VariableBasicData variable)
{
if (!NodeIdTags.TryGetValue(variable.Name, out var uaTag))
if (!NodeIdTags.TryGetValue($"{variable.DeviceName}.{variable.Name}", out var uaTag))
return;
object initialItemValue = null;
initialItemValue = variable.Value;
@@ -389,7 +374,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
SymbolicName = variableRuntime.Name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
NodeId = new NodeId(variableRuntime.Name, NamespaceIndex),
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}", NamespaceIndex),
Description = variableRuntime.Description,
BrowseName = new QualifiedName(variableRuntime.Name, NamespaceIndex),
DisplayName = new LocalizedText(variableRuntime.Name),
@@ -411,7 +396,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
variable.Timestamp = variableRuntime.CollectTime ?? DateTime.MinValue;
variable.OnWriteValue = OnWriteDataValue;
parent?.AddChild(variable);
NodeIdTags.AddOrUpdate(variable.SymbolicName, variable);
NodeIdTags.AddOrUpdate($"{variableRuntime.DeviceName}.{variableRuntime.Name}", variable);
return variable;
}
@@ -447,28 +432,29 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
//{
// return StatusCodes.BadUserAccessDenied;
//}
OpcUaTag variable = node as OpcUaTag;
if (NodeIdTags.TryGetValue(variable.SymbolicName, out OpcUaTag tag))
OpcUaTag opcuaTag = node as OpcUaTag;
if (NodeIdTags.TryGetValue(opcuaTag.NodeId.Identifier.ToString(), out OpcUaTag tag) && GlobalData.ReadOnlyIdVariables.TryGetValue(tag.Id, out var variableRuntime))
{
if (StatusCode.IsGood(variable.StatusCode))
if (StatusCode.IsGood(opcuaTag.StatusCode))
{
//仅当指定了值时才将值写入
if (variable.Value != null)
if (opcuaTag.Value != null)
{
var result = GlobalData.RpcService.InvokeDeviceMethodAsync("OpcUaSlave - " + context1?.OperationContext?.Session?.Identity?.DisplayName,
var result = GlobalData.RpcService.InvokeDeviceMethodAsync("OpcUaServer - " + context1?.OperationContext?.Session?.Identity?.DisplayName,
new()
{
{ variable.SymbolicName, value?.ToString() }
{
variableRuntime.DeviceName, new Dictionary<string, string>() { {opcuaTag.SymbolicName, value?.ToString() } }
}
}
).ConfigureAwait(true).GetAwaiter().GetResult();
if (result.Values.FirstOrDefault().IsSuccess)
if (result.Values.FirstOrDefault()?.FirstOrDefault().Value.IsSuccess == true)
{
return StatusCodes.Good;
}
else
{
return new(StatusCodes.BadWaitingForResponse, result.Values.FirstOrDefault().ErrorMessage);
return new(StatusCodes.BadWaitingForResponse, result.Values.FirstOrDefault()?.FirstOrDefault().Value.ToString());
}
}
}

View File

@@ -45,7 +45,7 @@ public partial class OpcUaServer : BusinessBase
protected override BusinessPropertyBase _businessPropertyBase => _driverPropertys;
protected IStringLocalizer Localizer { get; private set; }
private ConcurrentQueue<VariableData> CollectVariableRuntimes { get; set; } = new();
private ConcurrentQueue<VariableBasicData> CollectVariableRuntimes { get; set; } = new();
private static readonly string[] separator = new string[] { ";" };
@@ -59,7 +59,6 @@ public partial class OpcUaServer : BusinessBase
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(LogMessage);//默认返回true
CollectVariableRuntimes?.Clear();
//Utils.SetLogger(new OpcUaLogger(LogMessage)); //调试用途
m_application = new ApplicationInstance();
m_configuration = GetDefaultConfiguration();
@@ -83,7 +82,7 @@ public partial class OpcUaServer : BusinessBase
if (_driverPropertys.IsAllVariable)
{
LogMessage?.LogInformation("Refresh variable");
VariableRuntimes = new(GlobalData.GetEnableVariables());
IdVariableRuntimes = new(GlobalData.GetEnableVariables());
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.Value.IsCollect == true).ToDictionary();
}
@@ -91,9 +90,12 @@ public partial class OpcUaServer : BusinessBase
{
await base.AfterVariablesChangedAsync().ConfigureAwait(false);
}
VariableRuntimes.ForEach(a =>
CollectVariableRuntimes.Clear();
IdVariableRuntimes.ForEach(a =>
{
VariableValueChange(a.Value, a.Value.Adapt<VariableData>());
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
@@ -104,7 +106,6 @@ public partial class OpcUaServer : BusinessBase
{
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(LogMessage);//默认返回true
CollectVariableRuntimes?.Clear();
//Utils.SetLogger(new OpcUaLogger(LogMessage)); //调试用途
m_application = new ApplicationInstance();
m_configuration = GetDefaultConfiguration();
@@ -119,7 +120,6 @@ public partial class OpcUaServer : BusinessBase
}
m_server = new(this);
CollectVariableRuntimes.Clear();
GlobalData.VariableValueChangeEvent += VariableValueChange;
@@ -151,7 +151,7 @@ public partial class OpcUaServer : BusinessBase
m_application?.Stop();
m_server?.SafeDispose();
CollectVariableRuntimes?.Clear();
VariableRuntimes?.Clear();
IdVariableRuntimes?.Clear();
base.Dispose(disposing);
}
@@ -181,9 +181,9 @@ public partial class OpcUaServer : BusinessBase
await m_application.CheckApplicationInstanceCertificates(true, 1200, cancellationToken).ConfigureAwait(false);
await m_application.Start(m_server).ConfigureAwait(false);
success = true;
VariableRuntimes.ForEach(a =>
IdVariableRuntimes.ForEach(a =>
{
VariableValueChange(a.Value, a.Value.Adapt<VariableData>());
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}
catch (Exception ex)
@@ -195,32 +195,35 @@ public partial class OpcUaServer : BusinessBase
}
}
var data = CollectVariableRuntimes.ToListWithDequeue();
data.Reverse();
////变化推送
var varList = data.DistinctBy(a => a.Name).ToList();
if (varList?.Count > 0)
if (data.Count > 0)
{
foreach (var item in varList)
data.Reverse();
////变化推送
var varList = data.DistinctBy(a => a.Id).ToList();
if (varList?.Count > 0)
{
try
foreach (var item in varList)
{
if (!cancellationToken.IsCancellationRequested)
try
{
m_server?.NodeManager?.UpVariable(item);
if (!cancellationToken.IsCancellationRequested)
{
m_server?.NodeManager?.UpVariable(item);
}
else
{
break;
}
}
else
catch (Exception ex)
{
break;
LogMessage.LogWarning(ex);
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex);
}
}
success = true;
}
success = true;
}
catch (Exception ex)
{
@@ -419,12 +422,16 @@ public partial class OpcUaServer : BusinessBase
return config;
}
private void VariableValueChange(VariableRuntime variableRuntime, VariableData variableData)
private void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variableData)
{
if (CurrentDevice.Pause)
return;
if (DisposedValue) return;
if (VariableRuntimes.ContainsKey(variableData.Name))
if (IdVariableRuntimes.ContainsKey(variableData.Id))
CollectVariableRuntimes.Enqueue(variableData);
else
{
}
}
}

View File

@@ -312,7 +312,7 @@ public partial class OpcUaImportVariable
variables.Add(new Variable()
{
Name = a.DisplayName.Text + "-" + id,
Name = a.NodeId.ToString().Replace('.', '/'),
RegisterAddress = a.NodeId.ToString(),
DeviceId = device.Id,
DataType = dataTypeEnum,

View File

@@ -19,7 +19,7 @@ namespace ThingsGateway.Plugin.RabbitMQ;
/// <summary>
/// RabbitMQProducer
/// </summary>
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<VariableData, DeviceData, AlarmVariable>
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private readonly RabbitMQProducerProperty _driverPropertys = new();
private readonly RabbitMQProducerVariableProperty _variablePropertys = new();

View File

@@ -26,7 +26,7 @@ namespace ThingsGateway.Plugin.RabbitMQ;
/// <summary>
/// RabbitMQProducer
/// </summary>
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<VariableData, DeviceData, AlarmVariable>
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private IConnection _connection;
private ConnectionFactory _connectionFactory;
@@ -55,17 +55,17 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
{
return UpdateAlarmModel(item.Select(a => a.Value), cancellationToken);
return UpdateAlarmModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
{
return UpdateDevModel(item.Select(a => a.Value), cancellationToken);
return UpdateDevModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableData>> item, CancellationToken cancellationToken)
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
{
return UpdateVarModel(item.Select(a => a.Value), cancellationToken);
return UpdateVarModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
}
protected override void VariableTimeInterval(VariableRuntime variableRuntime, VariableBasicData variable)
{
@@ -110,15 +110,15 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceData> item, CancellationToken cancellationToken)
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableData> item, CancellationToken cancellationToken)
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariable(item);
List<TopicJson> topicJsonList = GetVariableBasicData(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
@@ -130,9 +130,9 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
{
//保留消息
//分解List避免超出字节大小限制
var varData = VariableRuntimes.Select(a => a.Value).Adapt<List<VariableData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
var varData = IdVariableRuntimes.Select(a => a.Value).Adapt<List<VariableBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var devData = CollectDevices?.Select(a => a.Value).Adapt<List<DeviceBasicData>>().ChunkBetter(_driverPropertys.SplitSize);
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).Adapt<List<AlarmVariable>>().ChunkBetter(_driverPropertys.SplitSize);
foreach (var item in varData)
{
if (!success)

View File

@@ -199,7 +199,7 @@ public partial class GatewayIndex : IDisposable
{
return new TimelineItem()
{
Content = $"{a.OperateObject} [Source] {a.OperateSource} ",
Content = $"{a.OperateDevice} : {a.OperateObject} [Source] {a.OperateSource} ",
Description = a.LogTime.ToDefaultDateTimeFormat()
};

View File

@@ -95,12 +95,12 @@ public partial class GatewayIndexComponent : IDisposable
AlarmChartDataSource.Labels = data.Select(a => Localizer["AlarmCount"].Value);
AlarmChartDataSource.Data.Add(new ChartDataset()
{
Data = new List<object>() { GlobalData.ReadOnlyRealAlarmVariables.Count }
Data = new List<object>() { GlobalData.ReadOnlyRealAlarmIdVariables.Count }
});
}
else
{
AlarmChartDataSource.Data[0].Data = new List<object>() { GlobalData.ReadOnlyRealAlarmVariables.Count };
AlarmChartDataSource.Data[0].Data = new List<object>() { GlobalData.ReadOnlyRealAlarmIdVariables.Count };
}
return Task.FromResult(AlarmChartDataSource!);
}

View File

@@ -1,15 +1,29 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5000"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59494/",
"sslPort": 44372
}
}
}
}

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.0.2.18</Version>
<Version>10.1.0.1</Version>
</PropertyGroup>
<ItemGroup>