Compare commits

..

19 Commits

Author SHA1 Message Date
Diego
525ec740b5 添加脚本demo 2025-05-06 11:43:56 +08:00
2248356998 qq.com
b790cf5f4e 更新依赖 2025-05-05 20:25:43 +08:00
Diego
d1248811fd build: 10.5.11 2025-04-30 23:05:36 +08:00
2248356998 qq.com
022d016e8e feat: 添加采集组 2025-04-30 23:04:51 +08:00
Diego
f73245e650 build: 10.5.10 2025-04-30 15:31:39 +08:00
2248356998 qq.com
484461fa05 更新依赖 2025-04-30 15:29:19 +08:00
Diego
7e0b7aff2a feat: sqldb支持数组 2025-04-28 15:52:32 +08:00
Diego
6c450dcb09 feat: sqldb支持数组类型 2025-04-28 15:52:11 +08:00
Diego
227f44283f build: 10.5.8 2025-04-28 15:30:10 +08:00
Diego
74f6e79625 build: 10.5.7
优化opcua变量缓存
修复cron表达式间隔
2025-04-27 16:35:58 +08:00
Diego
cec43e2ce8 feat: 防呆设计,强制设置通道的最大并发数 2025-04-27 10:13:35 +08:00
Diego
7553b258bb build: 10.5.5 2025-04-26 17:46:31 +08:00
Diego
8bdbdc117e 支持bind链路设置多个通道 2025-04-26 17:10:46 +08:00
Diego
0e206be296 更新依赖 2025-04-26 16:17:02 +08:00
Diego
00b7353433 兼容性增强 2025-04-26 15:53:20 +08:00
Diego
44e7a83593 更新依赖 2025-04-26 15:02:57 +08:00
Diego
dd68d555d4 feat: 支持字节数组上传 2025-04-24 10:25:36 +08:00
2248356998 qq.com
0456296103 更新依赖包 2025-04-23 23:07:27 +08:00
Diego
a1b66277ff nuget 2025-04-23 15:28:53 +08:00
93 changed files with 3163 additions and 2560 deletions

View File

@@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using System.Net;
using System.Security.Claims;
using UAParser;

View File

@@ -11,7 +11,6 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using System.Security.Claims;
using UAParser;

View File

@@ -9,7 +9,6 @@
//------------------------------------------------------------------------------
using System.Net;
using System.Security.Claims;
using UAParser;

View File

@@ -18,10 +18,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.3" />
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.4" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.189" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.191" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />

View File

@@ -32,7 +32,7 @@ public class TimeTick
/// <summary>
/// 上次触发时间
/// </summary>
public DateTime LastTime { get; private set; } = DateTime.Now;
public DateTime LastTime { get; private set; } = DateTime.UtcNow;
/// <summary>
/// 是否触发时间刻度
@@ -62,7 +62,7 @@ public class TimeTick
return result;
}
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = true)
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = false)
{
// 在没有 Cron 表达式的情况下,使用固定间隔
if (cron == null)
@@ -86,7 +86,7 @@ public class TimeTick
}
public DateTime GetNextTime(bool setLastTime = true) => GetNextTime(DateTime.UtcNow, setLastTime);
public DateTime GetNextTime(bool setLastTime = false) => GetNextTime(DateTime.UtcNow, setLastTime);
/// <summary>
/// 是否到达设置的时间间隔

View File

@@ -22,12 +22,34 @@ public static class JSRuntimeExtensions
/// 获取文化信息
/// </summary>
/// <param name="jsRuntime"></param>
public static ValueTask<string> GetCulture(this IJSRuntime jsRuntime) => jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
public static async ValueTask<string> GetCulture(this IJSRuntime jsRuntime)
{
try
{
return await jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
}
catch
{
return null;
}
}
/// <summary>
/// 设置文化信息
/// </summary>
/// <param name="jsRuntime"></param>
/// <param name="cultureName"></param>
public static ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName) => jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
public static async ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName)
{
try
{
await jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
}
catch
{
}
}
}

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor" Version="9.5.9" />
<PackageReference Include="BootstrapBlazor" Version="9.6.0" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.5.2</PluginVersion>
<ProPluginVersion>10.5.2</ProPluginVersion>
<PluginVersion>10.5.13</PluginVersion>
<ProPluginVersion>10.5.13</ProPluginVersion>
<AuthenticationVersion>2.1.7</AuthenticationVersion>
</PropertyGroup>

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.9.5" />
<PackageReference Include="CS-Script" Version="4.9.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -94,11 +94,11 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
return WaitLocks.GetOrAdd(key, (a) => new WaitLock(WaitLock.MaxCount));
}
public override Task StopAsync()
public override Task<Result> StopAsync(CancellationToken token)
{
WaitLocks.ForEach(a => a.Value.SafeDispose());
WaitLocks.Clear();
return base.StopAsync();
return base.StopAsync(token);
}
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();

View File

@@ -128,17 +128,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
public Protocol Protocol => new Protocol("Other");
public DateTime LastReceivedTime { get; private set; }
public DateTimeOffset LastReceivedTime { get; private set; }
public DateTime LastSentTime { get; private set; }
public DateTimeOffset LastSentTime { get; private set; }
public bool IsClient => true;
public bool Online => true;
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return Task.CompletedTask;
return Task.FromResult(Result.Success);
}
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)

View File

@@ -80,7 +80,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
if (HeartbeatByte.SequenceEqual(e.ByteBlock.AsSegment(0, len)))
{
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}

View File

@@ -81,7 +81,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
try
{
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
if (DateTimeOffset.Now - tcpClient.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}

View File

@@ -76,7 +76,7 @@ public static class PluginUtil
.SetOnClose(async (c, t) =>
{
await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
c.SafeClose($"{channelOptions.CheckClearTime}ms Timeout");
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
});
};

View File

@@ -74,8 +74,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
{
if (Online)
{
try
@@ -83,11 +84,12 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
return result;
}
}
finally
@@ -95,6 +97,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//_connectLock.Release();
}
}
return Result.Success;
}
/// <inheritdoc/>

View File

@@ -72,7 +72,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
{
if (Online)
{
@@ -81,11 +81,12 @@ public class TcpClientChannel : TcpClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
return result;
}
}
finally
@@ -93,6 +94,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//_connectLock.Release();
}
}
return Result.Success;
}
/// <inheritdoc/>

View File

@@ -94,22 +94,22 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
}
/// <inheritdoc/>
public override async Task StopAsync()
public override async Task<Result> StopAsync(CancellationToken token)
{
if (Monitors.Any())
{
try
{
await _connectLock.WaitAsync().ConfigureAwait(false);
await _connectLock.WaitAsync(token).ConfigureAwait(false);
if (Monitors.Any())
{
await ClearAsync().ConfigureAwait(false);
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
if (!Monitors.Any())
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
return result;
}
}
finally
@@ -120,8 +120,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
return Result.Success; ;
}
@@ -192,9 +194,9 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
public ChannelEventHandler Stoping { get; set; } = new();
/// <inheritdoc/>
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return StopAsync();
return StopAsync(token);
}
/// <inheritdoc/>

View File

@@ -64,10 +64,10 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public override Task CloseAsync(string msg)
public override Task<Result> CloseAsync(string msg, CancellationToken token)
{
WaitHandlePool.SafeDispose();
return base.CloseAsync(msg);
return base.CloseAsync(msg, token);
}
/// <inheritdoc/>

View File

@@ -73,9 +73,9 @@ public class UdpSessionChannel : UdpSession, IClientChannel
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return StopAsync();
return StopAsync(token);
}
/// <inheritdoc/>
@@ -127,26 +127,28 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
/// <inheritdoc/>
public override async Task StopAsync()
public override async Task<Result> StopAsync(CancellationToken token)
{
if (Monitor != null)
{
try
{
await _connectLock.WaitAsync().ConfigureAwait(false);
await _connectLock.WaitAsync(token).ConfigureAwait(false);
if (Monitor != null)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
if (Monitor == null)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
}
return result;
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
}
finally
@@ -156,7 +158,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
}

View File

@@ -46,9 +46,9 @@ public abstract class DeviceBase : DisposableObject, IDevice
return;
if (channel.Collects.Count > 0)
{
var device = channel.Collects.First();
if (device.GetType() != GetType())
throw new InvalidOperationException("The channel already exists in the device of another type");
//var device = channel.Collects.First();
//if (device.GetType() != GetType())
// throw new InvalidOperationException("The channel already exists in the device of another type");
if (!SupportMultipleDevice())
throw new InvalidOperationException("The proactive response device does not support multiple devices");
@@ -97,9 +97,17 @@ public abstract class DeviceBase : DisposableObject, IDevice
Channel.Stoping.Add(ChannelStoping);
Channel.Started.Add(ChannelStarted);
Channel.ChannelReceived.Add(ChannelReceived);
SetChannel();
}
}
protected virtual void SetChannel()
{
Channel.ChannelOptions.MaxConcurrentCount = 1;
}
/// <inheritdoc/>
~DeviceBase()
{
@@ -221,6 +229,14 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
else
{
if (Channel?.Collects?.Count > 1)
{
var dataHandlingAdapter = GetDataAdapter();
if (adapter.GetType() != dataHandlingAdapter.GetType())
{
clientChannel.SetDataHandlingAdapter(dataHandlingAdapter);
}
}
}
}
@@ -546,6 +562,8 @@ public abstract class DeviceBase : DisposableObject, IDevice
finally
{
waitLock.Release();
if (waitData.WaitResult != null)
waitData.WaitResult.Sign = sign;
clientChannel.WaitHandlePool.Destroy(waitData);
Channel.ChannelReceivedWaitDict.TryRemove(sign, out _);
}
@@ -944,7 +962,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
{
client.WaitHandlePool?.SafeDispose();
client.SafeClose();
client.Close();
}
}

