fix: 变量实时值偶发错误

feat: 优化内存占用
This commit is contained in:
2248356998 qq.com
2025-01-25 16:02:06 +08:00
parent 3cb2592660
commit ee4936b8c9
33 changed files with 326 additions and 307 deletions

View File

@@ -21,6 +21,7 @@ public class TestController : ControllerBase
[HttpPost]
public async Task Test(string data)
{
GC.Collect();
await Task.CompletedTask.ConfigureAwait(false);
}
}

View File

@@ -22,89 +22,87 @@
@using System.ComponentModel.DataAnnotations
@inject NavigationManager NavigationManager
@if (AppContext.CurrentUser != null && AppContext.OwnMenus != null)
{
<LoginConnectionHub />
<CascadingValue Value="ReloadMenu" Name="ReloadMenu" IsFixed="true">
<CascadingValue Value="ReloadUser" Name="ReloadUser" IsFixed="true">
<div class="mainlayout">
<LoginConnectionHub />
<CascadingValue Value="ReloadMenu" Name="ReloadMenu" IsFixed="true">
<CascadingValue Value="ReloadUser" Name="ReloadUser" IsFixed="true">
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuService.MenuItems"
AdditionalAssemblies=App.RazorAssemblies AllowDragTab=true
UseTabSet="false" TabDefaultUrl="/">
<Header>
<div class="ms-4"></div>
<ChoiceModuleComponent Value=@(AppContext.CurrentModuleId) ModuleList=@(AppContext.CurrentUser.ModuleList) OnClick=@(ChoiceModule) />
<div class="mainlayout">
<div class="flex-fill"></div>
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuService.MenuItems"
AdditionalAssemblies=App.RazorAssemblies AllowDragTab=true
UseTabSet="false" TabDefaultUrl="/">
<Header>
<div class="ms-4"></div>
<ChoiceModuleComponent Value=@(AppContext.CurrentModuleId) ModuleList=@(AppContext.CurrentUser.ModuleList) OnClick=@(ChoiceModule) />
@* 搜索框 *@
<GlobalSearch Menus=@(MenuService.SameLevelMenuItems) />
<div class="flex-fill"></div>
@* 语言选择 *@
<div class="d-none d-xl-flex ">
<CultureChooser />
@* 搜索框 *@
<GlobalSearch Menus=@(MenuService.SameLevelMenuItems) />
@* 语言选择 *@
<div class="d-none d-xl-flex ">
<CultureChooser />
</div>
<Logout ImageUrl="@(AppContext.CurrentUser.Avatar??$"{WebsiteConst.DefaultResourceUrl}images/defaultUser.svg")" ShowUserName=false DisplayName="@UserManager.UserAccount" UserName="@UserManager.VerificatId.ToString()" PrefixUserNameText=@AdminLocalizer["CurrentVerificat"]>
<LinkTemplate>
<a href=@("/") class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["系统首页"]</a>
<a @onclick="@OnUserInfoDialog" class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["UserCenter"]</a>
<a @onclick="@LogoutAsync" class="h6"><i class="fa-solid fa-key me-2"></i>@Localizer["Logout"]</a>
</LinkTemplate>
</Logout>
@* 全屏按钮 *@
<FullScreenButton class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-arrows-alt"
TooltipPlacement=Placement.Bottom TooltipText="@Localizer[nameof(FullScreenButton)]" />
@if (WebsiteOption.Value.IsShowAbout)
{
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
@* 主题切换 *@
@* <ThemeToggle /> *@
<ThemeProvider class="layout-header-bar d-none d-lg-flex px-0"></ThemeProvider>
</Header>
<Side>
<div class="layout-banner">
<span class="avatar">
@WebsiteOption.Value.Title?.GetNameLen2()
</span>
<div class="layout-title d-flex align-items-center justify-content-center">
<span>@WebsiteOption.Value.Title</span>
</div>
<Logout ImageUrl="@(AppContext.CurrentUser.Avatar??$"{WebsiteConst.DefaultResourceUrl}images/defaultUser.svg")" ShowUserName=false DisplayName="@UserManager.UserAccount" UserName="@UserManager.VerificatId.ToString()" PrefixUserNameText=@AdminLocalizer["CurrentVerificat"]>
<LinkTemplate>
<a href=@("/") class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["系统首页"]</a>
<a @onclick="@OnUserInfoDialog" class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["UserCenter"]</a>
<a @onclick="@LogoutAsync" class="h6"><i class="fa-solid fa-key me-2"></i>@Localizer["Logout"]</a>
</LinkTemplate>
</Logout>
@* 全屏按钮 *@
<FullScreenButton class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-arrows-alt"
TooltipPlacement=Placement.Bottom TooltipText="@Localizer[nameof(FullScreenButton)]" />
@if (WebsiteOption.Value.IsShowAbout)
</div>
</Side>
<Main>
<Tab @ref=Tab ClickTabToNavigation="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.MenuItems"
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
{
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
})>
</Tab>
@* 主题切换 *@
@* <ThemeToggle /> *@
<ThemeProvider class="layout-header-bar d-none d-lg-flex px-0"></ThemeProvider>
</Main>
<NotAuthorized>
<Redirect Url="/Account/Login" />
</NotAuthorized>
</Layout>
</Header>
<Side>
<div class="layout-banner">
<span class="avatar">
@WebsiteOption.Value.Title?.GetNameLen2()
</span>
</div>
<div class="layout-title d-flex align-items-center justify-content-center">
<span>@WebsiteOption.Value.Title</span>
</div>
</div>
</Side>
<Main>
<Tab @ref=Tab ClickTabToNavigation="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.MenuItems"
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
{
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
})>
</Tab>
</Main>
<NotAuthorized>
<Redirect Url="/Account/Login" />
</NotAuthorized>
</Layout>
</div>
</CascadingValue>
</CascadingValue>
</CascadingValue>
}

