2.0协议完善加简单交管配置
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
using StandardScene.Magnetic.Protocol;
|
||||
using StandardScene.Magnetic.Tasking;
|
||||
using StandardScene.Magnetic.Tests.TestHelpers;
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace StandardScene.Magnetic.Tests.Protocol
|
||||
{
|
||||
public sealed class Fass2ProtocolGoldenTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildControl_Start_Car1_Param0_MatchesGoldenLayout()
|
||||
{
|
||||
var frame = Fass2Protocol.BuildControl(Fass2Protocol.CmdStart, 1, 0);
|
||||
|
||||
Assert.Equal(Fass2Protocol.ControlFrameLength, frame.Length);
|
||||
Assert.Equal(Fass2Protocol.Begin, frame[0]);
|
||||
Assert.Equal(Fass2Protocol.CmdStart, frame[1]);
|
||||
Assert.Equal(0x01, frame[2]);
|
||||
Assert.Equal(0x00, frame[3]);
|
||||
Assert.Equal(0x00, frame[4]);
|
||||
Assert.Equal(0x00, frame[5]);
|
||||
Assert.Equal(Fass2Protocol.Xor(frame, 1, 47), frame[48]);
|
||||
Assert.Equal(Fass2Protocol.End, frame[49]);
|
||||
|
||||
const string goldenPrefix = "BB 01 01 00 00 00";
|
||||
Assert.StartsWith(goldenPrefix, Fass2TestFrameBuilder.ToHex(frame));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildNodes_TwoNodes_LittleEndianAndXor()
|
||||
{
|
||||
var nodes = new[]
|
||||
{
|
||||
new Fass2NodeMessage
|
||||
{
|
||||
Node = 1,
|
||||
Distance = 500,
|
||||
StartStop = 1,
|
||||
Speed = 120
|
||||
},
|
||||
new Fass2NodeMessage
|
||||
{
|
||||
Node = 2,
|
||||
StartStop = 2,
|
||||
Lift = 1
|
||||
}
|
||||
};
|
||||
|
||||
var frame = Fass2Protocol.BuildNodes(1, 0x0102030405060708UL, nodes);
|
||||
|
||||
Assert.Equal(Fass2Protocol.NodesFrameLength, frame.Length);
|
||||
Assert.Equal(Fass2Protocol.CmdNodes, frame[1]);
|
||||
Assert.Equal(0x08, frame[4]);
|
||||
Assert.Equal(0x01, frame[11]);
|
||||
Assert.Equal(0x02, frame[12]);
|
||||
Assert.Equal(0x00, frame[13]);
|
||||
Assert.Equal(Fass2Protocol.Xor(frame, 1, 297), frame[298]);
|
||||
Assert.Equal(Fass2Protocol.End, frame[299]);
|
||||
|
||||
var parsedNode0 = Fass2NodeMessage.FromBytes(CopyRange(frame, 14, 25));
|
||||
Assert.Equal((ushort)1, parsedNode0.Node);
|
||||
Assert.Equal((ushort)500, parsedNode0.Distance);
|
||||
Assert.Equal((byte)1, parsedNode0.StartStop);
|
||||
Assert.Equal((ushort)120, parsedNode0.Speed);
|
||||
|
||||
var parsedNode1 = Fass2NodeMessage.FromBytes(CopyRange(frame, 39, 25));
|
||||
Assert.Equal((ushort)2, parsedNode1.Node);
|
||||
Assert.Equal((byte)2, parsedNode1.StartStop);
|
||||
Assert.Equal((byte)1, parsedNode1.Lift);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAction_RoundTripsNodeBlock()
|
||||
{
|
||||
var node = new Fass2NodeMessage
|
||||
{
|
||||
Node = 3,
|
||||
StartStop = 22,
|
||||
Lift = 2,
|
||||
Speed = 120
|
||||
};
|
||||
|
||||
var frame = Fass2Protocol.BuildAction(2, 999, node);
|
||||
|
||||
Assert.Equal(Fass2Protocol.ActionFrameLength, frame.Length);
|
||||
Assert.Equal(Fass2Protocol.CmdAction, frame[1]);
|
||||
Assert.Equal(0x02, frame[2]);
|
||||
Assert.Equal(0x00, frame[3]);
|
||||
Assert.Equal(Fass2Protocol.Xor(frame, 1, 97), frame[98]);
|
||||
|
||||
var parsed = Fass2NodeMessage.FromBytes(CopyRange(frame, 14, 25));
|
||||
Assert.Equal(node.Node, parsed.Node);
|
||||
Assert.Equal(node.StartStop, parsed.StartStop);
|
||||
Assert.Equal(node.Lift, parsed.Lift);
|
||||
Assert.Equal(node.Speed, parsed.Speed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildStateResponse_FixedTimestamp_EncodesBeijingCalendar()
|
||||
{
|
||||
var frame = Fass2Protocol.BuildStateResponse(1, 1_704_067_200_000UL);
|
||||
|
||||
Assert.Equal(Fass2Protocol.CmdStateResponse, frame[1]);
|
||||
Assert.Equal(0x01, frame[2]);
|
||||
Assert.Equal(0x00, frame[3]);
|
||||
Assert.Equal(Fass2Protocol.Xor(frame, 1, 47), frame[48]);
|
||||
Assert.Equal(Fass2Protocol.End, frame[49]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseState_RoundTripsMinimalFrame()
|
||||
{
|
||||
var frame = Fass2TestFrameBuilder.BuildStateFrame(5, 2, 7, 42);
|
||||
|
||||
Assert.True(Fass2Protocol.TryExtractStateFrame(frame, frame.Length, out var extracted));
|
||||
Assert.Equal(frame, extracted);
|
||||
|
||||
var report = Fass2Protocol.ParseState(frame);
|
||||
Assert.Equal((ushort)5, report.Car);
|
||||
Assert.Equal((byte)2, report.State);
|
||||
Assert.Equal((ulong)42, report.Task);
|
||||
Assert.Equal((ushort)7, report.Node.Node);
|
||||
Assert.Equal((ushort)500, report.Node.Distance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeMessage_ToBytes_FromBytes_RoundTrip()
|
||||
{
|
||||
var source = new Fass2NodeMessage
|
||||
{
|
||||
Node = 11,
|
||||
Distance = 1234,
|
||||
StartStop = 2,
|
||||
Direction = 3,
|
||||
Orientation = 64,
|
||||
Byroad = 2,
|
||||
Speed = 120,
|
||||
Lift = 1,
|
||||
Roll = 2,
|
||||
Shutdown = 1
|
||||
};
|
||||
|
||||
var roundTrip = Fass2NodeMessage.FromBytes(source.ToBytes());
|
||||
Assert.Equal(source.Node, roundTrip.Node);
|
||||
Assert.Equal(source.Distance, roundTrip.Distance);
|
||||
Assert.Equal(source.StartStop, roundTrip.StartStop);
|
||||
Assert.Equal(source.Direction, roundTrip.Direction);
|
||||
Assert.Equal(source.Orientation, roundTrip.Orientation);
|
||||
Assert.Equal(source.Byroad, roundTrip.Byroad);
|
||||
Assert.Equal(source.Speed, roundTrip.Speed);
|
||||
Assert.Equal(source.Lift, roundTrip.Lift);
|
||||
Assert.Equal(source.Roll, roundTrip.Roll);
|
||||
Assert.Equal(source.Shutdown, roundTrip.Shutdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeedToProtocol_UsesMPerMinTimesTen()
|
||||
{
|
||||
Assert.Equal((ushort)120, Fass2TaskBuilder.SpeedToProtocol(12));
|
||||
Assert.Equal((ushort)120, Fass2TaskBuilder.SpeedToProtocol(0.2));
|
||||
}
|
||||
|
||||
private static byte[] CopyRange(byte[] source, int offset, int length)
|
||||
{
|
||||
var copy = new byte[length];
|
||||
Buffer.BlockCopy(source, offset, copy, 0, length);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using StandardScene.Fass2Simulator;
|
||||
using StandardScene.Magnetic.Protocol;
|
||||
using Xunit;
|
||||
|
||||
namespace StandardScene.Magnetic.Tests.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证 Magnetic 编解码与模拟器 100B 状态帧字段布局一致。
|
||||
/// </summary>
|
||||
public sealed class Fass2SimMagneticProtocolCrossTests
|
||||
{
|
||||
[Fact]
|
||||
public void StateFrame_NodeBlockOffset_MatchesMagneticParser()
|
||||
{
|
||||
var vehicle = new Fass2SimVehicle(new Fass2SimConfig
|
||||
{
|
||||
VehicleCode = 1,
|
||||
InitialNode = 9,
|
||||
InitialState = 2
|
||||
});
|
||||
vehicle.StartStop = 2;
|
||||
vehicle.Distance = 777;
|
||||
vehicle.Speed = 120;
|
||||
vehicle.Lift = 1;
|
||||
vehicle.Task = 1001;
|
||||
|
||||
var simFrame = Fass2SimProtocol.BuildState(vehicle.Snapshot());
|
||||
var report = Fass2Protocol.ParseState(simFrame);
|
||||
|
||||
Assert.Equal((ushort)1, report.Car);
|
||||
Assert.Equal((byte)2, report.State);
|
||||
Assert.Equal((ulong)1001, report.Task);
|
||||
Assert.Equal((ushort)9, report.Node.Node);
|
||||
Assert.Equal((ushort)777, report.Node.Distance);
|
||||
Assert.Equal((byte)2, report.Node.StartStop);
|
||||
Assert.Equal((ushort)120, report.Node.Speed);
|
||||
Assert.Equal((byte)1, report.Node.Lift);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeBytes_Are25BytesAligned()
|
||||
{
|
||||
var node = new Fass2NodeMessage
|
||||
{
|
||||
Node = 4,
|
||||
Distance = 100,
|
||||
StartStop = 1,
|
||||
Byroad = 2,
|
||||
Speed = 60
|
||||
};
|
||||
|
||||
var bytes = node.ToBytes();
|
||||
Assert.Equal(25, bytes.Length);
|
||||
|
||||
var simNode = Fass2SimNodeMessage.FromBytes(bytes, 0);
|
||||
Assert.Equal((ushort)4, simNode.Node);
|
||||
Assert.Equal((ushort)100, simNode.Distance);
|
||||
Assert.Equal((byte)1, simNode.StartStop);
|
||||
Assert.Equal((byte)2, simNode.Byroad);
|
||||
Assert.Equal((ushort)60, simNode.Speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using StandardScene.Magnetic.Protocol;
|
||||
using StandardScene.Magnetic.Tests.TestHelpers;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace StandardScene.Magnetic.Tests.Protocol
|
||||
{
|
||||
public sealed class Fass2UdpHubMultiCarTests : IDisposable
|
||||
{
|
||||
private readonly int _hubPort;
|
||||
private readonly TestUdpCar _car1;
|
||||
private readonly TestUdpCar _car2;
|
||||
|
||||
public Fass2UdpHubMultiCarTests()
|
||||
{
|
||||
_hubPort = AllocateUdpPort();
|
||||
_car1 = new TestUdpCar(1, AllocateUdpPort());
|
||||
_car2 = new TestUdpCar(2, AllocateUdpPort());
|
||||
|
||||
Fass2UdpHub.Register(_car1);
|
||||
Fass2UdpHub.Register(_car2);
|
||||
Fass2UdpHub.EnsureStarted(_hubPort);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hub_RoutesStateToRegisteredCar_AndRepliesWith0x10()
|
||||
{
|
||||
SendState(_car1.VehicleCode, 2, 10, 100);
|
||||
var ack1 = _car1.WaitForAck();
|
||||
Assert.NotNull(ack1);
|
||||
Assert.Equal(Fass2Protocol.ControlFrameLength, ack1.Length);
|
||||
Assert.Equal(Fass2Protocol.CmdStateResponse, ack1[1]);
|
||||
Assert.Equal(0x01, ack1[2]);
|
||||
Assert.Single(_car1.Reports);
|
||||
Assert.Equal((ushort)10, _car1.Reports[0].Node.Node);
|
||||
Assert.Empty(_car2.Reports);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hub_DispatchesMultipleCarsIndependently()
|
||||
{
|
||||
SendState(1, 1, 11, 201);
|
||||
SendState(2, 2, 22, 202);
|
||||
|
||||
Assert.NotNull(_car1.WaitForAck());
|
||||
Assert.NotNull(_car2.WaitForAck());
|
||||
|
||||
Assert.Single(_car1.Reports);
|
||||
Assert.Single(_car2.Reports);
|
||||
Assert.Equal((ushort)11, _car1.Reports[0].Node.Node);
|
||||
Assert.Equal((ushort)22, _car2.Reports[0].Node.Node);
|
||||
Assert.Equal((ulong)201, _car1.Reports[0].Task);
|
||||
Assert.Equal((ulong)202, _car2.Reports[0].Task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hub_IgnoresUnregisteredCarCode()
|
||||
{
|
||||
SendState(99, 2, 30, 0);
|
||||
Thread.Sleep(300);
|
||||
Assert.Empty(_car1.Reports);
|
||||
Assert.Empty(_car2.Reports);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Fass2UdpHub.Unregister(_car1);
|
||||
Fass2UdpHub.Unregister(_car2);
|
||||
Fass2UdpHub.Stop();
|
||||
_car1.Dispose();
|
||||
_car2.Dispose();
|
||||
}
|
||||
|
||||
private void SendState(ushort carCode, byte state, ushort nodeId, ulong taskId)
|
||||
{
|
||||
var frame = Fass2TestFrameBuilder.BuildStateFrame(carCode, state, nodeId, taskId);
|
||||
using var sender = new UdpClient();
|
||||
sender.Send(frame, frame.Length, new IPEndPoint(IPAddress.Loopback, _hubPort));
|
||||
Thread.Sleep(80);
|
||||
}
|
||||
|
||||
private static int AllocateUdpPort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
return port;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user