Compare commits

...

15 Commits

Author SHA1 Message Date
2248356998 qq.com
ff1f632de2 build: 10.11.67 sqldb插件 2025-09-23 22:38:05 +08:00
2248356998 qq.com
fc3d7015ee sqldb插件 清理数据cron表达式错误 2025-09-23 22:36:45 +08:00
2248356998 qq.com
40c5acb522 调整UI 2025-09-23 13:41:20 +08:00
2248356998 qq.com
f9cc1cbb05 10.11.65 2025-09-23 12:02:17 +08:00
2248356998 qq.com
cf6e8b58f0 更新内存变量 2025-09-23 01:22:02 +08:00
2248356998 qq.com
615e3bb24c 修改 脚本调试项目 依赖项 2025-09-22 14:58:03 +08:00
2248356998 qq.com
4a7534b210 添加 脚本调试项目 2025-09-22 10:36:28 +08:00
2248356998 qq.com
58e099cb93 10.11.58 2025-09-21 20:53:50 +08:00
2248356998 qq.com
a94a9c953c 10.11.57 2025-09-19 14:32:34 +08:00
Diego
35e1ffa3e9 添加文件 2025-09-18 17:54:29 +08:00
Diego
4921642151 10.11.56 2025-09-18 17:15:57 +08:00
2248356998 qq.com
d71ee29da8 10.11.54 2025-09-17 20:50:41 +08:00
2248356998 qq.com
901aa2d59f 10.11.52 2025-09-17 11:07:45 +08:00
2248356998 qq.com
764957c014 10.11.51 2025-09-16 22:19:01 +08:00
Diego
0e3898218b 10.11.44 2025-09-16 19:13:19 +08:00
152 changed files with 3400 additions and 915 deletions

View File

@@ -15,7 +15,7 @@
</Card>
</div>
<div class="col-12 col-sm-10 h-100">
<div class="col-12 col-sm-10 h-100 ps-2">
<AdminTable @ref=table TItem="SysUser"
AutoGenerateColumns="true"
ShowAdvancedSearch=false

View File

@@ -5,7 +5,7 @@
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
</ItemGroup>

View File

@@ -26,6 +26,8 @@
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css?v={this.GetType().Assembly.GetName().Version}") />
@* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
<!-- PWA Manifest -->
<link rel="manifest" href="./manifest.json" />
@@ -44,6 +46,7 @@
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>
<script src="pwa-install.js"></script>
</body>
</html>

View File

@@ -0,0 +1,34 @@
let installPromptTriggered = false;
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
function hasShownInstallPrompt() {
return getCookie("tgPWAInstallPromptShown") === "true";
}
function markInstallPromptShown() {
document.cookie = "tgPWAInstallPromptShown=true; max-age=31536000; path=/";
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
if (!hasShownInstallPrompt() && !installPromptTriggered) {
installPromptTriggered = true;
setTimeout(() => {
e.prompt()
.then(() => e.userChoice)
.then(choiceResult => {
markInstallPromptShown();
})
.catch(err => {
// 可选错误处理
});
}, 2000); // 延迟 2 秒提示
} else {
// console.log("已提示过安装,不再弹出");
}
});

View File

@@ -24,7 +24,7 @@ public static class ParallelExtensions
/// <typeparam name="T">集合元素类型</typeparam>
/// <param name="source">要操作的集合</param>
/// <param name="body">要执行的操作</param>
public static void ParallelForEach<T>(this IList<T> source, Action<T> body)
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body)
{
ParallelOptions options = new();
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
@@ -38,7 +38,7 @@ public static class ParallelExtensions
/// <typeparam name="T">集合元素类型</typeparam>
/// <param name="source">要操作的集合</param>
/// <param name="body">要执行的操作</param>
public static void ParallelForEach<T>(this IList<T> source, Action<T, ParallelLoopState, long> body)
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T, ParallelLoopState, long> body)
{
ParallelOptions options = new();
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
@@ -53,7 +53,7 @@ public static class ParallelExtensions
/// <param name="source">要操作的集合</param>
/// <param name="body">要执行的操作</param>
/// <param name="parallelCount">最大并行度</param>
public static void ParallelForEach<T>(this IList<T> source, Action<T> body, int parallelCount)
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body, int parallelCount)
{
// 创建并行操作的选项对象,设置最大并行度为指定的值
var options = new ParallelOptions();
@@ -109,7 +109,7 @@ public static class ParallelExtensions
/// <param name="parallelCount">最大并行度</param>
/// <param name="cancellationToken">取消操作的标志</param>
/// <returns>表示异步操作的任务</returns>
public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default)
public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default)
{
// 创建并行操作的选项对象,设置最大并行度和取消标志
var options = new ParallelOptions();
@@ -126,7 +126,7 @@ public static class ParallelExtensions
/// <param name="body">异步执行的操作</param>
/// <param name="cancellationToken">取消操作的标志</param>
/// <returns>表示异步操作的任务</returns>
public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default)
public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default)
{
return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken);
}

View File

@@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.10.1" />
<PackageReference Include="BootstrapBlazor" Version="9.10.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -27,10 +27,15 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
public virtual long Id { get; set; }
}
public interface IPrimaryKeyEntity
{
string ExtJson { get; set; }
}
/// <summary>
/// 主键实体基类
/// </summary>
public abstract class PrimaryKeyEntity : PrimaryIdEntity
public abstract class PrimaryKeyEntity : PrimaryIdEntity, IPrimaryKeyEntity
{
/// <summary>
/// 拓展信息

View File

@@ -15,23 +15,19 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
public void TryAdd(TKey key, TValue value)
{
if (_cleanupTimer.Disposed) throw new ObjectDisposedException(nameof(ExpiringDictionary<TKey, TValue>));
_dict.TryAdd(key, value);
}
public bool TryGetValue(TKey key, out TValue value)
{
if (_cleanupTimer.Disposed) throw new ObjectDisposedException(nameof(ExpiringDictionary<TKey, TValue>));
return _dict.TryGetValue(key, out value);
}
public TValue GetOrAdd(TKey key, Func<TKey, TValue> func)
{
if (_cleanupTimer.Disposed) throw new ObjectDisposedException(nameof(ExpiringDictionary<TKey, TValue>));
return _dict.GetOrAdd(key, func);
}
public TValue GetOrAdd(TKey key, TValue value)
{
if (_cleanupTimer.Disposed) throw new ObjectDisposedException(nameof(ExpiringDictionary<TKey, TValue>));
return _dict.GetOrAdd(key, value);
}

View File

@@ -106,6 +106,17 @@ public static class Runtime
#if NET6_0_OR_GREATER
public static Boolean IsSystemd
{
get
{
var id = Environment.GetEnvironmentVariable("INVOCATION_ID");
return !string.IsNullOrEmpty(id);
}
}
public static Boolean? isLegacyWindows;
/// <summary>

View File

@@ -10,7 +10,7 @@
using Newtonsoft.Json.Linq;
namespace ThingsGateway.Foundation;
namespace ThingsGateway.NewLife.Json.Extension;
/// <summary>
/// JTokenUtil
@@ -131,6 +131,63 @@ public static class JTokenUtil
}
}
/// <summary>
/// 把任意对象转换为 JToken。
/// 支持 JsonElement、JToken、本地 CLR 类型。
/// </summary>
public static JToken GetJTokenFromObj(this object value)
{
if (value == null)
return JValue.CreateNull();
switch (value)
{
case JToken jt:
return jt;
#if NET6_0_OR_GREATER
case System.Text.Json.JsonElement elem:
return elem.ToJToken();
#endif
case string s:
return new JValue(s);
case bool b:
return new JValue(b);
case int i:
return new JValue(i);
case long l:
return new JValue(l);
case double d:
return new JValue(d);
case float f:
return new JValue(f);
case decimal m:
return new JValue(m);
case DateTime dt:
return new JValue(dt);
case DateTimeOffset dto:
return new JValue(dto);
case Guid g:
return new JValue(g);
default:
// 兜底:用 Newtonsoft 来包装成 JToken
return JToken.FromObject(value);
}
}
#region json
/// <summary>

View File

@@ -0,0 +1,176 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
#if NET6_0_OR_GREATER
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Globalization;
using System.Numerics;
using System.Text.Json;
namespace ThingsGateway.NewLife.Json.Extension;
public static class JsonElementExtensions
{
public static string GetValue(object src, bool parseBoolNumber = false)
{
if (src == null)
return string.Empty;
switch (src)
{
case string strValue:
return strValue;
case bool boolValue:
return boolValue ? parseBoolNumber ? "1" : "True" : parseBoolNumber ? "0" : "False";
case JsonElement elem: // System.Text.Json.JsonElement
return elem.ValueKind switch
{
JsonValueKind.String => elem.GetString(),
JsonValueKind.Number => elem.GetRawText(), // 或 elem.GetDecimal().ToString()
JsonValueKind.True => "1",
JsonValueKind.False => "0",
JsonValueKind.Null => string.Empty,
_ => elem.GetRawText(), // 对象、数组等直接输出 JSON
};
default:
return (src).GetJTokenFromObj().ToString();
}
}
/// <summary>
/// 将 System.Text.Json.JsonElement 递归转换为 Newtonsoft.Json.Linq.JToken
/// - tryParseDates: 是否尝试把字符串解析为 DateTime/DateTimeOffset
/// - tryParseGuids: 是否尝试把字符串解析为 Guid
/// </summary>
public static JToken ToJToken(this JsonElement element, bool tryParseDates = true, bool tryParseGuids = true)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
var obj = new JObject();
foreach (var prop in element.EnumerateObject())
obj.Add(prop.Name, prop.Value.ToJToken(tryParseDates, tryParseGuids));
return obj;
case JsonValueKind.Array:
var arr = new JArray();
foreach (var item in element.EnumerateArray())
arr.Add(item.ToJToken(tryParseDates, tryParseGuids));
return arr;
case JsonValueKind.String:
// 优先按语义尝试解析 Guid / DateTimeOffset / DateTime
if (tryParseGuids && element.TryGetGuid(out Guid g))
return new JValue(g);
if (tryParseDates && element.TryGetDateTimeOffset(out DateTimeOffset dto))
return new JValue(dto);
if (tryParseDates && element.TryGetDateTime(out DateTime dt))
return new JValue(dt);
return new JValue(element.GetString());
case JsonValueKind.Number:
return NumberElementToJToken(element);
case JsonValueKind.True:
return new JValue(true);
case JsonValueKind.False:
return new JValue(false);
case JsonValueKind.Null:
case JsonValueKind.Undefined:
default:
return JValue.CreateNull();
}
}
private static JToken NumberElementToJToken(JsonElement element)
{
// 取原始文本(保持原始表示,方便处理超出标准类型范围的数字)
string raw = element.GetRawText(); // 例如 "123", "1.23e4"
// 如果不含小数点或指数优先尝试整数解析long / ulong / BigInteger
if (!raw.Contains('.') && !raw.Contains('e') && !raw.Contains('E'))
{
if (long.TryParse(raw, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var l))
return new JValue(l);
if (ulong.TryParse(raw, NumberStyles.None, CultureInfo.InvariantCulture, out var ul))
return new JValue(ul);
if (BigInteger.TryParse(raw, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var bi))
// BigInteger 可能不被 JValue 直接识别为数字类型,使用 FromObject 保证正确表示
return JToken.FromObject(bi);
}
// 含小数或指数,或整数解析失败,尝试 decimal -> double
if (decimal.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec))
return new JValue(dec);
if (double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var d))
return new JValue(d);
// 最后兜底:把原始文本当字符串返回(极端情况)
return new JValue(raw);
}
/// <summary>
/// 把 JToken 转成“平面”字符串,适合用于日志或写入 CSV 的单元格:
/// - string -> 原文
/// - bool -> "1"/"0"
/// - number -> 原始数字文本
/// - date -> ISO 8601 (o)
/// - object/array -> 紧凑的 JSON 文本
/// - null/undefined -> empty string
/// </summary>
public static string JTokenToPlainString(this JToken token)
{
if (token == null || token.Type == JTokenType.Null || token.Type == JTokenType.Undefined)
return string.Empty;
switch (token.Type)
{
case JTokenType.String:
return token.Value<string>() ?? string.Empty;
case JTokenType.Boolean:
return token.Value<bool>() ? "1" : "0";
case JTokenType.Integer:
case JTokenType.Float:
// 保持紧凑数字文本(不加引号)
return token.ToString(Formatting.None);
case JTokenType.Date:
{
// Date 类型可能是 DateTime 或 DateTimeOffset
var val = token.Value<object>();
if (val is DateTimeOffset dto) return dto.ToString("o");
if (val is DateTime dt) return dt.ToString("o");
return token.ToString(Formatting.None);
}
default:
// 对象/数组等,返回紧凑 JSON 表示
return token.ToString(Formatting.None);
}
}
}
#endif

View File

@@ -21,6 +21,7 @@ public class Setting : Config<Setting>
#region
/// <summary>是否启用全局调试。默认启用</summary>
[Description("全局调试。XTrace.Debug")]
[XmlIgnore, IgnoreDataMember]
public Boolean Debug { get; set; } = true;
/// <summary>日志等级只输出大于等于该级别的日志All/Debug/Info/Warn/Error/Fatal默认Info</summary>
@@ -30,6 +31,7 @@ public class Setting : Config<Setting>
/// <summary>文件日志目录。默认Log子目录</summary>
[Description("文件日志目录。默认Log子目录")]
[XmlIgnore, IgnoreDataMember]
public String LogPath { get; set; } = "Logs/XLog";
/// <summary>日志文件上限。超过上限后拆分新日志文件默认5MB0表示不限制大小</summary>
@@ -42,6 +44,7 @@ public class Setting : Config<Setting>
/// <summary>日志文件格式。默认{0:yyyy_MM_dd}.log支持日志等级如 {1}_{0:yyyy_MM_dd}.log</summary>
[Description("日志文件格式。默认{0:yyyy_MM_dd}.log支持日志等级如 {1}_{0:yyyy_MM_dd}.log")]
[XmlIgnore, IgnoreDataMember]
public String LogFileFormat { get; set; } = "{0:yyyy_MM_dd}.log";
/// <summary>日志行格式。默认Time|ThreadId|Kind|Name|Message还支持Level</summary>

View File

@@ -52,7 +52,7 @@
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>

View File

@@ -2,5 +2,5 @@
@if (show)
{
<Spinner class="ms-auto"></Spinner>
<Spinner Size="Size" class="ms-auto"></Spinner>
}

View File

@@ -18,4 +18,7 @@ public partial class SpinnerComponent
StateHasChanged();
}
private bool show;
[Parameter]
public Size Size { get; set; } = Size.Small;
}

View File