View File

@@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.4" />
<PackageReference Include="TouchSocket" Version="3.0.26" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.26" />
<PackageReference Include="TouchSocket" Version="3.1.1" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -87,12 +87,12 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
// 触发一次设备状态变化和变量值变化事件
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}

View File

@@ -85,12 +85,12 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}

View File

@@ -9,6 +9,9 @@
//------------------------------------------------------------------------------
using System.Globalization;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.RegularExpressions;
using ThingsGateway.NewLife.Json.Extension;
@@ -63,7 +66,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
if (topics.Count > 0)
{
@@ -128,7 +131,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
if (topics.Count > 0)
{
@@ -193,7 +196,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
@@ -267,7 +270,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
data = item;
}
List<TopicJson> topicJsonList = new List<TopicJson>();
var topicJsonList = new List<TopicJson>();
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Count > 0)
{
@@ -329,6 +332,40 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
return topicJsonList;
}
//protected static byte[] Serialize(object value)
//{
// var block = new ValueByteBlock(1024 * 64);
// try
// {
// //将数据序列化到内存块
// FastBinaryFormatter.Serialize(ref block, value);
// block.SeekToStart();
// return block.Memory.GetArray().Array;
// }
// finally
// {
// block.Dispose();
// }
//}
protected static JsonSerializerOptions NoWriteIndentedJsonSerializerOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
};
protected static JsonSerializerOptions WriteIndentedJsonSerializerOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
protected static byte[] Serialize(object data, bool writeIndented)
{
if (data == null) return Array.Empty<byte>();
byte[] payload = JsonSerializer.SerializeToUtf8Bytes(data, data.GetType(), writeIndented ? WriteIndentedJsonSerializerOptions : NoWriteIndentedJsonSerializerOptions);
return payload;
}
protected List<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
@@ -355,7 +392,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
var json = Serialize(group.Select(a => a).ToList().ToList());
var json = Serialize(group.Select(a => a).ToList().ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -364,7 +401,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -376,14 +413,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
var json = Serialize(data.Select(a => a).ToList());
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
}
}
@@ -392,22 +429,6 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
return topicJsonList;
}
protected static ArraySegment<byte> Serialize(object value)
{
var block = new ValueByteBlock(1024 * 64);
try
{
//将数据序列化到内存块
FastBinaryFormatter.Serialize(ref block, value);
block.SeekToStart();
return block.Memory.GetArray();
}
finally
{
block.Dispose();
}
}
protected List<TopicArray> GetDeviceTopicArray(IEnumerable<DevModel> item)
{
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
@@ -436,7 +457,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
// 如果是设备列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
var json = Serialize(group.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -445,7 +466,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -458,14 +479,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
var json = Serialize(data.Select(a => a).ToList());
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
}
}
@@ -501,7 +522,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
var json = Serialize(group.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -510,7 +531,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -523,14 +544,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var json = Serialize(data.Select(a => a).ToList());
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
}
@@ -538,7 +559,6 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
return topicJsonList;
}
protected List<TopicArray> GetVariableBasicDataTopicArray(IEnumerable<VariableBasicData> item)
{
IEnumerable<VariableBasicData>? data = null;
@@ -576,7 +596,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var json = Serialize(group.Select(a => a).ToList());
var json = Serialize(group.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -585,7 +605,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = Serialize(gro);
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
topicJsonList.Add(new(topic, json));
}
@@ -598,14 +618,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
{
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var json = Serialize(data.Select(a => a).ToList());
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
else
{
foreach (var group in data)
{
var json = Serialize(group);
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
}
}
@@ -614,5 +634,9 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
}
protected string GetString(string topic, byte[] json, int count)
{
return $"Topic{topic}{Environment.NewLine}PayLoad{Encoding.UTF8.GetString(json)} {Environment.NewLine} VarModelQueue:{count}";
}
#endregion
}

View File

@@ -73,7 +73,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
// 触发一次变量值变化事件
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}

View File

@@ -1,32 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// <inheritdoc/>
/// </summary>
public class BusinessPropertyWithCacheIntervalDBScript : BusinessPropertyWithCacheInterval
{
/// <summary>
/// 表定义实体脚本
/// </summary>
[DynamicProperty]
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 3)]
public string? BigTextScriptTableModel { get; set; }
/// <summary>
/// 数据脚本
/// </summary>
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 3)]
public string? BigTextScriptDataModel { get; set; }
}

View File

@@ -12,11 +12,12 @@ namespace ThingsGateway.Gateway.Application;
public struct TopicArray
{
public TopicArray(string topic, ArraySegment<byte> json)
public TopicArray(string topic, byte[] json)
{
Topic = topic; Json = json;
}
public ArraySegment<byte> Json { get; set; }
public byte[] Json { get; set; }
public string Topic { get; set; }
}

View File

