SSPC-Tester/SSPCTester.Tests/ModbusProfileTests.cs

74 lines
2.7 KiB
C#
Raw Normal View History

using SSPCTester.Devices.Config;
using SSPCTester.Devices.Protocol;
namespace SSPCTester.Tests;
public sealed class ModbusProfileTests
{
[Fact]
public void FieldProfile_IsValidAndCoversPhysicalSlots1To24()
{
string path = FindRepoFile("Docs", "Modbus接口规范", "device-profile.json");
var profile = ModbusDeviceProfileLoader.Load(path);
Assert.Equal(Enumerable.Range(1, 24), profile.Channels.OrderBy(x => x.PhysicalSlot).Select(x => x.PhysicalSlot));
Assert.Equal((byte)1, profile.Channels.Single(x => x.PhysicalSlot == 1).Relay.Slave);
Assert.Equal((ushort)7, profile.Channels.Single(x => x.PhysicalSlot == 1).Relay.Coil);
Assert.Equal(12, profile.Channels.Single(x => x.PhysicalSlot == 24).Current.RegisterOffset);
}
[Fact]
public void Crc16_UsesModbusLowByteFirst()
{
byte[] request = ModbusRtuProtocol.BuildRequest(4, 3, 0, 24);
Assert.Equal(new byte[] { 0x04, 0x03, 0x00, 0x00, 0x00, 0x18 }, request[..6]);
Assert.Equal((byte)(ModbusRtuProtocol.ComputeCrc(request.AsSpan(0, 6)) & 0xFF), request[6]);
ModbusRtuProtocol.ValidateCrc(request);
}
[Fact]
public void SignedCurrent_UsesInt16TwosComplementAndFormulaA()
{
ushort rawBits = unchecked((ushort)(short)-1000);
double amps = ModbusValueConverter.ConvertRaw(rawBits, true, "A", 60.0);
Assert.Equal(-6.0, amps, 6);
}
[Fact]
public void InvalidDuplicateRegisterMapping_IsRejected()
{
var profile = ModbusDeviceProfileLoader.Load(ProfilePath());
profile.Current.ChannelsForTestDuplicate(profile.Channels);
var error = Assert.Throws<InvalidDataException>(() => ModbusDeviceProfileLoader.Validate(profile));
Assert.Contains("current.registerOffset 存在重复", error.Message);
}
private static string ProfilePath() => FindRepoFile("Docs", "Modbus接口规范", "device-profile.json");
private static string FindRepoFile(params string[] parts)
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory != null)
{
string candidate = Path.Combine(new[] { directory.FullName }.Concat(parts).ToArray());
if (File.Exists(candidate)) return candidate;
directory = directory.Parent;
}
throw new FileNotFoundException($"找不到仓库文件:{Path.Combine(parts)}");
}
}
internal static class ProfileTestExtensions
{
public static void ChannelsForTestDuplicate(
this MeasurementProtocol _, IReadOnlyList<ChannelMapping> channels)
{
channels[1].Current.RegisterOffset = channels[0].Current.RegisterOffset;
}
}