@@ -1,11 +1,11 @@
<Project>
<PropertyGroup>
<PluginVersion>10.11.43</PluginVersion>
<ProPluginVersion>10.11.43</ProPluginVersion>
<DefaultVersion>10.11.43</DefaultVersion>
<AuthenticationVersion>10.11.5</AuthenticationVersion>
<SourceGeneratorVersion>10.11.4</SourceGeneratorVersion>
<PluginVersion>10.11.67</PluginVersion>
<ProPluginVersion>10.11.67</ProPluginVersion>
<DefaultVersion>10.11.67</DefaultVersion>
<AuthenticationVersion>10.11.6</AuthenticationVersion>
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
<NET8Version>8.0.20</NET8Version>
<NET9Version>9.0.9</NET9Version>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>

View File

@@ -136,6 +136,9 @@ public static class CSharpScriptEngineExtension
}
catch (NullReferenceException)
{
//如果编译失败应该不重复编译避免oom
Instance.Set<T>(field, null, TimeSpan.FromHours(1));
string exString = string.Format(CSScriptResource.CSScriptResource.Error1, typeof(T).FullName);
throw new(exString);
}

View File

@@ -68,6 +68,7 @@ public class ModbusMasterDemo
bytes = await device.ModbusReadAsync(new ModbusAddress()
{
Station = 1,
StartAddress = 0,
FunctionCode = 3,
Length = 10,
@@ -92,6 +93,7 @@ public class ModbusMasterDemo
write = await device.ModbusRequestAsync(new ModbusAddress()
{
Station = 1,
StartAddress = 0,
FunctionCode = 3,
MasterWriteDatas = device.ThingsGatewayBitConverter.GetBytes(new double[] { 123.456, 123.456 })

View File

@@ -14,6 +14,7 @@ using Newtonsoft.Json.Linq;
using System.Linq.Expressions;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.NewLife.Reflection;
namespace ThingsGateway.Foundation;
@@ -68,12 +69,12 @@ public abstract class VariableObject
/// <returns></returns>
public virtual JToken GetExpressionsValue(object value, VariableRuntimeProperty variableRuntimeProperty)
{
var jToken = JToken.FromObject(value);
var jToken = value.GetJTokenFromObj();
if (!string.IsNullOrEmpty(variableRuntimeProperty.Attribute.WriteExpressions))
{
object rawdata = jToken.GetObjectFromJToken();
object data = variableRuntimeProperty.Attribute.WriteExpressions.GetExpressionsResult(rawdata, Device?.Logger);
jToken = JToken.FromObject(data);
jToken = data.GetJTokenFromObj();
}
return jToken;

View File

@@ -98,6 +98,19 @@ public static class ChannelOptionsExtensions
if (channelOptions.MaxClientCount > 0)
config.SetMaxCount(channelOptions.MaxClientCount);
config.SetTransportOption(new TouchSocket.Sockets.TransportOption()
{
SendPipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 1024,
useSynchronizationContext: false),
ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 1024,
pauseWriterThreshold: 1024 * 1024,
resumeWriterThreshold: 1024 * 512,
useSynchronizationContext: false)
});
switch (channelType)
{
case ChannelTypeEnum.TcpClient:
@@ -125,7 +138,7 @@ public static class ChannelOptionsExtensions
/// <param name="config">配置</param>
/// <param name="channelOptions">串口配置</param>
/// <returns></returns>
public static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var serialPortOption = channelOptions.Map<SerialPortOption>();
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
@@ -143,7 +156,7 @@ public static class ChannelOptionsExtensions
/// <param name="channelOptions">通道配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static TcpClientChannel GetTcpClient(this TouchSocketConfig config, IChannelOptions channelOptions)
private static TcpClientChannel GetTcpClient(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var remoteUrl = channelOptions.RemoteUrl;
var bindUrl = channelOptions.BindUrl;
@@ -165,7 +178,7 @@ public static class ChannelOptionsExtensions
/// <param name="channelOptions">通道配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static IChannel GetTcpService(this TouchSocketConfig config, IChannelOptions channelOptions)
private static IChannel GetTcpService(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var bindUrl = channelOptions.BindUrl;
bindUrl.ThrowIfNull(nameof(bindUrl));
@@ -193,7 +206,7 @@ public static class ChannelOptionsExtensions
/// <param name="channelOptions">通道配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static UdpSessionChannel GetUdpSession(this TouchSocketConfig config, IChannelOptions channelOptions)
private static UdpSessionChannel GetUdpSession(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var remoteUrl = channelOptions.RemoteUrl;
var bindUrl = channelOptions.BindUrl;

View File

@@ -57,7 +57,7 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
/// </summary>
public ChannelEventHandler Stoping { get; }
void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue);
void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1);
}

View File

@@ -30,6 +30,7 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
WaitHandlePool<MessageBase> WaitHandlePool { get; }
WaitLock GetLock(string key);
void LogSeted(bool logSeted);
/// <summary>
/// 设置数据处理适配器

View File

@@ -36,7 +36,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -68,7 +68,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
@@ -98,7 +98,10 @@ public class OtherChannel : SetupConfigObject, IClientChannel
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <summary>
/// 设置数据处理适配器。
/// </summary>

View File

@@ -31,7 +31,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -71,6 +71,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public ChannelEventHandler Started { get; } = new();
@@ -86,7 +91,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;

View File

@@ -28,7 +28,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -51,6 +51,10 @@ public class TcpClientChannel : TcpClient, IClientChannel
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -79,7 +83,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>

View File

@@ -210,7 +210,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
}
public int MinSign { get; private set; } = 0;
public int MaxSign { get; private set; } = ushort.MaxValue;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
MinSign = minSign;
MaxSign = maxSign;

View File

@@ -43,8 +43,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
logSet = false;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -79,7 +82,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock { get; internal set; } = new(nameof(TcpSessionClientChannel));

View File

@@ -40,6 +40,10 @@ public class UdpSessionChannel : UdpSession, IClientChannel
handleAdapter.Logger = log;
}
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
@@ -50,7 +54,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -89,7 +93,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;

View File

@@ -569,7 +569,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
timeoutStatus = reusableTimeout.TimeoutStatus;
return timeoutStatus
? new MessageBase(new TimeoutException())
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
: new MessageBase(new OperationCanceledException());
}
catch (Exception ex)
@@ -583,9 +583,15 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
_reusableTimeouts.Return(reusableTimeout);
}
return waitData.Status == WaitDataStatus.Success
? waitData.CompletedData
: new MessageBase(waitData.Check(timeoutStatus));
if (waitData.Status == WaitDataStatus.Success)
{
return waitData.CompletedData;
}
else
{
var operResult = waitData.Check(timeoutStatus);
return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
}
}
catch (Exception ex)
{
@@ -978,6 +984,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
lock (Channel)
{
Channel.Starting.Remove(ChannelStarting);
Channel.Stoped.Remove(ChannelStoped);
Channel.Started.Remove(ChannelStarted);
@@ -1018,6 +1025,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}
}
_reusableTimeouts?.SafeDispose();
@@ -1070,6 +1082,12 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}
_reusableTimeouts?.SafeDispose();

View File

@@ -12,6 +12,7 @@ using System.Globalization;
using System.Net;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Foundation.Extension.String;

View File

@@ -11,8 +11,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
<PackageReference Include="TouchSocket" Version="4.0.0-beta.27" />
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.27" />
<PackageReference Include="TouchSocket" Version="4.0.0-beta.57" />
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.57" />
</ItemGroup>
<ItemGroup>

View File