View File

@@ -166,7 +166,6 @@ public partial class MainLayout : IDisposable
DispatchService.Subscribe(Dispatch);
await AppContext.InitUserAsync();
await AppContext.InitMenus(NavigationManager.ToBaseRelativePath(NavigationManager.Uri));
StateHasChanged();
await base.OnInitializedAsync();
}
private Tab Tab { get; set; }

View File

@@ -349,7 +349,7 @@ public sealed class HttpContextForwardBuilder
// 初始化 MultipartReader 实例
var multipartReader = new MultipartReader(boundary, bodyStream);
while ((await multipartReader.ReadNextSectionAsync(cancellationToken).ConfigureAwait(false))is { } multipartSection)
while ((await multipartReader.ReadNextSectionAsync(cancellationToken).ConfigureAwait(false)) is { } multipartSection)
{
// 检查当前节是否为文件节
if (multipartSection.AsFileSection() is not null)
@@ -364,7 +364,7 @@ public sealed class HttpContextForwardBuilder
await CopyTextMultipartSectionAsync(multipartSection, httpMultipartFormDataBuilder, cancellationToken).ConfigureAwait(false);
}
}
// 设置多部分表单内容

View File

@@ -23,6 +23,7 @@
"False": "No",
"Save": "Save",
"Fail": "Fail {0}",
"Clear": "Clear",
"Success": "Success",
"PleaseSelect": "Please select the data",
"TablesExportButtonExcelText": "Export Excel",

View File

@@ -23,6 +23,7 @@
"False": "否",
"Save": "保存",
"Fail": "失败 {0}",
"Clear": "清空",
"Success": "成功",
"PleaseSelect": "请选择需要操作的数据",
"TablesExportButtonExcelText": "导出Excel",

View File

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

View File

