增加dstTracker

This commit is contained in:
shuai.li
2026-07-04 14:58:03 +08:00
parent fc7c8e243b
commit 22670d916b
4 changed files with 221 additions and 96 deletions
+97 -58
View File
@@ -1,5 +1,7 @@
using ClumsyCore; using ClumsyCore;
using ClumsyCore.Interfaces; using ClumsyCore.Interfaces;
using ClumsyCore.Sensors;
using CommonUsage.Chassis;
using FundamentalLib; using FundamentalLib;
using MDCSToolBox.Clumsy.AgvInterfaces; using MDCSToolBox.Clumsy.AgvInterfaces;
using MDCSToolBox.Clumsy.MotionControllers; using MDCSToolBox.Clumsy.MotionControllers;
@@ -10,6 +12,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Numerics; using System.Numerics;
using System.Security.Cryptography;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static ClumsyCore.DTools.Painter; using static ClumsyCore.DTools.Painter;
@@ -65,20 +68,20 @@ namespace MultiWheelC
} }
} }
//参数1:tireNum 需要钻过的轮胎对数量 //参数1:tireNum 需要钻过的轮胎对数量
//参数2frontLidarDetect true:前雷达识别 false:后雷达识别 //参数2frontLidarDetect true:前雷达识别 false:后雷达识别
public void TireFollowing(int tireNum, bool frontLidarDetect, int srcId, int dstId) public void TireFollowing(int tireNum, bool frontLidarDetect, int srcId, int dstId)
{ {
while (!TryLock(dstId)) while (!TryLock(dstId))
{ {
Thread.Sleep(50); Thread.Sleep(50);
} }
DLog.Log($"锁点{dstId}完成", "TireFollowing"); DLog.Log($"锁点{dstId}完成", "TireFollowing");
var lidarName = frontLidarDetect ? "前雷达" : "后雷达"; var lidarName = frontLidarDetect ? "前雷达" : "后雷达";
DLog.Log($"开始钻车动作,通过{lidarName}识别结果钻{tireNum}对轮胎", "TireFollowing"); DLog.Log($"开始钻车动作,通过{lidarName}识别结果钻{tireNum}对轮胎", "TireFollowing");
if (tireNum != 1 && tireNum != 2) if (tireNum != 1 && tireNum != 2)
{ {
DLog.Log($"TireNum必须是1或2 (当前输入:{tireNum})", "TireFollowing"); DLog.Log($"TireNum必须是1或2 (当前输入:{tireNum})", "TireFollowing");
return; return;
} }
@@ -86,20 +89,20 @@ namespace MultiWheelC
{ {
while (!TryLock(dstId)) while (!TryLock(dstId))
{ {
Console.WriteLine("等待锁取货点中..."); Console.WriteLine("等待锁取货点中...");
Thread.Sleep(200); Thread.Sleep(200);
} }
Console.WriteLine($"锁点{dstId}完成"); Console.WriteLine($"锁点{dstId}完成");
Thread.Sleep(1000); Thread.Sleep(1000);
Console.WriteLine($"开始钻车动作,通过{lidarName}识别结果钻{tireNum}对轮胎"); Console.WriteLine($"开始钻车动作,通过{lidarName}识别结果钻{tireNum}对轮胎");
Thread.Sleep(1000); Thread.Sleep(1000);
Leave(srcId); Leave(srcId);
Console.WriteLine($"开始第一段盲走,此时释放预取货点{srcId}"); Console.WriteLine($"开始第一段盲走,此时释放预取货点{srcId}");
Thread.Sleep(2000); Thread.Sleep(2000);
//Leave(dstId); //Leave(dstId);
//Console.WriteLine($"结束第一段盲走,此时释放取货点{dstId}"); //Console.WriteLine($"结束第一段盲走,此时释放取货点{dstId}");
Thread.Sleep(2000); Thread.Sleep(2000);
Console.WriteLine($"结束钻车动作"); Console.WriteLine($"结束钻车动作");
return; return;
} }
@@ -133,7 +136,7 @@ namespace MultiWheelC
0) 0)
}, },
}; };
DLog.Log($"钻胎为{tireNum}", "TireFollowing"); DLog.Log($"钻胎为{tireNum}", "TireFollowing");
var following = new TireFollowing() var following = new TireFollowing()
{ {
GetController = () => new ChassisController().Get(), GetController = () => new ChassisController().Get(),
@@ -148,18 +151,18 @@ namespace MultiWheelC
}; };
var _dt = new DriveTask(following.Get()); var _dt = new DriveTask(following.Get());
_dt.Wait(); _dt.Wait();
DLog.Log("钻车动作结束", "TireFollowing"); DLog.Log("钻车动作结束", "TireFollowing");
} }
//离车一定是后雷达识别一个轮胎 //离车一定是后雷达识别一个轮胎
public void LeaveCar(int srcId, float srcX, float srcY, int dstId, float dstX, float dstY) public void LeaveCar(int srcId, float srcX, float srcY, int dstId, float dstX, float dstY)
{ {
while (!TryLock(dstId)) while (!TryLock(dstId))
{ {
Thread.Sleep(50); Thread.Sleep(50);
} }
DLog.Log($"锁点{dstId}完成", "TireFollowing"); DLog.Log($"锁点{dstId}完成", "TireFollowing");
DLog.Log($"开始钻车动作,通过后雷达识别结果钻1对轮胎", "TireFollowing"); DLog.Log($"开始钻车动作,通过后雷达识别结果钻1对轮胎", "TireFollowing");
var following = new TireFollowing() var following = new TireFollowing()
{ {
@@ -173,7 +176,7 @@ namespace MultiWheelC
DetectFunction = (_, lastDetectX, filters) => TireDetect.Detect(lastDetectX, filters, false), DetectFunction = (_, lastDetectX, filters) => TireDetect.Detect(lastDetectX, filters, false),
StartGuessingX = -PilotDefinition.Conf.TireFollowingStage2GuessX, StartGuessingX = -PilotDefinition.Conf.TireFollowingStage2GuessX,
StartGuessingY = 0, StartGuessingY = 0,
SwitchWalkBlindCondition = rd => rd <= PilotDefinition.Conf.TireFollowingWalkBlindSwitchingDistance, SwitchWalkBlindCondition = rd => rd <= PilotDefinition.Conf.TireFollowingLeaveCarWalkBlindSwitchingDistance,
FinishWalkBlindCondition = rd => rd <= PilotDefinition.Conf.TireFollowingWalkBlindFinishDistance, FinishWalkBlindCondition = rd => rd <= PilotDefinition.Conf.TireFollowingWalkBlindFinishDistance,
PathTransformation = new Tuple<float, float, float>( PathTransformation = new Tuple<float, float, float>(
PilotDefinition.Conf.TireFollowingLeaveCarBackLidarPathTransformationX, PilotDefinition.Conf.TireFollowingLeaveCarBackLidarPathTransformationX,
@@ -190,39 +193,97 @@ namespace MultiWheelC
WalkBlindTh = 0, WalkBlindTh = 0,
TireNum = 1 TireNum = 1
}; };
var _dt = new DriveTask(following.Get());
_dt.Wait(); IEnumerable<bool> LeaveThenFollow()
new DriveTask(new DstTracker()
{ {
Src = new Vector2(srcX, srcY), foreach (var running in following.Get())
Dst = new Vector2(dstX, dstY), {
}.Get()); Wait(); if (!running) break;
DLog.Log("钻车动作结束", "TireFollowing"); yield return true;
}
DLog.Log($"释放锁点{srcId}完成", "TireFollowing");
DLog.Log("离车TireFollowing结束,开始DstTracker", "TireFollowing");
foreach (var running in new DstTracker()
{
Src = new Vector2(srcX, srcY),
Dst = new Vector2(dstX, dstY),
CarDirectionBias = 180f,
}.Get())
{
if (!running) break;
yield return true;
}
yield return false;
}
var _dt = new DriveTask(LeaveThenFollow());
_dt.Wait();
DLog.Log("离车动作1结束", "TireFollowing");
} }
//驱动器上使能 public void LineTracking(int srcId, float srcX, float srcY, int dstId, float dstX, float dstY)
{
while (!TryLock(dstId))
{
Thread.Sleep(50);
}
DLog.Log($"锁点{dstId}完成", "TireFollowing");
IEnumerable<bool> TrackThenFollow()
{
foreach (var running in new LineTracking()
{
Target = PilotDefinition.Conf.LineTrackDistance + (PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2,
LeaveSrcFunction = Leave,
SrcId = srcId,
}.Get())
{
if (!running) break;
yield return true;
}
DLog.Log("离车LineTracking结束,开始DstTracker", "TireFollowing");
foreach (var running in new DstTracker()
{
Src = new Vector2(srcX, srcY),
Dst = new Vector2(dstX, dstY),
}.Get())
{
if (!running) break;
yield return true;
}
yield return false;
}
var _dt = new DriveTask(TrackThenFollow());
_dt.Wait();
//Leave(srcId);
DLog.Log($"释放锁点{srcId}完成", "TireFollowing");
DLog.Log("离车动作2结束", "TireFollowing");
}
//驱动器上使能
public void DriverAble() public void DriverAble()
{ {
var dl = new DriveTask(new DriverAble() { }.Get()); var dl = new DriveTask(new DriverAble() { }.Get());
dl.Wait(); dl.Wait();
DLog.Log("驱动器上使能完成", "TireFollowing"); DLog.Log("驱动器上使能完成", "TireFollowing");
} }
//驱动器下使能 //驱动器下使能
public void DriverDisable() public void DriverDisable()
{ {
var dl = new DriveTask(new DriverDisable() { }.Get()); var dl = new DriveTask(new DriverDisable() { }.Get());
dl.Wait(); dl.Wait();
DLog.Log("驱动器下使能完成", "TireFollowing"); DLog.Log("驱动器下使能完成", "TireFollowing");
} }
// 夹抱:close 为 true 时关闭夹抱,否则打开夹抱。 // 夹抱:close 为 true 时关闭夹抱,否则打开夹抱。
public void ClamptoTarget(bool close) public void ClamptoTarget(bool close)
{ {
if (PilotDefinition.Self.GhostMode) if (PilotDefinition.Self.GhostMode)
{ {
Thread.Sleep(2000); Thread.Sleep(2000);
Console.WriteLine("夹抱完成"); Console.WriteLine("夹抱完成");
return; return;
} }
new DriveTask(new ClampToTarget() new DriveTask(new ClampToTarget()
@@ -231,8 +292,8 @@ namespace MultiWheelC
RightClampTarget = close ? PilotDefinition.Self.RightArmUpperPos : PilotDefinition.Self.RightArmLowerPos RightClampTarget = close ? PilotDefinition.Self.RightArmUpperPos : PilotDefinition.Self.RightArmLowerPos
}.Get()).Wait(); }.Get()).Wait();
} }
// 车队联动-自动蟹行:供调度按绝对起终点触发。 // 车队联动-自动蟹行:供调度按绝对起终点触发。
// CarDirectionBias 表示车队方向相对路径方向的夹角,逆时针为正。 // CarDirectionBias 表示车队方向相对路径方向的夹角,逆时针为正。
public void FleetCrabWalk(float srcX, float srcY, int srcId, float dstX, float dstY, int dstId, public void FleetCrabWalk(float srcX, float srcY, int srcId, float dstX, float dstY, int dstId,
float speed, float CarDirectionBias) float speed, float CarDirectionBias)
{ {
@@ -256,7 +317,7 @@ namespace MultiWheelC
{ {
Thread.Sleep(50); Thread.Sleep(50);
} }
DLog.Log($"锁点{dstId}完成", "FleetCrabDbg"); DLog.Log($"锁点{dstId}完成", "FleetCrabDbg");
} }
var action = new MultiWheelC.FleetCrabWalk var action = new MultiWheelC.FleetCrabWalk
@@ -286,33 +347,11 @@ namespace MultiWheelC
if (srcId != -1) if (srcId != -1)
{ {
Leave(srcId); Leave(srcId);
DLog.Log($"释放放车点{srcId}", "FleetCrabDbg"); DLog.Log($"释放放车点{srcId}", "FleetCrabDbg");
} }
} }
} }
public void LineTracking(int srcId, float srcX, float srcY, int dstId, float dstX, float dstY)
{
while (!TryLock(dstId))
{
Thread.Sleep(50);
}
DLog.Log($"锁点{dstId}完成", "TireFollowing");
new DriveTask(new LineTracking()
{
Target = PilotDefinition.Conf.LineTrackDistance + (PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2,
//LeaveSrcFunction = Leave,
//SrcId = srcId,
}.Get()).Wait();
new DriveTask(new DstTracker()
{
Src = new Vector2(srcX, srcY),
Dst = new Vector2(dstX, dstY),
}.Get());Wait();
Leave(srcId);
DLog.Log($"释放锁点{srcId}完成", "TireFollowing");
}
public void ChangeAvoidanceDistance(float stopDistance, float slowDistance) public void ChangeAvoidanceDistance(float stopDistance, float slowDistance)
{ {
@@ -390,13 +390,6 @@ namespace MultiWheelC
if (!running) break; if (!running) break;
yield return true; yield return true;
} }
// 衔接速度:避免第一段结束后瞬间“掉速”,让第二段接管更连贯
var chassis = (MultiWheelChassis)PilotDefinition.Chassis;
var handoverSpeed = Math.Max(0.08f, Math.Min(PilotDefinition.Conf.DstTrackerMaxSpeed, 0.2f));
var direction = Math.Sign(dst.x - src.x);
if (direction == 0) direction = 1;
chassis.SendXYThSpeed(handoverSpeed * direction, 0f, 0f);
yield return true;
foreach (var running in new DstTracker() foreach (var running in new DstTracker()
{ {
Src = src, Src = src,
@@ -415,7 +408,87 @@ namespace MultiWheelC
private DriveTask _dt; private DriveTask _dt;
} }
[MovementTest(name = "测试先离车再终点跟踪")]
public class LeaveCarThenDstTrackerTest : MovementTest
{
private readonly Painter _painter = UI.GetPainter("LeaveCarThenDstTrackerTest");
public override void TestStop()
{
_dt?.Stop();
_painter?.Clear();
}
public override void Test()
{
var src = UI.GetPoint("请在上位机选择离车后起点(src)");
var dst = UI.GetPoint("请在上位机选择终点(dst)");
_painter.Clear();
_painter.DrawLine(Color.Cyan, src.X, src.Y, dst.X, dst.Y, width: 3);
_painter.DrawCircle(Color.LimeGreen, src.X, src.Y, 80f);
_painter.DrawCircle(Color.OrangeRed, dst.X, dst.Y, 80f);
_painter.DrawText(Color.LimeGreen, "src", src.X + 80f, src.Y + 80f);
_painter.DrawText(Color.OrangeRed, "dst", dst.X + 80f, dst.Y + 80f);
IEnumerable<bool> LeaveThenFollow()
{
var following = new TireFollowing()
{
GetController = () => new ChassisController().Get(),
GuessRangeX = PilotDefinition.Conf.TireFilterLength / 2,
GuessRangeY = PilotDefinition.Conf.TireFilterWidth / 2,
detectors = new List<TireFollowing.DetectorDefinition>()
{
new TireFollowing.DetectorDefinition()
{
DetectFunction = (_, lastDetectX, filters) => TireDetect.Detect(lastDetectX, filters, false),
StartGuessingX = -PilotDefinition.Conf.TireFollowingStage2GuessX,
StartGuessingY = 0,
SwitchWalkBlindCondition = rd => rd <= PilotDefinition.Conf.TireFollowingWalkBlindSwitchingDistance,
FinishWalkBlindCondition = rd => rd <= PilotDefinition.Conf.TireFollowingWalkBlindFinishDistance,
PathTransformation = new Tuple<float, float, float>(
PilotDefinition.Conf.TireFollowingLeaveCarBackLidarPathTransformationX,
PilotDefinition.Conf.TireFollowingBackLidarPathTransformationY,
0),
},
},
CarDirection = 180f,
SlowDistance = PilotDefinition.Conf.TireFollowingSlowDistance,
MaxSpeed = PilotDefinition.Conf.TireFollowingMaxSpeed,
WalkBlindTh = 0,
TireNum = 1
};
foreach (var running in following.Get())
{
if (!running) break;
yield return true;
}
foreach (var running in new DstTracker()
{
Src = src,
Dst = dst,
CarDirectionBias = 180f,
}.Get())
{
if (!running) break;
yield return true;
}
yield return false;
}
_dt = new DriveTask(LeaveThenFollow());
_dt.Wait();
}
private DriveTask _dt;
}
[MovementTest(name = "驱动器下使能测试")]
public class DriverDisableTest : MovementTest public class DriverDisableTest : MovementTest
{ {
public override void TestStop() public override void TestStop()
+43 -31
View File
@@ -25,6 +25,7 @@ using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using static ClumsyCore.DTools.Painter;
namespace MultiWheelC namespace MultiWheelC
{ {
@@ -208,43 +209,20 @@ namespace MultiWheelC
public Action<int> LeaveSrcFunction = null; public Action<int> LeaveSrcFunction = null;
private PIDController pid; private PIDController pid;
// GhostMode 虚拟里程计
private float _ghostDistance;
private DateTime _lastTick;
public override IEnumerable<bool> Get() public override IEnumerable<bool> Get()
{ {
bool isGhost = PilotDefinition.Self.GhostMode;
if (isGhost)
{
_ghostDistance = 0f;
_lastTick = DateTime.Now;
}
pid = new PIDController(() => pid = new PIDController(() =>
isGhost ? _ghostDistance : (PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2, (PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2,
Kp, Ki, Kd, 0, DeadZone, MaxSpeed) Kp, Ki, Kd, 0, DeadZone, MaxSpeed)
{ SpeedAccPerSec = MaxSpeed / 2f }; { SpeedAccPerSec = MaxSpeed / 2f };
var chassis = (MultiWheelChassis)PilotDefinition.Chassis; var chassis = (MultiWheelChassis)PilotDefinition.Chassis;
DLog.Log($"直线行驶距离:{Target}", "TireFollowing");
while (true) while (true)
{ {
var speed = pid.GetResponse(Target); var speed = pid.GetResponse(Target);
Console.WriteLine($"output: {speed} current: {(PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2}");
if (isGhost)
{
var now = DateTime.Now;
float dt = (float)(now - _lastTick).TotalSeconds;
_ghostDistance += speed * dt * 1000f;
_lastTick = now;
Console.WriteLine($"[Ghost] output: {speed:F2} current: {_ghostDistance:F2}");
}
else
{
Console.WriteLine($"output: {speed} current: {(PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2}");
}
var current = isGhost ? _ghostDistance : (PilotDefinition.Self.LFLActualPos + PilotDefinition.Self.LFRActualPos) / 2;
chassis.SendXYThSpeed(speed, 0, 0); chassis.SendXYThSpeed(speed, 0, 0);
if (pid.IsArrived()) break; if (pid.IsArrived()) break;
@@ -261,26 +239,60 @@ namespace MultiWheelC
public class DriverAble : MovementDefinition public class DriverAble : MovementDefinition
{ {
public int WaitTimeoutMs = 2000;
public int PollIntervalMs = 50;
public override IEnumerable<bool> Get() public override IEnumerable<bool> Get()
{ {
Console.WriteLine("驱动器上使能"); Console.WriteLine("驱动器上使能");
PilotDefinition.Self.ResetFromC = true; PilotDefinition.Self.ResetFromC = true;
Thread.Sleep(200);
var start = DateTime.Now;
var timeoutMs = Math.Max(0, WaitTimeoutMs);
var pollMs = Math.Max(1, PollIntervalMs);
var success = PilotDefinition.Self.WheelAbleState;
while (!success && (DateTime.Now - start).TotalMilliseconds < timeoutMs)
{
Thread.Sleep(pollMs);
success = PilotDefinition.Self.WheelAbleState;
if (!success) yield return true;
}
PilotDefinition.Self.ResetFromC = false; PilotDefinition.Self.ResetFromC = false;
Console.WriteLine("驱动器上使能完成"); if (success)
Console.WriteLine($"驱动器上使能完成,WheelAbleState={PilotDefinition.Self.WheelAbleState}");
else
Console.WriteLine($"驱动器上使能超时,WheelAbleState={PilotDefinition.Self.WheelAbleState},等待{timeoutMs}ms");
yield return false; yield return false;
} }
} }
public class DriverDisable : MovementDefinition public class DriverDisable : MovementDefinition
{ {
public int WaitTimeoutMs = 3000;
public int PollIntervalMs = 20;
public override IEnumerable<bool> Get() public override IEnumerable<bool> Get()
{ {
Console.WriteLine("驱动器下使能"); Console.WriteLine("驱动器下使能");
PilotDefinition.Self.DisableFromC = true; PilotDefinition.Self.DisableFromC = true;
Thread.Sleep(200);
var start = DateTime.Now;
var timeoutMs = Math.Max(0, WaitTimeoutMs);
var pollMs = Math.Max(1, PollIntervalMs);
var success = !PilotDefinition.Self.WheelAbleState;
while (!success && (DateTime.Now - start).TotalMilliseconds < timeoutMs)
{
Thread.Sleep(pollMs);
success = !PilotDefinition.Self.WheelAbleState;
if (!success) yield return true;
}
PilotDefinition.Self.DisableFromC = false; PilotDefinition.Self.DisableFromC = false;
Console.WriteLine("驱动器下使能完成"); if (success)
Console.WriteLine($"驱动器下使能完成,WheelAbleState={PilotDefinition.Self.WheelAbleState}");
else
Console.WriteLine($"驱动器下使能超时,WheelAbleState={PilotDefinition.Self.WheelAbleState},等待{timeoutMs}ms");
yield return false; yield return false;
} }
} }
@@ -132,6 +132,7 @@ public class PilotDefinition : MultiWheelPilotDefinition<PilotConfig, PilotDefin
[AsUpperIO(desc = "从C往驱动器下使能")] public bool DisableFromC = false; [AsUpperIO(desc = "从C往驱动器下使能")] public bool DisableFromC = false;
[AsUpperIO(desc = "从C上复位")] public bool ResetFromC = false; [AsUpperIO(desc = "从C上复位")] public bool ResetFromC = false;
[AsLowerIO(desc = "驱动轮使能状态")] public bool WheelAbleState = true;
private float _multiVehicleAccumulateTh; private float _multiVehicleAccumulateTh;
private DateTime _multiVehicleLastThTime = DateTime.Now; private DateTime _multiVehicleLastThTime = DateTime.Now;