Use binary multi-vehicle sync transport
This commit is contained in:
@@ -17,7 +17,6 @@ using CommonUsage.Mathematics;
|
||||
using FundamentalLib;
|
||||
using FundamentalLib.Utilities;
|
||||
using MDCSToolBox.Clumsy.Pilot.MultiWheel;
|
||||
using Newtonsoft.Json;
|
||||
using Vector = ClumsyCore.Utilities.Vector;
|
||||
|
||||
namespace MultiWheelC;
|
||||
@@ -426,73 +425,32 @@ public class PilotDefinition : MultiWheelPilotDefinition<PilotConfig, PilotDefin
|
||||
|
||||
var hc = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
|
||||
|
||||
PicoHttpServer.AddGetHandler("/multi-vehicle-register", new { CarNum = 0, Info = "" }, query =>
|
||||
PicoHttpServer.AddPostByteHandler("/multi-vehicle-register-bin", body =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var infoJson = Uri.UnescapeDataString(query.Info ?? "");
|
||||
var info = JsonConvert.DeserializeObject<VehicleSyncInfo>(infoJson)
|
||||
?? throw new Exception("VehicleSyncInfo is null");
|
||||
lock (FleetLock)
|
||||
{
|
||||
MultiVehicleFleet[query.CarNum] = info;
|
||||
_multiVehicleFleetSeen[query.CarNum] = DateTime.Now; // C: 刷新存活时刻
|
||||
}
|
||||
return JsonConvert.SerializeObject(new { code = 200, message = "ok" });
|
||||
var (carNum, info) = VehicleSyncBinaryCodec.DecodeRegister(body);
|
||||
ApplyVehicleSyncRegister(carNum, info);
|
||||
return "ok";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DLog.Log($"/multi-vehicle-register error: {e.FormatEx()}", "MultiVehicle");
|
||||
return JsonConvert.SerializeObject(new { code = 500, message = e.Message });
|
||||
DLog.Log($"/multi-vehicle-register-bin error: {e.FormatEx()}", "MultiVehicle");
|
||||
throw;
|
||||
}
|
||||
});
|
||||
|
||||
// F: notify 改用 POST + JSON body(取代 GET query 串),避免整队 Fleet 字典撑爆 URL 长度上限;
|
||||
// 用 Seq 丢弃乱序到达的旧包,避免从车短暂套用过期指令。
|
||||
PicoHttpServer.AddPostTextHandler("/multi-vehicle-notify", body =>
|
||||
PicoHttpServer.AddPostByteHandler("/multi-vehicle-notify-bin", body =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var notification = JsonConvert.DeserializeObject<VehicleSyncNotification>(body ?? "")
|
||||
?? throw new Exception("notification is null");
|
||||
// 乱序丢弃:仅应用序列号大于已应用值的包。Seq==0 视为旧版无序列号始终接受;
|
||||
// 若 Seq 明显回退(差值>100),判定为主车重启的新会话,重新接受并对齐序列号。
|
||||
if (notification.Seq != 0 && notification.Seq <= _multiVehicleAppliedSeq &&
|
||||
notification.Seq > _multiVehicleAppliedSeq - 100)
|
||||
return JsonConvert.SerializeObject(new { code = 200, message = "stale" });
|
||||
_multiVehicleAppliedSeq = notification.Seq;
|
||||
|
||||
MultiVehicleAligned = notification.Aligned;
|
||||
lock (FleetLock)
|
||||
{
|
||||
MultiVehicleFleet = notification.Fleet ?? new Dictionary<int, VehicleSyncInfo>();
|
||||
// C: notify 内含整队成员,逐一刷新其本地存活时刻。
|
||||
var nowSeen = DateTime.Now;
|
||||
foreach (var key in MultiVehicleFleet.Keys)
|
||||
_multiVehicleFleetSeen[key] = nowSeen;
|
||||
}
|
||||
lock (_multiVehicleNotificationLock)
|
||||
{
|
||||
MultiVehicleNotification = notification;
|
||||
_multiVehicleLastNotifyTime = DateTime.Now;
|
||||
}
|
||||
var applyNow = DateTime.Now;
|
||||
if ((applyNow - _mvNotifyApplyLastLog).TotalMilliseconds >= 200)
|
||||
{
|
||||
_mvNotifyApplyLastLog = applyNow;
|
||||
DLog.Log(
|
||||
$"car{CarNum} NOTIFY_APPLY seq={notification.Seq} mode={notification.Mode} " +
|
||||
$"omega={notification.FleetOmega:0.000} reqOmega={notification.RequestedFleetOmega:0.000} " +
|
||||
$"released={notification.FleetMotionReleased} stop={notification.FleetStopActive} " +
|
||||
$"reason={notification.FleetStopReason}",
|
||||
"MultiVehicleRemoteDbg");
|
||||
}
|
||||
return JsonConvert.SerializeObject(new { code = 200, message = "ok" });
|
||||
var notification = VehicleSyncBinaryCodec.DecodeNotification(body);
|
||||
return ApplyVehicleSyncNotification(notification);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DLog.Log($"/multi-vehicle-notify error: {e.FormatEx()}", "MultiVehicle");
|
||||
return JsonConvert.SerializeObject(new { code = 500, message = e.Message });
|
||||
DLog.Log($"/multi-vehicle-notify-bin error: {e.FormatEx()}", "MultiVehicle");
|
||||
throw;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -501,6 +459,53 @@ public class PilotDefinition : MultiWheelPilotDefinition<PilotConfig, PilotDefin
|
||||
new Thread(() => MultiVehicleLoop(hc)) { Name = "MultiVehicle", IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
private void ApplyVehicleSyncRegister(int carNum, VehicleSyncInfo info)
|
||||
{
|
||||
lock (FleetLock)
|
||||
{
|
||||
MultiVehicleFleet[carNum] = info;
|
||||
_multiVehicleFleetSeen[carNum] = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private string ApplyVehicleSyncNotification(VehicleSyncNotification notification)
|
||||
{
|
||||
// Keep the same stale-packet semantics as the previous transport.
|
||||
if (notification.Seq != 0 && notification.Seq <= _multiVehicleAppliedSeq &&
|
||||
notification.Seq > _multiVehicleAppliedSeq - 100)
|
||||
return "stale";
|
||||
_multiVehicleAppliedSeq = notification.Seq;
|
||||
|
||||
MultiVehicleAligned = notification.Aligned;
|
||||
lock (FleetLock)
|
||||
{
|
||||
MultiVehicleFleet = notification.Fleet ?? new Dictionary<int, VehicleSyncInfo>();
|
||||
var nowSeen = DateTime.Now;
|
||||
foreach (var key in MultiVehicleFleet.Keys)
|
||||
_multiVehicleFleetSeen[key] = nowSeen;
|
||||
}
|
||||
|
||||
lock (_multiVehicleNotificationLock)
|
||||
{
|
||||
MultiVehicleNotification = notification;
|
||||
_multiVehicleLastNotifyTime = DateTime.Now;
|
||||
}
|
||||
|
||||
var applyNow = DateTime.Now;
|
||||
if ((applyNow - _mvNotifyApplyLastLog).TotalMilliseconds >= 200)
|
||||
{
|
||||
_mvNotifyApplyLastLog = applyNow;
|
||||
DLog.Log(
|
||||
$"car{CarNum} NOTIFY_APPLY seq={notification.Seq} mode={notification.Mode} " +
|
||||
$"omega={notification.FleetOmega:0.000} reqOmega={notification.RequestedFleetOmega:0.000} " +
|
||||
$"released={notification.FleetMotionReleased} stop={notification.FleetStopActive} " +
|
||||
$"reason={notification.FleetStopReason}",
|
||||
"MultiVehicleRemoteDbg");
|
||||
}
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
private void MultiVehicleLoop(HttpClient hc)
|
||||
{
|
||||
var carLength = CarLength;
|
||||
@@ -1524,29 +1529,52 @@ public class PilotDefinition : MultiWheelPilotDefinition<PilotConfig, PilotDefin
|
||||
|
||||
private void FireAndForgetRegister(HttpClient hc, VehicleSyncInfo info)
|
||||
{
|
||||
ParseMasterEndpoint(out var masterIp, out var masterPort);
|
||||
var infoJson = JsonConvert.SerializeObject(info);
|
||||
var url =
|
||||
$"http://{masterIp}:{masterPort}/multi-vehicle-register?CarNum={CarNum}&Info={Uri.EscapeDataString(infoJson)}";
|
||||
_ = hc.GetStringAsync(url).ContinueWith(t =>
|
||||
try
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
DLog.Log($"register failed: {t.Exception?.GetBaseException().Message}", "MultiVehicle");
|
||||
}, TaskScheduler.Default);
|
||||
ParseMasterEndpoint(out var masterIp, out var masterPort);
|
||||
var payload = VehicleSyncBinaryCodec.EncodeRegister(CarNum, info);
|
||||
var url = $"http://{masterIp}:{masterPort}/multi-vehicle-register-bin";
|
||||
_ = PostBinaryAsync(hc, url, payload, "register");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DLog.Log($"register binary encode failed: {e.GetBaseException().Message}", "MultiVehicle");
|
||||
}
|
||||
}
|
||||
|
||||
private static void FireAndForgetNotify(HttpClient hc, string ip, int port, VehicleSyncNotification notification)
|
||||
private void FireAndForgetNotify(HttpClient hc, string ip, int port, VehicleSyncNotification notification)
|
||||
{
|
||||
// F: POST + JSON body,payload 不再受 URL 长度限制;fire-and-forget 但记录失败。
|
||||
var notifyJson = JsonConvert.SerializeObject(notification);
|
||||
var url = $"http://{ip}:{port}/multi-vehicle-notify";
|
||||
var content = new StringContent(notifyJson, System.Text.Encoding.UTF8, "application/json");
|
||||
_ = hc.PostAsync(url, content).ContinueWith(t =>
|
||||
try
|
||||
{
|
||||
content.Dispose();
|
||||
if (t.IsFaulted)
|
||||
DLog.Log($"notify {ip}:{port} failed: {t.Exception?.GetBaseException().Message}", "MultiVehicle");
|
||||
}, TaskScheduler.Default);
|
||||
var payload = VehicleSyncBinaryCodec.EncodeNotification(notification);
|
||||
var url = $"http://{ip}:{port}/multi-vehicle-notify-bin";
|
||||
_ = PostBinaryAsync(hc, url, payload, $"notify {ip}:{port}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DLog.Log($"notify {ip}:{port} binary encode failed: {e.GetBaseException().Message}", "MultiVehicle");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task PostBinaryAsync(HttpClient hc, string url, byte[] payload, string description)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var content = new ByteArrayContent(payload);
|
||||
content.Headers.ContentType =
|
||||
new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
|
||||
using var response = await hc.PostAsync(url, content).ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
return;
|
||||
|
||||
DLog.Log(
|
||||
$"{description} binary failed: HTTP {(int)response.StatusCode} {response.ReasonPhrase}",
|
||||
"MultiVehicle");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DLog.Log($"{description} binary failed: {e.GetBaseException().Message}", "MultiVehicle");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveMultiVehicleSelfEndpoint(out string ip, out int port)
|
||||
|
||||
Reference in New Issue
Block a user