Fixes for SerialRtuProxy

- Adding UnitTests
- Fixing some bugs
- Updating UnitTest dependencies
This commit is contained in:
2025-01-27 17:26:56 +01:00
parent 6fc7cfda9a
commit 05759f8e12
9 changed files with 2163 additions and 1206 deletions

View File

@@ -20,10 +20,10 @@ build-debug:
rules: rules:
- if: $CI_COMMIT_TAG == null - if: $CI_COMMIT_TAG == null
script: script:
- dotnet restore --no-cache --force
- dotnet build -c Debug --nologo --no-restore --no-incremental
- shopt -s globstar - shopt -s globstar
- mkdir ./artifacts - mkdir ./artifacts
- dotnet restore --no-cache --force
- dotnet build -c Debug --nologo --no-restore --no-incremental
- mv ./**/*.nupkg ./artifacts/ - mv ./**/*.nupkg ./artifacts/
- mv ./**/*.snupkg ./artifacts/ - mv ./**/*.snupkg ./artifacts/
artifacts: artifacts:
@@ -42,10 +42,20 @@ test-debug:
- 64bit - 64bit
rules: rules:
- if: $CI_COMMIT_TAG == null - if: $CI_COMMIT_TAG == null
coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/' coverage: /Branch coverage[\s\S].+%/
before_script:
- dotnet tool install dotnet-reportgenerator-globaltool --tool-path /dotnet-tools
script: script:
- dotnet restore --no-cache --force - dotnet test -c Debug --nologo /p:CoverletOutputFormat=Cobertura
- dotnet test -c Debug --nologo --no-restore - /dotnet-tools/reportgenerator "-reports:${CI_PROJECT_DIR}/**/coverage.cobertura.xml" "-targetdir:/reports" -reportType:TextSummary
after_script:
- cat /reports/Summary.txt
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: ./**/coverage.cobertura.xml
deploy-debug: deploy-debug:
stage: deploy stage: deploy
@@ -72,17 +82,17 @@ build-release:
rules: rules:
- if: $CI_COMMIT_TAG != null - if: $CI_COMMIT_TAG != null
script: script:
- dotnet restore --no-cache --force
- dotnet build -c Release --nologo --no-restore --no-incremental
- shopt -s globstar - shopt -s globstar
- mkdir ./artifacts - mkdir ./artifacts
- dotnet restore --no-cache --force
- dotnet build -c Release --nologo --no-restore --no-incremental
- mv ./**/*.nupkg ./artifacts/ - mv ./**/*.nupkg ./artifacts/
- mv ./**/*.snupkg ./artifacts/ - mv ./**/*.snupkg ./artifacts/
artifacts: artifacts:
paths: paths:
- artifacts/*.nupkg - artifacts/*.nupkg
- artifacts/*.snupkg - artifacts/*.snupkg
expire_in: 1 days expire_in: 7 days
test-release: test-release:
stage: test stage: test
@@ -94,10 +104,20 @@ test-release:
- amd64 - amd64
rules: rules:
- if: $CI_COMMIT_TAG != null - if: $CI_COMMIT_TAG != null
coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/' coverage: /Branch coverage[\s\S].+%/
before_script:
- dotnet tool install dotnet-reportgenerator-globaltool --tool-path /dotnet-tools
script: script:
- dotnet restore --no-cache --force - dotnet test -c Release --nologo /p:CoverletOutputFormat=Cobertura
- dotnet test -c Release --nologo --no-restore - /dotnet-tools/reportgenerator "-reports:${CI_PROJECT_DIR}/**/coverage.cobertura.xml" "-targetdir:/reports" -reportType:TextSummary
after_script:
- cat /reports/Summary.txt
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: ./**/coverage.cobertura.xml
deploy-release: deploy-release:
stage: deploy stage: deploy

View File

@@ -48,7 +48,8 @@ namespace AMWD.Protocols.Modbus.Common.Utils
#region Properties #region Properties
internal VirtualProtocol TypedProtocol => Protocol as VirtualProtocol; internal VirtualProtocol TypedProtocol
=> Protocol as VirtualProtocol;
#endregion Properties #endregion Properties

View File