@@ -435,5 +435,311 @@ public static class ThingsGatewayBitConverterExtension
}
}
/// <summary>
/// 根据数据类型获取实际值
/// </summary>
public static bool GetChangedDataFormJToken(
JToken jToken,
DataTypeEnum dataType,
int arrayLength,
object? oldValue,
out object? result)
{
switch (dataType)
{
case DataTypeEnum.Boolean:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Boolean[]>();
if (oldValue is bool[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Boolean>();
if (oldValue is bool oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Byte:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Byte[]>();
if (oldValue is byte[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Byte>();
if (oldValue is byte oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Int16:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Int16[]>();
if (oldValue is short[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Int16>();
if (oldValue is short oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.UInt16:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<UInt16[]>();
if (oldValue is ushort[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<UInt16>();
if (oldValue is ushort oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Int32:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Int32[]>();
if (oldValue is int[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Int32>();
if (oldValue is int oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.UInt32:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<UInt32[]>();
if (oldValue is uint[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<UInt32>();
if (oldValue is uint oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Int64:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Int64[]>();
if (oldValue is long[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Int64>();
if (oldValue is long oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.UInt64:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<UInt64[]>();
if (oldValue is ulong[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<UInt64>();
if (oldValue is ulong oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Float:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Single[]>();
if (oldValue is float[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Single>();
if (oldValue is float oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Double:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Double[]>();
if (oldValue is double[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Double>();
if (oldValue is double oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.Decimal:
if (arrayLength > 1)
{
var newVal = jToken.ToObject<Decimal[]>();
if (oldValue is decimal[] oldArr && newVal.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
else
{
var newVal = jToken.ToObject<Decimal>();
if (oldValue is decimal oldVal && oldVal == newVal)
{
result = oldValue;
return false;
}
result = newVal;
return true;
}
case DataTypeEnum.String:
default:
if (arrayLength > 1)
{
var newArr = new string[arrayLength];
for (int i = 0; i < arrayLength; i++)
{
newArr[i] = jToken.ToObject<string>();
}
if (oldValue is string[] oldArr && newArr.SequenceEqual(oldArr))
{
result = oldValue;
return false;
}
result = newArr;
return true;
}
else
{
var str = jToken.ToObject<string>();
if (oldValue is string oldStr && oldStr == str)
{
result = oldStr;
return false;
}
result = str;
return true;
}
}
}
#endregion
}

View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Gateway.Application;
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public class StringNotMemoryAttribute : ValidationAttribute
{
public StringNotMemoryAttribute()
: base("The field {0} cannot be 'Memory'.")
{
}
public override bool IsValid(object value)
{
var str = value as string;
return !string.Equals(str, "Memory", StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -77,7 +77,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
{
LogMessage?.LogInformation("Refresh variable");
IdVariableRuntimes.Clear();
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().Where(a => a.IsInternalMemoryVariable == false).ToDictionary(a => a.Id));
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());

View File

@@ -105,5 +105,6 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
/// 报警实体脚本
/// </summary>
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public virtual string? BigTextScriptPluginEventDataModel { get; set; }
}

View File

@@ -482,7 +482,6 @@ public abstract partial class CollectBase : DriverBase
protected virtual Task TestOnline(object? state, CancellationToken cancellationToken)
{
CurrentDevice.SetDeviceStatus(TimerX.Now, false, null);
return Task.CompletedTask;
}
@@ -541,7 +540,7 @@ public abstract partial class CollectBase : DriverBase
// 如果成功,每个变量都读取一次最新值,再次比较写入值
var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet();
var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToList();
var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray();
await groups.ParallelForEachAsync(async (varRead, token) =>
{
@@ -595,7 +594,7 @@ public abstract partial class CollectBase : DriverBase
// 根据写入表达式转换数据
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
// 将转换后的数据重新赋值给写入信息列表
writeInfoLists[deviceVariable] = JToken.FromObject(data);
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
}
catch (Exception ex)
{
@@ -611,7 +610,7 @@ public abstract partial class CollectBase : DriverBase
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
var list = writeInfoLists
.Where(a => !results.Any(b => b.Key == a.Key.Name))
.ToDictionary(item => item.Key, item => item.Value).ToArray();
.ToArray();
// 使用并发方式遍历写入信息列表,并进行异步写入操作
await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
{
@@ -663,7 +662,7 @@ public abstract partial class CollectBase : DriverBase
// 根据写入表达式转换数据
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
// 将转换后的数据重新赋值给写入信息列表
writeInfoLists[deviceVariable] = JToken.FromObject(data);
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
}
catch (Exception ex)
{

View File

@@ -200,9 +200,8 @@ public abstract class CollectFoundationBase : CollectBase
// 创建用于存储操作结果的并发字典
ConcurrentDictionary<string, OperResult> operResults = new();
var list = writeInfoLists.ToArray();
// 使用并发方式遍历写入信息列表,并进行异步写入操作
await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
{
try
{

View File

@@ -1,53 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 插件配置项
/// <br></br>
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
/// </summary>
public class MempryDevicePropertyBase : CollectPropertyBase
{
}
/// <summary>
/// <para></para>
/// 采集插件继承实现不同PLC通讯
/// <para></para>
/// </summary>
public class MempryDevice : CollectBase
{
private MempryDevicePropertyBase _driverPropertyBase = new MempryDevicePropertyBase();
public override CollectPropertyBase CollectProperties => _driverPropertyBase;
#if !Management
/// <summary>
/// 是否连接成功
/// </summary>
public override bool IsConnected()
{
return true;
}
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
return Task.FromResult(new List<VariableSourceRead>());
}
protected override bool IsRuntimeSourceValid(VariableRuntime a)
{
return false;
}
#endif
}

View File

@@ -37,6 +37,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
#region
public virtual bool RefreshRuntimeAlways { get; set; } = false;
/// <summary>
/// 调试UI Type如果不存在返回null
/// </summary>

View File

@@ -62,28 +62,40 @@ public static class DynamicModelExtension
}
if (GlobalData.IdDevices.TryGetValue(businessId, out var deviceRuntime))
{
if (deviceRuntime.Driver is BusinessBase businessBase && businessBase.DriverProperties is IBusinessPropertyAllVariableBase property)
if (deviceRuntime.Driver is BusinessBase businessBase)
{
if (property.IsAllVariable)
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
{
// 检查是否存在对应的业务设备Id
if (variableRuntime.VariablePropertys?.ContainsKey(businessId) == true)
{
variableRuntime.VariablePropertys[businessId].TryGetValue(propertyName, out var value);
return value; // 返回属性值
}
else
{
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
}
if (variableRuntime.IsInternalMemoryVariable == false)
return GetVariableProperty(variableRuntime, businessId, propertyName, businessBase);
}
else if (businessBase.RefreshRuntimeAlways)
{
return GetVariableProperty(variableRuntime, businessId, propertyName, businessBase);
}
}
}
return null; // 未找到对应的业务设备Id返回null
static string? GetVariableProperty(VariableRuntime variableRuntime, long businessId, string propertyName, BusinessBase businessBase)
{
// 检查是否存在对应的业务设备Id
if (variableRuntime.VariablePropertys?.TryGetValue(businessId, out var kv) == true)
{
kv.TryGetValue(propertyName, out var value);
return value; // 返回属性值
}
else
{
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
}
}
}
#endif

View File

@@ -43,6 +43,7 @@ namespace ThingsGateway.Gateway.Application
string PluginDirectory { get; }
Dictionary<long, VariableRuntime> IdVariableRuntimes { get; }
IDeviceThreadManage DeviceThreadManage { get; }
bool RefreshRuntimeAlways { get; set; }
bool IsConnected();
void PauseThread(bool pause);

View File

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

View File

@@ -15,6 +15,8 @@ using Riok.Mapperly.Abstractions;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -302,13 +304,11 @@ public class Variable : BaseDataEntity, IValidatableObject
#endregion
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(RegisterAddress) && string.IsNullOrEmpty(OtherMethod))
{
yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(OtherMethod), nameof(RegisterAddress) });
}
}
}

View File

@@ -23,5 +23,5 @@ public enum PluginTypeEnum
/// <summary>
/// 业务
/// </summary>
Business,
Business
}

View File

@@ -29,6 +29,7 @@ public delegate void DelegateOnDeviceChanged(DeviceRuntime deviceRuntime, Device
/// <param name="variableRuntime">变量运行时对象</param>
/// <param name="variableData">变量数据对象</param>
public delegate void VariableChangeEventHandler(VariableRuntime variableRuntime, VariableBasicData variableData);
/// <summary>
/// 变量采集事件委托,用于通知变量进行采集时的事件
/// </summary>
@@ -100,10 +101,11 @@ public static class GlobalData
return ReadOnlyIdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
}
public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
return IdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
}
@@ -117,7 +119,7 @@ public static class GlobalData
public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables()
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
return AlarmEnableIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
}
@@ -125,9 +127,15 @@ public static class GlobalData
{
if (GlobalData.IdDevices.TryGetValue(businessDeviceId, out var deviceRuntime))
{
if (deviceRuntime.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property)
if (deviceRuntime.Driver is BusinessBase businessBase)
{
if (property.IsAllVariable)
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
{
if (a.IsInternalMemoryVariable == false)
return true;
}
else if (businessBase.RefreshRuntimeAlways)
{
return true;
}
@@ -141,7 +149,46 @@ public static class GlobalData
return a.VariablePropertys?.ContainsKey(businessDeviceId) == true;
}
public static DeviceRuntime[] GetAllVariableBusinessDeviceRuntime()
{
var channelDevice = GlobalData.IdDevices.Where(a =>
{
if (a.Value.Driver is BusinessBase businessBase)
{
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
{
return true;
}
else if (businessBase.RefreshRuntimeAlways)
{
return true;
}
}
return false;
}).Select(a => a.Value).ToArray();
return channelDevice;
}
public static IDriver[] GetAllVariableBusinessDriver()
{
var channelDevice = GlobalData.IdDevices.Where(a =>
{
if (a.Value.Driver is BusinessBase businessBase)
{
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
{
return true;
}
else if (businessBase.RefreshRuntimeAlways)
{
return true;
}
}
return false;
}).Select(a => a.Value.Driver).ToArray();
return channelDevice;
}
/// <summary>
/// 只读的通道字典,提供对通道的只读访问
/// </summary>
@@ -162,6 +209,32 @@ public static class GlobalData
return null;
}
public static VariableRuntime GetVariable(string variableName)
{
if (string.IsNullOrEmpty(variableName))
return null;
var names = variableName.Split('.');
if (names.Length > 2)
return null;
if (names.Length == 1)
{
if (MemoryVariables.TryGetValue(names[0], out var variable))
{
return variable;
}
}
else if (Devices.TryGetValue(names[0], out var device))
{
if (device.VariableRuntimes.TryGetValue(names[1], out var variable))
{
return variable;
}
}
return null;
}
public static IEnumerable<DeviceRuntime> GetEnableDevices()
{
var idSet = GetRedundantDeviceIds();
@@ -189,7 +262,7 @@ public static class GlobalData
public static IEnumerable<VariableRuntime> GetEnableVariables()
{
return IdDevices.SelectMany(a => a.Value.VariableRuntimes).Where(a => a.Value?.Enable == true).Select(a => a.Value);
return IdVariables.Where(a => a.Value.DeviceRuntime?.Enable != false && a.Value.DeviceRuntime?.ChannelRuntime?.Enable != false && a.Value?.Enable == true).Select(a => a.Value);
}
public static bool TryGetDeviceThreadManage(DeviceRuntime deviceRuntime, out IDeviceThreadManage deviceThreadManage)
@@ -483,6 +556,9 @@ public static class GlobalData
/// </summary>
internal static ConcurrentDictionary<long, VariableRuntime> IdVariables { get; } = new();
internal static ConcurrentDictionary<string, VariableRuntime> MemoryVariables { get; } = new();
public static IReadOnlyDictionary<string, VariableRuntime> ReadOnlyMemoryVariables => MemoryVariables;
/// <summary>
/// 实时报警列表
/// </summary>

View File

@@ -131,8 +131,10 @@ public class VariableBasicData
//[System.Text.Json.Serialization.JsonIgnore]
//[Newtonsoft.Json.JsonIgnore]
//public DeviceBasicData DeviceRuntime { get; set; }
/// <inheritdoc cref="Variable.DeviceId"/>
public long DeviceId { get; set; }
/// <inheritdoc cref="VariableRuntime.LastErrorMessage"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
@@ -142,6 +144,11 @@ public class VariableBasicData
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? RegisterAddress { get; set; }
/// <inheritdoc cref="Variable.OtherMethod"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string? OtherMethod { get; set; }
/// <inheritdoc cref="Variable.Unit"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
@@ -190,5 +197,6 @@ public class VariableBasicData
/// <inheritdoc cref="VariableRuntime.ValueInited"/>
public bool ValueInited { get; set; }
/// <inheritdoc cref="VariableRuntime.IsMemoryVariable"/>
public bool IsMemoryVariable { get; set; }
}

View File

@@ -0,0 +1,16 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
public interface IMemoryVariableRpc
{
OperResult MemoryVariableRpc(string value, CancellationToken cancellationToken = default);
}

View File

@@ -14,6 +14,7 @@ using Newtonsoft.Json.Linq;
using Riok.Mapperly.Abstractions;
#if !Management
using ThingsGateway.Gateway.Application.Extensions;
#endif
@@ -32,6 +33,13 @@ public partial class VariableRuntime : Variable
IDisposable
#endif
{
[AutoGenerateColumn(Ignore = true)]
public virtual bool IsMemoryVariable { get => _isMemoryVariable; set => _isMemoryVariable = value; }
[AutoGenerateColumn(Ignore = true)]
public virtual bool IsInternalMemoryVariable { get => _isInternalMemoryVariable; set => _isInternalMemoryVariable = value; }
[AutoGenerateColumn(Ignore = true)]
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
@@ -74,15 +82,22 @@ public partial class VariableRuntime : Variable
public object RawValue { get => rawValue; set => rawValue = value; }
#if !Management
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public TouchSocket.Core.ILog? LogMessage => DeviceRuntime?.Driver?.LogMessage;
/// <summary>
/// 所在采集设备
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; set => deviceRuntime = value; }
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; private set => deviceRuntime = value; }
/// <summary>
/// VariableSource
@@ -239,6 +254,8 @@ public partial class VariableRuntime : Variable
#pragma warning restore CS0169
#pragma warning restore CS0414
private bool _valueInited;
private bool _isMemoryVariable;
private bool _isInternalMemoryVariable;
private object _value;
private object lastSetValue;
@@ -272,7 +289,7 @@ public partial class VariableRuntime : Variable
{
try
{
var data = ReadExpressions.GetExpressionsResult(RawValue, DeviceRuntime?.Driver?.LogMessage);
var data = ReadExpressions.GetExpressionsResult(RawValue, LogMessage);
Set(data, dateTime);
}
catch (Exception ex)
@@ -291,7 +308,7 @@ public partial class VariableRuntime : Variable
}
if (oldMessage != _lastErrorMessage)
{
DeviceRuntime?.Driver?.LogMessage?.LogWarning(_lastErrorMessage);
LogMessage?.LogWarning(_lastErrorMessage);
}
return new($"{Name} Conversion expression failed", ex);
}
@@ -394,6 +411,12 @@ public partial class VariableRuntime : Variable
DeviceRuntime = deviceRuntime;
if (deviceRuntime == null)
{
GlobalData.MemoryVariables.Remove(Name);
GlobalData.MemoryVariables.TryAdd(Name, this);
}
DeviceRuntime?.VariableRuntimes?.TryAdd(Name, this);
GlobalData.IdVariables.Remove(Id);
GlobalData.IdVariables.TryAdd(Id, this);
@@ -407,7 +430,10 @@ public partial class VariableRuntime : Variable
public void Dispose()
{
DeviceRuntime?.VariableRuntimes?.Remove(Name);
if (DeviceRuntime == null)
{
GlobalData.MemoryVariables.Remove(Name);
}
GlobalData.IdVariables.Remove(Id);
GlobalData.AlarmEnableIdVariables.Remove(Id);
@@ -422,7 +448,7 @@ public partial class VariableRuntime : Variable
}
/// <inheritdoc/>
public async ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default)
public virtual async ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default)
{
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>()
{

View File

@@ -10,7 +10,6 @@
using Microsoft.Extensions.Logging;
using ThingsGateway.Common.Extension;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife.Extension;
@@ -110,7 +109,7 @@ internal sealed class AlarmTask : IDisposable
if (tag.AlarmPropertys.CustomAlarmEnable) // 检查是否启用了自定义报警功能
{
// 调用变量的CustomAlarmCode属性的GetExpressionsResult方法传入变量的值获取报警表达式的计算结果
var result = tag.AlarmPropertys.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
var result = tag.AlarmPropertys.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.LogMessage);
if (result is bool boolResult) // 检查计算结果是否为布尔类型
{
@@ -227,7 +226,7 @@ internal sealed class AlarmTask : IDisposable
if (!string.IsNullOrEmpty(ex))
{
// 如果存在报警约束表达式,则计算表达式结果,以确定是否触发报警事件
var data = ex.GetExpressionsResult(item.Value, item.DeviceRuntime?.Driver?.LogMessage);
var data = ex.GetExpressionsResult(item.Value, item.LogMessage);
if (data is bool result)
{
if (result)
@@ -487,6 +486,11 @@ internal sealed class AlarmTask : IDisposable
#endregion
ParallelOptions ParallelOptions = new()
{
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2)
};
/// <summary>
/// 执行工作任务,对设备变量进行报警分析。
/// </summary>
@@ -499,32 +503,36 @@ internal sealed class AlarmTask : IDisposable
if (!GlobalData.StartBusinessChannelEnable)
return;
//Stopwatch stopwatch = Stopwatch.StartNew();
// 遍历设备变量列表
//System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
if (scheduledTask.Period < 100 && scheduledTask.Period > 1 && GlobalData.AlarmEnableIdVariables.Count > 50000)
{
scheduledTask.Change(100, 100);
}
// 遍历设备变量列表
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
{
var list = GlobalData.AlarmEnableIdVariables.Select(a => a.Value).ToArray();
list.ParallelForEach((item, state, index) =>
// 使用 Parallel.ForEach 执行指定的操作
Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, (item, state, index) =>
{
{
// 如果取消请求已经被触发,则结束任务
if (cancellation.IsCancellationRequested)
return;
// 如果取消请求已经被触发,则结束任务
if (cancellation.IsCancellationRequested)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.AlarmEnable)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.Value.AlarmEnable)
return;
// 如果该变量离线,则跳过该变量
if (!item.IsOnline)
return;
// 如果该变量离线,则跳过该变量
if (!item.Value.IsOnline)
return;
// 对该变量进行报警分析
AlarmAnalysis(item);
}
// 对该变量进行报警分析
AlarmAnalysis(item.Value);
});
}
else
{
//if (scheduledTask.Period != 5000)

View File

@@ -573,8 +573,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
{
//传入变量
//newDeviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.SafeDispose());
var list = deviceRuntime.VariableRuntimes.Select(a => a.Value).ToArray();
list.ParallelForEach(a => a.Init(newDeviceRuntime));
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
GlobalData.VariableRuntimeDispatchService.Dispatch(null);
}

View File

@@ -54,7 +54,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
{
item.Init(channelRuntime);
var varRuntimes = variableRuntimes.Where(x => x.DeviceId == item.Id).ToArray();
var varRuntimes = variableRuntimes.Where(x => x.DeviceId == item.Id);
varRuntimes.ParallelForEach(varItem => varItem.Init(item));
}

View File

@@ -92,7 +92,7 @@ public partial class ManagementTask : AsyncDisposableObject
var tcpDmtpClient = new TcpDmtpClient();
var config = new TouchSocketConfig()
.SetRemoteIPHost(_managementOptions.ServerUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
.SetAdapterOption(a => a.MaxPackageSize = 1024 * 1024 * 1024)
.SetDmtpOption(a => a.VerifyToken = _managementOptions.VerifyToken)
.ConfigureContainer(a =>
{
@@ -156,7 +156,7 @@ public partial class ManagementTask : AsyncDisposableObject
var tcpDmtpService = new TcpDmtpService();
var config = new TouchSocketConfig()
.SetListenIPHosts(_managementOptions.ServerUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
.SetAdapterOption(a => a.MaxPackageSize = 1024 * 1024 * 1024)
.SetDmtpOption(a => a.VerifyToken = _managementOptions.VerifyToken)
.ConfigureContainer(a =>
{

View File

@@ -331,7 +331,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
var tcpDmtpClient = new TcpDmtpClient();
var config = new TouchSocketConfig()
.SetRemoteIPHost(redundancy.MasterUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
.SetAdapterOption(a => a.MaxPackageSize = 0x20000000)
.SetDmtpOption(a => a.VerifyToken = redundancy.VerifyToken)
.ConfigureContainer(a =>
{
@@ -376,7 +376,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
var tcpDmtpService = new TcpDmtpService();
var config = new TouchSocketConfig()
.SetListenIPHosts(redundancy.MasterUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
.SetAdapterOption(a => a.MaxPackageSize = 0x20000000)
.SetDmtpOption(a => a.VerifyToken = redundancy.VerifyToken)
.ConfigureContainer(a =>
{
@@ -479,7 +479,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
{
int maxBatchSize = GetBatchSize() / 10;
var groups = GlobalData.IdVariables.Select(a => a.Value).GroupBy(a => a.DeviceRuntime);
var groups = GlobalData.IdVariables.Select(a => a.Value).GroupBy(a => a.DeviceRuntime).Where(a => a.Key != null);
var channelBatch = new HashSet<Channel>();
var deviceBatch = new HashSet<Device>();

View File

@@ -30,6 +30,10 @@ internal sealed class RpcService : IRpcService
{
private readonly ConcurrentQueue<RpcLog> _logQueues = new();
private readonly RpcLogOptions? _rpcLogOptions;
private SqlSugarClient _db = DbContext.GetDB<RpcLog>(); // 创建一个新的数据库上下文实例
private IStringLocalizer Localizer { get; }
/// <inheritdoc cref="RpcService"/>
public RpcService(IStringLocalizer<RpcService> localizer)
{
@@ -38,50 +42,118 @@ internal sealed class RpcService : IRpcService
_rpcLogOptions = App.GetOptions<RpcLogOptions>();
}
private IStringLocalizer Localizer { get; }
/// <inheritdoc />
public async Task<Dictionary<string, Dictionary<string, IOperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default)
public async Task<Dictionary<string, Dictionary<string, IOperResult>>> InvokeDeviceMethodAsync(
string sourceDes,
Dictionary<string, Dictionary<string, string>> deviceDatas,
CancellationToken cancellationToken = default)
{
// 初始化用于存储将要写入的变量和方法的字典
Dictionary<IRpcDriver, Dictionary<VariableRuntime, JToken>> writeVariables = new();
Dictionary<IRpcDriver, Dictionary<VariableRuntime, JToken>> writeMethods = new();
Dictionary<VariableRuntime, string> memoryVariables = new();
// 用于存储结果的并发字典
ConcurrentDictionary<string, Dictionary<string, IOperResult>> results = new();
deviceDatas.ForEach(a => results.TryAdd(a.Key, new()));
// 对每个要操作的变量进行检查和处理(内存变量)
foreach (var item in deviceDatas.Where(a => a.Key.IsNullOrEmpty() || a.Key.Equals("Memory", StringComparison.OrdinalIgnoreCase)).SelectMany(a => a.Value))
{
// 查找变量是否存在
if (!(GlobalData.MemoryVariables.TryGetValue(item.Key, out var tag) &&
tag is IMemoryVariableRpc memoryVariableRuntime))
{
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
continue;
}
// 检查变量的保护类型和远程写入权限
if (tag.ProtectType == ProtectTypeEnum.ReadOnly)
{
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
continue;
}
if (!tag.RpcWriteEnable)
{
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
continue;
}
try
{
var start = DateTime.Now;
var variableResult = memoryVariableRuntime.MemoryVariableRpc(item.Value, cancellationToken);
var end = DateTime.Now;
string operObj = tag.Name;
string parJson = deviceDatas["Memory"][tag.Name];
if (!variableResult.IsSuccess || _rpcLogOptions.SuccessLog)
{
_logQueues.Enqueue(
new RpcLog()
{
LogTime = start,
ExecutionTime = (int)(end - start).TotalMilliseconds,
OperateMessage = variableResult.IsSuccess ? null : variableResult.ToString(),
IsSuccess = variableResult.IsSuccess,
OperateMethod = AppResource.WriteVariable,
OperateDevice = string.Empty,
OperateObject = operObj,
OperateSource = sourceDes,
ParamJson = parJson,
ResultJson = null
});
}
// 不返回详细错误
if (!variableResult.IsSuccess)
{
var result1 = variableResult;
result1.Exception = null;
variableResult = result1;
}
results["Memory"].Add(tag.Name, variableResult);
}
catch (Exception ex)
{
// 将异常信息添加到结果字典中
results["Memory"].Add(tag.Name, new OperResult(ex));
}
}
var deviceDict = GlobalData.Devices;
// 对每个要操作的变量进行检查和处理
foreach (var deviceData in deviceDatas)
// 对每个要操作的变量进行检查和处理(设备变量)
foreach (var deviceData in deviceDatas.Where(a => (!a.Key.IsNullOrEmpty() && !a.Key.Equals("Memory", StringComparison.OrdinalIgnoreCase))))
{
// 查找设备是否存在
if (!deviceDict.TryGetValue(deviceData.Key, out var device))
{
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
deviceData.Value.ForEach(a =>
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DeviceNotNull", deviceData.Key]))
);
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DeviceNotNull", deviceData.Key])));
continue;
}
// 查找变量对应的设备
var collect = device.Driver as IRpcDriver;
collect ??= device.RpcDriver;
var collect = device.Driver as IRpcDriver ?? device.RpcDriver;
if (collect == null)
{
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
deviceData.Value.ForEach(a =>
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DriverNotNull", deviceData.Key]))
);
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DriverNotNull", deviceData.Key])));
continue;
}
// 检查设备状态,如果设备处于暂停状态,则添加相应的错误信息到结果中并继续下一个变量的处理
// 检查设备状态
if (device.DeviceStatus == DeviceStatusEnum.Pause)
{
deviceData.Value.ForEach(a =>
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DevicePause", deviceData.Key]))
);
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DevicePause", deviceData.Key])));
continue;
}
@@ -90,7 +162,6 @@ internal sealed class RpcService : IRpcService
// 查找变量是否存在
if (!device.VariableRuntimes.TryGetValue(item.Key, out var tag))
{
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
continue;
}
@@ -101,6 +172,7 @@ internal sealed class RpcService : IRpcService
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]));
@@ -110,6 +182,7 @@ internal sealed class RpcService : IRpcService
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);
@@ -121,26 +194,26 @@ internal sealed class RpcService : IRpcService
}
}
}
var writeVariableArrays = writeVariables.ToArray();
// 使用并行方式写入变量
var writeVariableArrays = writeVariables;
await writeVariableArrays.ParallelForEachAsync(async (driverData, cancellationToken) =>
{
try
{
var start = DateTime.Now;
// 调用设备的写入方法
var result = await driverData.Key.InVokeWriteAsync(driverData.Value, cancellationToken).ConfigureAwait(false);
var end = DateTime.Now;
// 写入日志
foreach (var resultItem in result)
{
foreach (var variableResult in resultItem.Value)
{
string operObj = variableResult.Key;
string parJson = deviceDatas[resultItem.Key][variableResult.Key];
if (!variableResult.Value.IsSuccess || _rpcLogOptions.SuccessLog)
{
_logQueues.Enqueue(
new RpcLog()
{
@@ -154,10 +227,9 @@ internal sealed class RpcService : IRpcService
OperateSource = sourceDes,
ParamJson = parJson,
ResultJson = null
}
);
});
}
// 不返回详细错误
if (!variableResult.Value.IsSuccess)
{
var result1 = variableResult.Value;
@@ -166,44 +238,42 @@ internal sealed class RpcService : IRpcService
}
}
// 将结果添加到结果字典中
results[resultItem.Key].AddRange(resultItem.Value);
}
}
catch (Exception ex)
{
// 将异常信息添加到结果字典中
foreach (var item in driverData.Value)
{
results[item.Key.DeviceName].Add(item.Key.Name, new OperResult(ex));
}
}
}, cancellationToken).ConfigureAwait(false);
var writeMethodArrays = writeMethods.ToArray();
// 使用并行方式执行方法
var writeMethodArrays = writeMethods;
await writeMethodArrays.ParallelForEachAsync(async (driverData, cancellationToken) =>
{
try
{
var start = DateTime.Now;
// 调用设备的写入方法
var result = await driverData.Key.InvokeMethodAsync(driverData.Value, cancellationToken).ConfigureAwait(false);
Dictionary<string, string> operateMethods = driverData.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!);
var end = DateTime.Now;
// 写入日志
foreach (var resultItem in result)
{
foreach (var variableResult in resultItem.Value)
{
string operObj = variableResult.Key;
string parJson = deviceDatas[resultItem.Key][variableResult.Key];
// 写入日志
if (!variableResult.Value.IsSuccess || _rpcLogOptions.SuccessLog)
{
_logQueues.Enqueue(
new RpcLog()
{
@@ -216,11 +286,12 @@ internal sealed class RpcService : IRpcService
OperateObject = operObj,
OperateSource = sourceDes,
ParamJson = parJson?.ToString(),
ResultJson = variableResult.Value is IOperResult<object> operResult ? operResult.Content?.ToSystemTextJsonString() : string.Empty
}
);
ResultJson = variableResult.Value is IOperResult<object> operResult
? operResult.Content?.ToSystemTextJsonString()
: string.Empty
});
}
// 不返回详细错误
if (!variableResult.Value.IsSuccess)
{
var result1 = variableResult.Value;
@@ -234,7 +305,6 @@ internal sealed class RpcService : IRpcService
}
catch (Exception ex)
{
// 将异常信息添加到结果字典中
foreach (var item in driverData.Value)
{
results[item.Key.DeviceName].Add(item.Key.Name, new OperResult(ex));
@@ -246,8 +316,6 @@ internal sealed class RpcService : IRpcService
return new(results);
}
private SqlSugarClient _db = DbContext.GetDB<RpcLog>(); // 创建一个新的数据库上下文实例
/// <summary>
/// 异步执行RPC日志插入操作的方法。
/// </summary>
@@ -258,11 +326,12 @@ internal sealed class RpcService : IRpcService
{
try
{
var data = _logQueues.ToListWithDequeue(); // 从日志队列中获取数据
var data = _logQueues.ToListWithDequeue();
if (data.Count > 0)
{
// 将数据插入到数据库中
await _db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);
await _db.InsertableWithAttr(data)
.ExecuteCommandAsync(appLifetime.ApplicationStopping)
.ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -271,7 +340,7 @@ internal sealed class RpcService : IRpcService
}
finally
{
await Task.Delay(3000).ConfigureAwait(false); // 在finally块中等待一段时间后继续下一次循环
await Task.Delay(3000).ConfigureAwait(false);
}
}
}

View File

@@ -18,6 +18,7 @@ using ThingsGateway.Blazor.Diagrams.Core;
using ThingsGateway.Blazor.Diagrams.Core.Anchors;
using ThingsGateway.Blazor.Diagrams.Core.Geometry;
using ThingsGateway.Blazor.Diagrams.Core.Models;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Application;
@@ -88,7 +89,7 @@ public static class RuleHelpers
var propertyInfos = nodeModel.GetType().GetRuntimeProperties().Where(a => a.GetCustomAttribute<ModelValue>() != null);
foreach (var item in propertyInfos)
{
jtokens.Add(item.Name, JToken.FromObject(item.GetValue(nodeModel) ?? JValue.CreateNull()));
jtokens.Add(item.Name, (item.GetValue(nodeModel) ?? JValue.CreateNull()).GetJTokenFromObj());
}
return jtokens;
}

View File

@@ -1,5 +1,7 @@
using Newtonsoft.Json.Linq;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Application;
public class NodeInput
@@ -9,7 +11,7 @@ public class NodeInput
{
get
{
return JToken.FromObject(input);
return (input).GetJTokenFromObj();
}
}

View File

@@ -1,5 +1,7 @@
using Newtonsoft.Json.Linq;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Application;
public class NodeOutput
@@ -9,7 +11,7 @@ public class NodeOutput
{
get
{
return JToken.FromObject(output);
return (output).GetJTokenFromObj();
}
}

View File

@@ -35,7 +35,7 @@ internal static class RuntimeServiceHelper
{
newDeviceRuntime.Init(newChannelRuntime);
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptListVariableRuntime();
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
}
@@ -79,7 +79,7 @@ internal static class RuntimeServiceHelper
{
newDeviceRuntime.Init(newChannelRuntime);
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptListVariableRuntime();
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
}
@@ -113,8 +113,7 @@ internal static class RuntimeServiceHelper
}
if (deviceRuntime != null)
{
var list = deviceRuntime.VariableRuntimes.Select(a => a.Value).ToArray();
list.ParallelForEach(a => a.Init(newDeviceRuntime));
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
}
}
@@ -141,7 +140,7 @@ internal static class RuntimeServiceHelper
{
var channels = oldChannelRuntimes.ToArray();
var devs = channels.SelectMany(a => a.DeviceRuntimes).Select(a => a.Value).ToArray();
devs.SelectMany(a => a.VariableRuntimes).Select(a => a.Value).ToArray().ParallelForEach(a => a.Dispose());
devs.SelectMany(a => a.VariableRuntimes).Select(a => a.Value).ParallelForEach(a => a.Dispose());
devs.ParallelForEach(a => a.Dispose());
channels.ParallelForEach(a => a.Dispose());
@@ -168,7 +167,7 @@ internal static class RuntimeServiceHelper
foreach (var deviceRuntime in deviceRuntimes)
{
//也需要删除变量
var vars = deviceRuntime.VariableRuntimes.Select(a => a.Value).ToArray();
var vars = deviceRuntime.VariableRuntimes.Select(a => a.Value);
vars.ParallelForEach(v =>
{
//需要重启业务线程
@@ -205,7 +204,7 @@ internal static class RuntimeServiceHelper
//也需要删除设备和变量
devs.ParallelForEach((a =>
{
var vars = a.VariableRuntimes.Select(b => b.Value).ToArray();
var vars = a.VariableRuntimes.Select(b => b.Value);
ParallelExtensions.ParallelForEach(vars, (v =>
{
//需要重启业务线程
@@ -240,6 +239,11 @@ internal static class RuntimeServiceHelper
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.Value, false).ConfigureAwait(false);
}
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
}
}
public static async Task RemoveDeviceAsync(HashSet<long> newDeciceIds)
{
@@ -255,11 +259,18 @@ internal static class RuntimeServiceHelper
if (group.Key != null)
await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
}
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
}
}
public static async Task ChangedDriverAsync(ILogger logger)
{
var channelDevice = GlobalData.IdDevices.Where(a => a.Value.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable).Select(a => a.Value).ToArray();
var channelDevice = GlobalData.GetAllVariableBusinessDeviceRuntime();
await channelDevice.ParallelForEachAsync(async (item, token) =>
{
@@ -275,7 +286,7 @@ internal static class RuntimeServiceHelper
}
public static async Task ChangedDriverAsync(ConcurrentHashSet<IDriver> changedDriver, ILogger logger)
{
var drivers = GlobalData.IdDevices.Where(a => a.Value.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable).Select(a => a.Value.Driver);
var drivers = GlobalData.GetAllVariableBusinessDriver();
var changedDrivers = drivers.Concat(changedDriver).Where(a => a.DisposedValue == false).Distinct().ToArray();
await changedDrivers.ParallelForEachAsync(async (driver, token) =>
@@ -293,7 +304,7 @@ internal static class RuntimeServiceHelper
public static void AddBusinessChangedDriver(HashSet<long> variableIds, ConcurrentHashSet<IDriver> changedDriver)
{
var data = GlobalData.IdVariables.FilterByKeys(variableIds).GroupBy(a => a.Value.DeviceRuntime);
var data = GlobalData.IdVariables.FilterByKeys(variableIds).GroupBy(a => a.Value.DeviceRuntime).Where(a => a.Key != null);
foreach (var group in data)
{

View File

@@ -11,9 +11,9 @@
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.27" />
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.27" />-->
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.27" />
<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.57" />
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.57" />-->
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.57" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />-->

View File

@@ -167,7 +167,9 @@
"Date": "Date"
},
"ThingsGateway.Gateway.Razor.ChannelDeviceTree": {
"ShowType": "ShowType"
"ChannelTable": "ChannelTable",
"DeviceTable": "DeviceTable",
"LogInfo": "LogInfo"
},
"ThingsGateway.Gateway.Razor.GatewayAbout": {
"AuthName": "AuthName",

View File

@@ -167,7 +167,9 @@
"Date": "日期"
},
"ThingsGateway.Gateway.Razor.ChannelDeviceTree": {
"ShowType": "显示类型"
"ChannelTable": "通道表格",
"DeviceTable": "设备表格",
"LogInfo": "日志信息"
},
"ThingsGateway.Gateway.Razor.GatewayAbout": {
"AuthName": "公司名称",

View File

@@ -13,7 +13,7 @@ namespace ThingsGateway.Gateway.Razor;
public partial class ChannelRuntimeInfo
{
#if !Management
private string Height { get; set; } = "calc(100% - 270px)";
private string Height { get; set; } = "calc(100% - 300px)";
#else
private string Height { get; set; } = "calc(100% - 330px)";

View File

@@ -6,12 +6,11 @@
<div class="listtree-view">
<div class="d-flex align-items-center">
<RadioList class="m-2" IsButton="true" AutoSelectFirstWhenValueIsNull="false" TValue="ShowTypeEnum?" ValueExpression=@(() => ShowType) Value="ShowType" ValueChanged="OnShowTypeChanged" ShowLabel="false" />
<span style="color:var(--bs-body-color);opacity: 0.5;" class="text-h6 py-1">@GatewayLocalizer["DeviceList"]</span>
<SpinnerComponent @ref=Spinner></SpinnerComponent>
<SpinnerComponent Size="Size.Small" @ref=Spinner></SpinnerComponent>
</div>
<span style="color:var(--bs-body-color);opacity: 0.5;" class="text-h6 mb-2">@GatewayLocalizer["DeviceList"]</span>
<ContextMenuZone>
<TreeView TItem="ChannelDeviceTreeItem" Items="Items" ShowIcon="false" ShowSearch IsAccordion=false IsVirtualize="true" OnTreeItemClick="OnTreeItemClick" OnSearchAsync="OnClickSearch" ModelEqualityComparer=ModelEqualityComparer ShowToolbar="true" >
@@ -37,7 +36,7 @@
@if (context.ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Channel)
{
@* <Tooltip Title=@(GatewayLocalizer["AddDevice"]) Placement="Placement.Bottom"> *@
<Button Size=Size.ExtraSmall Icon="fa-solid fa-plus" Color=Color.None OnClickWithoutRender=@(() => EditDevice(GatewayLocalizer["AddDevice"], context, ItemChangedType.Add))>
<Button Size=Size.ExtraSmall Icon="fa-solid fa-circle-info" Color=Color.None OnClickWithoutRender=@(() => ShowLogInfo(context))>
</Button>
@* </Tooltip> *@
@@ -58,6 +57,10 @@
@* //更新设备 *@
@if (context.ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Device)
{
<Button Size=Size.ExtraSmall Icon="fa-solid fa-circle-info" Color=Color.None OnClickWithoutRender=@(() => ShowLogInfo(context))>
</Button>
@* <Tooltip Title=@(GatewayLocalizer["UpdateDevice"]) Placement="Placement.Bottom"> *@
<Button Size=Size.ExtraSmall Icon="fa-regular fa-pen-to-square" Color=Color.None OnClickWithoutRender=@(() => EditDevice(GatewayLocalizer["UpdateDevice"], context, ItemChangedType.Update))>
</Button>
@@ -77,6 +80,14 @@
<ContextMenu style="max-height:800px" class="tgTree" OnBeforeShowCallback="OnBeforeShowCallback">
@if (SelectModel != null)
{
<ContextMenuItem Icon="fa-solid fa-table" Text="@Localizer["ChannelTable"]" OnClick=@((a, b) => ShowChannelRuntimeTable(b as ChannelDeviceTreeItem))></ContextMenuItem>
<ContextMenuItem Icon="fa-solid fa-table" Text="@Localizer["DeviceTable"]" OnClick=@((a, b) => ShowDeviceRuntimeTable(b as ChannelDeviceTreeItem))></ContextMenuItem>
<ContextMenuItem Icon="fa-solid fa-circle-info" Disabled="SelectModel.ChannelDevicePluginType < ChannelDevicePluginTypeEnum.Channel" Text ="@Localizer["LogInfo"]" OnClick=@((a, b) => ShowLogInfo(b as ChannelDeviceTreeItem))></ContextMenuItem>
<ContextMenuDivider></ContextMenuDivider>
<ContextMenuItem Icon="fa-solid fa-plus" Disabled="(SelectModel.ChannelDevicePluginType > ChannelDevicePluginTypeEnum.Channel || !AuthorizeButton(AdminOperConst.Add))" Text="@GatewayLocalizer["AddChannel"]" OnClick=@((a, b) => EditChannel(a, b, ItemChangedType.Add))></ContextMenuItem>
<ContextMenuItem Icon="fa-regular fa-pen-to-square" Disabled="(SelectModel.ChannelDevicePluginType >= ChannelDevicePluginTypeEnum.Channel || !AuthorizeButton(AdminOperConst.Edit))" Text="@GatewayLocalizer["BatchEditChannel"]" OnClick=@((a, b) => BatchEditChannel(a, b))></ContextMenuItem>

View File

@@ -10,13 +10,11 @@
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using ThingsGateway.Admin.Application;
using ThingsGateway.Admin.Razor;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Razor.Extension;
using ThingsGateway.SqlSugar;
namespace ThingsGateway.Gateway.Razor;
@@ -39,32 +37,258 @@ public partial class ChannelDeviceTree : IDisposable
return AppContext.IsHasButtonWithRole(RouteName, operate);
}
[Parameter]
public EventCallback<ShowTypeEnum?> ShowTypeChanged { get; set; }
[Parameter]
public ShowTypeEnum? ShowType { get; set; }
[Inject]
IJSRuntime JSRuntime { get; set; }
private async Task OnShowTypeChanged(ShowTypeEnum? showType)
#if !Management
private async Task ShowChannelRuntimeTable(ChannelDeviceTreeItem channelDeviceTreeItem)
{
ShowType = showType;
if (showType != null)
await JSRuntime.SetLocalStorage("showType", ShowType);
if (ShowTypeChanged.HasDelegate)
await ShowTypeChanged.InvokeAsync(showType);
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{
var ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
await ShowChannelTable(ChannelRuntimes);
}
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{
var ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
await ShowChannelTable(ChannelRuntimes);
}
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
var ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
await ShowChannelTable(ChannelRuntimes);
}
else
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
{
if (pluginTypeEnum != null)
{
var ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
await ShowChannelTable(ChannelRuntimes);
}
else
{
var ChannelRuntimes = channels;
await ShowChannelTable(ChannelRuntimes);
}
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
private async Task ShowDeviceRuntimeTable(ChannelDeviceTreeItem channelDeviceTreeItem)
{
if (firstRender)
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{
var showType = await JSRuntime!.GetLocalStorage<ShowTypeEnum>("showType");
await OnShowTypeChanged(showType);
StateHasChanged();
var DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
await ShowDeviceTable(DeviceRuntimes);
}
await base.OnAfterRenderAsync(firstRender);
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{
var DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
await ShowDeviceTable(DeviceRuntimes);
}
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
{
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
var DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
await ShowDeviceTable(DeviceRuntimes);
}
else
{
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
{
if (pluginTypeEnum != null)
{
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
var DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
await ShowDeviceTable(DeviceRuntimes);
}
else
{
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
var DeviceRuntimes = devices;
await ShowDeviceTable(DeviceRuntimes);
}
}
}
}
private async Task ShowLogInfo(ChannelDeviceTreeItem channelDeviceTreeItem)
{
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{
await ShowLogInfo(channelRuntime);
}
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{
await ShowLogInfo(deviceRuntime);
}
}
private async Task ShowLogInfo(ChannelRuntime channel)
{
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(ChannelRuntimeInfo), new Dictionary<string, object?>()
{
{nameof(ChannelRuntimeInfo.ChannelRuntime),channel},
}).Render();
if (renderFragment != null)
{
var option = new WinBoxOption()
{
Title = Localizer["LogInfo"],
ContentTemplate = renderFragment,
Max = false,
Width = "60%",
Height = "80%",
Top = "0%",
Left = "10%",
Background = "var(--bb-primary-color)",
Overflow = true
};
await WinBoxService.Show(option);
}
}
private async Task ShowLogInfo(DeviceRuntime device)
{
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(DeviceRuntimeInfo), new Dictionary<string, object?>()
{
{nameof(DeviceRuntimeInfo.DeviceRuntime),device},
}).Render();
if (renderFragment != null)
{
var option = new WinBoxOption()
{
Title = Localizer["LogInfo"],
ContentTemplate = renderFragment,
Max = false,
Width = "60%",
Height = "80%",
Top = "0%",
Left = "10%",
Background = "var(--bb-primary-color)",
Overflow = true
};
await WinBoxService.Show(option);
}
}
private async Task ShowChannelTable(IEnumerable<ChannelRuntime> ChannelRuntimes)
{
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(ChannelTable), new Dictionary<string, object?>()
{
{nameof(ChannelTable.SelectModel),SelectModel},
{nameof(ChannelTable.Items),ChannelRuntimes},
{nameof(ChannelTable.AutoRestartThread),AutoRestartThread},
}).Render();
if (renderFragment != null)
{
var option = new WinBoxOption()
{
Title = Localizer["ChannelTable"],
ContentTemplate = renderFragment,
Max = false,
Width = "60%",
Height = "60%",
Top = "0%",
Left = "10%",
Background = "var(--bb-primary-color)",
Overflow = true
};
await WinBoxService.Show(option);
}
}
private async Task ShowDeviceTable(IEnumerable<DeviceRuntime> DeviceRuntimes)
{
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(DeviceTable), new Dictionary<string, object?>()
{
{nameof(DeviceTable.SelectModel),SelectModel},
{nameof(DeviceTable.Items),DeviceRuntimes},
{nameof(DeviceTable.AutoRestartThread),AutoRestartThread},
}).Render();
if (renderFragment != null)
{
var option = new WinBoxOption()
{
Title = Localizer["DeviceTable"],
ContentTemplate = renderFragment,
Max = false,
Width = "60%",
Height = "60%",
Top = "0%",
Left = "10%",
Background = "var(--bb-primary-color)",
Overflow = true
};
await WinBoxService.Show(option);
}
}
[Inject]
WinBoxService WinBoxService { get; set; }
#endif
//[Parameter]
//public EventCallback<ShowTypeEnum?> ShowTypeChanged { get; set; }
//[Parameter]
//public ShowTypeEnum? ShowType { get; set; }
//[Inject]
//IJSRuntime JSRuntime { get; set; }
//private async Task OnShowTypeChanged(ShowTypeEnum? showType)
//{
// ShowType = showType;
// if (showType != null)
// await JSRuntime.SetLocalStorage("showType", ShowType);
// if (ShowTypeChanged.HasDelegate)
// await ShowTypeChanged.InvokeAsync(showType);
//}
//protected override async Task OnAfterRenderAsync(bool firstRender)
//{
// if (firstRender)
// {
// var showType = await JSRuntime!.GetLocalStorage<ShowTypeEnum>("showType");
// await OnShowTypeChanged(showType);
// StateHasChanged();
// }
// await base.OnAfterRenderAsync(firstRender);
//}
[Parameter]
public bool AutoRestartThread { get; set; }
@@ -99,8 +323,6 @@ public partial class ChannelDeviceTree : IDisposable
[Inject]
DialogService DialogService { get; set; }
[Inject]
WinBoxService WinBoxService { get; set; }
[Inject]
[NotNull]

View File

@@ -5,7 +5,7 @@
<DeviceRuntimeInfo1 DeviceRuntime="DeviceRuntime" />
<LogConsole HeightString="calc(100% - 270px)" LogLevel=@(LogLevel)
<LogConsole HeightString=@Height LogLevel=@(LogLevel)
LogLevelChanged="async (logLevel)=>
{
LogLevel = logLevel;

View File

@@ -12,6 +12,13 @@ namespace ThingsGateway.Gateway.Razor;
public partial class DeviceRuntimeInfo
{
#if !Management
private string Height { get; set; } = "calc(100% - 300px)";
#else
private string Height { get; set; } = "calc(100% - 330px)";
#endif
[Parameter, EditorRequired]
public DeviceRuntime DeviceRuntime { get; set; }

View File

@@ -1,31 +0,0 @@
@inherits ComponentDefault
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application
@namespace ThingsGateway.Gateway.Razor
@if (ShowType == ShowTypeEnum.VariableTable)
{
<VariableRuntimeInfo Items="VariableRuntimes" SelectModel="SelectModel" AutoRestartThread="AutoRestartThread" />
}
else if (ShowType == ShowTypeEnum.LogInfo)
{
if (GlobalData.ReadOnlyIdDevices.TryGetValue(ShowDeviceRuntime, out var device))
{
<DeviceRuntimeInfo DeviceRuntime="device" />
}
if (GlobalData.ReadOnlyIdChannels.TryGetValue(ShowChannelRuntime, out var channel))
{
<ChannelRuntimeInfo ChannelRuntime="channel" />
}
}
else if (ShowType == ShowTypeEnum.ChannelTable)
{
<ChannelTable SelectModel="SelectModel" Items="ChannelRuntimes" AutoRestartThread=AutoRestartThread />
}
else if (ShowType == ShowTypeEnum.DeviceTable)
{
<DeviceTable SelectModel="SelectModel" Items="DeviceRuntimes" AutoRestartThread=AutoRestartThread />
}

View File

@@ -1,67 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
public partial class GatewayInfo : IDisposable
{
[Parameter]
public ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = null };
[Parameter]
public IEnumerable<VariableRuntime> VariableRuntimes { get; set; } = Enumerable.Empty<VariableRuntime>();
[Parameter]
public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
[Parameter]
public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
[Parameter]
public long ShowChannelRuntime { get; set; }
[Parameter]
public long ShowDeviceRuntime { get; set; }
[Parameter]
public ShowTypeEnum? ShowType { get; set; }
[Parameter]
public bool AutoRestartThread { get; set; } = true;
protected override void OnInitialized()
{
_ = RunTimerAsync();
base.OnInitialized();
}
private bool Disposed;
private async Task RunTimerAsync()
{
while (!Disposed)
{
try
{
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
NewLife.Log.XTrace.WriteException(ex);
}
finally
{
await Task.Delay(5000);
}
}
}
public void Dispose()
{
Disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -15,7 +15,7 @@
<Card IsShadow=true class="h-100 me-1" Color="Color.Primary">
<BodyTemplate>
<ChannelDeviceTree @bind-ShowType=ShowType AutoRestartThread="AutoRestartThread"
<ChannelDeviceTree AutoRestartThread="AutoRestartThread"
ChannelDeviceChanged="TreeChangedAsync" Value="SelectModel"></ChannelDeviceTree>
</BodyTemplate>
</Card>
@@ -24,7 +24,7 @@
</FirstPaneTemplate>
<SecondPaneTemplate>
<GatewayInfo AutoRestartThread=AutoRestartThread SelectModel=SelectModel ShowChannelRuntime=ShowChannelRuntime ShowDeviceRuntime=ShowDeviceRuntime ShowType=ShowType VariableRuntimes=VariableRuntimes ChannelRuntimes="ChannelRuntimes" DeviceRuntimes="DeviceRuntimes" />
<VariableRuntimeInfo Items="VariableRuntimes" SelectModel="SelectModel" AutoRestartThread="AutoRestartThread" />
</SecondPaneTemplate>
</Split>

View File

@@ -18,15 +18,15 @@ public partial class GatewayMonitorPage
private async Task TreeChangedAsync(ChannelDeviceTreeItem channelDeviceTreeItem)
{
ShowChannelRuntime = 0;
ShowDeviceRuntime = 0;
//ShowChannelRuntime = 0;
//ShowDeviceRuntime = 0;
SelectModel = channelDeviceTreeItem;
var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false);
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
{
ShowChannelRuntime = channelRuntime.Id;
//ShowChannelRuntime = channelRuntime.Id;
if (channelRuntime.IsCollect == true)
{
@@ -36,12 +36,12 @@ public partial class GatewayMonitorPage
{
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.Where(a => a.Value?.Driver?.IdVariableRuntimes != null).SelectMany(a => a.Value?.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)?.Select(a => a.Value)).Where(a => a != null);
}
ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
//ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
//DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
}
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{
ShowDeviceRuntime = deviceRuntime.Id;
//ShowDeviceRuntime = deviceRuntime.Id;
if (deviceRuntime.IsCollect == true)
{
VariableRuntimes = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).Where(a => a != null);
@@ -65,8 +65,8 @@ public partial class GatewayMonitorPage
}
}
ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
//ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
//DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
}
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
{
@@ -80,26 +80,26 @@ public partial class GatewayMonitorPage
VariableRuntimes = channels.Where(a => a.PluginName == pluginName).SelectMany(a => a.ReadDeviceRuntimes).Where(a => a.Value.Driver?.IdVariableRuntimes != null).SelectMany(a => a.Value.Driver?.IdVariableRuntimes).Select(a => a.Value);
}
ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
//ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
//DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
}
else
{
VariableRuntimes = variables.Where(a => a != null);
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
{
if (pluginTypeEnum != null)
{
ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
}
else
{
ChannelRuntimes = channels;
DeviceRuntimes = devices;
}
}
//if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
//{
// if (pluginTypeEnum != null)
// {
// ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
// DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
// }
// else
// {
// ChannelRuntimes = channels;
// DeviceRuntimes = devices;
// }
//}
}
await InvokeAsync(StateHasChanged);
}
@@ -113,11 +113,11 @@ public partial class GatewayMonitorPage
}
public IEnumerable<VariableRuntime> VariableRuntimes { get; set; } = Enumerable.Empty<VariableRuntime>();
public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
//public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
//public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
private long ShowChannelRuntime { get; set; }
private long ShowDeviceRuntime { get; set; }
public ShowTypeEnum? ShowType { get; set; }
//private long ShowChannelRuntime { get; set; }
//private long ShowDeviceRuntime { get; set; }
//public ShowTypeEnum? ShowType { get; set; }
private bool AutoRestartThread { get; set; } = true;
}

View File

@@ -23,7 +23,7 @@
LogLevel=a;
if(LogLevelChanged!=null)
{
await LogLevelChanged?.Invoke(a);
await LogLevelChanged.Invoke(a);
}
}" LogPath=@LogPath HeaderText=@HeaderText></ThingsGateway.Debug.LogConsole>

View File

@@ -47,7 +47,7 @@ public partial class RedundancyOptionsPage
LogLevel = logLevel;
if (LogLevelChanged != null)
{
await LogLevelChanged?.Invoke(LogLevel);
await LogLevelChanged.Invoke(LogLevel);
}
await InvokeAsync(StateHasChanged);
}

View File

@@ -15,8 +15,8 @@ public partial class DragAndDrop
{
private readonly BlazorDiagram _blazorDiagram = new(new BlazorDiagramOptions
{
GridSize = 75,
GridSnapToCenter = true,
GridSize = null,
GridSnapToCenter = false,
});
private string? _draggedType;
[Inject]

View File

@@ -77,7 +77,7 @@ ShowDesign=true;}" />
</RowButtonTemplate>
</AdminTable>
</div>
<div class="col-12 col-sm-6 h-100">
<div class="col-12 col-sm-6 h-100 ps-2">
<RulesStatus RulesId="RulesId"></RulesStatus>
</div>

View File

@@ -11,7 +11,7 @@
@if (_rules != null)
{
<LogConsole HeightString="100%" LogLevel=@(LogLevel) LogLevelChanged="(a)=>{
<LogConsole HeightString="calc(100% - 50px)" LogLevel=@(LogLevel) LogLevelChanged="(a)=>{
LogLevel=a;
return RulesEngineHostedService.SetRulesLogLevelAsync(RulesId, a);
}" LogPath=@LogPath HeaderText="@_rules.Name"></LogConsole>

View File

@@ -19,7 +19,6 @@ using Longbow.TcpSocket;
using Microsoft.Extensions.DependencyInjection;
using System.IO.Pipelines;
using System.Net.Sockets;
using ThingsGateway.Foundation.Modbus;
@@ -40,13 +39,6 @@ public class ModbusBenchmark : IDisposable
private List<IModbusMaster> nmodbuss = new();
//private List<ModbusTcpNet> modbusTcpNets = new();
private List<ModbusTcpMaster> modbusTcpMasters = new();
private PipeOptions GetNoDelayPipeOptions()
{
return new PipeOptions(
readerScheduler: PipeScheduler.Inline,
writerScheduler: PipeScheduler.Inline,
useSynchronizationContext: false);
}
public ModbusBenchmark()
{
@@ -54,12 +46,8 @@ public class ModbusBenchmark : IDisposable
{
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
//clientConfig.SetTransportOption(new TouchSocket.Sockets.TransportOption()
//{
// ReceivePipeOptions = GetNoDelayPipeOptions(),
// SendPipeOptions = GetNoDelayPipeOptions(),
//}).SetNoDelay(true);
var clientChannel = clientConfig.GetTcpClient(new ChannelOptions() { RemoteUrl = "127.0.0.1:502", MaxConcurrentCount = 10 });
var clientChannel = clientConfig.GetChannel(new ChannelOptions() { ChannelType = ChannelTypeEnum.TcpClient, RemoteUrl = "127.0.0.1:502", MaxConcurrentCount = 10 });
var thingsgatewaymodbus = new ModbusMaster()
{
//modbus协议格式
@@ -121,26 +109,7 @@ public class ModbusBenchmark : IDisposable
}
}
}
[Benchmark]
public async Task LongbowModbus()
{
List<Task> tasks = new List<Task>();
foreach (var _lgbModbusClient in _lgbModbusClients)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var task = await _lgbModbusClient.ReadHoldingRegistersAsync(1, 0, 100);
}
}));
}
}
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task ThingsGateway()
{
@@ -155,7 +124,7 @@ public class ModbusBenchmark : IDisposable
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await thingsgatewaymodbus.ModbusReadAsync(new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 });
var result = await thingsgatewaymodbus.ModbusReadAsync(new ModbusAddress() { Station = 1, FunctionCode = 3, StartAddress = 0, Length = 100 }).ConfigureAwait(false);
if (!result.IsSuccess)
{
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.ToString());
@@ -168,7 +137,27 @@ public class ModbusBenchmark : IDisposable
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task LongbowModbus()
{
List<Task> tasks = new List<Task>();
foreach (var _lgbModbusClient in _lgbModbusClients)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
using var cts = new CancellationTokenSource(3000);
var task = await _lgbModbusClient.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
}
}));
}
}
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task TouchSocket()
@@ -182,7 +171,7 @@ public class ModbusBenchmark : IDisposable
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await modbusTcpMaster.ReadHoldingRegistersAsync(0, 100);
var result = await modbusTcpMaster.ReadHoldingRegistersAsync(0, 100).ConfigureAwait(false);
var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Data.Span, EndianType.Little);
if (!result.IsSuccess)
{
@@ -208,7 +197,7 @@ public class ModbusBenchmark : IDisposable
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
var result = await nmodbus.ReadHoldingRegistersAsync(1, 0, 100).ConfigureAwait(false);
}
}));
}

View File

@@ -17,8 +17,6 @@ using HslCommunication.Profinet.Siemens;
using S7.Net;
using System.IO.Pipelines;
using ThingsGateway.Foundation.SiemensS7;
using TouchSocket.Core;
@@ -32,13 +30,7 @@ public class S7Benchmark : IDisposable
private List<Plc> plcs = new();
private List<SiemensS7Net> siemensS7Nets = new();
private PipeOptions GetNoDelayPipeOptions()
{
return new PipeOptions(
readerScheduler: PipeScheduler.Inline,
writerScheduler: PipeScheduler.Inline,
useSynchronizationContext: false);
}
public S7Benchmark()
{
@@ -46,12 +38,8 @@ public class S7Benchmark : IDisposable
for (int i = 0; i < Program.ClientCount; i++)
{
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
clientConfig.SetTransportOption(new TouchSocket.Sockets.TransportOption()
{
ReceivePipeOptions = GetNoDelayPipeOptions(),
SendPipeOptions = GetNoDelayPipeOptions(),
}).SetNoDelay(true);
var clientChannel = clientConfig.GetTcpClient(new ChannelOptions() { RemoteUrl = "127.0.0.1:102" });
var clientChannel = clientConfig.GetChannel(new ChannelOptions() { ChannelType = ChannelTypeEnum.TcpClient, RemoteUrl = "127.0.0.1:102" });
var siemensS7 = new SiemensS7Master()
{
//modbus协议格式

View File

@@ -20,9 +20,9 @@ namespace BenchmarkConsoleApp
{
internal class Program
{
public static int ClientCount = 30;
public static int TaskNumberOfItems = 10;
public static int NumberOfItems = 30;
public static int ClientCount = 3;
public static int TaskNumberOfItems = 1;
public static int NumberOfItems = 3;
private static async Task Main(string[] args)
{

View File

@@ -41,19 +41,19 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
<PackageReference Include="HslCommunication" Version="12.5.0" />
<PackageReference Include="Longbow.Modbus" Version="9.0.4" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.3" />
<PackageReference Include="HslCommunication" Version="12.5.1" />
<PackageReference Include="Longbow.Modbus" Version="9.0.9" />
<PackageReference Include="NModbus" Version="3.0.81" />
<PackageReference Include="NModbus.Serial" Version="3.0.81" />
<PackageReference Include="S7netplus" Version="0.20.0" />
<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />
<PackageReference Include="TouchSocket.Modbus" Version="4.0.0-beta.27" />
<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />-->
<PackageReference Include="TouchSocket.Modbus" Version="4.0.0-beta.57" />
</ItemGroup>
<ItemGroup>
<!--<ProjectReference Include="..\..\Plugin\ThingsGateway.Foundation.Modbus\ThingsGateway.Foundation.Modbus.csproj" />
<ProjectReference Include="..\..\Plugin\ThingsGateway.Foundation.SiemensS7\ThingsGateway.Foundation.SiemensS7.csproj" />-->
<ProjectReference Include="..\..\Plugin\ThingsGateway.Foundation.Modbus\ThingsGateway.Foundation.Modbus.csproj" />
<ProjectReference Include="..\..\Plugin\ThingsGateway.Foundation.SiemensS7\ThingsGateway.Foundation.SiemensS7.csproj" />
</ItemGroup>
</Project>

View File

@@ -52,7 +52,7 @@ public class ModbusRequest
/// <summary>
/// 站号
/// </summary>
public byte Station { get; set; }
public byte Station { get; set; } = 1;
#endregion Request
}

View File

@@ -97,7 +97,9 @@ public class ModbusSlave : DeviceBase, IModbusAddress
{
return $"{base.GetAddressDescription()}{Environment.NewLine}{ModbusHelper.GetAddressDescription()}";
}
protected override void SetChannel()
{
}
/// <inheritdoc/>
public override DataHandlingAdapter GetDataAdapter()
{

View File

@@ -11,6 +11,9 @@
//修改自https://github.com/dathlin/OpcUaHelper 与OPC基金会net库
using System.Collections.Concurrent;
#if NET8_0_OR_GREATER
using System.Collections.Frozen;
#endif
namespace ThingsGateway.Foundation.OpcUa;
@@ -28,7 +31,7 @@ public delegate void LogEventHandler(byte level, object sender, string message,
/// <summary>
/// OpcUaMaster
/// </summary>
public class OpcUaMaster : IDisposable, IAsyncDisposable
public class OpcUaMaster : IAsyncDisposable
{
#region
@@ -157,9 +160,9 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
},
};
certificateValidator.Update(m_configuration);
Task.Run(() => certificateValidator.UpdateAsync(m_configuration)).GetAwaiter().GetResult();
m_configuration.Validate(ApplicationType.Client);
Task.Run(() => m_configuration.ValidateAsync(ApplicationType.Client)).GetAwaiter().GetResult();
m_application.ApplicationConfiguration = m_configuration;
}
@@ -367,19 +370,21 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
/// </summary>
/// <param name="tagParent">方法的父节点tag</param>
/// <param name="tag">方法的节点tag</param>
/// <param name="cancellationToken">cancellationToken</param>
/// <param name="args">传递的参数</param>
/// <returns>输出的结果值</returns>
public object[] CallMethodByNodeId(string tagParent, string tag, params object[] args)
public async Task<IList<object>> CallMethodByNodeIdAsync(string tagParent, string tag, CancellationToken cancellationToken, params object[] args)
{
if (m_session == null)
{
return null;
}
IList<object> outputArguments = m_session.Call(
IList<object> outputArguments = await m_session.CallAsync(
new NodeId(tagParent),
new NodeId(tag),
args);
cancellationToken,
args).ConfigureAwait(false);
return outputArguments.ToArray();
}
@@ -392,19 +397,6 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
await ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// 断开连接。
/// </summary>
public void Disconnect()
{
PrivateDisconnect();
// disconnect any existing session.
if (m_session != null)
{
m_session = null;
}
}
/// <summary>
/// 断开连接。
/// </summary>
@@ -417,14 +409,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
m_session = null;
}
}
/// <inheritdoc/>
public void Dispose()
{
Disconnect();
_variableDicts?.Clear();
_subscriptionDicts?.Clear();
waitLock?.Dispose();
}
public async ValueTask DisposeAsync()
{
await DisconnectAsync().ConfigureAwait(false);
@@ -573,8 +558,9 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
/// 读取一个节点的所有属性
/// </summary>
/// <param name="tag">节点信息</param>
/// <param name="cancellationToken">cancellationToken</param>
/// <returns>节点的特性值</returns>
public OPCNodeAttribute[] ReadNoteAttributes(string tag)
public async Task<OPCNodeAttribute[]> ReadNoteAttributesAsync(string tag, CancellationToken cancellationToken)
{
NodeId sourceId = new(tag);
ReadValueIdCollection nodesToRead = new();
@@ -608,7 +594,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
};
// fetch property references from the server.
ReferenceDescriptionCollection references = OpcUaUtils.Browse(m_session, nodesToBrowse, false);
ReferenceDescriptionCollection references = await OpcUaUtils.BrowseAsync(m_session, nodesToBrowse, false, cancellationToken).ConfigureAwait(false);
if (references == null)
{
@@ -631,15 +617,15 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
nodesToRead.Add(nodeToRead);
}
// read all values.
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out DataValueCollection results,
out DiagnosticInfoCollection diagnosticInfos);
// 读取当前的值
var result = await m_session.ReadAsync(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
cancellationToken).ConfigureAwait(false);
var results = result.Results;
var diagnosticInfos = result.DiagnosticInfos;
ClientBase.ValidateResponse(results, nodesToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
@@ -661,7 +647,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
}
// get the name of the attribute.
item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);
item.Name = GetBrowseName(nodesToRead[ii].AttributeId);
// display any unexpected error.
if (StatusCode.IsBad(results[ii].StatusCode))
@@ -726,7 +712,34 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
return nodeAttribute.ToArray();
}
/// <summary>
/// Returns the browse name for the attribute.
/// </summary>
public static string GetBrowseName(uint identifier)
{
return s_attributesIdToName.Value.TryGetValue(identifier, out string name)
? name : string.Empty;
}
/// <summary>
/// Creates a dictionary of identifiers to browse names for the attributes.
/// </summary>
private static readonly Lazy<IReadOnlyDictionary<long, string>> s_attributesIdToName =
new(() =>
{
System.Reflection.FieldInfo[] fields = typeof(Attributes).GetFields(
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var keyValuePairs = new Dictionary<long, string>();
foreach (System.Reflection.FieldInfo field in fields)
{
keyValuePairs.TryAdd(Convert.ToInt64(field.GetValue(typeof(Attributes))), field.Name);
}
#if NET8_0_OR_GREATER
return keyValuePairs.ToFrozenDictionary();
#else
return new System.Collections.ObjectModel.ReadOnlyDictionary<long, string>(keyValuePairs);
#endif
});
/// <summary>
/// 移除所有的订阅消息
/// </summary>
@@ -825,7 +838,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
{
foreach (var value in monitoreditem.DequeueValues())
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode));
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode)).GetAwaiter().GetResult();
if (value.Value != null)
{
@@ -861,7 +874,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
/// <returns></returns>
public async Task CheckApplicationInstanceCertificate()
{
await m_application.CheckApplicationInstanceCertificates(true, 1200).ConfigureAwait(false);
await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200).ConfigureAwait(false);
}
SemaphoreSlim waitLock = new(1, 1);
@@ -910,11 +923,12 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
}
//创建本地证书
if (useSecurity)
await m_application.CheckApplicationInstanceCertificates(true, 1200, cancellationToken).ConfigureAwait(false);
m_session = await Opc.Ua.Client.Session.Create(
await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200, cancellationToken).ConfigureAwait(false);
m_session = await Opc.Ua.Client.Session.CreateAsync(
DefaultSessionFactory.Instance,
m_configuration,
(ITransportWaitingConnection)null,
endpoint,
false,
OpcUaProperty.CheckDomain,
@@ -940,29 +954,6 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
}
}
private void PrivateDisconnect()
{
bool state = m_session?.Connected == true;
if (m_reConnectHandler != null)
{
try { m_reConnectHandler.Dispose(); } catch { }
m_reConnectHandler = null;
}
if (m_session != null)
{
m_session.KeepAlive -= Session_KeepAlive;
m_session.Close(10000);
m_session.Dispose();
m_session = null;
}
if (state)
{
Log(2, null, "Disconnected");
DoConnectComplete(false);
}
}
private async Task PrivateDisconnectAsync()
{
bool state = m_session?.Connected == true;
@@ -1035,7 +1026,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true)
private async Task<VariableNode> ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true, CancellationToken cancellationToken = default)
{
if (!isOnlyServer)
{
@@ -1061,16 +1052,17 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
}
// read from server.
DataValueCollection values = null;
DiagnosticInfoCollection diagnosticInfos = null;
ResponseHeader responseHeader = m_session.Read(
null,
0,
TimestampsToReturn.Neither,
itemsToRead,
out values,
out diagnosticInfos);
var result = await m_session.ReadAsync(
null,
0,
TimestampsToReturn.Neither,
itemsToRead,
cancellationToken).ConfigureAwait(false);
var values = result.Results;
var diagnosticInfos = result.DiagnosticInfos;
var responseHeader = result.ResponseHeader;
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
@@ -1121,7 +1113,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject)
await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
await typeSystem.LoadTypeAsync(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
@@ -1222,7 +1214,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
_variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
if (node.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(node.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject)
{
await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false);
await typeSystem.LoadTypeAsync(node.DataType, ct: cancellationToken).ConfigureAwait(false);
}
}
result.Add(node);
@@ -1314,7 +1306,7 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable
continue;
}
item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);
item.Name = GetBrowseName(nodesToRead[ii].AttributeId);
if (StatusCode.IsBad(nodeValue.StatusCode))
{
item.Type = Utils.Format("{0}", Attributes.GetDataTypeId(nodesToRead[ii].AttributeId));

View File

@@ -12,8 +12,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.377.21" />
</ItemGroup>
<ItemGroup>

View File

@@ -56,133 +56,6 @@ public static class OpcUaUtils
}
}
/// <summary>
/// Browses the address space and returns the references found.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="nodesToBrowse">The set of browse operations to perform.</param>
/// <param name="throwOnError">if set to <c>true</c> a exception will be thrown on an error.</param>
/// <returns>
/// The references found. Null if an error occurred.
/// </returns>
public static ReferenceDescriptionCollection Browse(ISession session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError)
{
try
{
ReferenceDescriptionCollection references = new();
BrowseDescriptionCollection unprocessedOperations = new();
while (nodesToBrowse.Count > 0)
{
// start the browse operation.
session.Browse(
null,
null,
0,
nodesToBrowse,
out BrowseResultCollection results,
out DiagnosticInfoCollection diagnosticInfos);
ClientBase.ValidateResponse(results, nodesToBrowse);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);
ByteStringCollection continuationPoints = new();
for (int ii = 0; ii < nodesToBrowse.Count; ii++)
{
// check for error.
if (StatusCode.IsBad(results[ii].StatusCode))
{
// this error indicates that the server does not have enough simultaneously active
// continuation points. This request will need to be resent after the other operations
// have been completed and their continuation points released.
if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints)
{
unprocessedOperations.Add(nodesToBrowse[ii]);
}
continue;
}
// check if all references have been fetched.
if (results[ii].References.Count == 0)
{
continue;
}
// save results.
references.AddRange(results[ii].References);
// check for continuation point.
if (results[ii].ContinuationPoint != null)
{
continuationPoints.Add(results[ii].ContinuationPoint);
}
}
// process continuation points.
ByteStringCollection revisedContiuationPoints = new();
while (continuationPoints.Count > 0)
{
// continue browse operation.
session.BrowseNext(
null,
true,
continuationPoints,
out results,
out diagnosticInfos);
ClientBase.ValidateResponse(results, continuationPoints);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);
for (int ii = 0; ii < continuationPoints.Count; ii++)
{
// check for error.
if (StatusCode.IsBad(results[ii].StatusCode))
{
continue;
}
// check if all references have been fetched.
if (results[ii].References.Count == 0)
{
continue;
}
// save results.
references.AddRange(results[ii].References);
// check for continuation point.
if (results[ii].ContinuationPoint != null)
{
revisedContiuationPoints.Add(results[ii].ContinuationPoint);
}
}
// check if browsing must continue;
revisedContiuationPoints = continuationPoints;
}
// check if unprocessed results exist.
nodesToBrowse = unprocessedOperations;
}
// return complete list.
return references;
}
catch (Exception exception)
{
if (throwOnError)
{
throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError);
}
return null;
}
}
/// <summary>
/// 浏览地址空间
/// </summary>
@@ -570,7 +443,7 @@ public static class OpcUaUtils
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <returns>A list of server urls.</returns>
public static IList<string> DiscoverServers(ApplicationConfiguration configuration)
public static async Task<IList<string>> DiscoverServers(ApplicationConfiguration configuration)
{
List<string> serverUrls = new();
@@ -581,7 +454,7 @@ public static class OpcUaUtils
// Connect to the local discovery server and find the available servers.
using (DiscoveryClient client = DiscoveryClient.Create(new Uri("opc.tcp://localhost:4840"), endpointConfiguration))
{
ApplicationDescriptionCollection servers = client.FindServers(null);
ApplicationDescriptionCollection servers = await client.FindServersAsync(null).ConfigureAwait(false);
// populate the drop down list with the discovery URLs for the available servers.
for (int ii = 0; ii < servers.Count; ii++)
@@ -641,7 +514,7 @@ public static class OpcUaUtils
/// <summary>
/// 指定的属性的显示文本。
/// </summary>
public static string GetAttributeDisplayText(ISession session, uint attributeId, Variant value)
public static async Task<string> GetAttributeDisplayTextAsync(ISession session, uint attributeId, Variant value)
{
if (value == Variant.Null)
{
@@ -677,7 +550,7 @@ public static class OpcUaUtils
case Attributes.DataType:
{
return session.NodeCache.GetDisplayText(value.Value as NodeId);
return await session.NodeCache.GetDisplayTextAsync(value.Value as NodeId).ConfigureAwait(false);
}
case Attributes.ValueRank:
@@ -727,102 +600,6 @@ public static class OpcUaUtils
return value.ToString();
}
/// <summary>
/// Finds the endpoint that best matches the current settings.
/// </summary>
/// <param name="discoveryUrl">The discovery URL.</param>
/// <param name="useSecurity">if set to <c>true</c> select an endpoint that uses security.</param>
/// <returns>The best available endpoint.</returns>
public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity)
{
// needs to add the '/discovery' back onto non-UA TCP URLs.
if (!discoveryUrl.StartsWith(Utils.UriSchemeOpcTcp))
{
if (!discoveryUrl.EndsWith("/discovery"))
{
discoveryUrl += "/discovery";
}
}
// parse the selected URL.
Uri uri = new(discoveryUrl);
// set a short timeout because this is happening in the drop down event.
EndpointConfiguration configuration = EndpointConfiguration.Create();
configuration.OperationTimeout = 5000;
EndpointDescription selectedEndpoint = null;
// Connect to the server's discovery endpoint and find the available configuration.
using (DiscoveryClient client = DiscoveryClient.Create(uri, configuration))
{
EndpointDescriptionCollection endpoints = client.GetEndpoints(null);
// select the best endpoint to use based on the selected URL and the UseSecurity checkbox.
for (int ii = 0; ii < endpoints.Count; ii++)
{
EndpointDescription endpoint = endpoints[ii];
// check for a match on the URL scheme.
if (endpoint.EndpointUrl.StartsWith(uri.Scheme))
{
// check if security was requested.
if (useSecurity)
{
if (endpoint.SecurityMode == MessageSecurityMode.None)
{
continue;
}
}
else
{
if (endpoint.SecurityMode != MessageSecurityMode.None)
{
continue;
}
}
// pick the first available endpoint by default.
selectedEndpoint ??= endpoint;
// The security level is a relative measure assigned by the server to the
// endpoints that it returns. Clients should always pick the highest level
// unless they have a reason not too.
if (endpoint.SecurityLevel > selectedEndpoint.SecurityLevel)
{
selectedEndpoint = endpoint;
}
}
}
// pick the first available endpoint by default.
if (selectedEndpoint == null && endpoints.Count > 0)
{
selectedEndpoint = endpoints[0];
}
}
// if a server is behind a firewall it may return URLs that are not accessible to the client.
// This problem can be avoided by assuming that the domain in the URL used to call
// GetEndpoints can be used to access any of the endpoints. This code makes that conversion.
// Note that the conversion only makes sense if discovery uses the same protocol as the endpoint.
Uri endpointUrl = Utils.ParseUri(selectedEndpoint.EndpointUrl);
if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme)
{
UriBuilder builder = new(endpointUrl)
{
Host = uri.DnsSafeHost,
Port = uri.Port
};
selectedEndpoint.EndpointUrl = builder.ToString();
}
// return the selected endpoint.
return selectedEndpoint;
}
/// <summary>
/// 返回一组相对路径的节点id
/// </summary>

View File

@@ -10,6 +10,7 @@
using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.Foundation.Modbus;
using ThingsGateway.NewLife.Json.Extension;
using TouchSocket.Core;
@@ -28,14 +29,14 @@ public class ModbusTest
[Theory]
[InlineData("400045", true, "00010000002F01032C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("300045", true, "00010000002F01042C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("100045", true, "000100000009010206000000000000")]
[InlineData("000045", true, "000100000009010106000000000000")]
[InlineData("400045", false, "0001000000060106002C0001", "1", DataTypeEnum.UInt16)]
[InlineData("000045", false, "0001000000060105002CFF00", "true", DataTypeEnum.Boolean)]
[InlineData("400045;w=16", false, "0001000000090110002C0001020001", "1", DataTypeEnum.UInt16)]
[InlineData("000045;w=15", false, "000100000008010F002C00010101", "true", DataTypeEnum.Boolean)]
[InlineData("400045", true, "00020000002F01032C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("300045", true, "00020000002F01042C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("100045", true, "000200000009010206000000000000")]
[InlineData("000045", true, "000200000009010106000000000000")]
[InlineData("400045", false, "0002000000060106002C0001", "1", DataTypeEnum.UInt16)]
[InlineData("000045", false, "0002000000060105002CFF00", "true", DataTypeEnum.Boolean)]
[InlineData("400045;w=16", false, "0002000000090110002C0001020001", "1", DataTypeEnum.UInt16)]
[InlineData("000045;w=15", false, "000200000008010F002C00010101", "true", DataTypeEnum.Boolean)]
public async Task ModbusTcp_ReadWrite_OK(string address, bool read, string data, string writeData = null, DataTypeEnum dataTypeEnum = DataTypeEnum.UInt16)
{
var modbusChannel = new TouchSocketConfig().GetChannel(new ChannelOptions()

View File

@@ -10,6 +10,7 @@
using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.Foundation.SiemensS7;
using ThingsGateway.NewLife.Json.Extension;
using TouchSocket.Core;
@@ -29,8 +30,8 @@ public class SiemensS7Test
[Theory]
[InlineData("M100", true, "03 00 00 1B 02 F0 80 32 03 00 00 00 01 00 02 00 06 00 00 04 01 FF 04 00 10 00 00")]
[InlineData("M100", false, "03 00 00 16 02 F0 80 32 03 00 00 00 01 00 02 00 01 00 00 05 01 FF", "1", DataTypeEnum.UInt16)]
[InlineData("M100", true, "03 00 00 1B 02 F0 80 32 03 00 00 00 02 00 02 00 06 00 00 04 01 FF 04 00 10 00 00")]
[InlineData("M100", false, "03 00 00 16 02 F0 80 32 03 00 00 00 02 00 02 00 01 00 00 05 01 FF", "1", DataTypeEnum.UInt16)]
public async Task SiemensS7_ReadWrite_OK(string address, bool read, string data, string writeData = null, DataTypeEnum dataTypeEnum = DataTypeEnum.UInt16)
{
var siemensS7Channel = new TouchSocketConfig().GetChannel(new ChannelOptions()

View File

@@ -31,6 +31,15 @@ public abstract class DynamicSQLBase
/// <returns></returns>
public virtual Task DBInsertable(ISqlSugarClient db, IEnumerable<object> datas, CancellationToken cancellationToken)
{
throw new NotSupportedException();
return Task.CompletedTask;
}
/// <summary>
/// 删除n天前数据
/// </summary>
/// <returns></returns>
public virtual Task<int> DBDeleteable(ISqlSugarClient db, int days, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}

View File

@@ -8,9 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json.Linq;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Plugin.QuestDB;
using ThingsGateway.Plugin.SqlDB;
@@ -23,7 +22,7 @@ internal static class Helper
{
var dest = new SQLHistoryValue();
dest.Id = src.Id;
dest.Value = GetValue(src.Value);
dest.Value = JsonElementExtensions.GetValue(src.Value, true);
dest.CreateTime = DateTime.Now;
dest.CollectTime = src.CollectTime;
@@ -43,7 +42,7 @@ internal static class Helper
{
var dest = new SQLHistoryValue();
dest.Id = src.Id;
dest.Value = GetValue(src.Value);
dest.Value = JsonElementExtensions.GetValue(src.Value, true);
dest.CreateTime = DateTime.Now;
dest.CollectTime = src.CollectTime;
@@ -121,8 +120,9 @@ internal static class Helper
{
var dest = new SQLRealValue();
dest.Id = src.Id;
dest.Value = GetValue(src.Value);
dest.Value = JsonElementExtensions.GetValue(src.Value, true);
dest.CollectTime = src.CollectTime;
//dest.UpdateTime = DateTime.Now;
dest.DeviceName = src.DeviceName;
dest.IsOnline = src.IsOnline;
dest.Name = src.Name;
@@ -145,8 +145,9 @@ internal static class Helper
{
var dest = new SQLRealValue();
dest.Id = src.Id;
dest.Value = GetValue(src.Value);
dest.Value = JsonElementExtensions.GetValue(src.Value, true);
dest.CollectTime = src.CollectTime;
//dest.UpdateTime = DateTime.Now;
dest.DeviceName = src.DeviceName;
dest.IsOnline = src.IsOnline;
dest.Name = src.Name;
@@ -172,7 +173,7 @@ internal static class Helper
{
var dest = new QuestDBHistoryValue();
dest.Id = src.Id;
dest.Value = GetValue(src.Value);
dest.Value = JsonElementExtensions.GetValue(src.Value, true);
dest.CreateTime = DateTime.UtcNow;
dest.CollectTime = src.CollectTime < DateTime.MinValue ? UtcTime1970 : src.CollectTime;
@@ -192,7 +193,7 @@ internal static class Helper
{
var dest = new QuestDBHistoryValue();
dest.Id = src.Id;
dest.Value = GetValue(src.Value);
dest.Value = JsonElementExtensions.GetValue(src.Value, true);
dest.CreateTime = DateTime.UtcNow;
dest.CollectTime = src.CollectTime < DateTime.MinValue ? UtcTime1970 : src.CollectTime;
@@ -252,26 +253,4 @@ internal static class Helper
#endregion
private static string GetValue(object src)
{
if (src != null)
{
if (src is string strValue)
{
return strValue;
}
else if (src is bool boolValue)
{
return boolValue ? "1" : "0";
}
else
{
return JToken.FromObject(src).ToString();
}
}
else
{
return string.Empty;
}
}
}

View File

@@ -33,13 +33,16 @@ public class RealDBProducerProperty : BusinessPropertyWithCacheInterval
[Required]
public string StringTableName { get; set; } = "historyStringValue";
[DynamicProperty]
public int SaveDays { get; set; } = 3650;
public string NumberTableNameLow => NumberTableName.ToLower();
public string StringTableNameLow => StringTableName.ToLower();
/// <summary>
/// 历史表脚本
/// </summary>
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptHistoryTable { get; set; }
}

View File

@@ -1,6 +1,7 @@
{
"ThingsGateway.Plugin.DB.RealDBProducerProperty": {
"BigTextConnectStr": "ConnectString",
"SaveDays": "SaveDays",
"BigTextScriptHistoryTable": "DynamicScriptHistoryTable",
"RealTableBusinessInterval": "RealTableBusinessInterval",
"StringTableName": "StringTableName",
@@ -25,6 +26,7 @@
"Value": "Value"
},
"ThingsGateway.Plugin.SqlDB.SqlDBProducerProperty": {
"SaveDays": "SaveDays",
"BigTextConnectStr": "ConnectString",
"BigTextScriptHistoryTable": "DynamicScriptHistoryTable",
"BigTextScriptRealTable": "DynamicScriptRealTable",
@@ -87,6 +89,7 @@
"Remark5": "Remark5"
},
"ThingsGateway.Plugin.DB.SQLHistoryAlarmProperty": {
"SaveDays": "SaveDays",
"BigTextConnectStr": "ConnectString",
"DbType": "DbType",
"TableName": "TableName",

View File

@@ -1,6 +1,7 @@
{
"ThingsGateway.Plugin.DB.RealDBProducerProperty": {
"BigTextConnectStr": "连接字符串",
"SaveDays": "保留天数",
"BigTextScriptHistoryTable": "历史表动态脚本",
"RealTableBusinessInterval": "实时表定时上传间隔",
"NumberTableName": "数值变量历史表名称",
@@ -25,6 +26,7 @@
"Value": "变量值"
},
"ThingsGateway.Plugin.SqlDB.SqlDBProducerProperty": {
"SaveDays": "保留天数",
"BigTextConnectStr": "链接字符串",
"BigTextScriptHistoryTable": "历史表动态脚本",
"BigTextScriptRealTable": "实时表动态脚本",
@@ -87,6 +89,7 @@
"Remark5": "备用5"
},
"ThingsGateway.Plugin.DB.SQLHistoryAlarmProperty": {
"SaveDays": "保留天数",
"BigTextConnectStr": "链接字符串",
"DbType": "数据库类型",
"TableName": "表名称",

View File

@@ -16,6 +16,7 @@ using ThingsGateway.Debug;
using ThingsGateway.Foundation;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Threading;
using ThingsGateway.Plugin.DB;
using ThingsGateway.SqlSugar;
@@ -69,6 +70,56 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
#if !Management
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
{
var list = base.ProtectedGetTasks(cancellationToken);
list.Add(ScheduledTaskHelper.GetTask("0 0 * * *", DeleteByDayAsync, null, LogMessage, cancellationToken));
return list;
}
private async Task DeleteByDayAsync(object? state, CancellationToken cancellationToken)
{
try
{
using var db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
await hisModel.DBDeleteable(db, _driverPropertys.SaveDays, cancellationToken).ConfigureAwait(false);
}
else
{
{
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
string sql = $"""
ALTER TABLE {_driverPropertys.NumberTableNameLow}
DROP PARTITION
WHERE createtime < to_timestamp('{time.ToString("yyyy-MM-dd:HH:mm:ss")}', 'yyyy-MM-dd:HH:mm:ss');
""";
await db.Ado.ExecuteCommandAsync("", default, cancellationToken: cancellationToken).ConfigureAwait(false);
sql = $"""
ALTER TABLE {_driverPropertys.StringTableNameLow}
DROP PARTITION
WHERE createtime < to_timestamp('{time.ToString("yyyy-MM-dd:HH:mm:ss")}', 'yyyy-MM-dd:HH:mm:ss');
""";
await db.Ado.ExecuteCommandAsync("", default, cancellationToken: cancellationToken).ConfigureAwait(false);
LogMessage?.LogInformation($"Clean up historical data from {_driverPropertys.SaveDays} days ago");
}
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "Clearing historical data error");
}
}
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
{
_db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
@@ -85,7 +136,6 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
{
_db.DbMaintenance.CreateDatabase();
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
DynamicSQLBase? hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);

View File

@@ -58,4 +58,9 @@ public class SQLRealValue : IPrimaryIdEntity
[AutoGenerateColumn(Order = 22, Visible = true, Sortable = true, Filterable = false)]
[SugarColumn(ColumnDescription = "采集时间")]
public DateTime CollectTime { get; set; }
//[AutoGenerateColumn(Order = 23, Visible = true, Sortable = true, Filterable = false)]
//[SugarColumn(ColumnDescription = "更新时间")]
//public DateTime UpdateTime { get; set; }
}

View File

@@ -18,6 +18,7 @@ using ThingsGateway.Foundation;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.DictionaryExtensions;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Threading;
using ThingsGateway.Plugin.DB;
using ThingsGateway.SqlSugar;
@@ -70,7 +71,82 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
return $" {nameof(SqlDBProducer)}";
}
#if !Management
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
{
var list = base.ProtectedGetTasks(cancellationToken);
list.Add(ScheduledTaskHelper.GetTask("0 0 * * *", DeleteByDayAsync, null, LogMessage, cancellationToken));
return list;
}
private async Task DeleteByDayAsync(object? state, CancellationToken cancellationToken)
{
try
{
using var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
if (_driverPropertys.IsHistoryDB)
{
await hisModel.DBDeleteable(db, _driverPropertys.SaveDays, cancellationToken).ConfigureAwait(false);
}
}
else
{
if (_driverPropertys.IsHistoryDB)
{
{
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
var tableNames = db.SplitHelper<SQLHistoryValue>().GetTables();//根据时间获取表名
var filtered = tableNames.Where(a => a.Date < time).ToList();
// 去掉最后一个
var oldTable = filtered.Take(filtered.Count - 1);
foreach (var table in oldTable)
{
db.DbMaintenance.DropTable(table.TableName);
}
var deldata = filtered.LastOrDefault();
if (deldata != null)
{
await db.Deleteable<SQLHistoryValue>().AS(deldata.TableName).Where(a => a.CreateTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
}
}
{
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
var tableNames = db.SplitHelper<SQLNumberHistoryValue>().GetTables();//根据时间获取表名
var filtered = tableNames.Where(a => a.Date < time).ToList();
// 去掉最后一个
var oldTable = filtered.Take(filtered.Count - 1);
foreach (var table in oldTable)
{
db.DbMaintenance.DropTable(table.TableName);
}
var deldata = filtered.LastOrDefault();
if (deldata != null)
{
await db.Deleteable<SQLNumberHistoryValue>().AS(deldata.TableName).Where(a => a.CreateTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
}
}
}
LogMessage?.LogInformation($"Clean up historical data from {_driverPropertys.SaveDays} days ago");
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "Clearing historical data error");
}
}
public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input)
{
@@ -112,7 +188,6 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
{
_db.DbMaintenance.CreateDatabase();
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);

View File

@@ -58,6 +58,9 @@ public class SqlDBProducerProperty : BusinessPropertyWithCacheInterval
[DynamicProperty]
public DbType DbType { get; set; } = DbType.SqlServer;
[DynamicProperty]
public int SaveDays { get; set; } = 3650;
[DynamicProperty]
public SqlDBSplitType SqlDBSplitType { get; set; } = SqlDBSplitType.Week;
@@ -69,13 +72,13 @@ public class SqlDBProducerProperty : BusinessPropertyWithCacheInterval
/// <summary>
/// 实时表脚本
/// </summary>
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptRealTable { get; set; }
/// <summary>
/// 历史表脚本
/// </summary>
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptHistoryTable { get; set; }
}

View File

@@ -16,6 +16,7 @@ using ThingsGateway.Debug;
using ThingsGateway.Foundation;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Threading;
using ThingsGateway.SqlSugar;
namespace ThingsGateway.Plugin.DB;
@@ -30,7 +31,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
{
internal readonly SqlHistoryAlarmProperty _driverPropertys = new();
private readonly SqlHistoryAlarmVariableProperty _variablePropertys = new();
public override bool RefreshRuntimeAlways { get; set; } = true;
/// <inheritdoc/>
public override Type DriverUIType
{
@@ -65,6 +66,44 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
}
#if !Management
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
{
var list = base.ProtectedGetTasks(cancellationToken);
list.Add(ScheduledTaskHelper.GetTask("0 0 * * *", DeleteByDayAsync, null, LogMessage, cancellationToken));
return list;
}
private async Task DeleteByDayAsync(object? state, CancellationToken cancellationToken)
{
try
{
using var db = BusinessDatabaseUtil.GetDb((DbType)_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
await hisModel.DBDeleteable(db, _driverPropertys.SaveDays, cancellationToken).ConfigureAwait(false);
}
else
{
{
var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
await db.Deleteable<HistoryAlarm>().Where(a => a.EventTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
LogMessage?.LogInformation($"Clean up historical data from {_driverPropertys.SaveDays} days ago");
}
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "Clearing historical data error");
}
}
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
{

View File

@@ -23,6 +23,10 @@ public class SqlHistoryAlarmProperty : BusinessPropertyWithCache
{
[DynamicProperty]
public DbType DbType { get; set; } = DbType.SqlServer;
[DynamicProperty]
public int SaveDays { get; set; } = 3650;
[DynamicProperty]
[Required]
public string TableName { get; set; } = "historyAlarm";

View File

@@ -102,7 +102,6 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
{
_db.DbMaintenance.CreateDatabase();
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
@@ -131,6 +130,12 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
`value` DOUBLE ) TAGS(`devicename` VARCHAR(100) ,`name` VARCHAR(100))
""";
await _db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
await _db.Ado.ExecuteCommandAsync($"ALTER STABLE `{_driverPropertys.StringTableNameLow}` KEEP 10;", default, cancellationToken: cancellationToken).ConfigureAwait(false);
await _db.Ado.ExecuteCommandAsync($"ALTER STABLE `{_driverPropertys.NumberTableNameLow}` KEEP 10;", default, cancellationToken: cancellationToken).ConfigureAwait(false);
}
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
}

View File

@@ -8,13 +8,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Text;
using ThingsGateway.Extension.Generic;
using ThingsGateway.Foundation;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.Plugin.DB;
using ThingsGateway.SqlSugar;
using ThingsGateway.SqlSugar.TDengine;
@@ -155,7 +154,7 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
foreach (var item in variableGroup)
{
stringBuilder.Append($"""(NOW,"{item.CollectTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}",{item.Id},{item.IsOnline},"{GetValue(item)}"),""");
stringBuilder.Append($"""(NOW,"{item.CollectTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}",{item.Id},{item.IsOnline},"{JsonElementExtensions.GetValue(item.Value, true)}"),""");
}
stringBuilder.Remove(stringBuilder.Length - 1, 1);
}
@@ -174,28 +173,6 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
LogMessage?.Trace($"TableName{tableName}Count{result}watchTime: {stopwatch.ElapsedMilliseconds} ms");
}
}
private string GetValue(VariableBasicData src)
{
if (src.Value != null)
{
if (src.Value is string strValue)
{
return strValue;
}
else if (src.Value is bool boolValue)
{
return boolValue ? "1" : "0";
}
else
{
return JToken.FromObject(src.Value).ToString();
}
}
else
{
return string.Empty;
}
}
#endregion
#endif

View File

@@ -18,7 +18,7 @@ public class TDengineDBProducerProperty : RealDBProducerProperty
public TDengineDBProducerProperty()
{
BigTextConnectStr = "protocol=WebSocket;host=localhost;port=6041;useSSL=false;username=root;password=taosdata;db=power";
BigTextConnectStr = "protocol=WebSocket;host=localhost;port=6041;useSSL=false;username=root;password=taosdata;db=power;autoReconnect=true";
}
}

Some files were not shown because too many files have changed in this diff Show More