diff --git a/src/ThingsGateway.Foundation/Protocol/ProtocolBase.cs b/src/ThingsGateway.Foundation/Protocol/ProtocolBase.cs index 7b7e23e42..a89b6a849 100644 --- a/src/ThingsGateway.Foundation/Protocol/ProtocolBase.cs +++ b/src/ThingsGateway.Foundation/Protocol/ProtocolBase.cs @@ -24,14 +24,17 @@ public abstract class ProtocolBase : DisposableObject, IProtocol public ProtocolBase(IChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); - channel.Collects.Add(this); - Channel = channel; - Logger = channel.Logger; - Channel.Starting += ChannelStarting; - Channel.Stoped += ChannelStoped; - Channel.Started += ChannelStarted; - Channel.ChannelReceived += ChannelReceived; - Channel.Config.ConfigurePlugins(ConfigurePlugins()); + lock (channel) + { + channel.Collects.Add(this); + Channel = channel; + Logger = channel.Logger; + Channel.Starting += ChannelStarting; + Channel.Stoped += ChannelStoped; + Channel.Started += ChannelStarted; + Channel.ChannelReceived += ChannelReceived; + Channel.Config.ConfigurePlugins(ConfigurePlugins()); + } } /// diff --git a/src/Version.props b/src/Version.props index 8ae2a0e40..50c180586 100644 --- a/src/Version.props +++ b/src/Version.props @@ -1,6 +1,6 @@ - 6.0.4.38 + 6.0.4.41 diff --git a/src/adapter/ThingsGateway.Foundation.Demo/S7MasterTest.cs b/src/adapter/ThingsGateway.Foundation.Demo/S7MasterTest.cs index 99b9ee0c2..18cda9caa 100644 --- a/src/adapter/ThingsGateway.Foundation.Demo/S7MasterTest.cs +++ b/src/adapter/ThingsGateway.Foundation.Demo/S7MasterTest.cs @@ -8,6 +8,7 @@ // QQ群:605534569 //------------------------------------------------------------------------------ +using ThingsGateway.Foundation.Json.Extension; using ThingsGateway.Foundation.SiemensS7; using TouchSocket.Core; @@ -35,7 +36,8 @@ internal class S7MasterTest addresss[1].Length = addresss[0].Data.Length; addresss[2].Data = new byte[] { 0x01, 0x02, 0x03, 0x04 }; addresss[2].Length = addresss[0].Data.Length; - var result = await siemensS7Master.S7RequestAsync(addresss, false, false); + var result = await siemensS7Master.S7WriteAsync(addresss); + Console.WriteLine(result.ToJsonNetString()); S7Variable s7Variable = new S7Variable(siemensS7Master, 200); diff --git a/src/adapter/ThingsGateway.Foundation.Demo/ThingsGateway.Foundation.Demo.csproj b/src/adapter/ThingsGateway.Foundation.Demo/ThingsGateway.Foundation.Demo.csproj index 35e5acd08..ecab4cbba 100644 --- a/src/adapter/ThingsGateway.Foundation.Demo/ThingsGateway.Foundation.Demo.csproj +++ b/src/adapter/ThingsGateway.Foundation.Demo/ThingsGateway.Foundation.Demo.csproj @@ -12,6 +12,4 @@ - - diff --git a/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/S7Send.cs b/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/S7Send.cs index 629415574..af52d86bc 100644 --- a/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/S7Send.cs +++ b/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/S7Send.cs @@ -8,6 +8,8 @@ // QQ群:605534569 //------------------------------------------------------------------------------ +using ThingsGateway.Foundation.Extension.Generic; + using TouchSocket.Core; namespace ThingsGateway.Foundation.SiemensS7; @@ -46,6 +48,9 @@ public class S7Request /// public int Length { get; set; } + + public int BitLength { get; set; } + public bool IsBit { get; set; } #endregion Request } @@ -56,7 +61,6 @@ internal class S7Send : ISendMessage { internal bool Handshake; internal byte[] HandshakeBytes; - internal bool IsBit; internal bool Read; internal SiemensAddress[] SiemensAddress; @@ -66,11 +70,10 @@ internal class S7Send : ISendMessage Handshake = true; } - public S7Send(SiemensAddress[] siemensAddress, bool read, bool isBit) + public S7Send(SiemensAddress[] siemensAddress, bool read) { SiemensAddress = siemensAddress; Read = read; - IsBit = isBit; } public int MaxLength => 2048; @@ -110,7 +113,7 @@ internal class S7Send : ISendMessage valueByteBlock.WriteByte(0x32);//协议id valueByteBlock.WriteByte(0x01);//请求 valueByteBlock.WriteUInt16(0x00, EndianType.Big);//冗余识别 - valueByteBlock.WriteUInt16(0x01, EndianType.Big);//数据引用 + valueByteBlock.WriteUInt16((ushort)this.Sign, EndianType.Big);//数据ID标识 valueByteBlock.WriteUInt16(parameterLen, EndianType.Big);//参数长度,item.len*12+2 valueByteBlock.WriteUInt16(0x00, EndianType.Big);//数据长度,data.len+4 ,写入时填写,读取时为0 //par @@ -157,7 +160,7 @@ internal class S7Send : ISendMessage valueByteBlock.WriteByte(0x32);//协议id valueByteBlock.WriteByte(0x01);//请求 valueByteBlock.WriteUInt16(0x00, EndianType.Big);//冗余识别 - valueByteBlock.WriteUInt16(0x01, EndianType.Big);//数据引用 + valueByteBlock.WriteUInt16((ushort)this.Sign, EndianType.Big);//数据ID标识 valueByteBlock.WriteUInt16(parameterLen, EndianType.Big);//参数长度,item.len*12+2 valueByteBlock.WriteUInt16(0, EndianType.Big);//数据长度,data.len+4 ,写入时填写,读取时为0 @@ -172,7 +175,7 @@ internal class S7Send : ISendMessage { var data = address.Data; byte len = (byte)address.Length; - bool isBit = (IsBit && len == 1); + bool isBit = (address.IsBit && len == 1); valueByteBlock.WriteByte(0x12);//Var 规范 valueByteBlock.WriteByte(0x0a);//剩余的字节长度 valueByteBlock.WriteByte(0x10);//Syntax ID @@ -190,14 +193,15 @@ internal class S7Send : ISendMessage { var data = address.Data; byte len = (byte)address.Length; - bool isBit = (IsBit && len == 1); + bool isBit = (address.IsBit && len == 1); + data = data.ArrayExpandToLengthEven(); //后面跟的是写入的数据信息 valueByteBlock.WriteByte(0); - valueByteBlock.WriteByte((byte)(isBit ? 3 : 4));//Bit:3;Byte:4;Counter或者Timer:9 - valueByteBlock.WriteUInt16((ushort)(isBit ? len : len * 8), EndianType.Big); + valueByteBlock.WriteByte((byte)(isBit ? address.DataCode == (byte)S7WordLength.Counter ? 9 : 3 : 4));//Bit:3;Byte:4;Counter或者Timer:9 + valueByteBlock.WriteUInt16((ushort)(isBit ? (byte)address.BitLength : len * 8), EndianType.Big); valueByteBlock.Write(data); - dataLen = (ushort)(dataLen +data.Length+4); + dataLen = (ushort)(dataLen + data.Length + 4); } ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen); valueByteBlock.Position = 2; diff --git a/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/SiemensAddress.cs b/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/SiemensAddress.cs index 9df0c2e4d..6d47b5405 100644 --- a/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/SiemensAddress.cs +++ b/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/SiemensAddress.cs @@ -31,6 +31,9 @@ public class SiemensAddress : S7Request this.DbBlock = siemensAddress.DbBlock; this.Data = siemensAddress.Data; this.Length = siemensAddress.Length; + + BitLength=siemensAddress.BitLength; + IsBit= siemensAddress.IsBit; } /// diff --git a/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs b/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs index ce7c8ac59..d2e1a5a68 100644 --- a/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs +++ b/src/adapter/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs @@ -9,6 +9,7 @@ //------------------------------------------------------------------------------ using System.Data; +using System.Net; using ThingsGateway.Foundation.Extension.String; @@ -23,8 +24,18 @@ public partial class SiemensS7Master : ProtocolBase /// public SiemensS7Master(IChannel channel) : base(channel) { - RegisterByteLength = 1; - ThingsGatewayBitConverter = new S7BitConverter(EndianType.Big); + lock (channel) + { + RegisterByteLength = 1; + ThingsGatewayBitConverter = new S7BitConverter(EndianType.Big); + + if (channel.Collects.Count(a => a.GetType() == typeof(SiemensS7Master)) > 1) + { + Channel.Starting -= ChannelStarting; + Channel.Stoped -= ChannelStoped; + Channel.Started -= ChannelStarted; + } + } } /// @@ -117,9 +128,8 @@ public partial class SiemensS7Master : ProtocolBase /// /// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组 /// - public async ValueTask> S7RequestAsync(SiemensAddress[] sAddresss, bool read, bool isBit, int bitLength = 0, CancellationToken cancellationToken = default) + public async ValueTask> S7ReadAsync(SiemensAddress[] sAddresss, CancellationToken cancellationToken = default) { - if (read) { var byteBlock = new ValueByteBlock(2048); try @@ -135,7 +145,7 @@ public partial class SiemensS7Master : ProtocolBase sAddress.Length = len; var result = await this.SendThenReturnAsync( - new S7Send([sAddress], read, isBit), cancellationToken: cancellationToken).ConfigureAwait(false); + new S7Send([sAddress], true), cancellationToken: cancellationToken).ConfigureAwait(false); if (!result.IsSuccess) return result; byteBlock.Write(result.Content); @@ -163,14 +173,27 @@ public partial class SiemensS7Master : ProtocolBase byteBlock.SafeDispose(); } } - else + } + /// + /// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组 + /// + public async ValueTask> S7WriteAsync(SiemensAddress[] sAddresss, CancellationToken cancellationToken = default) + { + + + var dictOperResult = new Dictionary(); + + void SetFailOperResult(OperResult operResult) + { + foreach (var item in sAddresss) + { + dictOperResult.TryAdd(item, operResult); + } + } + { var sAddress = sAddresss[0]; - if (sAddresss.Length > 1 && isBit) - { - return new OperResult("Only supports single write"); - } - else if (sAddresss.Length <= 1 && isBit) + if (sAddresss.Length <= 1 && sAddress.IsBit) { //读取,再写入 var byteBlock = new ValueByteBlock(2048); @@ -179,24 +202,36 @@ public partial class SiemensS7Master : ProtocolBase var addressLen = sAddress.Length == 0 ? 1 : sAddress.Length; if (addressLen > PduLength) - return new OperResult("Write length exceeds limit"); + { + SetFailOperResult(new OperResult("Write length exceeds limit")); + return dictOperResult; + } var result = await this.SendThenReturnAsync( - new S7Send([sAddress], true, isBit), cancellationToken: cancellationToken).ConfigureAwait(false); + new S7Send([sAddress], true), cancellationToken: cancellationToken).ConfigureAwait(false); - if (!result.IsSuccess) return result; - var vaue = sAddress.Data.ByteToBoolArray(bitLength); - for (int i = sAddress.BitCode; i < vaue.Length + sAddress.BitCode; i++) + if (!result.IsSuccess) { - result.Content[i / 8] = result.Content[i / 8].SetBit((i % 8), vaue[i - sAddress.BitCode]); + SetFailOperResult(result); + return dictOperResult; + } + + var value = sAddress.Data.ByteToBoolArray(sAddress.BitLength); + for (int i = sAddress.BitCode; i < value.Length + sAddress.BitCode; i++) + { + result.Content[i / 8] = result.Content[i / 8].SetBit((i % 8), value[i - sAddress.BitCode]); } sAddress.Data = result.Content; - return await this.SendThenReturnAsync( -new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).ConfigureAwait(false); + var wresult = await this.SendThenReturnAsync( +new S7Send([sAddress], false), cancellationToken: cancellationToken).ConfigureAwait(false); + dictOperResult.TryAdd(sAddress, wresult); + return dictOperResult; + } catch (Exception ex) { - return new OperResult(ex); + SetFailOperResult(new OperResult(ex)); + return dictOperResult; } finally { @@ -212,7 +247,7 @@ new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).Conf List addresses = new(); foreach (var item in sAddresss) { - siemensAddresses.Add(addresses); + siemensAddresses.Add(addresses); dataLen = (ushort)(dataLen + item.Data.Length + 4); ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen); if (telegramLen < PduLength) @@ -234,7 +269,8 @@ new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).Conf } else { - return new OperResult("Write length exceeds limit"); + SetFailOperResult(new OperResult("Write length exceeds limit")); + return dictOperResult; } } @@ -246,24 +282,25 @@ new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).Conf try { var result = await this.SendThenReturnAsync( - new S7Send(item.ToArray(), read, isBit), cancellationToken: cancellationToken).ConfigureAwait(false); - if(!result.IsSuccess) + new S7Send(item.ToArray(), false), cancellationToken: cancellationToken).ConfigureAwait(false); + foreach (var i1 in item) { - return result; + dictOperResult.TryAdd(i1, result); } } catch (Exception ex) { - return new OperResult(ex); + + SetFailOperResult(new OperResult(ex)); + return dictOperResult; } } - return new(); + return dictOperResult; } } } - #region 读写 /// @@ -272,7 +309,7 @@ new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).Conf try { var sAddress = SiemensAddress.ParseFrom(address, length); - return S7RequestAsync([sAddress], true, false, 0, cancellationToken); + return S7ReadAsync([sAddress], cancellationToken); } catch (Exception ex) { @@ -288,7 +325,7 @@ new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).Conf var sAddress = SiemensAddress.ParseFrom(address); sAddress.Data = value; sAddress.Length = value.Length; - return await S7RequestAsync([sAddress], false, false, 0, cancellationToken); + return (await S7WriteAsync([sAddress], cancellationToken)).FirstOrDefault().Value; } catch (Exception ex) { @@ -299,16 +336,14 @@ new S7Send([sAddress], false, isBit), cancellationToken: cancellationToken).Conf /// public override async ValueTask WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default) { - //if (value.Length > 1) - //{ - // return new OperResult(SiemensS7Resource.Localizer["MulWriteError"]); - //} try { var sAddress = SiemensAddress.ParseFrom(address); sAddress.Data = value.BoolArrayToByte(); sAddress.Length = sAddress.Data.Length; - return await S7RequestAsync([sAddress], false, true, value.Length, cancellationToken); + sAddress.BitLength = value.Length; + sAddress.IsBit = true; + return (await S7WriteAsync([sAddress], cancellationToken)).FirstOrDefault().Value; } catch (Exception ex) { diff --git a/src/plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs b/src/plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs index 9535456d2..e71f6ed69 100644 --- a/src/plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs +++ b/src/plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs @@ -8,6 +8,13 @@ // QQ群:605534569 //------------------------------------------------------------------------------ +using Newtonsoft.Json.Linq; + +using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; +using System.IO; + +using ThingsGateway.Foundation.SiemensS7; using ThingsGateway.Gateway.Application; using TouchSocket.Core; @@ -54,6 +61,127 @@ public class SiemensS7Master : CollectBase }; } + /// + /// 获取jtoken值代表的字节数组,不包含字符串 + /// + /// + /// + /// + public byte[] GetBytes(DataTypeEnum dataType, JToken value) + { + //排除字符串 + if (value is JArray jArray) + { + return dataType switch + { + DataTypeEnum.Boolean => jArray.ToObject().BoolArrayToByte(), + DataTypeEnum.Byte => jArray.ToObject(), + DataTypeEnum.Int16 => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.UInt16 => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.Int32 => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.UInt32 => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.Int64 => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.UInt64 => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.Single => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + DataTypeEnum.Double => _plc.ThingsGatewayBitConverter.GetBytes(jArray.ToObject()), + _ => throw new(DefaultResource.Localizer["DataTypeNotSupported", dataType]), + }; + } + else + { + return dataType switch + { + DataTypeEnum.Boolean => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.Byte => [value.ToObject()], + DataTypeEnum.Int16 => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.UInt16 => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.Int32 => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.UInt32 => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.Int64 => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.UInt64 => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.Single => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + DataTypeEnum.Double => _plc.ThingsGatewayBitConverter.GetBytes(value.ToObject()), + _ => throw new(DefaultResource.Localizer["DataTypeNotSupported", dataType]), + }; + } + } + + + protected override async ValueTask> WriteValuesAsync(Dictionary writeInfoLists, CancellationToken cancellationToken) + { + try + { + // 如果是单线程模式,则等待写入锁 + if (IsSingleThread) + await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + // 检查协议是否为空,如果为空则抛出异常 + if (Protocol == null) + throw new NotSupportedException(); + + // 创建用于存储操作结果的并发字典 + ConcurrentDictionary operResults = new(); + + //转换 + Dictionary addresses = new(); + var w1 = writeInfoLists.Where(a => a.Key.DataType != DataTypeEnum.String); + var w2 = writeInfoLists.Where(a => a.Key.DataType == DataTypeEnum.String); + foreach (var item in w1) + { + SiemensAddress siemensAddress = SiemensAddress.ParseFrom(item.Key.RegisterAddress); + siemensAddress.Data = GetBytes(item.Key.DataType, item.Value); + siemensAddress.Length = siemensAddress.Data.Length; + siemensAddress.BitLength = 1; + siemensAddress.IsBit = item.Key.DataType == DataTypeEnum.Boolean; + if (item.Key.DataType == DataTypeEnum.Boolean) + { + if (item.Value is JArray jArray) + { + siemensAddress.BitLength = jArray.ToObject().Length; + } + } + addresses.Add(item.Key, siemensAddress); + } + + var result = await _plc.S7WriteAsync(addresses.Select(a => a.Value).ToArray(), cancellationToken).ConfigureAwait(false); + foreach (var writeInfo in addresses) + { + if (result.TryGetValue(writeInfo.Value, out var r1)) + { + operResults.TryAdd(writeInfo.Key.Name, r1); + } + } + + // 使用并发方式遍历写入信息列表,并进行异步写入操作 + await w2.ParallelForEachAsync(async (writeInfo, cancellationToken) => + { + try + { + // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 + var result = await Protocol.WriteAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false); + + // 将操作结果添加到结果字典中,使用变量名称作为键 + operResults.TryAdd(writeInfo.Key.Name, result); + } + catch (Exception ex) + { + operResults.TryAdd(writeInfo.Key.Name, new(ex)); + } + }, CollectProperties.ConcurrentCount, cancellationToken).ConfigureAwait(false); + + + // 返回包含操作结果的字典 + return new Dictionary(operResults); + } + finally + { + // 如果是单线程模式,则释放写入锁 + if (IsSingleThread) + WriteLock.Release(); + } + + } + /// /// /// diff --git a/src/tools/ThingsGateway.Startup/ThingsGateway.Startup.csproj b/src/tools/ThingsGateway.Startup/ThingsGateway.Startup.csproj index 48a37711e..73ffcaf8c 100644 --- a/src/tools/ThingsGateway.Startup/ThingsGateway.Startup.csproj +++ b/src/tools/ThingsGateway.Startup/ThingsGateway.Startup.csproj @@ -9,7 +9,7 @@ - +