✨ feat(fanuc): 添加协议编解码与状态页" -m "* 固化 10010 状态帧、10012 命令帧和 60015 J519 包编解码
* 扩展 ControllerClient 兼容层的执行参数和运行时编排 * 新增 /status 页面与 /api/status/snapshot 状态快照接口 * 补充 FANUC 协议、客户端和状态接口的最小验证测试 * 更新 README、兼容要求和真机 Socket 通信实现计划
This commit is contained in:
116
tests/Flyshot.Core.Tests/FanucProtocolTests.cs
Normal file
116
tests/Flyshot.Core.Tests/FanucProtocolTests.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Buffers.Binary;
|
||||
using Flyshot.Runtime.Fanuc.Protocol;
|
||||
|
||||
namespace Flyshot.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 FANUC 真机三条通信链路的二进制协议基础与逆向抓包样本一致。
|
||||
/// </summary>
|
||||
public sealed class FanucProtocolTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证 TCP 10012 程序命令封包与抓包中的 StopProg("RVBUSTSM") 完全一致。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CommandProtocol_PacksCapturedStopProgramFrame()
|
||||
{
|
||||
var frame = FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.StopProgram, "RVBUSTSM");
|
||||
|
||||
Assert.Equal(
|
||||
Convert.FromHexString("646f7a0000001a0000210300000008525642555354534d7a6f64"),
|
||||
frame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 TCP 10012 短响应和程序状态响应可以按抓包字段解析。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CommandProtocol_ParsesCapturedResponses()
|
||||
{
|
||||
var stopResponse = FanucCommandProtocol.ParseResultResponse(
|
||||
Convert.FromHexString("646f7a0000001200002103000000007a6f64"));
|
||||
var statusResponse = FanucCommandProtocol.ParseProgramStatusResponse(
|
||||
Convert.FromHexString("646f7a000000160000200300000000000000017a6f64"));
|
||||
|
||||
Assert.Equal(FanucCommandMessageIds.StopProgram, stopResponse.MessageId);
|
||||
Assert.True(stopResponse.IsSuccess);
|
||||
Assert.Equal(FanucCommandMessageIds.GetProgramStatus, statusResponse.MessageId);
|
||||
Assert.True(statusResponse.IsSuccess);
|
||||
Assert.Equal(1u, statusResponse.ProgramStatus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 TCP 10010 状态帧可以从抓包样本解析出尾部状态槽位。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void StateProtocol_ParsesCapturedStateFrame()
|
||||
{
|
||||
var frame = FanucStateProtocol.ParseFrame(Convert.FromHexString(
|
||||
"646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64"));
|
||||
|
||||
Assert.Equal(0u, frame.MessageId);
|
||||
Assert.Equal(6, frame.Pose.Count);
|
||||
Assert.Equal(9, frame.JointOrExtensionValues.Count);
|
||||
Assert.Equal([2u, 0u, 0u, 1u], frame.TailWords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 UDP 60015 的 J519 初始化、结束和命令包字段布局。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void J519Protocol_PacksControlAndCommandPackets()
|
||||
{
|
||||
var command = new FanucJ519Command(
|
||||
sequence: 2,
|
||||
targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
|
||||
|
||||
var packet = FanucJ519Protocol.PackCommandPacket(command);
|
||||
|
||||
Assert.Equal(Convert.FromHexString("0000000000000001"), FanucJ519Protocol.PackInitPacket());
|
||||
Assert.Equal(Convert.FromHexString("0000000200000001"), FanucJ519Protocol.PackEndPacket());
|
||||
Assert.Equal(FanucJ519Protocol.CommandPacketLength, packet.Length);
|
||||
Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x00, 4)));
|
||||
Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x04, 4)));
|
||||
Assert.Equal(2u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4)));
|
||||
Assert.Equal(2, packet[0x0d]);
|
||||
Assert.Equal(1, packet[0x12]);
|
||||
Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4)));
|
||||
Assert.Equal(6.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x30, 4)));
|
||||
Assert.Equal(0.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x38, 4)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 UDP 60015 的 132 字节响应包字段可以被解析成状态位和关节反馈。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void J519Protocol_ParsesResponsePacket()
|
||||
{
|
||||
var packet = new byte[FanucJ519Protocol.ResponsePacketLength];
|
||||
BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x00, 4), 0);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x04, 4), 1);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x08, 4), 12);
|
||||
packet[0x0c] = 15;
|
||||
packet[0x0d] = 2;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x0e, 2), 1);
|
||||
BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x10, 2), 255);
|
||||
BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x12, 2), 10);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x14, 4), 1234);
|
||||
BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x18, 4), 100.5f);
|
||||
BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x3c, 4), 1.25f);
|
||||
BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x60, 4), 2.5f);
|
||||
|
||||
var response = FanucJ519Protocol.ParseResponse(packet);
|
||||
|
||||
Assert.Equal(12u, response.Sequence);
|
||||
Assert.Equal(15, response.Status);
|
||||
Assert.True(response.AcceptsCommand);
|
||||
Assert.True(response.ReceivedCommand);
|
||||
Assert.True(response.SystemReady);
|
||||
Assert.True(response.RobotInMotion);
|
||||
Assert.Equal(10, response.ReadIoValue);
|
||||
Assert.Equal(1234u, response.Timestamp);
|
||||
Assert.Equal(100.5, response.Pose[0], precision: 6);
|
||||
Assert.Equal(1.25, response.JointDegrees[0], precision: 6);
|
||||
Assert.Equal(2.5, response.MotorCurrents[0], precision: 6);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user