mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-21 19:14:30 +08:00
Compare commits
20 Commits
10.11.36.0
...
10.11.62.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4a7534b210 | ||
![]() |
58e099cb93 | ||
![]() |
a94a9c953c | ||
![]() |
35e1ffa3e9 | ||
![]() |
4921642151 | ||
![]() |
d71ee29da8 | ||
![]() |
901aa2d59f | ||
![]() |
764957c014 | ||
![]() |
0e3898218b | ||
![]() |
61f13cef3c | ||
![]() |
0b663d9e01 | ||
![]() |
6c95c6209f | ||
![]() |
4d223d2622 | ||
![]() |
e8d7e91b64 | ||
![]() |
8175f541ec | ||
![]() |
0adbdb926b | ||
![]() |
42adee9980 | ||
![]() |
427a7404bc | ||
![]() |
3658199e0a | ||
![]() |
82eedee50a |
@@ -4,9 +4,9 @@
|
||||
|
||||
<div class="tg-table h-100">
|
||||
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!"
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" BeforeShowEditDialogCallback="BeforeShowEditDialogCallback!"
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!"
|
||||
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
|
||||
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
|
||||
ShowEmpty="ShowEmpty" EmptyText="@EmptyText" EmptyImage="@($"{WebsiteConst.DefaultResourceUrl}images/empty.svg")" SortString="@SortString" EditDialogSize="EditDialogSize"
|
||||
@@ -14,7 +14,7 @@
|
||||
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
|
||||
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
|
||||
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
|
||||
ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
|
||||
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
|
||||
FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
|
||||
IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
|
||||
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
|
||||
@@ -29,7 +29,7 @@
|
||||
ShowMultiFilterHeader=ShowMultiFilterHeader
|
||||
ShowFilterHeader=ShowFilterHeader
|
||||
ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
|
||||
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
|
||||
DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
|
||||
DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"
|
||||
|
@@ -13,6 +13,24 @@ namespace ThingsGateway.Admin.Razor;
|
||||
[CascadingTypeParameter(nameof(TItem))]
|
||||
public partial class AdminTable<TItem> where TItem : class, new()
|
||||
{
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
|
||||
[Parameter]
|
||||
public List<TItem> SelectedRows { get; set; } = new();
|
||||
|
||||
private async Task privateSelectedRowsChanged(List<TItem> items)
|
||||
{
|
||||
SelectedRows = items;
|
||||
if (SelectedRowsChanged.HasDelegate)
|
||||
await SelectedRowsChanged.InvokeAsync(items);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DoubleClickToEdit"/>
|
||||
[Parameter]
|
||||
public bool DoubleClickToEdit { get; set; } = false;
|
||||
@@ -210,14 +228,6 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? SearchTemplate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
|
||||
[Parameter]
|
||||
public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
|
||||
[Parameter]
|
||||
public Func<TItem, string?>? SetRowClassFormatter { get; set; }
|
||||
@@ -266,6 +276,15 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public bool ShowExportButton { get; set; } = false;
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableEditButtonCallback"/>
|
||||
public Func<List<TItem>, bool> DisableEditButtonCallback { get; set; } = (list) =>
|
||||
list.Count != 1;
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.DisableDeleteButtonCallback"/>
|
||||
[Parameter]
|
||||
public Func<List<TItem>, bool> DisableDeleteButtonCallback { get; set; } = (list) =>
|
||||
list.Count <= 0;
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.ShowExportCsvButton"/>
|
||||
[Parameter]
|
||||
public bool ShowExportCsvButton { get; set; } = false;
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
34
src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
Normal file
34
src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
Normal 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("已提示过安装,不再弹出");
|
||||
}
|
||||
});
|
@@ -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.0" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.10.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
@@ -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
|
@@ -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>日志文件上限。超过上限后拆分新日志文件,默认5MB,0表示不限制大小</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>
|
||||
|
@@ -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>
|
||||
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
@namespace ThingsGateway.Razor
|
||||
@typeparam TItem
|
||||
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" IsMultipleSelect="IsMultipleSelect" @ref="Instance" TableSize=TableSize SearchTemplate=SearchTemplate
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" IsMultipleSelect="IsMultipleSelect" @ref="Instance" TableSize=TableSize SearchTemplate=SearchTemplate SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText"
|
||||
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
|
||||
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
|
||||
@@ -10,7 +10,7 @@
|
||||
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
|
||||
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton
|
||||
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
|
||||
ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
|
||||
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
|
||||
FixedExtendButtonsColumn IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
|
||||
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
|
||||
AllowResizing=@AllowResizing ExportButtonDropdownTemplate=ExportButtonDropdownTemplate
|
||||
@@ -24,7 +24,7 @@
|
||||
ShowMultiFilterHeader=ShowMultiFilterHeader
|
||||
ShowFilterHeader=ShowFilterHeader
|
||||
ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
|
||||
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
|
||||
ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
|
||||
DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
|
||||
DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"
|
||||
|
@@ -13,6 +13,23 @@ namespace ThingsGateway.Razor;
|
||||
[CascadingTypeParameter(nameof(TItem))]
|
||||
public partial class DefaultTable<TItem> where TItem : class, new()
|
||||
{
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
|
||||
[Parameter]
|
||||
public List<TItem> SelectedRows { get; set; } = new();
|
||||
|
||||
private async Task privateSelectedRowsChanged(List<TItem> items)
|
||||
{
|
||||
SelectedRows = items;
|
||||
if (SelectedRowsChanged.HasDelegate)
|
||||
await SelectedRowsChanged.InvokeAsync(items);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.AllowDragColumn"/>
|
||||
[Parameter]
|
||||
public bool AllowDragColumn { get; set; } = false;
|
||||
@@ -186,14 +203,6 @@ public partial class DefaultTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public RenderFragment<TItem>? SearchTemplate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
|
||||
[Parameter]
|
||||
public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
|
||||
[Parameter]
|
||||
public Func<TItem, string?>? SetRowClassFormatter { get; set; }
|
||||
|
@@ -32,7 +32,7 @@
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<PackageReference Include="TDengine.Connector" Version="3.1.9" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.25" />
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.36</PluginVersion>
|
||||
<ProPluginVersion>10.11.36</ProPluginVersion>
|
||||
<DefaultVersion>10.11.36</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.4</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.4</SourceGeneratorVersion>
|
||||
<PluginVersion>10.11.62</PluginVersion>
|
||||
<ProPluginVersion>10.11.62</ProPluginVersion>
|
||||
<DefaultVersion>10.11.62</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>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 })
|
||||
|
@@ -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;
|
||||
|
@@ -37,6 +37,7 @@ public static class ChannelOptionsExtensions
|
||||
for (int i = 0; i < funcs.Count; i++)
|
||||
{
|
||||
var func = funcs[i];
|
||||
if (func == null) continue;
|
||||
await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
|
||||
if (e.Handled)
|
||||
{
|
||||
@@ -65,6 +66,7 @@ public static class ChannelOptionsExtensions
|
||||
for (int i = 0; i < funcs.Count; i++)
|
||||
{
|
||||
var func = funcs[i];
|
||||
if (func == null) continue;
|
||||
var handled = await func.Invoke(clientChannel, i == funcs.Count - 1).ConfigureAwait(false);
|
||||
if (handled)
|
||||
{
|
||||
@@ -96,19 +98,32 @@ 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:
|
||||
return config.GetTcpClientWithIPHost(channelOptions);
|
||||
return config.GetTcpClient(channelOptions);
|
||||
|
||||
case ChannelTypeEnum.TcpService:
|
||||
return config.GetTcpServiceWithBindIPHost(channelOptions);
|
||||
return config.GetTcpService(channelOptions);
|
||||
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
return config.GetSerialPortWithOption(channelOptions);
|
||||
return config.GetSerialPort(channelOptions);
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
return config.GetUdpSessionWithIPHost(channelOptions);
|
||||
return config.GetUdpSession(channelOptions);
|
||||
case ChannelTypeEnum.Other:
|
||||
channelOptions.Config = config;
|
||||
OtherChannel otherChannel = new OtherChannel(channelOptions);
|
||||
@@ -123,13 +138,12 @@ public static class ChannelOptionsExtensions
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="channelOptions">串口配置</param>
|
||||
/// <returns></returns>
|
||||
public static SerialPortChannel GetSerialPortWithOption(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
{
|
||||
var serialPortOption = channelOptions.Map<SerialPortOption>();
|
||||
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
|
||||
channelOptions.Config = config;
|
||||
config.SetSerialPortOption(serialPortOption);
|
||||
|
||||
//载入配置
|
||||
SerialPortChannel serialPortChannel = new SerialPortChannel(channelOptions);
|
||||
return serialPortChannel;
|
||||
@@ -142,7 +156,7 @@ public static class ChannelOptionsExtensions
|
||||
/// <param name="channelOptions">通道配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static TcpClientChannel GetTcpClientWithIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
private static TcpClientChannel GetTcpClient(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
{
|
||||
var remoteUrl = channelOptions.RemoteUrl;
|
||||
var bindUrl = channelOptions.BindUrl;
|
||||
@@ -164,7 +178,7 @@ public static class ChannelOptionsExtensions
|
||||
/// <param name="channelOptions">通道配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static IChannel GetTcpServiceWithBindIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
private static IChannel GetTcpService(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
{
|
||||
var bindUrl = channelOptions.BindUrl;
|
||||
bindUrl.ThrowIfNull(nameof(bindUrl));
|
||||
@@ -192,7 +206,7 @@ public static class ChannelOptionsExtensions
|
||||
/// <param name="channelOptions">通道配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static UdpSessionChannel GetUdpSessionWithIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
private static UdpSessionChannel GetUdpSession(this TouchSocketConfig config, IChannelOptions channelOptions)
|
||||
{
|
||||
var remoteUrl = channelOptions.RemoteUrl;
|
||||
var bindUrl = channelOptions.BindUrl;
|
||||
|
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
@@ -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>
|
||||
/// 设置数据处理适配器
|
||||
|
@@ -19,6 +19,11 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
{
|
||||
~OtherChannel()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
private SingleStreamDataHandlingAdapter m_dataHandlingAdapter;
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => m_dataHandlingAdapter;
|
||||
|
||||
@@ -31,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);
|
||||
@@ -63,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;
|
||||
@@ -93,7 +98,10 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置数据处理适配器。
|
||||
/// </summary>
|
||||
|
@@ -19,6 +19,10 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
{
|
||||
~SerialPortChannel()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
public SerialPortChannel(IChannelOptions channelOptions)
|
||||
{
|
||||
ChannelOptions = channelOptions;
|
||||
@@ -27,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);
|
||||
@@ -67,6 +71,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
logSet = false;
|
||||
}
|
||||
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
@@ -82,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;
|
||||
|
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public class TcpClientChannel : TcpClient, IClientChannel
|
||||
{
|
||||
~TcpClientChannel()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public TcpClientChannel(IChannelOptions channelOptions)
|
||||
{
|
||||
@@ -24,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);
|
||||
@@ -47,6 +51,10 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
@@ -75,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/>
|
||||
|
@@ -18,6 +18,11 @@ namespace ThingsGateway.Foundation;
|
||||
/// <typeparam name="TClient"></typeparam>
|
||||
public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcpService<TClient> where TClient : TcpSessionClientChannel, new()
|
||||
{
|
||||
|
||||
~TcpServiceChannelBase()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IDevice> Collects { get; } = new();
|
||||
|
||||
@@ -205,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;
|
||||
|
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
{
|
||||
~TcpSessionClientChannel()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public TcpSessionClientChannel()
|
||||
{
|
||||
@@ -39,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);
|
||||
@@ -75,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));
|
||||
|
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
{
|
||||
~UdpSessionChannel()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
private readonly WaitLock _connectLock = new WaitLock(nameof(UdpSessionChannel));
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -36,6 +40,10 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
handleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
@@ -46,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);
|
||||
@@ -85,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;
|
||||
|
@@ -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)
|
||||
{
|
||||
@@ -629,7 +635,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
DataTypeEnum.UInt32 => await ReadUInt32Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await ReadInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await ReadUInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Float => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await ReadDoubleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Decimal => await ReadDecimalAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult<Array>(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||
@@ -655,7 +661,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Float => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||
@@ -674,7 +680,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
DataTypeEnum.UInt32 => await WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Float => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Decimal => await WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||
@@ -864,7 +870,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, float value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Single, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Float, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -937,7 +943,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<float> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Single, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Float, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -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();
|
||||
|
@@ -45,12 +45,16 @@ public enum DataTypeEnum
|
||||
/// <inheritdoc/>
|
||||
UInt64,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Single,
|
||||
/// <summary>
|
||||
/// 大部分人并不认识Single,但都认识Float
|
||||
/// </summary>
|
||||
Float,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Double,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Decimal,
|
||||
|
||||
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ public static class DataTypeExtensions
|
||||
DataTypeEnum.UInt32 => 4,
|
||||
DataTypeEnum.Int64 => 8,
|
||||
DataTypeEnum.UInt64 => 8,
|
||||
DataTypeEnum.Single => 4,
|
||||
DataTypeEnum.Float => 4,
|
||||
DataTypeEnum.Double => 8,
|
||||
DataTypeEnum.Decimal => 16,
|
||||
_ => 0,
|
||||
@@ -57,7 +57,7 @@ public static class DataTypeExtensions
|
||||
TypeCode.UInt32 => DataTypeEnum.UInt32,
|
||||
TypeCode.Int64 => DataTypeEnum.Int64,
|
||||
TypeCode.UInt64 => DataTypeEnum.UInt64,
|
||||
TypeCode.Single => DataTypeEnum.Single,
|
||||
TypeCode.Single => DataTypeEnum.Float,
|
||||
TypeCode.Double => DataTypeEnum.Double,
|
||||
TypeCode.Decimal => DataTypeEnum.Decimal,
|
||||
_ => DataTypeEnum.Object,
|
||||
@@ -82,7 +82,7 @@ public static class DataTypeExtensions
|
||||
DataTypeEnum.UInt32 => typeof(uint),
|
||||
DataTypeEnum.Int64 => typeof(long),
|
||||
DataTypeEnum.UInt64 => typeof(ulong),
|
||||
DataTypeEnum.Single => typeof(float),
|
||||
DataTypeEnum.Float => typeof(float),
|
||||
DataTypeEnum.Double => typeof(double),
|
||||
DataTypeEnum.Decimal => typeof(decimal),
|
||||
_ => typeof(object),
|
||||
|
@@ -12,6 +12,7 @@ using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
|
@@ -11,8 +11,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
|
||||
<PackageReference Include="TouchSocket" Version="4.0.0-beta.25" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.25" />
|
||||
<PackageReference Include="TouchSocket" Version="4.0.0-beta.57" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.57" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -56,7 +56,7 @@ public static class ThingsGatewayBitConverterExtension
|
||||
case DataTypeEnum.UInt64:
|
||||
return byteConverter.GetBytes(value.ToObject<UInt64[]>());
|
||||
|
||||
case DataTypeEnum.Single:
|
||||
case DataTypeEnum.Float:
|
||||
return byteConverter.GetBytes(value.ToObject<Single[]>());
|
||||
|
||||
case DataTypeEnum.Double:
|
||||
@@ -107,7 +107,7 @@ public static class ThingsGatewayBitConverterExtension
|
||||
case DataTypeEnum.UInt64:
|
||||
return byteConverter.GetBytes(value.ToObject<UInt64>());
|
||||
|
||||
case DataTypeEnum.Single:
|
||||
case DataTypeEnum.Float:
|
||||
return byteConverter.GetBytes(value.ToObject<Single>());
|
||||
|
||||
case DataTypeEnum.Double:
|
||||
@@ -333,7 +333,7 @@ public static class ThingsGatewayBitConverterExtension
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Single:
|
||||
case DataTypeEnum.Float:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = byteConverter.ToSingle(buffer, index, arrayLength);
|
||||
|
@@ -106,5 +106,5 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public string? BigTextScriptPluginEventDataModel { get; set; }
|
||||
public virtual string? BigTextScriptPluginEventDataModel { get; set; }
|
||||
}
|
||||
|
@@ -55,7 +55,15 @@ public abstract partial class CollectBase : DriverBase
|
||||
/// 特殊方法
|
||||
/// </summary>
|
||||
public List<DriverMethodInfo>? DriverMethodInfos { get; private set; }
|
||||
|
||||
protected virtual bool IsRuntimeSourceValid(VariableRuntime a)
|
||||
{
|
||||
//筛选特殊变量地址
|
||||
//1、DeviceStatus
|
||||
return !a.RegisterAddress.Equals(nameof(DeviceRuntime.DeviceStatus), StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.RegisterAddress.Equals("Script", StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase)
|
||||
;
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
@@ -84,21 +92,13 @@ public abstract partial class CollectBase : DriverBase
|
||||
&& string.IsNullOrEmpty(it.OtherMethod)
|
||||
&& !string.IsNullOrEmpty(it.RegisterAddress));
|
||||
|
||||
//筛选特殊变量地址
|
||||
//1、DeviceStatus
|
||||
Func<VariableRuntime, bool> source = (a =>
|
||||
{
|
||||
return !a.RegisterAddress.Equals(nameof(DeviceRuntime.DeviceStatus), StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.RegisterAddress.Equals("Script", StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase)
|
||||
;
|
||||
});
|
||||
|
||||
var now = DateTime.Now;
|
||||
#pragma warning disable CA1851
|
||||
try
|
||||
{
|
||||
|
||||
currentDevice.VariableScriptReads = tags.Where(a => !source(a)).Select(a =>
|
||||
currentDevice.VariableScriptReads = tags.Where(a => !IsRuntimeSourceValid(a)).Select(a =>
|
||||
{
|
||||
var data = new VariableScriptRead();
|
||||
data.VariableRuntime = a;
|
||||
@@ -111,9 +111,9 @@ public abstract partial class CollectBase : DriverBase
|
||||
// 如果出现异常,记录日志并初始化 VariableSourceReads 属性为新实例
|
||||
currentDevice.VariableScriptReads = new();
|
||||
LogMessage?.LogWarning(ex, string.Format(AppResource.VariablePackError, ex.Message));
|
||||
tags.Where(a => !source(a)).ForEach(a => a.SetValue(null, now, isOnline: false));
|
||||
tags.Where(a => !IsRuntimeSourceValid(a)).ForEach(a => a.SetValue(null, now, isOnline: false));
|
||||
}
|
||||
var variableReads = tags.Where(source).ToList();
|
||||
var variableReads = tags.Where(IsRuntimeSourceValid).ToList();
|
||||
try
|
||||
{
|
||||
// 将打包后的结果存储在当前设备的 VariableSourceReads 属性中
|
||||
@@ -594,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)
|
||||
{
|
||||
@@ -662,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)
|
||||
{
|
||||
|
@@ -37,6 +37,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
|
||||
|
||||
#region 属性
|
||||
|
||||
public virtual bool RefreshRuntimeAlways { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 调试UI Type,如果不存在,返回null
|
||||
/// </summary>
|
||||
|
@@ -64,26 +64,36 @@ 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));
|
||||
}
|
||||
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?.ContainsKey(businessId) == true)
|
||||
{
|
||||
variableRuntime.VariablePropertys[businessId].TryGetValue(propertyName, out var value);
|
||||
return value; // 返回属性值
|
||||
}
|
||||
else
|
||||
{
|
||||
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
||||
|
@@ -125,9 +125,14 @@ 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)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (businessBase.RefreshRuntimeAlways)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -141,7 +146,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>
|
||||
|
@@ -409,7 +409,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
// 查找具有指定设备ID的驱动程序对象
|
||||
if (Drivers.TryRemove(deviceId, out var driver))
|
||||
{
|
||||
driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed");
|
||||
driver.CurrentDevice.SetDeviceStatus(now, true, "Communication connection has been removed");
|
||||
if (IsCollectChannel == true)
|
||||
{
|
||||
foreach (var a in driver.IdVariableRuntimes)
|
||||
|
@@ -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 =>
|
||||
{
|
||||
|
@@ -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 =>
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -240,6 +240,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 +260,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 +287,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) =>
|
||||
|
@@ -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.25" />
|
||||
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.25" />-->
|
||||
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.25" />
|
||||
<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" />-->
|
||||
|
||||
|
@@ -48,7 +48,7 @@
|
||||
}
|
||||
/*切换高度*/
|
||||
.quickactions-list.is-open {
|
||||
height: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.quickactions-header {
|
||||
|
@@ -76,18 +76,34 @@ public partial class DeviceRuntimeInfo1 : IDisposable
|
||||
{
|
||||
return;
|
||||
}
|
||||
await DialogService.Show(new DialogOption()
|
||||
{
|
||||
IsScrolling = false,
|
||||
ShowMaximizeButton = true,
|
||||
Size = Size.ExtraExtraLarge,
|
||||
Title = DeviceRuntime.Name,
|
||||
Component = BootstrapDynamicComponent.CreateComponent(driver, new Dictionary<string, object?>()
|
||||
var renderFragment = BootstrapDynamicComponent.CreateComponent(driver, new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(IDriverUIBase.Driver),DeviceRuntime.Driver},
|
||||
})
|
||||
});
|
||||
}).Render();
|
||||
if (renderFragment != null)
|
||||
{
|
||||
var option = new WinBoxOption()
|
||||
{
|
||||
Title = DeviceRuntime.Name,
|
||||
ContentTemplate = renderFragment,
|
||||
Max = false,
|
||||
Width = "80%",
|
||||
Height = "80%",
|
||||
Top = "0%",
|
||||
Left = "10%",
|
||||
Background = "var(--bb-primary-color)",
|
||||
Overflow = true
|
||||
};
|
||||
await WinBoxService.Show(option);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private WinBoxService? WinBoxService { get; set; }
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
[Inject]
|
||||
|
@@ -48,6 +48,14 @@
|
||||
@Localizer["Check"]
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-12 col-md-12 min-height-500">
|
||||
<BootstrapLabel Value=@PropertyComponentLocalizer["BigTextScriptPluginEventDataModel"] ShowLabelTooltip="true" />
|
||||
<CodeEditor IsReadonly=@(!CanWrite) ShowLineNo @bind-Value=@businessProperty.BigTextScriptPluginEventDataModel Language="csharp" Theme="vs-dark" />
|
||||
|
||||
</div>
|
||||
|
||||
</EditTemplate>
|
||||
|
@@ -48,8 +48,22 @@ public partial class GatewayMonitorPage
|
||||
}
|
||||
else
|
||||
{
|
||||
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
|
||||
if (deviceRuntime.Driver == null)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
|
||||
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
|
||||
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
|
||||
}
|
||||
|
||||
}
|
||||
ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
|
||||
DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
|
||||
|
@@ -28,15 +28,7 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<VariableRuntime>? Items { get; set; } = Enumerable.Empty<VariableRuntime>();
|
||||
private IEnumerable<VariableRuntime>? _previousItemsRef;
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!ReferenceEquals(_previousItemsRef, Items))
|
||||
{
|
||||
_previousItemsRef = Items;
|
||||
await Refresh(null);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private static void BeforeShowEditDialogCallback(ITableEditDialogOption<VariableRuntime> tableEditDialogOption)
|
||||
|
@@ -13,9 +13,12 @@ using BenchmarkConsoleApp;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
|
||||
using HslCommunication.ModBus;
|
||||
|
||||
using System.IO.Pipelines;
|
||||
using Longbow.Modbus;
|
||||
using Longbow.TcpSocket;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using System.Net.Sockets;
|
||||
|
||||
using ThingsGateway.Foundation.Modbus;
|
||||
@@ -31,17 +34,11 @@ namespace ThingsGateway.Foundation;
|
||||
[MemoryDiagnoser]
|
||||
public class ModbusBenchmark : IDisposable
|
||||
{
|
||||
private readonly List<IModbusClient> _lgbModbusClients = [];
|
||||
private List<ModbusMaster> thingsgatewaymodbuss = new();
|
||||
private List<IModbusMaster> nmodbuss = new();
|
||||
private List<ModbusTcpNet> modbusTcpNets = 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()
|
||||
{
|
||||
@@ -49,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.GetTcpClientWithIPHost(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协议格式
|
||||
@@ -77,15 +70,15 @@ public class ModbusBenchmark : IDisposable
|
||||
nmodbus.ReadHoldingRegistersAsync(1, 0, 100).GetFalseAwaitResult();
|
||||
nmodbuss.Add(nmodbus);
|
||||
}
|
||||
for (int i = 0; i < Program.ClientCount; i++)
|
||||
{
|
||||
ModbusTcpNet modbusTcpNet = new();
|
||||
modbusTcpNet.IpAddress = "127.0.0.1";
|
||||
modbusTcpNet.Port = 502;
|
||||
modbusTcpNet.ConnectServer();
|
||||
modbusTcpNet.ReadAsync("0", 100).GetFalseAwaitResult();
|
||||
modbusTcpNets.Add(modbusTcpNet);
|
||||
}
|
||||
//for (int i = 0; i < Program.ClientCount; i++)
|
||||
//{
|
||||
// ModbusTcpNet modbusTcpNet = new();
|
||||
// modbusTcpNet.IpAddress = "127.0.0.1";
|
||||
// modbusTcpNet.Port = 502;
|
||||
// modbusTcpNet.ConnectServer();
|
||||
// modbusTcpNet.ReadAsync("0", 100).GetFalseAwaitResult();
|
||||
// modbusTcpNets.Add(modbusTcpNet);
|
||||
//}
|
||||
|
||||
for (int i = 0; i < Program.ClientCount; i++)
|
||||
{
|
||||
@@ -96,12 +89,31 @@ public class ModbusBenchmark : IDisposable
|
||||
client.ReadHoldingRegistersAsync(0, 100).GetFalseAwaitResult();
|
||||
modbusTcpMasters.Add(client);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
var sc = new ServiceCollection();
|
||||
sc.AddTcpSocketFactory();
|
||||
sc.AddModbusFactory();
|
||||
|
||||
var provider = sc.BuildServiceProvider();
|
||||
var factory = provider.GetRequiredService<IModbusFactory>();
|
||||
|
||||
for (int i = 0; i < Program.ClientCount; i++)
|
||||
{
|
||||
var client = factory.GetOrCreateTcpMaster();
|
||||
client.ConnectAsync("127.0.0.1", 502).GetAwaiter().GetResult();
|
||||
client.ReadHoldingRegistersAsync(0x01, 0x00, 10).GetAwaiter().GetResult();
|
||||
|
||||
_lgbModbusClients.Add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ThingsGateway()
|
||||
{
|
||||
ModbusAddress addr = new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 };
|
||||
//ModbusAddress addr = new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 };
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var thingsgatewaymodbus in thingsgatewaymodbuss)
|
||||
{
|
||||
@@ -112,11 +124,12 @@ public class ModbusBenchmark : IDisposable
|
||||
{
|
||||
for (int i = 0; i < Program.NumberOfItems; i++)
|
||||
{
|
||||
var result = await thingsgatewaymodbus.ModbusReadAsync(addr);
|
||||
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());
|
||||
}
|
||||
var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Content.Span, EndianType.Little);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -124,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()
|
||||
@@ -138,7 +171,8 @@ 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)
|
||||
{
|
||||
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.ToString());
|
||||
@@ -163,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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -199,9 +233,12 @@ public class ModbusBenchmark : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose());
|
||||
thingsgatewaymodbuss?.ForEach(a => a.SafeDispose());
|
||||
nmodbuss?.ForEach(a => a.SafeDispose());
|
||||
modbusTcpNets?.ForEach(a => a.SafeDispose());
|
||||
//modbusTcpNets?.ForEach(a => a.SafeDispose());
|
||||
_lgbModbusClients?.ForEach(a => a.DisposeAsync().GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
}
|
@@ -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.GetTcpClientWithIPHost(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协议格式
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -41,18 +41,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
|
||||
<PackageReference Include="HslCommunication" Version="12.3.3" />
|
||||
<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="10.11.33" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="10.11.33" />
|
||||
<PackageReference Include="TouchSocket.Modbus" Version="4.0.0-beta.25" />
|
||||
<!--<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>
|
||||
|
@@ -186,7 +186,7 @@ public class Dlt645_2007Send : ISendMessage
|
||||
}
|
||||
|
||||
var lenSpan = writerLenAnchor.Rewind(ref byteBlock, out var length);
|
||||
lenSpan.WriteValue<byte>((byte)(length - 1));//数据域长度
|
||||
lenSpan.WriteValue<byte>((byte)(length));//数据域长度
|
||||
|
||||
int num = 0;
|
||||
for (int index = 0; index < byteBlock.WrittenCount - SendHeadCodeIndex; ++index)
|
||||
|
@@ -52,7 +52,7 @@ public class ModbusRequest
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
public byte Station { get; set; }
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
#endregion Request
|
||||
}
|
||||
|
@@ -69,7 +69,6 @@ public class ModbusRtuSend : ISendMessage
|
||||
else if (wf == 15 || wf == 16)
|
||||
{
|
||||
var data = ModbusAddress.MasterWriteDatas.ArrayExpandToLengthEven().Span;
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)(data.Length + 7), EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode);
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big);
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -12,7 +12,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ public class RealDBProducerProperty : BusinessPropertyWithCacheInterval
|
||||
/// <summary>
|
||||
/// 历史表脚本
|
||||
/// </summary>
|
||||
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public string? BigTextScriptHistoryTable { get; set; }
|
||||
}
|
||||
|
@@ -85,7 +85,6 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
DynamicSQLBase? hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
@@ -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; }
|
||||
}
|
||||
|
@@ -112,7 +112,6 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
@@ -69,13 +69,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; }
|
||||
}
|
||||
|
@@ -30,7 +30,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
|
||||
{
|
||||
|
@@ -102,7 +102,6 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
@@ -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
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Dlt645\ThingsGateway.Foundation.Dlt645.csproj">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj">
|
||||
</ProjectReference>
|
||||
<PackageReference Include="Confluent.Kafka" Version="2.11.1" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -10,8 +10,6 @@
|
||||
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Foundation.Modbus;
|
||||
@@ -114,7 +112,8 @@ public class ModbusSlave : BusinessBase
|
||||
|
||||
try
|
||||
{
|
||||
await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (channel.ChannelType == ChannelTypeEnum.TcpService)
|
||||
await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -184,11 +183,11 @@ public class ModbusSlave : BusinessBase
|
||||
var type = variableRuntime.GetPropertyValue(CurrentDevice.Id, nameof(ModbusSlaveVariableProperty.DataType));
|
||||
if (Enum.TryParse(type, out DataTypeEnum result))
|
||||
{
|
||||
await _plc.WriteJTokenAsync(item.Key, JToken.FromObject(variableRuntime.Value), result, cancellationToken).ConfigureAwait(false);
|
||||
await _plc.WriteJTokenAsync(item.Key, (variableRuntime.Value).GetJTokenFromObj(), result, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _plc.WriteJTokenAsync(item.Key, JToken.FromObject(variableRuntime.Value), variableRuntime.DataType, cancellationToken).ConfigureAwait(false);
|
||||
await _plc.WriteJTokenAsync(item.Key, (variableRuntime.Value).GetJTokenFromObj(), variableRuntime.DataType, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Modbus\ThingsGateway.Foundation.Modbus.csproj">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.Mqtt.MqttClientProperty": {
|
||||
"BigTextScriptPluginEventDataModel": "BigTextScriptPluginEventDataModel",
|
||||
"CAFile_BrowserFile": "CAFile",
|
||||
"ClientCertificateFile_BrowserFile": "ClientCertificateFile",
|
||||
"ClientKeyFile_BrowserFile": "ClientKeyFile",
|
||||
@@ -40,6 +41,7 @@
|
||||
"WebSocketUrl": "WebSocketUrl"
|
||||
},
|
||||
"ThingsGateway.Plugin.Mqtt.MqttServerProperty": {
|
||||
"BigTextScriptPluginEventDataModel": "BigTextScriptPluginEventDataModel",
|
||||
"AnonymousEnable": "AnonymousEnable",
|
||||
"DeviceRpcEnable": "DeviceRpcEnable",
|
||||
"IP": "IP",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ThingsGateway.Plugin.Mqtt.MqttClientProperty": {
|
||||
"BigTextScriptPluginEventDataModel": "插件事件上传脚本",
|
||||
"CAFile_BrowserFile": "CA文件",
|
||||
"ClientCertificateFile_BrowserFile": "客户端证书",
|
||||
"ClientKeyFile_BrowserFile": "客户端key文件",
|
||||
@@ -40,6 +41,7 @@
|
||||
"WebSocketUrl": "WebSocketUrl"
|
||||
},
|
||||
"ThingsGateway.Plugin.Mqtt.MqttServerProperty": {
|
||||
"BigTextScriptPluginEventDataModel": "插件事件上传脚本",
|
||||
"AnonymousEnable": "允许匿名登录",
|
||||
"DeviceRpcEnable": "允许Rpc写入",
|
||||
"IP": "IP",
|
||||
|
@@ -124,4 +124,8 @@ public class MqttClientProperty : BusinessPropertyWithCacheIntervalScript
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public string? BigTextScriptRpc { get; set; }
|
||||
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public override string? BigTextScriptPluginEventDataModel { get; set; }
|
||||
}
|
||||
|
@@ -76,4 +76,8 @@ public class MqttServerProperty : BusinessPropertyWithCacheIntervalScript
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public string? BigTextScriptRpc { get; set; }
|
||||
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public override string? BigTextScriptPluginEventDataModel { get; set; }
|
||||
}
|
||||
|
@@ -27,21 +27,21 @@
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
|
||||
<PackageReference Include="MQTTnet.AspNetCore" Version="4.3.7.1207" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet" Version="4.3.7.1207" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' != 'net6.0' ">
|
||||
<PackageReference Include="MQTTnet.Server" Version="5.0.1.1416" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet.AspNetCore" Version="5.0.1.1416" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet" Version="5.0.1.1416" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -24,7 +24,7 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.OpcDa\ThingsGateway.Foundation.OpcDa.csproj">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -19,6 +19,7 @@ using System.Globalization;
|
||||
|
||||
using ThingsGateway.Foundation.OpcUa;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
using TouchSocket.Core;
|
||||
@@ -300,7 +301,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
{
|
||||
SetDataType(tag, value);
|
||||
}
|
||||
var jToken = JToken.FromObject(value);
|
||||
var jToken = (value).GetJTokenFromObj();
|
||||
var dataValue = JsonUtils.DecoderObject(
|
||||
Server.MessageContext,
|
||||
tag.DataType,
|
||||
|
@@ -26,31 +26,31 @@
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" />
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<!--<PackageReference Include="System.Formats.Asn1" Version="8.0.2" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>-->
|
||||
|
||||
</ItemGroup>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.SiemensS7\ThingsGateway.Foundation.SiemensS7.csproj">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
23
src/ThingsGateway.ScriptDebug/Program.cs
Normal file
23
src/ThingsGateway.ScriptDebug/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Server;
|
||||
|
||||
public class Program
|
||||
{
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
}
|
12
src/ThingsGateway.ScriptDebug/Properties/launchSettings.json
Normal file
12
src/ThingsGateway.ScriptDebug/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ThingsGateway.ScriptDebug": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:60174;http://localhost:60175"
|
||||
}
|
||||
}
|
||||
}
|
52
src/ThingsGateway.ScriptDebug/Test/TestClientRpc.cs
Normal file
52
src/ThingsGateway.ScriptDebug/Test/TestClientRpc.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using MQTTnet;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.Plugin.Mqtt;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
public class TestClientRpc : DynamicMqttClientRpcBase
|
||||
{
|
||||
public override async Task RPCInvokeAsync(ILog logMessage, MqttApplicationMessageReceivedEventArgs args, MqttClientProperty driverPropertys, IMqttClient mqttClient, Func<string, Dictionary<string, Dictionary<string, JToken>>, ValueTask<Dictionary<string, Dictionary<string, IOperResult>>>> getRpcResult, Func<CancellationToken, ValueTask<OperResult>> tryMqttClientAsync, CancellationToken cancellationToken)
|
||||
{
|
||||
if (driverPropertys.RpcWriteTopic.IsNullOrWhiteSpace()) return;
|
||||
var t = string.Format(null, "{0}/+", driverPropertys.RpcWriteTopic);
|
||||
if (MqttTopicFilterComparer.Compare(args.ApplicationMessage.Topic, t) != MqttTopicFilterCompareResult.IsMatch)
|
||||
return;
|
||||
|
||||
var rpcDatas = Encoding.UTF8.GetString(args.ApplicationMessage.Payload).FromJsonNetString<Dictionary<string, Dictionary<string, JToken>>>();
|
||||
if (rpcDatas == null)
|
||||
return;
|
||||
|
||||
var mqttRpcResult = await getRpcResult(args.ClientId, rpcDatas).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var isConnect = await tryMqttClientAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
if (isConnect.IsSuccess)
|
||||
{
|
||||
var variableMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic($"{args.ApplicationMessage.Topic}/Response")
|
||||
.WithPayload(mqttRpcResult.ToSystemTextJsonString(driverPropertys.JsonFormattingIndented)).Build();
|
||||
await mqttClient.PublishAsync(variableMessage, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
263
src/ThingsGateway.ScriptDebug/Test/TestCollectPlugin.cs
Normal file
263
src/ThingsGateway.ScriptDebug/Test/TestCollectPlugin.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Server;
|
||||
|
||||
/// <summary>
|
||||
/// 插件类,默认实现了<see cref="IDevice"/>接口,继承<see cref="CollectFoundationBase"/> 实现采集插件
|
||||
/// </summary>
|
||||
public class TestCollectPlugin : CollectFoundationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 调试UI Type,如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverDebugUIType => base.DriverDebugUIType;
|
||||
/// <summary>
|
||||
/// 插件属性UI Type,继承<see cref="IPropertyUIBase"/>如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverPropertyUIType => base.DriverPropertyUIType;
|
||||
/// <summary>
|
||||
/// 插件UI Type,继承<see cref="IDriverUIBase"/>如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverUIType => base.DriverUIType;
|
||||
/// <summary>
|
||||
/// 插件变量寄存器UI Type,继承<see cref="IAddressUIBase"/>如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverVariableAddressUIType => base.DriverVariableAddressUIType;
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置项,继承<see cref="CollectPropertyBase"/> 返回类实例
|
||||
/// </summary>
|
||||
public override CollectPropertyBase CollectProperties => _property;
|
||||
private TestCollectProperty? _property = new();
|
||||
|
||||
/// <summary>
|
||||
/// 在插件初始化时调用,只会执行一次,参数为插件默认的链路通道类,如未实现可忽略l
|
||||
/// </summary>
|
||||
protected override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
//做一些初始化操作
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量打包操作,会在默认的AfterVariablesChangedAsync方法里执行,参数为设备变量列表,返回源读取变量列表
|
||||
/// </summary>
|
||||
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
|
||||
{
|
||||
//实现将设备变量打包成源读取变量
|
||||
//比如如果需要实现MC中的字多读功能,需将多个变量地址打包成一个源读取地址和读取长度,根据一系列规则,添加解析标识,然后在返回的整个字节数组中解析出原来的变量地址代表的数据字节
|
||||
|
||||
//一般可操作 VariableRuntime 类中的 index, thingsgatewaybitconvter 等属性
|
||||
//一般可操作 VariableSourceRead 类中的 address, length 等属性
|
||||
|
||||
return Task.FromResult(new List<VariableSourceRead>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实现<see cref="IDevice"/>
|
||||
/// </summary>
|
||||
public override IDevice? FoundationDevice => base.FoundationDevice;
|
||||
|
||||
/// <summary>
|
||||
/// 特殊方法,添加<see cref="DynamicMethodAttribute"/> 特性 ,返回IOperResult
|
||||
/// 支持<see cref="CancellationToken"/> 参数,需放到最后
|
||||
/// 默认解析方式为英文分号
|
||||
/// 比如rpc参数为 test1;test2,解析query1="test1",query2="test2"
|
||||
/// 也可以在变量地址中填入test1,rpc参数传入test2,解析query1="test1",query2="test2"
|
||||
/// </summary>
|
||||
[DynamicMethod("测试特殊方法")]
|
||||
public IOperResult<string> TestMethod(string query1, string query2, CancellationToken cancellationToken)
|
||||
{
|
||||
return new OperResult<string>() { Content = "测试特殊方法" };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件类配置
|
||||
/// </summary>
|
||||
public class TestCollectProperty : CollectFoundationPackPropertyBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加<see cref="DynamicPropertyAttribute"/> 特性,如需多语言配置,可添加json资源,参考其他插件
|
||||
/// </summary>
|
||||
[DynamicProperty(Description = null, Remark = null)]
|
||||
public string TestString { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件类,完全自定义
|
||||
/// </summary>
|
||||
public class TestCollectPlugin1 : CollectBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 调试UI Type,如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverDebugUIType => base.DriverDebugUIType;
|
||||
/// <summary>
|
||||
/// 插件属性UI Type,继承<see cref="IPropertyUIBase"/>如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverPropertyUIType => base.DriverPropertyUIType;
|
||||
/// <summary>
|
||||
/// 插件UI Type,继承<see cref="IDriverUIBase"/>如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverUIType => base.DriverUIType;
|
||||
/// <summary>
|
||||
/// 插件变量寄存器UI Type,继承<see cref="IAddressUIBase"/>如果不存在无需重写
|
||||
/// </summary>
|
||||
public override Type DriverVariableAddressUIType => base.DriverVariableAddressUIType;
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置项,继承<see cref="CollectPropertyBase"/> 返回类实例
|
||||
/// </summary>
|
||||
public override CollectPropertyBase CollectProperties => _property;
|
||||
private TestCollectProperty1? _property = new();
|
||||
|
||||
/// <summary>
|
||||
/// 在插件初始化时调用,只会执行一次,参数为插件默认的链路通道类,如未实现可忽略l
|
||||
/// </summary>
|
||||
protected override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
//做一些初始化操作
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量打包操作,会在默认的AfterVariablesChangedAsync方法里执行,参数为设备变量列表,返回源读取变量列表
|
||||
/// </summary>
|
||||
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
|
||||
{
|
||||
//实现将设备变量打包成源读取变量
|
||||
//比如如果需要实现MC中的字多读功能,需将多个变量地址打包成一个源读取地址和读取长度,根据一系列规则,添加解析标识,然后在返回的整个字节数组中解析出原来的变量地址代表的数据字节
|
||||
|
||||
//一般可操作 VariableRuntime 类中的 index, thingsgatewaybitconvter 等属性
|
||||
//一般可操作 VariableSourceRead 类中的 address, length 等属性
|
||||
|
||||
return Task.FromResult(new List<VariableSourceRead>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果不实现ReadSourceAsync方法,可以返回flase
|
||||
/// </summary>
|
||||
protected override bool VariableSourceReadsEnable => base.VariableSourceReadsEnable;
|
||||
|
||||
/// <summary>
|
||||
/// 获取任务列表,默认会实现 TestOnline任务,SetDeviceStatus任务以及 VariableSourceRead等任务,VariableSourceRead任务启用条件为<see cref="VariableSourceReadsEnable"/> 为true。任务即是timer实现,可通过<see cref="ScheduledTaskHelper.GetTask(string, Func{object?, CancellationToken, Task}, object?, TouchSocket.Core.ILog, CancellationToken)"/> 方法实现定时任务
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
|
||||
{
|
||||
return base.ProtectedGetTasks(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实现离线重连任务
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override Task TestOnline(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
return base.TestOnline(state, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回是否成功连接设备
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool IsConnected()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在变量发生组态变化后执行,默认会执行<see cref="ProtectedLoadSourceReadAsync"/> 方法,重新获取源读取变量列表,并且重新启动VariableSourceRead等任务
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public override Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return base.AfterVariablesChangedAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量寄存器的字符串描述
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备暂停时执行,默认会暂停所有任务
|
||||
/// </summary>
|
||||
/// <param name="pause"></param>
|
||||
public override void PauseThread(bool pause)
|
||||
{
|
||||
base.PauseThread(pause);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始前执行
|
||||
/// </summary>
|
||||
protected override Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
//一般实现PLC长连接
|
||||
return base.ProtectedStartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入变量,实现设备写入操作,注意执行写锁, using var writeLock =await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
/// </summary>
|
||||
protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return base.WriteValuesAsync(writeInfoLists, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取源变量,在<see cref="VariableSourceReadsEnable"/> 为true时,添加源读取任务,任务启动时会执行
|
||||
/// 一般需要更新设备变量值,调用<see cref="VariableRuntime.SetValue(object?, DateTime, bool)"/>
|
||||
/// </summary>
|
||||
protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
{
|
||||
return base.ReadSourceAsync(variableSourceRead, cancellationToken);
|
||||
}
|
||||
|
||||
protected override Task DisposeAsync(bool disposing)
|
||||
{
|
||||
return base.DisposeAsync(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 特殊方法,添加<see cref="DynamicMethodAttribute"/> 特性 ,返回IOperResult
|
||||
/// 支持<see cref="CancellationToken"/> 参数,需放到最后
|
||||
/// 默认解析方式为英文分号
|
||||
/// 比如rpc参数为 test1;test2,解析query1="test1",query2="test2"
|
||||
/// 也可以在变量地址中填入test1,rpc参数传入test2,解析query1="test1",query2="test2"
|
||||
/// </summary>
|
||||
[DynamicMethod("测试特殊方法")]
|
||||
public IOperResult<string> TestMethod(string query1, string query2, CancellationToken cancellationToken)
|
||||
{
|
||||
return new OperResult<string>() { Content = "测试特殊方法" };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件类配置
|
||||
/// </summary>
|
||||
public class TestCollectProperty1 : CollectPropertyBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加<see cref="DynamicPropertyAttribute"/> 特性,如需多语言配置,可添加json资源,参考其他插件
|
||||
/// </summary>
|
||||
[DynamicProperty(Description = null, Remark = null)]
|
||||
public string TestString { get; set; }
|
||||
|
||||
}
|
56
src/ThingsGateway.ScriptDebug/Test/TestDynamicModel.cs
Normal file
56
src/ThingsGateway.ScriptDebug/Test/TestDynamicModel.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
////------------------------------------------------------------------------------
|
||||
////此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
//// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
//// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
//// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
//// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
//// 使用文档:https://thingsgateway.cn/
|
||||
//// QQ群:605534569
|
||||
//// ------------------------------------------------------------------------------
|
||||
|
||||
using System.Dynamic;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
public class TestDynamicModel : IDynamicModel
|
||||
{
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
if (datas == null) return null;
|
||||
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
|
||||
//按设备名称分组
|
||||
var groups = datas.Where(a => !string.IsNullOrEmpty(((AlarmVariable)a).DeviceName)).GroupBy(a => ((AlarmVariable)a).DeviceName, a => ((AlarmVariable)a));
|
||||
foreach (var group in groups)
|
||||
{
|
||||
//按采集时间分组
|
||||
var data = group.GroupBy(a => a.AlarmTime.DateTimeToUnixTimestamp());
|
||||
var deviceObj = new ExpandoObject();
|
||||
List<ExpandoObject> expandos = new List<ExpandoObject>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var expando = new ExpandoObject();
|
||||
expando.TryAdd("ts", item.Key);
|
||||
var variableObj = new ExpandoObject();
|
||||
foreach (var tag in item)
|
||||
{
|
||||
var alarmObj = new ExpandoObject();
|
||||
alarmObj.TryAdd(nameof(tag.AlarmCode), tag.AlarmCode);
|
||||
alarmObj.TryAdd(nameof(tag.AlarmText), tag.AlarmText);
|
||||
alarmObj.TryAdd(nameof(tag.AlarmType), tag.AlarmType);
|
||||
alarmObj.TryAdd(nameof(tag.AlarmLimit), tag.AlarmLimit);
|
||||
alarmObj.TryAdd(nameof(tag.EventTime), tag.EventTime);
|
||||
alarmObj.TryAdd(nameof(tag.EventType), tag.EventType);
|
||||
|
||||
variableObj.TryAdd(tag.Name, alarmObj);
|
||||
}
|
||||
expando.TryAdd("alarms", variableObj);
|
||||
|
||||
expandos.Add(expando);
|
||||
}
|
||||
deviceObj.TryAdd(group.Key, expandos);
|
||||
deviceObjs.Add(deviceObj);
|
||||
}
|
||||
|
||||
return deviceObjs;
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
|
||||
public class TestExexcuteExpressions : IExexcuteExpressions
|
||||
{
|
||||
|
||||
public TouchSocket.Core.ILog Logger { get; set; }
|
||||
|
||||
public async System.Threading.Tasks.Task<NodeOutput> ExecuteAsync(NodeInput input, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
//想上传mqtt,可以自己写mqtt上传代码,或者通过mqtt插件的公开方法上传
|
||||
|
||||
//直接获取mqttclient插件类型的第一个设备
|
||||
//var mqttClient = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver as ThingsGateway.Plugin.Mqtt.MqttClient;
|
||||
//if (mqttClient == null)
|
||||
// throw new("mqttClient NOT FOUND");
|
||||
|
||||
//TopicArray topicArray = new()
|
||||
//{
|
||||
// Topic = "test",
|
||||
// Payload = Encoding.UTF8.GetBytes("test")
|
||||
//};
|
||||
//var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载
|
||||
//if (!result.IsSuccess)
|
||||
// throw new(result.ErrorMessage);
|
||||
//return new NodeOutput() { Value = result };
|
||||
|
||||
//通过设备名称找出mqttClient插件
|
||||
var mqttClient = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver as ThingsGateway.Plugin.Mqtt.MqttClient;
|
||||
|
||||
if (mqttClient == null)
|
||||
throw new("mqttClient NOT FOUND");
|
||||
|
||||
TopicArray topicArray = new()
|
||||
{
|
||||
Topic = "test",
|
||||
Payload = Encoding.UTF8.GetBytes("test")
|
||||
};
|
||||
var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载
|
||||
if (!result.IsSuccess)
|
||||
throw new(result.ErrorMessage);
|
||||
return new NodeOutput() { Value = result };
|
||||
}
|
||||
}
|
235
src/ThingsGateway.ScriptDebug/Test/TestKafkaDynamicModel.cs
Normal file
235
src/ThingsGateway.ScriptDebug/Test/TestKafkaDynamicModel.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
public class TestKafkaDynamicModel1 : DynamicModelBase
|
||||
{
|
||||
private Dictionary<string, VariableRuntime> variableRuntimes = new();
|
||||
|
||||
private long id = 0;
|
||||
|
||||
public TestKafkaDynamicModel1()
|
||||
{
|
||||
var name = "测试MqttServer";
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(name, out var kafka1))
|
||||
{
|
||||
id = kafka1.Id;
|
||||
|
||||
foreach (var item in kafka1.Driver?.IdVariableRuntimes)
|
||||
{
|
||||
//变量备注1作为Key(AE报警SourceId)
|
||||
var data1 = item.Value.GetPropertyValue(id, nameof(BusinessVariableProperty.Data1));
|
||||
if (!data1.IsNullOrEmpty())
|
||||
{
|
||||
variableRuntimes.Add(data1, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"找不到设备 {name}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<Tuple<string, string, int>, DateTime> EventKeyTimes = new();
|
||||
|
||||
public override IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
if (datas == null) return null;
|
||||
var pluginEventDatas = datas.Cast<PluginEventData>();
|
||||
var opcDatas = pluginEventDatas.Select(
|
||||
a =>
|
||||
{
|
||||
if (a.ObjectValue == null)
|
||||
{
|
||||
a.ObjectValue = a.Value.ToObject(Type.GetType(a.ValueType));
|
||||
}
|
||||
return a.ObjectValue is ThingsGateway.Plugin.OpcAe.OpcAeEventData opcData ? opcData : null;
|
||||
}
|
||||
).Where(a => a != null).ToList();
|
||||
|
||||
List<KafkaAlarmEntity> alarmEntities = new List<KafkaAlarmEntity>();
|
||||
if (opcDatas.Count == 0)
|
||||
{
|
||||
Logger?.LogInformation("没有OPCAE数据");
|
||||
return alarmEntities;
|
||||
}
|
||||
|
||||
|
||||
foreach (var opcAeEventData in opcDatas)
|
||||
{
|
||||
//一般只需要条件报警
|
||||
//if (opcAeEventData.EventType != Opc.Ae.EventType.Condition)
|
||||
// continue;
|
||||
//重连时触发的事件,可以跳过不处理
|
||||
//if(opcAeEventData.Refresh)
|
||||
// continue;
|
||||
var sourceName = opcAeEventData.SourceID;
|
||||
if (variableRuntimes.TryGetValue(sourceName, out var variableRuntime))
|
||||
{
|
||||
var ack = opcAeEventData.EventType != Opc.Ae.EventType.Condition ? false : ((Opc.Ae.ConditionState)opcAeEventData.NewState).HasFlag(Opc.Ae.ConditionState.Acknowledged);
|
||||
|
||||
bool isRecover = opcAeEventData.EventType != Opc.Ae.EventType.Condition ? false : !((Opc.Ae.ConditionState)opcAeEventData.NewState).HasFlag(Opc.Ae.ConditionState.Active);
|
||||
if (opcAeEventData.EventType != Opc.Ae.EventType.Condition)
|
||||
{
|
||||
bool alarm = (opcAeEventData.Message).Contains("raised");
|
||||
if (alarm)
|
||||
{
|
||||
opcAeEventData.AlarmTime = opcAeEventData.Time;
|
||||
EventKeyTimes.AddOrUpdate(Tuple.Create(opcAeEventData.SourceID, opcAeEventData.ConditionName, opcAeEventData.Cookie), opcAeEventData.Time, (k, v) => opcAeEventData.Time);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (EventKeyTimes.TryGetValue(Tuple.Create(opcAeEventData.SourceID, opcAeEventData.ConditionName, opcAeEventData.Cookie), out var time))
|
||||
opcAeEventData.AlarmTime = time;
|
||||
else
|
||||
opcAeEventData.AlarmTime = opcAeEventData.Time;
|
||||
}
|
||||
isRecover = !alarm;
|
||||
}
|
||||
|
||||
//构建告警实体
|
||||
KafkaAlarmEntity alarmEntity = new KafkaAlarmEntity
|
||||
{
|
||||
alarmCode = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data2)), //唯一编码
|
||||
resourceCode = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data3)), //资源编码
|
||||
resourceName = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data4)), //资源名称
|
||||
metricCode = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data5)), //指标编码
|
||||
metricName = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data6)), //指标名称
|
||||
content = $"{variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data4))}{variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data6))}{opcAeEventData.Message}", //告警内容,设备名称+告警内容(包含阈值信息),可能opcae里没有带阈值信息,那么就需要录入固定值,可选Data10
|
||||
alarmType = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data7)), // opcAeEventData.Severity 告警类型,子系统产生告警的类型,可能需要固定备注值
|
||||
|
||||
confirmedTime = ack ? opcAeEventData.Time.DateTimeToUnixTimestamp() : null, //告警确认时间
|
||||
fixTime = isRecover ? opcAeEventData.Time : null, //解除告警时间
|
||||
lastTime = opcAeEventData.AlarmTime, //产生告警时间
|
||||
status = isRecover ? "FIXED" : "UNFIXED", //告警状态
|
||||
alarmLevel = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data8)), //opcAeEventData.Severity.ToString(), //告警等级,可能需要固定备注值
|
||||
subSystemCode = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data9)), //子系统编码
|
||||
type = "SUB_SYSTEM_ALARM", //默认填写字段
|
||||
confirmAccount = opcAeEventData.ActorID, //告警确认人
|
||||
clearAccount = opcAeEventData.ActorID, //告警清除人
|
||||
processInstruction = null //告警处理说明,OPCAE不带有
|
||||
};
|
||||
alarmEntities.Add(alarmEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger?.LogInformation($"找不到相关变量{sourceName}");
|
||||
}
|
||||
}
|
||||
|
||||
return alarmEntities;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 告警实体
|
||||
/// </summary>
|
||||
public class KafkaAlarmEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 告警编码唯一 (非空)
|
||||
/// 示例:"8e8a118ac452fd04da8c26fa588a7cab"
|
||||
/// </summary>
|
||||
public string alarmCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源编码,唯一编码,需要按照映射表上传 (非空)
|
||||
/// 示例:"RS_A6K9MUSG19V"
|
||||
/// </summary>
|
||||
public string resourceCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源名称,需要按照映射表上传 (非空)
|
||||
/// 示例:"MB-A7"
|
||||
/// </summary>
|
||||
public string resourceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 指标编码唯一,需要按照映射表上传 (非空)
|
||||
/// 示例:"ActivePowerPa"
|
||||
/// </summary>
|
||||
public string metricCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 指标名称,需要按照映射表上传 (非空)
|
||||
/// 示例:"有功功率Pa"
|
||||
/// </summary>
|
||||
public string metricName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警内容:设备名称+告警内容(包含阈值信息) (非空)
|
||||
/// 示例:"MB-A7,有功功率Pa > 30"
|
||||
/// </summary>
|
||||
public string content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警类型,子系统产生告警的类型 (非空)
|
||||
/// 示例:"0101" 表示高限报警
|
||||
/// </summary>
|
||||
public string alarmType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警确认时间 (可空,时间戳)
|
||||
/// 示例:1586152800000
|
||||
/// </summary>
|
||||
public long? confirmedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解除告警时间 (可空,时间戳)
|
||||
/// 示例:1586152800000
|
||||
/// </summary>
|
||||
public DateTime? fixTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产生告警时间 (非空,时间戳)
|
||||
/// 示例:1586152800000
|
||||
/// </summary>
|
||||
public DateTime lastTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警状态 (非空)
|
||||
/// 可选值:UNFIXED(新增告警)、FIXED(解除告警)
|
||||
/// </summary>
|
||||
public string status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警等级,需要按照映射表上传 (非空)
|
||||
/// 示例:"1"
|
||||
/// </summary>
|
||||
public string alarmLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子系统编码 (非空)
|
||||
/// 示例:"MS_NEW_PD_DCIM_001"
|
||||
/// </summary>
|
||||
public string subSystemCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认填写字段 (非空)
|
||||
/// 固定值:"SUB_SYSTEM_ALARM"
|
||||
/// </summary>
|
||||
public string type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警确认人 (可空)
|
||||
/// 示例:"admin3"
|
||||
/// </summary>
|
||||
public string confirmAccount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警清除人 (可空)
|
||||
/// 示例:"admin"
|
||||
/// </summary>
|
||||
public string clearAccount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 告警处理说明 (可空)
|
||||
/// 示例:"admin"
|
||||
/// </summary>
|
||||
public string processInstruction { get; set; }
|
||||
}
|
46
src/ThingsGateway.ScriptDebug/Test/TestSQL.cs
Normal file
46
src/ThingsGateway.ScriptDebug/Test/TestSQL.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.Plugin.DB;
|
||||
using ThingsGateway.SqlSugar;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
public class TestSQL : DynamicSQLBase
|
||||
{
|
||||
public override Task DBInit(ISqlSugarClient db, CancellationToken cancellationToken)
|
||||
{
|
||||
db.DbMaintenance.CreateDatabase();
|
||||
db.CodeFirst.InitTables<ThingsGateway.Plugin.OpcAe.OpcAeEventData>();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task DBInsertable(ISqlSugarClient db, IEnumerable<object> datas, CancellationToken cancellationToken)
|
||||
{
|
||||
var pluginEventDatas = datas.Cast<PluginEventData>();
|
||||
var opcDatas = pluginEventDatas.Select(
|
||||
a =>
|
||||
{
|
||||
if (a.ObjectValue == null)
|
||||
{
|
||||
a.ObjectValue = a.Value.ToObject(Type.GetType(a.ValueType));
|
||||
}
|
||||
return a.ObjectValue is ThingsGateway.Plugin.OpcAe.OpcAeEventData opcData ? opcData : null;
|
||||
}
|
||||
).Where(a => a != null).ToList();
|
||||
if (opcDatas.Count == 0)
|
||||
return;
|
||||
Logger?.Info(opcDatas.ToSystemTextJsonString());
|
||||
|
||||
await db.Insertable(opcDatas).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
49
src/ThingsGateway.ScriptDebug/Test/TestServerRpc.cs
Normal file
49
src/ThingsGateway.ScriptDebug/Test/TestServerRpc.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using MQTTnet;
|
||||
using MQTTnet.Server;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.Plugin.Mqtt;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
public class TestServerRpc : DynamicMqttServerRpcBase
|
||||
{
|
||||
public override async Task RPCInvokeAsync(ILog logMessage, InterceptingPublishEventArgs args, MqttServerProperty driverPropertys, MQTTnet.Server.MqttServer mqttServer, Func<string, Dictionary<string, Dictionary<string, JToken>>, ValueTask<Dictionary<string, Dictionary<string, IOperResult>>>> getRpcResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (driverPropertys.RpcWriteTopic.IsNullOrWhiteSpace()) return;
|
||||
|
||||
var t = string.Format(null, "{0}/+", driverPropertys.RpcWriteTopic);
|
||||
if (MqttTopicFilterComparer.Compare(args.ApplicationMessage.Topic, t) != MqttTopicFilterCompareResult.IsMatch)
|
||||
return;
|
||||
var rpcDatas = Encoding.UTF8.GetString(args.ApplicationMessage.Payload).FromJsonNetString<Dictionary<string, Dictionary<string, JToken>>>();
|
||||
if (rpcDatas == null)
|
||||
return;
|
||||
var mqttRpcResult = await getRpcResult(args.ClientId, rpcDatas).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var variableMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic($"{args.ApplicationMessage.Topic}/Response")
|
||||
.WithPayload(mqttRpcResult.ToSystemTextJsonString(driverPropertys.JsonFormattingIndented)).Build();
|
||||
await mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(variableMessage), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Import Project="..\Version.props" />
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Gateway\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
|
||||
<ProjectReference Include="..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--直接引用-->
|
||||
<Import Project="targets\PluginDebug.targets"/>
|
||||
|
||||
|
||||
<Import Project="targets\ProPluginDebug.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' "/>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
|
||||
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
|
||||
<CustomTargetFramework>$(TargetFramework)</CustomTargetFramework>
|
||||
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
||||
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<TieredCompilation>false</TieredCompilation>-->
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="favicon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
|
||||
"IgnoreConfigurationFiles": [ "" ],
|
||||
"ExternalAssemblies": [ "" ],
|
||||
"DetailedErrors": true
|
||||
}
|
9
src/ThingsGateway.ScriptDebug/appsettings.json
Normal file
9
src/ThingsGateway.ScriptDebug/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"urls": "http://*:5000",
|
||||
|
||||
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
|
||||
"IgnoreConfigurationFiles": [ "" ],
|
||||
"ExternalAssemblies": [ "" ],
|
||||
"DetailedErrors": true
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user