Skip to content

Commit 3b4f419

Browse files
committed
Add auto scheduling of missions
1 parent 6682d97 commit 3b4f419

File tree

6 files changed

+392
-4
lines changed

6 files changed

+392
-4
lines changed

backend/api/Database/Models/MissionRun.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ public MissionStatus Status
5050
[Required]
5151
public IList<MissionTask> Tasks
5252
{
53-
get => _tasks.OrderBy(t => t.TaskOrder).ToList();
53+
get =>
54+
_tasks != null
55+
? _tasks.OrderBy(t => t.TaskOrder).ToList()
56+
: new List<MissionTask>();
5457
set => _tasks = value;
5558
}
5659

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using System;
2+
using Api.Controllers.Models;
3+
using Api.Database.Models;
4+
using Api.Services;
5+
using Api.Services.MissionLoaders;
6+
using Api.Utilities;
7+
using Hangfire;
8+
9+
namespace Api.HostedServices
10+
{
11+
public class InspectionFrequencyHostedService : IHostedService, IDisposable
12+
{
13+
private readonly ILogger<InspectionFrequencyHostedService> _logger;
14+
private readonly IServiceScopeFactory _scopeFactory;
15+
private Timer? _timer = null;
16+
17+
public InspectionFrequencyHostedService(
18+
ILogger<InspectionFrequencyHostedService> logger,
19+
IServiceScopeFactory scopeFactory
20+
)
21+
{
22+
_logger = logger;
23+
_scopeFactory = scopeFactory;
24+
}
25+
26+
private IMissionDefinitionService MissionDefinitionService =>
27+
_scopeFactory
28+
.CreateScope()
29+
.ServiceProvider.GetRequiredService<IMissionDefinitionService>();
30+
31+
private IMissionSchedulingService MissionSchedulingService =>
32+
_scopeFactory
33+
.CreateScope()
34+
.ServiceProvider.GetRequiredService<IMissionSchedulingService>();
35+
36+
private IRobotService RobotService =>
37+
_scopeFactory.CreateScope().ServiceProvider.GetRequiredService<IRobotService>();
38+
39+
private IMissionLoader MissionLoader =>
40+
_scopeFactory.CreateScope().ServiceProvider.GetRequiredService<IMissionLoader>();
41+
42+
public Task StartAsync(CancellationToken stoppingToken)
43+
{
44+
_logger.LogInformation("Inspection Frequency Hosted Service Running.");
45+
46+
var timeUntilMidnight = (DateTime.UtcNow.AddDays(1) - DateTime.UtcNow).TotalSeconds;
47+
_timer = new Timer(
48+
DoWork,
49+
null,
50+
TimeSpan.FromSeconds(timeUntilMidnight),
51+
TimeSpan.FromDays(1)
52+
);
53+
return Task.CompletedTask;
54+
}
55+
56+
private async void DoWork(object? state)
57+
{
58+
var missionQuery = new MissionDefinitionQueryStringParameters();
59+
60+
List<MissionDefinition>? missionDefinitions;
61+
try
62+
{
63+
missionDefinitions = await MissionDefinitionService.ReadByHasInspectionFrequency();
64+
}
65+
catch (InvalidDataException e)
66+
{
67+
_logger.LogError(e, "{ErrorMessage}", e.Message);
68+
return;
69+
}
70+
71+
if (missionDefinitions == null)
72+
{
73+
_logger.LogInformation("No mission definitions with inspection frequency found.");
74+
return;
75+
}
76+
77+
var selectedMissionDefinitions = missionDefinitions.Where(m =>
78+
m.AutoScheduleFrequency != null
79+
&& m.AutoScheduleFrequency.GetSchedulingTimesForNext24Hours() != null
80+
);
81+
82+
if (selectedMissionDefinitions.Any() == false)
83+
{
84+
_logger.LogInformation(
85+
"No mission definitions with inspection frequency found that are due for inspection today."
86+
);
87+
return;
88+
}
89+
90+
foreach (var missionDefinition in selectedMissionDefinitions)
91+
{
92+
var jobDelays =
93+
missionDefinition.AutoScheduleFrequency!.GetSchedulingTimesForNext24Hours();
94+
95+
if (jobDelays == null)
96+
{
97+
_logger.LogWarning(
98+
"No job schedules found for mission definition {MissionDefinitionId}.",
99+
missionDefinition.Id
100+
);
101+
return;
102+
}
103+
104+
foreach (var jobDelay in jobDelays)
105+
{
106+
_logger.LogInformation(
107+
"Scheduling mission run for mission definition {MissionDefinitionId} in {TimeLeft}.",
108+
missionDefinition.Id,
109+
jobDelay
110+
);
111+
BackgroundJob.Schedule(
112+
() => AutomaticScheduleMissionRun(missionDefinition),
113+
jobDelay
114+
);
115+
}
116+
}
117+
}
118+
119+
public async Task AutomaticScheduleMissionRun(MissionDefinition missionDefinition)
120+
{
121+
_logger.LogInformation(
122+
"Scheduling mission run for mission definition {MissionDefinitionId}.",
123+
missionDefinition.Id
124+
);
125+
126+
if (missionDefinition.InspectionArea == null)
127+
{
128+
_logger.LogWarning(
129+
"Mission definition {MissionDefinitionId} has no inspection area.",
130+
missionDefinition.Id
131+
);
132+
return;
133+
}
134+
135+
IList<Robot> robots;
136+
try
137+
{
138+
robots = await RobotService.ReadRobotsForInstallation(
139+
missionDefinition.InstallationCode
140+
);
141+
}
142+
catch (Exception e)
143+
{
144+
_logger.LogError(e, "{ErrorMessage}", e.Message);
145+
return;
146+
}
147+
148+
if (robots == null)
149+
{
150+
_logger.LogInformation(
151+
"No robots found for installation code {InstallationCode}.",
152+
missionDefinition.InstallationCode
153+
);
154+
return;
155+
}
156+
157+
var robot = robots.FirstOrDefault(r =>
158+
r.CurrentInspectionArea?.Id == missionDefinition.InspectionArea.Id
159+
);
160+
if (robot == null)
161+
{
162+
_logger.LogWarning(
163+
"No robot found for mission definition {MissionDefinitionId} and inspection area {InspectionAreaId}.",
164+
missionDefinition.Id,
165+
missionDefinition.InspectionArea.Id
166+
);
167+
return;
168+
}
169+
170+
_logger.LogInformation(
171+
"Scheduling mission run for mission definition {MissionDefinitionId} and robot {RobotId}.",
172+
missionDefinition.Id,
173+
robot.Id
174+
);
175+
176+
try
177+
{
178+
await MissionSchedulingService.ScheduleMissionRunFromMissionDefinitionLastSuccessfullRun(
179+
missionDefinition.Id,
180+
robot.Id
181+
);
182+
}
183+
catch (Exception e)
184+
{
185+
_logger.LogError(e, "{ErrorMessage}", e.Message);
186+
return;
187+
}
188+
189+
return;
190+
}
191+
192+
public Task StopAsync(CancellationToken stoppingToken)
193+
{
194+
_logger.LogInformation("Inspection Frequency Hosted Service is stopping.");
195+
196+
_timer?.Change(Timeout.Infinite, 0);
197+
198+
return Task.CompletedTask;
199+
}
200+
201+
public void Dispose()
202+
{
203+
_timer?.Dispose();
204+
}
205+
}
206+
}

