using RobotNet10.NavigationTune.Shared.Models; namespace RobotNet10.NavigationTune.Services; /// /// Safety monitor for test execution /// public class SafetyMonitor(SafetyConfig config) { private readonly List _violations = new(); private DateTime? _trackingErrorStart; /// /// Check safety conditions /// public bool CheckSafety(TelemetryData telemetry, ReferencePath referencePath) { bool isSafe = true; // 1. Check cross-track error if (telemetry.CrossTrackError > config.MaxCrossTrackError) { LogViolation(new SafetyViolation { Type = ViolationType.CrossTrackError, Severity = ViolationSeverity.Critical, Value = telemetry.CrossTrackError, Threshold = config.MaxCrossTrackError, Message = $"CTE {telemetry.CrossTrackError:F3}m exceeds limit {config.MaxCrossTrackError:F3}m", Timestamp = DateTime.UtcNow }); isSafe = false; } // 2. Check heading error if (Math.Abs(telemetry.HeadingError) > config.MaxHeadingError) { LogViolation(new SafetyViolation { Type = ViolationType.HeadingError, Severity = ViolationSeverity.Critical, Value = Math.Abs(telemetry.HeadingError), Threshold = config.MaxHeadingError, Message = $"Heading error {telemetry.HeadingError * 180 / Math.PI:F1}° exceeds limit", Timestamp = DateTime.UtcNow }); isSafe = false; } // 3. Check velocity limits if (Math.Abs(telemetry.RobotTwist.Linear) > config.MaxLinearVelocity * 1.1) { LogViolation(new SafetyViolation { Type = ViolationType.VelocityLimit, Severity = ViolationSeverity.Warning, Value = Math.Abs(telemetry.RobotTwist.Linear), Threshold = config.MaxLinearVelocity, Message = $"Linear velocity {telemetry.RobotTwist.Linear:F2} m/s exceeds limit", Timestamp = DateTime.UtcNow }); } // 4. Check sustained tracking error if (telemetry.CrossTrackError > config.MaxCrossTrackError * 0.5) { _trackingErrorStart ??= DateTime.UtcNow; var duration = (DateTime.UtcNow - _trackingErrorStart.Value).TotalMilliseconds; if (duration > config.MaxTrackingErrorDuration) { LogViolation(new SafetyViolation { Type = ViolationType.SustainedTrackingError, Severity = ViolationSeverity.Critical, Value = duration, Threshold = config.MaxTrackingErrorDuration, Message = $"Tracking error sustained for {duration:F0}ms", Timestamp = DateTime.UtcNow }); isSafe = false; } } else { _trackingErrorStart = null; } return isSafe; } public List GetViolations() => _violations; public void Reset() { _violations.Clear(); _trackingErrorStart = null; } private void LogViolation(SafetyViolation violation) { _violations.Add(violation); } } /// /// Safety configuration /// public class SafetyConfig { public double MaxCrossTrackError { get; set; } = 0.5; // meters public double MaxHeadingError { get; set; } = 45f * Math.PI / 180f; // radians (45 degrees) public double MaxLinearVelocity { get; set; } = 1.5; // m/s public double MaxAngularVelocity { get; set; } = 6.0; // rad/s public int MaxTrackingErrorDuration { get; set; } = 3000; // milliseconds }