205 lines
6.7 KiB
C#
205 lines
6.7 KiB
C#
using RobotNet10.RobotApp.Devices;
|
|
using RobotNet10.RobotApp.SLAM;
|
|
using RobotNet10.Shared.Geometry;
|
|
|
|
namespace RobotNet10.RobotApp.Detection;
|
|
|
|
public class DetectSession(Guid id, ISLAMService sLAMService) : IDetectSession
|
|
{
|
|
private static uint SequenceDetection = 1;
|
|
private readonly List<DetectorInfo> _detectors = [];
|
|
private readonly Lock _lockGoal = new();
|
|
private Thread? _updateThread;
|
|
private bool _isRunning = false;
|
|
private const int UPDATE_FREQUENCY_HZ = 30;
|
|
private const double DETECTION_TIMEOUT_SECONDS = 2.0;
|
|
|
|
public Guid SessionId { get; } = id;
|
|
|
|
public PoseStamped? Goal
|
|
{
|
|
get
|
|
{
|
|
lock (_lockGoal)
|
|
{
|
|
return field;
|
|
}
|
|
}
|
|
private set
|
|
{
|
|
lock (_lockGoal)
|
|
{
|
|
field = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddShapeReflectiveDetector(string markerId, int priority, Point2D[] markerReferencePoints, RectangleRegion searchRegion, ILidar lidar, Pose lidarPose, double intensityThreshold = 1000)
|
|
{
|
|
// Create detector with configurable intensity threshold
|
|
var detector = new ShapeReflectiveDetector(
|
|
markerReferencePoints,
|
|
searchRegion,
|
|
sLAMService,
|
|
lidar,
|
|
lidarPose,
|
|
intensityThreshold);
|
|
|
|
// Add to list with priority
|
|
_detectors.Add(new DetectorInfo(markerId, priority, detector));
|
|
|
|
// Sort by priority (higher priority first)
|
|
_detectors.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
|
}
|
|
|
|
public void AddQRDetector(string markerId, int priority, string qrCode, ICameraQr camera, Pose cameraPose)
|
|
{
|
|
// Create QR detector
|
|
var detector = new QRDetector(
|
|
qrCode,
|
|
sLAMService,
|
|
camera,
|
|
cameraPose);
|
|
|
|
// Add to list with priority
|
|
_detectors.Add(new DetectorInfo(markerId, priority, detector));
|
|
|
|
// Sort by priority (higher priority first)
|
|
_detectors.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
if (_isRunning)
|
|
return;
|
|
|
|
_isRunning = true;
|
|
|
|
// Activate all detectors
|
|
foreach (var detectorInfo in _detectors)
|
|
{
|
|
detectorInfo.Detector.Active();
|
|
}
|
|
|
|
// Create and start goal update thread (30Hz)
|
|
_updateThread = new Thread(UpdateGoalLoop)
|
|
{
|
|
Name = "DetectSession_UpdateGoal",
|
|
IsBackground = true
|
|
};
|
|
_updateThread.Start();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!_isRunning)
|
|
return;
|
|
|
|
_isRunning = false;
|
|
|
|
// Disable all detectors
|
|
foreach (var detectorInfo in _detectors)
|
|
{
|
|
detectorInfo.Detector.Disable();
|
|
detectorInfo.Detector.Dispose();
|
|
}
|
|
|
|
// Wait for thread to finish
|
|
_updateThread?.Join();
|
|
_updateThread = null;
|
|
|
|
// Clear detectors list
|
|
_detectors.Clear();
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thread loop to update Goal based on detector priorities at 30Hz
|
|
/// Filters detectors by detection time, pose validity, and priority
|
|
/// </summary>
|
|
private void UpdateGoalLoop()
|
|
{
|
|
int delayMs = 1000 / UPDATE_FREQUENCY_HZ; // ~33ms for 30Hz
|
|
|
|
while (_isRunning)
|
|
{
|
|
try
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
PoseStamped? bestGoal = null;
|
|
int bestPriority = int.MinValue;
|
|
DateTime bestDetectionTime = DateTime.MinValue;
|
|
|
|
// Find best detector based on priority and detection freshness
|
|
foreach (var detectorInfo in _detectors)
|
|
{
|
|
var markerPose = detectorInfo.Detector.MarkerPose;
|
|
var detectionTime = detectorInfo.Detector.DetectionTime;
|
|
|
|
// Skip if pose is invalid (default value)
|
|
if (IsDefaultPose(markerPose))
|
|
continue;
|
|
|
|
// Skip if detection is too old (timeout)
|
|
var timeSinceDetection = (now - detectionTime).TotalSeconds;
|
|
if (timeSinceDetection > DETECTION_TIMEOUT_SECONDS)
|
|
continue;
|
|
|
|
// Select detector with highest priority
|
|
// If same priority, prefer more recent detection
|
|
if (detectorInfo.Priority > bestPriority ||
|
|
(detectorInfo.Priority == bestPriority && detectionTime > bestDetectionTime))
|
|
{
|
|
bestGoal = new PoseStamped()
|
|
{
|
|
Header = new RobotNet10.Shared.Header()
|
|
{
|
|
FrameId = detectorInfo.MakerId,
|
|
Stamp = detectionTime,
|
|
Seq = ++SequenceDetection,
|
|
},
|
|
Pose = markerPose,
|
|
};
|
|
bestPriority = detectorInfo.Priority;
|
|
bestDetectionTime = detectionTime;
|
|
}
|
|
}
|
|
|
|
// Update goal if we found a valid one
|
|
if (bestGoal.HasValue)
|
|
{
|
|
Goal = bestGoal.Value;
|
|
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff} [DetectSession] Updated Goal from detector '{bestGoal.Value.Header.FrameId}' with priority {bestPriority} at pose: [{bestGoal.Value.Pose.Position.X}, {bestGoal.Value.Pose.Position.Y}, {bestGoal.Value.Pose.Orientation.ToYawDegrees()}deg]");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors in update loop
|
|
}
|
|
|
|
// Sleep to maintain 30Hz update rate
|
|
Thread.Sleep(delayMs);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if pose is default/invalid
|
|
/// </summary>
|
|
private static bool IsDefaultPose(Pose pose)
|
|
{
|
|
return pose.Position.X == 0 &&
|
|
pose.Position.Y == 0 &&
|
|
pose.Position.Z == 0 &&
|
|
pose.Orientation.X == 0 &&
|
|
pose.Orientation.Y == 0 &&
|
|
pose.Orientation.Z == 0 &&
|
|
pose.Orientation.W == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal class to store detector with priority
|
|
/// </summary>
|
|
private record DetectorInfo(string MakerId, int Priority, IDetector Detector);
|
|
}
|