Skip to content

Commit

Permalink
Add auto scheduling of missions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrica-equinor committed Feb 21, 2025
1 parent 6682d97 commit 3b4f419
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 4 deletions.
5 changes: 4 additions & 1 deletion backend/api/Database/Models/MissionRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public MissionStatus Status
[Required]
public IList<MissionTask> Tasks
{
get => _tasks.OrderBy(t => t.TaskOrder).ToList();
get =>
_tasks != null
? _tasks.OrderBy(t => t.TaskOrder).ToList()
: new List<MissionTask>();
set => _tasks = value;
}

Expand Down
206 changes: 206 additions & 0 deletions backend/api/HostedServices/InspectionFrequencyHostedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using System;
using Api.Controllers.Models;
using Api.Database.Models;
using Api.Services;
using Api.Services.MissionLoaders;
using Api.Utilities;
using Hangfire;

namespace Api.HostedServices
{
public class InspectionFrequencyHostedService : IHostedService, IDisposable
{
private readonly ILogger<InspectionFrequencyHostedService> _logger;
private readonly IServiceScopeFactory _scopeFactory;
private Timer? _timer = null;

public InspectionFrequencyHostedService(
ILogger<InspectionFrequencyHostedService> logger,
IServiceScopeFactory scopeFactory
)
{
_logger = logger;
_scopeFactory = scopeFactory;
}

private IMissionDefinitionService MissionDefinitionService =>
_scopeFactory
.CreateScope()
.ServiceProvider.GetRequiredService<IMissionDefinitionService>();

private IMissionSchedulingService MissionSchedulingService =>
_scopeFactory
.CreateScope()
.ServiceProvider.GetRequiredService<IMissionSchedulingService>();

private IRobotService RobotService =>
_scopeFactory.CreateScope().ServiceProvider.GetRequiredService<IRobotService>();

private IMissionLoader MissionLoader =>
_scopeFactory.CreateScope().ServiceProvider.GetRequiredService<IMissionLoader>();

public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Inspection Frequency Hosted Service Running.");

var timeUntilMidnight = (DateTime.UtcNow.AddDays(1) - DateTime.UtcNow).TotalSeconds;
_timer = new Timer(
DoWork,
null,
TimeSpan.FromSeconds(timeUntilMidnight),
TimeSpan.FromDays(1)
);
return Task.CompletedTask;
}

private async void DoWork(object? state)
{
var missionQuery = new MissionDefinitionQueryStringParameters();

List<MissionDefinition>? missionDefinitions;
try
{
missionDefinitions = await MissionDefinitionService.ReadByHasInspectionFrequency();
}
catch (InvalidDataException e)
{
_logger.LogError(e, "{ErrorMessage}", e.Message);
return;
}

if (missionDefinitions == null)
{
_logger.LogInformation("No mission definitions with inspection frequency found.");
return;
}

var selectedMissionDefinitions = missionDefinitions.Where(m =>
m.AutoScheduleFrequency != null
&& m.AutoScheduleFrequency.GetSchedulingTimesForNext24Hours() != null
);

if (selectedMissionDefinitions.Any() == false)
{
_logger.LogInformation(
"No mission definitions with inspection frequency found that are due for inspection today."
);
return;
}

foreach (var missionDefinition in selectedMissionDefinitions)
{
var jobDelays =
missionDefinition.AutoScheduleFrequency!.GetSchedulingTimesForNext24Hours();

if (jobDelays == null)
{
_logger.LogWarning(
"No job schedules found for mission definition {MissionDefinitionId}.",
missionDefinition.Id
);
return;
}

foreach (var jobDelay in jobDelays)
{
_logger.LogInformation(
"Scheduling mission run for mission definition {MissionDefinitionId} in {TimeLeft}.",
missionDefinition.Id,
jobDelay
);
BackgroundJob.Schedule(
() => AutomaticScheduleMissionRun(missionDefinition),
jobDelay
);
}
}
}

public async Task AutomaticScheduleMissionRun(MissionDefinition missionDefinition)
{
_logger.LogInformation(
"Scheduling mission run for mission definition {MissionDefinitionId}.",
missionDefinition.Id
);

if (missionDefinition.InspectionArea == null)
{
_logger.LogWarning(
"Mission definition {MissionDefinitionId} has no inspection area.",
missionDefinition.Id
);
return;
}

IList<Robot> robots;
try
{
robots = await RobotService.ReadRobotsForInstallation(
missionDefinition.InstallationCode
);
}
catch (Exception e)
{
_logger.LogError(e, "{ErrorMessage}", e.Message);
return;
}

if (robots == null)
{
_logger.LogInformation(
"No robots found for installation code {InstallationCode}.",
missionDefinition.InstallationCode
);
return;
}

var robot = robots.FirstOrDefault(r =>
r.CurrentInspectionArea?.Id == missionDefinition.InspectionArea.Id
);
if (robot == null)
{
_logger.LogWarning(
"No robot found for mission definition {MissionDefinitionId} and inspection area {InspectionAreaId}.",
missionDefinition.Id,
missionDefinition.InspectionArea.Id
);
return;
}

_logger.LogInformation(
"Scheduling mission run for mission definition {MissionDefinitionId} and robot {RobotId}.",
missionDefinition.Id,
robot.Id
);

try
{
await MissionSchedulingService.ScheduleMissionRunFromMissionDefinitionLastSuccessfullRun(
missionDefinition.Id,
robot.Id
);
}
catch (Exception e)
{
_logger.LogError(e, "{ErrorMessage}", e.Message);
return;
}

return;
}

public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Inspection Frequency Hosted Service is stopping.");

_timer?.Change(Timeout.Infinite, 0);

return Task.CompletedTask;
}