@@ -13,7 +13,6 @@ using Newtonsoft.Json.Linq;
using System.Text;
using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.NewLife.Caching;
namespace ThingsGateway.Foundation;
@@ -22,6 +21,8 @@ namespace ThingsGateway.Foundation;
/// </summary>
public static class ThingsGatewayBitConverterExtension
{
//private static MemoryCache MemoryCache = new() { Capacity = 10000000 };
/// <summary>
/// 从设备地址中解析附加信息
/// 这个方法获取<see cref="IThingsGatewayBitConverter"/>
@@ -36,18 +37,18 @@ public static class ThingsGatewayBitConverterExtension
var type = defaultBitConverter.GetType();
// 尝试从缓存中获取解析结果
var cacheKey = $"{nameof(ThingsGatewayBitConverterExtension)}_{nameof(GetTransByAddress)}_{type.FullName}_{type.TypeHandle.Value}_{defaultBitConverter.ToJsonString()}_{registerAddress}";
if (MemoryCache.Instance.TryGetValue(cacheKey, out IThingsGatewayBitConverter cachedConverter))
{
if (cachedConverter.Equals(defaultBitConverter))
{
return defaultBitConverter;
}
else
{
return (IThingsGatewayBitConverter)cachedConverter.Map(type);
}
}
//var cacheKey = $"{nameof(ThingsGatewayBitConverterExtension)}_{nameof(GetTransByAddress)}_{type.FullName}_{type.TypeHandle.Value}_{defaultBitConverter.ToJsonString()}_{registerAddress}_{defaultBitConverter.GetHashCode()}";
//if (MemoryCache.TryGetValue(cacheKey, out IThingsGatewayBitConverter cachedConverter))
//{
// if (cachedConverter.Equals(defaultBitConverter))
// {
// return defaultBitConverter;
// }
// else
// {
// return (IThingsGatewayBitConverter)cachedConverter.Map(type);
// }
//}
// 去除设备地址两端的空格
registerAddress = registerAddress.Trim();
@@ -109,7 +110,7 @@ public static class ThingsGatewayBitConverterExtension
// 如果没有解析出任何附加信息,则直接返回默认的数据转换器
if (bcdFormat == null && stringlength == null && encoding == null && dataFormat == null && wstring == null)
{
MemoryCache.Instance.Set(cacheKey, defaultBitConverter!, 3600);
//MemoryCache.Set(cacheKey, defaultBitConverter!, 3600);
return defaultBitConverter;
}
@@ -139,7 +140,7 @@ public static class ThingsGatewayBitConverterExtension
}
// 将解析结果添加到缓存中缓存有效期为3600秒
MemoryCache.Instance.Set(cacheKey, converter!, 3600);
//MemoryCache.Set(cacheKey, converter!, 3600);
return converter;
}

View File

@@ -85,7 +85,7 @@ 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 => a.VariablePropertys.ContainsKey(input.BusinessDeviceId))
.WhereIF(input.BusinessDeviceId > 0, a => a.VariablePropertys?.ContainsKey(input.BusinessDeviceId) == true)
.ToPagedList(input);
return data.Adapt<SqlSugarPagedList<AlarmVariable>>();
}
@@ -119,7 +119,7 @@ public class RuntimeInfoController : ControllerBase
.WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name)
.WhereIF(!input.DeviceName.IsNullOrEmpty(), a => a.DeviceName == input.DeviceName)
.WhereIF(!input.RegisterAddress.IsNullOrWhiteSpace(), a => a.RegisterAddress == input.RegisterAddress)
.WhereIF(input.BusinessDeviceId > 0, a => a.VariablePropertys.ContainsKey(input.BusinessDeviceId))
.WhereIF(input.BusinessDeviceId > 0, a => a.VariablePropertys?.ContainsKey(input.BusinessDeviceId) == true)
.ToPagedList(input);
return data;

View File

@@ -71,7 +71,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
// 触发一次设备状态变化和变量值变化事件
CollectDevices.ForEach(a =>
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
@@ -162,10 +162,14 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
{
if (_exT2TimerTick.IsTickHappen())
{
// 间隔推送全部设备
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
if (CollectDevices != null)
{
DeviceTimeInterval(deviceRuntime, deviceRuntime.Adapt<DeviceBasicData>());
// 间隔推送全部设备
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
{
DeviceTimeInterval(deviceRuntime, deviceRuntime.Adapt<DeviceBasicData>());
}
}
}
}
@@ -237,7 +241,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
// 检查当前设备的设备列表是否包含此设备,如果包含,则触发设备的状态变化处理方法
if (CollectDevices.ContainsKey(deviceData.Id))
if (CollectDevices?.ContainsKey(deviceData.Id) == true)
DeviceChange(deviceRuntime, deviceData);
}
}

View File

@@ -75,7 +75,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
}
CollectDevices.ForEach(a =>
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
@@ -159,10 +159,13 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
{
if (_exT2TimerTick.IsTickHappen())
{
// 上传所有设备信息
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
if (CollectDevices != null)
{
DeviceTimeInterval(deviceRuntime, deviceRuntime.Adapt<DeviceBasicData>());
// 上传所有设备信息
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
{
DeviceTimeInterval(deviceRuntime, deviceRuntime.Adapt<DeviceBasicData>());
}
}
}
}
@@ -219,7 +222,7 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
// 检查当前设备集合中是否包含该设备,并进行相应处理
if (CollectDevices.ContainsKey(deviceRuntime.Id))
if (CollectDevices?.ContainsKey(deviceRuntime.Id) == true)
DeviceChange(deviceRuntime, deviceData);
}
}