@@ -140,7 +140,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 缓存超时
/// </summary>
[SugarColumn(ColumnDescription = "缓存超时")]
[SugarColumn(ColumnDescription = "缓存超时", IsNullable = true, DefaultValue = "500")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(100)]
public override int CacheTimeout { get; set; } = 500;
@@ -148,7 +148,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 连接超时
/// </summary>
[SugarColumn(ColumnDescription = "连接超时")]
[SugarColumn(ColumnDescription = "连接超时", IsNullable = true, DefaultValue = "3000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(100)]
public override ushort ConnectTimeout { get; set; } = 3000;
@@ -156,33 +156,36 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 最大并发数
/// </summary>
[SugarColumn(ColumnDescription = "最大并发数")]
[SugarColumn(ColumnDescription = "最大并发数", IsNullable = true, DefaultValue = "1")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(1)]
public override int MaxConcurrentCount { get; set; } = 1;
[SugarColumn(ColumnDescription = "最大连接数")]
[SugarColumn(ColumnDescription = "最大连接数", IsNullable = true, DefaultValue = "10000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int MaxClientCount { get; set; } = 10000;
[SugarColumn(ColumnDescription = "客户端滑动过期时间")]
[SugarColumn(ColumnDescription = "客户端滑动过期时间", IsNullable = true, DefaultValue = "120000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int CheckClearTime { get; set; } = 120000;
[SugarColumn(ColumnDescription = "心跳内容", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override string Heartbeat { get; set; } = "Heartbeat";
#region dtu终端
[SugarColumn(ColumnDescription = "心跳间隔")]
[SugarColumn(ColumnDescription = "心跳间隔", IsNullable = true, DefaultValue = "60000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int HeartbeatTime { get; set; } = 60000;
[SugarColumn(ColumnDescription = "DtuId", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override string DtuId { get; set; }
#endregion
[SugarColumn(ColumnDescription = "Dtu类型")]
[SugarColumn(ColumnDescription = "Dtu类型", IsNullable = true, DefaultValue = "0")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override DtuSeviceType DtuSeviceType { get; set; }

View File

@@ -51,6 +51,13 @@ public class Variable : BaseDataEntity, IValidatableObject
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 采集组
/// </summary>
[SugarColumn(ColumnDescription = "采集组", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 1)]
public virtual string CollectGroup { get; set; } = string.Empty;
/// <summary>
/// 分组名称
/// </summary>

View File

@@ -97,6 +97,7 @@
"Name": "Name",
"Description": "Description",
"Group": "Group",
"CollectGroup": "CollectGroup",
"DeviceId": "CollectionDevice",
"DeviceId.MinValue": "{0} cannot be empty",
"DeviceId.Required": "{0} cannot be empty",
@@ -452,6 +453,7 @@
"Name.Required": "{0} cannot be empty",
"Description": "Description",
"Group": "Group",
"CollectGroup": "CollectGroup",
"DeviceId": "CollectionDevice",
"DeviceId.MinValue": "{0} cannot be empty",
"DeviceId.Required": "{0} cannot be empty",

View File

@@ -89,7 +89,8 @@
"Name": "名称",
"Name.Required": " {0} 不可为空",
"Description": "描述",
"Group": "组",
"Group": "业务组",
"CollectGroup": "采集组",
"DeviceId": "采集设备",
"DeviceId.MinValue": " {0} 不可为空",
"DeviceId.Required": " {0} 不可为空",
@@ -486,7 +487,8 @@
"Name": "名称",
"Name.Required": " {0} 不可为空",
"Description": "描述",
"Group": "组",
"Group": "业务组",
"CollectGroup": "采集组",
"DeviceId": "采集设备",
"DeviceId.MinValue": " {0} 不可为空",
"DeviceId.Required": " {0} 不可为空",

View File

@@ -131,7 +131,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
public void Dispose()
{
Config?.SafeDispose();
//Config?.SafeDispose();
GlobalData.Channels.TryRemove(Id, out _);
DeviceThreadManage = null;
@@ -146,4 +146,57 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
return $"{Name}[{base.ToString()}]";
}
public IChannel GetChannel(TouchSocketConfig config)
{
lock (GlobalData.Channels)
{
if (DeviceThreadManage?.Channel?.DisposedValue == false)
return DeviceThreadManage?.Channel;
if (ChannelType == ChannelTypeEnum.TcpService
|| ChannelType == ChannelTypeEnum.SerialPort
|| ChannelType == ChannelTypeEnum.UdpSession
)
{
//获取相同配置的Tcp服务或Udp服务或COM
var same = GlobalData.Channels.FirstOrDefault(a =>
{
if (a.Value == this)
return false;
if (a.Value.DeviceThreadManage?.Channel?.DisposedValue == true || a.Value.DeviceThreadManage?.Channel?.DisposedValue == null)
return false;
if (a.Value.ChannelType == ChannelType)
{
if (a.Value.ChannelType == ChannelTypeEnum.TcpService)
if (a.Value.BindUrl == BindUrl)
return true;
if (a.Value.ChannelType == ChannelTypeEnum.UdpSession)
if ((!BindUrl.IsNullOrWhiteSpace()) && a.Value.BindUrl == BindUrl)
return true;
if (a.Value.ChannelType == ChannelTypeEnum.SerialPort)
if (a.Value.PortName == PortName)
return true;
}
return false;
}).Value;
if (same != null)
{
return same.GetChannel(config);
}
}
if (DeviceThreadManage?.Channel?.DisposedValue == false)
return DeviceThreadManage?.Channel;
var ichannel = config.GetChannel(this);
return ichannel;
}
}
}

View File

@@ -19,5 +19,8 @@ public class VariableMapper : IRegister
{
config.ForType<Variable, VariableRuntime>()
.Map(dest => dest.Value, src => src.InitValue);
config.ForType<VariableRuntime, VariableRuntime>()
.Ignore(dest => dest.DeviceRuntime);
}
}

View File

@@ -216,16 +216,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
await RuntimeServiceHelper.InitAsync(newChannelRuntimes, newDeviceRuntimes, _logger).ConfigureAwait(false);
var startCollectChannelEnable = GlobalData.StartCollectChannelEnable;
var startBusinessChannelEnable = GlobalData.StartBusinessChannelEnable;
var collectChannelRuntimes = newChannelRuntimes.Where(x => (x.Enable && x.IsCollect == true && startCollectChannelEnable));
var businessChannelRuntimes = newChannelRuntimes.Where(x => (x.Enable && x.IsCollect == false && startBusinessChannelEnable));
//根据初始冗余属性,筛选启动
await GlobalData.ChannelThreadManage.RestartChannelAsync(businessChannelRuntimes).ConfigureAwait(false);
await GlobalData.ChannelThreadManage.RestartChannelAsync(collectChannelRuntimes).ConfigureAwait(false);
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
}
catch (Exception ex)

View File

@@ -16,8 +16,6 @@ using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
internal sealed class ChannelThreadManage : IChannelThreadManage
@@ -115,8 +113,6 @@ internal sealed class ChannelThreadManage : IChannelThreadManage
{
await PrivateRemoveChannelsAsync(channelRuntimes.Select(a => a.Id)).ConfigureAwait(false);
BytePool.Default.Clear();
await channelRuntimes.ParallelForEachAsync(async (channelRuntime, token) =>
{
try

View File

@@ -117,10 +117,9 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
// 添加默认日志记录器
LogMessage.AddLogger(new EasyLogger(logger.Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
var ichannel = config.GetChannel(channelRuntime);
// 根据配置获取通道实例
Channel = ichannel;
Channel = channelRuntime.GetChannel(config);
//初始设置输出文本日志
SetLog(CurrentChannel.LogLevel);
@@ -901,7 +900,8 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
await NewDeviceLock.WaitAsync().ConfigureAwait(false);
await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
Channel?.SafeDispose();
if (Channel?.Collects.Count == 0)
Channel?.SafeDispose();
LogMessage?.LogInformation(Localizer["ChannelDispose", CurrentChannel?.Name ?? string.Empty]);

View File

@@ -71,16 +71,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
}
}
var startCollectChannelEnable = GlobalData.StartCollectChannelEnable;
var startBusinessChannelEnable = GlobalData.StartBusinessChannelEnable;
var collectChannelRuntimes = channelRuntimes.Where(x => (x.Enable && x.IsCollect == true && startCollectChannelEnable));
var businessChannelRuntimes = channelRuntimes.Where(x => (x.Enable && x.IsCollect == false && startBusinessChannelEnable));
//根据初始冗余属性,筛选启动
await ChannelThreadManage.RestartChannelAsync(businessChannelRuntimes).ConfigureAwait(false);
await ChannelThreadManage.RestartChannelAsync(collectChannelRuntimes).ConfigureAwait(false);
await ChannelThreadManage.RestartChannelAsync(channelRuntimes).ConfigureAwait(false);
}

View File

@@ -76,6 +76,7 @@ public class Startup : AppStartup
if (configId.Count() > 1) throw new($"Sqlsugar connect configId: {configId.Key} Duplicate!");
}
//遍历配置
DbContext.DbConfigs?.ForEach(it =>
{
@@ -84,6 +85,20 @@ public class Startup : AppStartup
if (it.InitTable == true)
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
});
//兼容变量名称唯一键处理
try
{
using var db = DbContext.GetDB<Variable>();
if (db.DbMaintenance.IsAnyIndex("unique_variable_name"))
{
var tables = db.DbMaintenance.DropIndex("unique_variable_name");
}
}
catch { }
var fullName = Assembly.GetExecutingAssembly().FullName;//获取程序集全名
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
@@ -109,6 +124,9 @@ public class Startup : AppStartup
}
catch { }
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStarted.Register(() =>
{
serviceProvider.GetService<ILoggerFactory>().CreateLogger(nameof(ThingsGateway)).LogInformation("ThingsGateway is started...");

View File

@@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.26" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.26" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.1" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.1" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
</ItemGroup>

View File

@@ -105,8 +105,8 @@ public class TcpSessionClientDto
public string PluginInfos { get; set; }
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public DateTime LastReceivedTime { get; set; }
public DateTimeOffset LastReceivedTime { get; set; }
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public DateTime LastSentTime { get; set; }
public DateTimeOffset LastSentTime { get; set; }
}

View File

@@ -1299,6 +1299,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
try
{
if (Disposed) return;
await Task.Delay(1000);
await OnClickSearch(SearchText);
Value = GetValue(Value);
@@ -1310,7 +1311,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
}
finally
{
await Task.Delay(2000);
_isExecuting = false;
}
}

View File

@@ -129,7 +129,6 @@ public partial class PropertyComponent : IPropertyUIBase
return;
}
var op = new DialogOption()
{
IsScrolling = true,
@@ -144,6 +143,147 @@ public partial class PropertyComponent : IPropertyUIBase
{
{nameof(ScriptCheck.Data),data },
{nameof(ScriptCheck.Script),script },
{nameof(ScriptCheck.OnGetDemo),()=>
{
return
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel)?
"""
using ThingsGateway.Foundation;
using System.Dynamic;
using TouchSocket.Core;
public class S1 : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
foreach (var v in datas)
{
var device = (DeviceBasicData)v;
var expando = new ExpandoObject();
var deviceObj = new ExpandoObject();
deviceObj.TryAdd(nameof(Device.Description), device.Description);
deviceObj.TryAdd(nameof(DeviceBasicData.ActiveTime), device.ActiveTime);
deviceObj.TryAdd(nameof(DeviceBasicData.DeviceStatus), device.DeviceStatus.ToString());
deviceObj.TryAdd(nameof(DeviceBasicData.LastErrorMessage), device.LastErrorMessage);
deviceObj.TryAdd(nameof(DeviceBasicData.PluginName), device.PluginName);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark1), device.Remark1);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark2), device.Remark2);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark3), device.Remark3);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark4), device.Remark4);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark5), device.Remark5);
expando.TryAdd(nameof(Device.Name), deviceObj);
}
return deviceObjs;
}
}
"""
:
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptVariableModel)?
"""
using System.Dynamic;
using ThingsGateway.Foundation;
using TouchSocket.Core;
public class S2 : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
//按设备名称分组
var groups = datas.Where(a => !string.IsNullOrEmpty(((VariableBasicData)a).DeviceName)).GroupBy(a => ((VariableBasicData)a).DeviceName, a => ((VariableBasicData)a));
foreach (var group in groups)
{
//按采集时间分组
var data = group.GroupBy(a => a.CollectTime.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)
{
variableObj.TryAdd(tag.Name, tag.Value);
}
expando.TryAdd("values", variableObj);
expandos.Add(expando);
}
deviceObj.TryAdd(group.Key, expandos);
deviceObjs.Add(deviceObj);
}
return deviceObjs;
}
}
"""
:
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel)?
"""
using System.Dynamic;
using ThingsGateway.Foundation;
using TouchSocket.Core;
public class DeviceScript : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
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;
}
}
"""
:
""
;
}
},
{nameof(ScriptCheck.ScriptChanged),EventCallback.Factory.Create<string>(this, v =>
{
if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel))
@@ -171,3 +311,7 @@ public partial class PropertyComponent : IPropertyUIBase
[Inject]
private DialogService DialogService { get; set; }
}

View File

@@ -18,6 +18,8 @@
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("获取变量类实体,可用方法 <code>GlobalData.GetVariable(\"设备名称1\",\"变量名称1\")</code> "))</Alert>
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("详细说明查看文档对应内容页面"))</Alert>
<Button IsAsync OnClick="GetDemo" class="mt-3" Text="Demo" />
</div>
<div class="col-6 col-md-6">
<BootstrapLabel Value=@Localizer["Input"] ShowLabelTooltip="true" />

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Threading.Tasks;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife.Json.Extension;
@@ -56,4 +58,13 @@ public partial class ScriptCheck
}
[Inject]
private IStringLocalizer<DeviceEditComponent> Localizer { get; set; }
private async Task GetDemo(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
{
Script = OnGetDemo?.Invoke();
await Change(Script);
}
[Parameter, EditorRequired]
public Func<string> OnGetDemo { get; set; }
}

View File

@@ -44,6 +44,7 @@
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
<EditorItem @bind-Field="@context.Description" />
<EditorItem @bind-Field="@context.CollectGroup" />
<EditorItem @bind-Field="@context.Group" />
<EditorItem @bind-Field="@context.Unit" />

View File

@@ -212,8 +212,8 @@ public partial class VariableEditComponent
{
var component = new BootstrapDynamicComponent(data.VariablePropertyUIType, new Dictionary<string, object?>
{
[nameof(VariableEditComponent.Model)] = Model,
[nameof(DeviceEditComponent.PluginPropertyEditorItems)] = data.EditorItems,
[nameof(IPropertyUIBase.Model)] = Model,
[nameof(IPropertyUIBase.PluginPropertyEditorItems)] = data.EditorItems,
});
VariablePropertyRenderFragments.AddOrUpdate(id, component.Render());
}

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.3" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.4" />
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.1" />
<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />

View File

@@ -15,7 +15,7 @@ using TouchSocket.Rpc;
namespace ThingsGateway.Management;
public partial class ReverseCallbackServer : RpcServer
public partial class ReverseCallbackServer : SingletonRpcServer
{
[DmtpRpc(MethodInvoke = true)]
public void UpdateGatewayData(List<DeviceDataWithValue> deviceDatas)

View File

@@ -4,12 +4,19 @@
@if (WebsiteOption.Value.ShowAuthorize)
{
<Card class="mb-2">
<Card class="h-100">
<BodyTemplate>
<div class="ma-1">
<span>@Localizer["UUID"]</span>
<div class="my-2 ms-4 text-truncate">
<Textarea Value="@ProAuthentication.UUID" rows="5" />
<div class="row g-3 form-inline">
<div class="col-12 col-sm-12">
<label class="form-label">
@Localizer["UUID"]
</label>
<Textarea Value="@ProAuthentication.UUID" rows="5" />
</div>
</div>
<PopConfirmButton Size="Size.Small" Color="Color.Success" Icon="fa-solid fa-bars" Text="@Localizer["Register"]" IsAsync OnConfirm="Register">
@@ -22,7 +29,7 @@
</div>
<div class="ma-1">
<div class="ma-1 mt-4">
<div class="row g-3 form-inline">
<div class="col-12 col-sm-12">

View File

@@ -9,21 +9,21 @@
@namespace ThingsGateway.Management
<div class=@($"{ClassString} row my-2 mx-2") style="min-height:500px;height: 50%;">
<div class=@($"{ClassString} row g-0 h-100 mx-2") style="min-height:500px;">
<div class="col-12 h-100">
<div class="col-12 col-md-6 h-100">
<EditComponent Model="Model" OnSave="OnSaveRedundancy"/>
<EditComponent ItemsPerRow=1 Model="Model" OnSave="OnSaveRedundancy" />
</div>
<div class="col-12 ">
<div class="col-12 col-md-6 h-100">
@if (Logger != null)
{
<LogConsole HeightString="500px" LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
@if (Logger != null)
{
<LogConsole LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
Logger.LogLevel=a;
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
}
}
</div>
</div>

View File

@@ -19,7 +19,7 @@
<TabItem Text=@RedundancyLocalizer[nameof(RedundancyOptions)]>
<Card class="mb-2">
<Card class="h-100">
<HeaderTemplate>
@@ -52,7 +52,7 @@
<TabItem Text=@ManagementLocalizer["Restart"]>
<Card class="mb-2">
<Card class="h-100">
<BodyTemplate>

View File

@@ -14,9 +14,7 @@
<div class="col-12 col-md-8 p-1">
<div class="p-1">
<Button IsAsync Color="Color.Primary" OnClick="OnUpdate">@ManagementLocalizer["Update"]</Button>
</div>
<Button IsAsync Color="Color.Primary" OnClick="OnUpdate">@ManagementLocalizer["Update"]</Button>
</div>
<div class="col-12 col-md-4 p-1">

View File

@@ -13,7 +13,7 @@ using TouchSocket.Rpc;
namespace ThingsGateway.Management;
public partial class UpgradeRpcServer : RpcServer
public partial class UpgradeRpcServer : SingletonRpcServer
{
[DmtpRpc(MethodInvoke = true)]
public void Restart()

View File

@@ -9,7 +9,64 @@ namespace ThingsGateway.RulesEngine;
[CategoryNode(Category = "Actuator", ImgUrl = "_content/ThingsGateway.RulesEngine/img/CSharpScript.svg", Desc = nameof(ExecuteScriptNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(CSharpScriptWidget))]
public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBase, IDisposable
{
public ExecuteScriptNode(string id, Point? position = null) : base(id, position) { Title = "ExecuteScriptNode"; Placeholder = "ExecuteScriptNode.Placeholder"; }
public ExecuteScriptNode(string id, Point? position = null) : base(id, position)
{
Title = "ExecuteScriptNode"; Placeholder = "ExecuteScriptNode.Placeholder";
Text =
"""
using ThingsGateway.RulesEngine;
using ThingsGateway.Foundation;
using TouchSocket.Core;
using System.Text;
public class TestEx : IExexcuteExpressions
{
public TouchSocket.Core.ILog Logger { get; set; }
public async System.Threading.Tasks.Task<NodeOutput> ExecuteAsync(NodeInput input, System.Threading.CancellationToken cancellationToken)
{
//想上传mqtt可以自己写mqtt上传代码或者通过mqtt插件的公开方法上传
//直接获取mqttclient插件类型的第一个设备
var driver = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver;
if (driver != null)
{
//找到对应的MqttClient插件设备
var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
if (mqttClient == null)
throw new("mqttClient NOT FOUND");
var result = await mqttClient.MqttUpAsync("test", Encoding.UTF8.GetBytes("test"),1, default);// 主题 和 负载
if (!result.IsSuccess)
throw new(result.ErrorMessage);
return new NodeOutput() { Value = result };
}
throw new("mqttClient NOT FOUND");
//通过设备名称找出mqttClient插件
//var driver = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver;
//if (driver != null)
//{
// //找到对应的MqttClient插件设备
// var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
// if (mqttClient == null)
// throw new("mqttClient NOT FOUND");
// var result = await mqttClient.MqttUpAsync("test", "test", default);// 主题 和 负载
// if (!result.IsSuccess)
// throw new(result.ErrorMessage);
// return new NodeOutput() { Value = result };
//}
//throw new("mqttClient NOT FOUND");
}
}
""";
}
private string text;

View File

@@ -11,7 +11,14 @@ namespace ThingsGateway.RulesEngine;
[CategoryNode(Category = "Expression", ImgUrl = "_content/ThingsGateway.RulesEngine/img/CSharpScript.svg", Desc = nameof(ConditionNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(CSharpScriptWidget))]
public class ConditionNode : TextNode, IConditionNode
{
public ConditionNode(string id, Point? position = null) : base(id, position) { Title = "ConditionNode"; Placeholder = "ConditionNode.Placeholder"; }
public ConditionNode(string id, Point? position = null) : base(id, position)
{
Title = "ConditionNode"; Placeholder = "ConditionNode.Placeholder";
Text = "return true;";
}
Task<bool> IConditionNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{

View File

@@ -8,7 +8,12 @@ namespace ThingsGateway.RulesEngine;
[CategoryNode(Category = "Expression", ImgUrl = "_content/ThingsGateway.RulesEngine/img/CSharpScript.svg", Desc = nameof(DataNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(CSharpScriptWidget))]
public class DataNode : TextNode, IExpressionNode
{
public DataNode(string id, Point? position = null) : base(id, position) { Title = "DataNode"; Placeholder = "DataNode.Placeholder"; }
public DataNode(string id, Point? position = null) : base(id, position)
{
Title = "DataNode"; Placeholder = "DataNode.Placeholder";
Text = "return 1;";
}
Task<NodeOutput> IExpressionNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{

View File

@@ -19,7 +19,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
{
Func = func;
FuncDict.Add(this, func);
if (!DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
if (!DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list))
{
var deviceChangedTriggerNodes = new ConcurrentList<DeviceChangedTriggerNode>();
deviceChangedTriggerNodes.Add(this);
@@ -44,7 +44,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
private static void GlobalData_DeviceStatusChangeEvent(DeviceRuntime deviceRunTime, DeviceBasicData deviceData)
{
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name ?? string.Empty, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
{
if (!DeviceDatas.IsAddingCompleted)
{
@@ -63,7 +63,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
return DeviceDatas.GetConsumingEnumerable().ParallelForEachAsync((async (deviceDatas, token) =>
{
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name, out var valueChangedTriggerNodes))
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name ?? string.Empty, out var valueChangedTriggerNodes))
{
await valueChangedTriggerNodes.ParallelForEachAsync(async (item, token) =>
{
@@ -89,7 +89,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
public void Dispose()
{
FuncDict.Remove(this);
if (DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
if (DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list))
{
list.Remove(this);
}

View File

@@ -160,9 +160,19 @@ public class OpcDaMaster : IDisposable
/// <returns></returns>
public Dictionary<string, List<OpcItem>> AddItemsWithSave(List<string> items)
{
int i = 0;
ItemDicts = items.ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OpcDaProperty.GroupSize).ToDictionary(a => "default" + (i++));
return ItemDicts;
lock (this)
{
int i = ItemDicts.Count;
var addItems = items.ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OpcDaProperty.GroupSize).ToDictionary(a => "default" + (i++));
foreach (var item in addItems)
{
ItemDicts.TryAdd(item.Key, item.Value);
}
return addItems;
}
}
/// <summary>

View File

@@ -251,7 +251,7 @@ public class OpcUaMaster : IDisposable
DisplayName = subscriptionName
};
List<MonitoredItem> monitoredItems = new();
var variableNodes = loadType ? await ReadNodesAsync(items, cancellationToken).ConfigureAwait(false) : null;
var variableNodes = loadType ? await ReadNodesAsync(items, false, cancellationToken).ConfigureAwait(false) : null;
for (int i = 0; i < items.Length; i++)
{
try
@@ -743,7 +743,7 @@ public class OpcUaMaster : IDisposable
NodeId = new NodeId(item.Key),
AttributeId = Attributes.Value,
};
var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken).ConfigureAwait(false);
var variableNode = await ReadNodeAsync(item.Key, false, false, cancellationToken).ConfigureAwait(false);
var dataValue = JsonUtils.Decode(
m_session.MessageContext,
variableNode.DataType,
@@ -793,9 +793,10 @@ public class OpcUaMaster : IDisposable
{
if (m_session != null)
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
foreach (var value in monitoreditem.DequeueValues())
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode));
if (value.Value != null)
{
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
@@ -974,7 +975,7 @@ public class OpcUaMaster : IDisposable
List<(string, DataValue, JToken)> jTokens = new();
for (int i = 0; i < results.Count; i++)
{
var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken).ConfigureAwait(false);
var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, StatusCode.IsGood(results[i].StatusCode), cancellationToken).ConfigureAwait(false);
var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable);
var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
@@ -985,7 +986,7 @@ public class OpcUaMaster : IDisposable
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true)
{
if (!isOnlyServer)
{
@@ -1025,14 +1026,15 @@ public class OpcUaMaster : IDisposable
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
return variableNode;
}
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, CancellationToken cancellationToken = default)
private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, bool cache = true, CancellationToken cancellationToken = default)
{
if (!isOnlyServer)
{
@@ -1073,7 +1075,9 @@ public class OpcUaMaster : IDisposable
if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
return variableNode;
}
@@ -1127,7 +1131,7 @@ public class OpcUaMaster : IDisposable
/// <summary>
/// 从服务器读取节点
/// </summary>
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, bool cache = false, CancellationToken cancellationToken = default)
{
List<Node> result = new(nodeIdStrs.Length);
foreach (var items in nodeIdStrs.ChunkBetter(OpcUaProperty.GroupSize))
@@ -1171,7 +1175,8 @@ public class OpcUaMaster : IDisposable
}
else
{
_variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
if (node.DataType != NodeId.Null && TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
{
await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false);

View File

@@ -12,7 +12,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.375.457" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.213" />
</ItemGroup>
<ItemGroup>

File diff suppressed because it is too large Load Diff

View File

@@ -184,7 +184,7 @@ public static class JsonUtils
/// CreateEncoder
/// </summary>
/// <returns></returns>
private static OPCUAJsonEncoder CreateEncoder(
private static JsonEncoder CreateEncoder(
IServiceMessageContext context,
Stream stream,
bool useReversibleEncoding = false,
@@ -193,14 +193,14 @@ public static class JsonUtils
bool includeDefaultNumbers = true
)
{
return new OPCUAJsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream)
return new JsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream)
{
IncludeDefaultValues = includeDefaultValues,
IncludeDefaultNumberValues = includeDefaultNumbers
};
}
private static void Encode(OPCUAJsonEncoder encoder, BuiltInType builtInType, string fieldName, object value)
private static void Encode(JsonEncoder encoder, BuiltInType builtInType, string fieldName, object value)
{
bool isArray = (value?.GetType().IsArray ?? false) && (builtInType != BuiltInType.ByteString);
bool isCollection = (value is IList) && (builtInType != BuiltInType.ByteString);

View File

@@ -401,7 +401,7 @@ public partial class SiemensS7Master : DeviceBase
var result2 = await GetResponsedDataAsync(new S7Send(ISO_CR), channel, Timeout).ConfigureAwait(false);
if (!result2.IsSuccess)
{
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError1", channel.ToString(), result2.ErrorMessage]);
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError1", channel.ToString(), result2]);
await channel.CloseAsync().ConfigureAwait(false);
return true;
}
@@ -409,7 +409,7 @@ public partial class SiemensS7Master : DeviceBase
catch (OperationCanceledException) { }
catch (Exception ex)
{
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError1", channel.ToString(), ex.Message]);
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError1", channel.ToString(), ex]);
await channel.CloseAsync().ConfigureAwait(false);
return true;
}
@@ -418,7 +418,12 @@ public partial class SiemensS7Master : DeviceBase
var result2 = await GetResponsedDataAsync(new S7Send(S7_PN), channel, Timeout).ConfigureAwait(false);
if (!result2.IsSuccess)
{
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError2", channel.ToString(), result2.ErrorMessage]);
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError2", channel.ToString(), result2]);
await channel.CloseAsync().ConfigureAwait(false);
return true;
}
if (result2.Content == null)
{
await channel.CloseAsync().ConfigureAwait(false);
return true;
}
@@ -429,7 +434,7 @@ public partial class SiemensS7Master : DeviceBase
catch (OperationCanceledException) { }
catch (Exception ex)
{
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError2", channel.ToString(), ex.Message]);
Logger?.LogWarning(SiemensS7Resource.Localizer["HandshakeError2", channel.ToString(), ex]);
await channel.CloseAsync().ConfigureAwait(false);
return true;
}

View File

@@ -12,7 +12,7 @@
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -25,11 +25,20 @@
<div class="col-12 col-md-12">
<BootstrapLabel Value=@SqlDBProducerPropertyLocalizer["BigTextScriptHistoryTable"] ShowLabelTooltip="true" />
<CodeEditor @bind-Value=@businessProperty.BigTextScriptHistoryTable Language="csharp" Theme="vs-dark" />
<div class="ms-2 d-flex justify-content-center align-items-center">
<Button IsDisabled=@(!CanWrite) OnClick="()=>CheckScript(businessProperty,nameof(businessProperty.BigTextScriptHistoryTable))">
@RazorLocalizer["Check"]
</Button>
</div>
</div>
<div class="col-12 col-md-12">
<BootstrapLabel Value=@SqlDBProducerPropertyLocalizer["BigTextScriptRealTable"] ShowLabelTooltip="true" />
<CodeEditor @bind-Value=@businessProperty.BigTextScriptRealTable Language="csharp" Theme="vs-dark" />
<div class="ms-2 d-flex justify-content-center align-items-center">
<Button IsDisabled=@(!CanWrite) OnClick="()=>CheckScript(businessProperty,nameof(businessProperty.BigTextScriptRealTable))">
@RazorLocalizer["Check"]
</Button>
</div>
</div>
</EditTemplate>
</EditorItem>

View File

@@ -8,17 +8,22 @@
// QQ群605534569
// ------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Gateway.Razor;
using ThingsGateway.Plugin.SqlDB;
using ThingsGateway.Razor;
namespace ThingsGateway.Debug
{
public partial class SqlDBProducerPropertyRazor : IPropertyUIBase
{
[Inject]
IStringLocalizer<ThingsGateway.Razor._Imports> RazorLocalizer { get; set; }
[Parameter, EditorRequired]
@@ -39,6 +44,144 @@ namespace ThingsGateway.Debug
return base.OnParametersSetAsync();
}
private async Task CheckScript(SqlDBProducerProperty businessProperty, string pname)
{
IEnumerable<object> data = null;
string script = null;
{
data = new List<VariableBasicData>() { new() {
Name = "testName",
DeviceName = "testDevice",
Value = "1",
ChangeTime = DateTime.Now,
CollectTime = DateTime.Now,
Remark1="1",
Remark2="2",
Remark3="3",
Remark4="4",
Remark5="5",
} ,
new() {
Name = "testName2",
DeviceName = "testDevice",
Value = "1",
ChangeTime = DateTime.Now,
CollectTime = DateTime.Now,
Remark1="1",
Remark2="2",
Remark3="3",
Remark4="4",
Remark5="5",
} };
script = pname == businessProperty.BigTextScriptHistoryTable ? businessProperty.BigTextScriptHistoryTable : businessProperty.BigTextScriptRealTable;
}
var op = new DialogOption()
{
IsScrolling = true,
Title = RazorLocalizer["Check"],
ShowFooter = false,
ShowCloseButton = false,
Size = Size.ExtraExtraLarge,
FullScreenSize = FullScreenSize.None
};
op.Component = BootstrapDynamicComponent.CreateComponent<ScriptCheck>(new Dictionary<string, object?>
{
{nameof(ScriptCheck.Data),data },
{nameof(ScriptCheck.Script),script },
{nameof(ScriptCheck.OnGetDemo),()=>
{
return
pname == nameof(SqlDBProducerProperty.BigTextScriptHistoryTable)?
""""
using ThingsGateway.Foundation;
using System.Dynamic;
using TouchSocket.Core;
public class S1 : DynamicSQLBase
{
public override async Task DBInit(ISqlSugarClient db, CancellationToken cancellationToken)
{
var sql = $"""
""";
await db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
}
public override async Task DBInsertable(ISqlSugarClient db, IEnumerable<object> datas, CancellationToken cancellationToken)
{
var sql = $"""
""";
await db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
""""
:
pname == nameof(SqlDBProducerProperty.BigTextScriptRealTable)?
""""
using System.Dynamic;
using ThingsGateway.Foundation;
using TouchSocket.Core;
public class S1 : DynamicSQLBase
{
public override async Task DBInit(ISqlSugarClient db, CancellationToken cancellationToken)
{
var sql = $"""
""";
await db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
}
public override async Task DBInsertable(ISqlSugarClient db, IEnumerable<object> datas, CancellationToken cancellationToken)
{
var sql = $"""
""";
await db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
""""
:
""
;
}
},
{nameof(ScriptCheck.ScriptChanged),EventCallback.Factory.Create<string>(this, v =>
{
if (pname == nameof(SqlDBProducerProperty.BigTextScriptHistoryTable))
{
businessProperty.BigTextScriptHistoryTable=v;
}
}
else if (pname == nameof(SqlDBProducerProperty.BigTextScriptRealTable))
{
businessProperty.BigTextScriptRealTable=v;
}
}) },
});
await DialogService.Show(op);
}
[Inject]
DialogService DialogService { get; set; }
}
}

View File

@@ -8,13 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Mapster;
using Microsoft.Data.Sqlite;
using SqlSugar;
using System.Reflection;
using System.Text.RegularExpressions;
namespace ThingsGateway.Plugin.SqlDB;
@@ -25,7 +20,7 @@ public class SqlDBDateSplitTableService : DateSplitTableService
public SqlDBDateSplitTableService(SqlDBProducerProperty sqlDBProducerProperty)
{
this._sqlDBProducerProperty = sqlDBProducerProperty;
_sqlDBProducerProperty = sqlDBProducerProperty;
}
#region Core

View File

@@ -166,7 +166,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
{
if (datas?.Count > 0)
{
var result = await db.Storageable(datas).As(_driverPropertys.ReadDBTableName).PageSize(5000).ExecuteSqlBulkCopyAsync().ConfigureAwait(false);
var result = await db.Storageable(datas).As(_driverPropertys.ReadDBTableName).PageSize(5000).ExecuteSqlBulkCopyAsync(cancellationToken).ConfigureAwait(false);
if (result > 0)
LogMessage.Trace($"RealTable Data Count{result}");
_initRealData = true;

View File

@@ -19,7 +19,7 @@
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.8" GeneratePathProperty="true">
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.11" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>

View File

@@ -1,32 +1,11 @@
<Project>
<Target Name="CopyNugetPackages" AfterTargets="Build">
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<!-- setting up the variable for convenience -->
<ApplicationPackageFiles Include="$(PkgMQTTnet_AspNetCore)\lib\net6.0\*.*" />
<MQTTnetApplicationPackageFiles Include="$(PkgMQTTnet)\lib\net6.0\*.*" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0'">
<!-- setting up the variable for convenience -->
<ApplicationPackageFiles Include="$(PkgMQTTnet_AspNetCore)\lib\net8.0\*.*" />
<MQTTnet_ServerApplicationPackageFiles Include="$(PkgMQTTnet_Server)\lib\net8.0\*.*" />
<MQTTnetApplicationPackageFiles Include="$(PkgMQTTnet)\lib\net8.0\*.*" />
</ItemGroup>
<PropertyGroup>
<ApplicationFolder>$(TargetDir)</ApplicationFolder>
</PropertyGroup>
<Copy SourceFiles="@(ApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(MQTTnet_ServerApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(MQTTnetApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
</Target>
<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包-->
<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build">
<ItemGroup>
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*MQTT*.dll">
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Http*.dll">
<Pack>true</Pack>
<PackagePath>Content</PackagePath>
</Content>

View File

@@ -12,8 +12,6 @@ using BootstrapBlazor.Components;
using Mapster;
using System.Text;
using ThingsGateway.Foundation;
using ThingsGateway.NewLife.Extension;
@@ -135,11 +133,13 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
private readonly HttpClient client = new HttpClient();
private async Task<OperResult> WebhookUpAsync(string topic, string json, int count, CancellationToken cancellationToken)
private async Task<OperResult> WebhookUpAsync(string topic, byte[] payLoad, int count, CancellationToken cancellationToken)
{
// 设置请求内容
var content = new StringContent(json, Encoding.UTF8, "application/json");
//var content = new StringContent(json, Encoding.UTF8, "application/json");
using var content = new ByteArrayContent(payLoad);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
try
{
@@ -152,7 +152,7 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
if (_driverPropertys.DetailLog)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}PayLoad{json} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count}");
LogMessage.LogTrace(GetString(topic, payLoad, _memoryVarModelQueue.Count));
}
else
{
@@ -175,11 +175,12 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
#region private
private async ValueTask<OperResult> Update(List<TopicJson> topicJsonList, int count, CancellationToken cancellationToken)
private async ValueTask<OperResult> Update(List<TopicArray> topicJsonList, int count, CancellationToken cancellationToken)
{
foreach (var topicJson in topicJsonList)
{
var result = await WebhookUpAsync(topicJson.Topic, topicJson.Json, count, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
return result;
if (success != result.IsSuccess)
@@ -202,20 +203,20 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetAlarms(item);
var topicJsonList = GetAlarmTopicArrays(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
var topicJsonList = GetDeviceTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariableBasicData(item);
var topicJsonList = GetVariableBasicDataTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}

View File

@@ -46,7 +46,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
_producerconfig.SaslPassword = _driverPropertys.SaslPassword;
//2、创建生产者
_producerBuilder = new ProducerBuilder<Null, string>(_producerconfig);
_producerBuilder = new ProducerBuilder<Null, byte[]>(_producerconfig);
//3、错误日志监视
_producerBuilder.SetErrorHandler((p, msg) =>
{
@@ -75,6 +75,14 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
try
{
_producer?.Flush(TimeSpan.FromSeconds(10));
}
catch
{
}
_producer?.SafeDispose();
base.Dispose(disposing);
}

View File

@@ -24,8 +24,8 @@ namespace ThingsGateway.Plugin.Kafka;
/// </summary>
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
{
private IProducer<Null, string> _producer;
private ProducerBuilder<Null, string> _producerBuilder;
private IProducer<Null, byte[]> _producer;
private ProducerBuilder<Null, byte[]> _producerBuilder;
private ProducerConfig _producerconfig;
private volatile bool producerSuccess = true;
@@ -127,7 +127,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
#region private
private async ValueTask<OperResult> Update(List<TopicJson> topicJsonList, int count, CancellationToken cancellationToken)
private async ValueTask<OperResult> Update(List<TopicArray> topicJsonList, int count, CancellationToken cancellationToken)
{
foreach (var topicJson in topicJsonList)
{
@@ -150,19 +150,19 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
private async ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetAlarms(item);
var topicJsonList = GetAlarmTopicArrays(item);
return await Update(topicJsonList, item.Count(), cancellationToken).ConfigureAwait(false);
}
private async ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
var topicJsonList = GetDeviceTopicArray(item);
return await Update(topicJsonList, item.Count(), cancellationToken).ConfigureAwait(false);
}
private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariableBasicData(item);
var topicJsonList = GetVariableBasicDataTopicArray(item);
return await Update(topicJsonList, item.Count(), cancellationToken).ConfigureAwait(false);
}
@@ -203,42 +203,36 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
/// <summary>
/// kafka上传返回上传结果
/// </summary>
public async ValueTask<OperResult> KafKaUpAsync(string topic, string payLoad, int count, CancellationToken cancellationToken)
public async ValueTask<OperResult> KafKaUpAsync(string topic, byte[] payLoad, int count, CancellationToken cancellationToken)
{
try
{
using CancellationTokenSource cancellationTokenSource = new();
using CancellationTokenSource cancellationTokenSource = new(_driverPropertys.Timeout);
using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, cancellationToken);
Task<DeliveryResult<Null, string>> resultTask = _producer.ProduceAsync(topic, new Message<Null, string> { Value = payLoad }, stoppingToken.Token);
var timeOutResult = await Task.WhenAny(resultTask, Task.Delay(_driverPropertys.Timeout, stoppingToken.Token)).ConfigureAwait(false);
if (timeOutResult == resultTask)
var result = await _producer.ProduceAsync(topic, new Message<Null, byte[]> { Value = payLoad }, stoppingToken.Token).ConfigureAwait(false);
if (result.Status != PersistenceStatus.Persisted)
{
var result = await resultTask.ConfigureAwait(false);
if (result.Status != PersistenceStatus.Persisted)
{
return new OperResult("Upload fail");
}
else
{
if (_driverPropertys.DetailLog)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}PayLoad{payLoad} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count}");
}
else
{
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}Count{count} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count}");
}
return OperResult.Success;
}
return new OperResult("Upload fail");
}
else
{
stoppingToken.Cancel();
return new OperResult("Upload timeout");
if (_driverPropertys.DetailLog)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace(GetString(topic, payLoad, _memoryVarModelQueue.Count));
}
else
{
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}Count{count} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count}");
}
return OperResult.Success;
}
}
catch (OperationCanceledException)
{
return new OperResult("Timeout");
}
catch (Exception ex)
{
return new OperResult(ex);

View File

@@ -80,6 +80,11 @@ public class ModbusMaster : CollectFoundationBase
protected override async Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
await Task.CompletedTask.ConfigureAwait(false);
return _plc.LoadSourceRead<VariableSourceRead>(deviceVariables, _driverPropertys.MaxPack, CurrentDevice.IntervalTime);
List<VariableSourceRead> variableSourceReads = new();
foreach (var deviceVariable in deviceVariables.GroupBy(a => a.CollectGroup))
{
variableSourceReads.AddRange(_plc.LoadSourceRead<VariableSourceRead>(deviceVariable, _driverPropertys.MaxPack, CurrentDevice.IntervalTime));
}
return variableSourceReads;
}
}

View File

@@ -60,31 +60,35 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
private async ValueTask<OperResult> UpdateThingsBoardDeviceConnect(DeviceBasicData deviceData)
{
List<TopicJson> topicJsonTBList = new();
var topicJsonTBList = new List<TopicArray>();
{
if (deviceData.DeviceStatus == DeviceStatusEnum.OnLine)
{
var topicJson = new TopicJson()
var json = new
{
device = deviceData.Name,
};
var topicJson = new TopicArray()
{
Topic = "v1/gateway/connect",
Json = new
{
device = deviceData.Name,
}.ToJsonNetString(_driverPropertys.JsonFormattingIndented)
Json = Serialize(json, _driverPropertys.JsonFormattingIndented)
};
topicJsonTBList.Add(topicJson);
}
else
{
var topicJson = new TopicJson()
var json = new
{
device = deviceData.Name,
};
var topicJson = new TopicArray()
{
Topic = "v1/gateway/disconnect",
Json = new
{
device = deviceData.Name,
}.ToJsonNetString(_driverPropertys.JsonFormattingIndented)
Json = Serialize(json, _driverPropertys.JsonFormattingIndented)
};
topicJsonTBList.Add(topicJson);
}
@@ -202,7 +206,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
#region private
private async ValueTask<OperResult> Update(List<TopicJson> topicJsonList, int count, CancellationToken cancellationToken)
private async ValueTask<OperResult> Update(List<TopicArray> topicJsonList, int count, CancellationToken cancellationToken)
{
foreach (var topicJson in topicJsonList)
{
@@ -227,20 +231,20 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetAlarms(item);
var topicJsonList = GetAlarmTopicArrays(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
var topicJsonList = GetDeviceTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariableBasicData(item);
var topicJsonList = GetVariableBasicDataTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
@@ -459,7 +463,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
/// <summary>
/// 上传mqtt返回上传结果
/// </summary>
public async ValueTask<OperResult> MqttUpAsync(string topic, string payLoad, int count, CancellationToken cancellationToken = default)
public async ValueTask<OperResult> MqttUpAsync(string topic, byte[] payLoad, int count, CancellationToken cancellationToken = default)
{
try
{
@@ -475,7 +479,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
if (_driverPropertys.DetailLog)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}PayLoad{payLoad} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count}");
LogMessage.LogTrace(GetString(topic, payLoad, _memoryVarModelQueue.Count));
}
else
{

View File

@@ -46,7 +46,6 @@ namespace ThingsGateway.Plugin.Mqtt
if (mqttClientProperty.TLS == true)
{
{
var filePath = Path.Combine("PluginFile", Id, nameof(mqttClientProperty.CAFile));
if (!Directory.Exists(filePath))//如果不存在就创建文件夹
Directory.CreateDirectory(filePath);
@@ -230,7 +229,6 @@ namespace ThingsGateway.Plugin.Mqtt
return;
}
var op = new DialogOption()
{
IsScrolling = true,
@@ -245,6 +243,142 @@ namespace ThingsGateway.Plugin.Mqtt
{
{nameof(ScriptCheck.Data),data },
{nameof(ScriptCheck.Script),script },
{nameof(ScriptCheck.OnGetDemo),()=>
{
return
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel)?
"""
using TouchSocket.Core;
public class S1 : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
foreach (var v in datas)
{
var device = (DeviceBasicData)v;
var expando = new ExpandoObject();
var deviceObj = new ExpandoObject();
deviceObj.TryAdd(nameof(Device.Description), device.Description);
deviceObj.TryAdd(nameof(DeviceBasicData.ActiveTime), device.ActiveTime);
deviceObj.TryAdd(nameof(DeviceBasicData.DeviceStatus), device.DeviceStatus.ToString());
deviceObj.TryAdd(nameof(DeviceBasicData.LastErrorMessage), device.LastErrorMessage);
deviceObj.TryAdd(nameof(DeviceBasicData.PluginName), device.PluginName);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark1), device.Remark1);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark2), device.Remark2);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark3), device.Remark3);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark4), device.Remark4);
deviceObj.TryAdd(nameof(DeviceBasicData.Remark5), device.Remark5);
expando.TryAdd(nameof(Device.Name), deviceObj);
}
return deviceObjs;
}
}
"""
:
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptVariableModel)?
"""
using TouchSocket.Core;
public class S2 : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
//按设备名称分组
var groups = datas.Where(a => !string.IsNullOrEmpty(((VariableBasicData)a).DeviceName)).GroupBy(a => ((VariableBasicData)a).DeviceName, a => ((VariableBasicData)a));
foreach (var group in groups)
{
//按采集时间分组
var data = group.GroupBy(a => a.CollectTime.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)
{
variableObj.TryAdd(tag.Name, tag.Value);
}
expando.TryAdd("values", variableObj);
expandos.Add(expando);
}
deviceObj.TryAdd(group.Key, expandos);
deviceObjs.Add(deviceObj);
}
return deviceObjs;
}
}
"""
:
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel)?
"""
using TouchSocket.Core;
public class DeviceScript : IDynamicModel
{
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
{
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;
}
}
"""
:
""
;
}
},
{nameof(ScriptCheck.ScriptChanged),EventCallback.Factory.Create<string>(this, v =>
{
if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel))
@@ -269,6 +403,7 @@ namespace ThingsGateway.Plugin.Mqtt
}
[Inject]
private DialogService DialogService { get; set; }
}

View File

@@ -133,41 +133,6 @@ public partial class MqttCollect : CollectBase
}
}
/// <summary>
/// 上传mqtt返回上传结果
/// </summary>
private async ValueTask<OperResult> MqttUpAsync(string topic, string payLoad, CancellationToken cancellationToken)
{
try
{
var isConnect = await TryMqttClientAsync(cancellationToken).ConfigureAwait(false);
if (isConnect.IsSuccess)
{
var variableMessage = new MqttApplicationMessageBuilder()
.WithTopic(topic).WithRetainFlag(true)
.WithPayload(payLoad).Build();
var result = await _mqttClient.PublishAsync(variableMessage, cancellationToken).ConfigureAwait(false);
if (result.IsSuccess)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}PayLoad{payLoad}");
return OperResult.Success;
}
else
{
return new OperResult($"Upload fail{result.ReasonString}");
}
}
else
{
return isConnect;
}
}
catch (Exception ex)
{
return new OperResult($"Upload fail", ex);
}
}
private async ValueTask<OperResult> TryMqttClientAsync(CancellationToken cancellationToken)
{

View File

@@ -141,7 +141,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
}
#region private
private async ValueTask<OperResult> Update(List<TopicJson> topicJsonList, int count, CancellationToken cancellationToken)
private async ValueTask<OperResult> Update(List<TopicArray> topicJsonList, int count, CancellationToken cancellationToken)
{
foreach (var topicJson in topicJsonList)
{
@@ -164,20 +164,20 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetAlarms(item);
var topicJsonList = GetAlarmTopicArrays(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
var topicJsonList = GetDeviceTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariableBasicData(item);
var topicJsonList = GetVariableBasicDataTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
@@ -261,7 +261,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
{
foreach (var item in varData)
{
List<TopicJson> topicJsonList = GetVariableBasicData(item);
var topicJsonList = GetVariableBasicData(item);
foreach (var topicJson in topicJsonList)
{
Messages.Add(new MqttApplicationMessageBuilder()
@@ -276,7 +276,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
{
foreach (var item in devData)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
var topicJsonList = GetDeviceData(item);
foreach (var topicJson in topicJsonList)
{
Messages.Add(new MqttApplicationMessageBuilder()
@@ -290,7 +290,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
{
foreach (var item in alramData)
{
List<TopicJson> topicJsonList = GetAlarms(item);
var topicJsonList = GetAlarms(item);
foreach (var topicJson in topicJsonList)
{
Messages.Add(new MqttApplicationMessageBuilder()
@@ -394,7 +394,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
/// <summary>
/// 上传mqtt返回上传结果
/// </summary>
public async ValueTask<OperResult> MqttUpAsync(string topic, string payLoad, int count, CancellationToken cancellationToken = default)
public async ValueTask<OperResult> MqttUpAsync(string topic, byte[] payLoad, int count, CancellationToken cancellationToken = default)
{
try
{
@@ -407,7 +407,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
if (_driverPropertys.DetailLog)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}PayLoad{payLoad} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count} ");
LogMessage.LogTrace(GetString(topic, payLoad, _memoryVarModelQueue.Count));
}
else
{

View File

@@ -122,25 +122,31 @@ public class OpcDaMaster : CollectBase
{
if (deviceVariables.Count > 0)
{
var result = _plc.AddItemsWithSave(deviceVariables.Where(a => !string.IsNullOrEmpty(a.RegisterAddress)).Select(a => a.RegisterAddress!).ToList());
var sourVars = result?.Select(
it =>
{
var read = new VariableSourceRead()
{
TimeTick = new(_driverProperties.UpdateRate.ToString()),
RegisterAddress = it.Key,
};
HashSet<string> ids = new(it.Value.Select(b => b.ItemID));
List<VariableSourceRead> variableSourceReads = new List<VariableSourceRead>();
foreach (var deviceVariableGroups in deviceVariables.GroupBy(a => a.CollectGroup))
{
var variables = deviceVariables.Where(a => ids.Contains(a.RegisterAddress));
foreach (var v in variables)
var result = _plc.AddItemsWithSave(deviceVariableGroups.Where(a => !string.IsNullOrEmpty(a.RegisterAddress)).Select(a => a.RegisterAddress!).ToList());
var sourVars = result?.Select(
it =>
{
read.AddVariable(v);
}
return read;
}).ToList();
return sourVars;
var read = new VariableSourceRead()
{
TimeTick = new(_driverProperties.UpdateRate.ToString()),
RegisterAddress = it.Key,
};
HashSet<string> ids = new(it.Value.Select(b => b.ItemID));
var variables = deviceVariableGroups.Where(a => ids.Contains(a.RegisterAddress));
foreach (var v in variables)
{
read.AddVariable(v);
}
return read;
}).ToList();
variableSourceReads.AddRange(sourVars);
}
return variableSourceReads;
}
else
{

View File

@@ -187,24 +187,27 @@ public class OpcUaMaster : CollectBase
await Task.CompletedTask.ConfigureAwait(false);
if (deviceVariables.Count > 0)
{
var dataLists = deviceVariables.ChunkBetter(_driverProperties.GroupSize);
var dataResult = new List<VariableSourceRead>();
foreach (var variable in dataLists)
List<VariableSourceRead> variableSourceReads = new List<VariableSourceRead>();
foreach (var deviceVariableGroups in deviceVariables.GroupBy(a => a.CollectGroup))
{
var sourVars = new VariableSourceRead()
{
TimeTick = new(_driverProperties.UpdateRate.ToString()),
RegisterAddress = Guid.NewGuid().ToString(),
};
foreach (var item in variable)
{
sourVars.AddVariable(item);
}
dataResult.Add(sourVars);
}
var dataLists = deviceVariableGroups.ChunkBetter(_driverProperties.GroupSize);
return dataResult;
foreach (var variable in dataLists)
{
var sourVars = new VariableSourceRead()
{
TimeTick = new(_driverProperties.UpdateRate.ToString()),
RegisterAddress = Guid.NewGuid().ToString(),
};
foreach (var item in variable)
{
sourVars.AddVariable(item);
}
variableSourceReads.Add(sourVars);
}
}
return variableSourceReads;
}
else
{

View File

@@ -22,27 +22,27 @@
</ProjectReference>
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.375.457" GeneratePathProperty="true">
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.213" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.375.457" GeneratePathProperty="true">
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.213" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.375.457" GeneratePathProperty="true">
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.213" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.375.457" GeneratePathProperty="true">
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.213" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.375.457" GeneratePathProperty="true">
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.213" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.375.457" GeneratePathProperty="true">
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.213" GeneratePathProperty="true">
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
</PackageReference>

View File

@@ -12,8 +12,6 @@ using Mapster;
using RabbitMQ.Client;
using System.Text;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.Generic;
@@ -130,7 +128,7 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
#region private
private async ValueTask<OperResult> Update(List<TopicJson> topicJsonList, int count, CancellationToken cancellationToken)
private async ValueTask<OperResult> Update(List<TopicArray> topicJsonList, int count, CancellationToken cancellationToken)
{
foreach (var topicJson in topicJsonList)
{
@@ -154,19 +152,19 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetAlarms(item);
var topicJsonList = GetAlarmTopicArrays(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetDeviceData(item);
var topicJsonList = GetDeviceTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
{
List<TopicJson> topicJsonList = GetVariableBasicData(item);
var topicJsonList = GetVariableBasicDataTopicArray(item);
return Update(topicJsonList, item.Count(), cancellationToken);
}
@@ -208,18 +206,18 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
/// <summary>
/// 上传,返回上传结果
/// </summary>
public async Task<OperResult> Publish(string topic, string payLoad, int count, CancellationToken cancellationToken)
public async Task<OperResult> Publish(string topic, byte[] payLoad, int count, CancellationToken cancellationToken)
{
try
{
if (_channel != null)
{
await _channel.BasicPublishAsync(_driverPropertys.ExchangeName, topic, Encoding.UTF8.GetBytes(payLoad), cancellationToken).ConfigureAwait(false);
await _channel.BasicPublishAsync(_driverPropertys.ExchangeName, topic, payLoad, cancellationToken).ConfigureAwait(false);
if (_driverPropertys.DetailLog)
{
if (LogMessage.LogLevel <= TouchSocket.Core.LogLevel.Trace)
LogMessage.LogTrace($"Topic{topic}{Environment.NewLine}PayLoad{payLoad} {Environment.NewLine} VarModelQueue:{_memoryVarModelQueue.Count}");
LogMessage.LogTrace(GetString(topic, payLoad, _memoryVarModelQueue.Count));
}
else
{

View File

@@ -221,7 +221,12 @@ public class SiemensS7Master : CollectFoundationBase
{
}
return _plc.LoadSourceRead<VariableSourceRead>(deviceVariables, _plc.OnLine ? _plc.PduLength : _driverPropertys.MaxPack, CurrentDevice.IntervalTime);
List<VariableSourceRead> variableSourceReads = new();
foreach (var deviceVariable in deviceVariables.GroupBy(a => a.CollectGroup))
{
variableSourceReads.AddRange(_plc.LoadSourceRead<VariableSourceRead>(deviceVariable, _driverPropertys.MaxPack, CurrentDevice.IntervalTime));
}
return variableSourceReads;
}
finally { }
}

View File

@@ -3,8 +3,8 @@
"MinCycleInterval": 10, //最小循环间隔
"MaxCycleInterval": 200, //最大循环间隔
"CheckInterval": 1800000, //检查间隔
"MaxChannelCount": 1000, //最大通道数量
"MaxDeviceCount": 1000, //最大设备数量
"MaxVariableCount": 1000000 //最大变量数量
"MaxChannelCount": 5000, //最大通道数量
"MaxDeviceCount": 5000, //最大设备数量
"MaxVariableCount": 2000000 //最大变量数量
}
}

View File

@@ -8,14 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using CSScripting;
using SqlSugar;
using System.Reflection;
using ThingsGateway.Extension.Generic;
namespace ThingsGateway.Server;
/// <summary>

View File

@@ -3,6 +3,7 @@
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹自动合并该文件夹里面所有json文件
"IgnoreConfigurationFiles": [ "" ],
"ExternalAssemblies": [ "" ]
"ExternalAssemblies": [ "" ],
"DetailedErrors": true
}

View File

@@ -162,7 +162,7 @@ public class FileHostService : BackgroundService, IFileHostService
_log.Exception(ex);
try
{
await TcpDmtpService.StopAsync().ConfigureAwait(false);
await TcpDmtpService.StopAsync(default).ConfigureAwait(false);
}
catch
{

View File

@@ -4,7 +4,7 @@ using TouchSocket.Rpc;
namespace ThingsGateway.Upgrade;
public partial class FileRpcServer : RpcServer
public partial class FileRpcServer : SingletonRpcServer
{
private readonly ILog _logger;

View File

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

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.5.2</Version>
<Version>10.5.13</Version>
</PropertyGroup>
<ItemGroup>