public void Dispose()
{
_timer?.Dispose();
}
}
}
6 changes: 6 additions & 0 deletions backend/api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using Api.Controllers;
using Api.Controllers.Models;
using Api.EventHandlers;
using Api.HostedServices;
using Api.Mqtt;
using Api.Options;
using Api.Services;
using Api.Services.ActionServices;
using Api.SignalRHubs;
using Api.Utilities;
using Azure.Identity;
using Hangfire;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http.Connections;
Expand Down Expand Up @@ -106,10 +108,14 @@
builder.Services.AddHostedService<MqttService>();
builder.Services.AddHostedService<IsarConnectionEventHandler>();
builder.Services.AddHostedService<TeamsMessageEventHandler>();
builder.Services.AddHostedService<InspectionFrequencyHostedService>();

builder.Services.Configure<AzureAdOptions>(builder.Configuration.GetSection("AzureAd"));
builder.Services.Configure<MapBlobOptions>(builder.Configuration.GetSection("Maps"));

builder.Services.AddHangfire(Configuration => Configuration.UseInMemoryStorage());
builder.Services.AddHangfireServer();

builder
.Services.AddControllers()
.AddJsonOptions(options =>
Expand Down
13 changes: 13 additions & 0 deletions backend/api/Services/MissionDefinitionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public Task<List<MissionDefinition>> ReadByInspectionAreaId(
bool readOnly = true
);

public Task<List<MissionDefinition>?> ReadByHasInspectionFrequency(bool readOnly = true);

public Task<List<MissionTask>?> GetTasksFromSource(Source source);

public Task<List<MissionDefinition>> ReadBySourceId(string sourceId, bool readOnly = true);
Expand Down Expand Up @@ -145,6 +147,17 @@ public async Task<List<MissionDefinition>> ReadBySourceId(
.ToListAsync();
}

public async Task<List<MissionDefinition>?> ReadByHasInspectionFrequency(
bool readOnly = true
)
{
var missions = await GetMissionDefinitionsWithSubModels(readOnly: readOnly)
.Where(m => m.IsDeprecated == false && m.AutoScheduleFrequency != null)
.ToListAsync();

return missions;
}

public async Task<MissionDefinition> UpdateLastSuccessfulMissionRun(
string missionRunId,
string missionDefinitionId
Expand Down
Loading

0 comments on commit 3b4f419

Please sign in to comment.