using MaxwellFramework.Core.Attributes; using MainShell.Alarm; using MainShell.Hardware; using MainShell.Log; using MainShell.Motion.Safety; using MwFramework.Device; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MainShell.Motion { [Singleton] public sealed class MotionAlarmReporter { private readonly AlarmOperate _alarmOperate; public MotionAlarmReporter(AlarmOperate alarmOperate) { _alarmOperate = alarmOperate ?? throw new ArgumentNullException(nameof(alarmOperate)); } public async Task ReportAlarmAsync(int? alarmId) { if (!alarmId.HasValue) { return false; } await _alarmOperate.AlertAsync(alarmId.Value).ConfigureAwait(false); return true; } } [Singleton] public sealed class StagePlatformSafetyOptionsProvider { private const double DefaultMaxPlaneSpread = 5.0d; private const double DefaultMaxTravelPerStep = 10.0d; public StagePlatformSafetyOptions GetOptions() { return new StagePlatformSafetyOptions(DefaultMaxPlaneSpread, DefaultMaxTravelPerStep); } } [Singleton] public sealed class MotionPrecheckService { private readonly MotionAlarmReporter _motionAlarmReporter; private readonly IReadOnlyList _motionSafetyChecks; public MotionPrecheckService(HardwareManager hardware, MotionSafetyStateProvider motionSafetyStateProvider, MotionAlarmReporter motionAlarmReporter, StagePlatformSafetyOptionsProvider stagePlatformSafetyOptionsProvider) { if (hardware == null) { throw new ArgumentNullException(nameof(hardware)); } if (motionSafetyStateProvider == null) { throw new ArgumentNullException(nameof(motionSafetyStateProvider)); } _motionAlarmReporter = motionAlarmReporter ?? throw new ArgumentNullException(nameof(motionAlarmReporter)); var stagePlatformSafetyOptions = (stagePlatformSafetyOptionsProvider ?? throw new ArgumentNullException(nameof(stagePlatformSafetyOptionsProvider))).GetOptions(); _motionSafetyChecks = new IMotionSafetyCheck[] { new MotionRequestDuplicateAxisCheck(), new MotionBatchSameSourceCheck(), new EmergencyStopReleasedCheck(motionSafetyStateProvider), new SafetyDoorClosedCheck(motionSafetyStateProvider), new AutoFlowModeCheck(motionSafetyStateProvider), new StageVacuumReadyCheck(motionSafetyStateProvider), new BondHeadSafePositionCheck(motionSafetyStateProvider), new StagePlatformPlaneMoveSafetyCheck(hardware, stagePlatformSafetyOptions) }; } public async Task ValidateAsync(MotionSafetyContext context, CancellationToken cancellationToken, int? fallbackAlarmId) { cancellationToken.ThrowIfCancellationRequested(); if (context == null) { throw new ArgumentNullException(nameof(context)); } foreach (var check in _motionSafetyChecks) { var result = check.Check(context); if (result == null || result.IsPassed) { continue; } var failureMessage = string.Format("Motion precheck failed. Rule:{0} Kind:{1} Source:{2} Message:{3}", result.RuleName, context.RequestKind, context.Source ?? "N/A", result.Message); failureMessage.LogSysError(); var alarmId = result.AlarmId ?? fallbackAlarmId; if (alarmId.HasValue) { await _motionAlarmReporter.ReportAlarmAsync(alarmId).ConfigureAwait(false); } throw new InvalidOperationException(failureMessage); } } } [Singleton] public sealed class MotionExecutionLogger { public void HandleMotionStarted(object sender, MotionStartedEventArgs e) { try { string.Format("Axis:{0} Motion:{1} Started StartPos:{2:F3} Target:{3}", e.AxisName, e.Operation, e.StartPosition, e.TargetPosition.HasValue ? e.TargetPosition.Value.ToString("F3") : "N/A").LogInfo(); } catch (Exception ex) { string.Format("Axis:{0} motion start log failed. Error:{1}", e.AxisName, ex.Message).LogSysError(); } } public void HandleMotionFinished(object sender, MotionFinishedEventArgs e) { try { var result = e.Result; var message = string.Format("Axis:{0} Motion:{1} Finished Success:{2} Cancelled:{3} EndPos:{4:F3} Duration:{5}ms FailureStage:{6}", result.AxisName, result.Operation, result.Succeeded, result.Cancelled, result.EndPosition, result.DurationMilliseconds, result.FailureStage ?? "N/A"); if (result.Succeeded) { message.LogInfo(); return; } var error = string.Format("{0} Error:{1}", message, result.Exception); error.LogSysError(); } catch (Exception ex) { string.Format("Axis:{0} motion finish log failed. Error:{1}", e.AxisName, ex.Message).LogSysError(); } } } [Singleton] public sealed class MotionControllerRegistry { private readonly Dictionary _controllers = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly object _syncRoot = new object(); private readonly MotionExecutionLogger _motionExecutionLogger; private sealed class ControllerEntry { public ControllerEntry(IAxis axis, MotionController controller) { Axis = axis ?? throw new ArgumentNullException(nameof(axis)); Controller = controller ?? throw new ArgumentNullException(nameof(controller)); } public IAxis Axis { get; } public MotionController Controller { get; } } public MotionControllerRegistry(MotionExecutionLogger motionExecutionLogger) { _motionExecutionLogger = motionExecutionLogger ?? throw new ArgumentNullException(nameof(motionExecutionLogger)); } public MotionController GetController(IAxis axis) { if (axis == null) { throw new ArgumentNullException(nameof(axis)); } var axisName = axis.Name; if (string.IsNullOrWhiteSpace(axisName)) { return CreateController(axis); } lock (_syncRoot) { ControllerEntry entry; if (_controllers.TryGetValue(axisName, out entry) && ReferenceEquals(entry.Axis, axis)) { return entry.Controller; } var controller = CreateController(axis); _controllers[axisName] = new ControllerEntry(axis, controller); return controller; } } private MotionController CreateController(IAxis axis) { var controller = new MotionController(axis); controller.MotionStarted += _motionExecutionLogger.HandleMotionStarted; controller.MotionFinished += _motionExecutionLogger.HandleMotionFinished; return controller; } } }