View File

@@ -167,7 +167,7 @@ public class Variable : BaseDataEntity, IValidatableObject
[SugarColumn(IsJson = true, ColumnDataType = StaticConfig.CodeFirst_BigString, ColumnDescription = "变量属性Json", IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public ConcurrentDictionary<long, Dictionary<string, string>>? VariablePropertys { get; set; } = new();
public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get; set; }
#region
/// <summary>

View File

@@ -35,6 +35,11 @@ public delegate void VariableChangeEventHandler(VariableRuntime variableRuntime,
/// <param name="variableRuntime">变量运行时对象</param>
public delegate void VariableCollectEventHandler(VariableRuntime variableRuntime);
/// <summary>
/// 变量报警事件委托
/// </summary>
public delegate void VariableAlarmEventHandler(AlarmVariable alarmVariable);
/// <summary>
/// 采集设备值与状态全局提供类,用于提供全局的设备状态和变量数据的管理
/// </summary>
@@ -57,7 +62,7 @@ public static class GlobalData
/// <summary>
/// 报警变化事件
/// </summary>
public static event VariableAlarmEventHandler AlarmChangedEvent;
public static event VariableAlarmEventHandler? AlarmChangedEvent;
/// <summary>
/// 只读的通道字典,提供对通道的只读访问

View File

@@ -19,11 +19,6 @@ using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 变量报警事件委托
/// </summary>
public delegate void VariableAlarmEventHandler(AlarmVariable alarmVariable);
/// <summary>
/// 设备采集报警后台服务
/// </summary>

View File

@@ -832,13 +832,14 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
Disposed = true;
try
{
Channel?.SafeDispose();
LogMessage?.LogInformation(Localizer["ChannelDispose", CurrentChannel?.Name ?? string.Empty]);
await NewDeviceLock.WaitAsync().ConfigureAwait(false);
await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
Channel?.SafeDispose();
LogMessage?.LogInformation(Localizer["ChannelDispose", CurrentChannel?.Name ?? string.Empty]);
}
finally
{

View File

@@ -138,9 +138,9 @@ public static class PluginServiceUtil
/// <summary>
/// 通过实体赋值到字典中
/// </summary>
public static ConcurrentDictionary<long, Dictionary<string, string>> SetDict(ConcurrentDictionary<long, ModelValueValidateForm>? models)
public static Dictionary<long, Dictionary<string, string>> SetDict(ConcurrentDictionary<long, ModelValueValidateForm>? models)
{
ConcurrentDictionary<long, Dictionary<string, string>> results = new();
Dictionary<long, Dictionary<string, string>> results = new();
models ??= new();
foreach (var model in models)
{

View File

@@ -43,7 +43,7 @@ public class VariableRuntimeService : IVariableRuntimeService
foreach (var item in group)
{
//需要重启业务线程
var deviceRuntimes = GlobalData.Devices.Where(a => item.Value.VariablePropertys.ContainsKey(a.Key)).Select(a => a.Value);
var deviceRuntimes = GlobalData.Devices.Where(a => item.Value.VariablePropertys?.ContainsKey(a.Key) == true).Select(a => a.Value);
foreach (var deviceRuntime in deviceRuntimes)
{
if (deviceRuntime.Driver != null)
@@ -116,7 +116,7 @@ public class VariableRuntimeService : IVariableRuntimeService
foreach (var item in group)
{
//需要重启业务线程
var deviceRuntimes = GlobalData.Devices.Where(a => item.Value.VariablePropertys.ContainsKey(a.Key)).Select(a => a.Value);
var deviceRuntimes = GlobalData.Devices.Where(a => item.Value.VariablePropertys?.ContainsKey(a.Key) == true).Select(a => a.Value);
foreach (var deviceRuntime in deviceRuntimes)
{
if (deviceRuntime.Driver != null)
@@ -192,7 +192,7 @@ public class VariableRuntimeService : IVariableRuntimeService
foreach (var item in group)
{
//需要重启业务线程
var deviceRuntimes = GlobalData.Devices.Where(a => item.VariablePropertys.ContainsKey(a.Key)).Select(a => a.Value);
var deviceRuntimes = GlobalData.Devices.Where(a => item.VariablePropertys?.ContainsKey(a.Key) == true).Select(a => a.Value);
foreach (var deviceRuntime in deviceRuntimes)
{
if (deviceRuntime.Driver != null)
@@ -254,7 +254,7 @@ public class VariableRuntimeService : IVariableRuntimeService
foreach (var item in group)
{
//需要重启业务线程
var deviceRuntimes = GlobalData.Devices.Where(a => item.Value.VariablePropertys.ContainsKey(a.Key)).Select(a => a.Value);
var deviceRuntimes = GlobalData.Devices.Where(a => item.Value.VariablePropertys?.ContainsKey(a.Key) == true).Select(a => a.Value);
foreach (var deviceRuntime in deviceRuntimes)
{
if (deviceRuntime.Driver != null)
@@ -437,7 +437,7 @@ public class VariableRuntimeService : IVariableRuntimeService
}
//需要重启业务线程
var deviceRuntimes = GlobalData.Devices.Where(a => newVariableRuntime.VariablePropertys.ContainsKey(a.Key)).Select(a => a.Value);
var deviceRuntimes = GlobalData.Devices.Where(a => newVariableRuntime.VariablePropertys?.ContainsKey(a.Key) == true).Select(a => a.Value);
foreach (var businessDeviceRuntime in deviceRuntimes)
{
if (businessDeviceRuntime.Driver != null)

View File

@@ -443,90 +443,93 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
variableExports.Add(varExport);
#region sheet
foreach (var item in variable.VariablePropertys ?? new())
if (variable.VariablePropertys != null)
{
//插件属性
//单个设备的行数据
Dictionary<string, object> driverInfo = new();
var has = deviceDicts.TryGetValue(item.Key, out var businessDevice);
if (!has)
continue;
channelDicts.TryGetValue(businessDevice.ChannelId, out var channel);
//没有包含设备名称,手动插入
driverInfo.TryAdd(ExportString.DeviceName, businessDevice.Name);
driverInfo.TryAdd(ExportString.VariableName, variable.Name);
var propDict = item.Value;
if (propertysDict.TryGetValue(channel.PluginName, out var propertys))
foreach (var item in variable.VariablePropertys)
{
}
else
{
try
//插件属性
//单个设备的行数据
Dictionary<string, object> driverInfo = new();
var has = deviceDicts.TryGetValue(item.Key, out var businessDevice);
if (!has)
continue;
channelDicts.TryGetValue(businessDevice.ChannelId, out var channel);
//没有包含设备名称,手动插入
driverInfo.TryAdd(ExportString.DeviceName, businessDevice.Name);
driverInfo.TryAdd(ExportString.VariableName, variable.Name);
var propDict = item.Value;
if (propertysDict.TryGetValue(channel.PluginName, out var propertys))
{
var variableProperty = ((BusinessBase)_pluginService.GetDriver(channel.PluginName))?.VariablePropertys;
propertys.Item1 = variableProperty;
var variablePropertyType = variableProperty.GetType();
propertys.Item2 = variablePropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => variablePropertyType.GetPropertyDisplayName(a.Name, a => a.GetCustomAttribute<DynamicPropertyAttribute>(true)?.Description));
propertysDict.TryAdd(channel.PluginName, propertys);
}
catch
{
}
}
if (propertys.Item2?.Count == null)
{
continue;
}
//根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值
foreach (var item1 in propertys.Item2)
{
if (propDict.TryGetValue(item1.Value.Name, out var dependencyProperty))
{
driverInfo.TryAdd(item1.Key, dependencyProperty);
}
else
{
//添加对应属性数据
driverInfo.TryAdd(item1.Key, ThingsGatewayStringConverter.Default.Serialize(null, item1.Value.GetValue(propertys.Item1)));
}
}
if (!driverPluginDicts.ContainsKey(channel.PluginName))
continue;
var pluginName = PluginServiceUtil.GetFileNameAndTypeName(channel.PluginName);
//lock (devicePropertys)
{
if (devicePropertys.ContainsKey(pluginName.Item2))
{
if (driverInfo.Count > 0)
devicePropertys[pluginName.Item2].Add(driverInfo);
}
else
{
lock (devicePropertys)
try
{
if (devicePropertys.ContainsKey(pluginName.Item2))
{
if (driverInfo.Count > 0)
devicePropertys[pluginName.Item2].Add(driverInfo);
}
else
{
if (driverInfo.Count > 0)
devicePropertys.TryAdd(pluginName.Item2, new() { driverInfo });
}
var variableProperty = ((BusinessBase)_pluginService.GetDriver(channel.PluginName))?.VariablePropertys;
propertys.Item1 = variableProperty;
var variablePropertyType = variableProperty.GetType();
propertys.Item2 = variablePropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => variablePropertyType.GetPropertyDisplayName(a.Name, a => a.GetCustomAttribute<DynamicPropertyAttribute>(true)?.Description));
propertysDict.TryAdd(channel.PluginName, propertys);
}
catch
{
}
}
if (propertys.Item2?.Count == null)
{
continue;
}
//根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值
foreach (var item1 in propertys.Item2)
{
if (propDict.TryGetValue(item1.Value.Name, out var dependencyProperty))
{
driverInfo.TryAdd(item1.Key, dependencyProperty);
}
else
{
//添加对应属性数据
driverInfo.TryAdd(item1.Key, ThingsGatewayStringConverter.Default.Serialize(null, item1.Value.GetValue(propertys.Item1)));
}
}
if (!driverPluginDicts.ContainsKey(channel.PluginName))
continue;
var pluginName = PluginServiceUtil.GetFileNameAndTypeName(channel.PluginName);
//lock (devicePropertys)
{
if (devicePropertys.ContainsKey(pluginName.Item2))
{
if (driverInfo.Count > 0)
devicePropertys[pluginName.Item2].Add(driverInfo);
}
else
{
lock (devicePropertys)
{
if (devicePropertys.ContainsKey(pluginName.Item2))
{
if (driverInfo.Count > 0)
devicePropertys[pluginName.Item2].Add(driverInfo);
}
else
{
if (driverInfo.Count > 0)
devicePropertys.TryAdd(pluginName.Item2, new() { driverInfo });
}
}
}
}
}

View File

@@ -190,7 +190,7 @@ public partial class VariableEditComponent
VariablePropertyRenderFragments.AddOrUpdate(id, component.Render());
}
if (Model.VariablePropertys.TryGetValue(id, out var dict))
if (Model.VariablePropertys?.TryGetValue(id, out var dict) == true)
{
PluginServiceUtil.SetModel(data.Model, dict);
}

View File

@@ -60,7 +60,7 @@ public class Dlt645_2007Master : CollectBase
_plc.CheckClearTime = _driverPropertys.CheckClearTime;
_plc.Station = _driverPropertys.Station;
_plc.Heartbeat = _driverPropertys.Heartbeat;
_plc.InitChannel(channel,LogMessage);
_plc.InitChannel(channel, LogMessage);
base.InitChannel(channel);
}

View File

@@ -128,7 +128,7 @@ 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 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);
foreach (var item in varData)
{
@@ -136,12 +136,14 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
break;
await UpdateVarModel(item, cancellationToken).ConfigureAwait(false);
}
foreach (var item in devData)
if (devData != null)
{
if (!success)
break;
await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
foreach (var item in devData)
{
if (!success)
break;
await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
}
}
foreach (var item in alramData)
{

View File

@@ -60,7 +60,7 @@ public class ModbusMaster : CollectBase
_plc.CheckClearTime = _driverPropertys.CheckClearTime;
_plc.ModbusType = _driverPropertys.ModbusType;
_plc.Heartbeat = _driverPropertys.Heartbeat;
_plc.InitChannel(channel,LogMessage);
_plc.InitChannel(channel, LogMessage);
base.InitChannel(channel);
}

View File

@@ -85,7 +85,7 @@ public class ModbusSlave : BusinessBase
_plc.DtuId = _driverPropertys.DtuId;
_plc.HeartbeatTime = _driverPropertys.HeartbeatTime;
_plc.Heartbeat = _driverPropertys.Heartbeat;
_plc.InitChannel(channel,LogMessage);
_plc.InitChannel(channel, LogMessage);
base.InitChannel(channel);
_plc.WriteData -= OnWriteData;
@@ -147,10 +147,13 @@ public class ModbusSlave : BusinessBase
CurrentDevice.SetDeviceStatus(TimerX.Now, true);
try
{
if (cancellationToken.IsCancellationRequested)
return;
await FoundationDevice.Channel.CloseAsync().ConfigureAwait(false);
await FoundationDevice.Channel.ConnectAsync(3000, cancellationToken).ConfigureAwait(false);
success = true;
}
catch (ObjectDisposedException) { }
catch (Exception ex)
{
if (success)

View File

@@ -199,7 +199,7 @@ public partial class MqttClient : 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<DeviceBasicData>>().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);
foreach (var item in varData)
{
@@ -207,12 +207,14 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableDa
break;
await UpdateVarModel(item, cancellationToken).ConfigureAwait(false);
}
foreach (var item in devData)
if (devData != null)
{
if (!success)
break;
await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
foreach (var item in devData)
{
if (!success)
break;
await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
}
}
foreach (var item in alramData)

View File

@@ -66,7 +66,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
}
/// <inheritdoc/>
protected override async void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_mqttServer != null)
@@ -79,7 +79,6 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
}
if (_webHost != null)
{
await _webHost.StopAsync().ConfigureAwait(false);
_webHost.SafeDispose();
}

View File

@@ -173,7 +173,7 @@ 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 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);
List<MqttApplicationMessage> Messages = new();
@@ -192,14 +192,17 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableDa
}
if (!_businessPropertyWithCacheIntervalScript.DeviceTopic.IsNullOrEmpty())
{
foreach (var item in devData)
if (devData != null)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
foreach (var topicJson in topicJsonList)
foreach (var item in devData)
{
Messages.Add(new MqttApplicationMessageBuilder()
.WithTopic(topicJson.Topic)
.WithPayload(topicJson.Json).Build());
List<TopicJson> topicJsonList = GetDeviceData(item);
foreach (var topicJson in topicJsonList)
{
Messages.Add(new MqttApplicationMessageBuilder()
.WithTopic(topicJson.Topic)
.WithPayload(topicJson.Json).Build());
}
}
}
}

View File

@@ -131,7 +131,7 @@ 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 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);
foreach (var item in varData)
{
@@ -139,12 +139,14 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
break;
await UpdateVarModel(item, cancellationToken).ConfigureAwait(false);
}
foreach (var item in devData)
if (devData != null)
{
if (!success)
break;
await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
foreach (var item in devData)
{
if (!success)
break;
await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
}
}
foreach (var item in alramData)

View File

@@ -64,7 +64,7 @@ public class SiemensS7Master : CollectBase
_plc.LocalTSAP = _driverPropertys.LocalTSAP;
_plc.Rack = _driverPropertys.Rack;
_plc.Slot = _driverPropertys.Slot;
_plc.InitChannel(channel,LogMessage);
_plc.InitChannel(channel, LogMessage);
base.InitChannel(channel);
}

View File

@@ -121,7 +121,7 @@ internal sealed class Program
return false;
};
hybridApp.Run();
Thread.Sleep(5000);
Thread.Sleep(3000);
}

