diff --git a/Ares.Core.Tests/Execution/CampaignExecutorTests.cs b/Ares.Core.Tests/Execution/CampaignExecutorTests.cs index f0f74be2..9e0e6730 100644 --- a/Ares.Core.Tests/Execution/CampaignExecutorTests.cs +++ b/Ares.Core.Tests/Execution/CampaignExecutorTests.cs @@ -58,7 +58,8 @@ public void SetUp() It.IsAny(), It.IsAny>(), It.IsAny>(), - It.IsAny>(), + It.IsAny>(), + It.IsAny(), It.IsAny())) .ReturnsAsync(true); @@ -217,7 +218,8 @@ public async Task Replan_Composes_And_Executes_Experiment_Again() It.IsAny(), It.IsAny>(), It.IsAny>(), - It.IsAny>(), + It.IsAny>(), + It.IsAny(), It.IsAny()), Times.Once); } diff --git a/Ares.Core/Execution/Enums/ExperimentLoopOutcome.cs b/Ares.Core/Execution/Enums/ExperimentLoopOutcome.cs new file mode 100644 index 00000000..33a07c72 --- /dev/null +++ b/Ares.Core/Execution/Enums/ExperimentLoopOutcome.cs @@ -0,0 +1,8 @@ +namespace Ares.Core.Execution.Enums; + +public enum ExperimentLoopOutcome +{ + Succeeded, + Failed, + Canceled +} \ No newline at end of file diff --git a/Ares.Core/Execution/Enums/ExperimentPhase.cs b/Ares.Core/Execution/Enums/ExperimentPhase.cs new file mode 100644 index 00000000..0a2cff41 --- /dev/null +++ b/Ares.Core/Execution/Enums/ExperimentPhase.cs @@ -0,0 +1,15 @@ +namespace Ares.Core.Execution.Enums; + +public enum ExperimentPhase +{ + Initialize, + Plan, + Compose, + Execute, + Analyze, + Retry, + Replan, + Complete, + Failed, + Canceled +} diff --git a/Ares.Core/Execution/Executors/CampaignExecutor.cs b/Ares.Core/Execution/Executors/CampaignExecutor.cs index 1ff90f92..f50b1f03 100644 --- a/Ares.Core/Execution/Executors/CampaignExecutor.cs +++ b/Ares.Core/Execution/Executors/CampaignExecutor.cs @@ -3,6 +3,7 @@ using Ares.Core.Device.State.Logging; using Ares.Core.Exceptions; using Ares.Core.Execution.ControlTokens; +using Ares.Core.Execution.Enums; using Ares.Core.Execution.Executors.Composers; using Ares.Core.Execution.Extensions; using Ares.Core.Execution.Safety; @@ -13,6 +14,7 @@ using Ares.Core.Settings; using Ares.Datamodel; using Ares.Datamodel.Analyzing; +using Ares.Datamodel.Planning; using Ares.Datamodel.Templates; using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.Logging; @@ -22,28 +24,7 @@ namespace Ares.Core.Execution.Executors; public class CampaignExecutor : ICampaignExecutor -{ - private enum ExperimentPhase - { - Initialize, - Plan, - Compose, - Execute, - Analyze, - Retry, - Replan, - Complete, - Failed, - Canceled - } - - private enum ExperimentLoopOutcome - { - Succeeded, - Failed, - Canceled - } - +{ private readonly IExecutionReporter _executionReporter; private readonly ISubject _executionStatusSubject; private readonly ICommandComposer _experimentComposer; @@ -66,6 +47,7 @@ private enum ExperimentLoopOutcome private ExperimentTemplate? _currentExperimentTemplate = null; private int _experimentCount = 0; private TaskCompletionSource? _userDecisionSource; + private PlanStatusCode _latestPlanStatusCode = PlanStatusCode.PlanStatusUnspecified; internal CampaignExecutor(ICommandComposer experimentComposer, IPlanningHelper planningHelper, @@ -227,7 +209,11 @@ private async Task NotifyCampaignStart() return (true, startupSummary); } - private async Task ExecuteExperimentLoop(string campaignPath, List analyses, List experimentSummaries, ExecutionControlToken token, ExperimentExecutionSummary startupSummary) + private async Task ExecuteExperimentLoop(string campaignPath, + List analyses, + List experimentSummaries, + ExecutionControlToken token, + ExperimentExecutionSummary startupSummary) { var currentPhase = ExperimentPhase.Initialize; var currentExperimentPath = ""; @@ -398,9 +384,17 @@ private async Task ExecuteCurrentExperiment(string currentExper .SelectMany(s => s.CommandSummaries) .FirstOrDefault(c => !c.Result.Success); - return failedCommandSummary is null - ? ExperimentPhase.Analyze - : await HandleError(failedCommandSummary, token); + if(failedCommandSummary is null) + { + _latestPlanStatusCode = PlanStatusCode.PlanAccepted; + return ExperimentPhase.Analyze; + } + + else + { + UpdatePlanStatus(failedCommandSummary.StatusCode); + return await HandleError(failedCommandSummary, token); + } } private async Task AnalyzeCurrentExperiment(ExperimentExecutionSummary startupSummary, List analyses, List experimentSummaries, ExecutionControlToken token) @@ -480,6 +474,7 @@ private async Task HandleError(CommandExecutionSummary cmdSumma var decisionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _userDecisionSource = decisionSource; + try { errorHandling = await decisionSource.Task.WaitAsync(token.CancellationToken); @@ -517,6 +512,15 @@ private async Task HandleError(CommandExecutionSummary cmdSumma } } + private void UpdatePlanStatus(CommandStatusCode failCode) + { + if(failCode == CommandStatusCode.OutOfRange || failCode == CommandStatusCode.ParametersUnachievable) + _latestPlanStatusCode = PlanStatusCode.PlanUnachievable; + + else + _latestPlanStatusCode = PlanStatusCode.PlanFailed; + } + private async Task PlanExperiment(List analyses, ExperimentTemplate currentExperimentTemplate, List experimentSummaries, ExecutionControlToken token) { Status.PlannerState = PlannerState.PlanningInProgress; @@ -537,6 +541,7 @@ private async Task PlanExperiment(List analyses, ExperimentTempl currentExperimentTemplate.GetAllPlannedParameters(), analyses, experimentSummaries.Select(es => es.ExperimentOverview), + _latestPlanStatusCode, token.CancellationToken); if(!resolveSuccess) @@ -745,7 +750,8 @@ private async Task GenerateExperimentExecutor(Experime metadata, experimentTemplate.GetAllPlannedParameters(), analyses, - previousExperiments, + previousExperiments, + PlanStatusCode.PlanAccepted, cancellationToken); if(!resolveSuccess) diff --git a/Ares.Core/Execution/Executors/CommandExecutor.cs b/Ares.Core/Execution/Executors/CommandExecutor.cs index aa2ad684..88ef908b 100644 --- a/Ares.Core/Execution/Executors/CommandExecutor.cs +++ b/Ares.Core/Execution/Executors/CommandExecutor.cs @@ -109,7 +109,6 @@ public async Task Execute(ExecutionControlToken token, Status.Result = result.Result; _stateSubject.OnNext(Status); - _stateSubject.OnCompleted(); return ExecutorSummaryHelpers.CreateCommandExecutionSummary(Template, result, timeStarted, DateTime.UtcNow); } diff --git a/Ares.Core/Execution/Executors/ExecutorSummaryHelpers.cs b/Ares.Core/Execution/Executors/ExecutorSummaryHelpers.cs index f2a1f198..7492dcc3 100644 --- a/Ares.Core/Execution/Executors/ExecutorSummaryHelpers.cs +++ b/Ares.Core/Execution/Executors/ExecutorSummaryHelpers.cs @@ -58,7 +58,8 @@ public static CommandExecutionSummary CreateCommandExecutionSummary(CommandTempl CommandDescription = template.Metadata.Description, CommandName = template.Metadata.Name, StatusCode = deviceResult?.StatusCode ?? CommandStatusCode.StatusUnspecified - }; + }; + if(template.HasOutputVarName) commandExecutionSummary.VarName = template.OutputVarName; diff --git a/Ares.Core/Execution/Executors/ExperimentExecutor.cs b/Ares.Core/Execution/Executors/ExperimentExecutor.cs index 08d0dba8..f375ea59 100644 --- a/Ares.Core/Execution/Executors/ExperimentExecutor.cs +++ b/Ares.Core/Execution/Executors/ExperimentExecutor.cs @@ -20,6 +20,7 @@ public ExperimentExecutor(ExperimentTemplate template, IExecutor executor.Status)); + var experimentStepExecutionObservation = experimentStepExecutors.Select(executor => { return executor.ExperimentStatusObservable.Select(_ => diff --git a/Ares.Core/Planning/IPlanningHelper.cs b/Ares.Core/Planning/IPlanningHelper.cs index 7ac1c5e9..74e2d2ff 100644 --- a/Ares.Core/Planning/IPlanningHelper.cs +++ b/Ares.Core/Planning/IPlanningHelper.cs @@ -23,6 +23,7 @@ Task TryResolveParameters(IEnumerable plannerAllocation IEnumerable parameters, IEnumerable seedAnalyses, IEnumerable seedExperiments, + PlanStatusCode code, CancellationToken cancellationToken); /// diff --git a/Ares.Core/Planning/PlanningHelper.cs b/Ares.Core/Planning/PlanningHelper.cs index 1f154544..4c28444c 100644 --- a/Ares.Core/Planning/PlanningHelper.cs +++ b/Ares.Core/Planning/PlanningHelper.cs @@ -34,6 +34,7 @@ public async Task TryResolveParameters(IEnumerable plan IEnumerable parameters, IEnumerable seedAnalyses, IEnumerable seedExperiments, + PlanStatusCode statusCode, CancellationToken cancellationToken) { var parameterArray = parameters.ToArray(); @@ -72,6 +73,7 @@ public async Task TryResolveParameters(IEnumerable plan var planRequest = new PlanningRequest(); planRequest.PlanningParameters.AddRange(plannableParameters.Select(parameter => ConvertToPlanningParameter(parameter, seedExperiments))); planRequest.AnalysisResults.AddRange(seedAnalysesArr.Select(a => (double)a.Result)); + planRequest.PreviousPlanStatusCode = statusCode; planRequest.Metadata = metadata; planTransaction.PlanningRequest = planRequest; diff --git a/UI/Components/Layouts/MainLayout.razor b/UI/Components/Layouts/MainLayout.razor index 5211faa6..a948b8d0 100644 --- a/UI/Components/Layouts/MainLayout.razor +++ b/UI/Components/Layouts/MainLayout.razor @@ -258,6 +258,7 @@ Tracker.OnSystemReady -= HandleSystemReady; notificationService.OnNotificationReceived -= HandleBackgroundNotification; _driverSubscription?.Dispose(); + _cts.Cancel(); _cts.Dispose(); } diff --git a/UI/Features/Execution/ExecutionViewModel.cs b/UI/Features/Execution/ExecutionViewModel.cs index ca594511..6953e041 100644 --- a/UI/Features/Execution/ExecutionViewModel.cs +++ b/UI/Features/Execution/ExecutionViewModel.cs @@ -236,6 +236,7 @@ private async Task RefreshExecutionEligibility() LastExecutionEligibility = await _automationClient.CheckExecutionEligibility(new Empty(), null); } + public async Task ExecutionNotesUploaded(Stream fileStream) { using var reader = new StreamReader(fileStream);