Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

@@ -0,0 +1,699 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RobotNet10.RobotApp.Devices;
using RobotNet10.Shared;
using RobotNet10.Shared.Geometry;
using RobotNet10.Shared.Sensor;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace RobotNet10.RobotApp.MarkerDetection;
/// <summary>
/// Integration service to dispatch laser scan data to marker detection engine
/// Provides rectangle or segment marker detection with pose estimation
/// </summary>
public class MarkerDetectionIntegrationService : IHostedService, IDisposable
{
private readonly MarkerDetectionIntegrationConfiguration _config;
private readonly IDeviceProvider _deviceProvider;
private readonly ILogger<MarkerDetectionIntegrationService> _logger;
private readonly object _lock = new();
private readonly SemaphoreSlim _dispatchSemaphore = new SemaphoreSlim(1, 1); // Only 1 dispatch at a time
private MarkerDetectionClient? _markerDetectionClient;
// Timer for periodic marker pose updates and logging
private Timer? _updateTimer;
// Cached device references
private ILidar? _lidarDevice;
// Statistics (accessed from multiple threads)
private long _laserScanCount = 0;
private long _markerDetectionCount = 0;
private DateTime _lastStatsLog = DateTime.UtcNow;
private bool _disposed = false;
private bool _isInitialized = false;
public MarkerDetectionIntegrationService(
IConfiguration configuration,
IDeviceProvider deviceProvider,
ILogger<MarkerDetectionIntegrationService> logger)
{
_deviceProvider = deviceProvider ?? throw new ArgumentNullException(nameof(deviceProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// Load configuration
var configSection = configuration.GetSection("MarkerDetection:Integration");
if (!configSection.Exists())
{
_logger.LogWarning("Configuration section 'MarkerDetection:Integration' not found. Using defaults.");
_config = new MarkerDetectionIntegrationConfiguration();
}
else
{
_config = new MarkerDetectionIntegrationConfiguration();
configSection.Bind(_config);
}
ValidateConfiguration();
}
private void ValidateConfiguration()
{
if (_config.Enabled)
{
if (_config.LaserScanDispatchIntervalMs <= 0 || _config.LaserScanDispatchIntervalMs > 1000)
{
_logger.LogWarning("LaserScanDispatchIntervalMs {Interval} is out of range [1-1000]. Using default 50 ms.", _config.LaserScanDispatchIntervalMs);
_config.LaserScanDispatchIntervalMs = 50;
}
if (_config.MarkerType == MarkerDetectionType.Rectangle && _config.RectangleMarkerOptions == null)
{
_logger.LogWarning("Rectangle marker type selected but options not provided. Using defaults.");
_config.RectangleMarkerOptions = new MarkerDetectionRectangleOptions();
}
if (_config.MarkerType == MarkerDetectionType.Segment && _config.SegmentMarkerOptions == null)
{
_logger.LogWarning("Segment marker type selected but options not provided. Using defaults.");
_config.SegmentMarkerOptions = new MarkerDetectionSegmentOptions();
}
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_config.Enabled)
{
_logger.LogInformation("MarkerDetectionIntegrationService is disabled in configuration");
return;
}
_logger.LogInformation("Starting MarkerDetectionIntegrationService...");
try
{
// Wait for devices to be connected
_logger.LogInformation("Waiting for devices to be connected...");
var connected = await _deviceProvider.WaitForDevicesConnectedAsync(TimeSpan.FromMinutes(5), cancellationToken);
if (!connected)
{
_logger.LogWarning("Timeout waiting for devices. MarkerDetectionIntegrationService will retry initialization.");
_ = Task.Run(async () => await RetryInitializationAsync(cancellationToken), cancellationToken);
return;
}
await InitializeAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting MarkerDetectionIntegrationService: {Message}", ex.Message);
}
}
private async Task RetryInitializationAsync(CancellationToken cancellationToken)
{
const int maxRetries = 60; // 5 minutes with 5 second intervals
int retryCount = 0;
while (retryCount < maxRetries && !cancellationToken.IsCancellationRequested && !_isInitialized)
{
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
var connected = await _deviceProvider.WaitForDevicesConnectedAsync(TimeSpan.FromSeconds(1), cancellationToken);
if (connected)
{
_logger.LogInformation("Devices are now connected. Initializing MarkerDetectionIntegrationService...");
await InitializeAsync(cancellationToken);
return;
}
retryCount++;
if (retryCount % 12 == 0) // Log every minute
{
_logger.LogInformation("Still waiting for devices... (attempt {Attempt}/{MaxAttempts})",
retryCount, maxRetries);
}
}
if (retryCount >= maxRetries)
{
_logger.LogWarning("Timeout waiting for devices. MarkerDetectionIntegrationService will not be initialized.");
}
}
private async Task InitializeAsync(CancellationToken cancellationToken)
{
lock (_lock)
{
if (_isInitialized)
return;
try
{
// Get LIDAR device reference
if (!string.IsNullOrEmpty(_config.LidarDeviceId))
{
var device = _deviceProvider.GetDevice(_config.LidarDeviceId);
if (device != null)
{
_logger.LogInformation(
"[LIDAR-INIT] Device '{DeviceId}' found. Type: {DeviceType}, IsConnected: {IsConnected}",
_config.LidarDeviceId, device.GetType().Name, device.IsConnected);
}
if (device is ILidar lidar && device.IsConnected)
{
_lidarDevice = lidar;
_logger.LogInformation(
"[LIDAR-INIT] SUCCESS: LIDAR device '{DeviceId}' is initialized and connected. Ready to receive laser scan data.",
_config.LidarDeviceId);
}
else
{
_logger.LogWarning("LIDAR device '{DeviceId}' not found or not connected. Marker detection will not work.",
_config.LidarDeviceId);
return;
}
}
else
{
_logger.LogWarning("No LIDAR device ID configured. Marker detection will not work.");
return;
}
// Create and initialize marker detection client
_markerDetectionClient = new MarkerDetectionClient(_logger);
// Initialize with appropriate marker type
if (_config.MarkerType == MarkerDetectionType.Rectangle && _config.RectangleMarkerOptions != null)
{
_markerDetectionClient.InitializeRectangle(_config.RectangleMarkerOptions);
}
else if (_config.MarkerType == MarkerDetectionType.Segment && _config.SegmentMarkerOptions != null)
{
_markerDetectionClient.InitializeSegment(_config.SegmentMarkerOptions);
}
else
{
throw new InvalidOperationException("Invalid marker type configuration or missing marker options.");
}
_logger.LogInformation("MarkerDetectionClient initialized successfully");
// Start laser scan dispatch loop as background task (non-blocking)
if (_lidarDevice != null)
{
_ = Task.Run(async () => await LaserScanDispatchLoopAsync(cancellationToken), cancellationToken);
_logger.LogInformation("Laser scan dispatch loop started at {Rate} Hz", 1000.0 / _config.LaserScanDispatchIntervalMs);
}
// Start periodic update timer for pose logging and status updates
_updateTimer = new Timer(UpdatePoseAndDiagnostics, null,
TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
_logger.LogInformation("MarkerDetection update timer started (every 2 seconds)");
_isInitialized = true;
_logger.LogInformation("MarkerDetectionIntegrationService initialized successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize MarkerDetectionIntegrationService: {Message}", ex.Message);
throw;
}
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Stopping MarkerDetectionIntegrationService...");
try
{
lock (_lock)
{
_isInitialized = false;
// Dispose timer
_updateTimer?.Dispose();
_updateTimer = null;
}
// Wait a bit for dispatch loops to notice _isInitialized = false and stop
_logger.LogInformation("Waiting for dispatch loops to stop...");
await Task.Delay(500, cancellationToken);
// Clean up marker detection client
if (_markerDetectionClient != null)
{
_markerDetectionClient.Dispose();
_markerDetectionClient = null;
_logger.LogInformation("MarkerDetectionClient disposed successfully");
}
LogFinalStatistics();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping MarkerDetectionIntegrationService: {Message}", ex.Message);
}
await Task.CompletedTask;
}
/// <summary>
/// Laser scan dispatch loop - runs in background task
/// </summary>
private async Task LaserScanDispatchLoopAsync(CancellationToken cancellationToken)
{
int noDataCounter = 0;
while (_config.Enabled && !cancellationToken.IsCancellationRequested)
{
if (!_isInitialized || _markerDetectionClient == null || _lidarDevice == null || !_markerDetectionClient.IsInitialized)
{
await Task.Delay(100, cancellationToken);
continue;
}
try
{
var scan = _lidarDevice.CurrentMeasurementData;
// Log periodically to monitor intensities status (every ~5 seconds at 20Hz)
if (scan.HasValue && _laserScanCount % 100 == 0)
{
var rawScan = scan.Value;
if (rawScan.Intensities == null || rawScan.Intensities.Length == 0)
{
_logger.LogWarning(
"[MARKER-DETECTION] LIDAR NOT providing intensities (Ranges: {RangeCount}). " +
"Using default values. Check LIDAR RSSI configuration.",
rawScan.Ranges?.Length ?? 0);
}
// else
// {
// _logger.LogInformation(
// "[MARKER-DETECTION] ✓ LIDAR providing REAL intensities: {Count} values, " +
// "Sample: [{I0:F3}, {I1:F3}, {I2:F3}]",
// rawScan.Intensities.Length,
// rawScan.Intensities[0],
// rawScan.Intensities.Length > 1 ? rawScan.Intensities[1] : 0,
// rawScan.Intensities.Length > 2 ? rawScan.Intensities[2] : 0);
// }
}
if (scan == null)
{
noDataCounter++;
if (noDataCounter % 100 == 0) // Log every 100 iterations (5 seconds at 20Hz)
{
// _logger.LogWarning(
// "[LIDAR-DATA-DEBUG] CurrentMeasurementData is NULL. " +
// "LIDAR device may not be connected or initialized. " +
// "Configured device: '{LidarDeviceId}'. Missing data for {MissingIterations} iterations.",
// _config.LidarDeviceId, noDataCounter);
}
continue;
}
var originalScan = scan.Value; // Assuming CurrentMeasurementData is nullable
//in ra dữ liệu intensities và ranges để debug
// _logger.LogInformation("LIDAR Data Debug - Ranges Count: {RangesCount}, Intensities Count: {IntensitiesCount}",
// originalScan.Ranges?.Length ?? 0, originalScan.Intensities?.Length ?? 0);
// Validate scan data before processing
// LIDAR must provide at least range data (Intensities may be empty)
if (originalScan.Ranges == null || originalScan.Ranges.Length == 0)
{
noDataCounter++;
// if (noDataCounter % 100 == 0)
// {
// _logger.LogWarning(
// "[LIDAR-DATA-DEBUG] No RANGE data in laser scan. " +
// "Ranges: {RangeLength}, Intensities: {IntensityLength}. " +
// "LIDAR device '{LidarDeviceId}' is not providing valid range measurements. " +
// "Missing data for {MissingIterations} iterations.",
// originalScan.Ranges?.Length ?? 0,
// originalScan.Intensities?.Length ?? 0,
// _config.LidarDeviceId,
// noDataCounter);
// }
continue;
}
// Ensure Intensities array is not null
// If LIDAR doesn't provide intensities, populate with default values
// if (originalScan.Intensities == null || originalScan.Intensities.Length == 0)
// {
// originalScan.Intensities = new float[originalScan.Ranges.Length];
// for (int i = 0; i < originalScan.Intensities.Length; i++)
// {
// originalScan.Intensities[i] = 1.0f; // Default fallback value
// }
// }
// Data is valid - reset counter
noDataCounter = 0;
// Log once per second when valid data is being received
// if (noDataCounter == 0 && _laserScanCount % 20 == 0) // ~1 second at 20Hz
// {
// _logger.LogDebug(
// "[LIDAR-DATA-OK] Valid laser scan received: Ranges: {RangeLength}, Intensities: {IntensityLength}",
// originalScan.Ranges.Length,
// originalScan.Intensities.Length);
// }
// Create updated scan with current timestamp
var updatedScan = new LaserScan
{
Header = new Header
{
Seq = originalScan.Header.Seq,
Stamp = DateTime.UtcNow,
FrameId = "scan"
},
AngleMin = (float)-2.356194496154785,
AngleMax = (float)2.356194496154785,
AngleIncrement =(float) 0.00581718236207962,
TimeIncrement = (float) 6.172839493956417e-05,
ScanTime = (float) 0.06666667014360428,
RangeMin = (float) 0.0,
RangeMax = (float) 100.0,
Ranges = originalScan.Ranges,
Intensities = originalScan.Intensities
};
// In ra tất cả giũ liệu trong mảng Intensities
// if (updatedScan.Intensities != null)
// {
// _logger.LogDebug("Intensities array length: {Length}", updatedScan.Intensities.Length);
// for (int i = 0; i < Math.Min(811, updatedScan.Intensities.Length); i++)
// {
// _logger.LogInformation("Intensity[{Index}]: {Value}", i, originalScan.Intensities[i]);
// _logger.LogInformation("Range[{Index}]: {Value}", i, updatedScan.Ranges[i]);
// }
// }
// In ra tất cả giũ liệu trong mảng Ranges và Intensities trong cùng 1 dòng và Seq, Stamp, FrameId để debug
// if (updatedScan.Intensities != null)
// {
// for (int i = 0; i < Math.Min(811, updatedScan.Intensities.Length); i++)
// {
// _logger.LogInformation("Detect marker Seq: {Seq}, Stamp: {Stamp}, FrameId: {FrameId}, Range[{Index}]: {Range}, Intensity[{Index}]: {Intensity}",
// updatedScan.Header.Seq,
// updatedScan.Header.Stamp.ToString("HH:mm:ss.ffff"),
// updatedScan.Header.FrameId,
// i,
// updatedScan.Ranges[i],
// i,
// updatedScan.Intensities[i]);
// }
// }
// Dispatch laser scan to marker detection
// Re-check that client is still valid (could have been disposed by another thread)
if (_markerDetectionClient == null || !_markerDetectionClient.IsInitialized)
{
continue;
}
await _dispatchSemaphore.WaitAsync(cancellationToken);
try
{
// Double-check again after acquiring semaphore
if (_markerDetectionClient != null && _markerDetectionClient.IsInitialized)
{
int rangeCount = updatedScan.Ranges?.Length ?? 0;
int intensityCount = updatedScan.Intensities?.Length ?? 0;
// _logger.LogInformation(
// "[MARKER-DETECTION-DISPATCH] Sending to native code - " +
// "Ranges: {RangeCount}, Intensities: {IntensityStatus} (Count: {IntensityCount})",
// rangeCount,
// updatedScan.Intensities == null ? "NULL" : "OK",
// intensityCount);
// if (updatedScan.Intensities != null && intensityCount > 2)
// {
// _logger.LogInformation(
// "[MARKER-DETECTION-DISPATCH] First 3 intensities being sent: [{I0:F2}, {I1:F2}, {I2:F2}]",
// updatedScan.Intensities[0],
// updatedScan.Intensities[1],
// updatedScan.Intensities[2]);
// }
_markerDetectionClient.DispatchLaserScan(updatedScan, _config.LaserScanSensorId ?? "scan");
Interlocked.Increment(ref _laserScanCount);
}
}
finally
{
_dispatchSemaphore.Release();
}
}
catch (InvalidOperationException ex) when (ex.Message.Contains("not initialized"))
{
// MarkerDetectionClient was disposed during shutdown - this is normal, just exit gracefully
_logger.LogDebug("Laser scan dispatch loop stopped: {Message}", ex.Message);
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error dispatching laser scan data: {Message}", ex.Message);
}
// Wait for next cycle
await Task.Delay(_config.LaserScanDispatchIntervalMs, cancellationToken);
}
}
/// <summary>
/// Periodic timer callback to update marker pose and log diagnostics
/// </summary>
private void UpdatePoseAndDiagnostics(object? state)
{
if (!_isInitialized || _markerDetectionClient == null)
return;
try
{
// Get current marker pose if available
if (_markerDetectionClient.IsInitialized)
{
var pose = _markerDetectionClient.GetMarkerPose();
if (pose != null)
{
Interlocked.Increment(ref _markerDetectionCount);
// _logger.LogDebug("Marker pose: x={X}, y={Y}, z={Z}, qx={Qx}, qy={Qy}, qz={Qz}, qw={Qw}, frame={Frame}",
// pose.Pose.Position[0], pose.Pose.Position[1], pose.Pose.Position[2],
// pose.Pose.Orientation[0], pose.Pose.Orientation[1], pose.Pose.Orientation[2], pose.Pose.Orientation[3],
// pose.Header.FrameId);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating marker pose and diagnostics: {Message}", ex.Message);
}
// Log statistics periodically
var now = DateTime.UtcNow;
if ((now - _lastStatsLog).TotalSeconds >= 10)
{
_lastStatsLog = now;
LogStatistics();
}
}
private void LogStatistics()
{
var laserCount = Interlocked.Read(ref _laserScanCount);
var markerCount = Interlocked.Read(ref _markerDetectionCount);
if (laserCount > 0)
{
var laserRate = laserCount / ((DateTime.UtcNow - _lastStatsLog).TotalSeconds + 0.001);
// _logger.LogInformation(
// "[MarkerDetection Stats] LaserScans: {LaserCount} (avg {LaserRate:F2} Hz), Marker Poses: {MarkerCount}",
// laserCount, laserRate, markerCount);
}
else
{
// LIDAR data still empty - provide diagnostic info
_logger.LogWarning(
"[MarkerDetection Stats] No laser scan data received. " +
"LIDAR Device ID configured: '{ConfigDeviceId}'. " +
"Please verify LIDAR device is connected and providing data.",
_config.LidarDeviceId);
}
}
private void LogFinalStatistics()
{
var laserCount = Interlocked.Read(ref _laserScanCount);
var markerCount = Interlocked.Read(ref _markerDetectionCount);
_logger.LogInformation(
"[MarkerDetection Final Stats] Total LaserScans: {LaserCount}, Total Marker Poses: {MarkerCount}",
laserCount, markerCount);
}
/// <summary>
/// Enable or disable marker detection
/// </summary>
public void SetEnableDetection(bool enable)
{
lock (_lock)
{
if (!_isInitialized || _markerDetectionClient == null || !_markerDetectionClient.IsInitialized)
{
_logger.LogWarning("Cannot set detection enable/disable: MarkerDetectionIntegrationService not initialized");
return;
}
try
{
_markerDetectionClient.SetEnableDetection(enable);
_logger.LogInformation("Marker detection {Status}", enable ? "enabled" : "disabled");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error setting detection enable/disable: {Message}", ex.Message);
}
}
}
/// <summary>
/// Get current marker pose
/// </summary>
public MarkerDetectionPoseStamped? GetMarkerPose()
{
lock (_lock)
{
if (!_isInitialized || _markerDetectionClient == null || !_markerDetectionClient.IsInitialized)
{
_logger.LogWarning("Cannot get marker pose: MarkerDetectionIntegrationService not initialized");
return null;
}
try
{
return _markerDetectionClient.GetMarkerPose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting marker pose: {Message}", ex.Message);
return null;
}
}
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
try
{
lock (_lock)
{
_isInitialized = false;
}
// Give loop time to stop
Thread.Sleep(500);
_updateTimer?.Dispose();
_markerDetectionClient?.Dispose();
_dispatchSemaphore?.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing MarkerDetectionIntegrationService: {Message}", ex.Message);
}
}
}
/// <summary>
/// Marker detection type
/// </summary>
public enum MarkerDetectionType
{
Rectangle,
Segment
}
/// <summary>
/// Configuration for MarkerDetectionIntegrationService
/// </summary>
public class MarkerDetectionIntegrationConfiguration
{
/// <summary>
/// Enable/disable the integration service
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Type of marker to detect (Rectangle or Segment)
/// </summary>
public MarkerDetectionType MarkerType { get; set; } = MarkerDetectionType.Rectangle;
/// <summary>
/// Rectangle marker detection options
/// Required if MarkerType is Rectangle
/// </summary>
public MarkerDetectionRectangleOptions? RectangleMarkerOptions { get; set; } = null;
/// <summary>
/// Segment marker detection options
/// Required if MarkerType is Segment
/// </summary>
public MarkerDetectionSegmentOptions? SegmentMarkerOptions { get; set; } = null;
/// <summary>
/// LIDAR device ID for laser scan dispatch
/// </summary>
public string LidarDeviceId { get; set; } = "scan_1";
/// <summary>
/// Sensor ID for laser scan
/// </summary>
public string? LaserScanSensorId { get; set; } = "scan_1";
/// <summary>
/// Frame ID for laser scan
/// </summary>
public string? LaserScanFrameId { get; set; } = "scan_1";
/// <summary>
/// Laser scan data dispatch interval in milliseconds (default: 50ms = 20Hz)
/// </summary>
public int LaserScanDispatchIntervalMs { get; set; } = 50;
/// <summary>
/// Enable estimated marker pose for improved stability
/// </summary>
public bool EnableEstimateMarkerPose { get; set; } = false;
/// <summary>
/// Initial estimated marker pose X (meters)
/// </summary>
public double EstimateMarkerPoseX { get; set; } = 0.0;
/// <summary>
/// Initial estimated marker pose Y (meters)
/// </summary>
public double EstimateMarkerPoseY { get; set; } = 0.0;
/// <summary>
/// Initial estimated marker pose theta (radians)
/// </summary>
public double EstimateMarkerPoseTheta { get; set; } = 0.0;
}