View File

@@ -1,9 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>ThingsGateway.Debug</title>
<title>ThingsGateway.Hybrid</title>
<base href="/" />
<link rel="icon" href="favicon.ico" type="image/x-icon">
@@ -15,7 +16,6 @@
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script>
</head>
<body>

View File

@@ -22,89 +22,86 @@
@using System.ComponentModel.DataAnnotations
@inject NavigationManager NavigationManager
@if (AppContext.CurrentUser != null && AppContext.OwnMenus != null)
{
<LoginConnectionHub />
<CascadingValue Value="ReloadMenu" Name="ReloadMenu" IsFixed="true">
<CascadingValue Value="ReloadUser" Name="ReloadUser" IsFixed="true">
<LoginConnectionHub />
<CascadingValue Value="ReloadMenu" Name="ReloadMenu" IsFixed="true">
<CascadingValue Value="ReloadUser" Name="ReloadUser" IsFixed="true">
<div class="mainlayout">
<div class="mainlayout">
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuService.MenuItems"
AdditionalAssemblies=App.RazorAssemblies AllowDragTab=true
UseTabSet="false" TabDefaultUrl="/">
<Header>
<div class="ms-4"></div>
<ChoiceModuleComponent Value=@(AppContext.CurrentModuleId) ModuleList=@(AppContext.CurrentUser.ModuleList) OnClick=@(ChoiceModule) />
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuService.MenuItems"
AdditionalAssemblies=App.RazorAssemblies AllowDragTab=true
UseTabSet="false" TabDefaultUrl="/">
<Header>
<div class="ms-4"></div>
<ChoiceModuleComponent Value=@(AppContext.CurrentModuleId) ModuleList=@(AppContext.CurrentUser.ModuleList) OnClick=@(ChoiceModule) />
<div class="flex-fill"></div>
<div class="flex-fill"></div>
@* 搜索框 *@
<GlobalSearch Menus=@(MenuService.SameLevelMenuItems) />
@* 搜索框 *@
<GlobalSearch Menus=@(MenuService.SameLevelMenuItems) />
@* 语言选择 *@
<div class="d-none d-xl-flex ">
<CultureChooser />
@* 语言选择 *@
<div class="d-none d-xl-flex ">
<CultureChooser />
</div>
<Logout ImageUrl="@(AppContext.CurrentUser.Avatar??$"{WebsiteConst.DefaultResourceUrl}images/defaultUser.svg")" ShowUserName=false DisplayName="@UserManager.UserAccount" UserName="@UserManager.VerificatId.ToString()" PrefixUserNameText=@AdminLocalizer["CurrentVerificat"]>
<LinkTemplate>
<a href=@("/") class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["系统首页"]</a>
<a @onclick="@OnUserInfoDialog" class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["UserCenter"]</a>
<a @onclick="@LogoutAsync" class="h6"><i class="fa-solid fa-key me-2"></i>@Localizer["Logout"]</a>
</LinkTemplate>
</Logout>
@* 全屏按钮 *@
<FullScreenButton class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-arrows-alt"
TooltipPlacement=Placement.Bottom TooltipText="@Localizer[nameof(FullScreenButton)]" />
@if (WebsiteOption.Value.IsShowAbout)
{
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
@* 主题切换 *@
@* <ThemeToggle /> *@
<ThemeProvider class="layout-header-bar d-none d-lg-flex px-0"></ThemeProvider>
</Header>
<Side>
<div class="layout-banner">
<span class="avatar">
@WebsiteOption.Value.Title?.GetNameLen2()
</span>
<div class="layout-title d-flex align-items-center justify-content-center">
<span>@WebsiteOption.Value.Title</span>
</div>
<Logout ImageUrl="@(AppContext.CurrentUser.Avatar??$"{WebsiteConst.DefaultResourceUrl}images/defaultUser.svg")" ShowUserName=false DisplayName="@UserManager.UserAccount" UserName="@UserManager.VerificatId.ToString()" PrefixUserNameText=@AdminLocalizer["CurrentVerificat"]>
<LinkTemplate>
<a href=@("/") class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["系统首页"]</a>
<a @onclick="@OnUserInfoDialog" class="h6"><i class="fa-solid fa-suitcase me-2"></i>@Localizer["UserCenter"]</a>
<a @onclick="@LogoutAsync" class="h6"><i class="fa-solid fa-key me-2"></i>@Localizer["Logout"]</a>
</LinkTemplate>
</Logout>
@* 全屏按钮 *@
<FullScreenButton class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-arrows-alt"
TooltipPlacement=Placement.Bottom TooltipText="@Localizer[nameof(FullScreenButton)]" />
@if (WebsiteOption.Value.IsShowAbout)
</div>
</Side>
<Main>
<Tab @ref=Tab ClickTabToNavigation="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems"
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
{
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
})>
</Tab>
@* 主题切换 *@
@* <ThemeToggle /> *@
<ThemeProvider class="layout-header-bar d-none d-lg-flex px-0"></ThemeProvider>
</Main>
<NotAuthorized>
<Redirect Url="/Account/Login" />
</NotAuthorized>
</Layout>
</Header>
<Side>
<div class="layout-banner">
<span class="avatar">
@WebsiteOption.Value.Title?.GetNameLen2()
</span>
</div>
<div class="layout-title d-flex align-items-center justify-content-center">
<span>@WebsiteOption.Value.Title</span>
</div>
</div>
</Side>
<Main>
<Tab @ref=Tab ClickTabToNavigation="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
AdditionalAssemblies="@App.RazorAssemblies" Menus="@MenuService.AllOwnMenuItems"
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
{
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
})>
</Tab>
</Main>
<NotAuthorized>
<Redirect Url="/Account/Login" />
</NotAuthorized>
</Layout>
</div>
</CascadingValue>
</CascadingValue>
</CascadingValue>
}

View File

@@ -166,7 +166,6 @@ public partial class MainLayout : IDisposable
DispatchService.Subscribe(Dispatch);
await AppContext.InitUserAsync();
await AppContext.InitMenus(NavigationManager.ToBaseRelativePath(NavigationManager.Uri));
StateHasChanged();
await base.OnInitializedAsync();
}
private Tab Tab { get; set; }

View File

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