mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-24 20:28:59 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10391f869b | ||
|
|
fba0723a6d | ||
|
|
2db3f78f0c | ||
|
|
badf61fe01 | ||
|
|
d74e0952dc | ||
|
|
fb1699ce80 | ||
|
|
44adddbcd4 | ||
|
|
0eab889452 | ||
|
|
e14d39a459 | ||
|
|
7575264ede | ||
|
|
3e1a077b96 | ||
|
|
a921cb8400 |
@@ -13,7 +13,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.8.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -406,22 +406,24 @@ AND sql LIKE '%" + tableName + "%'");
|
||||
public override bool CreateDatabase(string databaseName, string databaseDirectory = null)
|
||||
{
|
||||
var connString = this.Context.CurrentConnectionConfig.ConnectionString;
|
||||
var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value;
|
||||
if (path.IsNullOrEmpty())
|
||||
|
||||
|
||||
// 提取 Data Source=xxx(不管是绝对还是相对路径)
|
||||
var match = Regex.Match(connString, @"(?i)Data\s+Source\s*=\s*(.+?)(;|$)");
|
||||
if (match.Success)
|
||||
{
|
||||
path = Regex.Match(connString, @"\/.+\/").Value;
|
||||
}
|
||||
if (path.IsNullOrEmpty())
|
||||
{
|
||||
path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value;
|
||||
}
|
||||
if (!path.IsNullOrEmpty())
|
||||
{
|
||||
if (!FileHelper.IsExistDirectory(path))
|
||||
var filePath = match.Groups[1].Value.Trim(); // => ./DB/data.sqlite
|
||||
var folderPath = Path.GetDirectoryName(filePath); // => ./DB
|
||||
|
||||
if (!folderPath.IsNullOrEmpty())
|
||||
{
|
||||
FileHelper.CreateDirectory(path);
|
||||
if (!FileHelper.IsExistDirectory(folderPath))
|
||||
{
|
||||
FileHelper.CreateDirectory(folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Context.Ado.Connection.Open();
|
||||
this.Context.Ado.Connection.Close();
|
||||
return true;
|
||||
|
||||
@@ -31,11 +31,12 @@
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<PackageReference Include="TDengine.Connector" Version="3.1.7" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.20" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" />
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.9.91</PluginVersion>
|
||||
<ProPluginVersion>10.9.91</ProPluginVersion>
|
||||
<DefaultVersion>10.9.92</DefaultVersion>
|
||||
<PluginVersion>10.10.1</PluginVersion>
|
||||
<ProPluginVersion>10.10.1</ProPluginVersion>
|
||||
<DefaultVersion>10.10.2</DefaultVersion>
|
||||
<AuthenticationVersion>2.9.29</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.18</NET8Version>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CS-Script" Version="4.10.0" />
|
||||
<PackageReference Include="CS-Script" Version="4.10.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -58,7 +58,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -172,7 +172,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
//发送
|
||||
await GoSendAsync(memory, cancellationToken).ConfigureAwait(false);
|
||||
@@ -191,7 +191,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
{
|
||||
sendMessage.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
byteBlock.Position = 0;
|
||||
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
|
||||
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
|
||||
|
||||
TRequest request = null;
|
||||
if (IsSingleThread)
|
||||
@@ -151,7 +151,7 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
//发送
|
||||
await GoSendAsync(endPoint, memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
{
|
||||
sendMessage.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{endPoint}- Send:{(IsHexLog ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{endPoint}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
if (IsSingleThread)
|
||||
{
|
||||
|
||||
@@ -394,6 +394,8 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
await connectWaitLock.WaitAsync(token).ConfigureAwait(false);
|
||||
if (AutoConnect && Channel != null && Channel?.Online != true)
|
||||
{
|
||||
if (Channel.PluginManager == null)
|
||||
await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false);
|
||||
await Channel.CloseAsync().ConfigureAwait(false);
|
||||
await Task.Delay(500, token).ConfigureAwait(false);
|
||||
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, token).ConfigureAwait(false);
|
||||
@@ -444,7 +446,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
public virtual OperResult<IClientChannel> GetChannel(string socketId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(socketId))
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
return new OperResult<IClientChannel>() { Content = clientChannel };
|
||||
else
|
||||
return new OperResult<IClientChannel>("The communication link cannot be obtained, DtuId must be set!");
|
||||
}
|
||||
|
||||
|
||||
if (Channel is ITcpServiceChannel serviceChannel)
|
||||
{
|
||||
@@ -462,7 +470,12 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
}
|
||||
else
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
return new OperResult<IClientChannel>() { Content = clientChannel };
|
||||
else
|
||||
return new OperResult<IClientChannel>("The communication link cannot be obtained!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -562,12 +575,25 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
|
||||
var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false);
|
||||
if (!sendOperResult.IsSuccess)
|
||||
throw sendOperResult.Exception ?? new(sendOperResult.ErrorMessage ?? "unknown error");
|
||||
return new MessageBase(sendOperResult);
|
||||
|
||||
waitData.SetCancellationToken(cancellationToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
waitData.SetCancellationToken(Channel.ClosedToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!this.DisposedValue)
|
||||
{
|
||||
await Task.Delay(timeout, Channel.ClosedToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
var result = waitData.Check();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -575,25 +601,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!this.DisposedValue)
|
||||
{
|
||||
await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return new MessageBase(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!this.DisposedValue)
|
||||
{
|
||||
await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -252,7 +252,7 @@ public static class ByteExtensions
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ArraySegment<byte> buffer, char splite = ' ', int newLineCount = 0)
|
||||
public static string ToHexString(this ArraySegment<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
@@ -261,7 +261,7 @@ public static class ByteExtensions
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this byte[] buffer, char splite = ' ', int newLineCount = 0)
|
||||
public static string ToHexString(this byte[] buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
@@ -269,7 +269,7 @@ public static class ByteExtensions
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this Span<byte> buffer, char splite = ' ', int newLineCount = 0)
|
||||
public static string ToHexString(this Span<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
@@ -277,7 +277,7 @@ public static class ByteExtensions
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = ' ', int newLineCount = 0)
|
||||
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
|
||||
@@ -14,48 +14,88 @@ namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public class AsyncReadWriteLock
|
||||
{
|
||||
private readonly int _writeReadRatio = 3; // 写3次会允许1次读,但写入也不会被阻止,具体协议取决于插件协议实现
|
||||
public AsyncReadWriteLock(int writeReadRatio)
|
||||
{
|
||||
_writeReadRatio = writeReadRatio;
|
||||
}
|
||||
private AsyncAutoResetEvent _readerLock = new AsyncAutoResetEvent(false); // 控制读计数
|
||||
private long _writerCount = 0; // 当前活跃的写线程数
|
||||
private long _readerCount = 0; // 当前被阻塞的读线程数
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
/// <summary>
|
||||
/// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。
|
||||
/// </summary>
|
||||
public async Task<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
if (Interlocked.Read(ref _writerCount) > 0)
|
||||
{
|
||||
Interlocked.Increment(ref _readerCount);
|
||||
|
||||
// 第一个读者需要获取写入锁,防止写操作
|
||||
await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Interlocked.Decrement(ref _readerCount);
|
||||
|
||||
}
|
||||
return _cancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
public bool WriteWaited => _writerCount > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 获取写锁,阻止所有读取。
|
||||
/// </summary>
|
||||
public IDisposable WriterLock()
|
||||
public async Task<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
if (Interlocked.Increment(ref _writerCount) == 1)
|
||||
{
|
||||
var cancellationTokenSource = _cancellationTokenSource;
|
||||
_cancellationTokenSource = new();
|
||||
cancellationTokenSource.Cancel();//取消读取
|
||||
await cancellationTokenSource.CancelAsync().ConfigureAwait(false); // 取消读取
|
||||
cancellationTokenSource.SafeDispose();
|
||||
}
|
||||
|
||||
return new Writer(this);
|
||||
}
|
||||
|
||||
private void ReleaseWriter()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _writerCount) == 0)
|
||||
var writerCount = Interlocked.Decrement(ref _writerCount);
|
||||
if (writerCount == 0)
|
||||
{
|
||||
var resetEvent = _readerLock;
|
||||
_readerLock = new(false);
|
||||
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
|
||||
resetEvent.SetAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作
|
||||
|
||||
if (_writeReadRatio > 0)
|
||||
{
|
||||
if (Interlocked.Read(ref _readerCount) > 0)
|
||||
{
|
||||
var count = Interlocked.Increment(ref _writeSinceLastReadCount);
|
||||
if (count >= _writeReadRatio)
|
||||
{
|
||||
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
|
||||
_readerLock.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_readerLock.Set();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private int _writeSinceLastReadCount = 0;
|
||||
private struct Writer : IDisposable
|
||||
{
|
||||
private readonly AsyncReadWriteLock _lock;
|
||||
|
||||
@@ -94,7 +94,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (((!_businessPropertyWithCacheInterval.OnlineFilter) || a.Value.IsOnline) && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
VariableValueInit(a.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (((!_businessPropertyWithCacheInterval.OnlineFilter) || a.Value.IsOnline) && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
VariableValueInit(a.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -316,4 +316,29 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
VariableChange(variableRuntime, variable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当初始化时触发此事件处理方法。该方法内部会检查是否需要进行变量上传,如果需要,则调用 <see cref="VariableChange(VariableRuntime, VariableBasicData)"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时信息</param>
|
||||
protected void VariableValueInit(VariableRuntime variableRuntime)
|
||||
{
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
if (!VarModelEnable) return;
|
||||
if (TaskSchedulerLoop?.Stoped == true) return;
|
||||
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
// 检查当前设备的变量是否包含此变量,如果包含,则触发变量的变化处理方法
|
||||
if (IdVariableRuntimes.ContainsKey(variableRuntime.Id))
|
||||
{
|
||||
var data = variableRuntime.AdaptVariableBasicData();
|
||||
data.ValueInited = false;
|
||||
VariableChange(variableRuntime, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -194,6 +194,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver
|
||||
|
||||
// 从插件服务中获取当前设备关联的驱动方法信息列表
|
||||
DriverMethodInfos = GlobalData.PluginService.GetDriverMethodInfos(device.PluginName, this);
|
||||
|
||||
ReadWriteLock = new(CollectProperties.DutyCycle);
|
||||
}
|
||||
|
||||
public virtual string GetAddressDescription()
|
||||
@@ -474,7 +476,7 @@ public abstract class CollectBase : DriverBase, IRpcDriver
|
||||
/// <returns></returns>
|
||||
protected abstract Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables);
|
||||
|
||||
protected AsyncReadWriteLock ReadWriteLock = new();
|
||||
protected AsyncReadWriteLock ReadWriteLock;
|
||||
|
||||
/// <summary>
|
||||
/// 采集驱动读取,读取成功后直接赋值变量
|
||||
@@ -565,7 +567,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver
|
||||
|
||||
ConcurrentDictionary<string, OperResult<object>> operResults = new();
|
||||
|
||||
using var writeLock = ReadWriteLock.WriterLock();
|
||||
|
||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
var list = writeInfoLists
|
||||
.Where(a => !results.Any(b => b.Key == a.Key.Name))
|
||||
.ToDictionary(item => item.Key, item => item.Value).ToArray();
|
||||
|
||||
@@ -175,7 +175,7 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
/// <returns></returns>
|
||||
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
using var writeLock = ReadWriteLock.WriterLock();
|
||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
// 检查协议是否为空,如果为空则抛出异常
|
||||
if (FoundationDevice == null)
|
||||
throw new NotSupportedException();
|
||||
@@ -188,8 +188,6 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
|
||||
LogMessage?.Debug(string.Format("{0} - Writing [{1} - {2} - {3}]", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType));
|
||||
|
||||
// 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
|
||||
var result = await FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -31,6 +31,11 @@ public abstract class CollectPropertyBase : DriverPropertyBase
|
||||
/// 失败重试次数,默认3
|
||||
/// </summary>
|
||||
public virtual int RetryCount { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 读写占空比
|
||||
/// </summary>
|
||||
public virtual int DutyCycle { get; set; } = 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,4 +50,8 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public override int RetryCount { get; set; } = 3;
|
||||
|
||||
[DynamicProperty(Remark = "n 次写入操作会执行一次读取")]
|
||||
public override int DutyCycle { get; set; } = 3;
|
||||
|
||||
}
|
||||
@@ -295,7 +295,8 @@
|
||||
"RetryCount": "RetryCount"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
|
||||
"RetryCount": "RetryCount"
|
||||
"RetryCount": "RetryCount",
|
||||
"DutyCycle": "DutyCycle"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.ControlController": {
|
||||
"BatchSaveChannelAsync": "BatchSaveChannel",
|
||||
|
||||
@@ -294,7 +294,8 @@
|
||||
"RetryCount": "失败重试次数"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
|
||||
"RetryCount": "失败重试次数"
|
||||
"RetryCount": "失败重试次数",
|
||||
"DutyCycle": "占空比"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.ControlController": {
|
||||
"BatchSaveChannelAsync": "保存通道",
|
||||
|
||||
@@ -188,4 +188,7 @@ public class VariableBasicData
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public long CreateOrgId { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.ValueInited"/>
|
||||
public bool ValueInited { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
|
||||
|
||||
private bool _isOnline;
|
||||
private bool _isOnlineChanged;
|
||||
private bool _valueInited;
|
||||
|
||||
private string alarmLimit;
|
||||
private string alarmText;
|
||||
@@ -165,6 +166,8 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
|
||||
|
||||
LastSetValue = _value;
|
||||
|
||||
ValueInited = true;
|
||||
|
||||
if (_isOnline == true)
|
||||
{
|
||||
_value = data;
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace ThingsGateway.Gateway.Application;
|
||||
public partial class VariableRuntime : Variable, IVariable, IDisposable
|
||||
{
|
||||
|
||||
[AutoGenerateColumn(Visible = false)]
|
||||
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
|
||||
|
||||
#region 属性
|
||||
/// <summary>
|
||||
|
||||
@@ -54,7 +54,7 @@ public static class PluginServiceUtil
|
||||
{
|
||||
{ "title", classAttribute.Remark }
|
||||
};
|
||||
tc.ComponentParameters.AddItem(
|
||||
tc.ComponentParameters = tc.ComponentParameters.AddItem(
|
||||
new("title", classAttribute.Remark)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,12 +67,15 @@ public class ModbusRtuSend : ISendMessage
|
||||
}
|
||||
else if (wf == 15 || wf == 16)
|
||||
{
|
||||
var data = ModbusAddress.Data.ArrayExpandToLengthEven().Span;
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)(data.Length + 7), EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode);
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.Data.Length * 8 : ModbusAddress.Data.Length / 2.0), EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Data.Length);
|
||||
byteBlock.Write(ModbusAddress.Data.Span);
|
||||
var len = (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.Data.Length * 8 : ModbusAddress.Data.Length / 2.0);
|
||||
WriterExtension.WriteValue(ref byteBlock, len, EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)(len * 2));
|
||||
byteBlock.Write(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
@@ -84,13 +86,15 @@ public class ModbusTcpSend : ISendMessage
|
||||
}
|
||||
else if (wf == 15 || wf == 16)
|
||||
{
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)(ModbusAddress.Data.Length + 7), EndianType.Big);
|
||||
var data = ModbusAddress.Data.ArrayExpandToLengthEven().Span;
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)(data.Length + 7), EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode);
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.Data.Length * 8 : ModbusAddress.Data.Length / 2.0), EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Data.Length);
|
||||
byteBlock.Write(ModbusAddress.Data.Span);
|
||||
var len = (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.Data.Length * 8 : ModbusAddress.Data.Length / 2.0);
|
||||
WriterExtension.WriteValue(ref byteBlock, len, EndianType.Big);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)(len * 2));
|
||||
byteBlock.Write(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -286,7 +286,7 @@ public class OpcUaMaster : IDisposable
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
m_session.RemoveSubscription(m_subscription);
|
||||
await m_session.RemoveSubscriptionAsync(m_subscription, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -312,7 +312,7 @@ public class OpcUaMaster : IDisposable
|
||||
else if (_subscriptionDicts.TryGetValue(subscriptionName, out var existingSubscription))
|
||||
{
|
||||
// remove
|
||||
existingSubscription.Delete(true);
|
||||
await existingSubscription.DeleteAsync(true, cancellationToken).ConfigureAwait(false);
|
||||
await m_session.RemoveSubscriptionAsync(existingSubscription, cancellationToken).ConfigureAwait(false);
|
||||
try { existingSubscription.Dispose(); } catch { }
|
||||
_subscriptionDicts[subscriptionName] = m_subscription;
|
||||
@@ -715,7 +715,7 @@ public class OpcUaMaster : IDisposable
|
||||
{
|
||||
foreach (var item in _subscriptionDicts)
|
||||
{
|
||||
item.Value.Delete(true);
|
||||
await item.Value.DeleteAsync(true).ConfigureAwait(false);
|
||||
await m_session.RemoveSubscriptionAsync(item.Value).ConfigureAwait(false);
|
||||
try { item.Value.Dispose(); } catch { }
|
||||
}
|
||||
@@ -731,7 +731,7 @@ public class OpcUaMaster : IDisposable
|
||||
if (_subscriptionDicts.TryGetValue(subscriptionName, out var subscription))
|
||||
{
|
||||
// remove
|
||||
subscription.Delete(true);
|
||||
await subscription.DeleteAsync(true).ConfigureAwait(false);
|
||||
await m_session.RemoveSubscriptionAsync(subscription).ConfigureAwait(false);
|
||||
try { subscription.Dispose(); } catch { }
|
||||
_subscriptionDicts.TryRemove(subscriptionName, out _);
|
||||
@@ -758,7 +758,7 @@ public class OpcUaMaster : IDisposable
|
||||
var dataValue = JsonUtils.Decode(
|
||||
m_session.MessageContext,
|
||||
variableNode.DataType,
|
||||
TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable),
|
||||
await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false),
|
||||
item.Value.CalculateActualValueRank(),
|
||||
item.Value
|
||||
);
|
||||
@@ -983,7 +983,7 @@ public class OpcUaMaster : IDisposable
|
||||
for (int i = 0; i < results.Count; i++)
|
||||
{
|
||||
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 type = await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false);
|
||||
var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
|
||||
jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
|
||||
}
|
||||
@@ -1078,7 +1078,7 @@ public class OpcUaMaster : IDisposable
|
||||
var responseHeader = readResponse.ResponseHeader;
|
||||
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
|
||||
|
||||
if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
|
||||
if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject)
|
||||
await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cache)
|
||||
@@ -1178,7 +1178,7 @@ public class OpcUaMaster : IDisposable
|
||||
{
|
||||
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)
|
||||
if (node.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(node.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject)
|
||||
{
|
||||
await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Import Project="..\..\PackNuget.props" />
|
||||
<Import Project="..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;netstandard2.1;net6.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net48;netstandard2.1;net8.0;</TargetFrameworks>
|
||||
<Description>工业设备通讯协议-OpcUa协议</Description>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<DocumentationFile></DocumentationFile>
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.235" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -37,7 +37,7 @@ public class Dlt645Test
|
||||
var dltMaster = new Dlt645_2007Master() { Timeout = 10000, Station = "111111111111" };
|
||||
dltMaster.InitChannel(dltChannel);
|
||||
await dltChannel.SetupAsync(dltChannel.Config);
|
||||
await dltChannel.ConnectAsync(dltChannel.ChannelOptions.ConnectTimeout, CancellationToken.None);
|
||||
await dltMaster.ConnectAsync(CancellationToken.None);
|
||||
var adapter = dltChannel.ReadOnlyDataHandlingAdapter as SingleStreamDataHandlingAdapter;
|
||||
|
||||
var task1 = Task.Run(async () =>
|
||||
|
||||
@@ -42,7 +42,7 @@ public class ModbusTest
|
||||
var modbusMaster = new ModbusMaster() { ModbusType = ModbusTypeEnum.ModbusTcp, Timeout = 10000 };
|
||||
modbusMaster.InitChannel(modbusChannel);
|
||||
await modbusChannel.SetupAsync(modbusChannel.Config);
|
||||
await modbusChannel.ConnectAsync(modbusChannel.ChannelOptions.ConnectTimeout, CancellationToken.None);
|
||||
await modbusMaster.ConnectAsync(CancellationToken.None);
|
||||
var adapter = modbusChannel.ReadOnlyDataHandlingAdapter as SingleStreamDataHandlingAdapter;
|
||||
|
||||
var task1 = Task.Run(async () =>
|
||||
@@ -94,7 +94,7 @@ public class ModbusTest
|
||||
var modbusMaster = new ModbusMaster() { ModbusType = ModbusTypeEnum.ModbusRtu, Timeout = 10000, Station = 1 };
|
||||
modbusMaster.InitChannel(modbusChannel);
|
||||
await modbusChannel.SetupAsync(modbusChannel.Config);
|
||||
await modbusChannel.ConnectAsync(modbusChannel.ChannelOptions.ConnectTimeout, CancellationToken.None);
|
||||
await modbusMaster.ConnectAsync(CancellationToken.None);
|
||||
var adapter = modbusChannel.ReadOnlyDataHandlingAdapter as SingleStreamDataHandlingAdapter;
|
||||
|
||||
var task1 = Task.Run(async () =>
|
||||
|
||||
@@ -41,7 +41,7 @@ public class SiemensS7Test
|
||||
var siemensS7Master = new SiemensS7Master() { SiemensS7Type = SiemensTypeEnum.S1200, Timeout = 10000 };
|
||||
siemensS7Master.InitChannel(siemensS7Channel);
|
||||
await siemensS7Channel.SetupAsync(siemensS7Channel.Config);
|
||||
await siemensS7Channel.ConnectAsync(siemensS7Channel.ChannelOptions.ConnectTimeout, CancellationToken.None);
|
||||
await siemensS7Master.ConnectAsync(CancellationToken.None);
|
||||
var adapter = siemensS7Channel.ReadOnlyDataHandlingAdapter as SingleStreamDataHandlingAdapter;
|
||||
await siemensS7Master.ConnectAsync(CancellationToken.None);
|
||||
var task1 = Task.Run(async () =>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"IsWriteMemory": "IsWriteMemory",
|
||||
"ModbusType": "ModbusType",
|
||||
"MulStation": "MultipleStations",
|
||||
"SendDelayTime": "SendDelayTime",
|
||||
"Station": "DefaultStation"
|
||||
},
|
||||
"ThingsGateway.Plugin.Modbus.ModbusSlaveVariableProperty": {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"IsWriteMemory": "立即写入内存",
|
||||
"ModbusType": "协议类型",
|
||||
"MulStation": "多站点",
|
||||
"SendDelayTime": "发送延时",
|
||||
"Station": "默认站号"
|
||||
},
|
||||
"ThingsGateway.Plugin.Modbus.ModbusSlaveVariableProperty": {
|
||||
|
||||
@@ -100,6 +100,7 @@ public class ModbusSlave : BusinessBase
|
||||
_plc.IsWriteMemory = _driverPropertys.IsWriteMemory;
|
||||
_plc.MulStation = _driverPropertys.MulStation;
|
||||
_plc.ModbusType = _driverPropertys.ModbusType;
|
||||
_plc.SendDelayTime = _driverPropertys.SendDelayTime;
|
||||
_plc.InitChannel(channel, LogMessage);
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -53,4 +53,7 @@ public class ModbusSlaveProperty : BusinessPropertyBase
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public bool IsWriteMemory { get; set; } = true;
|
||||
|
||||
[DynamicProperty]
|
||||
public int SendDelayTime { get; set; }
|
||||
}
|
||||
|
||||
@@ -152,7 +152,14 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_mqttClient?.SafeDispose();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (_mqttClient != null)
|
||||
{
|
||||
await _mqttClient.DisconnectAsync().ConfigureAwait(false);
|
||||
_mqttClient.SafeDispose();
|
||||
}
|
||||
});
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ public class OpcDaMaster : CollectBase
|
||||
/// <inheritdoc/>
|
||||
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
using var writeLock = ReadWriteLock.WriterLock();
|
||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
await ValueTask.CompletedTask.ConfigureAwait(false);
|
||||
var result = _plc.WriteItem(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value.GetObjectFromJToken()!));
|
||||
var results = new ConcurrentDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key).Name, a =>
|
||||
@@ -167,7 +167,8 @@ public class OpcDaMaster : CollectBase
|
||||
));
|
||||
|
||||
await Check(writeInfoLists, results, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
|
||||
LogMessage?.Debug(string.Format("Write result: {0} - {1}", DeviceName, results.Select(a => $"{a.Key} - {a.Key.Length} - {(a.Value.IsSuccess ? "Success" : a.Value.ErrorMessage)}").ToSystemTextJsonString(false)));
|
||||
return new(results);
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -275,7 +275,7 @@ public class OpcUaMaster : CollectBase
|
||||
/// <inheritdoc/>
|
||||
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
using var writeLock = ReadWriteLock.WriterLock();
|
||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken).ConfigureAwait(false);
|
||||
var results = new ConcurrentDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name!
|
||||
, a =>
|
||||
@@ -287,7 +287,8 @@ public class OpcUaMaster : CollectBase
|
||||
}));
|
||||
|
||||
await Check(writeInfoLists, results, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
|
||||
LogMessage?.Debug(string.Format("Write result: {0} - {1}", DeviceName, results.Select(a => $"{a.Key} - {a.Key.Length} - {(a.Value.IsSuccess ? "Success" : a.Value.ErrorMessage)}").ToSystemTextJsonString(false)));
|
||||
return new(results);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,27 +22,27 @@
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" />
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.235" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.235" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.235" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.235" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.235" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.235" GeneratePathProperty="true">
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.244" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
|
||||
using ThingsGateway.Debug;
|
||||
using ThingsGateway.Foundation.SiemensS7;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Sockets;
|
||||
@@ -94,7 +95,7 @@ public class SiemensS7Master : CollectFoundationBase
|
||||
|
||||
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
using var writeLock = ReadWriteLock.WriterLock();
|
||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 检查协议是否为空,如果为空则抛出异常
|
||||
if (FoundationDevice == null)
|
||||
@@ -133,6 +134,9 @@ public class SiemensS7Master : CollectFoundationBase
|
||||
operResults.TryAdd(writeInfo.Key.Name, r1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
||||
@@ -153,7 +157,8 @@ public class SiemensS7Master : CollectFoundationBase
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
|
||||
LogMessage?.Debug(string.Format("Write result: {0} - {1}", DeviceName, operResults.Select(a => $"{a.Key} - {a.Key.Length} - {(a.Value.IsSuccess ? "Success" : a.Value.ErrorMessage)}").ToSystemTextJsonString(false)));
|
||||
// 返回包含操作结果的字典
|
||||
return new Dictionary<string, OperResult>(operResults);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 写入变量,实现设备写入操作,注意执行写锁,using var writeLock = ReadWriteLock.WriterLock();
|
||||
// /// 写入变量,实现设备写入操作,注意执行写锁, using var writeLock =await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
// /// </summary>
|
||||
// protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
// {
|
||||
|
||||
Reference in New Issue
Block a user