✨ feat(fanuc): 添加协议编解码与状态页" -m "* 固化 10010 状态帧、10012 命令帧和 60015 J519 包编解码
* 扩展 ControllerClient 兼容层的执行参数和运行时编排 * 新增 /status 页面与 /api/status/snapshot 状态快照接口 * 补充 FANUC 协议、客户端和状态接口的最小验证测试 * 更新 README、兼容要求和真机 Socket 通信实现计划
This commit is contained in:
@@ -53,7 +53,7 @@ public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory
|
||||
Assert.True(isSetupJson.RootElement.GetProperty("is_setup").GetBoolean());
|
||||
}
|
||||
|
||||
using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=false", content: null))
|
||||
using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=true", content: null))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, activeControllerResponse.StatusCode);
|
||||
using var activeControllerJson = await ReadJsonAsync(activeControllerResponse);
|
||||
@@ -145,6 +145,24 @@ public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory
|
||||
Assert.Equal(HttpStatusCode.OK, getPoseResponse.StatusCode);
|
||||
using var getPoseJson = await ReadJsonAsync(getPoseResponse);
|
||||
Assert.Equal(7, getPoseJson.RootElement.GetProperty("pose").GetArrayLength());
|
||||
|
||||
using (var executeTrajectoryResponse = await client.PostAsJsonAsync("/execute_trajectory/", new
|
||||
{
|
||||
method = "icsp",
|
||||
save_traj = true,
|
||||
waypoints = new[]
|
||||
{
|
||||
new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
|
||||
new[] { 0.1, 0.0, 0.0, 0.0, 0.0, 0.0 },
|
||||
new[] { 0.2, 0.0, 0.0, 0.0, 0.0, 0.0 },
|
||||
new[] { 0.3, 0.0, 0.0, 0.0, 0.0, 0.0 }
|
||||
}
|
||||
}))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, executeTrajectoryResponse.StatusCode);
|
||||
using var executeTrajectoryJson = await ReadJsonAsync(executeTrajectoryResponse);
|
||||
Assert.Equal("trajectory executed", executeTrajectoryJson.RootElement.GetProperty("status").GetString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -161,15 +179,19 @@ public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory
|
||||
addrs = new[]
|
||||
{
|
||||
new[] { 7, 8 },
|
||||
new[] { 7, 8 }
|
||||
new[] { 7, 8 },
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>()
|
||||
},
|
||||
name = "demo-http-flyshot",
|
||||
offset_values = new[] { 0.0, 1.0 },
|
||||
shot_flags = new[] { false, true },
|
||||
offset_values = new[] { 0.0, 1.0, 0.0, 0.0 },
|
||||
shot_flags = new[] { false, true, false, false },
|
||||
waypoints = new[]
|
||||
{
|
||||
new[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6 },
|
||||
new[] { 1.1, 1.2, 1.3, 1.4, 1.5, 1.6 }
|
||||
new[] { 0.2, 0.2, 0.3, 0.4, 0.5, 0.6 },
|
||||
new[] { 0.3, 0.2, 0.3, 0.4, 0.5, 0.6 },
|
||||
new[] { 0.4, 0.2, 0.3, 0.4, 0.5, 0.6 }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,7 +210,27 @@ public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory
|
||||
Assert.Contains("demo-http-flyshot", names);
|
||||
}
|
||||
|
||||
using (var executeResponse = await client.PostAsJsonAsync("/execute_flyshot/", new { name = "demo-http-flyshot" }))
|
||||
using (var validResponse = await client.PostAsJsonAsync("/is_flyShotTrajValid/", new
|
||||
{
|
||||
name = "demo-http-flyshot",
|
||||
method = "icsp",
|
||||
save_traj = false
|
||||
}))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, validResponse.StatusCode);
|
||||
using var validJson = await ReadJsonAsync(validResponse);
|
||||
Assert.True(validJson.RootElement.GetProperty("valid").GetBoolean());
|
||||
Assert.True(validJson.RootElement.GetProperty("time").GetDouble() > 0.0);
|
||||
}
|
||||
|
||||
using (var executeResponse = await client.PostAsJsonAsync("/execute_flyshot/", new
|
||||
{
|
||||
name = "demo-http-flyshot",
|
||||
move_to_start = true,
|
||||
method = "icsp",
|
||||
save_traj = true,
|
||||
use_cache = true
|
||||
}))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode);
|
||||
using var executeJson = await ReadJsonAsync(executeResponse);
|
||||
@@ -197,6 +239,17 @@ public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory
|
||||
Assert.True(executeRoot.GetProperty("success").GetBoolean());
|
||||
}
|
||||
|
||||
using (var saveInfoResponse = await client.PostAsJsonAsync("/save_traj_info/", new
|
||||
{
|
||||
name = "demo-http-flyshot",
|
||||
method = "icsp"
|
||||
}))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, saveInfoResponse.StatusCode);
|
||||
using var saveInfoJson = await ReadJsonAsync(saveInfoResponse);
|
||||
Assert.True(saveInfoJson.RootElement.GetProperty("success").GetBoolean());
|
||||
}
|
||||
|
||||
using (var deleteResponse = await client.PostAsJsonAsync("/delete_flyshot/", new { name = "demo-http-flyshot" }))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode);
|
||||
@@ -215,7 +268,8 @@ public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory
|
||||
server_ip = "127.0.0.1",
|
||||
port = 50001,
|
||||
robot_name = "FANUC_LR_Mate_200iD",
|
||||
robot_ip = "192.168.10.101"
|
||||
robot_ip = "192.168.10.101",
|
||||
sim = true
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, initResponse.StatusCode);
|
||||
|
||||
91
tests/Flyshot.Server.IntegrationTests/StatusEndpointTests.cs
Normal file
91
tests/Flyshot.Server.IntegrationTests/StatusEndpointTests.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Flyshot.Server.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态监控页面和状态快照 API 能读取当前 ControllerClient 兼容层状态。
|
||||
/// </summary>
|
||||
public sealed class StatusEndpointTests(FlyshotServerFactory factory) : IClassFixture<FlyshotServerFactory>
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证状态页返回可由浏览器直接打开的 HTML,并引用状态快照 API。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetStatusPage_ReturnsMonitoringHtml()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
using var response = await client.GetAsync("/status");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.StartsWith("text/html", response.Content.Headers.ContentType?.MediaType);
|
||||
|
||||
var html = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("Flyshot Replacement 状态监控", html, StringComparison.Ordinal);
|
||||
Assert.Contains("/api/status/snapshot", html, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态快照 API 会返回运行时连接、使能、速度和机器人元数据。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetStatusSnapshot_ReturnsRuntimeStateAfterLegacyInitialization()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
await InitializeRobotAsync(client);
|
||||
|
||||
using (var speedResponse = await client.PostAsJsonAsync("/set_speedRatio/", new { speed = 0.75 }))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, speedResponse.StatusCode);
|
||||
}
|
||||
|
||||
using var response = await client.GetAsync("/api/status/snapshot");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
await using var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
using var json = await JsonDocument.ParseAsync(responseStream);
|
||||
var root = json.RootElement;
|
||||
var snapshot = root.GetProperty("snapshot");
|
||||
|
||||
Assert.Equal("ok", root.GetProperty("status").GetString());
|
||||
Assert.True(root.GetProperty("isSetup").GetBoolean());
|
||||
Assert.Equal("FANUC_LR_Mate_200iD", root.GetProperty("robotName").GetString());
|
||||
Assert.Equal(6, root.GetProperty("degreesOfFreedom").GetInt32());
|
||||
Assert.Empty(root.GetProperty("uploadedTrajectories").EnumerateArray());
|
||||
Assert.Equal("Connected", snapshot.GetProperty("connectionState").GetString());
|
||||
Assert.True(snapshot.GetProperty("isEnabled").GetBoolean());
|
||||
Assert.False(snapshot.GetProperty("isInMotion").GetBoolean());
|
||||
Assert.Equal(0.75, snapshot.GetProperty("speedRatio").GetDouble(), precision: 6);
|
||||
Assert.Equal(6, snapshot.GetProperty("jointPositions").GetArrayLength());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化旧 HTTP 兼容链路,使状态页可以读取一个完整的已连接状态。
|
||||
/// </summary>
|
||||
/// <param name="client">测试 HTTP 客户端。</param>
|
||||
private static async Task InitializeRobotAsync(HttpClient client)
|
||||
{
|
||||
using (var setupResponse = await client.PostAsync("/setup_robot/?robot_name=FANUC_LR_Mate_200iD", content: null))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, setupResponse.StatusCode);
|
||||
}
|
||||
|
||||
using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=true", content: null))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, activeControllerResponse.StatusCode);
|
||||
}
|
||||
|
||||
using (var connectRobotResponse = await client.PostAsync("/connect_robot/?ip=192.168.10.101", content: null))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, connectRobotResponse.StatusCode);
|
||||
}
|
||||
|
||||
using (var enableRobotResponse = await client.GetAsync("/enable_robot/"))
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, enableRobotResponse.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user