74 lines
2.7 KiB
C#
74 lines
2.7 KiB
C#
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;
|
|
}
|
|
}
|