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
}