From ba332ad3a846448e1d9dfd1ca0ba0e5928efce52 Mon Sep 17 00:00:00 2001 From: Corentin Gallet <116062990+corentingallet@users.noreply.github.com> Date: Wed, 1 Mar 2023 09:58:18 +0100 Subject: [PATCH 01/25] Update index.md Missing a ";" in the first example. --- doc/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.md b/doc/index.md index ae4fed9..ae23603 100644 --- a/doc/index.md +++ b/doc/index.md @@ -54,7 +54,7 @@ client.Connect(); client.Connect(IPAddress.Parse("127.0.0.1")); // use specified IP adress and port -client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 502)) +client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 502)); ``` ## Modbus RTU client From f5b70bebc71d705b065909fed317dea150ea277c Mon Sep 17 00:00:00 2001 From: Lassi Helynranta Date: Thu, 31 Aug 2023 11:56:31 +0300 Subject: [PATCH 02/25] Add option for raising event even if values of buffer have not changed --- src/FluentModbus/Server/ModbusRequestHandler.cs | 6 +++--- src/FluentModbus/Server/ModbusServer.cs | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/FluentModbus/Server/ModbusRequestHandler.cs b/src/FluentModbus/Server/ModbusRequestHandler.cs index f2659e3..872a54b 100644 --- a/src/FluentModbus/Server/ModbusRequestHandler.cs +++ b/src/FluentModbus/Server/ModbusRequestHandler.cs @@ -191,7 +191,7 @@ private void DetectChangedRegisters(int startingAddress, Span oldValues, for (int i = 0; i < newValues.Length; i++) { - if (newValues[i] != oldValues[i]) + if (newValues[i] != oldValues[i] || ModbusServer.AlwaysRaiseChangedEvent) { changedRegisters[index] = startingAddress + i; index++; @@ -352,7 +352,7 @@ private void ProcessWriteSingleCoil() coils[bufferByteIndex] = newValue; - if (ModbusServer.EnableRaisingEvents && newValue != oldValue) + if (ModbusServer.EnableRaisingEvents && (newValue != oldValue || ModbusServer.AlwaysRaiseChangedEvent)) ModbusServer.OnCoilsChanged(UnitIdentifier, new int[] { outputAddress }); FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleCoil); @@ -379,7 +379,7 @@ private void ProcessWriteSingleRegister() var newValue = registerValue; holdingRegisters[registerAddress] = newValue; - if (ModbusServer.EnableRaisingEvents && newValue != oldValue) + if (ModbusServer.EnableRaisingEvents && (newValue != oldValue || ModbusServer.AlwaysRaiseChangedEvent)) ModbusServer.OnRegistersChanged(UnitIdentifier, new int[] { registerAddress }); FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleRegister); diff --git a/src/FluentModbus/Server/ModbusServer.cs b/src/FluentModbus/Server/ModbusServer.cs index 3cc6cf0..7c94b98 100644 --- a/src/FluentModbus/Server/ModbusServer.cs +++ b/src/FluentModbus/Server/ModbusServer.cs @@ -148,6 +148,11 @@ protected ModbusServer(bool isAsynchronous) /// public bool EnableRaisingEvents { get; set; } + /// + /// Trigger the RegisterChanged or CoilsChanged event even when value has not been updated. Default: false. + /// + public bool AlwaysRaiseChangedEvent { get; set; } = false; + internal bool IsSingleZeroUnitMode => UnitIdentifiers.Count == 1 && UnitIdentifiers[0] == 0; private protected CancellationTokenSource CTS { get; private set; } = new CancellationTokenSource(); From 3adda0bf44034a08356ab8f9251f857e74d8ae25 Mon Sep 17 00:00:00 2001 From: Luciano Martorella Date: Mon, 9 Oct 2023 21:15:12 +0200 Subject: [PATCH 03/25] - Fixed propagation of cancellationToken --- src/FluentModbus/Client/ModbusClientAsync.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs index 7ab5fcb..91979ad 100644 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -88,7 +88,7 @@ public async Task WriteMultipleRegistersAsync(int unitIdentifier, int startin if (SwapBytes) ModbusUtils.SwitchEndianness(dataset.AsSpan()); - await WriteMultipleRegistersAsync(unitIdentifier_converted, startingAddress_converted, MemoryMarshal.Cast(dataset).ToArray()).ConfigureAwait(false); + await WriteMultipleRegistersAsync(unitIdentifier_converted, startingAddress_converted, MemoryMarshal.Cast(dataset).ToArray(), cancellationToken).ConfigureAwait(false); } /// @@ -213,7 +213,7 @@ public async Task> ReadInputRegistersAsync(int unitIdentifier, int var count_converted = ConvertUshort(count); var dataset = SpanExtensions.Cast(await - ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted)).ConfigureAwait(false)); + ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset); From 2539a2664f12dbbbfae99478d2517735a84d6a25 Mon Sep 17 00:00:00 2001 From: Luciano Martorella Date: Mon, 23 Oct 2023 21:49:23 +0200 Subject: [PATCH 04/25] - Fixed exception for malformed messages --- src/FluentModbus/Client/ModbusClient.cs | 30 ++++++++++---------- src/FluentModbus/Client/ModbusClientAsync.cs | 30 ++++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index f70a548..16f27c6 100644 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -152,12 +152,12 @@ public Span ReadHoldingRegisters(byte unitIdentifier, ushort startingAddre writer.Write(startingAddress); // 08-09 Starting Address writer.Write(quantity); // 10-11 Quantity of Input Registers } - }).Slice(2); + }); - if (buffer.Length < quantity * 2) + if (buffer.Length < quantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -242,12 +242,12 @@ public Span ReadCoils(int unitIdentifier, int startingAddress, int quantit writer.Write(startingAddress_converted); // 08-09 Starting Address writer.Write(quantity_converted); // 10-11 Quantity of Coils } - }).Slice(2); + }); - if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8)) + if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8) + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -276,12 +276,12 @@ public Span ReadDiscreteInputs(int unitIdentifier, int startingAddress, in writer.Write(startingAddress_converted); // 08-09 Starting Address writer.Write(quantity_converted); // 10-11 Quantity of Coils } - }).Slice(2); + }); - if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8)) + if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8) + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -328,12 +328,12 @@ public Span ReadInputRegisters(byte unitIdentifier, ushort startingAddress writer.Write(startingAddress); // 08-09 Starting Address writer.Write(quantity); // 10-11 Quantity of Input Registers } - }).Slice(2); + }); - if (buffer.Length < quantity * 2) + if (buffer.Length < quantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -529,12 +529,12 @@ public Span ReadWriteMultipleRegisters(byte unitIdentifier, ushort readSta writer.Write((byte)(writeQuantity * 2)); // 16 Byte Count = Quantity to Write * 2 writer.Write(dataset, 0, dataset.Length); - }).Slice(2); + }); - if (buffer.Length < readQuantity * 2) + if (buffer.Length < readQuantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs index 7ab5fcb..b06d9e7 100644 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -64,12 +64,12 @@ public async Task> ReadHoldingRegistersAsync(byte unitIdentifier, u writer.Write(startingAddress); // 08-09 Starting Address writer.Write(quantity); // 10-11 Quantity of Input Registers } - }, cancellationToken).ConfigureAwait(false)).Slice(2); + }, cancellationToken).ConfigureAwait(false)); - if (buffer.Length < quantity * 2) + if (buffer.Length < quantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -155,12 +155,12 @@ public async Task> ReadCoilsAsync(int unitIdentifier, int startingA writer.Write(startingAddress_converted); // 08-09 Starting Address writer.Write(quantity_converted); // 10-11 Quantity of Coils } - }, cancellationToken).ConfigureAwait(false)).Slice(2); + }, cancellationToken).ConfigureAwait(false)); - if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8)) + if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8) + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -190,12 +190,12 @@ public async Task> ReadDiscreteInputsAsync(int unitIdentifier, int writer.Write(startingAddress_converted); // 08-09 Starting Address writer.Write(quantity_converted); // 10-11 Quantity of Coils } - }, cancellationToken).ConfigureAwait(false)).Slice(2); + }, cancellationToken).ConfigureAwait(false)); - if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8)) + if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8) + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -244,12 +244,12 @@ public async Task> ReadInputRegistersAsync(byte unitIdentifier, ush writer.Write(startingAddress); // 08-09 Starting Address writer.Write(quantity); // 10-11 Quantity of Input Registers } - }, cancellationToken).ConfigureAwait(false)).Slice(2); + }, cancellationToken).ConfigureAwait(false)); - if (buffer.Length < quantity * 2) + if (buffer.Length < quantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// @@ -449,12 +449,12 @@ public async Task> ReadWriteMultipleRegistersAsync(byte unitIdentif writer.Write((byte)(writeQuantity * 2)); // 16 Byte Count = Quantity to Write * 2 writer.Write(dataset, 0, dataset.Length); - }, cancellationToken).ConfigureAwait(false)).Slice(2); + }, cancellationToken).ConfigureAwait(false)); - if (buffer.Length < readQuantity * 2) + if (buffer.Length < readQuantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); - return buffer; + return buffer.Slice(2); } /// From a97cf08a82be01138621db1b37323d9c3ec76e60 Mon Sep 17 00:00:00 2001 From: Luciano Martorella Date: Sun, 26 Nov 2023 22:11:29 +0100 Subject: [PATCH 05/25] - Fixed ArgumentOutOfRangeException in case of custom modbus errors received. Still raise a ModbusError instead. --- src/FluentModbus/Client/ModbusClient.cs | 2 +- .../Resources/ErrorMessage.Designer.cs | 91 ++++++++++--------- src/FluentModbus/Resources/ErrorMessage.resx | 3 + 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index 16f27c6..3fd01b1 100644 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -71,7 +71,7 @@ internal void ProcessError(ModbusFunctionCode functionCode, ModbusExceptionCode throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x0B_GatewayTargetDeviceFailedToRespond); default: - throw new ArgumentOutOfRangeException(ErrorMessage.ModbusClient_InvalidExceptionCode); + throw new ModbusException(exceptionCode, string.Format(ErrorMessage.ModbusClient_Unknown_Error, (int)exceptionCode)); } } diff --git a/src/FluentModbus/Resources/ErrorMessage.Designer.cs b/src/FluentModbus/Resources/ErrorMessage.Designer.cs index 6d3c2fc..5550d9a 100644 --- a/src/FluentModbus/Resources/ErrorMessage.Designer.cs +++ b/src/FluentModbus/Resources/ErrorMessage.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.42000 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -13,13 +13,13 @@ namespace FluentModbus { /// - /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert - // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. - // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ErrorMessage { @@ -33,7 +33,7 @@ internal ErrorMessage() { } /// - /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ internal ErrorMessage() { } /// - /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ internal ErrorMessage() { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Invalid use of broadcast: Unit identifier '0' can only be used for write operations. ähnelt. + /// Looks up a localized string similar to Invalid use of broadcast: Unit identifier '0' can only be used for write operations.. /// internal static string Modbus_InvalidUseOfBroadcast { get { @@ -70,7 +70,7 @@ internal static string Modbus_InvalidUseOfBroadcast { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The value is invalid. Valid values are in the range of 0 - 65535. ähnelt. + /// Looks up a localized string similar to The value is invalid. Valid values are in the range of 0 - 65535.. /// internal static string Modbus_InvalidValueUShort { get { @@ -79,7 +79,7 @@ internal static string Modbus_InvalidValueUShort { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The function code received in the query is not an allowable action for the server. This may be because the function code is only applicable to newer devices, and was not implemented in the unit selected. It could also indicate that the server is in the wrong state to process a request of this type, for example because it is unconfigured and is being asked to return register values. ähnelt. + /// Looks up a localized string similar to The function code received in the query is not an allowable action for the server. This may be because the function code is only applicable to newer devices, and was not implemented in the unit selected. It could also indicate that the server is in the wrong state to process a request of this type, for example because it is unconfigured and is being asked to return register values.. /// internal static string ModbusClient_0x01_IllegalFunction { get { @@ -88,7 +88,7 @@ internal static string ModbusClient_0x01_IllegalFunction { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The data address received in the query is not an allowable address for the server. More specifically, the combination of reference number and transfer length is invalid. ähnelt. + /// Looks up a localized string similar to The data address received in the query is not an allowable address for the server. More specifically, the combination of reference number and transfer length is invalid.. /// internal static string ModbusClient_0x02_IllegalDataAddress { get { @@ -97,7 +97,7 @@ internal static string ModbusClient_0x02_IllegalDataAddress { } /// - /// Sucht eine lokalisierte Zeichenfolge, die A value contained in the query data field is not an allowable value for server. This indicates a fault in the structure of the remainder of a complex request, such as that the implied length is incorrect. ähnelt. + /// Looks up a localized string similar to A value contained in the query data field is not an allowable value for server. This indicates a fault in the structure of the remainder of a complex request, such as that the implied length is incorrect.. /// internal static string ModbusClient_0x03_IllegalDataValue { get { @@ -106,7 +106,7 @@ internal static string ModbusClient_0x03_IllegalDataValue { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The quantity of registers is out of range (1..123). Make sure to request a minimum of one register. If you use the generic overload methods, please note that a single register consists of 2 bytes. If, for example, 1 x int32 value is requested, this results in a read operation of 2 registers. ähnelt. + /// Looks up a localized string similar to The quantity of registers is out of range (1..123). Make sure to request a minimum of one register. If you use the generic overload methods, please note that a single register consists of 2 bytes. If, for example, 1 x int32 value is requested, this results in a read operation of 2 registers.. /// internal static string ModbusClient_0x03_IllegalDataValue_0x7B { get { @@ -115,7 +115,7 @@ internal static string ModbusClient_0x03_IllegalDataValue_0x7B { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The quantity of registers is out of range (1..125). Make sure to request a minimum of one register. If you use the generic overload methods, please note that a single register consists of 2 bytes. If, for example, 1 x int32 value is requested, this results in a read operation of 2 registers. ähnelt. + /// Looks up a localized string similar to The quantity of registers is out of range (1..125). Make sure to request a minimum of one register. If you use the generic overload methods, please note that a single register consists of 2 bytes. If, for example, 1 x int32 value is requested, this results in a read operation of 2 registers.. /// internal static string ModbusClient_0x03_IllegalDataValue_0x7D { get { @@ -124,7 +124,7 @@ internal static string ModbusClient_0x03_IllegalDataValue_0x7D { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The quantity of coils is out of range (1..2000). ähnelt. + /// Looks up a localized string similar to The quantity of coils is out of range (1..2000).. /// internal static string ModbusClient_0x03_IllegalDataValue_0x7D0 { get { @@ -133,7 +133,7 @@ internal static string ModbusClient_0x03_IllegalDataValue_0x7D0 { } /// - /// Sucht eine lokalisierte Zeichenfolge, die An unrecoverable error occurred while the server was attempting to perform the requested action. ähnelt. + /// Looks up a localized string similar to An unrecoverable error occurred while the server was attempting to perform the requested action.. /// internal static string ModbusClient_0x04_ServerDeviceFailure { get { @@ -142,7 +142,7 @@ internal static string ModbusClient_0x04_ServerDeviceFailure { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The server has accepted the request and is processing it, but a long duration of time will be required to do so. ähnelt. + /// Looks up a localized string similar to The server has accepted the request and is processing it, but a long duration of time will be required to do so.. /// internal static string ModbusClient_0x05_Acknowledge { get { @@ -151,7 +151,7 @@ internal static string ModbusClient_0x05_Acknowledge { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The server is engaged in processing a long–duration program command. ähnelt. + /// Looks up a localized string similar to The server is engaged in processing a long–duration program command.. /// internal static string ModbusClient_0x06_ServerDeviceBusy { get { @@ -160,7 +160,7 @@ internal static string ModbusClient_0x06_ServerDeviceBusy { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The server attempted to read record file, but detected a parity error in the memory. ähnelt. + /// Looks up a localized string similar to The server attempted to read record file, but detected a parity error in the memory.. /// internal static string ModbusClient_0x08_MemoryParityError { get { @@ -169,7 +169,7 @@ internal static string ModbusClient_0x08_MemoryParityError { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The gateway was unable to allocate an internal communication path from the input port to the output port for processing the request. ähnelt. + /// Looks up a localized string similar to The gateway was unable to allocate an internal communication path from the input port to the output port for processing the request.. /// internal static string ModbusClient_0x0A_GatewayPathUnavailable { get { @@ -178,7 +178,7 @@ internal static string ModbusClient_0x0A_GatewayPathUnavailable { } /// - /// Sucht eine lokalisierte Zeichenfolge, die No response was obtained from the target device ähnelt. + /// Looks up a localized string similar to No response was obtained from the target device. /// internal static string ModbusClient_0x0B_GatewayTargetDeviceFailedToRespond { get { @@ -187,7 +187,7 @@ internal static string ModbusClient_0x0B_GatewayTargetDeviceFailedToRespond { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Array length must be equal to two bytes. ähnelt. + /// Looks up a localized string similar to Array length must be equal to two bytes.. /// internal static string ModbusClient_ArrayLengthMustBeEqualToTwo { get { @@ -196,7 +196,7 @@ internal static string ModbusClient_ArrayLengthMustBeEqualToTwo { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Array length must be greater than two bytes and even. ähnelt. + /// Looks up a localized string similar to Array length must be greater than two bytes and even.. /// internal static string ModbusClient_ArrayLengthMustBeGreaterThanTwoAndEven { get { @@ -205,7 +205,7 @@ internal static string ModbusClient_ArrayLengthMustBeGreaterThanTwoAndEven { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The exception code received from the server is invalid. ähnelt. + /// Looks up a localized string similar to The exception code received from the server is invalid.. /// internal static string ModbusClient_InvalidExceptionCode { get { @@ -214,7 +214,7 @@ internal static string ModbusClient_InvalidExceptionCode { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The protocol identifier is invalid. ähnelt. + /// Looks up a localized string similar to The protocol identifier is invalid.. /// internal static string ModbusClient_InvalidProtocolIdentifier { get { @@ -223,7 +223,7 @@ internal static string ModbusClient_InvalidProtocolIdentifier { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The responsed function code is invalid. ähnelt. + /// Looks up a localized string similar to The responsed function code is invalid.. /// internal static string ModbusClient_InvalidResponseFunctionCode { get { @@ -232,7 +232,7 @@ internal static string ModbusClient_InvalidResponseFunctionCode { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The response message length is invalid. ähnelt. + /// Looks up a localized string similar to The response message length is invalid.. /// internal static string ModbusClient_InvalidResponseMessageLength { get { @@ -241,7 +241,7 @@ internal static string ModbusClient_InvalidResponseMessageLength { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The unit identifier is invalid. Valid node addresses are in the range of 0 - 247. Use address '0' to broadcast write command to all available servers. ähnelt. + /// Looks up a localized string similar to The unit identifier is invalid. Valid node addresses are in the range of 0 - 247. Use address '0' to broadcast write command to all available servers.. /// internal static string ModbusClient_InvalidUnitIdentifier { get { @@ -250,7 +250,7 @@ internal static string ModbusClient_InvalidUnitIdentifier { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Quantity must be a positive integer number. Choose the 'count' parameter such that an even number of bytes is requested. ähnelt. + /// Looks up a localized string similar to Quantity must be a positive integer number. Choose the 'count' parameter such that an even number of bytes is requested.. /// internal static string ModbusClient_QuantityMustBePositiveInteger { get { @@ -259,7 +259,7 @@ internal static string ModbusClient_QuantityMustBePositiveInteger { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The TCP connection closed unexpectedly. ähnelt. + /// Looks up a localized string similar to The TCP connection closed unexpectedly.. /// internal static string ModbusClient_TcpConnectionClosedUnexpectedly { get { @@ -268,7 +268,7 @@ internal static string ModbusClient_TcpConnectionClosedUnexpectedly { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Could not connect within the specified time. ähnelt. + /// Looks up a localized string similar to Could not connect within the specified time.. /// internal static string ModbusClient_TcpConnectTimeout { get { @@ -277,7 +277,16 @@ internal static string ModbusClient_TcpConnectTimeout { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The unit identifier is invalid. Valid node addresses are in the range of 1 - 247. ähnelt. + /// Looks up a localized string similar to Unknonw {0} Modbus error received.. + /// + internal static string ModbusClient_Unknown_Error { + get { + return ResourceManager.GetString("ModbusClient_Unknown_Error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The unit identifier is invalid. Valid node addresses are in the range of 1 - 247.. /// internal static string ModbusServer_InvalidUnitIdentifier { get { @@ -286,16 +295,16 @@ internal static string ModbusServer_InvalidUnitIdentifier { } /// - /// Sucht eine lokalisierte Zeichenfolge, die No unit found for the specified unit identifier. ähnelt. + /// Looks up a localized string similar to No unit found for the specified unit identifier.. /// internal static string ModbusServer_UnitIdentifierNotFound { get { return ResourceManager.GetString("ModbusServer_UnitIdentifierNotFound", resourceCulture); } } - + /// - /// Sucht eine lokalisierte Zeichenfolge, die There is no valid request available. ähnelt. + /// Looks up a localized string similar to There is no valid request available.. /// internal static string ModbusTcpRequestHandler_NoValidRequestAvailable { get { diff --git a/src/FluentModbus/Resources/ErrorMessage.resx b/src/FluentModbus/Resources/ErrorMessage.resx index 2674c96..8f59aa7 100644 --- a/src/FluentModbus/Resources/ErrorMessage.resx +++ b/src/FluentModbus/Resources/ErrorMessage.resx @@ -183,6 +183,9 @@ Could not connect within the specified time. + + Unknonw {0} Modbus error received. + The unit identifier is invalid. Valid node addresses are in the range of 1 - 247. From e3e2634e55b95fc967c091737723534812561347 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 08:40:17 +0100 Subject: [PATCH 06/25] Switch to .NET 8 and update build scripts --- .editorconfig | 4 ++++ Directory.Build.props | 3 ++- build/print_version.py | 4 ++-- build/release.py | 1 - build/version.py | 6 +++--- src/Directory.Build.props | 4 +++- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.editorconfig b/.editorconfig index cc61ddd..0ccff01 100755 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,7 @@ +# How to format: +# (1) Add dotnet_diagnostic.XXXX.severity = error +# (2) Run dotnet-format: dotnet format --diagnostics XXXX + [*.cs] # "run cleanup": https://betterprogramming.pub/enforce-net-code-style-with-editorconfig-d2f0d79091ac # TODO: build real editorconfig file: https://github.com/dotnet/roslyn/blob/main/.editorconfig diff --git a/Directory.Build.props b/Directory.Build.props index 83cb829..8220ece 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,10 +2,11 @@ true - net6.0 + net8.0 enable enable true + latest https://www.myget.org/F/apollo3zehn-dev/api/v3/index.json diff --git a/build/print_version.py b/build/print_version.py index ff4e96a..5a041ec 100755 --- a/build/print_version.py +++ b/build/print_version.py @@ -4,9 +4,9 @@ build = sys.argv[1] is_final_build = sys.argv[2] == "true" -as_pypi_version = sys.argv[3] == "true" +version_type = sys.argv[3] if is_final_build: build = None -print(version.get_version(build, as_pypi_version)) +print(version.get_version(build, version_type)) diff --git a/build/release.py b/build/release.py index 8e271f8..f493c37 100755 --- a/build/release.py +++ b/build/release.py @@ -1,4 +1,3 @@ -import itertools import subprocess import sys import re diff --git a/build/version.py b/build/version.py index 1e75b1f..4cef349 100755 --- a/build/version.py +++ b/build/version.py @@ -2,11 +2,11 @@ from typing import Optional -def get_version(build: Optional[str], as_pypi_version: bool = False) -> str: +def get_version(build: Optional[str], version_type: str = "default") -> str: with open("version.json", "r") as fh: version_data = json.load(fh) - + # version version = version_data["version"] suffix = version_data["suffix"] @@ -17,7 +17,7 @@ def get_version(build: Optional[str], as_pypi_version: bool = False) -> str: if build: # PEP440 does not support SemVer versioning (https://semver.org/#spec-item-9) - if as_pypi_version: + if version_type == "pypi": version = f"{version}{int(build):03d}" else: diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 28e1aa0..99d638f 100755 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,7 +1,8 @@ - + + true embedded @@ -10,6 +11,7 @@ + $(VERSION)+$(GITHUB_SHA) $(VERSION.Split('.')[0]).0.0.0 $(VERSION.Split('.')[0]).$(VERSION.Split('.')[1]).$(VERSION.Split('.')[2].Split('-')[0]).0 From e40aad679672973e8b6fcb6ca641d8af5aaef08e Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 08:55:36 +0100 Subject: [PATCH 07/25] Minor performance improvement --- .../Server/ModbusRequestHandler.cs | 25 ++++++++++++++----- src/FluentModbus/Server/ModbusServer.cs | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/FluentModbus/Server/ModbusRequestHandler.cs b/src/FluentModbus/Server/ModbusRequestHandler.cs index 872a54b..3dc7258 100644 --- a/src/FluentModbus/Server/ModbusRequestHandler.cs +++ b/src/FluentModbus/Server/ModbusRequestHandler.cs @@ -187,18 +187,31 @@ private void DetectChangedRegisters(int startingAddress, Span oldValues, { Span changedRegisters = stackalloc int[newValues.Length]; - var index = 0; + var length = 0; - for (int i = 0; i < newValues.Length; i++) + if (ModbusServer.AlwaysRaiseChangedEvent) { - if (newValues[i] != oldValues[i] || ModbusServer.AlwaysRaiseChangedEvent) + for (int i = 0; i < newValues.Length; i++) { - changedRegisters[index] = startingAddress + i; - index++; + changedRegisters[length] = startingAddress + i; + } + + length = newValues.Length; + } + + else + { + for (int i = 0; i < newValues.Length; i++) + { + if (newValues[i] != oldValues[i]) + { + changedRegisters[length] = startingAddress + i; + length++; + } } } - ModbusServer.OnRegistersChanged(UnitIdentifier, changedRegisters.Slice(0, index).ToArray()); + ModbusServer.OnRegistersChanged(UnitIdentifier, changedRegisters.Slice(0, length).ToArray()); } // class 0 diff --git a/src/FluentModbus/Server/ModbusServer.cs b/src/FluentModbus/Server/ModbusServer.cs index 7c94b98..69c9aaf 100644 --- a/src/FluentModbus/Server/ModbusServer.cs +++ b/src/FluentModbus/Server/ModbusServer.cs @@ -149,7 +149,7 @@ protected ModbusServer(bool isAsynchronous) public bool EnableRaisingEvents { get; set; } /// - /// Trigger the RegisterChanged or CoilsChanged event even when value has not been updated. Default: false. + /// Trigger the RegistersChanged or CoilsChanged event even when value has not been updated. Default: false. /// public bool AlwaysRaiseChangedEvent { get; set; } = false; From 654d2a188f68d7c6e42db92c544bb4aef7446912 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 09:29:42 +0100 Subject: [PATCH 08/25] Add tests --- .../Server/ModbusRequestHandler.cs | 3 +- tests/FluentModbus.Tests/ModbusServerTests.cs | 43 ++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/FluentModbus/Server/ModbusRequestHandler.cs b/src/FluentModbus/Server/ModbusRequestHandler.cs index 3dc7258..c050bbd 100644 --- a/src/FluentModbus/Server/ModbusRequestHandler.cs +++ b/src/FluentModbus/Server/ModbusRequestHandler.cs @@ -194,9 +194,8 @@ private void DetectChangedRegisters(int startingAddress, Span oldValues, for (int i = 0; i < newValues.Length; i++) { changedRegisters[length] = startingAddress + i; + length++; } - - length = newValues.Length; } else diff --git a/tests/FluentModbus.Tests/ModbusServerTests.cs b/tests/FluentModbus.Tests/ModbusServerTests.cs index 4df84e2..7052900 100644 --- a/tests/FluentModbus.Tests/ModbusServerTests.cs +++ b/tests/FluentModbus.Tests/ModbusServerTests.cs @@ -306,11 +306,12 @@ public void CanSetAndGetBigEndianInputRegisters() } [Theory] - [InlineData(true, false, true)] - [InlineData(false, true, true)] - [InlineData(false, false, false)] - [InlineData(true, true, false)] - public async Task CanDetectCoilChanged(bool initialValue, bool newValue, bool expected) + [InlineData(true, false, true, false)] + [InlineData(false, true, true, false)] + [InlineData(false, false, false, false)] + [InlineData(true, true, false, false)] + [InlineData(false, false, true, true)] + public async Task CanDetectCoilChanged(bool initialValue, bool newValue, bool expected, bool alwaysRaiseChangedEvent) { // Arrange var actual = false; @@ -319,7 +320,8 @@ public async Task CanDetectCoilChanged(bool initialValue, bool newValue, bool ex using var server = new ModbusTcpServer() { - EnableRaisingEvents = true + EnableRaisingEvents = true, + AlwaysRaiseChangedEvent = alwaysRaiseChangedEvent }; server.GetCoils().Set(address, initialValue); @@ -346,10 +348,11 @@ await Task.Run(() => } [Theory] - [InlineData(99, 100, true)] - [InlineData(0, -1, true)] - [InlineData(1, 1, false)] - public async Task CanDetectRegisterChanged(short initialValue, short newValue, bool expected) + [InlineData(99, 100, true, false)] + [InlineData(0, -1, true, false)] + [InlineData(1, 1, false, false)] + [InlineData(0, 0, true, true)] + public async Task CanDetectRegisterChanged(short initialValue, short newValue, bool expected, bool alwaysRaiseChangedEvent) { // Arrange var actual = false; @@ -358,7 +361,8 @@ public async Task CanDetectRegisterChanged(short initialValue, short newValue, b using var server = new ModbusTcpServer() { - EnableRaisingEvents = true + EnableRaisingEvents = true, + AlwaysRaiseChangedEvent = alwaysRaiseChangedEvent }; server.GetHoldingRegisters()[address] = initialValue; @@ -385,9 +389,11 @@ await Task.Run(() => } [Theory] - [InlineData(false, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new bool[] { true, false, true })] - [InlineData(true, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new bool[] { true, false, true })] - public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] initialValues, short[] newValues, bool[] expected) + [InlineData(false, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new bool[] { true, false, true }, false)] + [InlineData(true, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new bool[] { true, false, true }, false)] + [InlineData(false, new short[] { 0, 0, 0 }, new short[] { 0, 0, 0 }, new bool[] { true, true, true }, true)] + [InlineData(true, new short[] { 0, 0, 0 }, new short[] { 0, 0, 0 }, new bool[] { true, true, true }, true)] + public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] initialValues, short[] newValues, bool[] expected, bool alwaysRaiseChangedEvent) { // Arrange var actual = new bool[3]; @@ -396,7 +402,8 @@ public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] ini using var server = new ModbusTcpServer() { - EnableRaisingEvents = true + EnableRaisingEvents = true, + AlwaysRaiseChangedEvent = alwaysRaiseChangedEvent }; for (int i = 0; i < initialValues.Length; i++) @@ -406,7 +413,11 @@ public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] ini server.RegistersChanged += (sender, e) => { - Assert.True(e.Registers.Length == 2); + if (alwaysRaiseChangedEvent) + Assert.True(e.Registers.Length == 3); + + else + Assert.True(e.Registers.Length == 2); for (int i = 0; i < initialValues.Length; i++) { From 2bed4e39927e8f24f5b9611762b5634815bb641e Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 10:29:06 +0100 Subject: [PATCH 09/25] Clean up and fix typo --- src/FluentModbus/Client/ModbusClientAsync.cs | 20 +++++++++---------- .../Resources/ErrorMessage.Designer.cs | 2 +- src/FluentModbus/Resources/ErrorMessage.resx | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs index b06d9e7..bb6f6d1 100644 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -50,7 +50,7 @@ public async Task> ReadHoldingRegistersAsync(int unitIdentifier, in /// The token to monitor for cancellation requests. The default value is . public async Task> ReadHoldingRegistersAsync(byte unitIdentifier, ushort startingAddress, ushort quantity, CancellationToken cancellationToken = default) { - var buffer = (await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.ReadHoldingRegisters, writer => + var buffer = await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.ReadHoldingRegisters, writer => { writer.Write((byte)ModbusFunctionCode.ReadHoldingRegisters); // 07 Function Code @@ -64,7 +64,7 @@ public async Task> ReadHoldingRegistersAsync(byte unitIdentifier, u writer.Write(startingAddress); // 08-09 Starting Address writer.Write(quantity); // 10-11 Quantity of Input Registers } - }, cancellationToken).ConfigureAwait(false)); + }, cancellationToken).ConfigureAwait(false); if (buffer.Length < quantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); @@ -141,7 +141,7 @@ public async Task> ReadCoilsAsync(int unitIdentifier, int startingA var startingAddress_converted = ConvertUshort(startingAddress); var quantity_converted = ConvertUshort(quantity); - var buffer = (await TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.ReadCoils, writer => + var buffer = await TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.ReadCoils, writer => { writer.Write((byte)ModbusFunctionCode.ReadCoils); // 07 Function Code @@ -155,7 +155,7 @@ public async Task> ReadCoilsAsync(int unitIdentifier, int startingA writer.Write(startingAddress_converted); // 08-09 Starting Address writer.Write(quantity_converted); // 10-11 Quantity of Coils } - }, cancellationToken).ConfigureAwait(false)); + }, cancellationToken).ConfigureAwait(false); if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8) + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); @@ -176,7 +176,7 @@ public async Task> ReadDiscreteInputsAsync(int unitIdentifier, int var startingAddress_converted = ConvertUshort(startingAddress); var quantity_converted = ConvertUshort(quantity); - var buffer = (await TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.ReadDiscreteInputs, writer => + var buffer = await TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.ReadDiscreteInputs, writer => { writer.Write((byte)ModbusFunctionCode.ReadDiscreteInputs); // 07 Function Code @@ -190,7 +190,7 @@ public async Task> ReadDiscreteInputsAsync(int unitIdentifier, int writer.Write(startingAddress_converted); // 08-09 Starting Address writer.Write(quantity_converted); // 10-11 Quantity of Coils } - }, cancellationToken).ConfigureAwait(false)); + }, cancellationToken).ConfigureAwait(false); if (buffer.Length < (byte)Math.Ceiling((double)quantity_converted / 8) + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); @@ -230,7 +230,7 @@ public async Task> ReadInputRegistersAsync(int unitIdentifier, int /// The token to monitor for cancellation requests. The default value is . public async Task> ReadInputRegistersAsync(byte unitIdentifier, ushort startingAddress, ushort quantity, CancellationToken cancellationToken = default) { - var buffer = (await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.ReadInputRegisters, writer => + var buffer = await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.ReadInputRegisters, writer => { writer.Write((byte)ModbusFunctionCode.ReadInputRegisters); // 07 Function Code @@ -244,7 +244,7 @@ public async Task> ReadInputRegistersAsync(byte unitIdentifier, ush writer.Write(startingAddress); // 08-09 Starting Address writer.Write(quantity); // 10-11 Quantity of Input Registers } - }, cancellationToken).ConfigureAwait(false)); + }, cancellationToken).ConfigureAwait(false); if (buffer.Length < quantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); @@ -427,7 +427,7 @@ public async Task> ReadWriteMultipleRegistersAsync(byte unitIdentif var writeQuantity = dataset.Length / 2; - var buffer = (await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.ReadWriteMultipleRegisters, writer => + var buffer = await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.ReadWriteMultipleRegisters, writer => { writer.Write((byte)ModbusFunctionCode.ReadWriteMultipleRegisters); // 07 Function Code @@ -449,7 +449,7 @@ public async Task> ReadWriteMultipleRegistersAsync(byte unitIdentif writer.Write((byte)(writeQuantity * 2)); // 16 Byte Count = Quantity to Write * 2 writer.Write(dataset, 0, dataset.Length); - }, cancellationToken).ConfigureAwait(false)); + }, cancellationToken).ConfigureAwait(false); if (buffer.Length < readQuantity * 2 + 2) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseMessageLength); diff --git a/src/FluentModbus/Resources/ErrorMessage.Designer.cs b/src/FluentModbus/Resources/ErrorMessage.Designer.cs index 5550d9a..63b704f 100644 --- a/src/FluentModbus/Resources/ErrorMessage.Designer.cs +++ b/src/FluentModbus/Resources/ErrorMessage.Designer.cs @@ -277,7 +277,7 @@ internal static string ModbusClient_TcpConnectTimeout { } /// - /// Looks up a localized string similar to Unknonw {0} Modbus error received.. + /// Looks up a localized string similar to Unknown {0} Modbus error received.. /// internal static string ModbusClient_Unknown_Error { get { diff --git a/src/FluentModbus/Resources/ErrorMessage.resx b/src/FluentModbus/Resources/ErrorMessage.resx index 8f59aa7..a6cdfcb 100644 --- a/src/FluentModbus/Resources/ErrorMessage.resx +++ b/src/FluentModbus/Resources/ErrorMessage.resx @@ -184,7 +184,7 @@ Could not connect within the specified time. - Unknonw {0} Modbus error received. + Unknown {0} Modbus error received. The unit identifier is invalid. Valid node addresses are in the range of 1 - 247. From 0c87eaab0d9240285d68a8a690ee70e5f7e3412d Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 11:16:17 +0100 Subject: [PATCH 10/25] Fix tt files --- src/FluentModbus/Client/ModbusClient.cs | 0 src/FluentModbus/Client/ModbusClientAsync.cs | 12 ++-- src/FluentModbus/Client/ModbusClientAsync.tt | 57 +++++++++---------- src/FluentModbus/Client/ModbusRtuClient.cs | 3 +- .../Client/ModbusRtuClientAsync.cs | 14 +++-- .../Client/ModbusRtuClientAsync.tt | 37 ++++++------ src/FluentModbus/Client/ModbusTcpClient.cs | 0 .../Client/ModbusTcpClientAsync.cs | 19 ++++--- .../Client/ModbusTcpClientAsync.tt | 37 ++++++------ 9 files changed, 85 insertions(+), 94 deletions(-) mode change 100644 => 100755 src/FluentModbus/Client/ModbusClient.cs mode change 100644 => 100755 src/FluentModbus/Client/ModbusClientAsync.cs mode change 100644 => 100755 src/FluentModbus/Client/ModbusClientAsync.tt mode change 100644 => 100755 src/FluentModbus/Client/ModbusRtuClient.cs mode change 100644 => 100755 src/FluentModbus/Client/ModbusRtuClientAsync.cs mode change 100644 => 100755 src/FluentModbus/Client/ModbusRtuClientAsync.tt mode change 100644 => 100755 src/FluentModbus/Client/ModbusTcpClient.cs mode change 100644 => 100755 src/FluentModbus/Client/ModbusTcpClientAsync.cs mode change 100644 => 100755 src/FluentModbus/Client/ModbusTcpClientAsync.tt diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs old mode 100644 new mode 100755 diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs old mode 100644 new mode 100755 index 91979ad..9a4c062 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -1,5 +1,5 @@  -/* This is automatically translated code. */ + /* This is automatically translated code. */ #pragma warning disable CS1998 @@ -8,7 +8,7 @@ namespace FluentModbus { public abstract partial class ModbusClient - { + { /// /// Sends the requested modbus message and waits for the response. /// @@ -164,7 +164,7 @@ public async Task> ReadCoilsAsync(int unitIdentifier, int startingA } /// - /// Reads the specified number of discrete inputs as byte array. Each bit of the returned array represents a single discete input. + /// Reads the specified number of discrete inputs as byte array. Each bit of the returned array represents a single discrete input. /// /// The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network. /// The discrete input start address for the read operation. @@ -213,7 +213,7 @@ public async Task> ReadInputRegistersAsync(int unitIdentifier, int var count_converted = ConvertUshort(count); var dataset = SpanExtensions.Cast(await - ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)); + ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted)).ConfigureAwait(false)); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset); @@ -296,7 +296,7 @@ public async Task WriteSingleRegisterAsync(int unitIdentifier, int registerAddre if (SwapBytes) value = ModbusUtils.SwitchEndianness(value); - await WriteSingleRegisterAsync(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast(new [] { value }).ToArray()).ConfigureAwait(false); + await WriteSingleRegisterAsync(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast(new [] { value }).ToArray(), cancellationToken).ConfigureAwait(false); } /// @@ -314,7 +314,7 @@ public async Task WriteSingleRegisterAsync(int unitIdentifier, int registerAddre if (SwapBytes) value = ModbusUtils.SwitchEndianness(value); - await WriteSingleRegisterAsync(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast(new[] { value }).ToArray()).ConfigureAwait(false); + await WriteSingleRegisterAsync(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast(new[] { value }).ToArray(), cancellationToken).ConfigureAwait(false); } /// diff --git a/src/FluentModbus/Client/ModbusClientAsync.tt b/src/FluentModbus/Client/ModbusClientAsync.tt old mode 100644 new mode 100755 index e875e8b..851ee63 --- a/src/FluentModbus/Client/ModbusClientAsync.tt +++ b/src/FluentModbus/Client/ModbusClientAsync.tt @@ -4,57 +4,54 @@ <#@ import namespace="System.Text.RegularExpressions" #> <# - var csstring = File.ReadAllText("src/FluentModbus/Client/ModbusClient.cs"); - var methodsToConvert = Regex.Matches(csstring, @" \/{3}.*?(\n }|extendFrame\);)", RegexOptions.Singleline); + var csstring = File.ReadAllText("src/FluentModbus/Client/ModbusClient.cs"); + var methodsToConvert = Regex.Matches(csstring, @" \/{3}.*?(\n }|extendFrame\);)", RegexOptions.Singleline); #> /* This is automatically translated code. */ #pragma warning disable CS1998 -using System; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; namespace FluentModbus { - public abstract partial class ModbusClient - { + public abstract partial class ModbusClient + { <# - foreach (Match method in methodsToConvert) - { - var methodString = method.Value; + foreach (Match method in methodsToConvert) + { + var methodString = method.Value; - // add cancellation token XML comment - methodString = Regex.Replace(methodString, @"(>\r?\n) (public|protected)", m => $"{m.Groups[1]} /// The token to monitor for cancellation requests. The default value is .\n {m.Groups[2]}", RegexOptions.Singleline); + // add cancellation token XML comment + methodString = Regex.Replace(methodString, @"(>\r?\n) (public|protected)", m => $"{m.Groups[1]} /// The token to monitor for cancellation requests. The default value is .\n {m.Groups[2]}", RegexOptions.Singleline); - // add cancellation token - methodString = Regex.Replace(methodString, @"(>\r?\n (?:public|protected).*?)\((.*?)\)", m => $"{m.Groups[1]}({m.Groups[2]}, CancellationToken cancellationToken = default)"); + // add cancellation token + methodString = Regex.Replace(methodString, @"(>\r?\n (?:public|protected).*?)\((.*?)\)", m => $"{m.Groups[1]}({m.Groups[2]}, CancellationToken cancellationToken = default)"); - // replace return values - methodString = Regex.Replace(methodString, " void", $" {(methodString.Contains("abstract") ? "" : "async")} Task"); - methodString = Regex.Replace(methodString, " Span<(.*?)>", m => $"{(methodString.Contains("abstract") ? "" : " async")} Task>"); + // replace return values + methodString = Regex.Replace(methodString, " void", $" {(methodString.Contains("abstract") ? "" : "async")} Task"); + methodString = Regex.Replace(methodString, " Span<(.*?)>", m => $"{(methodString.Contains("abstract") ? "" : " async")} Task>"); - // replace method name - methodString = Regex.Replace(methodString, @"(.* Task[^ ]*) (.*?)([<|\(])", m => $"{m.Groups[1]} {m.Groups[2]}Async{m.Groups[3]}"); + // replace method name + methodString = Regex.Replace(methodString, @"(.* Task[^ ]*) (.*?)([<|\(])", m => $"{m.Groups[1]} {m.Groups[2]}Async{m.Groups[3]}"); - // replace TranceiveFrame - methodString = Regex.Replace(methodString, @"(TransceiveFrame)(.*?\n })\);", m => $"await {m.Groups[1]}Async{m.Groups[2]}, cancellationToken).ConfigureAwait(false);", RegexOptions.Singleline); + // replace TranceiveFrame + methodString = Regex.Replace(methodString, @"(TransceiveFrame)(.*?\n })\);", m => $"await {m.Groups[1]}Async{m.Groups[2]}, cancellationToken).ConfigureAwait(false);", RegexOptions.Singleline); - methodString = Regex.Replace(methodString, @"(TransceiveFrame)(.*?\n })\)\.Slice", m => $"(await {m.Groups[1]}Async{m.Groups[2]}, cancellationToken).ConfigureAwait(false)).Slice", RegexOptions.Singleline); + methodString = Regex.Replace(methodString, @"(TransceiveFrame)(.*?\n })\)\.Slice", m => $"(await {m.Groups[1]}Async{m.Groups[2]}, cancellationToken).ConfigureAwait(false)).Slice", RegexOptions.Singleline); - // replace MemoryMarshal - methodString = Regex.Replace(methodString, @"MemoryMarshal(.+?)\(((?:\r?\n)?.+?)(\(.+?\))\);", m => $"SpanExtensions{m.Groups[1]}(await {m.Groups[2]}Async{m.Groups[3]}.ConfigureAwait(false));"); + // replace MemoryMarshal + methodString = Regex.Replace(methodString, @"MemoryMarshal(.+?)\(((?:\r?\n)?.+?)(\(.+?\))\);", m => $"SpanExtensions{m.Groups[1]}(await {m.Groups[2]}Async{m.Groups[3]}.ConfigureAwait(false));"); - // replace remaining (WriteXXXRegister(s)) - methodString = Regex.Replace(methodString, @"(\n )(Write.*?Registers?)(\(.*?\));", m => $"{m.Groups[1]}await {m.Groups[2]}Async{m.Groups[3]}.ConfigureAwait(false);", RegexOptions.Singleline); + // replace remaining (WriteXXXRegister(s)) + methodString = Regex.Replace(methodString, @"(\n )(Write.*?Registers?)\((.*?)\);", m => $"{m.Groups[1]}await {m.Groups[2]}Async({m.Groups[3]}, cancellationToken).ConfigureAwait(false);", RegexOptions.Singleline); - methodString += "\n\n"; - Write(methodString); - } + methodString += "\n\n"; + Write(methodString); + } #> - } + } } #pragma warning restore CS1998 \ No newline at end of file diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs old mode 100644 new mode 100755 index 6e8489f..37752ee --- a/src/FluentModbus/Client/ModbusRtuClient.cs +++ b/src/FluentModbus/Client/ModbusRtuClient.cs @@ -197,8 +197,7 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio while (true) { - frameLength += _serialPort!.Value.Value - .Read(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength); + frameLength += _serialPort!.Value.Value.Read(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength); if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) { diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.cs b/src/FluentModbus/Client/ModbusRtuClientAsync.cs old mode 100644 new mode 100755 index 7fccae7..c376418 --- a/src/FluentModbus/Client/ModbusRtuClientAsync.cs +++ b/src/FluentModbus/Client/ModbusRtuClientAsync.cs @@ -1,12 +1,15 @@ -/* This is automatically translated code. */ + + /* This is automatically translated code. */ namespace FluentModbus { - public partial class ModbusRtuClient + public partial class ModbusRtuClient { /// protected override async Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default) { + // WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync! + int frameLength; byte rawFunctionCode; ushort crc; @@ -38,7 +41,7 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti frameLength = (int)_frameBuffer.Writer.BaseStream.Position; // add CRC - crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory().Slice(0, frameLength)); + crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory()[..frameLength]); _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; @@ -57,10 +60,11 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti { frameLength += await _serialPort!.Value.Value.ReadAsync(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength, cancellationToken).ConfigureAwait(false); - if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory().Slice(0, frameLength))) + if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) { break; } + else { // reset length because one or more chunks of data were received and written to @@ -70,7 +74,7 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti } } - unitIdentifier = _frameBuffer.Reader.ReadByte(); + _ = _frameBuffer.Reader.ReadByte(); rawFunctionCode = _frameBuffer.Reader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.tt b/src/FluentModbus/Client/ModbusRtuClientAsync.tt old mode 100644 new mode 100755 index 2d33ac5..1ab2b69 --- a/src/FluentModbus/Client/ModbusRtuClientAsync.tt +++ b/src/FluentModbus/Client/ModbusRtuClientAsync.tt @@ -4,31 +4,26 @@ <#@ import namespace="System.Text.RegularExpressions" #> <# - var csstring = File.ReadAllText("src/FluentModbus/Client/ModbusRtuClient.cs"); - var match = Regex.Matches(csstring, @"(protected override Span TransceiveFrame\()(.*?)\)(.*?\n })", RegexOptions.Singleline)[0]; + var csstring = File.ReadAllText("src/FluentModbus/Client/ModbusRtuClient.cs"); + var match = Regex.Matches(csstring, @"(protected override Span TransceiveFrame\()(.*?)\)(.*?\n })", RegexOptions.Singleline)[0]; #> /* This is automatically translated code. */ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - namespace FluentModbus { - public partial class ModbusRtuClient - { - <# - // replace AsSpan - var signature = match.Groups[2].Value; - var body = match.Groups[3].Value; - body = Regex.Replace(body, "AsSpan", "AsMemory"); - body = Regex.Replace(body, @"_serialPort.Write\((.*?)\)", m => $"await _serialPort.WriteAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); - body = Regex.Replace(body, @"_serialPort.Read\((.*?)\)", m => $"await _serialPort.ReadAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); - - Write($"///\n protected override async Task> TransceiveFrameAsync({signature}, CancellationToken cancellationToken = default){body}"); - #> - - } + public partial class ModbusRtuClient + { + <# + // replace AsSpan + var signature = match.Groups[2].Value; + var body = match.Groups[3].Value; + body = Regex.Replace(body, "AsSpan", "AsMemory"); + body = Regex.Replace(body, @"_serialPort!.Value.Value.Write\((.*?)\)", m => $"await _serialPort!.Value.Value.WriteAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); + body = Regex.Replace(body, @"_serialPort!.Value.Value.Read\((.*?)\)", m => $"await _serialPort!.Value.Value.ReadAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); + + Write($"///\n protected override async Task> TransceiveFrameAsync({signature}, CancellationToken cancellationToken = default){body}"); + #> + + } } \ No newline at end of file diff --git a/src/FluentModbus/Client/ModbusTcpClient.cs b/src/FluentModbus/Client/ModbusTcpClient.cs old mode 100644 new mode 100755 diff --git a/src/FluentModbus/Client/ModbusTcpClientAsync.cs b/src/FluentModbus/Client/ModbusTcpClientAsync.cs old mode 100644 new mode 100755 index 1211857..bb17428 --- a/src/FluentModbus/Client/ModbusTcpClientAsync.cs +++ b/src/FluentModbus/Client/ModbusTcpClientAsync.cs @@ -1,17 +1,18 @@ -/* This is automatically translated code. */ - - + + /* This is automatically translated code. */ + namespace FluentModbus { - public partial class ModbusTcpClient + public partial class ModbusTcpClient { /// protected override async Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default) { + // WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync! + int frameLength; int partialLength; - ushort transactionIdentifier; ushort protocolIdentifier; ushort bytesFollowing; @@ -104,10 +105,10 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti if (!isParsed) // read MBAP header only once { // read MBAP header - transactionIdentifier = reader.ReadUInt16Reverse(); // 00-01 Transaction Identifier - protocolIdentifier = reader.ReadUInt16Reverse(); // 02-03 Protocol Identifier - bytesFollowing = reader.ReadUInt16Reverse(); // 04-05 Length - unitIdentifier = reader.ReadByte(); // 06 Unit Identifier + _ = reader.ReadUInt16Reverse(); // 00-01 Transaction Identifier + protocolIdentifier = reader.ReadUInt16Reverse(); // 02-03 Protocol Identifier + bytesFollowing = reader.ReadUInt16Reverse(); // 04-05 Length + _ = reader.ReadByte(); // 06 Unit Identifier if (protocolIdentifier != 0) throw new ModbusException(ErrorMessage.ModbusClient_InvalidProtocolIdentifier); diff --git a/src/FluentModbus/Client/ModbusTcpClientAsync.tt b/src/FluentModbus/Client/ModbusTcpClientAsync.tt old mode 100644 new mode 100755 index 7ea88a0..f4e551d --- a/src/FluentModbus/Client/ModbusTcpClientAsync.tt +++ b/src/FluentModbus/Client/ModbusTcpClientAsync.tt @@ -4,32 +4,27 @@ <#@ import namespace="System.Text.RegularExpressions" #> <# - var csstring = File.ReadAllText("src/FluentModbus/Client/ModbusTcpClient.cs"); - var match = Regex.Matches(csstring, @"(protected override Span TransceiveFrame\()(.*?)\)(.*?\n })", RegexOptions.Singleline)[0]; + var csstring = File.ReadAllText("src/FluentModbus/Client/ModbusTcpClient.cs"); + var match = Regex.Matches(csstring, @"(protected override Span TransceiveFrame\()(.*?)\)(.*?\n })", RegexOptions.Singleline)[0]; #> /* This is automatically translated code. */ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - namespace FluentModbus { - public partial class ModbusTcpClient - { - <# - // replace AsSpan - var signature = match.Groups[2].Value; - var body = match.Groups[3].Value; - body = Regex.Replace(body, "AsSpan", "AsMemory"); - body = Regex.Replace(body, @"_networkStream.Write\((.*?)\)", m => $"await _networkStream.WriteAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); - body = Regex.Replace(body, @"_networkStream.Read\((.*?)\)", m => $"await _networkStream.ReadAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); - body = Regex.Replace(body, @"// ASYNC-ONLY: ", ""); + public partial class ModbusTcpClient + { + <# + // replace AsSpan + var signature = match.Groups[2].Value; + var body = match.Groups[3].Value; + body = Regex.Replace(body, "AsSpan", "AsMemory"); + body = Regex.Replace(body, @"_networkStream.Write\((.*?)\)", m => $"await _networkStream.WriteAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); + body = Regex.Replace(body, @"_networkStream.Read\((.*?)\)", m => $"await _networkStream.ReadAsync({m.Groups[1]}, cancellationToken).ConfigureAwait(false)"); + body = Regex.Replace(body, @"// ASYNC-ONLY: ", ""); - Write($"///\n protected override async Task> TransceiveFrameAsync({signature}, CancellationToken cancellationToken = default){body}"); - #> - - } + Write($"///\n protected override async Task> TransceiveFrameAsync({signature}, CancellationToken cancellationToken = default){body}"); + #> + + } } \ No newline at end of file From 4d4d0dc8e51b88092a74ba7686a8e4bbbedbc1a5 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 11:40:43 +0100 Subject: [PATCH 11/25] Propagate remaining cancellation tokens --- src/FluentModbus/Client/ModbusClientAsync.cs | 8 ++++---- src/FluentModbus/Client/ModbusClientAsync.tt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs index 9a4c062..bef6839 100755 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -33,7 +33,7 @@ public async Task> ReadHoldingRegistersAsync(int unitIdentifier, in var count_converted = ConvertUshort(count); var dataset = SpanExtensions.Cast(await - ReadHoldingRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted)).ConfigureAwait(false)); + ReadHoldingRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset); @@ -213,7 +213,7 @@ public async Task> ReadInputRegistersAsync(int unitIdentifier, int var count_converted = ConvertUshort(count); var dataset = SpanExtensions.Cast(await - ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted)).ConfigureAwait(false)); + ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset); @@ -403,7 +403,7 @@ public async Task> ReadWriteMultipleRegistersAsync( var readQuantity = ConvertSize(readCount_converted); var byteData = MemoryMarshal.Cast(dataset).ToArray(); - var dataset2 = SpanExtensions.Cast(await ReadWriteMultipleRegistersAsync(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData).ConfigureAwait(false)); + var dataset2 = SpanExtensions.Cast(await ReadWriteMultipleRegistersAsync(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData, cancellationToken).ConfigureAwait(false)); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset2); @@ -466,7 +466,7 @@ public async Task ReadFifoQueueAsync() throw new NotImplementedException(); } - } + } } #pragma warning restore CS1998 \ No newline at end of file diff --git a/src/FluentModbus/Client/ModbusClientAsync.tt b/src/FluentModbus/Client/ModbusClientAsync.tt index 851ee63..f6da547 100755 --- a/src/FluentModbus/Client/ModbusClientAsync.tt +++ b/src/FluentModbus/Client/ModbusClientAsync.tt @@ -42,7 +42,7 @@ namespace FluentModbus methodString = Regex.Replace(methodString, @"(TransceiveFrame)(.*?\n })\)\.Slice", m => $"(await {m.Groups[1]}Async{m.Groups[2]}, cancellationToken).ConfigureAwait(false)).Slice", RegexOptions.Singleline); // replace MemoryMarshal - methodString = Regex.Replace(methodString, @"MemoryMarshal(.+?)\(((?:\r?\n)?.+?)(\(.+?\))\);", m => $"SpanExtensions{m.Groups[1]}(await {m.Groups[2]}Async{m.Groups[3]}.ConfigureAwait(false));"); + methodString = Regex.Replace(methodString, @"MemoryMarshal(.+?)\(((?:\r?\n)?.+?)\((.+?)\)\);", m => $"SpanExtensions{m.Groups[1]}(await {m.Groups[2]}Async({m.Groups[3]}, cancellationToken).ConfigureAwait(false));"); // replace remaining (WriteXXXRegister(s)) methodString = Regex.Replace(methodString, @"(\n )(Write.*?Registers?)\((.*?)\);", m => $"{m.Groups[1]}await {m.Groups[2]}Async({m.Groups[3]}, cancellationToken).ConfigureAwait(false);", RegexOptions.Singleline); From 3049d038a1aa03f260dcf6dcd766e3d3025d1f6a Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 11:49:09 +0100 Subject: [PATCH 12/25] Remove useless lang version properties --- src/FluentModbus/FluentModbus.csproj | 2 -- tests/FluentModbus.Tests/FluentModbus.Tests.csproj | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/FluentModbus/FluentModbus.csproj b/src/FluentModbus/FluentModbus.csproj index c053625..129e7bb 100644 --- a/src/FluentModbus/FluentModbus.csproj +++ b/src/FluentModbus/FluentModbus.csproj @@ -7,8 +7,6 @@ netstandard2.0;netstandard2.1 icon.png README.md - - 10.0 diff --git a/tests/FluentModbus.Tests/FluentModbus.Tests.csproj b/tests/FluentModbus.Tests/FluentModbus.Tests.csproj index 8716212..4cc6a92 100644 --- a/tests/FluentModbus.Tests/FluentModbus.Tests.csproj +++ b/tests/FluentModbus.Tests/FluentModbus.Tests.csproj @@ -3,8 +3,6 @@ false $(TargetFrameworkVersion) - - 10.0 From 96657d81a33377730b48f9a8882b8b794b958431 Mon Sep 17 00:00:00 2001 From: ondraco Date: Tue, 4 Oct 2022 09:36:22 +0200 Subject: [PATCH 13/25] added support for WriteMultipleCoils --- .../Server/ModbusRequestHandler.cs | 86 +++++++++++++++---- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/src/FluentModbus/Server/ModbusRequestHandler.cs b/src/FluentModbus/Server/ModbusRequestHandler.cs index f2659e3..f51dc79 100644 --- a/src/FluentModbus/Server/ModbusRequestHandler.cs +++ b/src/FluentModbus/Server/ModbusRequestHandler.cs @@ -88,7 +88,7 @@ public void WriteResponse() ModbusFunctionCode.WriteSingleCoil => ProcessWriteSingleCoil, ModbusFunctionCode.WriteSingleRegister => ProcessWriteSingleRegister, //ModbusFunctionCode.ReadExceptionStatus - //ModbusFunctionCode.WriteMultipleCoils + ModbusFunctionCode.WriteMultipleCoils => ProcessWriteMultipleCoils, //ModbusFunctionCode.ReadFileRecord //ModbusFunctionCode.WriteFileRecord //ModbusFunctionCode.MaskWriteRegister @@ -325,6 +325,73 @@ private void ProcessReadInputRegisters() } } + private void ProcessWriteMultipleCoils() + { + const int maxQuantityOfRegisters = 0x07b0; + var startAddress = FrameBuffer.Reader.ReadUInt16Reverse(); + var quantityOfOutputs = FrameBuffer.Reader.ReadUInt16Reverse(); + var byteCount = FrameBuffer.Reader.ReadByte(); + var byteCountFromQuantity = quantityOfOutputs / 8; + var bitCountFromQuantity = quantityOfOutputs % 8; + + if (bitCountFromQuantity != 0) + ++byteCountFromQuantity; + + if ((quantityOfOutputs is < 1 or > maxQuantityOfRegisters) || byteCountFromQuantity != byteCount) + { + WriteExceptionResponse(ModbusFunctionCode.WriteMultipleCoils, ModbusExceptionCode.IllegalDataValue); + return; + } + + if (CheckRegisterBounds(ModbusFunctionCode.WriteMultipleCoils, startAddress, ModbusServer.MaxCoilAddress, quantityOfOutputs, maxQuantityOfRegisters)) + { + byte[] values = new byte[byteCount]; + for (int i = 0; i < byteCount; ++i) + values[i] = FrameBuffer.Reader.ReadByte(); + + for (ushort i = 0; i < quantityOfOutputs; ++i) + { + byte b = values[i / 8]; + int bit = i % 8; + bool value = (b & (1 << bit - 1)) != 0; + WriteCoil(value, (ushort)(startAddress + i)); + } + } + + FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); + + if (BitConverter.IsLittleEndian) + { + FrameBuffer.Writer.WriteReverse(startAddress); + FrameBuffer.Writer.WriteReverse(quantityOfOutputs); + } + else + { + FrameBuffer.Writer.Write(startAddress); + FrameBuffer.Writer.Write(quantityOfOutputs); + } + } + + private void WriteCoil(bool value, ushort outputAddress) + { + var bufferByteIndex = outputAddress / 8; + var bufferBitIndex = outputAddress % 8; + + var coils = ModbusServer.GetCoils(UnitIdentifier); + var oldValue = coils[bufferByteIndex]; + var newValue = oldValue; + + if (!value) + newValue &= (byte)~(1 << bufferBitIndex); + else + newValue |= (byte)(1 << bufferBitIndex); + + coils[bufferByteIndex] = newValue; + + if (ModbusServer.EnableRaisingEvents && newValue != oldValue) + ModbusServer.OnCoilsChanged(UnitIdentifier, new int[] { outputAddress }); + } + private void ProcessWriteSingleCoil() { var outputAddress = FrameBuffer.Reader.ReadUInt16Reverse(); @@ -338,22 +405,7 @@ private void ProcessWriteSingleCoil() } else { - var bufferByteIndex = outputAddress / 8; - var bufferBitIndex = outputAddress % 8; - - var coils = ModbusServer.GetCoils(UnitIdentifier); - var oldValue = coils[bufferByteIndex]; - var newValue = oldValue; - - if (outputValue == 0x0000) - newValue &= (byte)~(1 << bufferBitIndex); - else - newValue |= (byte)(1 << bufferBitIndex); - - coils[bufferByteIndex] = newValue; - - if (ModbusServer.EnableRaisingEvents && newValue != oldValue) - ModbusServer.OnCoilsChanged(UnitIdentifier, new int[] { outputAddress }); + WriteCoil(outputValue == 0x00FF, outputAddress); FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleCoil); From f37bcfb059402e94f36b3399afd1ad9193a1ff82 Mon Sep 17 00:00:00 2001 From: ondraco Date: Fri, 7 Oct 2022 09:54:21 +0200 Subject: [PATCH 14/25] Fixed bug in ProcessWriteMultipleCoils --- src/FluentModbus/Server/ModbusRequestHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FluentModbus/Server/ModbusRequestHandler.cs b/src/FluentModbus/Server/ModbusRequestHandler.cs index f51dc79..0fc34db 100644 --- a/src/FluentModbus/Server/ModbusRequestHandler.cs +++ b/src/FluentModbus/Server/ModbusRequestHandler.cs @@ -353,7 +353,7 @@ private void ProcessWriteMultipleCoils() { byte b = values[i / 8]; int bit = i % 8; - bool value = (b & (1 << bit - 1)) != 0; + bool value = (b & (1 << bit)) != 0; WriteCoil(value, (ushort)(startAddress + i)); } } From a3a557453e45a31e8b2c231a026316b1955e1de9 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 22:34:47 +0100 Subject: [PATCH 15/25] Add Modbus client functionality and tests --- src/FluentModbus/Client/ModbusClient.cs | 43 +++++++-- src/FluentModbus/Client/ModbusClientAsync.cs | 40 ++++++++- src/FluentModbus/FluentModbus.csproj | 17 ++-- .../Server/ModbusRequestHandler.cs | 88 ++++++++++++------- .../FluentModbus.Tests.csproj | 2 +- tests/FluentModbus.Tests/ModbusServerTests.cs | 71 +++++++++++++-- tests/FluentModbus.Tests/ProtocolTests.cs | 33 ++++++- 7 files changed, 229 insertions(+), 65 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index cc51224..a643071 100644 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Collections; +using System.Runtime.InteropServices; namespace FluentModbus { @@ -199,13 +200,11 @@ public void WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress, { writer.WriteReverse(startingAddress); // 08-09 Starting Address writer.WriteReverse((ushort)quantity); // 10-11 Quantity of Registers - } else { writer.Write(startingAddress); // 08-09 Starting Address writer.Write((ushort)quantity); // 10-11 Quantity of Registers - } writer.Write((byte)(quantity * 2)); // 12 Byte Count = Quantity of Registers * 2 @@ -425,12 +424,42 @@ public void WriteSingleRegister(byte unitIdentifier, ushort registerAddress, byt // class 2 /// - /// This methdod is not implemented. + /// Writes the provided to the coil registers. /// - [Obsolete("This method is not implemented.")] - public void WriteMultipleCoils() + /// The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network. + /// The coil register start address for the write operation. + /// The values to write to the server. + public void WriteMultipleCoils(int unitIdentifier, int startingAddress, bool[] values) { - throw new NotImplementedException(); + var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier); + var startingAddress_converted = ConvertUshort(startingAddress); + var quantityOfOutputs = values.Length; + var byteCount = (quantityOfOutputs + 7) / 8; + var convertedData = new byte[byteCount]; + + new BitArray(values) + .CopyTo(convertedData, 0); + + TransceiveFrame(unitIdentifier_converted, ModbusFunctionCode.WriteMultipleCoils, writer => + { + writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); // 07 Function Code + + if (BitConverter.IsLittleEndian) + { + writer.WriteReverse(startingAddress_converted); // 08-09 Starting Address + writer.WriteReverse((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs + } + + else + { + writer.Write(startingAddress_converted); // 08-09 Starting Address + writer.Write((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs + } + + writer.Write((byte)byteCount); // 12 Byte Count = Outputs + + writer.Write(convertedData); + }); } /// diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs index 7ab5fcb..24b5725 100644 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -3,6 +3,7 @@ #pragma warning disable CS1998 +using System.Collections; using System.Runtime.InteropServices; namespace FluentModbus @@ -343,12 +344,43 @@ await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.WriteSingleRegiste } /// - /// This methdod is not implemented. + /// Writes the provided to the coil registers. /// - [Obsolete("This method is not implemented.")] - public async Task WriteMultipleCoilsAsync() + /// The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network. + /// The coil register start address for the write operation. + /// The values to write to the server. + /// The token to monitor for cancellation requests. The default value is . + public void WriteMultipleCoilsAsync(int unitIdentifier, int startingAddress, bool[] values, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier); + var startingAddress_converted = ConvertUshort(startingAddress); + var quantityOfOutputs = values.Length; + var byteCount = (quantityOfOutputs + 7) / 8; + var convertedData = new byte[byteCount]; + + new BitArray(values) + .CopyTo(convertedData, 0); + + TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.WriteMultipleCoils, writer => + { + writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); // 07 Function Code + + if (BitConverter.IsLittleEndian) + { + writer.WriteReverse(startingAddress_converted); // 08-09 Starting Address + writer.WriteReverse((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs + } + + else + { + writer.Write(startingAddress_converted); // 08-09 Starting Address + writer.Write((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs + } + + writer.Write((byte)byteCount); // 12 Byte Count = Outputs + + writer.Write(convertedData); + }, cancellationToken); } /// diff --git a/src/FluentModbus/FluentModbus.csproj b/src/FluentModbus/FluentModbus.csproj index 3f8b368..740db6a 100644 --- a/src/FluentModbus/FluentModbus.csproj +++ b/src/FluentModbus/FluentModbus.csproj @@ -8,19 +8,24 @@ icon.png README.md - 10.0 + 12.0 + + + all + runtime; build; native; contentfiles; analyzers + - - - + + + @@ -46,10 +51,6 @@ - - - - True diff --git a/src/FluentModbus/Server/ModbusRequestHandler.cs b/src/FluentModbus/Server/ModbusRequestHandler.cs index 0fc34db..29c4257 100644 --- a/src/FluentModbus/Server/ModbusRequestHandler.cs +++ b/src/FluentModbus/Server/ModbusRequestHandler.cs @@ -187,18 +187,18 @@ private void DetectChangedRegisters(int startingAddress, Span oldValues, { Span changedRegisters = stackalloc int[newValues.Length]; - var index = 0; + var length = 0; for (int i = 0; i < newValues.Length; i++) { if (newValues[i] != oldValues[i]) { - changedRegisters[index] = startingAddress + i; - index++; + changedRegisters[length] = startingAddress + i; + length++; } } - ModbusServer.OnRegistersChanged(UnitIdentifier, changedRegisters.Slice(0, index).ToArray()); + ModbusServer.OnRegistersChanged(UnitIdentifier, changedRegisters[..length].ToArray()); } // class 0 @@ -224,10 +224,14 @@ private void ProcessWriteMultipleRegisters() if (CheckRegisterBounds(ModbusFunctionCode.WriteMultipleRegisters, startingAddress, ModbusServer.MaxHoldingRegisterAddress, quantityOfRegisters, 0x7B)) { var holdingRegisters = ModbusServer.GetHoldingRegisters(UnitIdentifier); - var oldValues = holdingRegisters.Slice(startingAddress).ToArray(); + + var oldValues = ModbusServer.EnableRaisingEvents + ? holdingRegisters[startingAddress..].ToArray() + : Array.Empty(); + var newValues = MemoryMarshal.Cast(FrameBuffer.Reader.ReadBytes(byteCount).AsSpan()); - newValues.CopyTo(holdingRegisters.Slice(startingAddress)); + newValues.CopyTo(holdingRegisters[startingAddress..]); if (ModbusServer.EnableRaisingEvents) DetectChangedRegisters(startingAddress, oldValues, newValues); @@ -327,52 +331,64 @@ private void ProcessReadInputRegisters() private void ProcessWriteMultipleCoils() { - const int maxQuantityOfRegisters = 0x07b0; - var startAddress = FrameBuffer.Reader.ReadUInt16Reverse(); + const int maxQuantityOfOutputs = 0x07B0; + + var startingAddress = FrameBuffer.Reader.ReadUInt16Reverse(); var quantityOfOutputs = FrameBuffer.Reader.ReadUInt16Reverse(); var byteCount = FrameBuffer.Reader.ReadByte(); - var byteCountFromQuantity = quantityOfOutputs / 8; - var bitCountFromQuantity = quantityOfOutputs % 8; - - if (bitCountFromQuantity != 0) - ++byteCountFromQuantity; + var byteCountFromQuantity = (quantityOfOutputs + 7) / 8; - if ((quantityOfOutputs is < 1 or > maxQuantityOfRegisters) || byteCountFromQuantity != byteCount) + if (byteCountFromQuantity != byteCount) { WriteExceptionResponse(ModbusFunctionCode.WriteMultipleCoils, ModbusExceptionCode.IllegalDataValue); return; } - if (CheckRegisterBounds(ModbusFunctionCode.WriteMultipleCoils, startAddress, ModbusServer.MaxCoilAddress, quantityOfOutputs, maxQuantityOfRegisters)) + if (CheckRegisterBounds(ModbusFunctionCode.WriteMultipleCoils, startingAddress, ModbusServer.MaxCoilAddress, quantityOfOutputs, maxQuantityOfOutputs)) { - byte[] values = new byte[byteCount]; - for (int i = 0; i < byteCount; ++i) - values[i] = FrameBuffer.Reader.ReadByte(); + var newValues = FrameBuffer.Reader.ReadBytes(byteCount); + + Span changedOutputs = stackalloc int[0]; - for (ushort i = 0; i < quantityOfOutputs; ++i) + if (ModbusServer.EnableRaisingEvents) + changedOutputs = stackalloc int[quantityOfOutputs]; + + var changedOutputsLength = 0; + + for (var i = 0; i < quantityOfOutputs; i++) { - byte b = values[i / 8]; + byte b = newValues[i / 8]; int bit = i % 8; bool value = (b & (1 << bit)) != 0; - WriteCoil(value, (ushort)(startAddress + i)); + + var hasChanged = WriteCoil(value, (ushort)(startingAddress + i)); + + if (ModbusServer.EnableRaisingEvents && (hasChanged || ModbusServer.AlwaysRaiseChangedEvent)) + { + changedOutputs[changedOutputsLength] = startingAddress + i; + changedOutputsLength++; + } } + + if (ModbusServer.EnableRaisingEvents) + ModbusServer.OnCoilsChanged(UnitIdentifier, changedOutputs[..changedOutputsLength].ToArray()); } FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); if (BitConverter.IsLittleEndian) { - FrameBuffer.Writer.WriteReverse(startAddress); + FrameBuffer.Writer.WriteReverse(startingAddress); FrameBuffer.Writer.WriteReverse(quantityOfOutputs); } else { - FrameBuffer.Writer.Write(startAddress); + FrameBuffer.Writer.Write(startingAddress); FrameBuffer.Writer.Write(quantityOfOutputs); } } - private void WriteCoil(bool value, ushort outputAddress) + private bool WriteCoil(bool value, ushort outputAddress) { var bufferByteIndex = outputAddress / 8; var bufferBitIndex = outputAddress % 8; @@ -381,15 +397,15 @@ private void WriteCoil(bool value, ushort outputAddress) var oldValue = coils[bufferByteIndex]; var newValue = oldValue; - if (!value) - newValue &= (byte)~(1 << bufferBitIndex); - else + if (value) newValue |= (byte)(1 << bufferBitIndex); + else + newValue &= (byte)~(1 << bufferBitIndex); + coils[bufferByteIndex] = newValue; - if (ModbusServer.EnableRaisingEvents && newValue != oldValue) - ModbusServer.OnCoilsChanged(UnitIdentifier, new int[] { outputAddress }); + return newValue != oldValue; } private void ProcessWriteSingleCoil() @@ -405,7 +421,10 @@ private void ProcessWriteSingleCoil() } else { - WriteCoil(outputValue == 0x00FF, outputAddress); + var hasChanged = WriteCoil(outputValue == 0x00FF, outputAddress); + + if (ModbusServer.EnableRaisingEvents && (hasChanged || ModbusServer.AlwaysRaiseChangedEvent)) + ModbusServer.OnCoilsChanged(UnitIdentifier, [outputAddress]); FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleCoil); @@ -432,7 +451,7 @@ private void ProcessWriteSingleRegister() holdingRegisters[registerAddress] = newValue; if (ModbusServer.EnableRaisingEvents && newValue != oldValue) - ModbusServer.OnRegistersChanged(UnitIdentifier, new int[] { registerAddress }); + ModbusServer.OnRegistersChanged(UnitIdentifier, [registerAddress]); FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleRegister); @@ -463,10 +482,13 @@ private void ProcessReadWriteMultipleRegisters() // write data (write is performed before read according to spec) var writeData = MemoryMarshal.Cast(FrameBuffer.Reader.ReadBytes(writeByteCount).AsSpan()); - var oldValues = holdingRegisters.Slice(writeStartingAddress).ToArray(); + var oldValues = ModbusServer.EnableRaisingEvents + ? holdingRegisters[writeStartingAddress..].ToArray() + : Array.Empty(); + var newValues = writeData; - newValues.CopyTo(holdingRegisters.Slice(writeStartingAddress)); + newValues.CopyTo(holdingRegisters[writeStartingAddress..]); if (ModbusServer.EnableRaisingEvents) DetectChangedRegisters(writeStartingAddress, oldValues, newValues); diff --git a/tests/FluentModbus.Tests/FluentModbus.Tests.csproj b/tests/FluentModbus.Tests/FluentModbus.Tests.csproj index 8716212..bcae0ac 100644 --- a/tests/FluentModbus.Tests/FluentModbus.Tests.csproj +++ b/tests/FluentModbus.Tests/FluentModbus.Tests.csproj @@ -4,7 +4,7 @@ false $(TargetFrameworkVersion) - 10.0 + 12.0 diff --git a/tests/FluentModbus.Tests/ModbusServerTests.cs b/tests/FluentModbus.Tests/ModbusServerTests.cs index ae3d875..94ed2bb 100644 --- a/tests/FluentModbus.Tests/ModbusServerTests.cs +++ b/tests/FluentModbus.Tests/ModbusServerTests.cs @@ -345,6 +345,63 @@ await Task.Run(() => Assert.Equal(expected, actual); } + [Fact] + public async Task CanDetectCoilsChanged() + { + // Arrange + int[] actual = default!; + var address = 99; + var endpoint = EndpointSource.GetNext(); + + using var server = new ModbusTcpServer() + { + EnableRaisingEvents = true + }; + + server.GetCoils().Set(address + 0, false); + server.GetCoils().Set(address + 1, true); + server.GetCoils().Set(address + 2, false); + server.GetCoils().Set(address + 3, true); + server.GetCoils().Set(address + 4, false); + server.GetCoils().Set(address + 5, false); + server.GetCoils().Set(address + 6, false); + server.GetCoils().Set(address + 7, false); + server.GetCoils().Set(address + 8, false); + server.GetCoils().Set(address + 9, true); + server.GetCoils().Set(address + 10, false); + server.GetCoils().Set(address + 11, false); + server.GetCoils().Set(address + 12, false); + server.GetCoils().Set(address + 13, false); + server.GetCoils().Set(address + 14, false); + server.GetCoils().Set(address + 15, true); + + server.CoilsChanged += (sender, e) => + { + Assert.True(e.Coils.Length == 3); + actual = e.Coils; + }; + + server.Start(endpoint); + + // Act + var client = new ModbusTcpClient(); + + await Task.Run(() => + { + client.Connect(endpoint); + + client.WriteMultipleCoils(0, address, [ + false, false, true, true, false, false, false, false, + false, true, false, false, false, false, false, false + ]); + }); + + // Assert + var expected = new int[] { 100, 101, 114 }; + + Assert.True(expected.SequenceEqual(actual)); + } + [Theory] [InlineData(99, 100, true)] [InlineData(0, -1, true)] @@ -385,12 +442,12 @@ await Task.Run(() => } [Theory] - [InlineData(false, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new bool[] { true, false, true })] - [InlineData(true, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new bool[] { true, false, true })] - public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] initialValues, short[] newValues, bool[] expected) + [InlineData(false, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new int[] { 99, 101 })] + [InlineData(true, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new int[] { 99, 101 })] + public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] initialValues, short[] newValues, int[] expected) { // Arrange - var actual = new bool[3]; + int[] actual = default!; var address = 99; var endpoint = EndpointSource.GetNext(); @@ -407,11 +464,7 @@ public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] ini server.RegistersChanged += (sender, e) => { Assert.True(e.Registers.Length == 2); - - for (int i = 0; i < initialValues.Length; i++) - { - actual[i] = e.Registers.Contains(address + i); - } + actual = e.Registers; }; server.Start(endpoint); diff --git a/tests/FluentModbus.Tests/ProtocolTests.cs b/tests/FluentModbus.Tests/ProtocolTests.cs index cc7fbf5..21ab811 100644 --- a/tests/FluentModbus.Tests/ProtocolTests.cs +++ b/tests/FluentModbus.Tests/ProtocolTests.cs @@ -8,7 +8,7 @@ public class ProtocolTests : IClassFixture public ProtocolTests() { - _array = new float[] { 0, 0, 0, 0, 0, 65.455F, 24, 25, 0, 0 }; + _array = [0, 0, 0, 0, 0, 65.455F, 24, 25, 0, 0]; } // FC03: ReadHoldingRegisters @@ -219,9 +219,36 @@ public void FC06Test() } } - // F023 ReadWriteMultipleRegisters + // FC15: WriteMultipleCoils [Fact] - public void FC023Test() + public void FC15Test() + { + // Arrange + var endpoint = EndpointSource.GetNext(); + + using var server = new ModbusTcpServer(); + server.Start(endpoint); + + var client = new ModbusTcpClient(); + client.Connect(endpoint); + + // Act + client.WriteMultipleCoils(0, 15, [true, false, true, true, false]); + client.WriteMultipleCoils(0, 16, [ false, false, true, true, false, true]); + + // Assert + var expected = new byte[] { 0b1000_0000, 0b0010_1100 }; + + lock (server.Lock) + { + var actual = server.GetCoilBuffer().Slice(1, 2).ToArray(); + Assert.True(expected.SequenceEqual(actual.ToArray())); + } + } + + // FC23 ReadWriteMultipleRegisters + [Fact] + public void FC23Test() { // Arrange var endpoint = EndpointSource.GetNext(); From 6863bb8ccc13df5d67639143b413bbfd44a36d40 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 22:42:29 +0100 Subject: [PATCH 16/25] Clean up --- src/FluentModbus/Client/ModbusTcpClient.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/FluentModbus/Client/ModbusTcpClient.cs b/src/FluentModbus/Client/ModbusTcpClient.cs index e26c413..9327e8f 100755 --- a/src/FluentModbus/Client/ModbusTcpClient.cs +++ b/src/FluentModbus/Client/ModbusTcpClient.cs @@ -234,18 +234,18 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio if (BitConverter.IsLittleEndian) { - writer.WriteReverse(GetTransactionIdentifier()); // 00-01 Transaction Identifier - writer.WriteReverse((ushort)0); // 02-03 Protocol Identifier - writer.WriteReverse((ushort)(frameLength - 6)); // 04-05 Length + writer.WriteReverse(GetTransactionIdentifier()); // 00-01 Transaction Identifier + writer.WriteReverse((ushort)0); // 02-03 Protocol Identifier + writer.WriteReverse((ushort)(frameLength - 6)); // 04-05 Length } else { - writer.Write(GetTransactionIdentifier()); // 00-01 Transaction Identifier - writer.Write((ushort)0); // 02-03 Protocol Identifier - writer.Write((ushort)(frameLength - 6)); // 04-05 Length + writer.Write(GetTransactionIdentifier()); // 00-01 Transaction Identifier + writer.Write((ushort)0); // 02-03 Protocol Identifier + writer.Write((ushort)(frameLength - 6)); // 04-05 Length } - writer.Write(unitIdentifier); // 06 Unit Identifier + writer.Write(unitIdentifier); // 06 Unit Identifier // send request _networkStream.Write(frameBuffer.Buffer, 0, frameLength); From 723a34518951ec57d79f55835ed3516dc2021e09 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 20 Feb 2024 22:52:05 +0100 Subject: [PATCH 17/25] Improve tests --- tests/FluentModbus.Tests/ModbusServerTests.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/FluentModbus.Tests/ModbusServerTests.cs b/tests/FluentModbus.Tests/ModbusServerTests.cs index 86bf137..5d4aef9 100644 --- a/tests/FluentModbus.Tests/ModbusServerTests.cs +++ b/tests/FluentModbus.Tests/ModbusServerTests.cs @@ -347,8 +347,10 @@ await Task.Run(() => Assert.Equal(expected, actual); } - [Fact] - public async Task CanDetectCoilsChanged() + [Theory] + [InlineData(new int[] { 100, 101, 114 }, false)] + [InlineData(new int[] { 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114 }, true)] + public async Task CanDetectCoilsChanged(int[] expected, bool alwaysRaiseChangedEvent) { // Arrange int[] actual = default!; @@ -357,7 +359,8 @@ public async Task CanDetectCoilsChanged() using var server = new ModbusTcpServer() { - EnableRaisingEvents = true + EnableRaisingEvents = true, + AlwaysRaiseChangedEvent = alwaysRaiseChangedEvent }; server.GetCoils().Set(address + 0, false); @@ -379,7 +382,6 @@ public async Task CanDetectCoilsChanged() server.CoilsChanged += (sender, e) => { - Assert.True(e.Coils.Length == 3); actual = e.Coils; }; @@ -399,8 +401,6 @@ await Task.Run(() => }); // Assert - var expected = new int[] { 100, 101, 114 }; - Assert.True(expected.SequenceEqual(actual)); } @@ -450,7 +450,7 @@ await Task.Run(() => [InlineData(true, new short[] { 99, 101, 102 }, new short[] { 100, 101, 103 }, new int[] { 99, 101 }, false)] [InlineData(false, new short[] { 0, 0, 0 }, new short[] { 0, 0, 0 }, new int[] { 99, 100, 101 }, true)] [InlineData(true, new short[] { 0, 0, 0 }, new short[] { 0, 0, 0 }, new int[] { 99, 100, 101 }, true)] - public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] initialValues, short[] newValues, bool[] expected, bool alwaysRaiseChangedEvent) + public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] initialValues, short[] newValues, int[] expected, bool alwaysRaiseChangedEvent) { // Arrange int[] actual = default!; @@ -470,7 +470,6 @@ public async Task CanDetectRegistersChanged(bool useReadWriteMethod, short[] ini server.RegistersChanged += (sender, e) => { - Assert.True(e.Registers.Length == 2); actual = e.Registers; }; From b7bab82e67705759870d45d8c5e4cc6d34d62b91 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Wed, 21 Feb 2024 09:07:52 +0100 Subject: [PATCH 18/25] Prepare release and fix #102 --- .vscode/launch.json | 11 +++-------- CHANGELOG.md | 12 ++++++++++++ README.md | 1 + sample/SampleServerClientTcp/Program.cs | 2 +- version.json | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2092e10..4551eb8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,20 +5,15 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Core Launch (console)", + "name": "Sample: TCP", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/artifacts/bin/SampleServerClientTcp/net6.0/SampleServerClientTcp.dll", + "program": "${workspaceFolder}/artifacts/bin/SampleServerClientTcp/SampleServerClientTcp.dll", "args": [], "cwd": "${workspaceFolder}/sample/SampleServerClientTcp", - "console": "internalConsole", + "console": "externalTerminal", "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" } ] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7f465..50a2bb3 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## v5.1.0 - 2024-02-21 + +### Features +- Add option for raising event even if values of buffer have not changed (#96) +- support for WriteMultipleCoils (#111) + +### Bugs Fixed +- Fixed propagation of cancellationToken (#100) +- Fixed exception for malformed messages (#101) +- typo in ModbusClient docstring (#95) +- SampleServerClientTCP broken? (#102) + ## v5.0.3 - 2023-08-03 - The Modbus TCP server now returns the received unit identifier even when its own unit identifier is set to zero (the default) (solves #93). diff --git a/README.md b/README.md index 2f1cb3d..52b235c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ FluentModbus is a .NET Standard library (2.0 and 2.1) that provides Modbus TCP/R * FC06: WriteSingleRegister #### Class 2: +* FC15: WriteMultipleCoils * FC23: ReadWriteMultipleRegisters Please see the [introduction](https://apollo3zehn.github.io/FluentModbus/) to get a more detailed description on how to use this library! diff --git a/sample/SampleServerClientTcp/Program.cs b/sample/SampleServerClientTcp/Program.cs index 4f82659..37c0a64 100644 --- a/sample/SampleServerClientTcp/Program.cs +++ b/sample/SampleServerClientTcp/Program.cs @@ -113,7 +113,7 @@ static void DoClientWork(ModbusTcpClient client, ILogger logger) Span data; var sleepTime = TimeSpan.FromMilliseconds(100); - var unitIdentifier = 0xFF; + var unitIdentifier = 0x00; var startingAddress = 0; var registerAddress = 0; diff --git a/version.json b/version.json index 68dc3b2..987ca19 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "5.0.3", + "version": "5.1.0", "suffix": "" } \ No newline at end of file From 7935faf4669a86211735b3a559aca11eec7bc92b Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Wed, 21 Feb 2024 09:11:43 +0100 Subject: [PATCH 19/25] Try to fix https://github.com/dotnet/sdk/issues/2902 --- .github/workflows/build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 077398d..5641cbb 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -39,7 +39,7 @@ jobs: run: dotnet build -c Release src/FluentModbus/FluentModbus.csproj - name: Test - run: dotnet test -c Release /p:BuildProjectReferences=false + run: dotnet test -c Release /p:BuildProjectReferences=false /maxcpucount:1 - name: Upload Artifacts uses: actions/upload-artifact@v3 From ff212d7ed39c535d4f041b87b90547b3dacf7a46 Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Wed, 21 Feb 2024 09:13:18 +0100 Subject: [PATCH 20/25] Try again --- .github/workflows/build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 5641cbb..203c8bb 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -36,7 +36,7 @@ jobs: run: python build/create_tag_body.py - name: Build - run: dotnet build -c Release src/FluentModbus/FluentModbus.csproj + run: dotnet build -c Release src/FluentModbus/FluentModbus.csproj /maxcpucount:1 - name: Test run: dotnet test -c Release /p:BuildProjectReferences=false /maxcpucount:1 From e22a3f68191bd38d42a0f7fb284e752e9515b66c Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Wed, 21 Feb 2024 09:53:04 +0100 Subject: [PATCH 21/25] Now everything should be fine --- .github/workflows/build-and-publish.yml | 10 +++++----- Directory.Build.props | 10 ++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 203c8bb..073164a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -36,17 +36,17 @@ jobs: run: python build/create_tag_body.py - name: Build - run: dotnet build -c Release src/FluentModbus/FluentModbus.csproj /maxcpucount:1 + run: dotnet build -c Release src/FluentModbus/FluentModbus.csproj - name: Test - run: dotnet test -c Release /p:BuildProjectReferences=false /maxcpucount:1 + run: dotnet test -c Release /p:BuildProjectReferences=false - name: Upload Artifacts uses: actions/upload-artifact@v3 with: name: artifacts path: | - artifacts/packages/ + artifacts/package/release/ artifacts/tag_body.txt outputs: @@ -70,7 +70,7 @@ jobs: path: artifacts - name: Nuget package (MyGet) - run: dotnet nuget push 'artifacts/packages/*.nupkg' --api-key ${MYGET_API_KEY} --source https://www.myget.org/F/apollo3zehn-dev/api/v3/index.json + run: dotnet nuget push 'artifacts/package/release/*.nupkg' --api-key ${MYGET_API_KEY} --source https://www.myget.org/F/apollo3zehn-dev/api/v3/index.json env: MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }} @@ -96,6 +96,6 @@ jobs: body_path: artifacts/tag_body.txt - name: Nuget package (Nuget) - run: dotnet nuget push 'artifacts/packages/*.nupkg' --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json + run: dotnet nuget push 'artifacts/package/release/*.nupkg' --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} diff --git a/Directory.Build.props b/Directory.Build.props index 8220ece..db678da 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,14 +10,8 @@ https://www.myget.org/F/apollo3zehn-dev/api/v3/index.json - - - - $([MSBuild]::NormalizePath($(MSBuildThisFileDirectory)artifacts)) - $(ArtifactsPath)/obj/$(MSBuildProjectName) - $(BaseIntermediateOutputPath)/$(Configuration) - $(ArtifactsPath)/bin/$(MSBuildProjectName)/$(Configuration) - $(ArtifactsPath)/packages + true + $(MSBuildThisFileDirectory)artifacts \ No newline at end of file From 5c5b93ed21847e03a77e2d4032e40d492042134e Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Wed, 21 Feb 2024 11:15:44 +0100 Subject: [PATCH 22/25] Update docfx config --- doc/docfx.json | 39 ++-- doc/samples/modbus_tcp.md | 2 +- .../material/partials/head.tmpl.partial | 21 -- doc/templates/material/styles/main.css | 202 ------------------ doc/toc.yml | 2 +- 5 files changed, 26 insertions(+), 240 deletions(-) delete mode 100644 doc/templates/material/partials/head.tmpl.partial delete mode 100644 doc/templates/material/styles/main.css diff --git a/doc/docfx.json b/doc/docfx.json index 8ec5db6..ca7a9fe 100644 --- a/doc/docfx.json +++ b/doc/docfx.json @@ -6,24 +6,26 @@ "files": [ "**/*.csproj" ], - "src": "./../src" + "src": "../src" } ], - "dest": "./api", + "dest": "api", "filter": "filterConfig.yml", "properties": { "TargetFramework": "netstandard2.1" - } + }, + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "EnumSortOrder": "alphabetic", + "allowCompilationErrors": false } ], "build": { "content": [ - { - "files": [ - "*.md", - "toc.yml" - ] - }, { "files": [ "api/**.yml", @@ -33,7 +35,9 @@ { "files": [ "samples/**.md", - "samples/**/toc.yml" + "samples/**/toc.yml", + "toc.yml", + "*.md" ] } ], @@ -44,15 +48,20 @@ ] } ], - "dest": "_site", + "output": "_site", "globalMetadata": { "_appTitle": "FluentModbus", - "_appFooter": "Copyright © 2019 Vincent Wilms", - "_appFaviconPath": "images/icon.ico", + "_appFooter": "Copyright © 2024 Vincent Wilms", + "_appFaviconPath": "images/icon.png", "_appLogoPath": "images/logo.svg" }, "fileMetadataFiles": [], - "template":["default","templates/material"], - "markdownEngineName": "markdig" + "template": [ + "default", + "modern" + ], + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false } } \ No newline at end of file diff --git a/doc/samples/modbus_tcp.md b/doc/samples/modbus_tcp.md index f24c089..51c78d0 100644 --- a/doc/samples/modbus_tcp.md +++ b/doc/samples/modbus_tcp.md @@ -156,7 +156,7 @@ static void DoClientWork(ModbusTcpClient client, ILogger logger) Span data; var sleepTime = TimeSpan.FromMilliseconds(100); - var unitIdentifier = 0xFF; + var unitIdentifier = 0x00; var startingAddress = 0; var registerAddress = 0; diff --git a/doc/templates/material/partials/head.tmpl.partial b/doc/templates/material/partials/head.tmpl.partial deleted file mode 100644 index c05e8c1..0000000 --- a/doc/templates/material/partials/head.tmpl.partial +++ /dev/null @@ -1,21 +0,0 @@ -{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - - {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} - - - - {{#_description}}{{/_description}} - - - - - - - - {{#_noindex}}{{/_noindex}} - {{#_enableSearch}}{{/_enableSearch}} - {{#_enableNewTab}}{{/_enableNewTab}} - \ No newline at end of file diff --git a/doc/templates/material/styles/main.css b/doc/templates/material/styles/main.css deleted file mode 100644 index a15b061..0000000 --- a/doc/templates/material/styles/main.css +++ /dev/null @@ -1,202 +0,0 @@ -body { - color: #34393e; - font-family: 'Roboto', sans-serif; - line-height: 1.5; - font-size: 16px; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - word-wrap: break-word -} - -/* HEADINGS */ - -h1 { - font-weight: 600; - font-size: 32px; -} - -h2 { - font-weight: 600; - font-size: 24px; - line-height: 1.8; -} - -h3 { - font-weight: 600; - font-size: 20px; - line-height: 1.8; -} - -h5 { - font-size: 14px; - padding: 10px 0px; -} - -article h1, -article h2, -article h3, -article h4 { - margin-top: 35px; - margin-bottom: 15px; -} - -article h4 { - padding-bottom: 8px; - border-bottom: 2px solid #ddd; -} - -/* NAVBAR */ - -.navbar-brand>img { - color: #fff; -} - -.navbar { - border: none; - /* Both navbars use box-shadow */ - -webkit-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); - -moz-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); - box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); -} - -.subnav { - border-top: 1px solid #ddd; - background-color: #fff; -} - -.navbar-inverse { - background-color: #78002e; - z-index: 100; -} - -.navbar-inverse .navbar-nav>li>a, -.navbar-inverse .navbar-text { - color: #fff; - background-color: #78002e; - border-bottom: 3px solid transparent; - padding-bottom: 12px; -} - -.navbar-inverse .navbar-nav>li>a:focus, -.navbar-inverse .navbar-nav>li>a:hover { - color: #fff; - background-color: #78002e; - border-bottom: 3px solid white; -} - -.navbar-inverse .navbar-nav>.active>a, -.navbar-inverse .navbar-nav>.active>a:focus, -.navbar-inverse .navbar-nav>.active>a:hover { - color: #fff; - background-color: #78002e; - border-bottom: 3px solid white; -} - -.navbar-form .form-control { - border: none; - border-radius: 20px; -} - -/* SIDEBAR */ - -.toc .level1>li { - font-weight: 400; -} - -.toc .nav>li>a { - color: #34393e; -} - -.sidefilter { - background-color: #fff; - border-left: none; - border-right: none; -} - -.sidefilter { - background-color: #fff; - border-left: none; - border-right: none; -} - -.toc-filter { - padding: 10px; - margin: 0; -} - -.toc-filter>input { - border: 2px solid #ddd; - border-radius: 20px; -} - -.toc-filter>.filter-icon { - display: none; -} - -.sidetoc>.toc { - background-color: #fff; - overflow-x: hidden; -} - -.sidetoc { - background-color: #fff; - border: none; -} - -/* ALERTS */ - -.alert { - padding: 0px 0px 5px 0px; - color: inherit; - background-color: inherit; - border: none; - box-shadow: 0px 2px 2px 0px rgba(100, 100, 100, 0.4); -} - -.alert>p { - margin-bottom: 0; - padding: 5px 10px; -} - -.alert>ul { - margin-bottom: 0; - padding: 5px 40px; -} - -.alert>h5 { - padding: 10px 15px; - margin-top: 0; - text-transform: uppercase; - font-weight: bold; - border-radius: 4px 4px 0 0; -} - -.alert-info>h5 { - color: #1976d2; - border-bottom: 4px solid #1976d2; - background-color: #e3f2fd; -} - -.alert-warning>h5 { - color: #f57f17; - border-bottom: 4px solid #f57f17; - background-color: #fff3e0; -} - -.alert-danger>h5 { - color: #d32f2f; - border-bottom: 4px solid #d32f2f; - background-color: #ffebee; -} - -/* CODE HIGHLIGHT */ -pre { - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - word-break: break-all; - word-wrap: break-word; - background-color: #fffaef; - border-radius: 4px; - box-shadow: 0px 1px 4px 1px rgba(100, 100, 100, 0.4); -} \ No newline at end of file diff --git a/doc/toc.yml b/doc/toc.yml index 0575601..a3bdd57 100644 --- a/doc/toc.yml +++ b/doc/toc.yml @@ -1,5 +1,5 @@ - name: Samples href: samples/ -- name: Api Documentation +- name: API href: api/ From 522c429411a3450eb05d08d9659d0e54ac39164c Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Wed, 21 Feb 2024 11:29:07 +0100 Subject: [PATCH 23/25] Mark dark theme the default --- doc/dark-mode/public/main.js | 3 +++ doc/docfx.json | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 doc/dark-mode/public/main.js diff --git a/doc/dark-mode/public/main.js b/doc/dark-mode/public/main.js new file mode 100644 index 0000000..d1ede69 --- /dev/null +++ b/doc/dark-mode/public/main.js @@ -0,0 +1,3 @@ +export default { + defaultTheme: 'dark' +} \ No newline at end of file diff --git a/doc/docfx.json b/doc/docfx.json index ca7a9fe..b4051b9 100644 --- a/doc/docfx.json +++ b/doc/docfx.json @@ -58,7 +58,8 @@ "fileMetadataFiles": [], "template": [ "default", - "modern" + "modern", + "dark-mode" ], "postProcessors": [], "keepFileLink": false, From da5ba69da8e5bc0dd88c1e6aba967a1c4133e847 Mon Sep 17 00:00:00 2001 From: Marnix Date: Mon, 22 Apr 2024 16:05:41 -0400 Subject: [PATCH 24/25] Make IsConnected an abstract member of ModbusClient --- src/FluentModbus/Client/ModbusClient.cs | 5 +++++ src/FluentModbus/Client/ModbusRtuClient.cs | 2 +- src/FluentModbus/Client/ModbusTcpClient.cs | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index a57a023..a83a722 100755 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -10,6 +10,11 @@ public abstract partial class ModbusClient { #region Properties + /// + /// Gets the connection status of the underlying client. + /// + public abstract bool IsConnected { get; } + protected private bool SwapBytes { get; set; } #endregion diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs index 37752ee..e70c899 100755 --- a/src/FluentModbus/Client/ModbusRtuClient.cs +++ b/src/FluentModbus/Client/ModbusRtuClient.cs @@ -31,7 +31,7 @@ public ModbusRtuClient() /// /// Gets the connection status of the underlying serial port. /// - public bool IsConnected => _serialPort?.Value.IsOpen ?? false; + public override bool IsConnected => _serialPort?.Value.IsOpen ?? false; /// /// Gets or sets the serial baud rate. Default is 9600. diff --git a/src/FluentModbus/Client/ModbusTcpClient.cs b/src/FluentModbus/Client/ModbusTcpClient.cs index 9327e8f..6fc4722 100755 --- a/src/FluentModbus/Client/ModbusTcpClient.cs +++ b/src/FluentModbus/Client/ModbusTcpClient.cs @@ -35,6 +35,11 @@ public ModbusTcpClient() #region Properties + /// + /// Gets the connection status of the underlying TCP client. + /// + public override bool IsConnected => _tcpClient?.Value.Connected ?? false; + /// /// Gets or sets the connect timeout in milliseconds. Default is 1000 ms. /// @@ -56,11 +61,6 @@ public ModbusTcpClient() #region Methods - /// - /// Gets the connection status of the underlying TCP client. - /// - public bool IsConnected => _tcpClient?.Value.Connected ?? false; - /// /// Connect to localhost at port 502 with as default byte layout. /// From 3b3de6e2ef84c93ab5e873926d52a576093ae07d Mon Sep 17 00:00:00 2001 From: Apollo3zehn Date: Tue, 23 Apr 2024 10:40:59 +0200 Subject: [PATCH 25/25] Prepare release --- CHANGELOG.md | 5 +++++ version.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50a2bb3..3930ec6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v5.2.0 - 2024-04-23 + +### Features +- Make IsConnected an abstract member of ModbusClient (#115) + ## v5.1.0 - 2024-02-21 ### Features diff --git a/version.json b/version.json index 987ca19..0fece81 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "5.1.0", + "version": "5.2.0", "suffix": "" } \ No newline at end of file