-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a0a603f
commit 8d81105
Showing
14 changed files
with
536 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
using System; | ||
using System.ComponentModel.DataAnnotations; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
#pragma warning disable CS8618 | ||
namespace Api.Database.Models | ||
{ | ||
[Owned] | ||
public class AutoScheduleFrequency | ||
{ | ||
[Required] | ||
// In local time | ||
public IList<TimeOnly> TimesOfDay { get; set; } | ||
|
||
[Required] | ||
public IList<DayOfWeek> DaysOfWeek { get; set; } | ||
|
||
public void ValidateAutoScheduleFrequency() | ||
{ | ||
if (TimesOfDay.Count == 0) | ||
{ | ||
throw new ArgumentException( | ||
"AutoScheduleFrequency must have at least one time of day" | ||
); | ||
} | ||
|
||
if (DaysOfWeek.Count == 0) | ||
{ | ||
throw new ArgumentException( | ||
"AutoScheduleFrequency must have at least one day of week" | ||
); | ||
} | ||
} | ||
|
||
// | ||
public IList<TimeSpan>? GetSchedulingTimesForNext24Hours() | ||
{ | ||
// NCS is always in CET | ||
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById( | ||
"Central European Standard Time" | ||
); | ||
DateTime nowLocal = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi); | ||
TimeOnly nowLocalTimeOnly = new TimeOnly( | ||
nowLocal.Hour, | ||
nowLocal.Minute, | ||
nowLocal.Second | ||
); | ||
|
||
var autoScheduleNext24Hours = new List<TimeSpan>(); | ||
if (DaysOfWeek.Contains(nowLocal.DayOfWeek)) | ||
{ | ||
foreach (TimeOnly time in TimesOfDay) | ||
{ | ||
if (time > nowLocalTimeOnly) | ||
{ | ||
autoScheduleNext24Hours.Add((time - nowLocalTimeOnly)); | ||
} | ||
} | ||
} | ||
if (DaysOfWeek.Contains(nowLocal.DayOfWeek + 1)) | ||
{ | ||
foreach (TimeOnly time in TimesOfDay) | ||
{ | ||
if (time < nowLocalTimeOnly) | ||
{ | ||
autoScheduleNext24Hours.Add((time - nowLocalTimeOnly)); | ||
} | ||
} | ||
} | ||
if (autoScheduleNext24Hours.Count > 0) | ||
{ | ||
return autoScheduleNext24Hours; | ||
} | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
207 changes: 207 additions & 0 deletions
207
backend/api/HostedServices/InspectionFrequencyHostedService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
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(10), | ||
TimeSpan.FromMinutes(5) | ||
); | ||
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; | ||
} | ||
|
||
_logger.LogInformation("Mission definitions with inspection frequency found."); | ||
|
||
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.LogError( | ||
"No job delays 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.LogError( | ||
"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.LogError( | ||
"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.ScheduleMissionRunFromMissionDefinition( | ||
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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.