@@ -618,7 +618,7 @@ namespace AMWD.Protocols.Modbus.Serial
return null; return null;
var responseBytes = new List<byte>(); var responseBytes = new List<byte>();
responseBytes.AddRange(requestBytes.Take(8)); responseBytes.AddRange(requestBytes.Take(2));
ushort firstAddress = requestBytes.GetBigEndianUInt16(2); ushort firstAddress = requestBytes.GetBigEndianUInt16(2);
ushort count = requestBytes.GetBigEndianUInt16(4); ushort count = requestBytes.GetBigEndianUInt16(4);
@@ -646,6 +646,7 @@ namespace AMWD.Protocols.Modbus.Serial
HighByte = requestBytes[baseOffset + i * 2], HighByte = requestBytes[baseOffset + i * 2],
LowByte = requestBytes[baseOffset + i * 2 + 1] LowByte = requestBytes[baseOffset + i * 2 + 1]
}); });
}
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[0], list, cancellationToken); bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[0], list, cancellationToken);
if (isSuccess) if (isSuccess)
@@ -659,7 +660,6 @@ namespace AMWD.Protocols.Modbus.Serial
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure); responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
} }
} }
}
catch catch
{ {
responseBytes[1] |= 0x80; responseBytes[1] |= 0x80;
@@ -671,6 +671,9 @@ namespace AMWD.Protocols.Modbus.Serial
private async Task<byte[]> HandleEncapsulatedInterfaceAsync(byte[] requestBytes, CancellationToken cancellationToken) private async Task<byte[]> HandleEncapsulatedInterfaceAsync(byte[] requestBytes, CancellationToken cancellationToken)
{ {
if (requestBytes.Length < 7)
return null;
var responseBytes = new List<byte>(); var responseBytes = new List<byte>();
responseBytes.AddRange(requestBytes.Take(2)); responseBytes.AddRange(requestBytes.Take(2));
@@ -702,7 +705,7 @@ namespace AMWD.Protocols.Modbus.Serial
try try
{ {
var res = await Client.ReadDeviceIdentificationAsync(requestBytes[6], category, firstObject, cancellationToken); var deviceInfo = await Client.ReadDeviceIdentificationAsync(requestBytes[0], category, firstObject, cancellationToken);
var bodyBytes = new List<byte>(); var bodyBytes = new List<byte>();
@@ -711,31 +714,20 @@ namespace AMWD.Protocols.Modbus.Serial
// Conformity // Conformity
bodyBytes.Add((byte)category); bodyBytes.Add((byte)category);
if (res.IsIndividualAccessAllowed) if (deviceInfo.IsIndividualAccessAllowed)
bodyBytes[2] |= 0x80; bodyBytes[2] |= 0x80;
// More, NextId, NumberOfObjects // More, NextId, NumberOfObjects
bodyBytes.AddRange(new byte[3]); bodyBytes.AddRange(new byte[3]);
int maxObjectId; int maxObjectId = category switch
switch (category)
{ {
case ModbusDeviceIdentificationCategory.Basic: ModbusDeviceIdentificationCategory.Basic => 0x02,
maxObjectId = 0x02; ModbusDeviceIdentificationCategory.Regular => 0x06,
break; ModbusDeviceIdentificationCategory.Extended => 0xFF,
// Individual
case ModbusDeviceIdentificationCategory.Regular: _ => requestBytes[4],
maxObjectId = 0x06; };
break;
case ModbusDeviceIdentificationCategory.Extended:
maxObjectId = 0xFF;
break;
default: // Individual
maxObjectId = requestBytes[4];
break;
}
byte numberOfObjects = 0; byte numberOfObjects = 0;
for (int i = requestBytes[4]; i <= maxObjectId; i++) for (int i = requestBytes[4]; i <= maxObjectId; i++)
@@ -744,7 +736,7 @@ namespace AMWD.Protocols.Modbus.Serial
if (0x07 <= i && i <= 0x7F) if (0x07 <= i && i <= 0x7F)
continue; continue;
byte[] objBytes = GetDeviceObject((byte)i, res); byte[] objBytes = GetDeviceObject((byte)i, deviceInfo);
// We need to split the response if it would exceed the max ADU size // We need to split the response if it would exceed the max ADU size
if (responseBytes.Count + bodyBytes.Count + objBytes.Length > RtuProtocol.MAX_ADU_LENGTH) if (responseBytes.Count + bodyBytes.Count + objBytes.Length > RtuProtocol.MAX_ADU_LENGTH)
@@ -754,7 +746,8 @@ namespace AMWD.Protocols.Modbus.Serial
bodyBytes[5] = numberOfObjects; bodyBytes[5] = numberOfObjects;
responseBytes.AddRange(bodyBytes); responseBytes.AddRange(bodyBytes);
return [.. responseBytes];
return ReturnResponse(responseBytes);
} }
bodyBytes.AddRange(objBytes); bodyBytes.AddRange(objBytes);
@@ -782,7 +775,7 @@ namespace AMWD.Protocols.Modbus.Serial
{ {
case ModbusDeviceIdentificationObject.VendorName: case ModbusDeviceIdentificationObject.VendorName:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.VendorName); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.VendorName ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }
@@ -790,7 +783,7 @@ namespace AMWD.Protocols.Modbus.Serial
case ModbusDeviceIdentificationObject.ProductCode: case ModbusDeviceIdentificationObject.ProductCode:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.ProductCode); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.ProductCode ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }
@@ -798,7 +791,7 @@ namespace AMWD.Protocols.Modbus.Serial
case ModbusDeviceIdentificationObject.MajorMinorRevision: case ModbusDeviceIdentificationObject.MajorMinorRevision:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.MajorMinorRevision); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.MajorMinorRevision ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }
@@ -806,7 +799,7 @@ namespace AMWD.Protocols.Modbus.Serial
case ModbusDeviceIdentificationObject.VendorUrl: case ModbusDeviceIdentificationObject.VendorUrl:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.VendorUrl); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.VendorUrl ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }
@@ -814,7 +807,7 @@ namespace AMWD.Protocols.Modbus.Serial
case ModbusDeviceIdentificationObject.ProductName: case ModbusDeviceIdentificationObject.ProductName:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.ProductName); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.ProductName ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }
@@ -822,7 +815,7 @@ namespace AMWD.Protocols.Modbus.Serial
case ModbusDeviceIdentificationObject.ModelName: case ModbusDeviceIdentificationObject.ModelName:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.ModelName); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.ModelName ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }
@@ -830,7 +823,7 @@ namespace AMWD.Protocols.Modbus.Serial
case ModbusDeviceIdentificationObject.UserApplicationName: case ModbusDeviceIdentificationObject.UserApplicationName:
{ {
byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.UserApplicationName); byte[] bytes = Encoding.UTF8.GetBytes(deviceIdentification.UserApplicationName ?? "");
result.Add((byte)bytes.Length); result.Add((byte)bytes.Length);
result.AddRange(bytes); result.AddRange(bytes);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -24,9 +24,9 @@ namespace AMWD.Protocols.Modbus.Serial.Utils
public SerialPortWrapper() public SerialPortWrapper()
{ {
_serialPort.DataReceived += OnDataReceived; _serialPort.DataReceived += (sender, e) => DataReceived?.Invoke(this, e);
_serialPort.PinChanged += OnPinChanged; _serialPort.PinChanged += (sender, e) => PinChanged?.Invoke(this, e);
_serialPort.ErrorReceived += OnErrorReceived; _serialPort.ErrorReceived += (sender, e) => ErrorReceived?.Invoke(this, e);
} }
#endregion Constructor #endregion Constructor
@@ -42,15 +42,6 @@ namespace AMWD.Protocols.Modbus.Serial.Utils
/// <inheritdoc cref="SerialPort.ErrorReceived"/> /// <inheritdoc cref="SerialPort.ErrorReceived"/>
public virtual event SerialErrorReceivedEventHandler ErrorReceived; public virtual event SerialErrorReceivedEventHandler ErrorReceived;
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
=> DataReceived?.Invoke(sender, e);
private void OnPinChanged(object sender, SerialPinChangedEventArgs e)
=> PinChanged?.Invoke(sender, e);
private void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e)
=> ErrorReceived?.Invoke(sender, e);
#endregion Events #endregion Events
#region Properties #region Properties

View File

@@ -6,24 +6,21 @@
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<CollectCoverage>true</CollectCoverage> <CollectCoverage>true</CollectCoverage>
<CoverletOutputFormat>Cobertura</CoverletOutputFormat>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<GenerateDocumentationFile>false</GenerateDocumentationFile> <GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.msbuild" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Moq" Version="4.20.72" /> <PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.4" /> <PackageReference Include="MSTest.TestAdapter" Version="3.7.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.4" /> <PackageReference Include="MSTest.TestFramework" Version="3.7.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,23 @@
using System.Reflection;
namespace AMWD.Protocols.Modbus.Tests
{
internal static class Helper
{
public static T CreateInstance<T>(params object[] args)
{
var type = typeof(T);
object instance = type.Assembly.CreateInstance(
typeName: type.FullName,
ignoreCase: false,
bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic,
binder: null,
args: args,
culture: null,
activationAttributes: null);
return (T)instance;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
DeviceIdentification
VendorName: VendorName
ProductCode: ProductCode
MajorMinorRevision: MajorMinorRevision
VendorUrl:
ProductName:
ModelName:
UserApplicationName:
IsIndividualAccessAllowed: False