backend/api/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
using Api.Controllers;
44
using Api.Controllers.Models;
55
using Api.EventHandlers;
6+
using Api.HostedServices;
67
using Api.Mqtt;
78
using Api.Options;
89
using Api.Services;
910
using Api.Services.ActionServices;
1011
using Api.SignalRHubs;
1112
using Api.Utilities;
1213
using Azure.Identity;
14+
using Hangfire;
1315
using Microsoft.ApplicationInsights.Extensibility.Implementation;
1416
using Microsoft.AspNetCore.Authentication.JwtBearer;
1517
using Microsoft.AspNetCore.Http.Connections;
@@ -106,10 +108,14 @@
106108
builder.Services.AddHostedService<MqttService>();
107109
builder.Services.AddHostedService<IsarConnectionEventHandler>();
108110
builder.Services.AddHostedService<TeamsMessageEventHandler>();
111+
builder.Services.AddHostedService<InspectionFrequencyHostedService>();
109112

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

116+
builder.Services.AddHangfire(Configuration => Configuration.UseInMemoryStorage());
117+
builder.Services.AddHangfireServer();
118+
113119
builder
114120
.Services.AddControllers()
115121
.AddJsonOptions(options =>

backend/api/Services/MissionDefinitionService.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public Task<List<MissionDefinition>> ReadByInspectionAreaId(
2626
bool readOnly = true
2727
);
2828

29+
public Task<List<MissionDefinition>?> ReadByHasInspectionFrequency(bool readOnly = true);
30+
2931
public Task<List<MissionTask>?> GetTasksFromSource(Source source);
3032

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

150+
public async Task<List<MissionDefinition>?> ReadByHasInspectionFrequency(
151+
bool readOnly = true
152+
)
153+
{
154+
var missions = await GetMissionDefinitionsWithSubModels(readOnly: readOnly)
155+
.Where(m => m.IsDeprecated == false && m.AutoScheduleFrequency != null)
156+
.ToListAsync();
157+
158+
return missions;
159+
}
160+
148161
public async Task<MissionDefinition> UpdateLastSuccessfulMissionRun(
149162
string missionRunId,
150163
string missionDefinitionId

0 commit comments

Comments
 (0)