├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── BuildOnly.yml │ └── dotnet.yml ├── .gitignore ├── Directory.Build.props ├── Images ├── ModbusRx.ico └── ModbusRx.png ├── LICENSE ├── README.md ├── Version.json ├── src ├── ModbusRx.DriverTest │ ├── ModbusRx.DriverTest.csproj │ └── Program.cs ├── ModbusRx.IntegrationTests │ ├── CustomMessages │ │ ├── CustomReadHoldingRegistersRequest.cs │ │ ├── CustomReadHoldingRegistersResponse.cs │ │ ├── CustomWriteMultipleRegistersRequest.cs │ │ └── CustomWriteMultipleRegistersResponse.cs │ ├── EnronFixture.cs │ ├── ModbusRx.IntegrationTests.csproj │ ├── ModbusRxIpMasterFixture.cs │ ├── ModbusRxMasterFixture.cs │ ├── ModbusRxSerialAsciiMasterFixture.cs │ ├── ModbusRxSerialAsciiMasterJamodSerialAsciiSlaveFixture.cs │ ├── ModbusRxSerialAsciiMasterModbusRxSerialAsciiSlaveFixture.cs │ ├── ModbusRxSerialMasterFixture.cs │ ├── ModbusRxSerialRtuMasterDl06SlaveFixture.cs │ ├── ModbusRxSerialRtuMasterFixture.cs │ ├── ModbusRxSerialRtuMasterModbusRxSerialRtuSlaveFixture.cs │ ├── ModbusRxSerialRtuSlaveFixture.cs │ ├── ModbusRxTcpMasterJamodTcpSlaveFixture.cs │ ├── ModbusRxTcpMasterNModbusTcpSlaveFixture.cs │ ├── ModbusRxTcpSlaveFixture.cs │ ├── ModbusRxUdpMasterModbusRxUdpSlaveFixture.cs │ ├── ModbusRxUdpSlaveFixture.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── TestCases.cs ├── ModbusRx.UnitTests │ ├── Data │ │ ├── BoolModbusDataCollectionFixture.cs │ │ ├── DataStoreEventArgsFixture.cs │ │ ├── DataStoreFixture.cs │ │ ├── DiscreteCollectionFixture.cs │ │ ├── ModbusDataCollectionFixture.cs │ │ ├── RegisterCollectionFixture.cs │ │ └── UshortModbusDataCollectionFixture.cs │ ├── Device │ │ ├── ModbusMasterFixture.cs │ │ ├── ModbusSlaveFixture.cs │ │ └── TcpConnectionEventArgsFixture.cs │ ├── IO │ │ ├── EmptyTransportFixture.cs │ │ ├── ModbusAsciiTransportFixture.cs │ │ ├── ModbusRtuTransportFixture.cs │ │ ├── ModbusSerialTransportFixture.cs │ │ ├── ModbusTcpTransportFixture.cs │ │ ├── ModbusTransportFixture.cs │ │ └── UdpClientAdapterFixture.cs │ ├── InvalidModbusRequestExceptionFixture.cs │ ├── Message │ │ ├── DiagnosticsRequestResponseFixture.cs │ │ ├── MessageUtility.cs │ │ ├── ModbusMessageFactoryFixture.cs │ │ ├── ModbusMessageFixture.cs │ │ ├── ModbusMessageImplFixture.cs │ │ ├── ModbusMessageWithDataFixture.cs │ │ ├── ReadCoilsInputsRequestFixture.cs │ │ ├── ReadCoilsInputsResponseFixture.cs │ │ ├── ReadHoldingInputRegistersRequestFixture.cs │ │ ├── ReadHoldingInputRegistersResponseFixture.cs │ │ ├── ReadWriteMultipleRegistersRequestFixture.cs │ │ ├── ReturnQueryDataRequestResponseFixture.cs │ │ ├── SlaveExceptionResponseFixture.cs │ │ ├── WriteMultipleCoilsRequestFixture.cs │ │ ├── WriteMultipleCoilsResponseFixture.cs │ │ ├── WriteMultipleRegistersRequestFixture.cs │ │ ├── WriteMultipleRegistersResponseFixture.cs │ │ ├── WriteSingleCoilRequestResponseFixture.cs │ │ └── WriteSingleRegisterRequestResponseFixture.cs │ ├── ModbusRx.UnitTests.csproj │ ├── SlaveExceptionFixture.cs │ └── Utility │ │ ├── CollectionUtilityFixture.cs │ │ ├── DiscriminatedUnionFixture.cs │ │ └── ModbusUtilityFixture.cs ├── ModbusRx.sln └── ModbusRx │ ├── Data │ ├── DataStore.cs │ ├── DataStoreEventArgs.cs │ ├── DataStoreFactory.cs │ ├── DiscreteCollection.cs │ ├── IDataCollection.cs │ ├── ModbusDataCollection.cs │ ├── ModbusDataType.cs │ └── RegisterCollection.cs │ ├── Device │ ├── IModbusMaster.cs │ ├── IModbusSerialMaster.cs │ ├── ModbusDevice.cs │ ├── ModbusIpMaster.cs │ ├── ModbusMaster.cs │ ├── ModbusMasterTcpConnection.cs │ ├── ModbusSerialMaster.cs │ ├── ModbusSerialSlave.cs │ ├── ModbusSlave.cs │ ├── ModbusSlaveRequestEventArgs.cs │ ├── ModbusTcpSlave.cs │ ├── ModbusUdpSlave.cs │ └── TcpConnectionEventArgs.cs │ ├── Extensions │ └── Enron │ │ └── EnronModbus.cs │ ├── GlobalSuppressions.cs │ ├── IO │ ├── EmptyTransport.cs │ ├── IStreamResource.cs │ ├── ModbusAsciiTransport.cs │ ├── ModbusIpTransport.cs │ ├── ModbusRtuTransport.cs │ ├── ModbusSerialTransport.cs │ ├── ModbusTransport.cs │ ├── SerialPortAdapter.cs │ ├── StreamResourceUtility.cs │ ├── TcpClientAdapter.cs │ └── UdpClientAdapter.cs │ ├── InvalidModbusRequestException.cs │ ├── Message │ ├── AbstractModbusMessage.cs │ ├── AbstractModbusMessageWithData.cs │ ├── DiagnosticsRequestResponse.cs │ ├── IModbusMessage.cs │ ├── IModbusRequest.cs │ ├── ModbusMessageFactory.cs │ ├── ModbusMessageImpl.cs │ ├── ReadCoilsInputsRequest.cs │ ├── ReadCoilsInputsResponse.cs │ ├── ReadHoldingInputRegistersRequest.cs │ ├── ReadHoldingInputRegistersResponse.cs │ ├── ReadWriteMultipleRegistersRequest.cs │ ├── SlaveExceptionResponse.cs │ ├── WriteMultipleCoilsRequest.cs │ ├── WriteMultipleCoilsResponse.cs │ ├── WriteMultipleRegistersRequest.cs │ ├── WriteMultipleRegistersResponse.cs │ ├── WriteSingleCoilRequestResponse.cs │ └── WriteSingleRegisterRequestResponse.cs │ ├── Modbus.cs │ ├── ModbusRx.csproj │ ├── Reactive │ ├── Create.cs │ ├── ModbusCommunicationException.cs │ ├── ModbusSerialSlaveExtensions.cs │ ├── ModbusTcpSlaveExtensions.cs │ └── ModbusUdpSlaveExtensions.cs │ ├── Resources.cs │ ├── SlaveException.cs │ ├── Unme.Common │ ├── DisposableUtility.cs │ └── SequenceUtility.cs │ └── Utility │ ├── DiscriminatedUnion.cs │ └── ModbusUtility.cs └── stylecop.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "nuget" # See documentation for possible values 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "monthly" 10 | time: "08:00" 11 | timezone: "Europe/London" 12 | open-pull-requests-limit: 20 13 | -------------------------------------------------------------------------------- /.github/workflows/BuildOnly.yml: -------------------------------------------------------------------------------- 1 | name: ModbusRx CI-BuildOnly 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - main 7 | 8 | jobs: 9 | windows-latest: 10 | name: windows-latest 11 | runs-on: windows-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 17 | 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@v4 20 | with: 21 | dotnet-version: | 22 | 6.0.x 23 | 7.0.x 24 | 8.0.x 25 | 26 | - name: Add MSBuild to PATH 27 | uses: glennawatson/setup-msbuild@v1.0.3 28 | with: 29 | prerelease: true 30 | 31 | - name: NBGV 32 | id: nbgv 33 | uses: dotnet/nbgv@master 34 | with: 35 | setAllVars: true 36 | - run: echo 'SemVer2=${{ steps.nbgv.outputs.SemVer2 }}' 37 | 38 | - name: NuGet Restore 39 | run: dotnet restore ModbusRx.sln 40 | working-directory: src 41 | 42 | - name: Build 43 | run: msbuild /t:build,pack /nowarn:MSB4011 /maxcpucount /p:NoPackageAnalysis=true /verbosity:minimal /p:Configuration=Release ModbusRx.sln 44 | working-directory: src 45 | 46 | - name: Run Unit Tests and Generate Coverage 47 | uses: glennawatson/coverlet-msbuild@v2 48 | with: 49 | project-files: 'src/**/*Tests*.csproj' 50 | no-build: true 51 | include-filter: 'ModbusRx*' 52 | output-format: cobertura 53 | configuration: Release 54 | 55 | - name: Upload Code Coverage 56 | uses: codecov/codecov-action@v5 57 | 58 | - name: Create NuGet Artifacts 59 | uses: actions/upload-artifact@master 60 | with: 61 | name: nuget 62 | path: '**/*.nupkg' 63 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: ModbusRx CI-Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | windows-latest: 10 | name: windows-latest 11 | runs-on: windows-latest 12 | 13 | permissions: write-all 14 | environment: 15 | name: release 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 21 | 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: | 26 | 6.0.x 27 | 7.0.x 28 | 8.0.x 29 | 30 | - name: Add MSBuild to PATH 31 | uses: glennawatson/setup-msbuild@v1.0.3 32 | with: 33 | prerelease: true 34 | 35 | - name: NBGV 36 | id: nbgv 37 | uses: dotnet/nbgv@master 38 | with: 39 | setAllVars: true 40 | - run: echo 'SemVer2=${{ steps.nbgv.outputs.SemVer2 }}' 41 | 42 | - name: NuGet Restore 43 | run: dotnet restore ModbusRx.sln 44 | working-directory: src 45 | 46 | - name: Build 47 | run: msbuild /t:build,pack /nowarn:MSB4011 /maxcpucount /p:NoPackageAnalysis=true /verbosity:minimal /p:Configuration=Release ModbusRx.sln 48 | working-directory: src 49 | 50 | - name: Run Unit Tests and Generate Coverage 51 | uses: glennawatson/coverlet-msbuild@v2 52 | with: 53 | project-files: 'src/**/*Tests*.csproj' 54 | no-build: true 55 | include-filter: 'ModbusRx*' 56 | output-format: cobertura 57 | configuration: Release 58 | 59 | - name: Upload Code Coverage 60 | uses: codecov/codecov-action@v5 61 | 62 | - name: Create NuGet Artifacts 63 | uses: actions/upload-artifact@master 64 | with: 65 | name: nuget 66 | path: '**/*.nupkg' 67 | 68 | - name: NuGet Push 69 | env: 70 | NUGET_AUTH_TOKEN: ${{ secrets.NUGET_API_KEY }} 71 | SOURCE_URL: https://api.nuget.org/v3/index.json 72 | run: | 73 | dotnet nuget push -s ${{ env.SOURCE_URL }} -k ${{ env.NUGET_AUTH_TOKEN }} **/*.nupkg --skip-duplicate 74 | 75 | - name: Changelog 76 | uses: glennawatson/ChangeLog@v1 77 | id: changelog 78 | 79 | - name: Create Release 80 | uses: actions/create-release@v1.1.4 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 83 | with: 84 | tag_name: ${{ steps.nbgv.outputs.SemVer2 }} 85 | release_name: ${{ steps.nbgv.outputs.SemVer2 }} 86 | body: | 87 | ${{ steps.changelog.outputs.commitLog }} 88 | -------------------------------------------------------------------------------- /Images/ModbusRx.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPulman/ModbusRx/1ce1e87c2d6fe12350fd0f1ef0f8112bece09487/Images/ModbusRx.ico -------------------------------------------------------------------------------- /Images/ModbusRx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPulman/ModbusRx/1ce1e87c2d6fe12350fd0f1ef0f8112bece09487/Images/ModbusRx.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chris Pulman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisPulman/ModbusRx/1ce1e87c2d6fe12350fd0f1ef0f8112bece09487/README.md -------------------------------------------------------------------------------- /Version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.0", 4 | "publicReleaseRefSpec": [ 5 | "^refs/heads/master$", 6 | "^refs/heads/main$" 7 | ], 8 | "nugetPackageVersion": { 9 | "semVer": 2, 10 | "precision": "build" 11 | }, 12 | "cloudBuild": { 13 | "setVersionVariables": true, 14 | "buildNumber": { 15 | "enabled": false 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ModbusRx.DriverTest/ModbusRx.DriverTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/ModbusRx.DriverTest/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reactive.Linq; 5 | using ModbusRx.Reactive; 6 | 7 | namespace ModbusRx.DriverTest 8 | { 9 | internal static class Program 10 | { 11 | private static void Main(string[] args) 12 | { 13 | try 14 | { 15 | Create.TcpIpSlave("127.0.0.1") 16 | .WriteHoldingRegisters(0, Observable.Return(new ushort[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 })) 17 | .Subscribe(slave => 18 | { 19 | Console.WriteLine(slave.Masters.Count); 20 | }); 21 | 22 | Create.TcpIpMaster("127.0.0.1") 23 | .Where(x => x.master != null) 24 | .Do(async x => await x.master!.WriteMultipleRegistersAsync(0, new ushort[] { 100, 101 })) 25 | .ReadHoldingRegisters(0, 12) 26 | .Subscribe(modbus => 27 | { 28 | for (var i = 0; i < modbus.data?.Length; i++) 29 | { 30 | Console.WriteLine($"Input {i}={modbus.data[i]}"); 31 | } 32 | }); 33 | 34 | Console.ReadLine(); 35 | } 36 | catch (Exception e) 37 | { 38 | Console.WriteLine(e.Message); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/CustomMessages/CustomReadHoldingRegistersResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using ModbusRx.Data; 8 | using ModbusRx.Message; 9 | 10 | namespace ModbusRx.IntegrationTests.CustomMessages; 11 | 12 | /// 13 | /// CustomReadHoldingRegistersResponse. 14 | /// 15 | /// 16 | public class CustomReadHoldingRegistersResponse : IModbusMessage 17 | { 18 | /// 19 | /// The data. 20 | /// 21 | private RegisterCollection? _data; 22 | 23 | /// 24 | /// Gets the data. 25 | /// 26 | /// 27 | /// The data. 28 | /// 29 | public ushort[] Data => _data!.ToArray(); 30 | 31 | /// 32 | /// Gets composition of the slave address and protocol data unit. 33 | /// 34 | public byte[] MessageFrame 35 | { 36 | get 37 | { 38 | var frame = new List() 39 | { 40 | SlaveAddress 41 | }; 42 | frame.AddRange(ProtocolDataUnit); 43 | 44 | return frame.ToArray(); 45 | } 46 | } 47 | 48 | /// 49 | /// Gets composition of the function code and message data. 50 | /// 51 | public byte[] ProtocolDataUnit 52 | { 53 | get 54 | { 55 | var pdu = new List 56 | { 57 | FunctionCode, 58 | ByteCount 59 | }; 60 | pdu.AddRange(_data!.NetworkBytes); 61 | 62 | return pdu.ToArray(); 63 | } 64 | } 65 | 66 | /// 67 | /// Gets or sets a unique identifier assigned to a message when using the IP protocol. 68 | /// 69 | public ushort TransactionId { get; set; } 70 | 71 | /// 72 | /// Gets or sets the function code tells the server what kind of action to perform. 73 | /// 74 | public byte FunctionCode { get; set; } 75 | 76 | /// 77 | /// Gets or sets address of the slave (server). 78 | /// 79 | public byte SlaveAddress { get; set; } 80 | 81 | /// 82 | /// Gets or sets the byte count. 83 | /// 84 | /// 85 | /// The byte count. 86 | /// 87 | public byte ByteCount { get; set; } 88 | 89 | /// 90 | /// Initializes a modbus message from the specified message frame. 91 | /// 92 | /// Bytes of Modbus frame. 93 | /// frame. 94 | /// Message frame does not contain enough bytes. - frame. 95 | public void Initialize(byte[] frame) 96 | { 97 | if (frame == null) 98 | { 99 | throw new ArgumentNullException(nameof(frame)); 100 | } 101 | 102 | if (frame.Length < 3 || frame.Length < 3 + frame[2]) 103 | { 104 | throw new ArgumentException("Message frame does not contain enough bytes.", nameof(frame)); 105 | } 106 | 107 | SlaveAddress = frame[0]; 108 | FunctionCode = frame[1]; 109 | ByteCount = frame[2]; 110 | _data = new RegisterCollection(frame.Skip(3).Take(ByteCount).ToArray()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/CustomMessages/CustomWriteMultipleRegistersResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net; 7 | using ModbusRx.Message; 8 | 9 | namespace ModbusRx.IntegrationTests.CustomMessages; 10 | 11 | /// 12 | /// CustomWriteMultipleRegistersResponse. 13 | /// 14 | /// 15 | public class CustomWriteMultipleRegistersResponse : IModbusMessage 16 | { 17 | /// 18 | /// Gets composition of the slave address and protocol data unit. 19 | /// 20 | public byte[] MessageFrame 21 | { 22 | get 23 | { 24 | var frame = new List 25 | { 26 | SlaveAddress 27 | }; 28 | frame.AddRange(ProtocolDataUnit); 29 | 30 | return frame.ToArray(); 31 | } 32 | } 33 | 34 | /// 35 | /// Gets composition of the function code and message data. 36 | /// 37 | public byte[] ProtocolDataUnit 38 | { 39 | get 40 | { 41 | var pdu = new List 42 | { 43 | FunctionCode 44 | }; 45 | pdu.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)StartAddress))); 46 | pdu.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)NumberOfPoints))); 47 | 48 | return pdu.ToArray(); 49 | } 50 | } 51 | 52 | /// 53 | /// Gets or sets a unique identifier assigned to a message when using the IP protocol. 54 | /// 55 | public ushort TransactionId { get; set; } 56 | 57 | /// 58 | /// Gets or sets the function code tells the server what kind of action to perform. 59 | /// 60 | public byte FunctionCode { get; set; } 61 | 62 | /// 63 | /// Gets or sets address of the slave (server). 64 | /// 65 | public byte SlaveAddress { get; set; } 66 | 67 | /// 68 | /// Gets or sets the start address. 69 | /// 70 | /// 71 | /// The start address. 72 | /// 73 | public ushort StartAddress { get; set; } 74 | 75 | /// 76 | /// Gets or sets the number of points. 77 | /// 78 | /// 79 | /// The number of points. 80 | /// 81 | public ushort NumberOfPoints { get; set; } 82 | 83 | /// 84 | /// Initializes a modbus message from the specified message frame. 85 | /// 86 | /// Bytes of Modbus frame. 87 | /// frame. 88 | /// Message frame does not contain enough bytes. 89 | public void Initialize(byte[] frame) 90 | { 91 | if (frame == null) 92 | { 93 | throw new ArgumentNullException(nameof(frame)); 94 | } 95 | 96 | if (frame.Length < 6) 97 | { 98 | throw new FormatException("Message frame does not contain enough bytes."); 99 | } 100 | 101 | SlaveAddress = frame[0]; 102 | FunctionCode = frame[1]; 103 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 104 | NumberOfPoints = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 4)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/EnronFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | using ModbusRx.Extensions.Enron; 6 | using Xunit; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// EnronFixture. 12 | /// 13 | /// 14 | public class EnronFixture : NModbusSerialRtuMasterDl06SlaveFixture 15 | { 16 | /// 17 | /// Reads the holding registers32. 18 | /// 19 | [Fact] 20 | public virtual void ReadHoldingRegisters32() 21 | { 22 | var registers = Master?.ReadHoldingRegisters32(SlaveAddress, 104, 2); 23 | Assert.Equal(new uint[] { 0, 0 }, registers); 24 | } 25 | 26 | /// 27 | /// Reads the input registers32. 28 | /// 29 | [Fact] 30 | public virtual void ReadInputRegisters32() 31 | { 32 | var registers = Master?.ReadInputRegisters32(SlaveAddress, 104, 2); 33 | Assert.Equal(new uint[] { 0, 0 }, registers); 34 | } 35 | 36 | /// 37 | /// Writes the single register32. 38 | /// 39 | [Fact] 40 | public virtual void WriteSingleRegister32() 41 | { 42 | const ushort testAddress = 200; 43 | const uint testValue = 350; 44 | 45 | var originalValue = Master!.ReadHoldingRegisters32(SlaveAddress, testAddress, 1)[0]; 46 | Master?.WriteSingleRegister32(SlaveAddress, testAddress, testValue); 47 | Assert.Equal(testValue, Master?.ReadHoldingRegisters32(SlaveAddress, testAddress, 1)[0]); 48 | Master?.WriteSingleRegister32(SlaveAddress, testAddress, originalValue); 49 | Assert.Equal(originalValue, Master!.ReadHoldingRegisters(SlaveAddress, testAddress, 1)[0]); 50 | } 51 | 52 | /// 53 | /// Writes the multiple registers32. 54 | /// 55 | [Fact] 56 | public virtual void WriteMultipleRegisters32() 57 | { 58 | const ushort testAddress = 120; 59 | var testValues = new uint[] { 10, 20, 30, 40, 50 }; 60 | 61 | var originalValues = Master?.ReadHoldingRegisters32(SlaveAddress, testAddress, (ushort)testValues.Length); 62 | Master?.WriteMultipleRegisters32(SlaveAddress, testAddress, testValues); 63 | var newValues = Master?.ReadHoldingRegisters32(SlaveAddress, testAddress, (ushort)testValues.Length); 64 | Assert.Equal(testValues, newValues); 65 | Master?.WriteMultipleRegisters32(SlaveAddress, testAddress, originalValues!); 66 | } 67 | } 68 | #endif 69 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRx.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxIpMasterFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net.Sockets; 5 | using System.Threading; 6 | using CP.IO.Ports; 7 | using ModbusRx.Device; 8 | using Xunit; 9 | 10 | namespace ModbusRx.IntegrationTests; 11 | 12 | /// 13 | /// ModbusIpMasterFixture. 14 | /// 15 | public class ModbusRxIpMasterFixture 16 | { 17 | /// 18 | /// Overrides the timeout on TCP client. 19 | /// 20 | [Fact] 21 | public void OverrideTimeoutOnTcpClient() 22 | { 23 | var listener = new TcpListener(ModbusRxMasterFixture.TcpHost, ModbusRxMasterFixture.Port); 24 | using var slave = ModbusTcpSlave.CreateTcp(ModbusRxMasterFixture.SlaveAddress, listener); 25 | var slaveThread = new Thread(async () => await slave.ListenAsync()); 26 | slaveThread.Start(); 27 | 28 | var client = new TcpClientRx(ModbusRxMasterFixture.TcpHost.ToString(), ModbusRxMasterFixture.Port) 29 | { 30 | ReadTimeout = 1500, 31 | WriteTimeout = 3000 32 | }; 33 | using var master = ModbusIpMaster.CreateIp(client); 34 | Assert.Equal(1500, client.ReadTimeout); 35 | Assert.Equal(3000, client.WriteTimeout); 36 | } 37 | 38 | /// 39 | /// Overrides the timeout on network stream. 40 | /// 41 | [Fact] 42 | public void OverrideTimeoutOnNetworkStream() 43 | { 44 | var listener = new TcpListener(ModbusRxMasterFixture.TcpHost, ModbusRxMasterFixture.Port); 45 | using var slave = ModbusTcpSlave.CreateTcp(ModbusRxMasterFixture.SlaveAddress, listener); 46 | var slaveThread = new Thread(async () => await slave.ListenAsync()); 47 | slaveThread.Start(); 48 | 49 | var client = new TcpClientRx(ModbusRxMasterFixture.TcpHost.ToString(), ModbusRxMasterFixture.Port); 50 | client.Stream.ReadTimeout = 1500; 51 | client.Stream.WriteTimeout = 3000; 52 | using var master = ModbusIpMaster.CreateIp(client); 53 | Assert.Equal(1500, client.ReadTimeout); 54 | Assert.Equal(3000, client.WriteTimeout); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialAsciiMasterFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | using System; 6 | using ModbusRx.Device; 7 | using Xunit; 8 | 9 | namespace ModbusRx.IntegrationTests; 10 | 11 | /// 12 | /// NModbusSerialAsciiMasterFixture. 13 | /// 14 | public class ModbusRxSerialAsciiMasterFixture 15 | { 16 | /// 17 | /// ns the modbus ASCII master read timeout. 18 | /// 19 | [Fact] 20 | public void ModbusRxAsciiMaster_ReadTimeout() 21 | { 22 | var port = ModbusMasterFixture.CreateAndOpenSerialPort(ModbusMasterFixture.DefaultMasterSerialPortName); 23 | using IModbusSerialMaster master = ModbusSerialMaster.CreateAscii(port); 24 | master.Transport.ReadTimeout = master.Transport.WriteTimeout = 1000; 25 | Assert.Throws(() => master.ReadCoils(100, 1, 1)); 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialAsciiMasterJamodSerialAsciiSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if JAMOD 5 | using ModbusRx.Device; 6 | using Xunit; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// ModbusRxSerialAsciiMasterJamodSerialAsciiSlaveFixture. 12 | /// 13 | /// 14 | public class ModbusRxSerialAsciiMasterJamodSerialAsciiSlaveFixture : ModbusMasterFixture 15 | { 16 | private const string Program = $"SerialSlave {DefaultSlaveSerialPortName} ASCII"; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | public ModbusRxSerialAsciiMasterJamodSerialAsciiSlaveFixture() 22 | { 23 | StartJamodSlave(Program); 24 | 25 | MasterSerialPort = CreateAndOpenSerialPort(DefaultMasterSerialPortName); 26 | Master = ModbusSerialMaster.CreateAscii(MasterSerialPort); 27 | } 28 | 29 | /// 30 | /// Jamod slave does not support this function. 31 | /// 32 | public override void ReadWriteMultipleRegisters() 33 | { 34 | } 35 | 36 | /// 37 | /// Reads the coils. 38 | /// 39 | [Fact] 40 | public override void ReadCoils() => 41 | base.ReadCoils(); 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialAsciiMasterModbusRxSerialAsciiSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | using ModbusRx.Device; 6 | using Xunit; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// ModbusRxSerialAsciiMasterNModbusSerialAsciiSlaveFixture. 12 | /// 13 | /// 14 | public class ModbusRxSerialAsciiMasterModbusRxSerialAsciiSlaveFixture : ModbusSerialMasterFixture 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public ModbusRxSerialAsciiMasterModbusRxSerialAsciiSlaveFixture() 20 | { 21 | MasterSerialPort = CreateAndOpenSerialPort(DefaultMasterSerialPortName); 22 | Master = ModbusSerialMaster.CreateAscii(MasterSerialPort); 23 | SetupSlaveSerialPort(); 24 | Slave = ModbusSerialSlave.CreateAscii(SlaveAddress, SlaveSerialPort!); 25 | 26 | StartSlave(); 27 | } 28 | 29 | /// 30 | /// Reads the coils. 31 | /// 32 | [Fact] 33 | public override void ReadCoils() => 34 | base.ReadCoils(); 35 | 36 | /// 37 | /// Reads the inputs. 38 | /// 39 | [Fact] 40 | public override void ReadInputs() => 41 | base.ReadInputs(); 42 | 43 | /// 44 | /// Reads the holding registers. 45 | /// 46 | [Fact] 47 | public override void ReadHoldingRegisters() => 48 | base.ReadHoldingRegisters(); 49 | 50 | /// 51 | /// Reads the input registers. 52 | /// 53 | [Fact] 54 | public override void ReadInputRegisters() => 55 | base.ReadInputRegisters(); 56 | 57 | /// 58 | /// Writes the single coil. 59 | /// 60 | [Fact] 61 | public override void WriteSingleCoil() => 62 | base.WriteSingleCoil(); 63 | 64 | /// 65 | /// Writes the multiple coils. 66 | /// 67 | [Fact] 68 | public override void WriteMultipleCoils() => 69 | base.WriteMultipleCoils(); 70 | 71 | /// 72 | /// Writes the single register. 73 | /// 74 | [Fact] 75 | public override void WriteSingleRegister() => 76 | base.WriteSingleRegister(); 77 | 78 | /// 79 | /// Writes the multiple registers. 80 | /// 81 | [Fact] 82 | public override void WriteMultipleRegisters() => 83 | base.WriteMultipleRegisters(); 84 | 85 | /// 86 | /// Reads the write multiple registers. 87 | /// 88 | [Fact] 89 | public override void ReadWriteMultipleRegisters() => 90 | base.ReadWriteMultipleRegisters(); 91 | 92 | /// 93 | /// Returns the query data. 94 | /// 95 | [Fact] 96 | public override void ReturnQueryData() => 97 | base.ReturnQueryData(); 98 | } 99 | #endif 100 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialMasterFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Device; 5 | using Xunit; 6 | 7 | namespace ModbusRx.IntegrationTests; 8 | 9 | /// 10 | /// ModbusSerialMasterFixture. 11 | /// 12 | /// 13 | public abstract class ModbusRxSerialMasterFixture : ModbusRxMasterFixture 14 | { 15 | /// 16 | /// Returns the query data. 17 | /// 18 | [Fact] 19 | public virtual void ReturnQueryData() 20 | { 21 | Assert.True(((ModbusSerialMaster)Master!).ReturnQueryData(SlaveAddress, 18)); 22 | Assert.True(((ModbusSerialMaster)Master).ReturnQueryData(SlaveAddress, 5)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialRtuMasterDl06SlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | using ModbusRx.Device; 6 | using Xunit; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// ModbusRxSerialRtuMasterDl06SlaveFixture. 12 | /// 13 | /// 14 | public class ModbusRxSerialRtuMasterDl06SlaveFixture : ModbusSerialMasterFixture 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public ModbusRxSerialRtuMasterDl06SlaveFixture() 20 | { 21 | MasterSerialPort = CreateAndOpenSerialPort("COM1"); 22 | Master = ModbusSerialMaster.CreateRtu(MasterSerialPort); 23 | } 24 | 25 | /// 26 | /// Not supported by the DL06. 27 | /// 28 | public override void ReadWriteMultipleRegisters() 29 | { 30 | } 31 | 32 | /// 33 | /// Not supported by the DL06. 34 | /// 35 | public override void ReturnQueryData() 36 | { 37 | } 38 | 39 | /// 40 | /// Reads the coils. 41 | /// 42 | [Fact] 43 | public override void ReadCoils() => 44 | base.ReadCoils(); 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialRtuMasterFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | using ModbusRx.Device; 6 | using Xunit; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// ModbusRxSerialRtuMasterFixture. 12 | /// 13 | public class ModbusRxSerialRtuMasterFixture 14 | { 15 | /// 16 | /// ns the modbus rtu master read timeout. 17 | /// 18 | [Fact] 19 | public void ModbusRxRtuMaster_ReadTimeout() 20 | { 21 | var port = ModbusMasterFixture.CreateAndOpenSerialPort(ModbusMasterFixture.DefaultMasterSerialPortName); 22 | using var master = ModbusSerialMaster.CreateRtu(port); 23 | master.Transport.ReadTimeout = master.Transport.WriteTimeout = 1000; 24 | master.ReadCoils(100, 1, 1); 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialRtuMasterModbusRxSerialRtuSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | using ModbusRx.Device; 6 | using Xunit; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// NModbusSerialRtuMasterNModbusSerialRtuSlaveFixture. 12 | /// 13 | /// 14 | public class NModbusSerialRtuMasterNModbusSerialRtuSlaveFixture : ModbusSerialMasterFixture 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public NModbusSerialRtuMasterNModbusSerialRtuSlaveFixture() 20 | { 21 | SetupSlaveSerialPort(); 22 | Slave = ModbusSerialSlave.CreateRtu(SlaveAddress, SlaveSerialPort!); 23 | StartSlave(); 24 | 25 | MasterSerialPort = CreateAndOpenSerialPort(DefaultMasterSerialPortName); 26 | Master = ModbusSerialMaster.CreateRtu(MasterSerialPort); 27 | } 28 | 29 | /// 30 | /// Reads the coils. 31 | /// 32 | [Fact] 33 | public override void ReadCoils() => 34 | base.ReadCoils(); 35 | 36 | /// 37 | /// Reads the holding registers. 38 | /// 39 | [Fact] 40 | public override void ReadHoldingRegisters() => 41 | base.ReadHoldingRegisters(); 42 | 43 | /// 44 | /// Reads the inputs. 45 | /// 46 | [Fact] 47 | public override void ReadInputs() => 48 | base.ReadInputs(); 49 | 50 | /// 51 | /// Writes the single coil. 52 | /// 53 | [Fact] 54 | public override void WriteSingleCoil() => 55 | base.WriteSingleCoil(); 56 | 57 | /// 58 | /// Writes the multiple coils. 59 | /// 60 | [Fact] 61 | public override void WriteMultipleCoils() => 62 | base.WriteMultipleCoils(); 63 | 64 | /// 65 | /// Writes the single register. 66 | /// 67 | [Fact] 68 | public override void WriteSingleRegister() => 69 | base.WriteSingleRegister(); 70 | 71 | /// 72 | /// Writes the multiple registers. 73 | /// 74 | [Fact] 75 | public override void WriteMultipleRegisters() => 76 | base.WriteMultipleRegisters(); 77 | 78 | /// 79 | /// Reads the write multiple registers. 80 | /// 81 | [Fact(Skip = "Need to fix RTU slave for this function code")] 82 | public override void ReadWriteMultipleRegisters() => 83 | base.ReadWriteMultipleRegisters(); 84 | 85 | /// 86 | /// Returns the query data. 87 | /// 88 | [Fact] 89 | public override void ReturnQueryData() => 90 | base.ReturnQueryData(); 91 | } 92 | #endif 93 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxSerialRtuSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if SERIAL 5 | 6 | using System.Threading; 7 | using ModbusRx.Data; 8 | using ModbusRx.Device; 9 | using Xunit; 10 | 11 | namespace ModbusRx.IntegrationTests; 12 | 13 | /// 14 | /// ModbusRxSerialRtuSlaveFixture. 15 | /// 16 | public class ModbusRxSerialRtuSlaveFixture 17 | { 18 | /// 19 | /// ns the modbus serial rtu slave bonus character verify timeout. 20 | /// 21 | [Fact] 22 | public void ModbusRxSerialRtuSlave_BonusCharacter_VerifyTimeout() 23 | { 24 | var masterPort = ModbusMasterFixture.CreateAndOpenSerialPort(ModbusMasterFixture.DefaultMasterSerialPortName); 25 | var slavePort = ModbusMasterFixture.CreateAndOpenSerialPort(ModbusMasterFixture.DefaultSlaveSerialPortName); 26 | 27 | using var master = ModbusSerialMaster.CreateRtu(masterPort); 28 | using var slave = ModbusSerialSlave.CreateRtu(1, slavePort); 29 | master.Transport.ReadTimeout = master.Transport.WriteTimeout = 1000; 30 | slave.DataStore = DataStoreFactory.CreateTestDataStore(); 31 | 32 | var slaveThread = new Thread(async () => await slave.ListenAsync()) 33 | { 34 | IsBackground = true 35 | }; 36 | slaveThread.Start(); 37 | 38 | // assert successful communication 39 | Assert.Equal(new bool[] { false, true }, master.ReadCoils(1, 1, 2)); 40 | 41 | // write "bonus" character 42 | masterPort.Write("*"); 43 | 44 | // assert successful communication 45 | Assert.Equal(new bool[] { false, true }, master.ReadCoils(1, 1, 2)); 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxTcpMasterJamodTcpSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if JAMOD 5 | using System.Net.Sockets; 6 | using ModbusRx.Device; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// NModbusTcpMasterJamodTcpSlaveFixture. 12 | /// 13 | /// 14 | public class ModbusRxTcpMasterJamodTcpSlaveFixture : ModbusMasterFixture 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public ModbusRxTcpMasterJamodTcpSlaveFixture() 20 | { 21 | var program = $"TcpSlave {Port}"; 22 | StartJamodSlave(program); 23 | 24 | MasterTcp = new TcpClientRx(TcpHost.ToString(), Port); 25 | Master = ModbusIpMaster.CreateIp(MasterTcp); 26 | } 27 | 28 | /// 29 | /// Not supported by the Jamod Slave. 30 | /// 31 | public override void ReadWriteMultipleRegisters() 32 | { 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxTcpMasterNModbusTcpSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net.Sockets; 5 | using CP.IO.Ports; 6 | using ModbusRx.Device; 7 | 8 | namespace ModbusRx.IntegrationTests; 9 | 10 | /// 11 | /// NModbusTcpMasterNModbusTcpSlaveFixture. 12 | /// 13 | /// 14 | public class ModbusRxTcpMasterNModbusTcpSlaveFixture : ModbusRxMasterFixture 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public ModbusRxTcpMasterNModbusTcpSlaveFixture() 20 | { 21 | SlaveTcp = new TcpListener(TcpHost, Port); 22 | SlaveTcp.Start(); 23 | Slave = ModbusTcpSlave.CreateTcp(SlaveAddress, SlaveTcp); 24 | StartSlave(); 25 | 26 | MasterTcp = new TcpClientRx(TcpHost.ToString(), Port); 27 | Master = ModbusIpMaster.CreateIp(MasterTcp); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/ModbusRxUdpMasterModbusRxUdpSlaveFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using CP.IO.Ports; 5 | using ModbusRx.Device; 6 | 7 | namespace ModbusRx.IntegrationTests; 8 | 9 | /// 10 | /// NModbusUdpMasterNModbusUdpSlaveFixture. 11 | /// 12 | /// 13 | public class ModbusRxUdpMasterModbusRxUdpSlaveFixture : ModbusRxMasterFixture 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public ModbusRxUdpMasterModbusRxUdpSlaveFixture() 19 | { 20 | SlaveUdp = new UdpClientRx(Port); 21 | Slave = ModbusUdpSlave.CreateUdp(SlaveUdp); 22 | StartSlave(); 23 | 24 | MasterUdp = new UdpClientRx(); 25 | MasterUdp.Connect(DefaultModbusIPEndPoint); 26 | Master = ModbusIpMaster.CreateIp(MasterUdp); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Xunit; 5 | 6 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] 7 | -------------------------------------------------------------------------------- /src/ModbusRx.IntegrationTests/TestCases.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.IO.Ports; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using CP.IO.Ports; 11 | using ModbusRx.Data; 12 | using ModbusRx.Device; 13 | 14 | namespace ModbusRx.IntegrationTests; 15 | 16 | internal static class TestCases 17 | { 18 | public static async void Serial() 19 | { 20 | using var masterPort = new SerialPortRx("COM2"); 21 | using var slavePort = new SerialPortRx("COM1"); 22 | 23 | // configure serial ports 24 | masterPort.BaudRate = slavePort.BaudRate = 9600; 25 | masterPort.DataBits = slavePort.DataBits = 8; 26 | masterPort.Parity = slavePort.Parity = Parity.None; 27 | masterPort.StopBits = slavePort.StopBits = StopBits.One; 28 | await masterPort.Open(); 29 | await slavePort.Open(); 30 | 31 | using var slave = ModbusSerialSlave.CreateRtu(1, slavePort); 32 | StartSlave(slave); 33 | 34 | // create modbus master 35 | using var master = ModbusSerialMaster.CreateRtu(masterPort); 36 | await ReadRegistersAsync(master); 37 | } 38 | 39 | public static async void Tcp() 40 | { 41 | var slaveClient = new TcpListener(new IPAddress(new byte[] { 127, 0, 0, 1 }), 502); 42 | using var slave = ModbusTcpSlave.CreateTcp((byte)1, slaveClient); 43 | StartSlave(slave); 44 | 45 | var address = new IPAddress(new byte[] { 127, 0, 0, 1 }); 46 | var masterClient = new TcpClientRx(address.ToString(), 502); 47 | 48 | using var master = ModbusIpMaster.CreateIp(masterClient); 49 | await ReadRegistersAsync(master); 50 | } 51 | 52 | public static async void Udp() 53 | { 54 | var slaveClient = new UdpClientRx(502); 55 | using var slave = ModbusUdpSlave.CreateUdp(slaveClient); 56 | StartSlave(slave); 57 | 58 | var masterClient = new UdpClientRx(); 59 | var endPoint = new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 502); 60 | masterClient.Connect(endPoint); 61 | 62 | using var master = ModbusIpMaster.CreateIp(masterClient); 63 | await ReadRegistersAsync(master); 64 | } 65 | 66 | public static void StartSlave(ModbusSlave slave) 67 | { 68 | slave.DataStore = DataStoreFactory.CreateTestDataStore(); 69 | var slaveThread = new Thread(async () => await slave.ListenAsync()); 70 | slaveThread.Start(); 71 | } 72 | 73 | public static async Task ReadRegistersAsync(IModbusMaster master) 74 | { 75 | var result = await master.ReadHoldingRegistersAsync(1, 0, 5); 76 | 77 | for (var i = 0; i < 5; i++) 78 | { 79 | if (result[i] != i + 1) 80 | { 81 | throw new Exception(); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Data/BoolModbusDataCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.ObjectModel; 5 | using ModbusRx.Data; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Data; 9 | 10 | /// 11 | /// BoolModbusDataCollectionFixture. 12 | /// 13 | public class BoolModbusDataCollectionFixture : ModbusDataCollectionFixture 14 | { 15 | /// 16 | /// Removes from read only. 17 | /// 18 | [Fact] 19 | public void Remove_FromReadOnly() 20 | { 21 | bool[] source = { false, false, false, true, false, false }; 22 | var col = new ModbusDataCollection(new ReadOnlyCollection(source)); 23 | var expectedCount = source.Length; 24 | 25 | Assert.True(col.Remove(source[3])); 26 | 27 | Assert.Equal(expectedCount, col.Count); 28 | } 29 | 30 | /// 31 | /// Gets the array. 32 | /// 33 | /// A bool. 34 | protected override bool[] GetArray() => 35 | new[] { false, false, true, false, false }; 36 | 37 | /// 38 | /// Gets the non existent element. 39 | /// 40 | /// A bool. 41 | protected override bool GetNonExistentElement() => true; 42 | } 43 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Data/DataStoreEventArgsFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using ModbusRx.Data; 7 | using Xunit; 8 | 9 | namespace ModbusRx.UnitTests.Data; 10 | 11 | /// 12 | /// DataStoreEventArgsFixture. 13 | /// 14 | public class DataStoreEventArgsFixture 15 | { 16 | /// 17 | /// Creates the data store event arguments. 18 | /// 19 | [Fact] 20 | public void CreateDataStoreEventArgs() 21 | { 22 | var eventArgs = DataStoreEventArgs.CreateDataStoreEventArgs(5, ModbusDataType.HoldingRegister, new ushort[] { 1, 2, 3 }); 23 | Assert.Equal(ModbusDataType.HoldingRegister, eventArgs.ModbusDataType); 24 | Assert.Equal(5, eventArgs.StartAddress); 25 | Assert.Equal(new ushort[] { 1, 2, 3 }, eventArgs.Data!.B?.ToArray()); 26 | } 27 | 28 | /// 29 | /// Creates the type of the data store event arguments invalid. 30 | /// 31 | [Fact] 32 | public void CreateDataStoreEventArgs_InvalidType() => 33 | Assert.Throws(() => DataStoreEventArgs.CreateDataStoreEventArgs(5, ModbusDataType.HoldingRegister, new int[] { 1, 2, 3 })); 34 | 35 | /// 36 | /// Creates the data store event arguments data null. 37 | /// 38 | [Fact] 39 | public void CreateDataStoreEventArgs_DataNull() => 40 | Assert.Throws(() => 41 | DataStoreEventArgs.CreateDataStoreEventArgs(5, ModbusDataType.HoldingRegister, default(ushort[])!)); 42 | } 43 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Data/RegisterCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using Xunit; 6 | 7 | namespace ModbusRx.UnitTests.Data; 8 | 9 | /// 10 | /// RegisterCollectionFixture. 11 | /// 12 | public class RegisterCollectionFixture 13 | { 14 | /// 15 | /// Bytes the count. 16 | /// 17 | [Fact] 18 | public void ByteCount() 19 | { 20 | var col = new RegisterCollection(1, 2, 3); 21 | Assert.Equal(6, col.ByteCount); 22 | } 23 | 24 | /// 25 | /// Creates new registercollection. 26 | /// 27 | [Fact] 28 | public void NewRegisterCollection() 29 | { 30 | var col = new RegisterCollection(5, 3, 4, 6); 31 | Assert.NotNull(col); 32 | Assert.Equal(4, col.Count); 33 | Assert.Equal(5, col[0]); 34 | } 35 | 36 | /// 37 | /// Creates new registercollectionfrombytes. 38 | /// 39 | [Fact] 40 | public void NewRegisterCollectionFromBytes() 41 | { 42 | var col = new RegisterCollection(new byte[] { 0, 1, 0, 2, 0, 3 }); 43 | Assert.NotNull(col); 44 | Assert.Equal(3, col.Count); 45 | Assert.Equal(1, col[0]); 46 | Assert.Equal(2, col[1]); 47 | Assert.Equal(3, col[2]); 48 | } 49 | 50 | /// 51 | /// Registers the collection network bytes. 52 | /// 53 | [Fact] 54 | public void RegisterCollectionNetworkBytes() 55 | { 56 | var col = new RegisterCollection(5, 3, 4, 6); 57 | var bytes = col.NetworkBytes; 58 | Assert.NotNull(bytes); 59 | Assert.Equal(8, bytes.Length); 60 | Assert.Equal(new byte[] { 0, 5, 0, 3, 0, 4, 0, 6 }, bytes); 61 | } 62 | 63 | /// 64 | /// Registers the collection empty. 65 | /// 66 | [Fact] 67 | public void RegisterCollectionEmpty() 68 | { 69 | var col = new RegisterCollection(); 70 | Assert.NotNull(col); 71 | Assert.Empty(col.NetworkBytes); 72 | } 73 | 74 | /// 75 | /// Modifies the register. 76 | /// 77 | [Fact] 78 | public void ModifyRegister() 79 | { 80 | var col = new RegisterCollection(1, 2, 3, 4) 81 | { 82 | [0] = 5 83 | }; 84 | } 85 | 86 | /// 87 | /// Adds the register. 88 | /// 89 | [Fact] 90 | public void AddRegister() 91 | { 92 | var col = new RegisterCollection(); 93 | Assert.Empty(col); 94 | 95 | col.Add(45); 96 | Assert.Single(col); 97 | } 98 | 99 | /// 100 | /// Removes the register. 101 | /// 102 | [Fact] 103 | public void RemoveRegister() 104 | { 105 | var col = new RegisterCollection(3, 4, 5); 106 | Assert.Equal(3, col.Count); 107 | col.RemoveAt(2); 108 | Assert.Equal(2, col.Count); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Data/UshortModbusDataCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.ObjectModel; 5 | using ModbusRx.Data; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Data; 9 | 10 | /// 11 | /// UshortModbusDataCollectionFixture. 12 | /// 13 | public class UshortModbusDataCollectionFixture : ModbusDataCollectionFixture 14 | { 15 | /// 16 | /// Removes from read only. 17 | /// 18 | [Fact] 19 | public void Remove_FromReadOnly() 20 | { 21 | var source = GetArray(); 22 | var col = new ModbusDataCollection(new ReadOnlyCollection(source)); 23 | var expectedCount = source.Length; 24 | 25 | Assert.False(col.Remove(GetNonExistentElement())); 26 | Assert.True(col.Remove(source[3])); 27 | Assert.Equal(expectedCount, col.Count); 28 | } 29 | 30 | /// 31 | /// Gets the array. 32 | /// 33 | /// A ushort. 34 | protected override ushort[] GetArray() => 35 | new ushort[] { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; 36 | 37 | /// 38 | /// Gets the non existent element. 39 | /// 40 | /// A ushort. 41 | protected override ushort GetNonExistentElement() => 42; 42 | } 43 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Device/TcpConnectionEventArgsFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Device; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Device; 9 | 10 | /// 11 | /// TcpConnectionEventArgsFixture. 12 | /// 13 | public class TcpConnectionEventArgsFixture 14 | { 15 | /// 16 | /// TCPs the connection event arguments null end point. 17 | /// 18 | [Fact] 19 | public void TcpConnectionEventArgs_NullEndPoint() => 20 | Assert.Throws(() => new TcpConnectionEventArgs(null!)); 21 | 22 | /// 23 | /// TCPs the connection event arguments empty end point. 24 | /// 25 | [Fact] 26 | public void TcpConnectionEventArgs_EmptyEndPoint() => 27 | Assert.Throws(() => new TcpConnectionEventArgs(string.Empty)); 28 | 29 | /// 30 | /// TCPs the connection event arguments. 31 | /// 32 | [Fact] 33 | public void TcpConnectionEventArgs() 34 | { 35 | var args = new TcpConnectionEventArgs("foo"); 36 | 37 | Assert.Equal("foo", args.EndPoint); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/IO/EmptyTransportFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using ModbusRx.IO; 7 | using ModbusRx.Message; 8 | using Xunit; 9 | 10 | namespace ModbusRx.UnitTests.IO; 11 | 12 | /// 13 | /// EmptyTransportFixture. 14 | /// 15 | public static class EmptyTransportFixture 16 | { 17 | /// 18 | /// Negatives this instance. 19 | /// 20 | /// A representing the asynchronous unit test. 21 | [Fact] 22 | public static async Task NegativeAsync() 23 | { 24 | var transport = new EmptyTransport(); 25 | await Assert.ThrowsAsync(() => transport.ReadRequest()); 26 | await Assert.ThrowsAsync(() => transport.ReadResponse()); 27 | Assert.Throws(() => transport.BuildMessageFrame(null!)); 28 | Assert.Throws(() => transport.Write(null!)); 29 | Assert.Throws(() => transport.OnValidateResponse(null!, null!)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/IO/UdpClientAdapterFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using CP.IO.Ports; 7 | using ModbusRx.IO; 8 | using Xunit; 9 | 10 | namespace ModbusRx.UnitTests.IO; 11 | 12 | /// 13 | /// UdpClientAdapterFixture. 14 | /// 15 | public class UdpClientAdapterFixture 16 | { 17 | /// 18 | /// Reads the argument validation. 19 | /// 20 | /// A representing the asynchronous unit test. 21 | [Fact] 22 | public async Task Read_ArgumentValidationAsync() 23 | { 24 | var adapter = new UdpClientAdapter(new UdpClientRx()); 25 | 26 | // buffer 27 | await Assert.ThrowsAsync(() => adapter.ReadAsync(null!, 1, 1)); 28 | 29 | // offset 30 | await Assert.ThrowsAsync(() => adapter.ReadAsync(new byte[2], -1, 2)); 31 | await Assert.ThrowsAsync(() => adapter.ReadAsync(new byte[2], 3, 3)); 32 | 33 | await Assert.ThrowsAsync(() => adapter.ReadAsync(new byte[2], 0, -1)); 34 | await Assert.ThrowsAsync(() => adapter.ReadAsync(new byte[2], 1, 2)); 35 | } 36 | 37 | /// 38 | /// Writes the argument validation. 39 | /// 40 | [Fact] 41 | public void Write_ArgumentValidation() 42 | { 43 | var adapter = new UdpClientAdapter(new UdpClientRx()); 44 | 45 | // buffer 46 | Assert.Throws(() => adapter.Write(null!, 1, 1)); 47 | 48 | // offset 49 | Assert.Throws(() => adapter.Write(new byte[2], -1, 2)); 50 | Assert.Throws(() => adapter.Write(new byte[2], 3, 3)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/InvalidModbusRequestExceptionFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.IO; 5 | using Xunit; 6 | 7 | namespace ModbusRx.UnitTests; 8 | 9 | /// 10 | /// InvalidModbusRequestExceptionFixture. 11 | /// 12 | public class InvalidModbusRequestExceptionFixture 13 | { 14 | /// 15 | /// Constructors the with exception code. 16 | /// 17 | [Fact] 18 | public void ConstructorWithExceptionCode() 19 | { 20 | var e = new InvalidModbusRequestException(Modbus.SlaveDeviceBusy); 21 | Assert.Equal($"Modbus exception code {Modbus.SlaveDeviceBusy}.", e.Message); 22 | Assert.Equal(Modbus.SlaveDeviceBusy, e.ExceptionCode); 23 | Assert.Null(e.InnerException); 24 | } 25 | 26 | /// 27 | /// Constructors the with exception code and inner exception. 28 | /// 29 | [Fact] 30 | public void ConstructorWithExceptionCodeAndInnerException() 31 | { 32 | var inner = new IOException("Bar"); 33 | var e = new InvalidModbusRequestException(42, inner); 34 | Assert.Equal("Modbus exception code 42.", e.Message); 35 | Assert.Equal(42, e.ExceptionCode); 36 | Assert.Same(inner, e.InnerException); 37 | } 38 | 39 | /// 40 | /// Constructors the with message and exception code. 41 | /// 42 | [Fact] 43 | public void ConstructorWithMessageAndExceptionCode() 44 | { 45 | var e = new InvalidModbusRequestException("Hello World", Modbus.IllegalFunction); 46 | Assert.Equal("Hello World", e.Message); 47 | Assert.Equal(Modbus.IllegalFunction, e.ExceptionCode); 48 | Assert.Null(e.InnerException); 49 | } 50 | 51 | /// 52 | /// Constructors the with custom message and slave exception response. 53 | /// 54 | [Fact] 55 | public void ConstructorWithCustomMessageAndSlaveExceptionResponse() 56 | { 57 | var inner = new IOException("Bar"); 58 | var e = new InvalidModbusRequestException("Hello World", Modbus.IllegalDataAddress, inner); 59 | Assert.Equal("Hello World", e.Message); 60 | Assert.Equal(Modbus.IllegalDataAddress, e.ExceptionCode); 61 | Assert.Same(inner, e.InnerException); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/DiagnosticsRequestResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// DiagnosticsRequestResponseFixture. 12 | /// 13 | public class DiagnosticsRequestResponseFixture 14 | { 15 | /// 16 | /// Converts to string_test. 17 | /// 18 | [Fact] 19 | public void ToString_Test() 20 | { 21 | DiagnosticsRequestResponse response; 22 | 23 | response = new DiagnosticsRequestResponse(Modbus.DiagnosticsReturnQueryData, 3, new RegisterCollection(5)); 24 | Assert.Equal("Diagnostics message, sub-function return query data - {5}.", response.ToString()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/MessageUtility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace ModbusRx.UnitTests.Message; 8 | 9 | /// 10 | /// MessageUtility. 11 | /// 12 | public static class MessageUtility 13 | { 14 | /// 15 | /// Creates a collection initialized to a default value. 16 | /// 17 | /// The Key. 18 | /// The type of the v. 19 | /// The default value. 20 | /// The size. 21 | /// A value of T. 22 | /// size - Collection size cannot be less than 0. 23 | public static T CreateDefaultCollection(TV defaultValue, int size) 24 | where T : ICollection, new() 25 | { 26 | if (size < 0) 27 | { 28 | throw new ArgumentOutOfRangeException(nameof(size), "Collection size cannot be less than 0."); 29 | } 30 | 31 | var col = new T(); 32 | 33 | for (var i = 0; i < size; i++) 34 | { 35 | col.Add(defaultValue); 36 | } 37 | 38 | return col; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ModbusMessageFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Linq; 5 | using System.Reflection; 6 | using ModbusRx.Message; 7 | using Xunit; 8 | 9 | namespace ModbusRx.UnitTests.Message; 10 | 11 | /// 12 | /// ModbusMessageFixture. 13 | /// 14 | public class ModbusMessageFixture 15 | { 16 | /// 17 | /// Protocols the data unit read coils request. 18 | /// 19 | [Fact] 20 | public void ProtocolDataUnitReadCoilsRequest() 21 | { 22 | AbstractModbusMessage message = new ReadCoilsInputsRequest(Modbus.ReadCoils, 1, 100, 9); 23 | byte[] expectedResult = { Modbus.ReadCoils, 0, 100, 0, 9 }; 24 | Assert.Equal(expectedResult, message.ProtocolDataUnit); 25 | } 26 | 27 | /// 28 | /// Messages the frame read coils request. 29 | /// 30 | [Fact] 31 | public void MessageFrameReadCoilsRequest() 32 | { 33 | AbstractModbusMessage message = new ReadCoilsInputsRequest(Modbus.ReadCoils, 1, 2, 3); 34 | byte[] expectedMessageFrame = { 1, Modbus.ReadCoils, 0, 2, 0, 3 }; 35 | Assert.Equal(expectedMessageFrame, message.MessageFrame); 36 | } 37 | 38 | /// 39 | /// Modbuses the message to string overriden. 40 | /// 41 | [Fact] 42 | public void ModbusMessageToStringOverriden() 43 | { 44 | var messageTypes = from message in typeof(AbstractModbusMessage).GetTypeInfo().Assembly.GetTypes() 45 | let typeInfo = message.GetTypeInfo() 46 | where !typeInfo.IsAbstract && typeInfo.IsSubclassOf(typeof(AbstractModbusMessage)) 47 | select message; 48 | 49 | foreach (var messageType in messageTypes) 50 | { 51 | Assert.NotNull( 52 | messageType.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); 53 | } 54 | } 55 | 56 | /// 57 | /// Asserts the modbus message properties are equal. 58 | /// 59 | /// The obj1. 60 | /// The obj2. 61 | internal static void AssertModbusMessagePropertiesAreEqual(IModbusMessage obj1, IModbusMessage obj2) 62 | { 63 | Assert.Equal(obj1.FunctionCode, obj2.FunctionCode); 64 | Assert.Equal(obj1.SlaveAddress, obj2.SlaveAddress); 65 | Assert.Equal(obj1.MessageFrame, obj2.MessageFrame); 66 | Assert.Equal(obj1.ProtocolDataUnit, obj2.ProtocolDataUnit); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ModbusMessageImplFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ModbusMessageImplFixture. 12 | /// 13 | public class ModbusMessageImplFixture 14 | { 15 | /// 16 | /// Modbuses the message ctor initializes properties. 17 | /// 18 | [Fact] 19 | public void ModbusMessageCtorInitializesProperties() 20 | { 21 | var messageImpl = new ModbusMessageImpl(5, Modbus.ReadCoils); 22 | Assert.Equal(5, messageImpl.SlaveAddress); 23 | Assert.Equal(Modbus.ReadCoils, messageImpl.FunctionCode); 24 | } 25 | 26 | /// 27 | /// Initializes this instance. 28 | /// 29 | [Fact] 30 | public void Initialize() 31 | { 32 | var messageImpl = new ModbusMessageImpl(); 33 | messageImpl.Initialize(new byte[] { 1, 2, 9, 9, 9, 9 }); 34 | Assert.Equal(1, messageImpl.SlaveAddress); 35 | Assert.Equal(2, messageImpl.FunctionCode); 36 | } 37 | 38 | /// 39 | /// Checcks the initialize frame null. 40 | /// 41 | [Fact] 42 | public void ChecckInitializeFrameNull() 43 | { 44 | var messageImpl = new ModbusMessageImpl(); 45 | Assert.Throws(() => messageImpl.Initialize(null!)); 46 | } 47 | 48 | /// 49 | /// Initializes the invalid frame. 50 | /// 51 | [Fact] 52 | public void InitializeInvalidFrame() 53 | { 54 | var messageImpl = new ModbusMessageImpl(); 55 | Assert.Throws(() => messageImpl.Initialize(new byte[] { 1 })); 56 | } 57 | 58 | /// 59 | /// Protocols the data unit. 60 | /// 61 | [Fact] 62 | public void ProtocolDataUnit() 63 | { 64 | var messageImpl = new ModbusMessageImpl(11, Modbus.ReadCoils); 65 | byte[] expectedResult = { Modbus.ReadCoils }; 66 | Assert.Equal(expectedResult, messageImpl.ProtocolDataUnit); 67 | } 68 | 69 | /// 70 | /// Messages the frame. 71 | /// 72 | [Fact] 73 | public void MessageFrame() 74 | { 75 | var messageImpl = new ModbusMessageImpl(11, Modbus.ReadHoldingRegisters); 76 | byte[] expectedMessageFrame = { 11, Modbus.ReadHoldingRegisters }; 77 | Assert.Equal(expectedMessageFrame, messageImpl.MessageFrame); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ModbusMessageWithDataFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ModbusMessageWithDataFixture. 12 | /// 13 | public class ModbusMessageWithDataFixture 14 | { 15 | /// 16 | /// Modbuses the message with data fixture ctor initializes properties. 17 | /// 18 | [Fact] 19 | public void ModbusMessageWithDataFixtureCtorInitializesProperties() 20 | { 21 | AbstractModbusMessageWithData message = new ReadCoilsInputsResponse(Modbus.ReadCoils, 10, 1, new DiscreteCollection(true, false, true)); 22 | Assert.Equal(Modbus.ReadCoils, message.FunctionCode); 23 | Assert.Equal(10, message.SlaveAddress); 24 | } 25 | 26 | /// 27 | /// Protocols the data unit read coils response. 28 | /// 29 | [Fact] 30 | public void ProtocolDataUnitReadCoilsResponse() 31 | { 32 | AbstractModbusMessageWithData message = new ReadCoilsInputsResponse(Modbus.ReadCoils, 1, 2, new DiscreteCollection(true)); 33 | byte[] expectedResult = { 1, 2, 1 }; 34 | Assert.Equal(expectedResult, message.ProtocolDataUnit); 35 | } 36 | 37 | /// 38 | /// Datas the read coils response. 39 | /// 40 | [Fact] 41 | public void DataReadCoilsResponse() 42 | { 43 | var col = new DiscreteCollection(false, true, false, true, false, true, false, false, false, false); 44 | AbstractModbusMessageWithData message = new ReadCoilsInputsResponse(Modbus.ReadCoils, 11, 1, col); 45 | Assert.Equal(col.Count, message.Data.Count); 46 | Assert.Equal(col.NetworkBytes, message.Data.NetworkBytes); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ReadCoilsInputsRequestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ReadCoilsInputsRequestFixture. 12 | /// 13 | public class ReadCoilsInputsRequestFixture 14 | { 15 | /// 16 | /// Creates the read coils request. 17 | /// 18 | [Fact] 19 | public void CreateReadCoilsRequest() 20 | { 21 | var request = new ReadCoilsInputsRequest(Modbus.ReadCoils, 5, 1, 10); 22 | Assert.Equal(Modbus.ReadCoils, request.FunctionCode); 23 | Assert.Equal(5, request.SlaveAddress); 24 | Assert.Equal(1, request.StartAddress); 25 | Assert.Equal(10, request.NumberOfPoints); 26 | } 27 | 28 | /// 29 | /// Creates the read inputs request. 30 | /// 31 | [Fact] 32 | public void CreateReadInputsRequest() 33 | { 34 | var request = new ReadCoilsInputsRequest(Modbus.ReadInputs, 5, 1, 10); 35 | Assert.Equal(Modbus.ReadInputs, request.FunctionCode); 36 | Assert.Equal(5, request.SlaveAddress); 37 | Assert.Equal(1, request.StartAddress); 38 | Assert.Equal(10, request.NumberOfPoints); 39 | } 40 | 41 | /// 42 | /// Creates the read coils inputs request too much data. 43 | /// 44 | [Fact] 45 | public void CreateReadCoilsInputsRequestTooMuchData() => 46 | Assert.Throws(() => new ReadCoilsInputsRequest(Modbus.ReadCoils, 1, 2, Modbus.MaximumDiscreteRequestResponseSize + 1)); 47 | 48 | /// 49 | /// Creates the maximum size of the read coils inputs request. 50 | /// 51 | [Fact] 52 | public void CreateReadCoilsInputsRequestMaxSize() 53 | { 54 | var response = new ReadCoilsInputsRequest(Modbus.ReadCoils, 1, 2, Modbus.MaximumDiscreteRequestResponseSize); 55 | Assert.Equal(Modbus.MaximumDiscreteRequestResponseSize, response.NumberOfPoints); 56 | } 57 | 58 | /// 59 | /// Converts to string_readcoilsrequest. 60 | /// 61 | [Fact] 62 | public void ToString_ReadCoilsRequest() 63 | { 64 | var request = new ReadCoilsInputsRequest(Modbus.ReadCoils, 5, 1, 10); 65 | 66 | Assert.Equal("Read 10 coils starting at address 1.", request.ToString()); 67 | } 68 | 69 | /// 70 | /// Converts to string_readinputsrequest. 71 | /// 72 | [Fact] 73 | public void ToString_ReadInputsRequest() 74 | { 75 | var request = new ReadCoilsInputsRequest(Modbus.ReadInputs, 5, 1, 10); 76 | 77 | Assert.Equal("Read 10 inputs starting at address 1.", request.ToString()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ReadCoilsInputsResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ReadCoilsInputsResponseFixture. 12 | /// 13 | public class ReadCoilsInputsResponseFixture 14 | { 15 | /// 16 | /// Creates the read coils response. 17 | /// 18 | [Fact] 19 | public void CreateReadCoilsResponse() 20 | { 21 | var response = new ReadCoilsInputsResponse(Modbus.ReadCoils, 5, 2, new DiscreteCollection(true, true, true, true, true, true, false, false, true, true, false)); 22 | Assert.Equal(Modbus.ReadCoils, response.FunctionCode); 23 | Assert.Equal(5, response.SlaveAddress); 24 | Assert.Equal(2, response.ByteCount); 25 | var col = new DiscreteCollection(true, true, true, true, true, true, false, false, true, true, false); 26 | Assert.Equal(col.NetworkBytes, response.Data.NetworkBytes); 27 | } 28 | 29 | /// 30 | /// Creates the read inputs response. 31 | /// 32 | [Fact] 33 | public void CreateReadInputsResponse() 34 | { 35 | var response = new ReadCoilsInputsResponse(Modbus.ReadInputs, 5, 2, new DiscreteCollection(true, true, true, true, true, true, false, false, true, true, false)); 36 | Assert.Equal(Modbus.ReadInputs, response.FunctionCode); 37 | Assert.Equal(5, response.SlaveAddress); 38 | Assert.Equal(2, response.ByteCount); 39 | var col = new DiscreteCollection(true, true, true, true, true, true, false, false, true, true, false); 40 | Assert.Equal(col.NetworkBytes, response.Data.NetworkBytes); 41 | } 42 | 43 | /// 44 | /// Converts to string_coils. 45 | /// 46 | [Fact] 47 | public void ToString_Coils() 48 | { 49 | var response = new ReadCoilsInputsResponse(Modbus.ReadCoils, 5, 2, new DiscreteCollection(true, true, true, true, true, true, false, false, true, true, false)); 50 | 51 | Assert.Equal("Read 11 coils - {1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0}.", response.ToString()); 52 | } 53 | 54 | /// 55 | /// Converts to string_inputs. 56 | /// 57 | [Fact] 58 | public void ToString_Inputs() 59 | { 60 | var response = new ReadCoilsInputsResponse(Modbus.ReadInputs, 5, 2, new DiscreteCollection(true, true, true, true, true, true, false, false, true, true, false)); 61 | 62 | Assert.Equal("Read 11 inputs - {1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0}.", response.ToString()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ReadHoldingInputRegistersRequestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ReadHoldingInputRegistersRequestFixture. 12 | /// 13 | public class ReadHoldingInputRegistersRequestFixture 14 | { 15 | /// 16 | /// Creates the read holding registers request. 17 | /// 18 | [Fact] 19 | public void CreateReadHoldingRegistersRequest() 20 | { 21 | var request = new ReadHoldingInputRegistersRequest(Modbus.ReadHoldingRegisters, 5, 1, 10); 22 | 23 | Assert.Equal(Modbus.ReadHoldingRegisters, request.FunctionCode); 24 | Assert.Equal(5, request.SlaveAddress); 25 | Assert.Equal(1, request.StartAddress); 26 | Assert.Equal(10, request.NumberOfPoints); 27 | } 28 | 29 | /// 30 | /// Creates the read input registers request. 31 | /// 32 | [Fact] 33 | public void CreateReadInputRegistersRequest() 34 | { 35 | var request = new ReadHoldingInputRegistersRequest(Modbus.ReadInputRegisters, 5, 1, 10); 36 | 37 | Assert.Equal(Modbus.ReadInputRegisters, request.FunctionCode); 38 | Assert.Equal(5, request.SlaveAddress); 39 | Assert.Equal(1, request.StartAddress); 40 | Assert.Equal(10, request.NumberOfPoints); 41 | } 42 | 43 | /// 44 | /// Creates the read holding input registers request too much data. 45 | /// 46 | [Fact] 47 | public void CreateReadHoldingInputRegistersRequestTooMuchData() => 48 | Assert.Throws(() => 49 | new ReadHoldingInputRegistersRequest(Modbus.ReadHoldingRegisters, 1, 2, Modbus.MaximumRegisterRequestResponseSize + 1)); 50 | 51 | /// 52 | /// Creates the maximum size of the read holding input registers request. 53 | /// 54 | [Fact] 55 | public void CreateReadHoldingInputRegistersRequestMaxSize() 56 | { 57 | var response = new ReadHoldingInputRegistersRequest(Modbus.ReadHoldingRegisters, 1, 2, Modbus.MaximumRegisterRequestResponseSize); 58 | 59 | Assert.Equal(Modbus.MaximumRegisterRequestResponseSize, response.NumberOfPoints); 60 | } 61 | 62 | /// 63 | /// Converts to string_readholdingregistersrequest. 64 | /// 65 | [Fact] 66 | public void ToString_ReadHoldingRegistersRequest() 67 | { 68 | var request = new ReadHoldingInputRegistersRequest(Modbus.ReadHoldingRegisters, 5, 1, 10); 69 | 70 | Assert.Equal("Read 10 holding registers starting at address 1.", request.ToString()); 71 | } 72 | 73 | /// 74 | /// Converts to string_readinputregistersrequest. 75 | /// 76 | [Fact] 77 | public void ToString_ReadInputRegistersRequest() 78 | { 79 | var request = new ReadHoldingInputRegistersRequest(Modbus.ReadInputRegisters, 5, 1, 10); 80 | 81 | Assert.Equal("Read 10 input registers starting at address 1.", request.ToString()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ReadHoldingInputRegistersResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Data; 6 | using ModbusRx.Message; 7 | using Xunit; 8 | 9 | namespace ModbusRx.UnitTests.Message; 10 | 11 | /// 12 | /// ReadHoldingInputRegistersResponseFixture. 13 | /// 14 | public class ReadHoldingInputRegistersResponseFixture 15 | { 16 | /// 17 | /// Reads the holding input registers response null data. 18 | /// 19 | [Fact] 20 | public void ReadHoldingInputRegistersResponse_NullData() => Assert.Throws(() => new ReadHoldingInputRegistersResponse(0, 0, null!)); 21 | 22 | /// 23 | /// Reads the holding registers response. 24 | /// 25 | [Fact] 26 | public void ReadHoldingRegistersResponse() 27 | { 28 | var response = new ReadHoldingInputRegistersResponse(Modbus.ReadHoldingRegisters, 5, new RegisterCollection(1, 2)); 29 | Assert.Equal(Modbus.ReadHoldingRegisters, response.FunctionCode); 30 | Assert.Equal(5, response.SlaveAddress); 31 | Assert.Equal(4, response.ByteCount); 32 | var col = new RegisterCollection(1, 2); 33 | Assert.Equal(col.NetworkBytes, response.Data.NetworkBytes); 34 | } 35 | 36 | /// 37 | /// Converts to string_readholdingregistersresponse. 38 | /// 39 | [Fact] 40 | public void ToString_ReadHoldingRegistersResponse() 41 | { 42 | var response = new ReadHoldingInputRegistersResponse(Modbus.ReadHoldingRegisters, 1, new RegisterCollection(1)); 43 | Assert.Equal("Read 1 holding registers.", response.ToString()); 44 | } 45 | 46 | /// 47 | /// Reads the input registers response. 48 | /// 49 | [Fact] 50 | public void ReadInputRegistersResponse() 51 | { 52 | var response = new ReadHoldingInputRegistersResponse(Modbus.ReadInputRegisters, 5, new RegisterCollection(1, 2)); 53 | Assert.Equal(Modbus.ReadInputRegisters, response.FunctionCode); 54 | Assert.Equal(5, response.SlaveAddress); 55 | Assert.Equal(4, response.ByteCount); 56 | var col = new RegisterCollection(1, 2); 57 | Assert.Equal(col.NetworkBytes, response.Data.NetworkBytes); 58 | } 59 | 60 | /// 61 | /// Converts to string_readinputregistersresponse. 62 | /// 63 | [Fact] 64 | public void ToString_ReadInputRegistersResponse() 65 | { 66 | var response = new ReadHoldingInputRegistersResponse(Modbus.ReadInputRegisters, 1, new RegisterCollection(1)); 67 | Assert.Equal("Read 1 input registers.", response.ToString()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ReadWriteMultipleRegistersRequestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ReadWriteMultipleRegistersRequestFixture. 12 | /// 13 | public class ReadWriteMultipleRegistersRequestFixture 14 | { 15 | /// 16 | /// Reads the write multiple registers request. 17 | /// 18 | [Fact] 19 | public void ReadWriteMultipleRegistersRequest() 20 | { 21 | var writeCollection = new RegisterCollection(255, 255, 255); 22 | var request = new ReadWriteMultipleRegistersRequest(5, 3, 6, 14, writeCollection); 23 | Assert.Equal(Modbus.ReadWriteMultipleRegisters, request.FunctionCode); 24 | Assert.Equal(5, request.SlaveAddress); 25 | 26 | // test read 27 | Assert.NotNull(request.ReadRequest); 28 | Assert.Equal(request.SlaveAddress, request.ReadRequest!.SlaveAddress); 29 | Assert.Equal(3, request.ReadRequest.StartAddress); 30 | Assert.Equal(6, request.ReadRequest.NumberOfPoints); 31 | 32 | // test write 33 | Assert.NotNull(request.WriteRequest); 34 | Assert.Equal(request.SlaveAddress, request.WriteRequest!.SlaveAddress); 35 | Assert.Equal(14, request.WriteRequest.StartAddress); 36 | Assert.Equal(writeCollection.NetworkBytes, request.WriteRequest.Data.NetworkBytes); 37 | } 38 | 39 | /// 40 | /// Protocols the data unit. 41 | /// 42 | [Fact] 43 | public void ProtocolDataUnit() 44 | { 45 | var writeCollection = new RegisterCollection(255, 255, 255); 46 | var request = new ReadWriteMultipleRegistersRequest(5, 3, 6, 14, writeCollection); 47 | byte[] pdu = 48 | { 49 | 0x17, 0x00, 0x03, 0x00, 0x06, 0x00, 0x0e, 0x00, 0x03, 0x06, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 50 | }; 51 | Assert.Equal(pdu, request.ProtocolDataUnit); 52 | } 53 | 54 | /// 55 | /// Converts to string_readwritemultipleregistersrequest. 56 | /// 57 | [Fact] 58 | public void ToString_ReadWriteMultipleRegistersRequest() 59 | { 60 | var writeCollection = new RegisterCollection(255, 255, 255); 61 | var request = new ReadWriteMultipleRegistersRequest(5, 3, 6, 14, writeCollection); 62 | 63 | Assert.Equal( 64 | "Write 3 holding registers starting at address 14, and read 6 registers starting at address 3.", 65 | request.ToString()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/ReturnQueryDataRequestResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// ReturnQueryDataRequestResponseFixture. 12 | /// 13 | public class ReturnQueryDataRequestResponseFixture 14 | { 15 | /// 16 | /// Returns the query data request response. 17 | /// 18 | [Fact] 19 | public void ReturnQueryDataRequestResponse() 20 | { 21 | var data = new RegisterCollection(1, 2, 3, 4); 22 | var request = new DiagnosticsRequestResponse(Modbus.DiagnosticsReturnQueryData, 5, data); 23 | Assert.Equal(Modbus.Diagnostics, request.FunctionCode); 24 | Assert.Equal(Modbus.DiagnosticsReturnQueryData, request.SubFunctionCode); 25 | Assert.Equal(5, request.SlaveAddress); 26 | Assert.Equal(data.NetworkBytes, request.Data.NetworkBytes); 27 | } 28 | 29 | /// 30 | /// Protocols the data unit. 31 | /// 32 | [Fact] 33 | public void ProtocolDataUnit() 34 | { 35 | var data = new RegisterCollection(1, 2, 3, 4); 36 | var request = new DiagnosticsRequestResponse(Modbus.DiagnosticsReturnQueryData, 5, data); 37 | Assert.Equal(new byte[] { 8, 0, 0, 0, 1, 0, 2, 0, 3, 0, 4 }, request.ProtocolDataUnit); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/SlaveExceptionResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Message; 5 | using Xunit; 6 | 7 | namespace ModbusRx.UnitTests.Message; 8 | 9 | /// 10 | /// SlaveExceptionResponseFixture. 11 | /// 12 | public class SlaveExceptionResponseFixture 13 | { 14 | /// 15 | /// Creates the slave exception response. 16 | /// 17 | [Fact] 18 | public void CreateSlaveExceptionResponse() 19 | { 20 | var response = new SlaveExceptionResponse(11, Modbus.ReadCoils + Modbus.ExceptionOffset, 2); 21 | Assert.Equal(11, response.SlaveAddress); 22 | Assert.Equal(Modbus.ReadCoils + Modbus.ExceptionOffset, response.FunctionCode); 23 | Assert.Equal(2, response.SlaveExceptionCode); 24 | } 25 | 26 | /// 27 | /// Slaves the exception response pdu. 28 | /// 29 | [Fact] 30 | public void SlaveExceptionResponsePDU() 31 | { 32 | var response = new SlaveExceptionResponse(11, Modbus.ReadCoils + Modbus.ExceptionOffset, 2); 33 | Assert.Equal(new byte[] { response.FunctionCode, response.SlaveExceptionCode }, response.ProtocolDataUnit); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/WriteMultipleCoilsRequestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Data; 6 | using ModbusRx.Message; 7 | using Xunit; 8 | 9 | namespace ModbusRx.UnitTests.Message; 10 | 11 | /// 12 | /// WriteMultipleCoilsRequestFixture. 13 | /// 14 | public class WriteMultipleCoilsRequestFixture 15 | { 16 | /// 17 | /// Creates the write multiple coils request. 18 | /// 19 | [Fact] 20 | public void CreateWriteMultipleCoilsRequest() 21 | { 22 | var col = new DiscreteCollection(true, false, true, false, true, true, true, false, false); 23 | var request = new WriteMultipleCoilsRequest(34, 45, col); 24 | Assert.Equal(Modbus.WriteMultipleCoils, request.FunctionCode); 25 | Assert.Equal(34, request.SlaveAddress); 26 | Assert.Equal(45, request.StartAddress); 27 | Assert.Equal(9, request.NumberOfPoints); 28 | Assert.Equal(2, request.ByteCount); 29 | Assert.Equal(col.NetworkBytes, request.Data.NetworkBytes); 30 | } 31 | 32 | /// 33 | /// Creates the write multiple coils request too much data. 34 | /// 35 | [Fact] 36 | public void CreateWriteMultipleCoilsRequestTooMuchData() => 37 | Assert.Throws(() => 38 | new WriteMultipleCoilsRequest(1, 2, MessageUtility.CreateDefaultCollection(true, Modbus.MaximumDiscreteRequestResponseSize + 1))); 39 | 40 | /// 41 | /// Creates the maximum size of the write multiple coils request. 42 | /// 43 | [Fact] 44 | public void CreateWriteMultipleCoilsRequestMaxSize() 45 | { 46 | var request = new WriteMultipleCoilsRequest(1, 2, MessageUtility.CreateDefaultCollection(true, Modbus.MaximumDiscreteRequestResponseSize)); 47 | 48 | Assert.Equal(Modbus.MaximumDiscreteRequestResponseSize, request.Data.Count); 49 | } 50 | 51 | /// 52 | /// Converts to string_writemultiplecoilsrequest. 53 | /// 54 | [Fact] 55 | public void ToString_WriteMultipleCoilsRequest() 56 | { 57 | var col = new DiscreteCollection(true, false, true, false, true, true, true, false, false); 58 | var request = new WriteMultipleCoilsRequest(34, 45, col); 59 | 60 | Assert.Equal("Write 9 coils starting at address 45.", request.ToString()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/WriteMultipleCoilsResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// WriteMultipleCoilsResponseFixture. 12 | /// 13 | public class WriteMultipleCoilsResponseFixture 14 | { 15 | /// 16 | /// Creates the write multiple coils response. 17 | /// 18 | [Fact] 19 | public void CreateWriteMultipleCoilsResponse() 20 | { 21 | var response = new WriteMultipleCoilsResponse(17, 19, 45); 22 | Assert.Equal(Modbus.WriteMultipleCoils, response.FunctionCode); 23 | Assert.Equal(17, response.SlaveAddress); 24 | Assert.Equal(19, response.StartAddress); 25 | Assert.Equal(45, response.NumberOfPoints); 26 | } 27 | 28 | /// 29 | /// Creates the write multiple coils response too much data. 30 | /// 31 | [Fact] 32 | public void CreateWriteMultipleCoilsResponseTooMuchData() => Assert.Throws(() => new WriteMultipleCoilsResponse(1, 2, Modbus.MaximumDiscreteRequestResponseSize + 1)); 33 | 34 | /// 35 | /// Creates the maximum size of the write multiple coils response. 36 | /// 37 | [Fact] 38 | public void CreateWriteMultipleCoilsResponseMaxSize() 39 | { 40 | var response = new WriteMultipleCoilsResponse(1, 2, Modbus.MaximumDiscreteRequestResponseSize); 41 | Assert.Equal(Modbus.MaximumDiscreteRequestResponseSize, response.NumberOfPoints); 42 | } 43 | 44 | /// 45 | /// Converts to string_test. 46 | /// 47 | [Fact] 48 | public void ToString_Test() 49 | { 50 | var response = new WriteMultipleCoilsResponse(1, 2, 3); 51 | 52 | Assert.Equal("Wrote 3 coils starting at address 2.", response.ToString()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/WriteMultipleRegistersRequestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Data; 6 | using ModbusRx.Message; 7 | using Xunit; 8 | 9 | namespace ModbusRx.UnitTests.Message; 10 | 11 | /// 12 | /// WriteMultipleRegistersRequestFixture. 13 | /// 14 | public class WriteMultipleRegistersRequestFixture 15 | { 16 | /// 17 | /// Creates the write multiple registers request fixture. 18 | /// 19 | [Fact] 20 | public void CreateWriteMultipleRegistersRequestFixture() 21 | { 22 | var col = new RegisterCollection(10, 20, 30, 40, 50); 23 | var request = new WriteMultipleRegistersRequest(11, 34, col); 24 | 25 | Assert.Equal(Modbus.WriteMultipleRegisters, request.FunctionCode); 26 | Assert.Equal(11, request.SlaveAddress); 27 | Assert.Equal(34, request.StartAddress); 28 | Assert.Equal(10, request.ByteCount); 29 | Assert.Equal(col.NetworkBytes, request.Data.NetworkBytes); 30 | } 31 | 32 | /// 33 | /// Creates the write multiple registers request too much data. 34 | /// 35 | [Fact] 36 | public void CreateWriteMultipleRegistersRequestTooMuchData() => 37 | Assert.Throws(() => 38 | new WriteMultipleRegistersRequest(1, 2, MessageUtility.CreateDefaultCollection(3, Modbus.MaximumRegisterRequestResponseSize + 1))); 39 | 40 | /// 41 | /// Creates the maximum size of the write multiple registers request. 42 | /// 43 | [Fact] 44 | public void CreateWriteMultipleRegistersRequestMaxSize() 45 | { 46 | var request = new WriteMultipleRegistersRequest(1, 2, MessageUtility.CreateDefaultCollection(3, Modbus.MaximumRegisterRequestResponseSize)); 47 | 48 | Assert.Equal(Modbus.MaximumRegisterRequestResponseSize, request.NumberOfPoints); 49 | } 50 | 51 | /// 52 | /// Converts to string_writemultipleregistersrequest. 53 | /// 54 | [Fact] 55 | public void ToString_WriteMultipleRegistersRequest() 56 | { 57 | var col = new RegisterCollection(10, 20, 30, 40, 50); 58 | var request = new WriteMultipleRegistersRequest(11, 34, col); 59 | 60 | Assert.Equal("Write 5 holding registers starting at address 34.", request.ToString()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/WriteMultipleRegistersResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Message; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Message; 9 | 10 | /// 11 | /// WriteMultipleRegistersResponseFixture. 12 | /// 13 | public class WriteMultipleRegistersResponseFixture 14 | { 15 | /// 16 | /// Creates the write multiple registers response. 17 | /// 18 | [Fact] 19 | public void CreateWriteMultipleRegistersResponse() 20 | { 21 | var response = new WriteMultipleRegistersResponse(12, 39, 2); 22 | Assert.Equal(Modbus.WriteMultipleRegisters, response.FunctionCode); 23 | Assert.Equal(12, response.SlaveAddress); 24 | Assert.Equal(39, response.StartAddress); 25 | Assert.Equal(2, response.NumberOfPoints); 26 | } 27 | 28 | /// 29 | /// Creates the write multiple registers response too much data. 30 | /// 31 | [Fact] 32 | public void CreateWriteMultipleRegistersResponseTooMuchData() => Assert.Throws(() => new WriteMultipleRegistersResponse(1, 2, Modbus.MaximumRegisterRequestResponseSize + 1)); 33 | 34 | /// 35 | /// Creates the maximum size of the write multiple registers response. 36 | /// 37 | [Fact] 38 | public void CreateWriteMultipleRegistersResponseMaxSize() 39 | { 40 | var response = new WriteMultipleRegistersResponse(1, 2, Modbus.MaximumRegisterRequestResponseSize); 41 | Assert.Equal(Modbus.MaximumRegisterRequestResponseSize, response.NumberOfPoints); 42 | } 43 | 44 | /// 45 | /// Converts to string_test. 46 | /// 47 | [Fact] 48 | public void ToString_Test() 49 | { 50 | var response = new WriteMultipleRegistersResponse(1, 2, 3); 51 | 52 | Assert.Equal("Wrote 3 holding registers starting at address 2.", response.ToString()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/WriteSingleCoilRequestResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Message; 5 | using Xunit; 6 | 7 | namespace ModbusRx.UnitTests.Message; 8 | 9 | /// 10 | /// WriteSingleCoilRequestResponseFixture. 11 | /// 12 | public class WriteSingleCoilRequestResponseFixture 13 | { 14 | /// 15 | /// Creates new writesinglecoilrequestresponse. 16 | /// 17 | [Fact] 18 | public void NewWriteSingleCoilRequestResponse() 19 | { 20 | var request = new WriteSingleCoilRequestResponse(11, 5, true); 21 | Assert.Equal(11, request.SlaveAddress); 22 | Assert.Equal(5, request.StartAddress); 23 | Assert.Single(request.Data); 24 | Assert.Equal(Modbus.CoilOn, request.Data[0]); 25 | } 26 | 27 | /// 28 | /// Converts to string_true. 29 | /// 30 | [Fact] 31 | public void ToString_True() 32 | { 33 | var request = new WriteSingleCoilRequestResponse(11, 5, true); 34 | 35 | Assert.Equal("Write single coil 1 at address 5.", request.ToString()); 36 | } 37 | 38 | /// 39 | /// Converts to string_false. 40 | /// 41 | [Fact] 42 | public void ToString_False() 43 | { 44 | var request = new WriteSingleCoilRequestResponse(11, 5, false); 45 | 46 | Assert.Equal("Write single coil 0 at address 5.", request.ToString()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Message/WriteSingleRegisterRequestResponseFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Message; 5 | using Xunit; 6 | 7 | namespace ModbusRx.UnitTests.Message; 8 | 9 | /// 10 | /// WriteSingleRegisterRequestResponseFixture. 11 | /// 12 | public class WriteSingleRegisterRequestResponseFixture 13 | { 14 | /// 15 | /// Creates new writesingleregisterrequestresponse. 16 | /// 17 | [Fact] 18 | public void NewWriteSingleRegisterRequestResponse() 19 | { 20 | var message = new WriteSingleRegisterRequestResponse(12, 5, 1200); 21 | Assert.Equal(12, message.SlaveAddress); 22 | Assert.Equal(5, message.StartAddress); 23 | Assert.Single(message.Data); 24 | Assert.Equal(1200, message.Data[0]); 25 | } 26 | 27 | /// 28 | /// Converts to stringoverride. 29 | /// 30 | [Fact] 31 | public void ToStringOverride() 32 | { 33 | var message = new WriteSingleRegisterRequestResponse(12, 5, 1200); 34 | Assert.Equal("Write single holding register 1200 at address 5.", message.ToString()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/ModbusRx.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | net48;net6.0; 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/SlaveExceptionFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.IO; 6 | using ModbusRx.Message; 7 | using Xunit; 8 | 9 | namespace ModbusRx.UnitTests; 10 | 11 | /// 12 | /// SlaveExceptionFixture. 13 | /// 14 | public class SlaveExceptionFixture 15 | { 16 | /// 17 | /// Empties the constructor. 18 | /// 19 | [Fact] 20 | public void EmptyConstructor() 21 | { 22 | var e = new SlaveException(); 23 | Assert.Equal($"Exception of type '{typeof(SlaveException).FullName}' was thrown.", e.Message); 24 | Assert.Equal(0, e.SlaveAddress); 25 | Assert.Equal(0, e.FunctionCode); 26 | Assert.Equal(0, e.SlaveExceptionCode); 27 | Assert.Null(e.InnerException); 28 | } 29 | 30 | /// 31 | /// Constructors the with message. 32 | /// 33 | [Fact] 34 | public void ConstructorWithMessage() 35 | { 36 | var e = new SlaveException("Hello World"); 37 | Assert.Equal("Hello World", e.Message); 38 | Assert.Equal(0, e.SlaveAddress); 39 | Assert.Equal(0, e.FunctionCode); 40 | Assert.Equal(0, e.SlaveExceptionCode); 41 | Assert.Null(e.InnerException); 42 | } 43 | 44 | /// 45 | /// Constructors the with message and inner exception. 46 | /// 47 | [Fact] 48 | public void ConstructorWithMessageAndInnerException() 49 | { 50 | var inner = new IOException("Bar"); 51 | var e = new SlaveException("Foo", inner); 52 | Assert.Equal("Foo", e.Message); 53 | Assert.Same(inner, e.InnerException); 54 | Assert.Equal(0, e.SlaveAddress); 55 | Assert.Equal(0, e.FunctionCode); 56 | Assert.Equal(0, e.SlaveExceptionCode); 57 | } 58 | 59 | /// 60 | /// Constructors the with slave exception response. 61 | /// 62 | [Fact] 63 | public void ConstructorWithSlaveExceptionResponse() 64 | { 65 | var response = new SlaveExceptionResponse(12, Modbus.ReadCoils, 1); 66 | var e = new SlaveException(response); 67 | 68 | Assert.Equal(12, e.SlaveAddress); 69 | Assert.Equal(Modbus.ReadCoils, e.FunctionCode); 70 | Assert.Equal(1, e.SlaveExceptionCode); 71 | Assert.Null(e.InnerException); 72 | 73 | Assert.Equal( 74 | $@"Exception of type '{typeof(SlaveException).FullName}' was thrown.{Environment.NewLine}Function Code: {response.FunctionCode}{Environment.NewLine}Exception Code: {response.SlaveExceptionCode} - {Resources.IllegalFunction}", 75 | e.Message); 76 | } 77 | 78 | /// 79 | /// Constructors the with custom message and slave exception response. 80 | /// 81 | [Fact] 82 | public void ConstructorWithCustomMessageAndSlaveExceptionResponse() 83 | { 84 | var response = new SlaveExceptionResponse(12, Modbus.ReadCoils, 2); 85 | var customMessage = "custom message"; 86 | var e = new SlaveException(customMessage, response); 87 | 88 | Assert.Equal(12, e.SlaveAddress); 89 | Assert.Equal(Modbus.ReadCoils, e.FunctionCode); 90 | Assert.Equal(2, e.SlaveExceptionCode); 91 | Assert.Null(e.InnerException); 92 | 93 | Assert.Equal( 94 | $@"{customMessage}{Environment.NewLine}Function Code: {response.FunctionCode}{Environment.NewLine}Exception Code: {response.SlaveExceptionCode} - {Resources.IllegalDataAddress}", 95 | e.Message); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Utility/CollectionUtilityFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.Linq; 8 | using ModbusRx.Data; 9 | using ModbusRx.UnitTests.Message; 10 | using ModbusRx.Unme.Common; 11 | using Xunit; 12 | 13 | namespace ModbusRx.UnitTests.Utility; 14 | 15 | /// 16 | /// CollectionUtilityFixture. 17 | /// 18 | public class CollectionUtilityFixture 19 | { 20 | /// 21 | /// Slices the middle. 22 | /// 23 | [Fact] 24 | public void SliceMiddle() 25 | { 26 | byte[] test = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 27 | Assert.Equal(new byte[] { 3, 4, 5, 6, 7 }, test.Slice(2, 5).ToArray()); 28 | } 29 | 30 | /// 31 | /// Slices the beginning. 32 | /// 33 | [Fact] 34 | public void SliceBeginning() 35 | { 36 | byte[] test = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 37 | Assert.Equal(new byte[] { 1, 2 }, test.Slice(0, 2).ToArray()); 38 | } 39 | 40 | /// 41 | /// Slices the end. 42 | /// 43 | [Fact] 44 | public void SliceEnd() 45 | { 46 | byte[] test = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 47 | Assert.Equal(new byte[] { 9, 10 }, test.Slice(8, 2).ToArray()); 48 | } 49 | 50 | /// 51 | /// Slices the collection. 52 | /// 53 | [Fact] 54 | public void SliceCollection() 55 | { 56 | var col = new Collection(new bool[] { true, false, false, false, true, true }); 57 | Assert.Equal(new bool[] { false, false, true }, col.Slice(2, 3).ToArray()); 58 | } 59 | 60 | /// 61 | /// Slices the read only collection. 62 | /// 63 | [Fact] 64 | public void SliceReadOnlyCollection() 65 | { 66 | var col = new ReadOnlyCollection(new bool[] { true, false, false, false, true, true }); 67 | Assert.Equal(new bool[] { false, false, true }, col.Slice(2, 3).ToArray()); 68 | } 69 | 70 | /// 71 | /// Slices the null i collection. 72 | /// 73 | [Fact] 74 | public void SliceNullICollection() 75 | { 76 | ICollection col = null!; 77 | Assert.Throws(() => col.Slice(1, 1).ToArray()); 78 | } 79 | 80 | /// 81 | /// Slices the null array. 82 | /// 83 | [Fact] 84 | public void SliceNullArray() 85 | { 86 | bool[] array = null!; 87 | Assert.Throws(() => array.Slice(1, 1).ToArray()); 88 | } 89 | 90 | /// 91 | /// Creates the default size of the collection negative. 92 | /// 93 | [Fact] 94 | public void CreateDefaultCollectionNegativeSize() => 95 | Assert.Throws(() => MessageUtility.CreateDefaultCollection(0, -1)); 96 | 97 | /// 98 | /// Creates the default collection. 99 | /// 100 | [Fact] 101 | public void CreateDefaultCollection() 102 | { 103 | var col = MessageUtility.CreateDefaultCollection(3, 5); 104 | Assert.Equal(5, col.Count); 105 | Assert.Equal(new ushort[] { 3, 3, 3, 3, 3 }, col.ToArray()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/ModbusRx.UnitTests/Utility/DiscriminatedUnionFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using ModbusRx.Utility; 6 | using Xunit; 7 | 8 | namespace ModbusRx.UnitTests.Utility; 9 | 10 | /// 11 | /// DiscriminatedUnionFixture. 12 | /// 13 | public class DiscriminatedUnionFixture 14 | { 15 | /// 16 | /// Discriminateds the union create a. 17 | /// 18 | [Fact] 19 | public void DiscriminatedUnion_CreateA() 20 | { 21 | var du = DiscriminatedUnion.CreateA("foo"); 22 | Assert.Equal(DiscriminatedUnionOption.A, du.Option); 23 | Assert.Equal("foo", du.A); 24 | } 25 | 26 | /// 27 | /// Discriminateds the union create b. 28 | /// 29 | [Fact] 30 | public void DiscriminatedUnion_CreateB() 31 | { 32 | var du = DiscriminatedUnion.CreateB("foo"); 33 | Assert.Equal(DiscriminatedUnionOption.B, du.Option); 34 | Assert.Equal("foo", du.B); 35 | } 36 | 37 | /// 38 | /// Discriminateds the union allow nulls. 39 | /// 40 | [Fact] 41 | public void DiscriminatedUnion_AllowNulls() 42 | { 43 | var du = DiscriminatedUnion.CreateB(null!); 44 | Assert.Equal(DiscriminatedUnionOption.B, du.Option); 45 | Assert.Null(du.B); 46 | } 47 | 48 | /// 49 | /// Accesses the invalid option a. 50 | /// 51 | [Fact] 52 | public void AccessInvalidOption_A() 53 | { 54 | var du = DiscriminatedUnion.CreateB("foo"); 55 | Assert.Throws(() => du.A?.ToString()); 56 | } 57 | 58 | /// 59 | /// Accesses the invalid option b. 60 | /// 61 | [Fact] 62 | public void AccessInvalidOption_B() 63 | { 64 | var du = DiscriminatedUnion.CreateA("foo"); 65 | Assert.Throws(() => du.B?.ToString()); 66 | } 67 | 68 | /// 69 | /// Discriminateds the union to string. 70 | /// 71 | [Fact] 72 | public void DiscriminatedUnion_ToString() 73 | { 74 | var du = DiscriminatedUnion.CreateA("foo"); 75 | Assert.Equal("foo", du.ToString()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ModbusRx/Data/DataStoreEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.ObjectModel; 5 | using ModbusRx.Utility; 6 | 7 | namespace ModbusRx.Data; 8 | 9 | /// 10 | /// Event args for read write actions performed on the DataStore. 11 | /// 12 | public sealed class DataStoreEventArgs : EventArgs 13 | { 14 | private DataStoreEventArgs(ushort startAddress, ModbusDataType modbusDataType) 15 | { 16 | StartAddress = startAddress; 17 | ModbusDataType = modbusDataType; 18 | } 19 | 20 | /// 21 | /// Gets type of Modbus data (e.g. Holding register). 22 | /// 23 | public ModbusDataType ModbusDataType { get; } 24 | 25 | /// 26 | /// Gets start address of data. 27 | /// 28 | public ushort StartAddress { get; } 29 | 30 | /// 31 | /// Gets data that was read or written. 32 | /// 33 | public DiscriminatedUnion, ReadOnlyCollection>? Data { get; private set; } 34 | 35 | internal static DataStoreEventArgs CreateDataStoreEventArgs(ushort startAddress, ModbusDataType modbusDataType, IEnumerable data) 36 | { 37 | if (data == null) 38 | { 39 | throw new ArgumentNullException(nameof(data)); 40 | } 41 | 42 | if (typeof(T) == typeof(bool)) 43 | { 44 | var a = new ReadOnlyCollection(data.Cast().ToArray()); 45 | 46 | return new DataStoreEventArgs(startAddress, modbusDataType) 47 | { 48 | Data = DiscriminatedUnion, ReadOnlyCollection>.CreateA(a), 49 | }; 50 | } 51 | else if (typeof(T) == typeof(ushort)) 52 | { 53 | var b = new ReadOnlyCollection(data.Cast().ToArray()); 54 | 55 | return new DataStoreEventArgs(startAddress, modbusDataType) 56 | { 57 | Data = DiscriminatedUnion, ReadOnlyCollection>.CreateB(b), 58 | }; 59 | } 60 | else 61 | { 62 | throw new ArgumentException("Generic type T should be of type bool or ushort"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ModbusRx/Data/DataStoreFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Data; 5 | 6 | /// 7 | /// Data story factory. 8 | /// 9 | public static class DataStoreFactory 10 | { 11 | /// 12 | /// Factory method for default data store - register values set to 0 and discrete values set to false. 13 | /// 14 | /// A DataStore. 15 | public static DataStore CreateDefaultDataStore() => 16 | CreateDefaultDataStore(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); 17 | 18 | /// 19 | /// Factory method for default data store - register values set to 0 and discrete values set to false. 20 | /// 21 | /// Number of discrete coils. 22 | /// Number of discrete inputs. 23 | /// Number of holding registers. 24 | /// Number of input registers. 25 | /// New instance of Data store with defined inputs/outputs. 26 | public static DataStore CreateDefaultDataStore(ushort coilsCount, ushort inputsCount, ushort holdingRegistersCount, ushort inputRegistersCount) 27 | { 28 | var coils = new bool[coilsCount]; 29 | var inputs = new bool[inputsCount]; 30 | var holdingRegs = new ushort[holdingRegistersCount]; 31 | var inputRegs = new ushort[inputRegistersCount]; 32 | 33 | return new DataStore(coils, inputs, holdingRegs, inputRegs); 34 | } 35 | 36 | /// 37 | /// Factory method for test data store. 38 | /// 39 | internal static DataStore CreateTestDataStore() 40 | { 41 | var dataStore = new DataStore(); 42 | 43 | for (var i = 1; i < 3000; i++) 44 | { 45 | var value = i % 2 > 0; 46 | dataStore.CoilDiscretes.Add(value); 47 | dataStore.InputDiscretes.Add(!value); 48 | dataStore.HoldingRegisters.Add((ushort)i); 49 | dataStore.InputRegisters.Add((ushort)(i * 10)); 50 | } 51 | 52 | return dataStore; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ModbusRx/Data/DiscreteCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.ObjectModel; 5 | using System.Diagnostics; 6 | 7 | namespace ModbusRx.Data; 8 | 9 | /// 10 | /// Collection of discrete values. 11 | /// 12 | public class DiscreteCollection : Collection, IDataCollection 13 | { 14 | private const int BitsPerByte = 8; 15 | 16 | private readonly List _discretes; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | public DiscreteCollection() 22 | : this(new List()) 23 | { 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// Array for discrete collection. 30 | public DiscreteCollection(params bool[] bits) 31 | : this((IList)bits) 32 | { 33 | } 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// Array for discrete collection. 39 | public DiscreteCollection(params byte[] bytes) 40 | : this() 41 | { 42 | if (bytes == null) 43 | { 44 | throw new ArgumentNullException(nameof(bytes)); 45 | } 46 | 47 | _discretes.Capacity = bytes.Length * BitsPerByte; 48 | 49 | foreach (var b in bytes) 50 | { 51 | _discretes.Add((b & 1) == 1); 52 | _discretes.Add((b & 2) == 2); 53 | _discretes.Add((b & 4) == 4); 54 | _discretes.Add((b & 8) == 8); 55 | _discretes.Add((b & 16) == 16); 56 | _discretes.Add((b & 32) == 32); 57 | _discretes.Add((b & 64) == 64); 58 | _discretes.Add((b & 128) == 128); 59 | } 60 | } 61 | 62 | /// 63 | /// Initializes a new instance of the class. 64 | /// 65 | /// List for discrete collection. 66 | public DiscreteCollection(IList bits) 67 | : this(new List(bits)) 68 | { 69 | } 70 | 71 | /// 72 | /// Initializes a new instance of the class. 73 | /// 74 | /// List for discrete collection. 75 | internal DiscreteCollection(List bits) 76 | : base(bits) 77 | { 78 | Debug.Assert(bits is not null, "Discrete bits is null."); 79 | _discretes = bits!; 80 | } 81 | 82 | /// 83 | /// Gets the network bytes. 84 | /// 85 | public byte[] NetworkBytes 86 | { 87 | get 88 | { 89 | var bytes = new byte[ByteCount]; 90 | 91 | for (var index = 0; index < _discretes.Count; index++) 92 | { 93 | if (_discretes[index]) 94 | { 95 | bytes[index / BitsPerByte] |= (byte)(1 << (index % BitsPerByte)); 96 | } 97 | } 98 | 99 | return bytes; 100 | } 101 | } 102 | 103 | /// 104 | /// Gets the byte count. 105 | /// 106 | public byte ByteCount => (byte)((Count + 7) / 8); 107 | 108 | /// 109 | /// Returns a that represents the current . 110 | /// 111 | /// 112 | /// A that represents the current . 113 | /// 114 | public override string ToString() => 115 | string.Concat("{", string.Join(", ", this.Select(discrete => discrete ? "1" : "0").ToArray()), "}"); 116 | } 117 | -------------------------------------------------------------------------------- /src/ModbusRx/Data/IDataCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Data; 5 | 6 | /// 7 | /// Modbus message containing data. 8 | /// 9 | public interface IDataCollection 10 | { 11 | /// 12 | /// Gets the network bytes. 13 | /// 14 | byte[] NetworkBytes { get; } 15 | 16 | /// 17 | /// Gets the byte count. 18 | /// 19 | byte ByteCount { get; } 20 | } 21 | -------------------------------------------------------------------------------- /src/ModbusRx/Data/ModbusDataType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Data; 5 | 6 | /// 7 | /// Types of data supported by the Modbus protocol. 8 | /// 9 | public enum ModbusDataType 10 | { 11 | /// 12 | /// Read/write register. 13 | /// 14 | HoldingRegister, 15 | 16 | /// 17 | /// Readonly register. 18 | /// 19 | InputRegister, 20 | 21 | /// 22 | /// Read/write discrete. 23 | /// 24 | Coil, 25 | 26 | /// 27 | /// Readonly discrete. 28 | /// 29 | Input, 30 | } 31 | -------------------------------------------------------------------------------- /src/ModbusRx/Data/RegisterCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.ObjectModel; 5 | using System.Net; 6 | using ModbusRx.Utility; 7 | 8 | namespace ModbusRx.Data; 9 | 10 | /// 11 | /// Collection of 16 bit registers. 12 | /// 13 | public class RegisterCollection : Collection, IDataCollection 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public RegisterCollection() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// Array for register collection. 26 | public RegisterCollection(byte[] bytes) 27 | : this((IList)ModbusUtility.NetworkBytesToHostUInt16(bytes)) 28 | { 29 | } 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | /// Array for register collection. 35 | public RegisterCollection(params ushort[] registers) 36 | : this((IList)registers) 37 | { 38 | } 39 | 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | /// List for register collection. 44 | public RegisterCollection(IList registers) 45 | : base(registers?.IsReadOnly == true ? new List(registers) : registers!) 46 | { 47 | } 48 | 49 | /// 50 | /// Gets the network bytes. 51 | /// 52 | public byte[] NetworkBytes 53 | { 54 | get 55 | { 56 | var bytes = new MemoryStream(ByteCount); 57 | 58 | foreach (var register in this) 59 | { 60 | var b = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)register)); 61 | bytes.Write(b, 0, b.Length); 62 | } 63 | 64 | return bytes.ToArray(); 65 | } 66 | } 67 | 68 | /// 69 | /// Gets the byte count. 70 | /// 71 | public byte ByteCount => (byte)(Count * 2); 72 | 73 | /// 74 | /// Returns a that represents the current . 75 | /// 76 | /// 77 | /// A that represents the current . 78 | /// 79 | public override string ToString() => 80 | string.Concat("{", string.Join(", ", this.Select(v => v.ToString()).ToArray()), "}"); 81 | } 82 | -------------------------------------------------------------------------------- /src/ModbusRx/Device/IModbusSerialMaster.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.IO; 5 | 6 | namespace ModbusRx.Device; 7 | 8 | /// 9 | /// Modbus Serial Master device. 10 | /// 11 | public interface IModbusSerialMaster : IModbusMaster 12 | { 13 | /// 14 | /// Gets transport for used by this master. 15 | /// 16 | new ModbusSerialTransport? Transport { get; } 17 | 18 | /// 19 | /// Serial Line only. 20 | /// Diagnostic function which loops back the original data. 21 | /// NModbus only supports looping back one ushort value, this is a 22 | /// limitation of the "Best Effort" implementation of the RTU protocol. 23 | /// 24 | /// Address of device to test. 25 | /// Data to return. 26 | /// Return true if slave device echoed data. 27 | bool ReturnQueryData(byte slaveAddress, ushort data); 28 | } 29 | -------------------------------------------------------------------------------- /src/ModbusRx/Device/ModbusDevice.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reactive.Disposables; 5 | using ModbusRx.IO; 6 | using ModbusRx.Unme.Common; 7 | 8 | namespace ModbusRx.Device; 9 | 10 | /// 11 | /// Modbus device. 12 | /// 13 | public abstract class ModbusDevice : ICancelable 14 | { 15 | private ModbusTransport? _transport; 16 | 17 | internal ModbusDevice(ModbusTransport transport) => _transport = transport; 18 | 19 | /// 20 | /// Gets the Modbus Transport. 21 | /// 22 | public ModbusTransport? Transport => _transport; 23 | 24 | /// 25 | /// Gets a value indicating whether gets a value that indicates whether the object is disposed. 26 | /// 27 | public bool IsDisposed { get; private set; } 28 | 29 | /// 30 | /// Releases unmanaged and - optionally - managed resources. 31 | /// 32 | public void Dispose() 33 | { 34 | Dispose(true); 35 | GC.SuppressFinalize(this); 36 | } 37 | 38 | /// 39 | /// Releases unmanaged and - optionally - managed resources. 40 | /// 41 | /// 42 | /// true to release both managed and unmanaged resources; 43 | /// false to release only unmanaged resources. 44 | /// 45 | protected virtual void Dispose(bool disposing) 46 | { 47 | if (disposing) 48 | { 49 | DisposableUtility.Dispose(ref _transport); 50 | IsDisposed = true; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ModbusRx/Device/ModbusSlaveRequestEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Message; 5 | 6 | namespace ModbusRx.Device; 7 | 8 | /// 9 | /// Modbus Slave request event args containing information on the message. 10 | /// 11 | public class ModbusSlaveRequestEventArgs : EventArgs 12 | { 13 | internal ModbusSlaveRequestEventArgs(IModbusMessage message) => Message = message; 14 | 15 | /// 16 | /// Gets the message. 17 | /// 18 | public IModbusMessage Message { get; } 19 | } 20 | -------------------------------------------------------------------------------- /src/ModbusRx/Device/ModbusUdpSlave.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using CP.IO.Ports; 8 | using ModbusRx.IO; 9 | using ModbusRx.Message; 10 | using ModbusRx.Unme.Common; 11 | 12 | namespace ModbusRx.Device; 13 | 14 | /// 15 | /// Modbus UDP slave device. 16 | /// 17 | public sealed class ModbusUdpSlave : ModbusSlave 18 | { 19 | private readonly UdpClientRx _udpClient; 20 | 21 | private ModbusUdpSlave(byte unitId, UdpClientRx udpClient) 22 | : base(unitId, new ModbusIpTransport(new UdpClientAdapter(udpClient))) => _udpClient = udpClient; 23 | 24 | /// 25 | /// Modbus UDP slave factory method. 26 | /// Creates NModbus UDP slave with default. 27 | /// 28 | /// The client. 29 | /// A ModbusUdpSlave. 30 | public static ModbusUdpSlave CreateUdp(UdpClientRx client) => 31 | new(Modbus.DefaultIpSlaveUnitId, client); 32 | 33 | /// 34 | /// Modbus UDP slave factory method. 35 | /// 36 | /// The unit identifier. 37 | /// The client. 38 | /// A ModbusUdpSlave. 39 | public static ModbusUdpSlave CreateUdp(byte unitId, UdpClientRx client) => 40 | new(unitId, client); 41 | 42 | /// 43 | /// Start slave listening for requests. 44 | /// 45 | /// A representing the asynchronous operation. 46 | public override async Task ListenAsync() 47 | { 48 | Debug.WriteLine("Start Modbus Udp Server."); 49 | 50 | try 51 | { 52 | while (true) 53 | { 54 | var receiveResult = await _udpClient.ReceiveAsync().ConfigureAwait(false); 55 | var masterEndPoint = receiveResult.RemoteEndPoint; 56 | var frame = receiveResult.Buffer; 57 | 58 | Debug.WriteLine($"Read Frame completed {frame.Length} bytes"); 59 | Debug.WriteLine($"RX: {string.Join(", ", frame)}"); 60 | 61 | var request = ModbusMessageFactory.CreateModbusRequest(frame.Slice(6, frame.Length - 6).ToArray()); 62 | request.TransactionId = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 0)); 63 | 64 | // perform action and build response 65 | var response = ApplyRequest(request); 66 | response.TransactionId = request.TransactionId; 67 | 68 | // write response 69 | var responseFrame = Transport?.BuildMessageFrame(response); 70 | Debug.WriteLine($"TX: {string.Join(", ", responseFrame!)}"); 71 | await _udpClient.SendAsync(responseFrame!, responseFrame!.Length, masterEndPoint).ConfigureAwait(false); 72 | } 73 | } 74 | catch (SocketException se) when (se.SocketErrorCode == SocketError.Interrupted) 75 | { 76 | throw; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ModbusRx/Device/TcpConnectionEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Device; 5 | 6 | internal class TcpConnectionEventArgs : EventArgs 7 | { 8 | public TcpConnectionEventArgs(string endPoint) 9 | { 10 | if (endPoint == null) 11 | { 12 | throw new ArgumentNullException(nameof(endPoint)); 13 | } 14 | 15 | if (endPoint == string.Empty) 16 | { 17 | throw new ArgumentException(Resources.EmptyEndPoint); 18 | } 19 | 20 | EndPoint = endPoint; 21 | } 22 | 23 | public string EndPoint { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /src/ModbusRx/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | [module: 7 | SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:ModbusRx", Justification = "Required")] 8 | [module: 9 | SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:ModbusRx.Utility", Justification = "Required")] 10 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/EmptyTransport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Message; 5 | 6 | namespace ModbusRx.IO; 7 | 8 | /// 9 | /// EmptyTransport. 10 | /// 11 | /// 12 | public class EmptyTransport : ModbusTransport 13 | { 14 | internal override Task ReadRequest() => 15 | throw new NotImplementedException(); 16 | 17 | internal override Task ReadResponse() => 18 | throw new NotImplementedException(); 19 | 20 | internal override byte[] BuildMessageFrame(Message.IModbusMessage message) => 21 | throw new NotImplementedException(); 22 | 23 | internal override void Write(IModbusMessage message) => 24 | throw new NotImplementedException(); 25 | 26 | internal override void OnValidateResponse(IModbusMessage request, IModbusMessage response) => 27 | throw new NotImplementedException(); 28 | } 29 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/IStreamResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.IO; 5 | 6 | /// 7 | /// Represents a serial resource. 8 | /// Implementor - http://en.wikipedia.org/wiki/Bridge_Pattern. 9 | /// 10 | public interface IStreamResource : IDisposable 11 | { 12 | /// 13 | /// Gets indicates that no timeout should occur. 14 | /// 15 | int InfiniteTimeout { get; } 16 | 17 | /// 18 | /// Gets or sets the number of milliseconds before a timeout occurs when a read operation does not finish. 19 | /// 20 | int ReadTimeout { get; set; } 21 | 22 | /// 23 | /// Gets or sets the number of milliseconds before a timeout occurs when a write operation does not finish. 24 | /// 25 | int WriteTimeout { get; set; } 26 | 27 | /// 28 | /// Purges the receive buffer. 29 | /// 30 | void DiscardInBuffer(); 31 | 32 | /// 33 | /// Reads a number of bytes from the input buffer and writes those bytes into a byte array at the specified offset. 34 | /// 35 | /// The byte array to write the input to. 36 | /// The offset in the buffer array to begin writing. 37 | /// The number of bytes to read. 38 | /// The number of bytes read. 39 | Task ReadAsync(byte[] buffer, int offset, int count); 40 | 41 | /// 42 | /// Writes a specified number of bytes to the port from an output buffer, starting at the specified offset. 43 | /// 44 | /// The byte array that contains the data to write to the port. 45 | /// The offset in the buffer array to begin writing. 46 | /// The number of bytes to write. 47 | void Write(byte[] buffer, int offset, int count); 48 | } 49 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/ModbusAsciiTransport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Text; 6 | using ModbusRx.Message; 7 | using ModbusRx.Utility; 8 | 9 | namespace ModbusRx.IO; 10 | 11 | /// 12 | /// Refined Abstraction - http://en.wikipedia.org/wiki/Bridge_Pattern. 13 | /// 14 | internal class ModbusAsciiTransport : ModbusSerialTransport 15 | { 16 | internal ModbusAsciiTransport(IStreamResource streamResource) 17 | : base(streamResource) => Debug.Assert(streamResource is not null, "Argument streamResource cannot be null."); 18 | 19 | internal override byte[] BuildMessageFrame(IModbusMessage message) 20 | { 21 | var msgFrame = message.MessageFrame; 22 | 23 | var msgFrameAscii = ModbusUtility.GetAsciiBytes(msgFrame); 24 | var lrcAscii = ModbusUtility.GetAsciiBytes(ModbusUtility.CalculateLrc(msgFrame)); 25 | var nlAscii = Encoding.UTF8.GetBytes(Modbus.NewLine.ToCharArray()); 26 | 27 | var frame = new MemoryStream(1 + msgFrameAscii.Length + lrcAscii.Length + nlAscii.Length); 28 | frame.WriteByte((byte)':'); 29 | frame.Write(msgFrameAscii, 0, msgFrameAscii.Length); 30 | frame.Write(lrcAscii, 0, lrcAscii.Length); 31 | frame.Write(nlAscii, 0, nlAscii.Length); 32 | 33 | return frame.ToArray(); 34 | } 35 | 36 | internal override bool ChecksumsMatch(IModbusMessage message, byte[] messageFrame) => 37 | ModbusUtility.CalculateLrc(message.MessageFrame) == messageFrame[^1]; 38 | 39 | internal override Task ReadRequest() => 40 | ReadRequestResponse(); 41 | 42 | internal override Task ReadResponse() => 43 | CreateResponse(ReadRequestResponse()); 44 | 45 | internal async Task ReadRequestResponse() 46 | { 47 | // read message frame, removing frame start ':' 48 | var frameHex = (await StreamResourceUtility.ReadLineAsync(StreamResource))[1..]; 49 | 50 | // convert hex to bytes 51 | var frame = ModbusUtility.HexToBytes(frameHex); 52 | Debug.WriteLine($"RX: {string.Join(", ", frame)}"); 53 | 54 | if (frame.Length < 3) 55 | { 56 | throw new IOException("Premature end of stream, message truncated."); 57 | } 58 | 59 | return frame; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/ModbusSerialTransport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using ModbusRx.Message; 6 | 7 | namespace ModbusRx.IO; 8 | 9 | /// 10 | /// Transport for Serial protocols. 11 | /// Refined Abstraction - http://en.wikipedia.org/wiki/Bridge_Pattern. 12 | /// 13 | public abstract class ModbusSerialTransport : ModbusTransport 14 | { 15 | internal ModbusSerialTransport(IStreamResource streamResource) 16 | : base(streamResource) => Debug.Assert(streamResource is not null, "Argument streamResource cannot be null."); 17 | 18 | /// 19 | /// Gets or sets a value indicating whether LRC/CRC frame checking is performed on messages. 20 | /// 21 | public bool CheckFrame { get; set; } = true; 22 | 23 | internal void DiscardInBuffer() => 24 | StreamResource.DiscardInBuffer(); 25 | 26 | internal override void Write(IModbusMessage message) 27 | { 28 | DiscardInBuffer(); 29 | 30 | var frame = BuildMessageFrame(message); 31 | Debug.WriteLine($"TX: {string.Join(", ", frame)}"); 32 | StreamResource.Write(frame, 0, frame.Length); 33 | } 34 | 35 | internal override async Task CreateResponse(Task frame) 36 | { 37 | var response = await base.CreateResponse(frame); 38 | 39 | // compare checksum 40 | if (CheckFrame && !ChecksumsMatch(response, await frame)) 41 | { 42 | var msg = $"Checksums failed to match {string.Join(", ", response.MessageFrame)} != {string.Join(", ", frame)}"; 43 | Debug.WriteLine(msg); 44 | throw new IOException(msg); 45 | } 46 | 47 | return response; 48 | } 49 | 50 | internal abstract bool ChecksumsMatch(IModbusMessage message, byte[] messageFrame); 51 | 52 | internal override void OnValidateResponse(IModbusMessage request, IModbusMessage response) 53 | { 54 | // no-op 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/SerialPortAdapter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.IO.Ports; 6 | using CP.IO.Ports; 7 | 8 | namespace ModbusRx.IO; 9 | 10 | /// 11 | /// Concrete Implementor - http://en.wikipedia.org/wiki/Bridge_Pattern. 12 | /// 13 | public class SerialPortAdapter : IStreamResource 14 | { 15 | /// 16 | /// Creates new line. 17 | /// 18 | private const string NewLine = "\r\n"; 19 | /// 20 | /// The serial port. 21 | /// 22 | private SerialPortRx _serialPort; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The serial port. 28 | public SerialPortAdapter(SerialPortRx serialPort) 29 | { 30 | Debug.Assert(serialPort is not null, "Argument serialPort cannot be null."); 31 | 32 | _serialPort = serialPort!; 33 | _serialPort.NewLine = NewLine; 34 | } 35 | 36 | /// 37 | /// Gets indicates that no timeout should occur. 38 | /// 39 | public int InfiniteTimeout => SerialPort.InfiniteTimeout; 40 | 41 | /// 42 | /// Gets or sets the number of milliseconds before a timeout occurs when a read operation does not finish. 43 | /// 44 | public int ReadTimeout 45 | { 46 | get => _serialPort.ReadTimeout; 47 | set => _serialPort.ReadTimeout = value; 48 | } 49 | 50 | /// 51 | /// Gets or sets the number of milliseconds before a timeout occurs when a write operation does not finish. 52 | /// 53 | public int WriteTimeout 54 | { 55 | get => _serialPort.WriteTimeout; 56 | set => _serialPort.WriteTimeout = value; 57 | } 58 | 59 | /// 60 | /// Purges the receive buffer. 61 | /// 62 | public void DiscardInBuffer() => 63 | _serialPort.DiscardInBuffer(); 64 | 65 | /// 66 | /// Reads a number of bytes from the input buffer and writes those bytes into a byte array at the specified offset. 67 | /// 68 | /// The byte array to write the input to. 69 | /// The offset in the buffer array to begin writing. 70 | /// The number of bytes to read. 71 | /// 72 | /// The number of bytes read. 73 | /// 74 | public Task ReadAsync(byte[] buffer, int offset, int count) => 75 | _serialPort.ReadAsync(buffer, offset, count); 76 | 77 | /// 78 | /// Writes a specified number of bytes to the port from an output buffer, starting at the specified offset. 79 | /// 80 | /// The byte array that contains the data to write to the port. 81 | /// The offset in the buffer array to begin writing. 82 | /// The number of bytes to write. 83 | public void Write(byte[] buffer, int offset, int count) => 84 | _serialPort.Write(buffer, offset, count); 85 | 86 | /// 87 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 88 | /// 89 | public void Dispose() 90 | { 91 | Dispose(true); 92 | GC.SuppressFinalize(this); 93 | } 94 | 95 | /// 96 | /// Releases unmanaged and - optionally - managed resources. 97 | /// 98 | /// true to release both managed and unmanaged resources; false to release only unmanaged resources. 99 | protected virtual void Dispose(bool disposing) 100 | { 101 | if (disposing) 102 | { 103 | _serialPort?.Dispose(); 104 | _serialPort = null!; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/StreamResourceUtility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Text; 5 | 6 | namespace ModbusRx.IO; 7 | 8 | internal static class StreamResourceUtility 9 | { 10 | internal static async Task ReadLineAsync(IStreamResource stream) 11 | { 12 | var result = new StringBuilder(); 13 | var singleByteBuffer = new byte[1]; 14 | 15 | do 16 | { 17 | if (await stream.ReadAsync(singleByteBuffer, 0, 1) == 0) 18 | { 19 | continue; 20 | } 21 | 22 | result.Append(Encoding.UTF8.GetChars(singleByteBuffer).First()); 23 | } 24 | while (!result.ToString().EndsWith(Modbus.NewLine)); 25 | 26 | return result.ToString()[..(result.Length - Modbus.NewLine.Length)]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/TcpClientAdapter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using CP.IO.Ports; 6 | using ModbusRx.Unme.Common; 7 | 8 | namespace ModbusRx.IO; 9 | 10 | /// 11 | /// Concrete Implementor - http://en.wikipedia.org/wiki/Bridge_Pattern. 12 | /// 13 | internal class TcpClientAdapter : IStreamResource 14 | { 15 | private TcpClientRx? _tcpClient; 16 | 17 | public TcpClientAdapter(TcpClientRx tcpClient) 18 | { 19 | Debug.Assert(tcpClient is not null, "Argument tcpClient cannot be null."); 20 | 21 | _tcpClient = tcpClient; 22 | } 23 | 24 | public int InfiniteTimeout => Timeout.Infinite; 25 | 26 | public int ReadTimeout 27 | { 28 | get => _tcpClient!.ReadTimeout; 29 | set => _tcpClient!.ReadTimeout = value; 30 | } 31 | 32 | public int WriteTimeout 33 | { 34 | get => _tcpClient!.WriteTimeout; 35 | set => _tcpClient!.WriteTimeout = value; 36 | } 37 | 38 | public void Write(byte[] buffer, int offset, int count) => 39 | _tcpClient!.Write(buffer, offset, count); 40 | 41 | public Task ReadAsync(byte[] buffer, int offset, int count) => 42 | _tcpClient!.ReadAsync(buffer, offset, count); 43 | 44 | public void DiscardInBuffer() => 45 | _tcpClient!.DiscardInBuffer(); 46 | 47 | public void Dispose() 48 | { 49 | Dispose(true); 50 | GC.SuppressFinalize(this); 51 | } 52 | 53 | protected virtual void Dispose(bool disposing) 54 | { 55 | if (disposing) 56 | { 57 | DisposableUtility.Dispose(ref _tcpClient); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ModbusRx/IO/UdpClientAdapter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using CP.IO.Ports; 5 | using ModbusRx.Unme.Common; 6 | 7 | namespace ModbusRx.IO; 8 | 9 | /// 10 | /// Concrete Implementor - http://en.wikipedia.org/wiki/Bridge_Pattern. 11 | /// 12 | internal class UdpClientAdapter : IStreamResource 13 | { 14 | private UdpClientRx? _udpClient; 15 | 16 | public UdpClientAdapter(UdpClientRx udpClient) 17 | { 18 | if (udpClient == null) 19 | { 20 | throw new ArgumentNullException(nameof(udpClient)); 21 | } 22 | 23 | _udpClient = udpClient; 24 | } 25 | 26 | public int InfiniteTimeout => _udpClient!.InfiniteTimeout; 27 | 28 | public int ReadTimeout 29 | { 30 | get => _udpClient!.ReadTimeout; 31 | set => _udpClient!.ReadTimeout = value; 32 | } 33 | 34 | public int WriteTimeout 35 | { 36 | get => _udpClient!.WriteTimeout; 37 | set => _udpClient!.WriteTimeout = value; 38 | } 39 | 40 | public void DiscardInBuffer() 41 | { 42 | // no-op 43 | } 44 | 45 | public Task ReadAsync(byte[] buffer, int offset, int count) => 46 | _udpClient!.ReadAsync(buffer, offset, count); 47 | 48 | public void Write(byte[] buffer, int offset, int count) => 49 | _udpClient?.Write(buffer, offset, count); 50 | 51 | public void Dispose() 52 | { 53 | Dispose(true); 54 | GC.SuppressFinalize(this); 55 | } 56 | 57 | protected virtual void Dispose(bool disposing) 58 | { 59 | if (disposing) 60 | { 61 | DisposableUtility.Dispose(ref _udpClient); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ModbusRx/InvalidModbusRequestException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx; 5 | 6 | /// 7 | /// An exception that provides the exception code that will be sent in response to an invalid Modbus request. 8 | /// 9 | #pragma warning disable RCS1194 // Implement exception constructors. 10 | public class InvalidModbusRequestException : Exception 11 | #pragma warning restore RCS1194 // Implement exception constructors. 12 | { 13 | /// 14 | /// Initializes a new instance of the class with a specified Modbus exception code. 15 | /// 16 | /// The Modbus exception code to provide to the slave. 17 | public InvalidModbusRequestException(byte exceptionCode) 18 | : this(GetMessage(exceptionCode), exceptionCode) 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class with a specified error message and Modbus exception code. 24 | /// 25 | /// The error message that explains the reason for the exception. 26 | /// The Modbus exception code to provide to the slave. 27 | public InvalidModbusRequestException(string message, byte exceptionCode) 28 | : this(message, exceptionCode, null!) 29 | { 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the class with a specified Modbus exception code and a reference to the inner exception that is the cause of this exception. 34 | /// 35 | /// The Modbus exception code to provide to the slave. 36 | /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. 37 | public InvalidModbusRequestException(byte exceptionCode, Exception innerException) 38 | : this(GetMessage(exceptionCode), exceptionCode, innerException) 39 | { 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class with a specified Modbus exception code and a reference to the inner exception that is the cause of this exception. 44 | /// 45 | /// The error message that explains the reason for the exception. 46 | /// The Modbus exception code to provide to the slave. 47 | /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. 48 | public InvalidModbusRequestException(string message, byte exceptionCode, Exception innerException) 49 | : base(message, innerException) => ExceptionCode = exceptionCode; 50 | 51 | /// 52 | /// Gets the Modbus exception code to provide to the slave. 53 | /// 54 | public byte ExceptionCode { get; } 55 | 56 | private static string GetMessage(byte exceptionCode) => 57 | $"Modbus exception code {exceptionCode}."; 58 | } 59 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/AbstractModbusMessage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Message; 5 | 6 | /// 7 | /// Abstract Modbus message. 8 | /// 9 | public abstract class AbstractModbusMessage 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Abstract Modbus message. 14 | /// 15 | internal AbstractModbusMessage() => MessageImpl = new ModbusMessageImpl(); 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// Abstract Modbus message. 20 | /// 21 | internal AbstractModbusMessage(byte slaveAddress, byte functionCode) => MessageImpl = new ModbusMessageImpl(slaveAddress, functionCode); 22 | 23 | /// 24 | /// Gets or sets the transaction identifier. 25 | /// 26 | /// 27 | /// The transaction identifier. 28 | /// 29 | public ushort TransactionId 30 | { 31 | get => MessageImpl.TransactionId; 32 | set => MessageImpl.TransactionId = value; 33 | } 34 | 35 | /// 36 | /// Gets or sets the function code. 37 | /// 38 | /// 39 | /// The function code. 40 | /// 41 | public byte FunctionCode 42 | { 43 | get => MessageImpl.FunctionCode; 44 | set => MessageImpl.FunctionCode = value; 45 | } 46 | 47 | /// 48 | /// Gets or sets the slave address. 49 | /// 50 | /// 51 | /// The slave address. 52 | /// 53 | public byte SlaveAddress 54 | { 55 | get => MessageImpl.SlaveAddress; 56 | set => MessageImpl.SlaveAddress = value; 57 | } 58 | 59 | /// 60 | /// Gets the message frame. 61 | /// 62 | /// 63 | /// The message frame. 64 | /// 65 | public byte[] MessageFrame => 66 | MessageImpl.MessageFrame; 67 | 68 | /// 69 | /// Gets the protocol data unit. 70 | /// 71 | /// 72 | /// The protocol data unit. 73 | /// 74 | public virtual byte[] ProtocolDataUnit => 75 | MessageImpl.ProtocolDataUnit; 76 | 77 | /// 78 | /// Gets the minimum size of the frame. 79 | /// 80 | /// 81 | /// The minimum size of the frame. 82 | /// 83 | public abstract int MinimumFrameSize { get; } 84 | 85 | internal ModbusMessageImpl MessageImpl { get; } 86 | 87 | /// 88 | /// Initializes the specified frame. 89 | /// 90 | /// The frame. 91 | public void Initialize(byte[] frame) 92 | { 93 | if (frame?.Length < MinimumFrameSize) 94 | { 95 | var msg = $"Message frame must contain at least {MinimumFrameSize} bytes of data."; 96 | throw new FormatException(msg); 97 | } 98 | 99 | MessageImpl.Initialize(frame!); 100 | InitializeUnique(frame!); 101 | } 102 | 103 | /// 104 | /// Initializes the unique. 105 | /// 106 | /// The frame. 107 | protected abstract void InitializeUnique(byte[] frame); 108 | } 109 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/AbstractModbusMessageWithData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | 6 | namespace ModbusRx.Message; 7 | 8 | /// 9 | /// AbstractModbusMessageWithData. 10 | /// 11 | /// The type of the data. 12 | /// 13 | public abstract class AbstractModbusMessageWithData : AbstractModbusMessage 14 | where TData : IDataCollection 15 | { 16 | internal AbstractModbusMessageWithData() 17 | { 18 | } 19 | 20 | internal AbstractModbusMessageWithData(byte slaveAddress, byte functionCode) 21 | : base(slaveAddress, functionCode) 22 | { 23 | } 24 | 25 | /// 26 | /// Gets or sets the data. 27 | /// 28 | /// 29 | /// The data. 30 | /// 31 | public TData Data 32 | { 33 | get => (TData)MessageImpl.Data!; 34 | set => MessageImpl.Data = value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/DiagnosticsRequestResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | using ModbusRx.Data; 7 | using ModbusRx.Unme.Common; 8 | 9 | namespace ModbusRx.Message; 10 | 11 | internal class DiagnosticsRequestResponse : AbstractModbusMessageWithData, IModbusMessage 12 | { 13 | public DiagnosticsRequestResponse() 14 | { 15 | } 16 | 17 | public DiagnosticsRequestResponse(ushort subFunctionCode, byte slaveAddress, RegisterCollection data) 18 | : base(slaveAddress, Modbus.Diagnostics) 19 | { 20 | SubFunctionCode = subFunctionCode; 21 | Data = data; 22 | } 23 | 24 | public override int MinimumFrameSize => 6; 25 | 26 | public ushort SubFunctionCode 27 | { 28 | get => MessageImpl.SubFunctionCode!.Value; 29 | set => MessageImpl.SubFunctionCode = value; 30 | } 31 | 32 | public override string ToString() 33 | { 34 | Debug.Assert( 35 | SubFunctionCode == Modbus.DiagnosticsReturnQueryData, 36 | "Need to add support for additional sub-function."); 37 | 38 | return $"Diagnostics message, sub-function return query data - {Data}."; 39 | } 40 | 41 | protected override void InitializeUnique(byte[] frame) 42 | { 43 | SubFunctionCode = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 44 | Data = new RegisterCollection(frame.Slice(4, 2).ToArray()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/IModbusMessage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Message; 5 | 6 | /// 7 | /// A message built by the master (client) that initiates a Modbus transaction. 8 | /// 9 | public interface IModbusMessage 10 | { 11 | /// 12 | /// Gets or sets the function code tells the server what kind of action to perform. 13 | /// 14 | byte FunctionCode { get; set; } 15 | 16 | /// 17 | /// Gets or sets address of the slave (server). 18 | /// 19 | byte SlaveAddress { get; set; } 20 | 21 | /// 22 | /// Gets composition of the slave address and protocol data unit. 23 | /// 24 | byte[] MessageFrame { get; } 25 | 26 | /// 27 | /// Gets composition of the function code and message data. 28 | /// 29 | byte[] ProtocolDataUnit { get; } 30 | 31 | /// 32 | /// Gets or sets a unique identifier assigned to a message when using the IP protocol. 33 | /// 34 | ushort TransactionId { get; set; } 35 | 36 | /// 37 | /// Initializes a modbus message from the specified message frame. 38 | /// 39 | /// Bytes of Modbus frame. 40 | void Initialize(byte[] frame); 41 | } 42 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/IModbusRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Message; 5 | 6 | /// 7 | /// Methods specific to a modbus request message. 8 | /// 9 | public interface IModbusRequest : IModbusMessage 10 | { 11 | /// 12 | /// Validate the specified response against the current request. 13 | /// 14 | /// The response. 15 | void ValidateResponse(IModbusMessage response); 16 | } 17 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/ModbusMessageFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Message; 5 | 6 | /// 7 | /// Modbus message factory. 8 | /// 9 | public static class ModbusMessageFactory 10 | { 11 | /// 12 | /// Minimum request frame length. 13 | /// 14 | private const int MinRequestFrameLength = 3; 15 | 16 | /// 17 | /// Create a Modbus message. 18 | /// 19 | /// Modbus message type. 20 | /// Bytes of Modbus frame. 21 | /// New Modbus message based on type and frame bytes. 22 | public static T CreateModbusMessage(byte[] frame) 23 | where T : IModbusMessage, new() 24 | { 25 | IModbusMessage message = new T(); 26 | message.Initialize(frame); 27 | 28 | return (T)message; 29 | } 30 | 31 | /// 32 | /// Create a Modbus request. 33 | /// 34 | /// Bytes of Modbus frame. 35 | /// Modbus request. 36 | public static IModbusMessage CreateModbusRequest(byte[] frame) 37 | { 38 | if (frame?.Length < MinRequestFrameLength) 39 | { 40 | throw new FormatException($"Argument 'frame' must have a length of at least {MinRequestFrameLength} bytes."); 41 | } 42 | 43 | var functionCode = frame![1]; 44 | return functionCode switch 45 | { 46 | Modbus.ReadCoils or Modbus.ReadInputs => CreateModbusMessage(frame), 47 | Modbus.ReadHoldingRegisters or Modbus.ReadInputRegisters => CreateModbusMessage(frame), 48 | Modbus.WriteSingleCoil => CreateModbusMessage(frame), 49 | Modbus.WriteSingleRegister => CreateModbusMessage(frame), 50 | Modbus.Diagnostics => CreateModbusMessage(frame), 51 | Modbus.WriteMultipleCoils => CreateModbusMessage(frame), 52 | Modbus.WriteMultipleRegisters => CreateModbusMessage(frame), 53 | Modbus.ReadWriteMultipleRegisters => CreateModbusMessage(frame), 54 | _ => throw new ArgumentException($"Unsupported function code {functionCode}", nameof(frame)), 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/ModbusMessageImpl.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net; 5 | using ModbusRx.Data; 6 | 7 | namespace ModbusRx.Message; 8 | 9 | /// 10 | /// Class holding all implementation shared between two or more message types. 11 | /// Interfaces expose subsets of type specific implementations. 12 | /// 13 | internal class ModbusMessageImpl 14 | { 15 | public ModbusMessageImpl() 16 | { 17 | } 18 | 19 | public ModbusMessageImpl(byte slaveAddress, byte functionCode) 20 | { 21 | SlaveAddress = slaveAddress; 22 | FunctionCode = functionCode; 23 | } 24 | 25 | public byte? ByteCount { get; set; } 26 | 27 | public byte? ExceptionCode { get; set; } 28 | 29 | public ushort TransactionId { get; set; } 30 | 31 | public byte FunctionCode { get; set; } 32 | 33 | public ushort? NumberOfPoints { get; set; } 34 | 35 | public byte SlaveAddress { get; set; } 36 | 37 | public ushort? StartAddress { get; set; } 38 | 39 | public ushort? SubFunctionCode { get; set; } 40 | 41 | public IDataCollection? Data { get; set; } 42 | 43 | public byte[] MessageFrame 44 | { 45 | get 46 | { 47 | var pdu = ProtocolDataUnit; 48 | var frame = new MemoryStream(1 + pdu.Length); 49 | 50 | frame.WriteByte(SlaveAddress); 51 | frame.Write(pdu, 0, pdu.Length); 52 | 53 | return frame.ToArray(); 54 | } 55 | } 56 | 57 | public byte[] ProtocolDataUnit 58 | { 59 | get 60 | { 61 | var pdu = new List 62 | { 63 | FunctionCode 64 | }; 65 | 66 | if (ExceptionCode.HasValue) 67 | { 68 | pdu.Add(ExceptionCode.Value); 69 | } 70 | 71 | if (SubFunctionCode.HasValue) 72 | { 73 | pdu.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)SubFunctionCode.Value))); 74 | } 75 | 76 | if (StartAddress.HasValue) 77 | { 78 | pdu.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)StartAddress.Value))); 79 | } 80 | 81 | if (NumberOfPoints.HasValue) 82 | { 83 | pdu.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)NumberOfPoints.Value))); 84 | } 85 | 86 | if (ByteCount.HasValue) 87 | { 88 | pdu.Add(ByteCount.Value); 89 | } 90 | 91 | if (Data is not null) 92 | { 93 | pdu.AddRange(Data.NetworkBytes); 94 | } 95 | 96 | return pdu.ToArray(); 97 | } 98 | } 99 | 100 | public void Initialize(byte[] frame) 101 | { 102 | if (frame == null) 103 | { 104 | throw new ArgumentNullException(nameof(frame)); 105 | } 106 | 107 | if (frame.Length < Modbus.MinimumFrameSize) 108 | { 109 | var msg = $"Message frame must contain at least {Modbus.MinimumFrameSize} bytes of data."; 110 | throw new FormatException(msg); 111 | } 112 | 113 | SlaveAddress = frame[0]; 114 | FunctionCode = frame[1]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/ReadCoilsInputsRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net; 5 | 6 | namespace ModbusRx.Message; 7 | 8 | /// 9 | /// ReadCoilsInputsRequest. 10 | /// 11 | /// 12 | /// 13 | public class ReadCoilsInputsRequest : AbstractModbusMessage, IModbusRequest 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public ReadCoilsInputsRequest() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The function code. 26 | /// The slave address. 27 | /// The start address. 28 | /// The number of points. 29 | public ReadCoilsInputsRequest(byte functionCode, byte slaveAddress, ushort startAddress, ushort numberOfPoints) 30 | : base(slaveAddress, functionCode) 31 | { 32 | StartAddress = startAddress; 33 | NumberOfPoints = numberOfPoints; 34 | } 35 | 36 | /// 37 | /// Gets or sets the start address. 38 | /// 39 | /// 40 | /// The start address. 41 | /// 42 | public ushort StartAddress 43 | { 44 | get => MessageImpl.StartAddress!.Value; 45 | set => MessageImpl.StartAddress = value; 46 | } 47 | 48 | /// 49 | public override int MinimumFrameSize => 6; 50 | 51 | /// 52 | /// Gets or sets the number of points. 53 | /// 54 | /// 55 | /// The number of points. 56 | /// 57 | /// NumberOfPoints. 58 | public ushort NumberOfPoints 59 | { 60 | get => MessageImpl.NumberOfPoints!.Value; 61 | set 62 | { 63 | if (value > Modbus.MaximumDiscreteRequestResponseSize) 64 | { 65 | var msg = $"Maximum amount of data {Modbus.MaximumDiscreteRequestResponseSize} coils."; 66 | throw new ArgumentOutOfRangeException(nameof(NumberOfPoints), msg); 67 | } 68 | 69 | MessageImpl.NumberOfPoints = value; 70 | } 71 | } 72 | 73 | /// 74 | public override string ToString() => 75 | $"Read {NumberOfPoints} {(FunctionCode == Modbus.ReadCoils ? "coils" : "inputs")} starting at address {StartAddress}."; 76 | 77 | /// 78 | public void ValidateResponse(IModbusMessage response) 79 | { 80 | var typedResponse = (ReadCoilsInputsResponse)response; 81 | 82 | // best effort validation - the same response for a request for 1 vs 6 coils (same byte count) will pass validation. 83 | var expectedByteCount = (NumberOfPoints + 7) / 8; 84 | 85 | if (expectedByteCount != typedResponse?.ByteCount) 86 | { 87 | var msg = $"Unexpected byte count. Expected {expectedByteCount}, received {typedResponse?.ByteCount}."; 88 | throw new IOException(msg); 89 | } 90 | } 91 | 92 | /// 93 | protected override void InitializeUnique(byte[] frame) 94 | { 95 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 96 | NumberOfPoints = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 4)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/ReadCoilsInputsResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Unme.Common; 6 | 7 | namespace ModbusRx.Message; 8 | 9 | /// 10 | /// ReadCoilsInputsResponse. 11 | /// 12 | /// 13 | public class ReadCoilsInputsResponse : AbstractModbusMessageWithData, IModbusMessage 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public ReadCoilsInputsResponse() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The function code. 26 | /// The slave address. 27 | /// The byte count. 28 | /// The data. 29 | public ReadCoilsInputsResponse(byte functionCode, byte slaveAddress, byte byteCount, DiscreteCollection data) 30 | : base(slaveAddress, functionCode) 31 | { 32 | ByteCount = byteCount; 33 | Data = data; 34 | } 35 | 36 | /// 37 | /// Gets or sets the byte count. 38 | /// 39 | /// 40 | /// The byte count. 41 | /// 42 | public byte ByteCount 43 | { 44 | get => MessageImpl.ByteCount!.Value; 45 | set => MessageImpl.ByteCount = value; 46 | } 47 | 48 | /// 49 | public override int MinimumFrameSize => 3; 50 | 51 | /// 52 | public override string ToString() => 53 | $"Read {Data.Count} {(FunctionCode == Modbus.ReadInputs ? "inputs" : "coils")} - {Data}."; 54 | 55 | /// 56 | protected override void InitializeUnique(byte[] frame) 57 | { 58 | if (frame?.Length < 3 + frame![2]) 59 | { 60 | throw new FormatException("Message frame data segment does not contain enough bytes."); 61 | } 62 | 63 | ByteCount = frame[2]; 64 | Data = new DiscreteCollection(frame.Slice(3, ByteCount).ToArray()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/ReadHoldingInputRegistersRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | 7 | namespace ModbusRx.Message; 8 | 9 | /// 10 | /// ReadHoldingInputRegistersRequest. 11 | /// 12 | /// 13 | /// 14 | public class ReadHoldingInputRegistersRequest : AbstractModbusMessage, IModbusRequest 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public ReadHoldingInputRegistersRequest() 20 | { 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The function code. 27 | /// The slave address. 28 | /// The start address. 29 | /// The number of points. 30 | public ReadHoldingInputRegistersRequest(byte functionCode, byte slaveAddress, ushort startAddress, ushort numberOfPoints) 31 | : base(slaveAddress, functionCode) 32 | { 33 | StartAddress = startAddress; 34 | NumberOfPoints = numberOfPoints; 35 | } 36 | 37 | /// 38 | /// Gets or sets the start address. 39 | /// 40 | /// 41 | /// The start address. 42 | /// 43 | public ushort StartAddress 44 | { 45 | get => MessageImpl.StartAddress!.Value; 46 | set => MessageImpl.StartAddress = value; 47 | } 48 | 49 | /// 50 | public override int MinimumFrameSize => 6; 51 | 52 | /// 53 | /// Gets or sets the number of points. 54 | /// 55 | /// 56 | /// The number of points. 57 | /// 58 | /// NumberOfPoints. 59 | public ushort NumberOfPoints 60 | { 61 | get => MessageImpl.NumberOfPoints!.Value; 62 | set 63 | { 64 | if (value > Modbus.MaximumRegisterRequestResponseSize) 65 | { 66 | var msg = $"Maximum amount of data {Modbus.MaximumRegisterRequestResponseSize} registers."; 67 | throw new ArgumentOutOfRangeException(nameof(NumberOfPoints), msg); 68 | } 69 | 70 | MessageImpl.NumberOfPoints = value; 71 | } 72 | } 73 | 74 | /// 75 | public override string ToString() 76 | => $"Read {NumberOfPoints} {(FunctionCode == Modbus.ReadHoldingRegisters ? "holding" : "input")} registers starting at address {StartAddress}."; 77 | 78 | /// 79 | public void ValidateResponse(IModbusMessage response) 80 | { 81 | var typedResponse = response as ReadHoldingInputRegistersResponse; 82 | Debug.Assert(typedResponse is not null, "Argument response should be of type ReadHoldingInputRegistersResponse."); 83 | var expectedByteCount = NumberOfPoints * 2; 84 | 85 | if (expectedByteCount != typedResponse?.ByteCount) 86 | { 87 | var msg = $"Unexpected byte count. Expected {expectedByteCount}, received {typedResponse?.ByteCount}."; 88 | throw new IOException(msg); 89 | } 90 | } 91 | 92 | /// 93 | protected override void InitializeUnique(byte[] frame) 94 | { 95 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 96 | NumberOfPoints = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 4)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/ReadHoldingInputRegistersResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Data; 5 | using ModbusRx.Unme.Common; 6 | 7 | namespace ModbusRx.Message; 8 | 9 | /// 10 | /// ReadHoldingInputRegistersResponse. 11 | /// 12 | /// 13 | public class ReadHoldingInputRegistersResponse : AbstractModbusMessageWithData, IModbusMessage 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public ReadHoldingInputRegistersResponse() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The function code. 26 | /// The slave address. 27 | /// The data. 28 | /// data. 29 | public ReadHoldingInputRegistersResponse(byte functionCode, byte slaveAddress, RegisterCollection data) 30 | : base(slaveAddress, functionCode) 31 | { 32 | if (data == null) 33 | { 34 | throw new ArgumentNullException(nameof(data)); 35 | } 36 | 37 | ByteCount = data.ByteCount; 38 | Data = data; 39 | } 40 | 41 | /// 42 | /// Gets or sets the byte count. 43 | /// 44 | /// 45 | /// The byte count. 46 | /// 47 | public byte ByteCount 48 | { 49 | get => MessageImpl.ByteCount!.Value; 50 | set => MessageImpl.ByteCount = value; 51 | } 52 | 53 | /// 54 | public override int MinimumFrameSize => 3; 55 | 56 | /// 57 | public override string ToString() => 58 | $"Read {Data.Count} {(FunctionCode == Modbus.ReadHoldingRegisters ? "holding" : "input")} registers."; 59 | 60 | /// 61 | protected override void InitializeUnique(byte[] frame) 62 | { 63 | if (frame?.Length < MinimumFrameSize + frame![2]) 64 | { 65 | throw new FormatException("Message frame does not contain enough bytes."); 66 | } 67 | 68 | ByteCount = frame[2]; 69 | Data = new RegisterCollection(frame.Slice(3, ByteCount).ToArray()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/SlaveExceptionResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Globalization; 5 | 6 | namespace ModbusRx.Message; 7 | 8 | /// 9 | /// SlaveExceptionResponse. 10 | /// 11 | /// 12 | /// 13 | public class SlaveExceptionResponse : AbstractModbusMessage, IModbusMessage 14 | { 15 | private static readonly Dictionary _exceptionMessages = CreateExceptionMessages(); 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | public SlaveExceptionResponse() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The slave address. 28 | /// The function code. 29 | /// The exception code. 30 | public SlaveExceptionResponse(byte slaveAddress, byte functionCode, byte exceptionCode) 31 | : base(slaveAddress, functionCode) => SlaveExceptionCode = exceptionCode; 32 | 33 | /// 34 | public override int MinimumFrameSize => 3; 35 | 36 | /// 37 | /// Gets or sets the slave exception code. 38 | /// 39 | /// 40 | /// The slave exception code. 41 | /// 42 | public byte SlaveExceptionCode 43 | { 44 | get => MessageImpl.ExceptionCode!.Value; 45 | set => MessageImpl.ExceptionCode = value; 46 | } 47 | 48 | /// 49 | /// Returns a that represents the current . 50 | /// 51 | /// 52 | /// A that represents the current . 53 | /// 54 | public override string ToString() 55 | { 56 | var msg = _exceptionMessages.ContainsKey(SlaveExceptionCode) 57 | ? _exceptionMessages[SlaveExceptionCode] 58 | : Resources.Unknown; 59 | 60 | return string.Format( 61 | CultureInfo.InvariantCulture, 62 | Resources.SlaveExceptionResponseFormat, 63 | Environment.NewLine, 64 | FunctionCode, 65 | SlaveExceptionCode, 66 | msg); 67 | } 68 | 69 | internal static Dictionary CreateExceptionMessages() => 70 | new(9) 71 | { 72 | { 1, Resources.IllegalFunction }, 73 | { 2, Resources.IllegalDataAddress }, 74 | { 3, Resources.IllegalDataValue }, 75 | { 4, Resources.SlaveDeviceFailure }, 76 | { 5, Resources.Acknowlege }, 77 | { 6, Resources.SlaveDeviceBusy }, 78 | { 8, Resources.MemoryParityError }, 79 | { 10, Resources.GatewayPathUnavailable }, 80 | { 11, Resources.GatewayTargetDeviceFailedToRespond } 81 | }; 82 | 83 | /// 84 | protected override void InitializeUnique(byte[] frame) 85 | { 86 | if (frame == null) 87 | { 88 | throw new ArgumentNullException(nameof(frame)); 89 | } 90 | 91 | if (FunctionCode <= Modbus.ExceptionOffset) 92 | { 93 | throw new FormatException(Resources.SlaveExceptionResponseInvalidFunctionCode); 94 | } 95 | 96 | SlaveExceptionCode = frame[2]; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/WriteMultipleCoilsResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net; 5 | 6 | namespace ModbusRx.Message; 7 | 8 | /// 9 | /// WriteMultipleCoilsResponse. 10 | /// 11 | /// 12 | /// 13 | public class WriteMultipleCoilsResponse : AbstractModbusMessage, IModbusMessage 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public WriteMultipleCoilsResponse() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The slave address. 26 | /// The start address. 27 | /// The number of points. 28 | public WriteMultipleCoilsResponse(byte slaveAddress, ushort startAddress, ushort numberOfPoints) 29 | : base(slaveAddress, Modbus.WriteMultipleCoils) 30 | { 31 | StartAddress = startAddress; 32 | NumberOfPoints = numberOfPoints; 33 | } 34 | 35 | /// 36 | /// Gets or sets the number of points. 37 | /// 38 | /// 39 | /// The number of points. 40 | /// 41 | /// NumberOfPoints. 42 | public ushort NumberOfPoints 43 | { 44 | get => MessageImpl.NumberOfPoints!.Value; 45 | 46 | set 47 | { 48 | if (value > Modbus.MaximumDiscreteRequestResponseSize) 49 | { 50 | var msg = $"Maximum amount of data {Modbus.MaximumDiscreteRequestResponseSize} coils."; 51 | throw new ArgumentOutOfRangeException("NumberOfPoints", msg); 52 | } 53 | 54 | MessageImpl.NumberOfPoints = value; 55 | } 56 | } 57 | 58 | /// 59 | /// Gets or sets the start address. 60 | /// 61 | /// 62 | /// The start address. 63 | /// 64 | public ushort StartAddress 65 | { 66 | get => MessageImpl.StartAddress!.Value; 67 | set => MessageImpl.StartAddress = value; 68 | } 69 | 70 | /// 71 | public override int MinimumFrameSize => 6; 72 | 73 | /// 74 | public override string ToString() => 75 | $"Wrote {NumberOfPoints} coils starting at address {StartAddress}."; 76 | 77 | /// 78 | protected override void InitializeUnique(byte[] frame) 79 | { 80 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 81 | NumberOfPoints = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 4)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/WriteMultipleRegistersResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net; 5 | 6 | namespace ModbusRx.Message; 7 | 8 | /// 9 | /// WriteMultipleRegistersResponse. 10 | /// 11 | /// 12 | /// 13 | public class WriteMultipleRegistersResponse : AbstractModbusMessage, IModbusMessage 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public WriteMultipleRegistersResponse() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The slave address. 26 | /// The start address. 27 | /// The number of points. 28 | public WriteMultipleRegistersResponse(byte slaveAddress, ushort startAddress, ushort numberOfPoints) 29 | : base(slaveAddress, Modbus.WriteMultipleRegisters) 30 | { 31 | StartAddress = startAddress; 32 | NumberOfPoints = numberOfPoints; 33 | } 34 | 35 | /// 36 | /// Gets or sets the number of points. 37 | /// 38 | /// 39 | /// The number of points. 40 | /// 41 | /// NumberOfPoints. 42 | public ushort NumberOfPoints 43 | { 44 | get => MessageImpl.NumberOfPoints!.Value; 45 | 46 | set 47 | { 48 | if (value > Modbus.MaximumRegisterRequestResponseSize) 49 | { 50 | var msg = $"Maximum amount of data {Modbus.MaximumRegisterRequestResponseSize} registers."; 51 | throw new ArgumentOutOfRangeException(nameof(NumberOfPoints), msg); 52 | } 53 | 54 | MessageImpl.NumberOfPoints = value; 55 | } 56 | } 57 | 58 | /// 59 | /// Gets or sets the start address. 60 | /// 61 | /// 62 | /// The start address. 63 | /// 64 | public ushort StartAddress 65 | { 66 | get => MessageImpl.StartAddress!.Value; 67 | set => MessageImpl.StartAddress = value; 68 | } 69 | 70 | /// 71 | /// Gets the minimum size of the frame. 72 | /// 73 | /// 74 | /// The minimum size of the frame. 75 | /// 76 | public override int MinimumFrameSize => 6; 77 | 78 | /// 79 | /// Converts to string. 80 | /// 81 | /// 82 | /// A that represents this instance. 83 | /// 84 | public override string ToString() => 85 | $"Wrote {NumberOfPoints} holding registers starting at address {StartAddress}."; 86 | 87 | /// 88 | /// Initializes the unique. 89 | /// 90 | /// The frame. 91 | protected override void InitializeUnique(byte[] frame) 92 | { 93 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 94 | NumberOfPoints = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 4)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/WriteSingleCoilRequestResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | using ModbusRx.Data; 7 | using ModbusRx.Unme.Common; 8 | 9 | namespace ModbusRx.Message; 10 | 11 | /// 12 | /// WriteSingleCoilRequestResponse. 13 | /// 14 | public class WriteSingleCoilRequestResponse : AbstractModbusMessageWithData, IModbusRequest 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public WriteSingleCoilRequestResponse() 20 | { 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The slave address. 27 | /// The start address. 28 | /// if set to true [coil state]. 29 | public WriteSingleCoilRequestResponse(byte slaveAddress, ushort startAddress, bool coilState) 30 | : base(slaveAddress, Modbus.WriteSingleCoil) 31 | { 32 | StartAddress = startAddress; 33 | Data = new RegisterCollection(coilState ? Modbus.CoilOn : Modbus.CoilOff); 34 | } 35 | 36 | /// 37 | /// Gets the minimum size of the frame. 38 | /// 39 | /// 40 | /// The minimum size of the frame. 41 | /// 42 | public override int MinimumFrameSize => 6; 43 | 44 | /// 45 | /// Gets or sets the start address. 46 | /// 47 | /// 48 | /// The start address. 49 | /// 50 | public ushort StartAddress 51 | { 52 | get => MessageImpl.StartAddress!.Value; 53 | set => MessageImpl.StartAddress = value; 54 | } 55 | 56 | /// 57 | /// Converts to string. 58 | /// 59 | /// 60 | /// A that represents this instance. 61 | /// 62 | public override string ToString() 63 | { 64 | Debug.Assert(Data is not null, "Argument Data cannot be null."); 65 | Debug.Assert(Data?.Count == 1, "Data should have a count of 1."); 66 | 67 | var msg = $"Write single coil {(Data.First() == Modbus.CoilOn ? 1 : 0)} at address {StartAddress}."; 68 | return msg; 69 | } 70 | 71 | /// 72 | /// Validate the specified response against the current request. 73 | /// 74 | /// The Modbus Message. 75 | public void ValidateResponse(IModbusMessage response) 76 | { 77 | var typedResponse = (WriteSingleCoilRequestResponse)response; 78 | 79 | if (StartAddress != typedResponse?.StartAddress) 80 | { 81 | var msg = $"Unexpected start address in response. Expected {StartAddress}, received {typedResponse?.StartAddress}."; 82 | throw new IOException(msg); 83 | } 84 | 85 | if (Data.First() != typedResponse.Data.First()) 86 | { 87 | var msg = $"Unexpected data in response. Expected {Data.First()}, received {typedResponse.Data.First()}."; 88 | throw new IOException(msg); 89 | } 90 | } 91 | 92 | /// 93 | /// Initializes the unique. 94 | /// 95 | /// The frame. 96 | protected override void InitializeUnique(byte[] frame) 97 | { 98 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 99 | Data = new RegisterCollection(frame.Slice(4, 2).ToArray()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/ModbusRx/Message/WriteSingleRegisterRequestResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | using ModbusRx.Data; 7 | 8 | namespace ModbusRx.Message; 9 | 10 | /// 11 | /// WriteSingleRegisterRequestResponse. 12 | /// 13 | public class WriteSingleRegisterRequestResponse : AbstractModbusMessageWithData, IModbusRequest 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public WriteSingleRegisterRequestResponse() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The slave address. 26 | /// The start address. 27 | /// The register value. 28 | public WriteSingleRegisterRequestResponse(byte slaveAddress, ushort startAddress, ushort registerValue) 29 | : base(slaveAddress, Modbus.WriteSingleRegister) 30 | { 31 | StartAddress = startAddress; 32 | Data = new RegisterCollection(registerValue); 33 | } 34 | 35 | /// 36 | /// Gets the minimum size of the frame. 37 | /// 38 | /// 39 | /// The minimum size of the frame. 40 | /// 41 | public override int MinimumFrameSize => 6; 42 | 43 | /// 44 | /// Gets or sets the start address. 45 | /// 46 | /// 47 | /// The start address. 48 | /// 49 | public ushort StartAddress 50 | { 51 | get => MessageImpl.StartAddress!.Value; 52 | set => MessageImpl.StartAddress = value; 53 | } 54 | 55 | /// 56 | /// Converts to string. 57 | /// 58 | /// 59 | /// A that represents this instance. 60 | /// 61 | public override string ToString() 62 | { 63 | Debug.Assert(Data is not null, "Argument Data cannot be null."); 64 | Debug.Assert(Data?.Count == 1, "Data should have a count of 1."); 65 | 66 | var msg = $"Write single holding register {Data![0]} at address {StartAddress}."; 67 | return msg; 68 | } 69 | 70 | /// 71 | /// Validate the specified response against the current request. 72 | /// 73 | /// The Modbus message. 74 | public void ValidateResponse(IModbusMessage response) 75 | { 76 | var typedResponse = (WriteSingleRegisterRequestResponse)response; 77 | 78 | if (StartAddress != typedResponse?.StartAddress) 79 | { 80 | var msg = $"Unexpected start address in response. Expected {StartAddress}, received {typedResponse?.StartAddress}."; 81 | throw new IOException(msg); 82 | } 83 | 84 | if (Data.First() != typedResponse.Data.First()) 85 | { 86 | var msg = $"Unexpected data in response. Expected {Data.First()}, received {typedResponse.Data.First()}."; 87 | throw new IOException(msg); 88 | } 89 | } 90 | 91 | /// 92 | /// Initializes the unique. 93 | /// 94 | /// The frame. 95 | protected override void InitializeUnique(byte[] frame) 96 | { 97 | StartAddress = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 2)); 98 | Data = new RegisterCollection((ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 4))); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ModbusRx/Modbus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx; 5 | 6 | /// 7 | /// Defines constants related to the Modbus protocol. 8 | /// 9 | internal static class Modbus 10 | { 11 | // supported function codes 12 | public const byte ReadCoils = 1; 13 | public const byte ReadInputs = 2; 14 | public const byte ReadHoldingRegisters = 3; 15 | public const byte ReadInputRegisters = 4; 16 | public const byte WriteSingleCoil = 5; 17 | public const byte WriteSingleRegister = 6; 18 | public const byte Diagnostics = 8; 19 | public const ushort DiagnosticsReturnQueryData = 0; 20 | public const byte WriteMultipleCoils = 15; 21 | public const byte WriteMultipleRegisters = 16; 22 | public const byte ReadWriteMultipleRegisters = 23; 23 | 24 | public const int MaximumDiscreteRequestResponseSize = 2040; 25 | public const int MaximumRegisterRequestResponseSize = 127; 26 | 27 | // modbus slave exception offset that is added to the function code, to flag an exception 28 | public const byte ExceptionOffset = 128; 29 | 30 | // modbus slave exception codes 31 | public const byte IllegalFunction = 1; 32 | public const byte IllegalDataAddress = 2; 33 | public const byte Acknowledge = 5; 34 | public const byte SlaveDeviceBusy = 6; 35 | 36 | // default setting for number of retries for IO operations 37 | public const int DefaultRetries = 3; 38 | 39 | // default number of milliseconds to wait after encountering an ACKNOWLEGE or SLAVE DEVIC BUSY slave exception response. 40 | public const int DefaultWaitToRetryMilliseconds = 250; 41 | 42 | // default setting for IO timeouts in milliseconds 43 | public const int DefaultTimeout = 1000; 44 | 45 | // smallest supported message frame size (sans checksum) 46 | public const int MinimumFrameSize = 2; 47 | 48 | public const ushort CoilOn = 0xFF00; 49 | public const ushort CoilOff = 0x0000; 50 | 51 | // IP slaves should be addressed by IP 52 | public const byte DefaultIpSlaveUnitId = 0; 53 | 54 | // An existing connection was forcibly closed by the remote host 55 | public const int ConnectionResetByPeer = 10054; 56 | 57 | // Existing socket connection is being closed 58 | public const int WSACancelBlockingCall = 10004; 59 | 60 | // used by the ASCII tranport to indicate end of message 61 | public const string NewLine = "\r\n"; 62 | } 63 | -------------------------------------------------------------------------------- /src/ModbusRx/ModbusRx.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net6.0;net7.0;net8.0 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>ModbusRx.IntegrationTests 19 | 20 | 21 | 22 | <_Parameter1>ModbusRx.DriverTest 23 | 24 | 25 | 26 | <_Parameter1>ModbusRx.UnitTests 27 | 28 | 29 | 30 | <_Parameter1>DynamicProxyGenAssembly2 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/ModbusRx/Reactive/ModbusCommunicationException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Reactive 5 | { 6 | /// 7 | /// Modbus Communication Exception. 8 | /// 9 | /// 10 | [Serializable] 11 | public class ModbusCommunicationException : Exception 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public ModbusCommunicationException() 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The message that describes the error. 24 | public ModbusCommunicationException(string message) 25 | : base(message) 26 | { 27 | } 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The message. 33 | /// The inner. 34 | public ModbusCommunicationException(string message, Exception inner) 35 | : base(message, inner) 36 | { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ModbusRx/Reactive/ModbusSerialSlaveExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reactive.Linq; 5 | using ModbusRx.Data; 6 | using ModbusRx.Device; 7 | using ModbusRx.Message; 8 | 9 | namespace ModbusRx.Reactive 10 | { 11 | /// 12 | /// ModbusSerialSlaveExtensions. 13 | /// 14 | public static class ModbusSerialSlaveExtensions 15 | { 16 | /// 17 | /// Writes the Input Registers. 18 | /// 19 | /// The slave. 20 | /// The start address. 21 | /// The values to write. 22 | /// 23 | /// Observable ModbusTcpSlave. 24 | /// 25 | public static IObservable WriteInputRegisters(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 26 | { 27 | slave.CombineLatest( 28 | valuesToWrite, (slave, data) => (slave, data)) 29 | .Subscribe(source => ModbusSlave.WriteMultipleRegisters(new WriteMultipleRegistersRequest(1, startAddress, new RegisterCollection(source.data)), source.slave.DataStore, source.slave.DataStore.InputRegisters)); 30 | return slave; 31 | } 32 | 33 | /// 34 | /// Writes the holding registers. 35 | /// 36 | /// The slave. 37 | /// The start address. 38 | /// The values to write. 39 | /// 40 | /// Observable ModbusTcpSlave. 41 | /// 42 | public static IObservable WriteHoldingRegisters(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 43 | { 44 | slave.CombineLatest( 45 | valuesToWrite, (slave, data) => (slave, data)) 46 | .Subscribe(source => ModbusSlave.WriteMultipleRegisters(new WriteMultipleRegistersRequest(1, startAddress, new RegisterCollection(source.data)), source.slave.DataStore, source.slave.DataStore.HoldingRegisters)); 47 | return slave; 48 | } 49 | 50 | /// 51 | /// Writes the coil discretes. 52 | /// 53 | /// The slave. 54 | /// The start address. 55 | /// The values to write. 56 | /// Observable ModbusTcpSlave. 57 | public static IObservable WriteCoilDiscretes(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 58 | { 59 | slave.CombineLatest( 60 | valuesToWrite, (slave, data) => (slave, data)) 61 | .Subscribe(source => ModbusSlave.WriteMultipleCoils(new WriteMultipleCoilsRequest(1, startAddress, new DiscreteCollection(source.data)), source.slave.DataStore, source.slave.DataStore.CoilDiscretes)); 62 | return slave; 63 | } 64 | 65 | /// 66 | /// Writes the input discretes. 67 | /// 68 | /// The slave. 69 | /// The start address. 70 | /// The values to write. 71 | /// Observable ModbusTcpSlave. 72 | public static IObservable WriteInputDiscretes(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 73 | { 74 | slave.CombineLatest( 75 | valuesToWrite, (slave, data) => (slave, data)) 76 | .Subscribe(source => ModbusSlave.WriteMultipleCoils(new WriteMultipleCoilsRequest(1, startAddress, new DiscreteCollection(source.data)), source.slave.DataStore, source.slave.DataStore.InputDiscretes)); 77 | return slave; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ModbusRx/Reactive/ModbusTcpSlaveExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reactive.Linq; 5 | using ModbusRx.Data; 6 | using ModbusRx.Device; 7 | using ModbusRx.Message; 8 | 9 | namespace ModbusRx.Reactive 10 | { 11 | /// 12 | /// ModbusTcpSlaveExtensions. 13 | /// 14 | public static class ModbusTcpSlaveExtensions 15 | { 16 | /// 17 | /// Writes the Input Registers. 18 | /// 19 | /// The slave. 20 | /// The start address. 21 | /// The values to write. 22 | /// 23 | /// Observable ModbusTcpSlave. 24 | /// 25 | public static IObservable WriteInputRegisters(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 26 | { 27 | slave.CombineLatest( 28 | valuesToWrite, (slave, data) => (slave, data)) 29 | .Subscribe(source => ModbusSlave.WriteMultipleRegisters(new WriteMultipleRegistersRequest(1, startAddress, new RegisterCollection(source.data)), source.slave.DataStore, source.slave.DataStore.InputRegisters)); 30 | return slave; 31 | } 32 | 33 | /// 34 | /// Writes the holding registers. 35 | /// 36 | /// The slave. 37 | /// The start address. 38 | /// The values to write. 39 | /// 40 | /// Observable ModbusTcpSlave. 41 | /// 42 | public static IObservable WriteHoldingRegisters(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 43 | { 44 | slave.CombineLatest( 45 | valuesToWrite, (slave, data) => (slave, data)) 46 | .Subscribe(source => ModbusSlave.WriteMultipleRegisters(new WriteMultipleRegistersRequest(1, startAddress, new RegisterCollection(source.data)), source.slave.DataStore, source.slave.DataStore.HoldingRegisters)); 47 | return slave; 48 | } 49 | 50 | /// 51 | /// Writes the coil discretes. 52 | /// 53 | /// The slave. 54 | /// The start address. 55 | /// The values to write. 56 | /// Observable ModbusTcpSlave. 57 | public static IObservable WriteCoilDiscretes(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 58 | { 59 | slave.CombineLatest( 60 | valuesToWrite, (slave, data) => (slave, data)) 61 | .Subscribe(source => ModbusSlave.WriteMultipleCoils(new WriteMultipleCoilsRequest(1, startAddress, new DiscreteCollection(source.data)), source.slave.DataStore, source.slave.DataStore.CoilDiscretes)); 62 | return slave; 63 | } 64 | 65 | /// 66 | /// Writes the input discretes. 67 | /// 68 | /// The slave. 69 | /// The start address. 70 | /// The values to write. 71 | /// Observable ModbusTcpSlave. 72 | public static IObservable WriteInputDiscretes(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 73 | { 74 | slave.CombineLatest( 75 | valuesToWrite, (slave, data) => (slave, data)) 76 | .Subscribe(source => ModbusSlave.WriteMultipleCoils(new WriteMultipleCoilsRequest(1, startAddress, new DiscreteCollection(source.data)), source.slave.DataStore, source.slave.DataStore.InputDiscretes)); 77 | return slave; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ModbusRx/Reactive/ModbusUdpSlaveExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reactive.Linq; 5 | using ModbusRx.Data; 6 | using ModbusRx.Device; 7 | using ModbusRx.Message; 8 | 9 | namespace ModbusRx.Reactive 10 | { 11 | /// 12 | /// ModbusUdpSlaveExtensions. 13 | /// 14 | public static class ModbusUdpSlaveExtensions 15 | { 16 | /// 17 | /// Writes the Input Registers. 18 | /// 19 | /// The slave. 20 | /// The start address. 21 | /// The values to write. 22 | /// 23 | /// Observable ModbusTcpSlave. 24 | /// 25 | public static IObservable WriteInputRegisters(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 26 | { 27 | slave.CombineLatest( 28 | valuesToWrite, (slave, data) => (slave, data)) 29 | .Subscribe(source => ModbusSlave.WriteMultipleRegisters(new WriteMultipleRegistersRequest(1, startAddress, new RegisterCollection(source.data)), source.slave.DataStore, source.slave.DataStore.InputRegisters)); 30 | return slave; 31 | } 32 | 33 | /// 34 | /// Writes the holding registers. 35 | /// 36 | /// The slave. 37 | /// The start address. 38 | /// The values to write. 39 | /// 40 | /// Observable ModbusTcpSlave. 41 | /// 42 | public static IObservable WriteHoldingRegisters(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 43 | { 44 | slave.CombineLatest( 45 | valuesToWrite, (slave, data) => (slave, data)) 46 | .Subscribe(source => ModbusSlave.WriteMultipleRegisters(new WriteMultipleRegistersRequest(1, startAddress, new RegisterCollection(source.data)), source.slave.DataStore, source.slave.DataStore.HoldingRegisters)); 47 | return slave; 48 | } 49 | 50 | /// 51 | /// Writes the coil discretes. 52 | /// 53 | /// The slave. 54 | /// The start address. 55 | /// The values to write. 56 | /// Observable ModbusTcpSlave. 57 | public static IObservable WriteCoilDiscretes(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 58 | { 59 | slave.CombineLatest( 60 | valuesToWrite, (slave, data) => (slave, data)) 61 | .Subscribe(source => ModbusSlave.WriteMultipleCoils(new WriteMultipleCoilsRequest(1, startAddress, new DiscreteCollection(source.data)), source.slave.DataStore, source.slave.DataStore.CoilDiscretes)); 62 | return slave; 63 | } 64 | 65 | /// 66 | /// Writes the input discretes. 67 | /// 68 | /// The slave. 69 | /// The start address. 70 | /// The values to write. 71 | /// Observable ModbusTcpSlave. 72 | public static IObservable WriteInputDiscretes(this IObservable slave, ushort startAddress, IObservable valuesToWrite) 73 | { 74 | slave.CombineLatest( 75 | valuesToWrite, (slave, data) => (slave, data)) 76 | .Subscribe(source => ModbusSlave.WriteMultipleCoils(new WriteMultipleCoilsRequest(1, startAddress, new DiscreteCollection(source.data)), source.slave.DataStore, source.slave.DataStore.InputDiscretes)); 77 | return slave; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ModbusRx/SlaveException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using ModbusRx.Message; 5 | 6 | namespace ModbusRx; 7 | 8 | /// 9 | /// Represents slave errors that occur during communication. 10 | /// 11 | public class SlaveException : Exception 12 | { 13 | private readonly SlaveExceptionResponse? _slaveExceptionResponse; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public SlaveException() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The message. 26 | public SlaveException(string message) 27 | : base(message) 28 | { 29 | } 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | /// The message. 35 | /// The inner exception. 36 | public SlaveException(string message, Exception innerException) 37 | : base(message, innerException) 38 | { 39 | } 40 | 41 | internal SlaveException(SlaveExceptionResponse slaveExceptionResponse) => 42 | _slaveExceptionResponse = slaveExceptionResponse; 43 | 44 | internal SlaveException(string message, SlaveExceptionResponse slaveExceptionResponse) 45 | : base(message) => _slaveExceptionResponse = slaveExceptionResponse; 46 | 47 | /// 48 | /// Gets a message that describes the current exception. 49 | /// 50 | /// 51 | /// The error message that explains the reason for the exception, or an empty string. 52 | /// 53 | public override string Message 54 | { 55 | get 56 | { 57 | var responseString = _slaveExceptionResponse is not null ? string.Concat(Environment.NewLine, _slaveExceptionResponse) : string.Empty; 58 | return string.Concat(base.Message, responseString); 59 | } 60 | } 61 | 62 | /// 63 | /// Gets the response function code that caused the exception to occur, or 0. 64 | /// 65 | /// The function code. 66 | public byte FunctionCode => 67 | _slaveExceptionResponse?.FunctionCode ?? 0; 68 | 69 | /// 70 | /// Gets the slave exception code, or 0. 71 | /// 72 | /// The slave exception code. 73 | public byte SlaveExceptionCode => 74 | _slaveExceptionResponse?.SlaveExceptionCode ?? 0; 75 | 76 | /// 77 | /// Gets the slave address, or 0. 78 | /// 79 | /// The slave address. 80 | public byte SlaveAddress => 81 | _slaveExceptionResponse?.SlaveAddress ?? 0; 82 | } 83 | -------------------------------------------------------------------------------- /src/ModbusRx/Unme.Common/DisposableUtility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Unme.Common; 5 | 6 | internal static class DisposableUtility 7 | { 8 | public static void Dispose(ref T? item) 9 | where T : class, IDisposable 10 | { 11 | if (item is null) 12 | { 13 | return; 14 | } 15 | 16 | item.Dispose(); 17 | item = default; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ModbusRx/Unme.Common/SequenceUtility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Unme.Common; 5 | 6 | internal static class SequenceUtility 7 | { 8 | public static IEnumerable Slice(this IEnumerable source, int startIndex, int size) 9 | { 10 | if (source == null) 11 | { 12 | throw new ArgumentNullException(nameof(source)); 13 | } 14 | 15 | var enumerable = source as T[] ?? source.ToArray(); 16 | var num = enumerable.Length; 17 | 18 | if (startIndex < 0 || num < startIndex) 19 | { 20 | throw new ArgumentOutOfRangeException(nameof(startIndex)); 21 | } 22 | 23 | if (size < 0 || startIndex + size > num) 24 | { 25 | throw new ArgumentOutOfRangeException(nameof(size)); 26 | } 27 | 28 | return enumerable.Skip(startIndex).Take(size); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ModbusRx/Utility/DiscriminatedUnion.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Pulman. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace ModbusRx.Utility; 5 | 6 | /// 7 | /// Possible options for DiscriminatedUnion type. 8 | /// 9 | public enum DiscriminatedUnionOption 10 | { 11 | /// 12 | /// Option A. 13 | /// 14 | A, 15 | 16 | /// 17 | /// Option B. 18 | /// 19 | B, 20 | } 21 | 22 | /// 23 | /// A data type that can store one of two possible strongly typed options. 24 | /// 25 | /// The type of option A. 26 | /// The type of option B. 27 | #pragma warning disable SA1402 // File may only contain a single type 28 | public class DiscriminatedUnion 29 | #pragma warning restore SA1402 // File may only contain a single type 30 | { 31 | private TA? _optionA; 32 | private TB? _optionB; 33 | 34 | /// 35 | /// Gets the value of option A. 36 | /// 37 | public TA? A 38 | { 39 | get 40 | { 41 | if (Option != DiscriminatedUnionOption.A) 42 | { 43 | var msg = $"{DiscriminatedUnionOption.A} is not a valid option for this discriminated union instance."; 44 | throw new InvalidOperationException(msg); 45 | } 46 | 47 | return _optionA; 48 | } 49 | } 50 | 51 | /// 52 | /// Gets the value of option B. 53 | /// 54 | public TB? B 55 | { 56 | get 57 | { 58 | if (Option != DiscriminatedUnionOption.B) 59 | { 60 | var msg = $"{DiscriminatedUnionOption.B} is not a valid option for this discriminated union instance."; 61 | throw new InvalidOperationException(msg); 62 | } 63 | 64 | return _optionB; 65 | } 66 | } 67 | 68 | /// 69 | /// Gets the discriminated value option set for this instance. 70 | /// 71 | public DiscriminatedUnionOption Option { get; private set; } 72 | 73 | /// 74 | /// Factory method for creating DiscriminatedUnion with option A set. 75 | /// 76 | /// a. 77 | /// A DiscriminatedUnion. 78 | public static DiscriminatedUnion CreateA(TA a) => 79 | new() { Option = DiscriminatedUnionOption.A, _optionA = a }; 80 | 81 | /// 82 | /// Factory method for creating DiscriminatedUnion with option B set. 83 | /// 84 | /// The b. 85 | /// A DiscriminatedUnion. 86 | public static DiscriminatedUnion CreateB(TB b) => 87 | new() { Option = DiscriminatedUnionOption.B, _optionB = b }; 88 | 89 | /// 90 | /// Returns a that represents the current . 91 | /// 92 | /// 93 | /// A that represents the current . 94 | /// 95 | public override string? ToString() => 96 | Option switch 97 | { 98 | DiscriminatedUnionOption.A => A?.ToString(), 99 | DiscriminatedUnionOption.B => B?.ToString(), 100 | _ => null, 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "indentation": { 5 | "useTabs": false, 6 | "indentationSize": 4 7 | }, 8 | "documentationRules": { 9 | "documentExposedElements": true, 10 | "documentInternalElements": false, 11 | "documentPrivateElements": false, 12 | "documentInterfaces": true, 13 | "documentPrivateFields": false, 14 | "documentationCulture": "en-US", 15 | "companyName": "Chris Pulman", 16 | "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.", 17 | "variables": { 18 | "licenseName": "MIT", 19 | "licenseFile": "LICENSE" 20 | }, 21 | "xmlHeader": false 22 | }, 23 | "layoutRules": { 24 | "newlineAtEndOfFile": "allow", 25 | "allowConsecutiveUsings": true 26 | }, 27 | "maintainabilityRules": { 28 | "topLevelTypes": [ 29 | "class", 30 | "interface", 31 | "struct", 32 | "enum", 33 | "delegate" 34 | ] 35 | }, 36 | "orderingRules": { 37 | "usingDirectivesPlacement": "outsideNamespace", 38 | "systemUsingDirectivesFirst": true 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------